From c83bb2d363e7e285d2e1c05f0b913fb5bfc854c5 Mon Sep 17 00:00:00 2001 From: zha0q1 Date: Wed, 3 Feb 2021 02:26:53 +0000 Subject: [PATCH 1/7] max and lp --- .../contrib/onnx/mx2onnx/_op_translations.py | 63 +++++++++++++- tests/python-pytest/onnx/test_operators.py | 85 ++++++++++++++++++- 2 files changed, 142 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index cf8ff2b783aa..93a5aacdb528 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -679,12 +679,67 @@ def convert_linalg_gemm2(node, **kwargs): return [trans_a_node, trans_b_node, matmul_node] -@mx_op.register("Pooling") +@mx_op.register('Pooling') def convert_pooling(node, **kwargs): """Map MXNet's Pooling operator attributes to onnx's MaxPool/AveragePool/GlobalMaxPool/GlobalAveragePool operators - based on the input node's attributes and return the created node. """ + from onnx.helper import make_node + name, input_nodes, attrs = get_inputs(node, kwargs) + + kernel = convert_string_to_list(attrs.get('kernel', '()')) + pool_type = attrs.get('pool_type', 'max') + global_pool = attrs.get('global_pool', 'False') + _ = attrs.get('cudnn_off', 'False') + pooling_convention = attrs.get('pooling_convention', 'valid') + stride = convert_string_to_list(attrs.get('stride', '(1, 1)')) + pad = convert_string_to_list(attrs.get('pad', '()')) + p_value = int(attrs.get('p_value', '0')) + count_include_pad = attrs.get('count_include_pad', 'True') + layout = attrs.get('layout', 'NCHW') + + if pooling_convention == 'same': + raise NotImplementedError('Pooling currently does not support ' + 'pooling_convention==\'same\'') + if pool_type == 'sum': + raise NotImplementedError('Pooling currently does not support pool_type==\'sum\'') + if pool_type == 'lp' and pooling_convention != 'valid': + raise NotImplementedError('Pooling currently does not support ' + 'pooling_convention!=\'valid\' when pool_type==\'lp\'') + if layout != 'NCHW': + raise NotImplementedError('Pooling currently does not support layout!=\'NCHW\'') + + kwargs_ = {} + if kernel: + kwargs_['kernel_shape'] = tuple(kernel) + if pad: + kwargs_['pads'] = tuple(pad) + tuple(pad) + if stride: + kwargs_['strides'] = stride + + ceil_mode = 1 if pooling_convention == 'full' else 0 + count_include_pad = 1 if count_include_pad=='True' else 0 + + print(kwargs_, ceil_mode, count_include_pad) + + nodes = [] + if pool_type == 'avg' and global_pool== 'False': + nodes += [ + make_node('AveragePool', [input_nodes[0]], [name], ceil_mode=ceil_mode, + count_include_pad=count_include_pad, **kwargs_) + ] + elif pool_type == 'max' and global_pool== 'False': + nodes += [ + make_node('MaxPool', [input_nodes[0]], [name], ceil_mode=ceil_mode, **kwargs_) + ] + elif pool_type == 'lp' and global_pool== 'False': + nodes += [ + make_node('LpPool', [input_nodes[0]], [name], p=p_value, **kwargs_) + ] + + return nodes + + ''' opset_version = kwargs["opset_version"] name, input_nodes, attrs = get_inputs(node, kwargs) @@ -751,7 +806,8 @@ def convert_pooling(node, **kwargs): pads=pad_dims, strides=stride, name=name, - ceil_mode=ceil_mode + ceil_mode=ceil_mode, + count_include_pad = 1 ) else: node = onnx.helper.make_node( @@ -765,6 +821,7 @@ def convert_pooling(node, **kwargs): ) return [node] + ''' @mx_op.register("exp") diff --git a/tests/python-pytest/onnx/test_operators.py b/tests/python-pytest/onnx/test_operators.py index d2c3884f4775..e205c752bf19 100644 --- a/tests/python-pytest/onnx/test_operators.py +++ b/tests/python-pytest/onnx/test_operators.py @@ -39,7 +39,7 @@ def hybrid_forward(self, F, *inputs): return func(*inputs, **params) return Model -def op_export_test(model_name, Model, inputs, tmp_path, dummy_input=False): +def op_export_test(model_name, Model, inputs, tmp_path, dummy_input=False, onnx_map=None): def export_to_onnx(model, model_name, inputs): model_path = '{}/{}'.format(tmp_path, model_name) model.export(model_path, epoch=0) @@ -69,9 +69,11 @@ def onnx_rt(onnx_file, inputs): pred_nat = pred_nat[0] if isinstance(pred_nat, list): for i in range(len(pred_nat)): - assert_almost_equal(pred_nat[i], pred_onx[i]) + pred_onx = onnx_map(pred_onx[i]) if onnx_map else pred_onx[i] + assert_almost_equal(pred_nat[i], pred_onx) else: - assert_almost_equal(pred_nat, pred_onx[0]) + pred_onx = onnx_map(pred_onx[0]) if onnx_map else pred_onx[0] + assert_almost_equal(pred_nat, pred_onx) def test_onnx_export_abs(tmp_path): @@ -720,3 +722,80 @@ def test_onnx_export_batch_dot(tmp_path, dtype, transpose_a, transpose_b): y2 = mx.nd.random.normal(0, 10, (2, 3, 4, 5, 5), dtype=dtype) M2 = def_model('batch_dot', transpose_a=transpose_a, transpose_b=transpose_b) op_export_test('batch_dot2', M2, [x2, y2], tmp_path) + + +@pytest.mark.parametrize('dtype', ['float32']) +@pytest.mark.parametrize('shape', [(1, 3, 64, 64), (2, 1, 60, 60)]) +@pytest.mark.parametrize('count_include_pad', [True, False]) +@pytest.mark.parametrize('pooling_convention', ['full', 'valid']) +@pytest.mark.parametrize('kernel', [(3, 3), (4, 5), (14, 14)]) +@pytest.mark.parametrize('stride', [None, (1, 1), (2, 2), (3, 4), (4, 5)]) +@pytest.mark.parametrize('pad', [None, (1, 1), (3, 4), (4, 5)]) +def test_onnx_export_pooling_avg(tmp_path, dtype, shape, count_include_pad, pooling_convention, + kernel, stride, pad): + # mxnet and onnxruntime has different implementation of count_include_pad on the left column + # and bottom row + if pooling_convention == 'full' and count_include_pad == True: + return + # onnxruntime requires that pad is smaller than kernel + if pad and pad[0] >= kernel[0] and pad[1] >= kernel[1]: + return + x = mx.random.uniform(0, 1, shape, dtype=dtype) + kwargs = {} + if kernel: + kwargs['kernel'] = kernel + if stride: + kwargs['stride'] = stride + if pad: + kwargs['pad'] = pad + M = def_model('Pooling', count_include_pad=count_include_pad, pool_type='avg', + pooling_convention=pooling_convention, **kwargs) + # Note here we use np.nan_to_num to map the onnx output because onnxruntime AveragePool will + # output NaN in some edge cases where mxnet outputs 0 + op_export_test('pooling_avg', M, [x], tmp_path, onnx_map=np.nan_to_num) + + +@pytest.mark.parametrize('dtype', ['float32']) +@pytest.mark.parametrize('shape', [(1, 3, 64, 64), (2, 1, 60, 60)]) +@pytest.mark.parametrize('pooling_convention', ['full', 'valid']) +@pytest.mark.parametrize('kernel', [(3, 3), (4, 5), (14, 14)]) +@pytest.mark.parametrize('stride', [None, (1, 1), (2, 2), (3, 4), (4, 5)]) +@pytest.mark.parametrize('pad', [None, (1, 1), (3, 4), (4, 5)]) +def test_onnx_export_pooling_max(tmp_path, dtype, shape, pooling_convention, kernel, stride, pad): + # onnxruntime requires that pad is smaller than kernel + if pad and pad[0] >= kernel[0] and pad[1] >= kernel[1]: + return + x = mx.random.uniform(0, 1, shape, dtype=dtype) + kwargs = {} + if kernel: + kwargs['kernel'] = kernel + if stride: + kwargs['stride'] = stride + if pad: + kwargs['pad'] = pad + M = def_model('Pooling', pool_type='max', pooling_convention=pooling_convention, **kwargs) + op_export_test('pooling_max', M, [x], tmp_path) + + +@pytest.mark.parametrize('dtype', ['float32']) +@pytest.mark.parametrize('shape', [(1, 3, 64, 64), (2, 1, 60, 60)]) +@pytest.mark.parametrize('p_value', [1, 2]) +@pytest.mark.parametrize('kernel', [(3, 3), (4, 5), (14, 14)]) +@pytest.mark.parametrize('stride', [None, (1, 1), (2, 2), (3, 4), (4, 5)]) +@pytest.mark.parametrize('pad', [None, (1, 1), (3, 4), (4, 5)]) +def test_onnx_export_pooling_lp(tmp_path, dtype, shape, p_value, kernel, stride, pad): + # onnxruntime requires that pad is smaller than kernel + if pad and pad[0] >= kernel[0] and pad[1] >= kernel[1]: + return + x = mx.random.uniform(0, 1, shape, dtype=dtype) + kwargs = {} + if kernel: + kwargs['kernel'] = kernel + if stride: + kwargs['stride'] = stride + if pad: + kwargs['pad'] = pad + M = def_model('Pooling', pool_type='lp', pooling_convention='valid', + p_value=p_value, **kwargs) + op_export_test('pooling_lp', M, [x], tmp_path) + From e9e99f98e6c9ef6800a5a51a2eaee0f403eb0bd9 Mon Sep 17 00:00:00 2001 From: zha0q1 Date: Wed, 3 Feb 2021 19:44:37 +0000 Subject: [PATCH 2/7] pooling global --- .../contrib/onnx/mx2onnx/_op_translations.py | 20 ++++++++++++---- tests/python-pytest/onnx/test_operators.py | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 93a5aacdb528..05d4f34e97fd 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -703,9 +703,9 @@ def convert_pooling(node, **kwargs): 'pooling_convention==\'same\'') if pool_type == 'sum': raise NotImplementedError('Pooling currently does not support pool_type==\'sum\'') - if pool_type == 'lp' and pooling_convention != 'valid': + if pool_type == 'lp' and global_pool == 'False' and pooling_convention != 'valid': raise NotImplementedError('Pooling currently does not support ' - 'pooling_convention!=\'valid\' when pool_type==\'lp\'') + 'pooling_convention!=\'valid\' when pool_type==\'lp\' and global_pool==False') if layout != 'NCHW': raise NotImplementedError('Pooling currently does not support layout!=\'NCHW\'') @@ -720,8 +720,6 @@ def convert_pooling(node, **kwargs): ceil_mode = 1 if pooling_convention == 'full' else 0 count_include_pad = 1 if count_include_pad=='True' else 0 - print(kwargs_, ceil_mode, count_include_pad) - nodes = [] if pool_type == 'avg' and global_pool== 'False': nodes += [ @@ -736,6 +734,20 @@ def convert_pooling(node, **kwargs): nodes += [ make_node('LpPool', [input_nodes[0]], [name], p=p_value, **kwargs_) ] + elif pool_type == 'avg' and global_pool == 'True': + nodes += [ + make_node('GlobalAveragePool', [input_nodes[0]], [name]) + ] + elif pool_type == 'max' and global_pool == 'True': + nodes += [ + make_node('GlobalMaxPool', [input_nodes[0]], [name]) + ] + elif pool_type == 'lp' and global_pool == 'True': + nodes += [ + make_node('GlobalLpPool', [input_nodes[0]], [name], p=p_value) + ] + else: + raise NotImplementedError('Unknown parameter values in Pooling') return nodes diff --git a/tests/python-pytest/onnx/test_operators.py b/tests/python-pytest/onnx/test_operators.py index e205c752bf19..17c3fd3a7b7e 100644 --- a/tests/python-pytest/onnx/test_operators.py +++ b/tests/python-pytest/onnx/test_operators.py @@ -799,3 +799,27 @@ def test_onnx_export_pooling_lp(tmp_path, dtype, shape, p_value, kernel, stride, p_value=p_value, **kwargs) op_export_test('pooling_lp', M, [x], tmp_path) + +@pytest.mark.parametrize('dtype', ['float32']) +@pytest.mark.parametrize('shape', [(1, 3, 64, 64), (2, 1, 60, 60)]) +@pytest.mark.parametrize('pool_type', ['avg', 'max', 'lp']) +@pytest.mark.parametrize('p_value', [1, 2]) +@pytest.mark.parametrize('kernel', [(3, 3), (14, 14)]) +@pytest.mark.parametrize('stride', [None, (3, 4)]) +@pytest.mark.parametrize('pad', [None, (3, 4)]) +def test_onnx_export_pooling_global(tmp_path, dtype, shape, pool_type, p_value, kernel, stride, pad): + # onnxruntime requires that pad is smaller than kernel + if pad and pad[0] >= kernel[0] and pad[1] >= kernel[1]: + return + x = mx.random.uniform(0, 1, shape, dtype=dtype) + kwargs = {} + if kernel: + kwargs['kernel'] = kernel + if stride: + kwargs['stride'] = stride + if pad: + kwargs['pad'] = pad + # kernel, stride, and pad should have no effect on the results + M = def_model('Pooling', global_pool=True, pool_type=pool_type, pooling_convention='valid', + p_value=p_value, **kwargs) + op_export_test('pooling_global', M, [x], tmp_path) From 0168d01f926cb74a8d351d5a88e32bd90908be0c Mon Sep 17 00:00:00 2001 From: zha0q1 Date: Wed, 3 Feb 2021 20:03:36 +0000 Subject: [PATCH 3/7] remove old implementation --- .../contrib/onnx/mx2onnx/_op_translations.py | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 05d4f34e97fd..00e195a976aa 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -751,90 +751,6 @@ def convert_pooling(node, **kwargs): return nodes - ''' - opset_version = kwargs["opset_version"] - name, input_nodes, attrs = get_inputs(node, kwargs) - - kernel = eval(attrs["kernel"]) - pool_type = attrs["pool_type"] if attrs.get("pool_type") else "max" - stride = eval(attrs["stride"]) if attrs.get("stride") else (1, 1) - global_pool = get_boolean_attribute_value(attrs, "global_pool") - p_value = attrs.get('p_value', 'None') - - pooling_convention = attrs.get('pooling_convention', 'valid') - ceil_mode = False - if pooling_convention == 'full': - if opset_version < 10: - pooling_warning = "Pooling: ONNX lower than 1.5.0 doesn't support pooling_convention. " \ - "This might lead to shape or accuracy issues. " \ - "https://github.com/onnx/onnx/issues/549" - logging.warning(pooling_warning) - ceil_mode = True - - pad_dims = list(parse_helper(attrs, "pad", [0, 0])) - pad_dims = pad_dims + pad_dims - pool_types = {"max": "MaxPool", "avg": "AveragePool", "lp": "LpPool"} - global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool", - "lp": "GlobalLpPool"} - - if pool_type == 'lp' and p_value == 'None': - raise AttributeError('ONNX requires a p value for LpPool and GlobalLpPool') - - if global_pool: - if pool_type == 'lp': - node = onnx.helper.make_node( - global_pool_types[pool_type], - input_nodes, # input - [name], - p=int(p_value), - name=name - ) - else: - node = onnx.helper.make_node( - global_pool_types[pool_type], - input_nodes, # input - [name], - name=name - ) - else: - if pool_type == 'lp': - node = onnx.helper.make_node( - pool_types[pool_type], - input_nodes, # input - [name], - p=int(p_value), - kernel_shape=kernel, - pads=pad_dims, - strides=stride, - name=name - ) - else: - if opset_version >= 10: - node = onnx.helper.make_node( - pool_types[pool_type], - input_nodes, # input - [name], - kernel_shape=kernel, - pads=pad_dims, - strides=stride, - name=name, - ceil_mode=ceil_mode, - count_include_pad = 1 - ) - else: - node = onnx.helper.make_node( - pool_types[pool_type], - input_nodes, # input - [name], - kernel_shape=kernel, - pads=pad_dims, - strides=stride, - name=name - ) - - return [node] - ''' - @mx_op.register("exp") def convert_exp(node, **kwargs): From b0c7bfcaf61fbd49aa093ff43d6d534d21fec42a Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Wed, 3 Feb 2021 13:10:27 -0800 Subject: [PATCH 4/7] Update _op_translations.py --- .../mxnet/contrib/onnx/mx2onnx/_op_translations.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index d2a11626aabd..c4079cd436fe 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -697,15 +697,15 @@ def convert_pooling(node, **kwargs): p_value = int(attrs.get('p_value', '0')) count_include_pad = attrs.get('count_include_pad', 'True') layout = attrs.get('layout', 'NCHW') - + if pooling_convention == 'same': raise NotImplementedError('Pooling currently does not support ' - 'pooling_convention==\'same\'') + 'pooling_convention==\'same\'') if pool_type == 'sum': raise NotImplementedError('Pooling currently does not support pool_type==\'sum\'') if pool_type == 'lp' and global_pool == 'False' and pooling_convention != 'valid': raise NotImplementedError('Pooling currently does not support ' - 'pooling_convention!=\'valid\' when pool_type==\'lp\' and global_pool==False') + 'pooling_convention!=\'valid\' when pool_type==\'lp\' and global_pool==False') if layout != 'NCHW': raise NotImplementedError('Pooling currently does not support layout!=\'NCHW\'') @@ -718,19 +718,19 @@ def convert_pooling(node, **kwargs): kwargs_['strides'] = stride ceil_mode = 1 if pooling_convention == 'full' else 0 - count_include_pad = 1 if count_include_pad=='True' else 0 + count_include_pad = 1 if count_include_pad == 'True' else 0 nodes = [] - if pool_type == 'avg' and global_pool== 'False': + if pool_type == 'avg' and global_pool == 'False': nodes += [ make_node('AveragePool', [input_nodes[0]], [name], ceil_mode=ceil_mode, count_include_pad=count_include_pad, **kwargs_) ] - elif pool_type == 'max' and global_pool== 'False': + elif pool_type == 'max' and global_pool == 'False': nodes += [ make_node('MaxPool', [input_nodes[0]], [name], ceil_mode=ceil_mode, **kwargs_) ] - elif pool_type == 'lp' and global_pool== 'False': + elif pool_type == 'lp' and global_pool == 'False': nodes += [ make_node('LpPool', [input_nodes[0]], [name], p=p_value, **kwargs_) ] From 1ab1fd27521fcbdfefac837581de23815276e55f Mon Sep 17 00:00:00 2001 From: zha0q1 Date: Thu, 4 Feb 2021 00:32:07 +0000 Subject: [PATCH 5/7] fix --- tests/python-pytest/onnx/test_operators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/python-pytest/onnx/test_operators.py b/tests/python-pytest/onnx/test_operators.py index f91931a0e16e..993c5b38d264 100644 --- a/tests/python-pytest/onnx/test_operators.py +++ b/tests/python-pytest/onnx/test_operators.py @@ -69,11 +69,11 @@ def onnx_rt(onnx_file, inputs): pred_nat = pred_nat[0] if isinstance(pred_nat, list): for i in range(len(pred_nat)): - pred_onx = onnx_map(pred_onx[i]) if onnx_map else pred_onx[i] - assert_almost_equal(pred_nat[i], pred_onx) + pred_onx_i = onnx_map(pred_onx[i]) if onnx_map else pred_onx[i] + assert_almost_equal(pred_nat[i], pred_onx_i, equal_nan=True) else: pred_onx = onnx_map(pred_onx[0]) if onnx_map else pred_onx[0] - assert_almost_equal(pred_nat, pred_onx) + assert_almost_equal(pred_nat, pred_onx, equal_nan=True) def test_onnx_export_abs(tmp_path): From 13b45f97e78d9a4fbe614d4c499bd3a4b2bd4abe Mon Sep 17 00:00:00 2001 From: zha0q1 Date: Thu, 4 Feb 2021 02:44:33 +0000 Subject: [PATCH 6/7] conv --- .../contrib/onnx/mx2onnx/_op_translations.py | 49 ++++++++++++------- tests/python-pytest/onnx/test_operators.py | 33 +++++++++++++ 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index c4079cd436fe..4082fe93a5dc 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -229,34 +229,45 @@ def convert_weights_and_inputs(node, **kwargs): return [tval_node] -@mx_op.register("Convolution") +@mx_op.register('Convolution') def convert_convolution(node, **kwargs): """Map MXNet's convolution operator attributes to onnx's Conv operator and return the created node. """ + from onnx.helper import make_node name, input_nodes, attrs = get_inputs(node, kwargs) - kernel_dims = list(parse_helper(attrs, "kernel")) - stride_dims = list(parse_helper(attrs, "stride", [1, 1])) - pad_dims = list(parse_helper(attrs, "pad", [0, 0])) - num_group = int(attrs.get("num_group", 1)) - dilations = list(parse_helper(attrs, "dilate", [1, 1])) + kernel = convert_string_to_list(attrs.get('kernel', '()')) + stride = convert_string_to_list(attrs.get('stride', '(1, 1)')) + dilate = convert_string_to_list(attrs.get('dilate', '(1, 1)')) + pad = convert_string_to_list(attrs.get('pad', '(0, 0)')) + num_group = int(attrs.get('num_group', 1)) + no_bias = attrs.get('no_bias', 'False') + layout = attrs.get('layout', 'NCHW') - pad_dims = pad_dims + pad_dims + if layout != 'NCHW': + raise NotImplementedError('Pooling currently does not support layout!=\'NCHW\'') - conv_node = onnx.helper.make_node( - "Conv", - inputs=input_nodes, - outputs=[name], - kernel_shape=kernel_dims, - strides=stride_dims, - dilations=dilations, - pads=pad_dims, - group=num_group, - name=name - ) + if no_bias == 'True': + assert len(input_nodes) == 2, 'Convolution takes 2 input if no_bias==True' + else: + assert len(input_nodes) == 3, 'Convolution takes 3 input if no_bias==False' - return [conv_node] + kwargs_ = {} + if kernel: + kwargs_['kernel_shape'] = tuple(kernel) + if pad: + kwargs_['pads'] = tuple(pad) + tuple(pad) + if stride: + kwargs_['strides'] = stride + if dilate: + kwargs_['dilations'] = dilate + + nodes = [ + make_node('Conv', input_nodes, [name], group=num_group, **kwargs_) + ] + + return nodes @mx_op.register("Deconvolution") diff --git a/tests/python-pytest/onnx/test_operators.py b/tests/python-pytest/onnx/test_operators.py index 993c5b38d264..1ff982100dd3 100644 --- a/tests/python-pytest/onnx/test_operators.py +++ b/tests/python-pytest/onnx/test_operators.py @@ -882,3 +882,36 @@ def test_onnx_export_broadcast_mul(tmp_path, dtype): x = mx.nd.array([[1,2,3],[4,5,6]], dtype=dtype) y = mx.nd.array([[0],[3]], dtype=dtype) op_export_test('broadcast_mul', M, [x, y], tmp_path) + + +@pytest.mark.parametrize('dtype', ['float32']) +@pytest.mark.parametrize('shape', [(1, 3, 64, 64), (2, 6, 60, 60)]) +@pytest.mark.parametrize('num_filter', [2, 4, 32]) +@pytest.mark.parametrize('num_group', [1, 2]) +@pytest.mark.parametrize('no_bias', [True, False]) +@pytest.mark.parametrize('kernel', [(3, 3), (4, 5), (14, 14)]) +@pytest.mark.parametrize('stride', [None, (1, 1), (2, 2), (3, 4), (4, 5)]) +@pytest.mark.parametrize('pad', [None, (1, 1), (3, 4), (4, 5)]) +@pytest.mark.parametrize('dilate', [None, (1, 1)]) +def test_onnx_export_convolution(tmp_path, dtype, shape, num_filter, num_group, no_bias, + kernel, stride, pad, dilate): + if shape[1] % num_group: + return + x = mx.random.uniform(0, 1, shape, dtype=dtype) + w_shape = (num_filter,) + (shape[1] // num_group,) + kernel + w = mx.random.uniform(0, 1, w_shape, dtype=dtype) + b_shape = (num_filter) + b = mx.random.uniform(0, 1, b_shape, dtype=dtype) + kwargs = {} + if kernel: + kwargs['kernel'] = kernel + if stride: + kwargs['stride'] = stride + if pad: + kwargs['pad'] = pad + if dilate: + kwargs['dilate'] = dilate + M = def_model('Convolution', num_filter=num_filter, num_group=num_group, no_bias=no_bias, + **kwargs) + inputs = [x, w] if no_bias else [x, w, b] + op_export_test('convolution', M, inputs, tmp_path) From 36e128d651f6de6a00912a22030278106e0cdb63 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Thu, 4 Feb 2021 10:43:18 -0800 Subject: [PATCH 7/7] Update _op_translations.py --- python/mxnet/contrib/onnx/mx2onnx/_op_translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 4082fe93a5dc..985f07dd7e89 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -264,7 +264,7 @@ def convert_convolution(node, **kwargs): kwargs_['dilations'] = dilate nodes = [ - make_node('Conv', input_nodes, [name], group=num_group, **kwargs_) + make_node('Conv', input_nodes, [name], group=num_group, **kwargs_) ] return nodes