From fc2b6121a48f9072066106c574877573366f8f90 Mon Sep 17 00:00:00 2001 From: pinto0309 Date: Sat, 28 Jan 2023 16:32:08 +0900 Subject: [PATCH 1/4] `Softmax` Detect conversion errors in axis and identify the axis with the smallest possible error and replace it. --- README.md | 2 +- json_samples/replace_MobileFormer-e9.json | 54 ----------------- onnx2tf/__init__.py | 2 +- onnx2tf/onnx2tf.py | 24 ++++---- onnx2tf/ops/ConvTranspose.py | 3 +- onnx2tf/ops/Softmax.py | 73 ++++++++++++++++++++++- onnx2tf/utils/common_functions.py | 57 +++++++++++++++++- 7 files changed, 141 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index a3bfe774..5f76968b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Video speed is adjusted approximately 50 times slower than actual speed. $ docker run --rm -it \ -v `pwd`:/workdir \ -w /workdir \ - ghcr.io/pinto0309/onnx2tf:1.5.32 + ghcr.io/pinto0309/onnx2tf:1.5.33 or diff --git a/json_samples/replace_MobileFormer-e9.json b/json_samples/replace_MobileFormer-e9.json index d70a6306..6aa6013f 100644 --- a/json_samples/replace_MobileFormer-e9.json +++ b/json_samples/replace_MobileFormer-e9.json @@ -55,12 +55,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_126", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_129", "param_target": "attributes", @@ -73,12 +67,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_297", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_300", "param_target": "attributes", @@ -91,12 +79,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_468", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_471", "param_target": "attributes", @@ -109,12 +91,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_638", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_641", "param_target": "attributes", @@ -127,12 +103,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_809", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_812", "param_target": "attributes", @@ -145,12 +115,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_979", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_982", "param_target": "attributes", @@ -163,12 +127,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_1148", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_1151", "param_target": "attributes", @@ -181,12 +139,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_1319", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_1322", "param_target": "attributes", @@ -199,12 +151,6 @@ "param_name": "perm", "values": [1,2,0,3] }, - { - "op_name": "Softmax_1449", - "param_target": "attributes", - "param_name": "axis", - "values": 3 - }, { "op_name": "Transpose_1452", "param_target": "attributes", diff --git a/onnx2tf/__init__.py b/onnx2tf/__init__.py index dc137fcc..81e21903 100644 --- a/onnx2tf/__init__.py +++ b/onnx2tf/__init__.py @@ -1,3 +1,3 @@ from onnx2tf.onnx2tf import convert, main -__version__ = '1.5.32' +__version__ = '1.5.33' diff --git a/onnx2tf/onnx2tf.py b/onnx2tf/onnx2tf.py index c877e79a..3ddf82d7 100644 --- a/onnx2tf/onnx2tf.py +++ b/onnx2tf/onnx2tf.py @@ -9,7 +9,6 @@ import sys sys.setrecursionlimit(10000) import ast -import copy import json import logging import warnings @@ -45,6 +44,8 @@ onnx_tf_tensor_validation, weights_export, download_test_image_data, + get_tf_model_inputs, + get_tf_model_outputs, ) from onnx2tf.utils.colors import Color from sng4onnx import generate as op_name_auto_generate @@ -599,7 +600,7 @@ def convert( output_op_name \ for output_op_name in output_names_to_interrupt_model_conversion ] - onnx_graph = extraction( + onnx_graph: onnx.ModelProto = extraction( input_op_names=[graph_input.name for graph_input in graph.inputs], output_op_names=output_names, onnx_graph=onnx_graph, @@ -720,16 +721,13 @@ def convert( ) # List "optype"="Input" - inputs = [ - layer_info['op'] \ - for layer_info in tf_layers_dict.values() \ - if layer_info['optype'] == 'Input' - ] - outputs = [ - layer_info['tf_node'] \ - for opname, layer_info in tf_layers_dict.items() \ - if opname in output_names - ] + inputs = get_tf_model_inputs( + tf_layers_dict=tf_layers_dict, + ) + outputs = get_tf_model_outputs( + tf_layers_dict=tf_layers_dict, + output_names=output_names, + ) model = tf.keras.Model(inputs=inputs, outputs=outputs) if not non_verbose: @@ -1177,7 +1175,7 @@ def representative_dataset_gen(): equal_nan=True, ) - check_results: Dict[str, List[np.ndarray, bool]] + check_results: Dict[str, List[np.ndarray, int, float|int]] { onnx_output_name: [ onnx_tensor, diff --git a/onnx2tf/ops/ConvTranspose.py b/onnx2tf/ops/ConvTranspose.py index 8d78a48e..2de275c0 100644 --- a/onnx2tf/ops/ConvTranspose.py +++ b/onnx2tf/ops/ConvTranspose.py @@ -3,6 +3,7 @@ random.seed(0) import numpy as np np.random.seed(0) +import onnx import tensorflow as tf import onnx_graphsurgeon as gs from onnx2tf.utils.common_functions import ( @@ -138,7 +139,7 @@ def make_node( f'Install onnxruntime. pip install onnxruntime' ) sys.exit(1) - onnx_graph = kwargs['onnx_graph'] + onnx_graph: onnx.ModelProto = kwargs['onnx_graph'] convtranspose_output = dummy_onnx_inference( onnx_graph=onnx_graph, output_names=[graph_node_output.name], diff --git a/onnx2tf/ops/Softmax.py b/onnx2tf/ops/Softmax.py index aeb7cbc9..7c1857cd 100644 --- a/onnx2tf/ops/Softmax.py +++ b/onnx2tf/ops/Softmax.py @@ -1,7 +1,9 @@ +import sys import random random.seed(0) import numpy as np np.random.seed(0) +import onnx import tensorflow as tf import onnx_graphsurgeon as gs from onnx2tf.utils.common_functions import ( @@ -14,7 +16,12 @@ get_replacement_parameter, pre_process_transpose, post_process_transpose, + dummy_onnx_inference, + dummy_tf_inference, + get_tf_model_inputs, + onnx_tf_tensor_validation, ) +from typing import List, Any, Dict @print_node_info @@ -98,11 +105,75 @@ def make_node( and before_trans_shape != after_trans_shape: tf_layers_dict[graph_node_output.name].pop('nhwc') + # Detect conversion errors in axis and identify the axis + # with the smallest possible error and replace it. + # ONNX dummy inference + onnx_graph: onnx.ModelProto = kwargs['onnx_graph'] + check_axes = reversed([idx for idx in range(tensor_rank)]) + dummy_onnx_outputs: List[np.ndarray] = dummy_onnx_inference( + onnx_graph=onnx_graph, + output_names=[graph_node_output.name], + ) + del onnx_graph + # Search for the axis with the smallest error + min_abs_err = sys.maxsize + min_abs_err_axis: int = axis + tf_model_inputs = get_tf_model_inputs( + tf_layers_dict=tf_layers_dict, + ) + for check_axis in check_axes: + # TF dummy inference + val_model = tf.keras.Model( + inputs=tf_model_inputs, + outputs=[ + tf.nn.softmax( + logits=input_tensor, + axis=check_axis, + name=graph_node.name, + ) + ], + ) + tf_tensor_infos: Dict[Any] = dummy_tf_inference( + model=val_model, + inputs=tf_model_inputs, + ) + del val_model + # Validation + onnx_tensor_infos = { + output_name: dummy_onnx_output \ + for output_name, dummy_onnx_output in zip([graph_node_output.name], dummy_onnx_outputs) + } + onnx_tf_output_pairs = { + (oi[0], ti[0]): (oi[1], ti[1]) \ + for oi, ti in zip(onnx_tensor_infos.items(), tf_tensor_infos.items()) + } + """ + check_results: Dict[str, List[np.ndarray, int, float|int]] + { + onnx_output_name: [ + onnx_tensor, + matched_flg, <--- 0: Unmatched, 1: Matched, 2: Skipped (Deleted or Shape Unmatched) + max_abs_err, + ] + } + """ + check_results = onnx_tf_tensor_validation( + output_pairs=onnx_tf_output_pairs, + rtol=0.0, + atol=0.0, + ) + result_err = sum([val[2] for val in check_results.values()]) + if result_err < min_abs_err: + min_abs_err = result_err + min_abs_err_axis = check_axis + if min_abs_err < 1e-3: + break + # Generation of TF OP tf_layers_dict[graph_node_output.name]['tf_node'] = \ tf.nn.softmax( logits=input_tensor, - axis=axis, + axis=min_abs_err_axis, name=graph_node.name, ) diff --git a/onnx2tf/utils/common_functions.py b/onnx2tf/utils/common_functions.py index f0d6cd36..97b34184 100644 --- a/onnx2tf/utils/common_functions.py +++ b/onnx2tf/utils/common_functions.py @@ -2965,7 +2965,7 @@ def onnx_tf_tensor_validation( Returns ---------- - check_results: Dict[str, List[np.ndarray, int]] + check_results: Dict[str, List[np.ndarray, int, float|int]] Tensor Comparison Results { onnx_output_name: [ @@ -3273,8 +3273,7 @@ def y_tile( def calc_tf_pooling_pads(input_shape, kernel, strides, func): - """ - Calculate how much padding is needed for tensorflow mode 'SAME' + """Calculate how much padding is needed for tensorflow mode 'SAME'. Parameters ---------- @@ -3316,3 +3315,55 @@ def calc_tf_pooling_pads(input_shape, kernel, strides, func): same_pads.extend(same_pads_end) return same_pads + + +def get_tf_model_inputs( + *, + tf_layers_dict: dict, +) -> List[Any]: + """Get a list of input OPs for a TensorFlow model. + + Parameters + ---------- + tf_layers_dict: dict + Graph structure of TensorFlow models + + Returns + ------- + tf_model_inputs: List + List of input OPs for TensorFlow model + """ + tf_model_inputs = [ + layer_info['op'] \ + for layer_info in tf_layers_dict.values() \ + if layer_info['optype'] == 'Input' + ] + return tf_model_inputs + + +def get_tf_model_outputs( + *, + tf_layers_dict: dict, + output_names: List[str], +) -> List[Any]: + """Get a list of output OPs for a TensorFlow model. + + Parameters + ---------- + tf_layers_dict: dict + Graph structure of TensorFlow models + + output_names: List[str] + Name of ONNX output OP to be extracted + + Returns + ------- + tf_model_outputs: List + List of output OPs for TensorFlow model + """ + tf_model_outputs = [ + layer_info['tf_node'] \ + for opname, layer_info in tf_layers_dict.items() \ + if opname in output_names + ] + return tf_model_outputs From 9c07163b7b51b2c1f1f9086d78705e3f1ec0d17c Mon Sep 17 00:00:00 2001 From: pinto0309 Date: Sat, 28 Jan 2023 16:48:54 +0900 Subject: [PATCH 2/4] Bug fixes --- onnx2tf/ops/ConvTranspose.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/onnx2tf/ops/ConvTranspose.py b/onnx2tf/ops/ConvTranspose.py index 2de275c0..8d78a48e 100644 --- a/onnx2tf/ops/ConvTranspose.py +++ b/onnx2tf/ops/ConvTranspose.py @@ -3,7 +3,6 @@ random.seed(0) import numpy as np np.random.seed(0) -import onnx import tensorflow as tf import onnx_graphsurgeon as gs from onnx2tf.utils.common_functions import ( @@ -139,7 +138,7 @@ def make_node( f'Install onnxruntime. pip install onnxruntime' ) sys.exit(1) - onnx_graph: onnx.ModelProto = kwargs['onnx_graph'] + onnx_graph = kwargs['onnx_graph'] convtranspose_output = dummy_onnx_inference( onnx_graph=onnx_graph, output_names=[graph_node_output.name], From 1f45158d9c835c451489ca263d82ad040a914920 Mon Sep 17 00:00:00 2001 From: pinto0309 Date: Sat, 28 Jan 2023 16:54:59 +0900 Subject: [PATCH 3/4] Add onnxruntime, protobuf --- .github/workflows/test-models.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-models.yml b/.github/workflows/test-models.yml index e3d59286..720b9319 100644 --- a/.github/workflows/test-models.yml +++ b/.github/workflows/test-models.yml @@ -29,6 +29,8 @@ jobs: pip install tensorflow==2.10.0 pip install nvidia-pyindex pip install onnx-graphsurgeon + pip install onnxruntime + pip install protobuf==3.20.3 pip install onnxsim pip install sng4onnx pip install -e . From a8b6f1792f14b9e3adffbcf38cf5b59c787a392e Mon Sep 17 00:00:00 2001 From: pinto0309 Date: Sat, 28 Jan 2023 18:39:05 +0900 Subject: [PATCH 4/4] Support for post-processing of faster_rcnn-10.onnx --- onnx2tf/ops/Softmax.py | 113 +++++++++++++++--------------- onnx2tf/utils/common_functions.py | 2 +- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/onnx2tf/ops/Softmax.py b/onnx2tf/ops/Softmax.py index 7c1857cd..f50c32bc 100644 --- a/onnx2tf/ops/Softmax.py +++ b/onnx2tf/ops/Softmax.py @@ -108,66 +108,69 @@ def make_node( # Detect conversion errors in axis and identify the axis # with the smallest possible error and replace it. # ONNX dummy inference - onnx_graph: onnx.ModelProto = kwargs['onnx_graph'] - check_axes = reversed([idx for idx in range(tensor_rank)]) - dummy_onnx_outputs: List[np.ndarray] = dummy_onnx_inference( - onnx_graph=onnx_graph, - output_names=[graph_node_output.name], - ) - del onnx_graph - # Search for the axis with the smallest error min_abs_err = sys.maxsize min_abs_err_axis: int = axis - tf_model_inputs = get_tf_model_inputs( - tf_layers_dict=tf_layers_dict, - ) - for check_axis in check_axes: - # TF dummy inference - val_model = tf.keras.Model( - inputs=tf_model_inputs, - outputs=[ - tf.nn.softmax( - logits=input_tensor, - axis=check_axis, - name=graph_node.name, - ) - ], + try: + onnx_graph: onnx.ModelProto = kwargs['onnx_graph'] + check_axes = reversed([idx for idx in range(tensor_rank)]) + dummy_onnx_outputs: List[np.ndarray] = dummy_onnx_inference( + onnx_graph=onnx_graph, + output_names=[graph_node_output.name], ) - tf_tensor_infos: Dict[Any] = dummy_tf_inference( - model=val_model, - inputs=tf_model_inputs, + del onnx_graph + # Search for the axis with the smallest error + tf_model_inputs = get_tf_model_inputs( + tf_layers_dict=tf_layers_dict, ) - del val_model - # Validation - onnx_tensor_infos = { - output_name: dummy_onnx_output \ - for output_name, dummy_onnx_output in zip([graph_node_output.name], dummy_onnx_outputs) - } - onnx_tf_output_pairs = { - (oi[0], ti[0]): (oi[1], ti[1]) \ - for oi, ti in zip(onnx_tensor_infos.items(), tf_tensor_infos.items()) - } - """ - check_results: Dict[str, List[np.ndarray, int, float|int]] - { - onnx_output_name: [ - onnx_tensor, - matched_flg, <--- 0: Unmatched, 1: Matched, 2: Skipped (Deleted or Shape Unmatched) - max_abs_err, - ] + for check_axis in check_axes: + # TF dummy inference + val_model = tf.keras.Model( + inputs=tf_model_inputs, + outputs=[ + tf.nn.softmax( + logits=input_tensor, + axis=check_axis, + name=graph_node.name, + ) + ], + ) + tf_tensor_infos: Dict[Any] = dummy_tf_inference( + model=val_model, + inputs=tf_model_inputs, + ) + del val_model + # Validation + onnx_tensor_infos = { + output_name: dummy_onnx_output \ + for output_name, dummy_onnx_output in zip([graph_node_output.name], dummy_onnx_outputs) } - """ - check_results = onnx_tf_tensor_validation( - output_pairs=onnx_tf_output_pairs, - rtol=0.0, - atol=0.0, - ) - result_err = sum([val[2] for val in check_results.values()]) - if result_err < min_abs_err: - min_abs_err = result_err - min_abs_err_axis = check_axis - if min_abs_err < 1e-3: - break + onnx_tf_output_pairs = { + (oi[0], ti[0]): (oi[1], ti[1]) \ + for oi, ti in zip(onnx_tensor_infos.items(), tf_tensor_infos.items()) + } + """ + check_results: Dict[str, List[np.ndarray, int, float|int]] + { + onnx_output_name: [ + onnx_tensor, + matched_flg, <--- 0: Unmatched, 1: Matched, 2: Skipped (Deleted or Shape Unmatched) + max_abs_err, + ] + } + """ + check_results = onnx_tf_tensor_validation( + output_pairs=onnx_tf_output_pairs, + rtol=0.0, + atol=0.0, + ) + result_err = sum([val[2] for val in check_results.values()]) + if result_err < min_abs_err: + min_abs_err = result_err + min_abs_err_axis = check_axis + if min_abs_err < 1e-3: + break + except tf.errors.InvalidArgumentError as ex: + pass # Generation of TF OP tf_layers_dict[graph_node_output.name]['tf_node'] = \ diff --git a/onnx2tf/utils/common_functions.py b/onnx2tf/utils/common_functions.py index 97b34184..39143747 100644 --- a/onnx2tf/utils/common_functions.py +++ b/onnx2tf/utils/common_functions.py @@ -2812,7 +2812,7 @@ def dummy_onnx_inference( # reduce all axes except batch axis gs_graph.nodes[i].attrs['axes'] = [ i for i in range(1, len(gs_graph.nodes[i].inputs[0].shape)) - ] + ] if len(gs_graph.nodes[i].inputs[0].shape) > 1 else [0] # instead, modify onnx graph manually gs_graph.outputs = []