From 3b2b293a01413b152aa7c93c1e2667a91f29f57b Mon Sep 17 00:00:00 2001 From: Yuanjing Shi <ys26@tyler.cs.illinois.edu> Date: Tue, 18 Feb 2020 23:13:01 -0600 Subject: [PATCH] parsing onnx model with building hape and cfg, not finished --- hpvm/projects/onnx/.gitignore | 4 + hpvm/projects/onnx/onnx2hpvm/__init__.py | 0 hpvm/projects/onnx/onnx2hpvm/frontend.py | 156 +++++++++++++++++++++++ hpvm/projects/onnx/onnx2hpvm/mnist.py | 23 ++++ hpvm/projects/onnx/onnx_environment.yml | 2 +- hpvm/projects/onnx/setup.py | 4 +- hpvm/projects/onnx/src/mnist.ipynb | 30 ++--- 7 files changed, 196 insertions(+), 23 deletions(-) create mode 100644 hpvm/projects/onnx/.gitignore create mode 100644 hpvm/projects/onnx/onnx2hpvm/__init__.py create mode 100644 hpvm/projects/onnx/onnx2hpvm/frontend.py create mode 100644 hpvm/projects/onnx/onnx2hpvm/mnist.py diff --git a/hpvm/projects/onnx/.gitignore b/hpvm/projects/onnx/.gitignore new file mode 100644 index 0000000000..2f4db75f1d --- /dev/null +++ b/hpvm/projects/onnx/.gitignore @@ -0,0 +1,4 @@ +build/ +dist/ +onnx2hpvm.egg-info/ +.ipynb_checkpoints/ diff --git a/hpvm/projects/onnx/onnx2hpvm/__init__.py b/hpvm/projects/onnx/onnx2hpvm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hpvm/projects/onnx/onnx2hpvm/frontend.py b/hpvm/projects/onnx/onnx2hpvm/frontend.py new file mode 100644 index 0000000000..1a14731876 --- /dev/null +++ b/hpvm/projects/onnx/onnx2hpvm/frontend.py @@ -0,0 +1,156 @@ +import sys +import numpy as np +import os + +class Node(object): + def __init__(self, name, shape, dtype): + self._name = name + self._shape = shape if shape else {} + self_dtype = dtype + +class GraphBuilder(object): + + def __init__(self, model, shape, dtype, opset): + self._nodes = {} + self._params = {} + self._renames = {} + self._num_input = 0 + self._num_param = 0 + self._dtype = dtype + self._graph = model.graph + self._opset = opset + self._shape = shape if shape else self._build_shape() + + def build_cfg(self): + # parse parameters + for init_tensor in self._graph.initializer: + if not init_tensor.name.strip(): + raise ValueError("Tensor's name is required.") + print(init_tensor.name) + print("###############################") + self._params[init_tensor.name] = self._parse_array(init_tensor) + print(self._params[init_tensor.name]) + print("###############################") + self._nodes[init_tensor.name] = Node(init_tensor.name, + self._params[init_tensor.name].shape, + self._params[init_tensor.name].dtype) + + # parse inputs + # from onnx v0.2, GraphProto.input has type ValueInfoProto, + # and the name is 'i.name' + for i in self._graph.input: + i_name = self._parse_value_proto(i) + d_type = self._parse_dtype(i, 'float32') + if i_name in self._params: + # i is a param instead of input + self._num_param += 1 + self._params[i_name] = self._params.pop(i_name) + self._nodes[i_name] = Node(i_name, + self._params[i_name].shape, + self._params[i_name].dtype) + else: + self._num_input += 1 + if i_name in self._shape: + tshape = self._shape[i_name] + else: + raise ValueError("Must provide an input shape for `{0}`.".format(i_name)) + if isinstance(self._dtype, dict): + dtype = self._dtype[i_name] if i_name in self._dtype else d_type + else: + dtype = d_type + self._nodes[i_name] = Node(i_name, + tshape, + dtype) + print(self._nodes) + + def _build_shape(self): + shape = {} + for input in self._graph.input: + print (input.name, end=": ") + # get type of input tensor + tensor_type = input.type.tensor_type + # check if it has a shape: + if (tensor_type.HasField("shape")): + shape[input.name] = tensor_type.shape + print(shape) + return shape + + def _parse_array(self, tensor_proto): + try: + from onnx.numpy_helper import to_array + except ImportError as e: + raise ImportError( + "Unable to import onnx which is required {}".format(e)) + np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims)) + return np_array + + def _parse_value_proto(self, value_proto): + """Parse ValueProto or raw str.""" + try: + name = value_proto.name + except AttributeError: + name = value_proto + return name + + def _parse_dtype(self, value_proto, dtype): + """Parse dtype.""" + try: + from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE + return TENSOR_TYPE_TO_NP_TYPE[value_proto.type.tensor_type.elem_type].name + except AttributeError: + return dtype + +def convert_to_hpvm(model, + shape=None, + dtype="float32", + opset=None): + """Converting an onnx model to equivalent HPVM IR + + ONNX graphs are represented as Python Protobuf objects. + The companion parameters will be handled automatically. + However, the input names from onnx graph is vague, mixing inputs and + network weights/bias such as "1", "2"... + For convenience, we rename the `real` input names to "input_0", + "input_1"... And renaming parameters to "param_0", "param_1"... + + Parameters + ---------- + model : protobuf object + ONNX ModelProto after ONNX v1.1.0 + + shape : dict of str to tuple, optional + The input shape to the graph + + dtype : str or dict of str to str + The input types to the graph + + opset : int, optional + Override to autodetected opset. + This can be helpful for some testing. + + Returns + ------- + + """ + try: + from onnx import checker, onnx_cpp2py_export + if hasattr(checker, 'check_model'): + # try use onnx's own model checker before converting any model + try: + checker.check_model(model) + except onnx_cpp2py_export.checker.ValidationError as e: + import warnings + # the checker is a bit violent about errors, so simply print warnings here + warnings.warn(str(e)) + except ImportError: + pass + print("onnx model is checked valid.") + graph = model.graph + if opset is None: + try: + opset = model.opset_import[0].version if model.opset_import else 1 + except AttributeError: + opset = 1 # default opset version set to 1 if not specified + print("opset version: ", opset) + gb = GraphBuilder(model, shape, dtype, opset) + gb.build_cfg() \ No newline at end of file diff --git a/hpvm/projects/onnx/onnx2hpvm/mnist.py b/hpvm/projects/onnx/onnx2hpvm/mnist.py new file mode 100644 index 0000000000..925fc8dc84 --- /dev/null +++ b/hpvm/projects/onnx/onnx2hpvm/mnist.py @@ -0,0 +1,23 @@ +import os +import sys +import numpy as np +import onnx +import glob +from onnxruntime.backend.backend import OnnxRuntimeBackend as backend + +from onnx import numpy_helper, version_converter + +# onnx2hpvm modules +from frontend import convert_to_hpvm + +model = onnx.load('../models/mnist/mnist.onnx') +test_data_dir = '../models/mnist/test_data_set_0' +# print('The model before conversion:\n{}'.format(model)) + +# A full list of supported adapters can be found here: +# https://github.com/onnx/onnx/blob/master/onnx/version_converter.py#L21 +# Apply the version conversion on the original model +# converted_model = version_converter.convert_version(model, 12) + +# print('The model after conversion:\n{}'.format(converted_model)) +convert_to_hpvm(model) \ No newline at end of file diff --git a/hpvm/projects/onnx/onnx_environment.yml b/hpvm/projects/onnx/onnx_environment.yml index b0aa451b76..1f1d42b762 100644 --- a/hpvm/projects/onnx/onnx_environment.yml +++ b/hpvm/projects/onnx/onnx_environment.yml @@ -1,4 +1,4 @@ -name: approxhpvm_keras +name: onnx_frontend channels: - pytorch - conda-forge diff --git a/hpvm/projects/onnx/setup.py b/hpvm/projects/onnx/setup.py index bf007ea334..8405aca7ec 100644 --- a/hpvm/projects/onnx/setup.py +++ b/hpvm/projects/onnx/setup.py @@ -2,11 +2,11 @@ from setuptools import setup setup( - name='onnx_frontend', + name='onnx2hpvm', version='1.0', description='HPVM onnx frontend modules', author='Yuanjing Shi', author_email='ys26@illinois.edu', - packages=['onnx_frontend'], + packages=['onnx2hpvm'], install_requires=[], ) diff --git a/hpvm/projects/onnx/src/mnist.ipynb b/hpvm/projects/onnx/src/mnist.ipynb index 59cd82bf11..1964ebe399 100644 --- a/hpvm/projects/onnx/src/mnist.ipynb +++ b/hpvm/projects/onnx/src/mnist.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -13,12 +13,15 @@ "import glob\n", "from onnxruntime.backend.backend import OnnxRuntimeBackend as backend\n", "\n", - "from onnx import numpy_helper" + "from onnx import numpy_helper\n", + "\n", + "# onnx2hpvm modules\n", + "from onnx2hpvm.onnx_translator import from_onnx_to_hpvm" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -28,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -37,20 +40,6 @@ "text": [ "1\n" ] - }, - { - "ename": "AssertionError", - "evalue": "\nArrays are not almost equal to 7 decimals\n\nMismatch: 80%\nMax absolute difference: 0.00292969\nMax relative difference: 4.834256e-06\n x: array([[ 975.6701 , -618.72394 , 6574.5684 , 668.02893 ,\n -917.27094 , -1671.6359 , -1952.7599 , -61.549873,\n -777.17664 , -1439.5316 ]], dtype=float32)\n y: array([[ 975.67035 , -618.7242 , 6574.5654 , 668.0283 ,\n -917.27106 , -1671.6361 , -1952.7599 , -61.549576,\n -777.17645 , -1439.5316 ]], dtype=float32)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-19-2c0c3f208847>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;31m# Compare the results with reference outputs.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mref_o\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mo\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mref_outputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtesting\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_almost_equal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mref_o\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/numpy/testing/_private/utils.py\u001b[0m in \u001b[0;36massert_almost_equal\u001b[0;34m(actual, desired, decimal, err_msg, verbose)\u001b[0m\n\u001b[1;32m 570\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mactual\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m\\\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 571\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdesired\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 572\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0massert_array_almost_equal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mactual\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdesired\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdecimal\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merr_msg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 573\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 574\u001b[0m \u001b[0;31m# If one of desired/actual is not finite, handle it specially here:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/numpy/testing/_private/utils.py\u001b[0m in \u001b[0;36massert_array_almost_equal\u001b[0;34m(x, y, decimal, err_msg, verbose)\u001b[0m\n\u001b[1;32m 1005\u001b[0m assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,\n\u001b[1;32m 1006\u001b[0m \u001b[0mheader\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Arrays are not almost equal to %d decimals'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mdecimal\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1007\u001b[0;31m precision=decimal)\n\u001b[0m\u001b[1;32m 1008\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1009\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/numpy/testing/_private/utils.py\u001b[0m in \u001b[0;36massert_array_compare\u001b[0;34m(comparison, x, y, err_msg, verbose, header, precision, equal_nan, equal_inf)\u001b[0m\n\u001b[1;32m 817\u001b[0m \u001b[0mverbose\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mverbose\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheader\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheader\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 818\u001b[0m names=('x', 'y'), precision=precision)\n\u001b[0;32m--> 819\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mAssertionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 820\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 821\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAssertionError\u001b[0m: \nArrays are not almost equal to 7 decimals\n\nMismatch: 80%\nMax absolute difference: 0.00292969\nMax relative difference: 4.834256e-06\n x: array([[ 975.6701 , -618.72394 , 6574.5684 , 668.02893 ,\n -917.27094 , -1671.6359 , -1952.7599 , -61.549873,\n -777.17664 , -1439.5316 ]], dtype=float32)\n y: array([[ 975.67035 , -618.7242 , 6574.5654 , 668.0283 ,\n -917.27106 , -1671.6361 , -1952.7599 , -61.549576,\n -777.17645 , -1439.5316 ]], dtype=float32)" - ] } ], "source": [ @@ -78,9 +67,10 @@ "# Run the model on the backend\n", "outputs = list(backend.run_model(model, inputs))\n", "\n", + "#from_onnx_to_hpvm(model)\n", "# Compare the results with reference outputs.\n", - "for ref_o, o in zip(ref_outputs, outputs):\n", - " np.testing.assert_almost_equal(ref_o, o)" + "#for ref_o, o in zip(ref_outputs, outputs):\n", + "# np.testing.assert_almost_equal(ref_o, o)" ] }, { -- GitLab