Skip to content
This repository was archived by the owner on May 29, 2023. It is now read-only.

Commit 18edc45

Browse files
authored
add deformable convolution (#30)
1 parent 486ae12 commit 18edc45

File tree

6 files changed

+209
-28
lines changed

6 files changed

+209
-28
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ jobs:
149149
run: |
150150
sudo apt-get install -y python3-setuptools libopencv-dev
151151
python3 -m pip install --upgrade pip
152-
python3 -m pip install torch==${{ matrix.torch-version }}
152+
python3 -m pip install torch==${{ matrix.torch-version }} torchvision
153153
python3 -m pip install -U protobuf
154154
python3 -m pip install openvino-dev[onnx]==${{env.OPENVINO_VERSION}}
155155

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Repository with guides to enable some layers from PyTorch in Intel OpenVINO:
55
* [nn.MaxUnpool2d](examples/unpool)
66
* [torch.fft](examples/fft)
77
* [nn.functional.grid_sample](https://github.com/dkurt/openvino_pytorch_layers/tree/master/examples/grid_sample)
8+
* [torchvision.ops.DeformConv2d](examples/deformable_conv)
89

910

1011
## OpenVINO Model Optimizer extension
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import torch
2+
import torch.nn as nn
3+
import torchvision.ops as ops
4+
5+
6+
class DeformableConvFunc(torch.autograd.Function):
7+
@staticmethod
8+
def symbolic(g, cls, x, offset):
9+
weight = cls.state_dict()["weight"]
10+
weight = g.op("Constant", value_t=weight)
11+
12+
return g.op(
13+
"DeformableConv2D",
14+
x,
15+
offset,
16+
weight,
17+
strides_i=(cls.stride, cls.stride),
18+
pads_i=(cls.padding, cls.padding, cls.padding, cls.padding),
19+
dilations_i=(cls.dilation, cls.dilation),
20+
deformable_group_i=cls.groups,
21+
)
22+
23+
@staticmethod
24+
def forward(self, cls, x, offset):
25+
y = cls.origin_forward(x, offset)
26+
return y
27+
28+
29+
class DeformableConvolution(ops.DeformConv2d):
30+
"""
31+
This is a support class which helps export network with SparseConv in ONNX format.
32+
"""
33+
34+
def __init__(self, *args, **kwargs):
35+
super().__init__(*args, **kwargs)
36+
self.origin_forward = super().forward
37+
self.stride = kwargs.get("stride", 1)
38+
self.padding = kwargs.get("padding", 0)
39+
self.dilation = kwargs.get("dilation", 1)
40+
self.groups = kwargs.get("groups", 1)
41+
self.pad_l = nn.ConstantPad2d((1, 1, 1, 1), 0)
42+
43+
def forward(self, x, offset):
44+
"""
45+
Using paddings is a workaround for 2021.4 release.
46+
"""
47+
x = self.pad_l(x)
48+
offset = self.pad_l(offset)
49+
y = DeformableConvFunc.apply(self, x, offset)
50+
y = y[:, :, 1:-1, 1:-1]
51+
return y
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import numpy as np
2+
import argparse
3+
import torch
4+
import torch.nn as nn
5+
from torch.autograd import Variable
6+
from .deformable_conv import DeformableConvolution
7+
8+
np.random.seed(324)
9+
torch.manual_seed(32)
10+
11+
12+
class MyModel(nn.Module):
13+
def __init__(
14+
self,
15+
inplanes,
16+
outplanes,
17+
kernel_size=3,
18+
stride=1,
19+
padding=1,
20+
dilation=1,
21+
bias=False,
22+
deformable_groups=1,
23+
):
24+
super(MyModel, self).__init__()
25+
self.def_conv = DeformableConvolution(
26+
inplanes,
27+
outplanes,
28+
kernel_size=kernel_size,
29+
stride=stride,
30+
padding=padding,
31+
dilation=dilation,
32+
bias=bias,
33+
groups=deformable_groups,
34+
)
35+
36+
def forward(self, x, offset):
37+
y = self.def_conv(x, offset)
38+
return y
39+
40+
41+
def export(
42+
inplanes,
43+
outplanes,
44+
kernel_size,
45+
stride,
46+
padding,
47+
dilation,
48+
deformable_groups,
49+
inp_shape,
50+
offset_shape,
51+
):
52+
np.random.seed(324)
53+
torch.manual_seed(32)
54+
55+
model = MyModel(
56+
inplanes,
57+
outplanes,
58+
kernel_size=kernel_size,
59+
stride=stride,
60+
padding=padding,
61+
dilation=dilation,
62+
deformable_groups=deformable_groups,
63+
)
64+
model.eval()
65+
66+
x = Variable(torch.randn(inp_shape))
67+
offset = Variable(torch.randn(offset_shape))
68+
ref = model(x, offset)
69+
70+
np.save("inp", x.detach().numpy())
71+
np.save("inp1", offset.detach().numpy())
72+
np.save("ref", ref.detach().numpy())
73+
74+
with torch.no_grad():
75+
torch.onnx.export(
76+
model,
77+
(x, offset),
78+
"model.onnx",
79+
input_names=["input", "input1"],
80+
output_names=["output"],
81+
operator_export_type=torch.onnx.OperatorExportTypes.ONNX_FALLTHROUGH,
82+
opset_version=12,
83+
)
84+
85+
86+
if __name__ == "__main__":
87+
parser = argparse.ArgumentParser(description="Generate ONNX model and test data")
88+
parser.add_argument("--inp_shape", type=int, nargs="+", default=[1, 15, 128, 240])
89+
parser.add_argument(
90+
"--offset_shape", type=int, nargs="+", default=[1, 18, 128, 240]
91+
)
92+
parser.add_argument("--inplanes", type=int, nargs="+", default=15)
93+
parser.add_argument("--outplanes", type=int, nargs="+", default=15)
94+
parser.add_argument("--kernel_size", type=int, nargs="+", default=3)
95+
parser.add_argument("--stride", type=int, nargs="+", default=1)
96+
parser.add_argument("--padding", type=int, nargs="+", default=1)
97+
parser.add_argument("--dilation", type=int, nargs="+", default=1)
98+
parser.add_argument("--deformable_groups", type=int, nargs="+", default=1)
99+
args = parser.parse_args()
100+
101+
export(
102+
args.inplanes,
103+
args.outplanes,
104+
args.kernel_size,
105+
args.stride,
106+
args.padding,
107+
args.dilation,
108+
args.deformable_groups,
109+
args.inp_shape,
110+
args.offset_shape,
111+
)

mo_extensions/ops/GridSample.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import numpy as np
21
from mo.graph.graph import Node, Graph
32
from mo.ops.op import Op
43

run_tests.py

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99

1010
import numpy as np
1111

12+
1213
class TestLayers(unittest.TestCase):
1314
def convert_model(self):
14-
subprocess.run([sys.executable,
15-
'-m',
16-
'mo',
17-
'--input_model=model.onnx',
18-
'--extension', Path(__file__).absolute().parent / 'mo_extensions'],
19-
check=True)
15+
subprocess.run(
16+
[
17+
sys.executable,
18+
"-m",
19+
"mo",
20+
"--input_model=model.onnx",
21+
"--extension",
22+
Path(__file__).absolute().parent / "mo_extensions",
23+
],
24+
check=True,
25+
)
2026

2127
def run_test(self, convert_ir=True, test_onnx=False, num_inputs=1, threshold=1e-5):
2228
if convert_ir and not test_onnx:
@@ -25,67 +31,63 @@ def run_test(self, convert_ir=True, test_onnx=False, num_inputs=1, threshold=1e-
2531
inputs = {}
2632
shapes = {}
2733
for i in range(num_inputs):
28-
suffix = '{}'.format(i if i > 0 else '')
29-
data = np.load('inp' + suffix + '.npy')
30-
inputs['input' + suffix] = data
31-
shapes['input' + suffix] = data.shape
34+
suffix = "{}".format(i if i > 0 else "")
35+
data = np.load("inp" + suffix + ".npy")
36+
inputs["input" + suffix] = data
37+
shapes["input" + suffix] = data.shape
3238

33-
ref = np.load('ref.npy')
39+
ref = np.load("ref.npy")
3440

3541
ie = IECore()
36-
ie.add_extension(get_extensions_path(), 'CPU')
37-
ie.set_config({'CONFIG_FILE': 'user_ie_extensions/gpu_extensions.xml'}, 'GPU')
42+
ie.add_extension(get_extensions_path(), "CPU")
43+
ie.set_config({"CONFIG_FILE": "user_ie_extensions/gpu_extensions.xml"}, "GPU")
3844

39-
net = ie.read_network('model.onnx' if test_onnx else 'model.xml')
45+
net = ie.read_network("model.onnx" if test_onnx else "model.xml")
4046
net.reshape(shapes)
41-
exec_net = ie.load_network(net, 'CPU')
47+
exec_net = ie.load_network(net, "CPU")
4248

4349
out = exec_net.infer(inputs)
4450
out = next(iter(out.values()))
4551

4652
diff = np.max(np.abs(ref - out))
4753
self.assertLessEqual(diff, threshold)
4854

49-
5055
def test_unpool(self):
5156
from examples.unpool.export_model import export
52-
export(mode='default')
53-
self.run_test()
5457

58+
export(mode="default")
59+
self.run_test()
5560

5661
def test_unpool_reshape(self):
5762
from examples.unpool.export_model import export
58-
export(mode='dynamic_size', shape=[5, 3, 6, 9])
63+
64+
export(mode="dynamic_size", shape=[5, 3, 6, 9])
5965
self.run_test()
6066

61-
export(mode='dynamic_size', shape=[4, 3, 17, 8])
67+
export(mode="dynamic_size", shape=[4, 3, 17, 8])
6268
self.run_test(convert_ir=False)
6369

64-
6570
def test_fft(self):
6671
from examples.fft.export_model import export
6772

6873
for shape in [[5, 120, 2], [4, 240, 320, 2], [3, 5, 240, 320, 2]]:
6974
export(shape=shape)
7075
self.run_test()
7176

72-
7377
def test_fft_roll(self):
7478
from examples.fft.export_model_with_roll import export
7579

7680
export()
7781
self.run_test()
7882
self.run_test(test_onnx=True)
7983

80-
8184
def test_grid_sample(self):
8285
from examples.grid_sample.export_model import export
8386

8487
export()
8588
self.run_test(num_inputs=2)
8689
self.run_test(num_inputs=2, test_onnx=True)
8790

88-
8991
def test_complex_mul(self):
9092
from examples.complex_mul.export_model import export
9193

@@ -94,6 +96,23 @@ def test_complex_mul(self):
9496
self.run_test(num_inputs=2)
9597
self.run_test(num_inputs=2, test_onnx=True)
9698

97-
98-
if __name__ == '__main__':
99+
def test_deformable_conv(self):
100+
from examples.deformable_conv.export_model import export
101+
102+
export(
103+
inplanes=15,
104+
outplanes=15,
105+
kernel_size=3,
106+
stride=1,
107+
padding=1,
108+
dilation=1,
109+
deformable_groups=1,
110+
inp_shape=[1, 15, 128, 240],
111+
offset_shape=[1, 18, 128, 240],
112+
)
113+
self.run_test(num_inputs=2, threshold=2e-5)
114+
self.run_test(num_inputs=2, test_onnx=True, threshold=2e-5)
115+
116+
117+
if __name__ == "__main__":
99118
unittest.main()

0 commit comments

Comments
 (0)