You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by zh...@apache.org on 2021/04/21 17:43:13 UTC

[incubator-mxnet] branch v1.x updated: [v1.x] Add onnx export operator unit tests. (#20192)

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

zha0q1 pushed a commit to branch v1.x
in repository https://gitbox.apache.org/repos/asf/incubator-mxnet.git


The following commit(s) were added to refs/heads/v1.x by this push:
     new 94fe808  [v1.x] Add onnx export operator unit tests. (#20192)
94fe808 is described below

commit 94fe808902e20f3c9ee60f95648b8fb98fcc8d5a
Author: Joe Evans <jo...@gmail.com>
AuthorDate: Wed Apr 21 10:40:24 2021 -0700

    [v1.x] Add onnx export operator unit tests. (#20192)
    
    * Add onnx operator export unit tests for reciprocal, power, broadcast_power, sqrt, depth_to_space and space_to_depth operators.
    
    * Add onnx operator export unit test for square operator.
    
    * Update export operators for broadcast_logical_and, broadcast_logical_or, broadcast_logical_xor.
    
    * Add onnx export unit tests for shape_array, hard_sigmoid, broadcast_lesser, broadcast_greater, log_softmax, broadcast_logical_and, broadcast_logical_or, broadcast_logical_xor.
    
    * Fix logical_not export by casting, add export unit tests for broadcast_to and logical_not.
    
    * Fix random_uniform and random_uniform_like to use proper data types and add unit tests.
    
    * Onnxruntime does not support float64 dtype for reciprocal.
    
    * Fix random_normal export function to include output dtype, add operator export unit test.
    
    * Fix syntax.
    
    * Fix pylint, add operator unit test for ROIPooling.
---
 .../_op_translations/_op_translations_opset12.py   |  86 ++++++--
 tests/python-pytest/onnx/test_operators.py         | 224 ++++++++++++++++++++-
 2 files changed, 293 insertions(+), 17 deletions(-)

diff --git a/python/mxnet/onnx/mx2onnx/_op_translations/_op_translations_opset12.py b/python/mxnet/onnx/mx2onnx/_op_translations/_op_translations_opset12.py
index 8f1114a..8550b70 100644
--- a/python/mxnet/onnx/mx2onnx/_op_translations/_op_translations_opset12.py
+++ b/python/mxnet/onnx/mx2onnx/_op_translations/_op_translations_opset12.py
@@ -2273,10 +2273,22 @@ def convert_broadcast_equal(node, **kwargs):
 
 @mx_op.register("broadcast_logical_and")
 def convert_broadcast_logical_and(node, **kwargs):
-    """Map MXNet's broadcast logical and operator attributes to onnx's Add operator
+    """Map MXNet's broadcast logical and operator attributes to onnx's And operator
     and return the created node.
     """
-    return create_basic_op_node('And', node, kwargs)
+    from onnx.helper import make_node
+    from onnx import TensorProto
+    name, input_nodes, _ = get_inputs(node, kwargs)
+    input_dtypes = get_input_dtypes(node, kwargs)
+    dtype = input_dtypes[0]
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
+    nodes = [
+        make_node("Cast", [input_nodes[0]], [name+"_cast0"], to=int(TensorProto.BOOL)),
+        make_node("Cast", [input_nodes[1]], [name+"_cast1"], to=int(TensorProto.BOOL)),
+        make_node("And", [name+"_cast0", name+"_cast1"], [name+"_and"]),
+        make_node("Cast", [name+"_and"], [name], name=name, to=int(dtype_t))
+    ]
+    return nodes
 
 
 @mx_op.register("broadcast_logical_or")
@@ -2284,7 +2296,19 @@ def convert_broadcast_logical_or(node, **kwargs):
     """Map MXNet's broadcast logical or operator attributes to onnx's Or operator
     and return the created node.
     """
-    return create_basic_op_node('Or', node, kwargs)
+    from onnx.helper import make_node
+    from onnx import TensorProto
+    name, input_nodes, _ = get_inputs(node, kwargs)
+    input_dtypes = get_input_dtypes(node, kwargs)
+    dtype = input_dtypes[0]
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
+    nodes = [
+        make_node("Cast", [input_nodes[0]], [name+"_cast0"], to=int(TensorProto.BOOL)),
+        make_node("Cast", [input_nodes[1]], [name+"_cast1"], to=int(TensorProto.BOOL)),
+        make_node("Or", [name+"_cast0", name+"_cast1"], [name+"_or"]),
+        make_node("Cast", [name+"_or"], [name], name=name, to=int(dtype_t))
+    ]
+    return nodes
 
 
 @mx_op.register("broadcast_logical_xor")
@@ -2292,7 +2316,19 @@ def convert_broadcast_logical_xor(node, **kwargs):
     """Map MXNet's broadcast logical xor operator attributes to onnx's Xor operator
     and return the created node.
     """
-    return create_basic_op_node('Xor', node, kwargs)
+    from onnx.helper import make_node
+    from onnx import TensorProto
+    name, input_nodes, _ = get_inputs(node, kwargs)
+    input_dtypes = get_input_dtypes(node, kwargs)
+    dtype = input_dtypes[0]
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
+    nodes = [
+        make_node("Cast", [input_nodes[0]], [name+"_cast0"], to=int(TensorProto.BOOL)),
+        make_node("Cast", [input_nodes[1]], [name+"_cast1"], to=int(TensorProto.BOOL)),
+        make_node("Xor", [name+"_cast0", name+"_cast1"], [name+"_xor"]),
+        make_node("Cast", [name+"_xor"], [name], name=name, to=int(dtype_t))
+    ]
+    return nodes
 
 
 @mx_op.register("logical_not")
@@ -2300,7 +2336,18 @@ def convert_logical_not(node, **kwargs):
     """Map MXNet's logical not operator attributes to onnx's Not operator
     and return the created node.
     """
-    return create_basic_op_node('Not', node, kwargs)
+    from onnx.helper import make_node
+    from onnx import TensorProto
+    name, input_nodes, _ = get_inputs(node, kwargs)
+    input_dtypes = get_input_dtypes(node, kwargs)
+    dtype = input_dtypes[0]
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
+    nodes = [
+        make_node("Cast", [input_nodes[0]], [name+"_cast"], to=int(TensorProto.BOOL)),
+        make_node("Not", [name+"_cast"], [name+"_not"]),
+        make_node("Cast", [name+"_not"], [name], name=name, to=int(dtype_t))
+    ]
+    return nodes
 
 
 @mx_op.register("size_array")
@@ -2346,6 +2393,9 @@ def convert_norm(node, **kwargs):
     keepdims = get_boolean_attribute_value(attrs, "keepdims")
     ord = int(attrs.get("ord", 2))
 
+    if ord not in [1, 2]:
+        raise AttributeError("norm export operator only supports ord=1 or ord=2.")
+
     onnx_op_name = "ReduceL1" if ord == 1 else "ReduceL2"
 
     if axes:
@@ -2396,25 +2446,26 @@ def convert_random_uniform(node, **kwargs):
     """Map MXNet's random_uniform operator attributes to onnx's RandomUniform
     operator and return the created node.
     """
-    name, input_nodes, attrs = get_inputs(node, kwargs)
+    name, _, attrs = get_inputs(node, kwargs)
 
     # Converting to float32
     low = float(attrs.get("low", 0))
     high = float(attrs.get("high", 1.0))
     shape = convert_string_to_list(attrs.get('shape', '[]'))
-    dtype = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(attrs.get('dtype', 'float32'))]
+    dtype = np.dtype(attrs.get('dtype', 'float32'))
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
 
     node = onnx.helper.make_node(
         'RandomUniform',
-        input_nodes,
+        [],
         [name],
         low=low,
         high=high,
-        dtype=dtype,
+        dtype=dtype_t,
         shape=shape,
         name=name
     )
-    return [node]
+    return [node], (dtype,)
 
 
 @mx_op.register("_random_normal")
@@ -2428,7 +2479,8 @@ def convert_random_normal(node, **kwargs):
     mean = float(attrs.get("loc", 0))
     scale = float(attrs.get("scale", 1.0))
     shape = convert_string_to_list(attrs.get('shape', '[]'))
-    dtype = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(attrs.get('dtype', 'float32'))]
+    dtype = np.dtype(attrs.get('dtype', 'float32'))
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
 
     node = onnx.helper.make_node(
         'RandomNormal',
@@ -2436,11 +2488,11 @@ def convert_random_normal(node, **kwargs):
         [name],
         mean=mean,
         scale=scale,
-        dtype=dtype,
+        dtype=dtype_t,
         shape=shape,
         name=name
     )
-    return [node]
+    return [node], (dtype,)
 
 
 @mx_op.register("ROIPooling")
@@ -4293,15 +4345,17 @@ def convert_random_uniform_like(node, **kwargs):
     """
     from onnx.helper import make_node
     name, input_nodes, attrs = get_inputs(node, kwargs)
+    input_dtypes = get_input_dtypes(node, kwargs)
+
+    dtype = input_dtypes[0]
+    dtype_t = onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[dtype]
 
     low = float(attrs.get('low', 0.))
     high = float(attrs.get('high', 1.))
-    dtype = attrs.get('dtype', 'float32')
 
     nodes = [
         make_node('RandomUniformLike', [input_nodes[0]], [name], name=name,
-                  dtype=onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(dtype)],
-                  low=low, high=high)
+                  dtype=dtype_t, low=low, high=high)
     ]
 
     return nodes
diff --git a/tests/python-pytest/onnx/test_operators.py b/tests/python-pytest/onnx/test_operators.py
index cd694a0..eedfd33 100644
--- a/tests/python-pytest/onnx/test_operators.py
+++ b/tests/python-pytest/onnx/test_operators.py
@@ -1290,6 +1290,154 @@ def test_onnx_export_contrib_div_sqrt_dim(tmp_path, dtype, shape):
     op_export_test('contrib_div_sqrt_dim', M, [A], tmp_path)
 
 
+@pytest.mark.parametrize('dtype', ['float16', 'float32'])
+@pytest.mark.parametrize('shape', [(100,), (3, 4, 5), (6, 7)])
+def test_onnx_export_reciprocal(tmp_path, dtype, shape):
+    A = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    M = def_model('reciprocal')
+    op_export_test('reciprocal', M, [A], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64", "int32", "int64"])
+@pytest.mark.parametrize('shape', [(1, 3), (3, 4, 5)])
+def test_onnx_export_power(tmp_path, shape, dtype):
+    x = mx.nd.random.uniform(-5, 5, shape).astype(dtype)
+    y = mx.nd.random.uniform(-10, 10, shape).astype(dtype)
+    M = def_model('_internal._power')
+    op_export_test('_internal._power', M, [x, y], tmp_path)
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64", "int32", "int64"])
+@pytest.mark.parametrize('shape', [(1, 3), (3, 4, 5)])
+def test_onnx_export_broadcast_power(tmp_path, shape, dtype):
+    x = mx.nd.random.uniform(-5, 5, shape).astype(dtype)
+    y = mx.nd.random.uniform(-10, 10, shape).astype(dtype)
+    M = def_model('broadcast_power')
+    op_export_test('broadcast_power', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64"])
+@pytest.mark.parametrize('shape', [(3, 4, 5), (6, 7), (8,)])
+def test_onnx_export_sqrt(tmp_path, dtype, shape):
+    A = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    M = def_model('sqrt')
+    op_export_test('sqrt', M, [A], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32"])
+@pytest.mark.parametrize("params", [[(1,4,2,3), 1], [(1,4,2,3), 2]])
+def test_onnx_export_depth_to_space(tmp_path, dtype, params):
+    shape, block_size = params
+    M = def_model('depth_to_space', block_size=block_size)
+    x = mx.nd.arange(0, np.prod(shape)).reshape(shape).astype(dtype)
+    op_export_test('depth_to_space', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32"])
+@pytest.mark.parametrize("params", [[(1,4,2,3), 1], [(1,1,4,6),2]])
+def test_onnx_export_space_to_depth(tmp_path, dtype, params):
+    shape, block_size = params
+    M = def_model('space_to_depth', block_size=block_size)
+    x = mx.nd.arange(0, np.prod(shape)).reshape(shape).astype(dtype)
+    op_export_test('space_to_depth', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64", "int32", "int64"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_square(tmp_path, dtype, shape):
+    M = def_model('square')
+    x = mx.nd.arange(0, np.prod(shape)).reshape(shape).astype(dtype)
+    op_export_test('square', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64", "int32", "int64"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_shape_array(tmp_path, dtype, shape):
+    M = def_model('shape_array')
+    x = mx.nd.arange(0, np.prod(shape)).reshape(shape).astype(dtype)
+    op_export_test('shape_array', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+@pytest.mark.parametrize("alpha", [None, 0.1, 0.4567, 0.9])
+@pytest.mark.parametrize("beta", [None, 0.1, 0.4567, 0.5, 0.9])
+def test_onnx_export_hard_sigmoid(tmp_path, dtype, shape, alpha, beta):
+    kwargs = { }
+    if alpha is not None:
+        kwargs['alpha'] = alpha
+    if beta is not None:
+        kwargs['beta'] = beta
+    M = def_model('hard_sigmoid', **kwargs)
+    x = mx.nd.arange(0, np.prod(shape)).reshape(shape).astype(dtype)
+    op_export_test('hard_sigmoid', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_broadcast_lesser(tmp_path, dtype, shape):
+    M = def_model('broadcast_lesser')
+    x = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    y = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    op_export_test('broadcast_lesser', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_broadcast_greater(tmp_path, dtype, shape):
+    M = def_model('broadcast_greater')
+    x = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    y = mx.nd.random.uniform(-100, 100, shape).astype(dtype)
+    op_export_test('broadcast_greater', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32'])
+@pytest.mark.parametrize("shape", [(10,5), (1,2,3), (4,5,6)])
+@pytest.mark.parametrize('axis', [None, 1])
+def test_onnx_export_log_softmax(tmp_path, dtype, shape, axis):
+    x = mx.nd.random.uniform(0, 1, shape, dtype=dtype)
+    kwargs = {}
+    if axis is not None:
+        kwargs['axis'] = axis
+    M = def_model('log_softmax', **kwargs)
+    op_export_test('log_softmax', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (2,3), (4,5,6)])
+def test_onnx_export_broadcast_logical_and(tmp_path, dtype, shape):
+    M = def_model('broadcast_logical_and')
+    x = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    y = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    op_export_test('broadcast_logical_and', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (2,3), (4,5,6)])
+def test_onnx_export_broadcast_logical_or(tmp_path, dtype, shape):
+    M = def_model('broadcast_logical_or')
+    x = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    y = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    op_export_test('broadcast_logical_or', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (2,3), (4,5,6)])
+def test_onnx_export_broadcast_logical_xor(tmp_path, dtype, shape):
+    M = def_model('broadcast_logical_xor')
+    x = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    y = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    op_export_test('broadcast_logical_xor', M, [x, y], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shapes", [[(1,3),(2,3)], [(2,1,3,1),(2,8,3,9)], [(1,3,6),(5,3,6)]])
+def test_onnx_export_broadcast_to(tmp_path, dtype, shapes):
+    in_shape, to_shape = shapes
+    M = def_model('broadcast_to', shape=to_shape)
+    x = mx.nd.random.uniform(-100, 100, in_shape).astype(dtype)
+    op_export_test('broadcast_to', M, [x], tmp_path)
+
+
 # onnxruntime currently does not support int32
 @pytest.mark.parametrize('dtype', ['float16', 'float32', 'int64'])
 @pytest.mark.parametrize('shape', [(1,), (2, 3), (4, 5, 6)])
@@ -1406,4 +1554,78 @@ def test_onnx_export_ufunc(tmp_path, dtype, shape, op_name):
 def test_onnx_export_squeeze(tmp_path, dtype, shape_axis):
     x = mx.nd.random.uniform(1, 100, shape=shape_axis[0]).astype(dtype)
     M = def_model('squeeze', axis=shape_axis[1])
-    op_export_test('squeeze', M, [x], tmp_path)
\ No newline at end of file
+    op_export_test('squeeze', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize('dtype', ['float16', 'float32', 'float64', 'int32', 'int64'])
+@pytest.mark.parametrize("shape", [(10,), (2,3), (4,5,6)])
+def test_onnx_export_logical_not(tmp_path, dtype, shape):
+    M = def_model('logical_not')
+    x = mx.nd.random.uniform(-1, 1, shape).astype(dtype)
+    op_export_test('logical_not', M, [x], tmp_path)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32", "float64"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_random_uniform_like(tmp_path, dtype, shape):
+    M = def_model('random.uniform_like')
+    low = -10
+    high = 10
+    x = mx.nd.zeros(shape=shape).astype(dtype)
+    def rand_check(out):
+        for i in out:
+            if i.any() < low or i.any() >= high:
+                raise Exception("Invalid value")
+        return np.zeros_like(out)
+    def rand_check_nd(out):
+        return rand_check(out.asnumpy())
+    op_export_test('random.uniform_like', M, [x], tmp_path, mx_map=rand_check_nd, onnx_map=rand_check)
+
+
+@pytest.mark.parametrize("dtype", ["float32", "float64"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+def test_onnx_export_random_uniform(tmp_path, dtype, shape):
+    low = -10
+    high = 10
+    M = def_model('random_uniform', low=low, high=high, shape=shape, dtype=dtype, dummy_input=True)
+    x = mx.nd.array([1], dtype='float32')
+    def rand_check(out):
+        for i in out:
+            if i.any() < low or i.any() >= high:
+                raise Exception("Invalid value")
+        return np.zeros_like(out)
+    def rand_check_nd(out):
+        return rand_check(out.asnumpy())
+    op_export_test('random_uniform', M, [x], tmp_path, mx_map=rand_check_nd, onnx_map=rand_check, dummy_input=True)
+
+
+@pytest.mark.parametrize("dtype", ["float32", "float64"])
+@pytest.mark.parametrize("shape", [(10,), (1,2,3), (4,5,6)])
+@pytest.mark.parametrize("loc", [None, 0, 1, 2])
+@pytest.mark.parametrize("scale", [None, 1, 2])
+def test_onnx_export_random_normal(tmp_path, dtype, loc, scale, shape):
+    kwargs = {
+        'dtype': dtype,
+        'shape': shape,
+        'dummy_input': True
+    }
+    if loc is not None:
+        kwargs['loc'] = loc
+    if scale is not None:
+        kwargs['scale'] = scale
+    M = def_model('random_normal', **kwargs)
+    x = mx.nd.array([1], dtype='float32')
+    def rand_check(out):
+        return np.zeros_like(out)
+    def rand_check_nd(out):
+        return rand_check(out.asnumpy())
+    op_export_test('random_normal', M, [x], tmp_path, mx_map=rand_check_nd, onnx_map=rand_check, dummy_input=True)
+
+
+@pytest.mark.parametrize("dtype", ["float16", "float32"])
+@pytest.mark.parametrize("spatial_scale", [0.7, 1.0])
+def test_onnx_export_roi_pooling(tmp_path, dtype, spatial_scale):
+    M = def_model('ROIPooling', pooled_size=(2,2), spatial_scale=spatial_scale)
+    x = mx.nd.arange(start=0, stop=48, dtype=dtype).reshape((1,1,8,6))
+    y = mx.nd.array([[0,0,0,4,4]], dtype=dtype)
+    op_export_test('ROIPooling', M, [x, y], tmp_path)