diff --git a/Athos/tests/onnx/unittests/test_arith_binops.py b/Athos/tests/onnx/unittests/test_arith_binops.py new file mode 100644 index 00000000..52855790 --- /dev/null +++ b/Athos/tests/onnx/unittests/test_arith_binops.py @@ -0,0 +1,383 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" + +import numpy as np +import onnx +from onnx import helper +from onnx.backend.test.case.node.gemm import gemm_reference_implementation + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + + +@pytest.mark.parametrize( + "a_shape,b_shape,dtype", + [ + ((4, 4, 4, 4), (4, 4, 4, 4), np.single), # Normal + pytest.param( + (4, 4), + (4, 4), + np.single, + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), # Normal + pytest.param( + (2, 2), + (1,), + np.single, + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), # Broadcasting + pytest.param( + (3, 1, 2, 1), + (2, 1, 4), + np.single, + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), # Broadcasting + pytest.param( + (2, 2), + (), + np.single, + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), # Constant + ], +) +@pytest.mark.parametrize( + "Op", + [ + ("Add"), + pytest.param("Sub", marks=pytest.mark.skip(reason="Sub not implemented")), + pytest.param("Mul", marks=pytest.mark.skip(reason="Mul not implemented")), + ], +) +def test_arith_binop(test_dir, backend, Op, a_shape, b_shape, dtype): + onnx_to_np_op = {"Add": np.add, "Sub": np.subtract, "Mul": np.multiply} + a = np.random.randn(*a_shape).astype(dtype) + b = np.random.randn(*b_shape).astype(dtype) + out = onnx_to_np_op[Op](a, b) + node = helper.make_node( + Op, + inputs=["a", "b"], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[b], + tensor_names=["b"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize("dtype", [np.single]) +@pytest.mark.parametrize( + "a_val, divisor", + [ + pytest.param( + [7.0, -7.0], 5.0, marks=pytest.mark.skip(reason="Div not implemented") + ), # [1.4, -1.4] + pytest.param( + 7.0, 5.0, marks=pytest.mark.skip(reason="Div not implemented") + ), # 1.4 + pytest.param( + [3.0, 4.0], [1.0, 2.0], marks=pytest.mark.skip(reason="Div not implemented") + ), + ], +) +def test_div(test_dir, backend, a_val, divisor, dtype): + Op = "Div" + a = np.array(a_val).astype(dtype) + b = np.array(divisor).astype(dtype) + out = np.divide(a, b) + node = helper.make_node( + Op, + inputs=["a", "b"], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[b], + tensor_names=["b"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, b_shape, bisModel", + [ + ([3, 2], [2, 3], True), + pytest.param( + [3, 2], + [2, 3], + False, + marks=pytest.mark.skip( + reason="[matmul] expect atleast one param to belong to model" + ), + ), + ([1, 2], [2, 3], True), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +@pytest.mark.skip(reason="matmul not supported for now. only gemm is.") +def test_matmul(test_dir, backend, a_shape, b_shape, bisModel, dtype): + if backend == "2PC_HE" and a_shape[0] != 1: + pytest.skip("HE only supports vector matrix multiplication") + Op = "MatMul" + a = np.random.randn(*a_shape).astype(dtype) + b = np.random.randn(*b_shape).astype(dtype) + out = np.matmul(a, b) + node = onnx.helper.make_node( + Op, + inputs=["a", "b"], + outputs=["out"], + ) + if not bisModel: + graph = make_onnx_graph( + node, + inputs=[a, b], + outputs=[out], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a, b]) + else: + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[b], + tensor_names=["b"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + if not bisModel: + mpc_output = compiler.compile_and_run([a, b]) + else: + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, b_shape, c_shape, alpha, beta, transA, transB", + [ + pytest.param( + (3, 2), + (2, 3), + (1, 5), + 0.25, + 0.35, + 1, + 1, + id="gemm_all_attributes", + marks=pytest.mark.skip(reason="[gemm] alpha,beta not handled"), + ), + pytest.param( + (3, 5), + (5, 4), + (1, 4), + 0.5, + None, + 0, + 0, + id="gemm_alpha", + marks=pytest.mark.skip(reason="[gemm] alpha not handled"), + ), + pytest.param( + (2, 7), + (7, 4), + (1, 4), + None, + 0.5, + 0, + 0, + id="gemm_beta", + marks=pytest.mark.skip(reason="[gemm] beta not handled"), + ), + pytest.param( + (3, 6), (6, 4), (3, 4), None, None, 0, 0, id="gemm_default_matrix_bias" + ), + pytest.param( + (2, 10), + (10, 3), + None, + None, + None, + 0, + 0, + id="gemm_default_no_bias", + marks=pytest.mark.skip(reason="[gemm] bias is mandatory"), + ), + pytest.param( + (2, 3), + (3, 4), + (), + None, + None, + 0, + 0, + id="gemm_default_scalar_bias", + marks=pytest.mark.skip(reason="[gemm] scaleup0 not found"), + ), + pytest.param( + (3, 7), + (7, 3), + (1,), + None, + None, + 0, + 0, + id="gemm_default_single_elem_vector_bias", + ), + pytest.param( + (2, 7), (7, 4), (1, 4), None, None, 0, 0, id="gemm_default_vector_bias" + ), + pytest.param((6, 3), (6, 4), (1, 4), None, None, 1, 0, id="gemm_transposeA"), + pytest.param((3, 6), (4, 6), (1, 4), None, None, 0, 1, id="gemm_transposeB"), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_gemm( + test_dir, backend, a_shape, b_shape, c_shape, alpha, beta, transA, transB, dtype +): + Op = "Gemm" + a = np.random.randn(*a_shape).astype(dtype) + b = np.random.randn(*b_shape).astype(dtype) + + kwargs = {"inputs": ["a", "b"], "outputs": ["out"]} + npkwargs = {} + + if c_shape is not None: + kwargs["inputs"].append("c") + c = dtype(np.random.randn(*c_shape)) + npkwargs["C"] = c + + if alpha is not None: + kwargs["alpha"] = alpha + npkwargs["alpha"] = alpha + if beta is not None: + kwargs["beta"] = beta + npkwargs["beta"] = beta + if transA == 1: + kwargs["transA"] = 1 + npkwargs["transA"] = 1 + if transB == 1: + kwargs["transB"] = 1 + npkwargs["transB"] = 1 + + out = gemm_reference_implementation(a, b, **npkwargs) + node = onnx.helper.make_node(Op, **kwargs) + + kwargs = { + "inputs": [a], + "outputs": [out], + "tensors": [b], + "tensor_names": ["b"], + "name": Op + "_test", + } + + if c_shape is not None: + kwargs["tensors"].append(c) + kwargs["tensor_names"].append("c") + + graph = make_onnx_graph(node, **kwargs) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_val, b_val", + [ + ([1.2, 1.3], [1.2, 1.3]), + ([1.2, 1.3], [1.2, 1.2]), + ([1.2, 1.3], [1.2]), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +@pytest.mark.skip(reason="[equal] Not able to cast boolean to int ezpc") +def test_equal(test_dir, backend, a_val, b_val, dtype): + Op = "Equal" + a = np.array(a_val).astype(dtype) + b = np.array(b_val).astype(dtype) + out = np.equal(a, b) + node = helper.make_node( + Op, + inputs=["a", "b"], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[b], + tensor_names=["b"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return diff --git a/Athos/tests/onnx/unittests/test_batchnorm.py b/Athos/tests/onnx/unittests/test_batchnorm.py new file mode 100644 index 00000000..3b1900da --- /dev/null +++ b/Athos/tests/onnx/unittests/test_batchnorm.py @@ -0,0 +1,95 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" +import numpy as np +import onnx +from onnx import helper + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + + +def _batchnorm_test_mode(x, s, bias, mean, var, epsilon=1e-5): # type: ignore + dims_x = len(x.shape) + dim_ones = (1,) * (dims_x - 2) + s = s.reshape(-1, *dim_ones) + bias = bias.reshape(-1, *dim_ones) + mean = mean.reshape(-1, *dim_ones) + var = var.reshape(-1, *dim_ones) + return s * (x - mean) / np.sqrt(var + epsilon) + bias + + +@pytest.mark.parametrize( + "a_shape, scale_val, bias_val, mean_val, var_val", + [ + ([1, 2, 2, 1], [1.5, 1.5], [2.3, 2.3], [0.5, 0.5], [0.2, 0.2]), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_batch_norm( + test_dir, backend, a_shape, scale_val, bias_val, mean_val, var_val, dtype +): + Op = "BatchNormalization" + a = np.random.randn(*a_shape).astype(dtype) + scale = np.array(scale_val).astype(dtype) + bias = np.array(bias_val).astype(dtype) + mean = np.array(mean_val).astype(dtype) + var = np.array(var_val).astype(dtype) + + out = _batchnorm_test_mode(a, scale, bias, mean, var).astype(dtype) + + node = onnx.helper.make_node( + Op, + inputs=["a", "scale", "bias", "mean", "var"], + outputs=["out"], + ) + + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[scale, bias, mean, var], + tensor_names=["scale", "bias", "mean", "var"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return diff --git a/Athos/tests/onnx/unittests/test_convolution.py b/Athos/tests/onnx/unittests/test_convolution.py new file mode 100644 index 00000000..230d4bef --- /dev/null +++ b/Athos/tests/onnx/unittests/test_convolution.py @@ -0,0 +1,311 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" +import numpy as np +import onnx +from onnx import helper + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + +# TODO: add conv autopad, convtranspose autopad, convtranspose_dilations, fix grouped conv dims + + +@pytest.mark.parametrize( + "a_shape, kernel_shape, pads, strides, output_shape, group", + [ + pytest.param( + [1, 1, 5, 5], + [1, 1, 3, 3], + [1, 1, 1, 1], + [1, 1], + [1, 1, 5, 5], + 1, + id="conv2d_pad", + ), + pytest.param( + [1, 1, 5, 5], + [1, 1, 3, 3], + [0, 0, 0, 0], + [1, 1], + [1, 1, 3, 3], + 1, + id="conv2d_nopad", + ), + pytest.param( + [1, 1, 7, 5], + [1, 1, 3, 3], + [1, 1, 1, 1], + [2, 2], + [1, 1, 4, 3], + 1, + id="conv2d_strides_pad", + ), + pytest.param( + [1, 1, 7, 5], + [1, 1, 3, 3], + [0, 0, 0, 0], + [2, 2], + [1, 1, 3, 2], + 1, + id="conv2d_strides_nopad", + ), + pytest.param( + [1, 1, 7, 5], + [1, 1, 3, 3], + [1, 0, 1, 0], + [2, 2], + [1, 1, 4, 2], + 1, + marks=pytest.mark.skip(reason="Seedot reshape typecheck assertion"), + id="conv2d_strides_assymetric_pad", + ), # padding only along H dimension + # a_shape, kernel_shape, pads, strides, output_shape", + pytest.param( + [1, 2, 4, 16, 16], + [2, 2, 3, 3, 3], + [1, 1, 1, 1, 1, 1], + [1, 1, 1], + [1, 2, 4, 16, 16], + 1, + id="conv3d_pad", + ), + pytest.param( + [1, 2, 4, 16, 16], + [2, 2, 3, 3, 3], + [0, 0, 0, 0, 0, 0], + [1, 1, 1], + [1, 2, 2, 14, 14], + 1, + id="conv3d_nopad", + ), + pytest.param( + [1, 2, 4, 16, 16], + [2, 2, 3, 3, 3], + [1, 1, 1, 1, 1, 1], + [2, 2, 2], + [1, 2, 2, 8, 8], + 1, + id="conv3d_strides_pad", + ), + pytest.param( + [1, 2, 4, 16, 16], + [2, 2, 3, 3, 3], + [0, 0, 0, 0, 0, 0], + [2, 2, 2], + [1, 2, 1, 7, 7], + 1, + id="conv3d_strides_nopad", + ), + pytest.param( + [1, 4, 5, 5], + [1, 1, 3, 3], + [0, 0, 0, 0], + [1, 1], + [1, 1, 3, 3], + 4, + id="conv2d_grouped", + marks=pytest.mark.skip(reason="fix test dims"), + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_conv( + test_dir, backend, a_shape, kernel_shape, pads, strides, output_shape, group, dtype +): + Op = "Conv" + if len(a_shape) == 4: + version = 2 # 2d + elif len(a_shape) == 5: + version = 3 # 3d + + if version == 3 and backend in ["2PC_HE", "2PC_OT"]: + pytest.skip("[conv3d] Missing Support in SCI") + + a = np.random.randn(*a_shape).astype(dtype) + kernel = np.random.randn(*kernel_shape).astype(dtype) + + # Only need this for its shape + out = np.zeros(output_shape).astype(dtype) + + hw_kernel_shape = kernel_shape[-version:] + node = onnx.helper.make_node( + Op, + inputs=["a", "kernel"], + outputs=["output"], + kernel_shape=hw_kernel_shape, + pads=pads, + strides=strides, + group=group, + # Default values for other attributes: dilations=[1, 1], groups=1 + ) + + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[kernel], + tensor_names=["kernel"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + config.config["scale"] = 12 + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, kernel_shape, pads, strides, output_shape, output_padding", + [ + pytest.param( + [1, 1, 3, 3], + [1, 2, 3, 3], + [0, 0, 0, 0], + [1, 1], + [1, 2, 5, 5], + False, + id="convtranspose2d_nopad", + ), + pytest.param( + [1, 1, 3, 3], + [1, 2, 3, 3], + [1, 1, 1, 1], + [1, 1], + [1, 2, 3, 3], + False, + id="convtranspose2d_pad", + ), + pytest.param( + [1, 1, 3, 3], + [1, 2, 3, 3], + [0, 0, 0, 0], + [3, 2], + [1, 2, 10, 8], + True, + id="convtranspose2d_output_padding", + ), + pytest.param( + [1, 1, 3, 4, 5], + [1, 2, 3, 3, 3], + [0, 0, 0, 0, 0, 0], + [1, 1, 1], + [1, 2, 5, 6, 7], + False, + id="convtranspose3d_nopad", + ), + pytest.param( + [1, 1, 3, 4, 5], + [1, 2, 3, 3, 3], + [1, 1, 1, 1, 1, 1], + [1, 1, 1], + [1, 2, 3, 4, 5], + False, + id="convtranspose3d_pad", + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_convtranspose( + test_dir, + backend, + a_shape, + kernel_shape, + pads, + strides, + output_shape, + output_padding, + dtype, +): + Op = "ConvTranspose" + if len(a_shape) == 4: + version = 2 # 2d + elif len(a_shape) == 5: + version = 3 # 3d + + if version == 3 and backend in ["2PC_HE", "2PC_OT"]: + pytest.skip("[conv3dtranspose] Missing Support in SCI") + + a = np.random.randn(*a_shape).astype(dtype) + kernel = np.random.randn(*kernel_shape).astype(dtype) + + # Only need this for its shape + out = np.zeros(output_shape).astype(dtype) + + hw_kernel_shape = kernel_shape[-version:] + if not output_padding: + node = onnx.helper.make_node( + Op, + inputs=["a", "kernel"], + outputs=["output"], + kernel_shape=hw_kernel_shape, + pads=pads, + strides=strides + # Default values for other attributes: dilations=[1, 1], groups=1 + ) + else: + node = onnx.helper.make_node( + Op, + inputs=["a", "kernel"], + outputs=["output"], + kernel_shape=hw_kernel_shape, + pads=pads, + strides=strides, + output_padding=[1, 1] + # Default values for other attributes: dilations=[1, 1], groups=1 + ) + + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[kernel], + tensor_names=["kernel"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + config.config["scale"] = 12 + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return diff --git a/Athos/tests/onnx/unittests/test_non_linear.py b/Athos/tests/onnx/unittests/test_non_linear.py new file mode 100644 index 00000000..f144fe29 --- /dev/null +++ b/Athos/tests/onnx/unittests/test_non_linear.py @@ -0,0 +1,100 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" +import numpy as np +import onnx +from onnx import helper + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + + +@pytest.mark.parametrize( + "a_shape", + [ + ((4, 4, 4, 4)), # Normal + pytest.param( + (4, 4), + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +@pytest.mark.parametrize( + "Op", + [ + pytest.param("Relu", id="relu"), + pytest.param("Sqrt", marks=pytest.mark.skip(reason="Sqrt not implemented")), + pytest.param( + "Sigmoid", marks=pytest.mark.skip(reason="Sigmoid not implemented") + ), + pytest.param("Tanh", marks=pytest.mark.skip(reason="Tanh not implemented")), + ], +) +def test_non_linear(test_dir, backend, Op, a_shape, dtype): + a = np.random.randn(*a_shape).astype(dtype) + if Op == "Relu": + out = np.clip(a, 0, np.inf) + elif Op == "Sigmoid": + out = 1.0 / (1.0 + np.exp(np.negative(a))) + elif Op == "Tanh": + out = np.tanh(a) + elif Op == "Sqrt": + out = np.sqrt(a) + + node = helper.make_node( + Op, + inputs=[ + "a", + ], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return diff --git a/Athos/tests/onnx/unittests/test_shape_manipulation.py b/Athos/tests/onnx/unittests/test_shape_manipulation.py new file mode 100644 index 00000000..95f72a0c --- /dev/null +++ b/Athos/tests/onnx/unittests/test_shape_manipulation.py @@ -0,0 +1,44 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" +import numpy as np +import onnx +from onnx import helper + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + +# concat, flatten, pad, reshape, split, transpose diff --git a/Athos/tests/onnx/unittests/test_unaryops.py b/Athos/tests/onnx/unittests/test_unaryops.py new file mode 100644 index 00000000..25ad3b87 --- /dev/null +++ b/Athos/tests/onnx/unittests/test_unaryops.py @@ -0,0 +1,702 @@ +""" + +Authors: Pratik Bhatu. + +Copyright: +Copyright (c) 2021 Microsoft Research +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +""" +import numpy as np +import onnx +from onnx import helper +from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE +from onnx import TensorProto + +import pytest + +# Athos DIR +import sys, os +import optparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +from tests.utils import ( + ONNXConfig, + Compiler, + assert_almost_equal, + make_onnx_graph, + run_onnx, + Frontend, +) + + +@pytest.mark.parametrize( + "a_shape", + [ + ((4, 4, 4, 4)), # Normal[[2, 2], []] + pytest.param( + (2, 2), + marks=pytest.mark.skip(reason="non 4/5D input not handled"), + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +@pytest.mark.parametrize( + "Op", + [ + pytest.param("Neg", marks=pytest.mark.skip(reason="Neg not implemented")), + pytest.param("Floor", marks=pytest.mark.skip(reason="Floor not implemented")), + pytest.param( + "Identity", marks=pytest.mark.skip(reason="Identity not implemented") + ), + ], +) +def test_uop(test_dir, backend, Op, a_shape, dtype): + a = dtype(np.random.randn(*a_shape)) + if Op == "Neg": + out = np.negative(a) + elif Op == "Floor": + out = np.floor(a) + elif Op == "Identity": + out = np.negative(a) + node = helper.make_node( + Op, + inputs=["a"], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, axes, keepdims", + [ + pytest.param( + (3, 2, 2), + None, + 1, + # marks=pytest.mark.skip(reason="axes can't be none. keepdims has to be 0"), + id="default_axes_keepdims", + ), + pytest.param( + (3, 2, 2), + [1], + 0, + marks=pytest.mark.skip(reason="axes length has to be 2"), + id="do_not_keepdims", + ), + pytest.param( + (3, 2, 2), + [1], + 1, + marks=pytest.mark.skip(reason="keepdims has to be 0"), + id="keepdims", + ), + pytest.param( + (3, 2, 2, 4), + [1, 2], + 0, + marks=pytest.mark.skip(reason="segfault"), + id="reduce_nc", + ), + pytest.param((3, 2, 2, 4), [2, 3], 0, id="reduce_hw"), + pytest.param( + (3, 2, 2), + [-2], + 1, + marks=pytest.mark.skip(reason="don't support negative axes"), + id="negative_axes_keepdims", + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_reducemean(test_dir, backend, a_shape, axes, keepdims, dtype): + Op = "ReduceMean" + a = dtype(np.random.randn(*a_shape)) + out = np.mean( + a, axis=(None if axes is None else tuple(axes)), keepdims=keepdims == 1 + ) + + kwargs = {"name": Op, "inputs": ["a"], "outputs": ["out"], "keepdims": keepdims} + + if axes is not None: + kwargs["axes"] = axes + + node = helper.make_node(Op, **kwargs) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, start, end", + [ + pytest.param( + (4, 4, 4, 4), + None, + None, + marks=pytest.mark.skip(reason="bug in addOutputs"), + ), + pytest.param( + (2, 2), + None, + None, + marks=pytest.mark.skip(reason="bug in addOutputs"), + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_shape(test_dir, backend, a_shape, start, end, dtype): + Op = "Shape" + a = dtype(np.random.randn(*a_shape)) + out = np.array(a.shape[start:end]).astype(np.int64) + kwargs = {} + if start is not None: + kwargs["start"] = start + if end is not None: + kwargs["end"] = end + node = onnx.helper.make_node(Op, inputs=["a"], outputs=["out"], **kwargs) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, kernel_shape, pads, strides, auto_pad, output_shape", + [ + pytest.param( + [1, 3, 32], + [2], + [0, 0], + [1], + "NOTSET", + [1, 3, 31], + id="averagepool_1d_default", + marks=pytest.mark.skip( + reason="bug helper_processPool: list index out of range" + ), + ), + pytest.param( + [1, 3, 32, 32], + [2, 2], + [0, 0, 0, 0], + [1, 1], + "NOTSET", + [1, 3, 31, 31], + id="averagepool_2d_default", + ), + pytest.param( + [1, 3, 28, 28], + [3, 3], + [2, 2, 2, 2], + [1, 1], + "NOTSET", + [1, 3, 30, 30], + id="averagepool_2d_pads1", + marks=pytest.mark.skip(reason="bug correctness issue. 23% mismatch"), + ), + pytest.param( + [1, 1, 5, 5], + [5, 5], + [2, 2, 2, 2], + [1, 1], + "NOTSET", + [1, 1, 5, 5], + id="averagepool_2d_pads2", + marks=pytest.mark.skip(reason="bug correctness issue. 80-90% mismatch"), + ), + pytest.param( + [1, 1, 5, 5], + [3, 3], + None, + [2, 2], + "SAME_UPPER", + [1, 1, 3, 3], + id="averagepool_2d_same_upper", + marks=pytest.mark.skip(reason="non explicit padding not supported"), + ), + pytest.param( + [1, 3, 32, 32], + [2, 2], + None, + [1, 1], + "SAME_LOWER", + [1, 3, 32, 32], + id="averagepool_2d_same_lower", + marks=pytest.mark.skip(reason="non explicit padding not supported"), + ), + pytest.param( + [1, 3, 32, 32], + [5, 5], + [0, 0, 0, 0], + [3, 3], + "NOTSET", + [1, 3, 10, 10], + id="averagepool_2d_strides", + ), + pytest.param( + [1, 3, 32, 32, 32], + [2, 2, 2], + [0, 0, 0, 0, 0, 0], + [1, 1, 1], + "NOTSET", + [1, 3, 31, 31, 31], + id="averagepool_3d_default", + marks=pytest.mark.skip(reason="averagepool_3d not supported"), + ), + ], +) +# we dont support ceil_mode, count_include_pad +@pytest.mark.parametrize("dtype", [np.single]) +def test_avgpool( + test_dir, + backend, + a_shape, + kernel_shape, + pads, + strides, + auto_pad, + output_shape, + dtype, +): + Op = "AveragePool" + a = np.random.randn(*a_shape).astype(dtype) + # Only need this for its shape + out = np.zeros(output_shape).astype(dtype) + + kwargs = { + "inputs": ["a"], + "outputs": ["output"], + "kernel_shape": kernel_shape, + "strides": strides, + } + if auto_pad is "NOTSET": + kwargs["pads"] = pads + else: + kwargs["auto_pad"] = auto_pad + + node = onnx.helper.make_node(Op, **kwargs) + + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape, kernel_shape, pads, strides, auto_pad, output_shape", + [ + pytest.param( + [1, 3, 32], + [2], + [0, 0], + [1], + "NOTSET", + [1, 3, 31], + id="maxpool_1d_default", + marks=pytest.mark.skip( + reason="bug helper_processPool: list index out of range" + ), + ), + pytest.param( + [1, 3, 32, 32], + [2, 2], + [0, 0, 0, 0], + [1, 1], + "NOTSET", + [1, 3, 31, 31], + id="maxpool_2d_default", + ), + pytest.param( + [1, 3, 28, 28], + [3, 3], + [2, 2, 2, 2], + [1, 1], + "NOTSET", + [1, 3, 30, 30], + id="maxpool_2d_pads1", + marks=pytest.mark.skip(reason="bug correctness issue. 1.8% mismatch"), + ), + pytest.param( + [1, 1, 5, 5], + [5, 5], + [2, 2, 2, 2], + [1, 1], + "NOTSET", + [1, 1, 5, 5], + id="maxpool_2d_pads2", + ), + pytest.param( + [1, 1, 5, 5], + [3, 3], + None, + [2, 2], + "SAME_UPPER", + [1, 1, 3, 3], + id="maxpool_2d_same_upper", + marks=pytest.mark.skip(reason="non explicit padding not supported"), + ), + pytest.param( + [1, 3, 32, 32], + [2, 2], + None, + [1, 1], + "SAME_LOWER", + [1, 3, 32, 32], + id="maxpool_2d_same_lower", + marks=pytest.mark.skip(reason="non explicit padding not supported"), + ), + pytest.param( + [1, 3, 32, 32], + [5, 5], + [0, 0, 0, 0], + [3, 3], + "NOTSET", + [1, 3, 10, 10], + id="maxpool_2d_strides", + ), + pytest.param( + [1, 3, 32, 32, 32], + [2, 2, 2], + [0, 0, 0, 0, 0, 0], + [1, 1, 1], + "NOTSET", + [1, 3, 31, 31, 31], + id="maxpool_3d_default", + marks=pytest.mark.skip(reason="maxpool_3d not supported"), + ), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_maxpool( + test_dir, + backend, + a_shape, + kernel_shape, + pads, + strides, + auto_pad, + output_shape, + dtype, +): + Op = "MaxPool" + a = np.random.randn(*a_shape).astype(dtype) + # Only need this for its shape + out = np.zeros(output_shape).astype(dtype) + + kwargs = { + "inputs": ["a"], + "outputs": ["output"], + "kernel_shape": kernel_shape, + "strides": strides, + } + if auto_pad is "NOTSET": + kwargs["pads"] = pads + else: + kwargs["auto_pad"] = auto_pad + + node = onnx.helper.make_node(Op, **kwargs) + + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "a_shape", + [ + ((1, 3, 5, 5)), + ], +) +@pytest.mark.parametrize("dtype", [np.single]) +def test_global_avgpool(test_dir, backend, a_shape, dtype): + a = dtype(np.random.randn(*a_shape)) + out = np.mean(a, axis=tuple(range(2, np.ndim(a))), keepdims=True) + Op = "GlobalAveragePool" + node = helper.make_node( + Op, + inputs=[ + "a", + ], + outputs=["out"], + ) + graph = make_onnx_graph( + node, + inputs=[a], + outputs=[out], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [a]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([a]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "from_type, to_type", + [ + pytest.param("FLOAT", "FLOAT", id="cast_identity"), + pytest.param("FLOAT", "FLOAT16", id="cast_f32_f16"), + pytest.param("FLOAT", "DOUBLE", id="cast_f32_d"), + pytest.param("FLOAT16", "FLOAT", id="cast_f16_f32"), + pytest.param("FLOAT16", "DOUBLE", id="cast_f16_d"), + pytest.param("DOUBLE", "FLOAT", id="cast_d_f32"), + pytest.param("DOUBLE", "FLOAT16", id="cast_d_f16"), + pytest.param("FLOAT", "STRING", id="cast_f32_string"), + pytest.param("STRING", "FLOAT", id="cast_string_f32"), + ], +) +@pytest.mark.parametrize( + "compile_time", + [ + pytest.param(True), + pytest.param( + False, + marks=pytest.mark.skip( + reason="""we don't support runtime casting. + Only casts of constants at compile time + are supported and no-ops casts (Identity)""" + ), + ), + ], +) +@pytest.mark.skip(reason="[cast] Bug in add_outputs() - KeyError: 'output'") +def test_cast(test_dir, backend, from_type, to_type, compile_time): + Op = "Cast" + shape = (3, 4) + if "STRING" != from_type: + input = np.random.random_sample(shape).astype( + TENSOR_TYPE_TO_NP_TYPE[getattr(TensorProto, from_type)] + ) + if "STRING" == to_type: + # Converting input to str, then give it object dtype for generating script + ss = [] + for i in input.flatten(): + s = str(i).encode("utf-8") + su = s.decode("utf-8") + ss.append(su) + + output = np.array(ss).astype(object).reshape([3, 4]) + else: + output = input.astype(TENSOR_TYPE_TO_NP_TYPE[getattr(TensorProto, to_type)]) + else: + input = np.array( + [ + "0.47892547", + "0.48033667", + "0.49968487", + "0.81910545", + "0.47031248", + "0.816468", + "0.21087195", + "0.7229038", + "NaN", + "INF", + "+INF", + "-INF", + ], + dtype=np.dtype(object), + ).reshape([3, 4]) + output = input.astype(TENSOR_TYPE_TO_NP_TYPE[getattr(TensorProto, to_type)]) + node = onnx.helper.make_node( + Op, + inputs=["input"], + outputs=["output"], + to=getattr(TensorProto, to_type), + ) + if compile_time == True: + graph = make_onnx_graph( + node, + inputs=[], + outputs=[output], + tensors=[input], + tensor_names=["input"], + name=Op + "_test", + ) + expected_output = run_onnx(graph, []) + else: + graph = make_onnx_graph( + node, + inputs=[input], + outputs=[output], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, [input]) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + if compile_time == True: + mpc_output = compiler.compile_and_run([]) + else: + mpc_output = compiler.compile_and_run([input]) + + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return + + +@pytest.mark.parametrize( + "shape, attribute", + [ + pytest.param((3, 4), "value", id="constant_tensor"), + pytest.param((1), "value_float", id="constant_float_scalar"), + pytest.param((20), "value_floats", id="constant_floats"), + pytest.param((1), "value_int", id="constant_int_scalar"), + pytest.param((20), "value_ints", id="constant_ints"), + pytest.param( + (3, 4), + "sparse_value", + marks=pytest.mark.skip(reason="We don't support sparse tensors"), + ), + pytest.param( + (1), + "value_string", + marks=pytest.mark.skip(reason="We don't support string tensors"), + ), + pytest.param( + (20), + "value_strings", + marks=pytest.mark.skip(reason="We don't support string tensors"), + ), + ], +) +@pytest.mark.skip( + reason="""[constant] onnxsim gives runtime error. +Issue is it doesn't support opset version 13 of this node. +Need to fix onnxoptimize upstream""" +) +def test_constant(test_dir, backend, shape, attribute): + Op = "Constant" + kwargs = {} + print("Shape = ", shape) + if attribute == "value": + values = np.random.randn(*shape).astype(np.float32) + kwargs[attribute] = onnx.helper.make_tensor( + name="const_tensor", + data_type=onnx.TensorProto.FLOAT, + dims=values.shape, + vals=values.flatten().astype(float), + ) + elif attribute == "value_float": + values = np.random.randn(1).astype(np.float32) + kwargs[attribute] = values[0] + elif attribute == "value_floats": + values = np.random.randn(*shape).astype(np.float32) + kwargs[attribute] = values.flatten().astype(float) + elif attribute == "value_int": + values = np.array(np.random.randint(-(2 ** 32 - 1), 2 ** 32 - 1)).astype( + np.int64 + ) + kwargs[attribute] = int(values) + elif attribute == "value_ints": + values = np.random.randint(-(2 ** 32 - 1), 2 ** 32 - 1, shape).astype(np.int32) + print(values) + kwargs[attribute] = values.flatten().astype(int) + + kwargs["inputs"] = [] + kwargs["outputs"] = ["values"] + + node = helper.make_node(Op, **kwargs) + graph = make_onnx_graph( + node, + inputs=[], + outputs=[values], + tensors=[], + tensor_names=[], + name=Op + "_test", + ) + expected_output = run_onnx(graph, []) + config = ONNXConfig(backend).parse_io(graph) + compiler = Compiler(graph, config, test_dir, Frontend.ONNX) + mpc_output = compiler.compile_and_run([]) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) + return diff --git a/Athos/tests/tf/unittests/test_arith_binops.py b/Athos/tests/tf/unittests/test_arith_binops.py index 0c1ffee6..7dd5c7f5 100644 --- a/Athos/tests/tf/unittests/test_arith_binops.py +++ b/Athos/tests/tf/unittests/test_arith_binops.py @@ -58,10 +58,12 @@ def test_arith_binop(test_dir, backend, tfOp, a_shape, b_shape, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -90,10 +92,12 @@ def test_bias_add(test_dir, backend, a_shape, b_shape, data_format, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -150,10 +154,12 @@ def test_div(test_dir, backend, tfOp, a_val, divisor, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -197,12 +203,14 @@ def test_matmul( if not bisModel: feed_dict[b] = b_inp expected_output = sess.run(output, feed_dict=feed_dict) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) if not bisModel: config.add_input(b) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -227,8 +235,10 @@ def test_equal(test_dir, backend, a, b, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/tf/unittests/test_batchnorm.py b/Athos/tests/tf/unittests/test_batchnorm.py index b1ab6bfb..21d1b7d9 100644 --- a/Athos/tests/tf/unittests/test_batchnorm.py +++ b/Athos/tests/tf/unittests/test_batchnorm.py @@ -63,8 +63,10 @@ def test_fused_batch_norm( with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) assert expected_output is not None - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/tf/unittests/test_convolution.py b/Athos/tests/tf/unittests/test_convolution.py index 08dc5216..f66df628 100644 --- a/Athos/tests/tf/unittests/test_convolution.py +++ b/Athos/tests/tf/unittests/test_convolution.py @@ -57,11 +57,13 @@ def test_conv(test_dir, backend, tfOp, a_shape, kernel_shape, strides, padding, with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) config.config["scale"] = 12 compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -89,11 +91,13 @@ def test_depthwise_conv( with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) config.config["scale"] = 12 compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -143,9 +147,11 @@ def test_conv_transpose( with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) config.config["scale"] = 12 compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/tf/unittests/test_non_linear.py b/Athos/tests/tf/unittests/test_non_linear.py index 230d2b3c..725bbfd3 100644 --- a/Athos/tests/tf/unittests/test_non_linear.py +++ b/Athos/tests/tf/unittests/test_non_linear.py @@ -74,11 +74,13 @@ def test_non_linear(test_dir, backend, tfOp, a_shape, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) assert expected_output is not None - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) config.config["scale"] = 12 compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -94,8 +96,10 @@ def test_softmax(test_dir, backend, a_shape, axis, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) assert expected_output is not None - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/tf/unittests/test_shape_manipulation.py b/Athos/tests/tf/unittests/test_shape_manipulation.py index 44d23c87..6f931e62 100644 --- a/Athos/tests/tf/unittests/test_shape_manipulation.py +++ b/Athos/tests/tf/unittests/test_shape_manipulation.py @@ -56,10 +56,12 @@ def test_reshape(test_dir, backend, a_shape, out_shape, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) assert expected_output is not None - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -77,10 +79,12 @@ def test_transpose(test_dir, backend, a_shape, perm, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -114,11 +118,11 @@ def test_split(test_dir, backend, a_shape, num_or_size_splits, axis, dtype): else: tf_output = output tf_expected_output = expected_output - config = Config(backend).add_input(a).add_output(tf_output) + config = TFConfig(backend).add_input(a).add_output(tf_output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) assert_almost_equal( - tf_output=tf_expected_output, mpc_tensor=mpc_output, precision=2 + model_output=tf_expected_output, mpc_tensor=mpc_output, precision=2 ) return @@ -149,10 +153,12 @@ def test_squeeze(test_dir, backend, a_shape, axis, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -174,10 +180,12 @@ def test_slice(test_dir, backend, a_shape, begin, size, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -200,10 +208,12 @@ def test_concat(test_dir, backend, a_shape, b_shape, axis, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp, b: b_inp}) - config = Config(backend).add_input(a).add_input(b).add_output(output) + config = TFConfig(backend).add_input(a).add_input(b).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp, b_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -229,10 +239,12 @@ def test_expand_dims(test_dir, backend, a_shape, axis, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -287,10 +299,12 @@ def test_pad(test_dir, backend, a_shape, paddings, mode, constant_values, dtype) with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -310,8 +324,10 @@ def test_tile(test_dir, backend, a_shape, multiples, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/tf/unittests/test_unaryops.py b/Athos/tests/tf/unittests/test_unaryops.py index 026ec145..5ccf7a11 100644 --- a/Athos/tests/tf/unittests/test_unaryops.py +++ b/Athos/tests/tf/unittests/test_unaryops.py @@ -61,10 +61,12 @@ def test_uop(test_dir, backend, tfOp, a_shape, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -93,10 +95,12 @@ def test_reduce(test_dir, backend, tfOp, a_shape, axis, keepdims, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -122,10 +126,12 @@ def test_argmax(test_dir, backend, a_shape, axis, dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -164,10 +170,12 @@ def test_pool( with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -202,10 +210,12 @@ def test_cast(test_dir, backend, a_shape, from_dtype, to_dtype): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output, feed_dict={a: a_inp}) - config = Config(backend).add_input(a).add_output(output) + config = TFConfig(backend).add_input(a).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([a_inp]) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return @@ -217,8 +227,10 @@ def test_fill(test_dir, backend, a_shape, value): with tf.compat.v1.Session(graph=graph) as sess: expected_output = sess.run(output) - config = Config(backend).add_output(output) + config = TFConfig(backend).add_output(output) compiler = Compiler(graph, config, test_dir) mpc_output = compiler.compile_and_run([], timeoutSeconds=60) - assert_almost_equal(tf_output=expected_output, mpc_tensor=mpc_output, precision=2) + assert_almost_equal( + model_output=expected_output, mpc_tensor=mpc_output, precision=2 + ) return diff --git a/Athos/tests/utils.py b/Athos/tests/utils.py index 784c0ba1..68e02134 100644 --- a/Athos/tests/utils.py +++ b/Athos/tests/utils.py @@ -26,21 +26,35 @@ import os import shutil import re +from enum import Enum, auto sys.path.append(os.path.join(os.path.dirname(__file__), "..")) import CompileTFGraph +import CompileONNXGraph import CompilerScripts.parse_config as parse_config from CompilerScripts.get_output import convert_raw_output_to_np +import onnx +from onnx import helper +from onnx.backend.test.case import node +from onnx import numpy_helper +import onnxruntime as ort + +from io import BytesIO + import numpy as np import subprocess import threading -class Config: +class Frontend(Enum): + Tensorflow = auto() + ONNX = auto() + + +class BaseConfig: def __init__(self, mode): self.config = { - "model_name": "model.pb", "scale": 23, "bitlength": 64, "save_weights": True, @@ -63,6 +77,12 @@ def __init__(self, mode): else: assert False, "Mode has to be one of CPP/3PC/2PC_OT/2PC_HE" + +class TFConfig(BaseConfig): + def __init__(self, mode): + super().__init__(mode) + self.config["model_name"] = "model.pb" + def add_input(self, tensor_op): input_name = tensor_op.op.name shape = tensor_op.shape.as_list() @@ -84,6 +104,32 @@ def add_output(self, tensor_op): return self +class ONNXConfig(BaseConfig): + def __init__(self, mode): + super().__init__(mode) + self.config["model_name"] = "model.onnx" + + def parse_io(self, graph): + for inp in graph.input: + input_name = inp.name + shape = [i.dim_value for i in inp.type.tensor_type.shape.dim] + shape_string = ",".join(map(str, shape)) + inputs = self.config.get("input_tensors") + if inputs == None: + self.config["input_tensors"] = {input_name: shape_string} + else: + self.config["input_tensors"][input_name] = shape_string + + for out in graph.output: + output_name = out.name + outputs = self.config.get("output_tensors") + if outputs == None: + self.config["output_tensors"] = [output_name] + else: + self.config["output_tensors"].append(output_name) + return self + + def get_params(config): return parse_config.parse_config(config) @@ -96,12 +142,15 @@ def make_dir(path): return -def save_graph(graph_def, config, test_dir): +def save_graph(graph_def, config, test_dir, frontend): fname = config["model_name"] fpath = os.path.join(test_dir, fname) - with open(fpath, "wb") as f: - f.write(graph_def.SerializeToString()) - print("\n\nfile name: ", f.name, "\n\n\n") + if frontend == Frontend.Tensorflow: + with open(fpath, "wb") as f: + f.write(graph_def.SerializeToString()) + elif frontend == Frontend.ONNX: + model = onnx.helper.make_model(graph_def, producer_name="onnx-test") + onnx.save(model, fpath) config["model_name"] = fpath return @@ -191,26 +240,94 @@ def run(self, inputs, timeoutSeconds): return convert_raw_output_to_np(raw_output, self.bitlength, self.scale) +# Compiler(graph, config, test_dir, frontend=Frontend.ONNX) class Compiler: - def __init__(self, graph, config, test_dir): - self.graph_def = graph.as_graph_def() + def __init__(self, graph, config, test_dir, frontend=Frontend.Tensorflow): + if frontend == Frontend.Tensorflow: + self.graph_def = graph.as_graph_def() + else: + self.graph_def = graph self.config = config.config self.test_dir = test_dir + self.frontend = frontend def compile_and_run(self, inputs, timeoutSeconds=40): - save_graph(self.graph_def, self.config, self.test_dir) + save_graph(self.graph_def, self.config, self.test_dir, self.frontend) params = get_params(self.config) - print(params) - (output_program, model_weight_file) = CompileTFGraph.generate_code( - params, role="server", debug=False - ) + if self.frontend == Frontend.Tensorflow: + (output_program, model_weight_file) = CompileTFGraph.generate_code( + params, role="server", debug=False + ) + else: + (output_program, model_weight_file) = CompileONNXGraph.generate_code( + params, role="server", debug=False + ) prog = Program(output_program, model_weight_file, params, self.test_dir) output = prog.run(inputs, timeoutSeconds) return output -def assert_almost_equal(tf_output, mpc_tensor, precision): - if tf_output.shape == (0,): +def assert_almost_equal(model_output, mpc_tensor, precision): + if model_output.shape == (0,): return - np.testing.assert_almost_equal(tf_output.flatten(), mpc_tensor, decimal=precision) + np.testing.assert_almost_equal( + model_output.flatten(), mpc_tensor, decimal=precision + ) return + + +def make_onnx_graph( + inp_node, # type: onnx.NodeProto + inputs, # type: Sequence[np.ndarray] + outputs, # type: Sequence[np.ndarray] + tensors, # type: Sequence[np.ndarray] The other tensors that appear in the graph + tensor_names, + name, +): + present_inputs = [x for x in inp_node.input if (x != "")] + present_outputs = [x for x in inp_node.output if (x != "")] + inputs_vi = [ + node._extract_value_info(arr, arr_name) + for arr, arr_name in zip(inputs, present_inputs) + ] + outputs_vi = [ + node._extract_value_info(arr, arr_name) + for arr, arr_name in zip(outputs, present_outputs) + ] + initializer = [ + numpy_helper.from_array(arr, arr_name) + for arr, arr_name in zip(tensors, tensor_names) + ] + value_info = [ + node._extract_value_info(arr, arr_name) + for arr, arr_name in zip(tensors, tensor_names) + ] + graph = onnx.helper.make_graph( + nodes=[inp_node], + name=name, + inputs=inputs_vi, + outputs=outputs_vi, + initializer=initializer, + value_info=value_info, + ) + return graph + + +def run_onnx(graph, inputs): + model = onnx.helper.make_model(graph, producer_name="onnx-test") + print(model.opset_import) + model.opset_import[0].version = 13 + onnx.checker.check_model(model) + model_file = BytesIO() + onnx.save_model(model, model_file) + sess = ort.InferenceSession(model_file.getvalue()) + feed_dict = {} + for i in range(0, len(inputs)): + input_name = sess.get_inputs()[i].name + feed_dict[input_name] = inputs[i] + output_names = [i.name for i in graph.output] + output = sess.run(output_names, feed_dict) + model_file.close() + if len(output) == 1: + return output[0] + return output