This is an automated email from the ASF dual-hosted git repository.

zhic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tvm.git


The following commit(s) were added to refs/heads/master by this push:
     new 8acc413  [Relay][Frontend][ONNX] Support auto_pad in Conv and 
ConvTranspose (#4563)
8acc413 is described below

commit 8acc413cf820d25b4039d8ae7bd455607ee1bae8
Author: Neo Chien <[email protected]>
AuthorDate: Mon Dec 23 01:03:39 2019 +0800

    [Relay][Frontend][ONNX] Support auto_pad in Conv and ConvTranspose (#4563)
---
 python/tvm/relay/frontend/onnx.py          | 73 ++++++++++++++++++++++---
 tests/python/frontend/onnx/test_forward.py | 88 +++++++++++++++++++++++++++++-
 2 files changed, 150 insertions(+), 11 deletions(-)

diff --git a/python/tvm/relay/frontend/onnx.py 
b/python/tvm/relay/frontend/onnx.py
index 2dec7e2..52211f8 100644
--- a/python/tvm/relay/frontend/onnx.py
+++ b/python/tvm/relay/frontend/onnx.py
@@ -66,6 +66,17 @@ def revert_caffe2_pad(pads):
     return pads
 
 
+def get_pad_pair(input1d, kernel1d, stride1d):
+    """infer pad size"""
+    if input1d % stride1d == 0:
+        pad = max(kernel1d - stride1d, 0)
+    else:
+        pad = max(kernel1d - (input1d % stride1d), 0)
+    pad_before = pad // 2
+    pad_after = pad - pad_before
+    return [pad_before, pad_after]
+
+
 def onnx_storage_order2layout(storage_order):
     """converter of onnx storage order parameter to tvm storage order format"""
     if storage_order not in (0, 1):
@@ -202,14 +213,37 @@ class Conv(OnnxOpConverter):
 
     @classmethod
     def _impl_v1(cls, inputs, attr, params):
-        out = AttrCvt(op_name=dimension_picker('conv'),
-                      transforms={
-                          'kernel_shape': 'kernel_size',
-                          'dilations': ('dilation', (0, 0)),
-                          'pads': ('padding', (0, 0), revert_caffe2_pad),
-                          'group': ('groups', 1)},
-                      ignores=['auto_pad'],
-                      custom_check=dimension_constraint())(inputs[:2], attr, 
params)
+        # infer pads for auto_pad
+        if 'auto_pad' in attr:
+            attr['auto_pad'] = attr['auto_pad'].decode('utf-8')
+            if attr['auto_pad'] in ('SAME_UPPER', 'SAME_LOWER'):
+                input_shape = infer_shape(inputs[0])
+                in_h, in_w = input_shape[2], input_shape[3]
+                stride_h, stride_w = attr['strides']
+                kernel_h, kernel_w = attr['kernel_shape']
+                dilation_h, dilation_w = attr['dilations']
+                dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
+                dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
+                pad_v = get_pad_pair(in_h, dilated_kernel_h, stride_h)
+                pad_h = get_pad_pair(in_w, dilated_kernel_w, stride_w)
+                attr['pads'] = (pad_v[0], pad_h[0], pad_v[1], pad_h[1])
+            elif attr['auto_pad'] == 'VALID':
+                attr['pads'] = (0, 0)
+            elif attr['auto_pad'] == 'NOTSET':
+                pass
+            else:
+                msg = 'Value {} in attribute "auto_pad" of operator Conv is 
invalid.'
+                raise 
tvm.error.OpAttributeInvalid(msg.format(attr['auto_pad']))
+            attr.pop('auto_pad')
+
+        out = AttrCvt(
+            op_name=dimension_picker('conv'),
+            transforms={
+                'kernel_shape': 'kernel_size',
+                'dilations': ('dilation', (0, 0)),
+                'pads': ('padding', (0, 0), revert_caffe2_pad),
+                'group': ('groups', 1)},
+            custom_check=dimension_constraint())(inputs[:2], attr, params)
         use_bias = len(inputs) == 3
         if use_bias:
             out = _op.nn.bias_add(out, inputs[2])
@@ -226,6 +260,29 @@ class ConvTranspose(OnnxOpConverter):
         attr['channels'] = channels
         groups = attr.pop('group')
         attr['groups'] = groups
+        # infer pads for auto_pad
+        if 'auto_pad' in attr:
+            attr['auto_pad'] = attr['auto_pad'].decode('utf-8')
+            if attr['auto_pad'] in ('SAME_UPPER', 'SAME_LOWER'):
+                input_shape = infer_shape(inputs[0])
+                in_h, in_w = input_shape[2], input_shape[3]
+                stride_h, stride_w = attr['strides']
+                kernel_h, kernel_w = attr['kernel_shape']
+                dilation_h, dilation_w = attr['dilations']
+                dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
+                dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
+                pad_v = get_pad_pair(in_h, dilated_kernel_h, stride_h)
+                pad_h = get_pad_pair(in_w, dilated_kernel_w, stride_w)
+                attr['pads'] = (pad_v[0], pad_h[0], pad_v[1], pad_h[1])
+            elif attr['auto_pad'] == 'VALID':
+                attr['pads'] = (0, 0)
+            elif attr['auto_pad'] == 'NOTSET':
+                pass
+            else:
+                msg = 'Value {} in attribute "auto_pad" of operator Conv is 
invalid.'
+                raise 
tvm.error.OpAttributeInvalid(msg.format(attr['auto_pad']))
+            attr.pop('auto_pad')
+
         out = AttrCvt(
             op_name=dimension_picker('conv', '_transpose'),
             transforms={
diff --git a/tests/python/frontend/onnx/test_forward.py 
b/tests/python/frontend/onnx/test_forward.py
index cdd2df4..12dee0f 100644
--- a/tests/python/frontend/onnx/test_forward.py
+++ b/tests/python/frontend/onnx/test_forward.py
@@ -77,11 +77,14 @@ def get_tvm_output(graph_def, input_data, target, ctx, 
output_shape=None, output
         return tvm_output.asnumpy()
 
 
-def get_onnxruntime_output(model, x, dtype='float32'):
+def get_onnxruntime_output(model, inputs, dtype='float32'):
     import onnxruntime.backend
     rep = onnxruntime.backend.prepare(model, 'CPU')
-    x = x.astype(dtype)
-    ort_out = rep.run(x)[0]
+    if isinstance(inputs, list) and len(inputs) > 1:
+        ort_out = rep.run(inputs)
+    else:
+        x = inputs.astype(dtype)
+        ort_out = rep.run(x)[0]
     return ort_out
 
 
@@ -1746,6 +1749,83 @@ def test_or():
     verify_or(indata=[x, y], dtype=bool)
 
 
+def verify_conv(x_shape, w_shape, y_shape, p):
+    node = helper.make_node('Conv',
+                            inputs=['x', 'W'],
+                            outputs=['y'],
+                            kernel_shape=[3, 3],
+                            # Default values for other attributes:
+                            # strides=[1, 1],
+                            # dilations=[1, 1],
+                            # groups=1
+                            pads=p,)
+
+    graph = helper.make_graph([node],
+                              'conv_test',
+                              inputs=[helper.make_tensor_value_info("x", 
TensorProto.FLOAT, list(x_shape)),
+                                      helper.make_tensor_value_info("W", 
TensorProto.FLOAT, list(w_shape))],
+                              outputs=[helper.make_tensor_value_info("y", 
TensorProto.FLOAT, list(y_shape))])
+
+    model = helper.make_model(graph, producer_name='conv_test')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=x_shape).astype('float32')
+        W = np.random.uniform(size=w_shape).astype('float32')
+        tvm_out = get_tvm_output(model, [x, W], target, ctx, y_shape)
+        onnx_out = get_onnxruntime_output(model, [x, W], 'float32')[0]
+        tvm.testing.assert_allclose(onnx_out, tvm_out, rtol=1e-5, atol=1e-5)
+
+
+def test_conv():
+    # Convolution with padding
+    # (1, 1, 5, 5) input tensor
+    # (1, 1, 3, 3) tensor for convolution weights
+    # (1, 1, 5, 5) output tensor
+    # [1, 1, 1, 1] list for pads
+    verify_conv((1, 1, 5, 5), (1, 1, 3, 3), (1, 1, 5, 5), [1, 1, 1, 1])
+
+    # Convolution without padding
+    # (1, 1, 5, 5) input tensor
+    # (1, 1, 3, 3) tensor for convolution weights
+    # (1, 1, 3, 3) output tensor
+    # [0, 0, 0, 0] list for pads
+    verify_conv((1, 1, 5, 5), (1, 1, 3, 3), (1, 1, 3, 3), [0, 0, 0, 0])
+
+
+def verify_convtranspose(x_shape, w_shape, y_shape, p):
+    node = onnx.helper.make_node("ConvTranspose",
+                                 inputs=["x", "W"],
+                                 outputs=['y'],
+                                 strides=[3, 2],
+                                 group=1,
+                                 kernel_shape=[3, 3],
+                                 pads=p)
+
+    graph = helper.make_graph([node],
+                              'verify_convtranspose_test',
+                              inputs=[helper.make_tensor_value_info("x", 
TensorProto.FLOAT, list(x_shape)),
+                                      helper.make_tensor_value_info("W", 
TensorProto.FLOAT, list(w_shape))],
+                              outputs=[helper.make_tensor_value_info("y", 
TensorProto.FLOAT, list(y_shape))])
+
+    model = helper.make_model(graph, producer_name='convtranspose_trest')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=x_shape).astype('float32')
+        W = np.random.uniform(size=w_shape).astype('float32')
+        tvm_out = get_tvm_output(model, [x, W], target, ctx, y_shape)
+        onnx_out = get_onnxruntime_output(model, [x, W], 'float32')[0]
+        tvm.testing.assert_allclose(onnx_out, tvm_out, rtol=1e-5, atol=1e-5)
+
+
+def test_convtranspose():
+    # Convolution Transpose with padding
+    # (1, 1, 3, 3) input tensor
+    # (1, 2, 3, 3) tensor for convolution weights
+    # (1, 2, 7, 3) output tensor
+    # [1, 2, 1, 2] list for pads
+    verify_convtranspose((1, 1, 3, 3), (1, 2, 3, 3), (1, 2, 7, 3), [1, 2, 1, 
2])
+
+
 if __name__ == '__main__':
     test_flatten()
     test_reshape()
@@ -1800,3 +1880,5 @@ if __name__ == '__main__':
     test_or()
     test_depth_to_space()
     test_space_to_depth()
+    test_conv()
+    test_convtranspose()

Reply via email to