From 7b3ac07fb97659a147a4b4eb507635871f49be82 Mon Sep 17 00:00:00 2001 From: David Fan <30608893+jiafatom@users.noreply.github.com> Date: Thu, 17 Dec 2020 13:26:13 -0800 Subject: [PATCH] Update to opset 13 (#156) --- onnxconverter_common/__init__.py | 2 +- onnxconverter_common/_opt_const_folding.py | 2 + onnxconverter_common/onnx_ex.py | 4 +- onnxconverter_common/onnx_ops.py | 73 +++++++++++++++--- onnxconverter_common/optimizer.py | 86 ++++++++++++++++------ 5 files changed, 135 insertions(+), 32 deletions(-) diff --git a/onnxconverter_common/__init__.py b/onnxconverter_common/__init__.py index 9109cab..00eb404 100644 --- a/onnxconverter_common/__init__.py +++ b/onnxconverter_common/__init__.py @@ -8,7 +8,7 @@ This framework performs optimization for ONNX models and includes common utilities for ONNX converters. """ -__version__ = "1.7.0" +__version__ = "1.8.0" __author__ = "Microsoft" __producer__ = "OnnxMLTools" __producer_version__ = __version__ diff --git a/onnxconverter_common/_opt_const_folding.py b/onnxconverter_common/_opt_const_folding.py index d38eb65..e3e876f 100644 --- a/onnxconverter_common/_opt_const_folding.py +++ b/onnxconverter_common/_opt_const_folding.py @@ -151,6 +151,8 @@ def _OnSub(self, node, inputs): def _OnUnsqueeze(self, node, inputs): axes = OnnxGraphContext.get_attribute(node, 'axes') + if axes is None: + axes = inputs[1] shape_in = inputs[0].shape dims_out = len(shape_in) + len(axes) shape_in = iter(shape_in) diff --git a/onnxconverter_common/onnx_ex.py b/onnxconverter_common/onnx_ex.py index 547295c..29bc7b5 100644 --- a/onnxconverter_common/onnx_ex.py +++ b/onnxconverter_common/onnx_ex.py @@ -9,9 +9,11 @@ from .metadata_props import add_metadata_props DEFAULT_OPSET_NUMBER = 12 # The maximum opset supported by the converter in the code branch. +# From https://github.com/onnx/onnx/blob/master/docs/Versioning.md OPSET_TO_IR_VERSION = { 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, - 7: 3, 8: 4, 9: 4, 10: 5, 11: 6, 12: 7 + 7: 3, 8: 3, 9: 4, 10: 5, 11: 6, 12: 7, + 13: 7 } diff --git a/onnxconverter_common/onnx_ops.py b/onnxconverter_common/onnx_ops.py index 7924b8e..07af827 100644 --- a/onnxconverter_common/onnx_ops.py +++ b/onnxconverter_common/onnx_ops.py @@ -765,6 +765,33 @@ def apply_reciprocal(scope, input_name, output_name, container, operator_name=No _apply_unary_operation(scope, 'Reciprocal', input_name, output_name, container, operator_name=operator_name) +def apply_reducesum(scope, input_name, output_name, container, operator_name=None, axes=None, keepdims=None, rank=0): + name = _create_name_or_use_existing_one(scope, 'ReduceSum', operator_name) + if container.target_opset < 13: + if container.target_opset < 11: + op_version = 1 + axes = [axis if axis >= 0 else axis + rank + 1 for axis in axes] + else: + op_version = 11 + container.add_node('ReduceSum', input_name, output_name, name=name, + op_version=op_version, axes=axes, keepdims=keepdims) + else: + if not isinstance(input_name, list): + input_name = [input_name] + op_version = 13 + if isinstance(axes, str): + container.add_node('ReduceSum', input_name + [axes], output_name, + op_version=op_version, name=name, keepdims=keepdims) + elif axes is None or len(axes) == 0: + container.add_node('ReduceSum', input_name, output_name, + op_version=op_version, name=name, keepdims=keepdims) + else: + axes_name = scope.get_unique_variable_name(name + '_reducesum') + container.add_initializer(axes_name, onnx_proto.TensorProto.INT64, [len(axes)], axes) + container.add_node('ReduceSum', input_name + [axes_name], output_name, + op_version=op_version, name=name, keepdims=keepdims) + + def apply_relu(scope, input_name, output_name, container, operator_name=None): _apply_unary_operation(scope, 'Relu', input_name, output_name, container, operator_name) @@ -860,8 +887,10 @@ def apply_selu(scope, input_name, output_name, container, operator_name=None, al _apply_unary_operation(scope, 'Selu', input_name, output_name, container, operator_name, alpha=alpha, gamma=gamma) -def apply_softmax(scope, input_name, output_name, container, operator_name=None, axis=1): +def apply_softmax(scope, input_name, output_name, container, operator_name=None, axis=None): name = _create_name_or_use_existing_one(scope, 'Softmax', operator_name) + if axis is None: + axis = 1 if container.target_opset < 13 else -1 container.add_node('Softmax', input_name, output_name, name=name, axis=axis) @@ -969,12 +998,25 @@ def apply_split(scope, input_name, output_names, container, operator_name=None, op_version = 1 elif container.target_opset < 11: op_version = 2 - else: + elif container.target_opset < 13: op_version = 11 + else: + op_version = 13 attrs = {'name': name} if split is not None: - attrs['split'] = split + if container.target_opset < 13: + attrs['split'] = split + else: + if not isinstance(input_name, list): + input_name = [input_name] + if isinstance(split, str): + split_name = split + else: + split_name = scope.get_unique_variable_name(name + '_split') + container.add_initializer(split_name, onnx_proto.TensorProto.INT64, [len(split)], split) + input_name = input_name + [split_name] + if axis is not None: attrs['axis'] = axis @@ -988,17 +1030,30 @@ def apply_sqrt(scope, input_name, output_name, container, operator_name=None): def _apply_squeeze_unsqueeze(scope, input_name, output_name, container, squeeze_str, operator_name=None, axes=None, rank=0): name = _create_name_or_use_existing_one(scope, squeeze_str, operator_name) - if container.target_opset < 11: - op_version = 1 - axes = [axis if axis >= 0 else axis + rank + 1 for axis in axes] + if container.target_opset < 13: + if container.target_opset < 11: + op_version = 1 + axes = [axis if axis >= 0 else axis + rank + 1 for axis in axes] + else: + op_version = 11 + container.add_node(squeeze_str, input_name, output_name, name=name, op_version=op_version, axes=axes) else: - op_version = 11 - container.add_node(squeeze_str, input_name, output_name, name=name, op_version=op_version, axes=axes) + op_version = 13 + if not isinstance(input_name, list): + input_name = [input_name] + if isinstance(axes, str): + container.add_node(squeeze_str, input_name + [axes], output_name, op_version=op_version, name=name) + elif len(axes) == 0: + container.add_node(squeeze_str, input_name, output_name, op_version=op_version, name=name) + else: + axes_name = scope.get_unique_variable_name(name + '_axes') + container.add_initializer(axes_name, onnx_proto.TensorProto.INT64, [len(axes)], axes) + container.add_node(squeeze_str, input_name + [axes_name], output_name, op_version=op_version, name=name) def apply_squeeze(scope, input_name, output_name, container, operator_name=None, axes=None, rank=0): if axes is None: - axes = [0] + axes = [] _apply_squeeze_unsqueeze(scope, input_name, output_name, container, 'Squeeze', operator_name, axes, rank) diff --git a/onnxconverter_common/optimizer.py b/onnxconverter_common/optimizer.py index 8947afb..96a7584 100644 --- a/onnxconverter_common/optimizer.py +++ b/onnxconverter_common/optimizer.py @@ -15,7 +15,7 @@ class LinkedNode(object): reserved_names_in_graph = frozenset() - def __init__(self, node=None, in_n=None, out_n=None, tensors_n=None): + def __init__(self, node=None, in_n=None, out_n=None, tensors_n=None, target_opset=None): self.origin = node # type: onnx_proto.NodeProto if in_n is None and node is not None: in_n = node.input @@ -29,6 +29,7 @@ def __init__(self, node=None, in_n=None, out_n=None, tensors_n=None): self.successor = [] self.attributes = {} self.unique_name = self.origin.name if self.origin and self.origin.name else str(uuid4().hex) + self.target_opset = target_opset def __repr__(self): return "name: {}, node: <{}>".format(self.unique_name, str(self.origin) if self.origin else 'None') @@ -283,11 +284,11 @@ def add_precedence(self, pre, tname): assert tname in self.input.values() and tname in pre.output.values() @staticmethod - def build_from_onnx(onnx_nodes, nchw_inputs, inputs, outputs, initializers=None): + def build_from_onnx(onnx_nodes, nchw_inputs, inputs, outputs, initializers=None, target_opset=None): view = [] var_map = {} for o_ in onnx_nodes: - ln = LinkedNode(o_) + ln = LinkedNode(o_, target_opset=target_opset) view.append(ln) for var_ in o_.output: assert var_map.get(var_) is None @@ -305,9 +306,10 @@ def build_from_onnx(onnx_nodes, nchw_inputs, inputs, outputs, initializers=None) assert var_ == '' or var_ in inputs if initializer_map is not None and var_ in initializer_map: target = LinkedNode(out_n=[var_], - tensors_n=[initializer_map[var_]]) # create an empty node as input + tensors_n=[initializer_map[var_]], + target_opset=target_opset) # create an empty node as input else: - target = LinkedNode(out_n=[var_]) + target = LinkedNode(out_n=[var_], target_opset=target_opset) new_output = var_ + '_nhwc' if var_ in nchw_inputs: nnode = LinkedNode( @@ -316,7 +318,8 @@ def build_from_onnx(onnx_nodes, nchw_inputs, inputs, outputs, initializers=None) [var_], [new_output], name='Transpose_nchw_' + str(count_nchw), - perm=[0, 2, 3, 1])) + perm=[0, 2, 3, 1]), + target_opset=target_opset) count_nchw = count_nchw + 1 var_map[new_output] = nnode nnode.add_precedence(target, var_) @@ -330,7 +333,7 @@ def build_from_onnx(onnx_nodes, nchw_inputs, inputs, outputs, initializers=None) for n_ in view: # add a dummy output node. for var_ in n_.origin.output: if var_ in outputs: - LinkedNode(in_n=[var_]).add_precedence(n_, var_) + LinkedNode(in_n=[var_], target_opset=target_opset).add_precedence(n_, var_) return view + additional_nodes @@ -642,6 +645,18 @@ def _get_pad_from_Pad(node): return pads +def _get_axes_from_Squeeze_Unsqueeze(node): + axes = node.get_attribute('axes') + if axes is None: + if len(node.origin.input) == 2: + axes_tensor = node.get_precedence_by_idx(1) + if axes_tensor is None: + axes = numpy_helper.to_array(node.initializers[0]).tolist() + else: + axes = numpy_helper.to_array(node.get_precedence_by_idx(1).tensors[0]).tolist() + return axes + + class MergePadConvSolution(Solution): def __init__(self, begin, begin_n, end_p, end): @@ -968,11 +983,13 @@ class MergeSqueezeUnsqueezeOptimizer(object): @staticmethod def find(node): if node.origin.op_type == 'Squeeze' and len(node.successor) == 1: - axes_0 = node.get_attribute('axes') + axes_0 = _get_axes_from_Squeeze_Unsqueeze(node) next = node.successor[0] - if next.origin is not None and next.origin.op_type == 'Unsqueeze' and len(next.successor) == 1: - axes_1 = next.get_attribute('axes') - if axes_0 == axes_1: + flag = next.origin is not None and axes_0 is not None \ + and next.origin.op_type == 'Unsqueeze' and len(next.successor) == 1 + if flag: + axes_1 = _get_axes_from_Squeeze_Unsqueeze(next) + if axes_1 is not None and axes_0 == axes_1: solution = Solution(node.get_precedence_by_idx(0), node, next, next.successor[0]) return solution @@ -993,6 +1010,10 @@ def _transpose_pass(node): if node.element_wise: return True + if node.origin.op_type in ['Squeeze', 'Unsqueeze']: + axes = _get_axes_from_Squeeze_Unsqueeze(node) + return axes is not None + if node.origin.op_type in _transpose_pass_type_set: return True @@ -1131,9 +1152,8 @@ def _process_transpose_pad(node, node_list, node_transpose_pass_name, cur_perm_m def _process_transpose_squeeze(node, node_list, node_transpose_pass_name, cur_perm_map): cur_perm = cur_perm_map[node.get_precedence_by_idx(0).unique_name] - squeeze_axes = node.get_attribute('axes') + squeeze_axes = _get_axes_from_Squeeze_Unsqueeze(node) squeeze_axes = [cur_perm[idx_] for idx_ in squeeze_axes] - attrs = {'axes': squeeze_axes} temp_perm = cur_perm.copy() sub_list = [0] * len(cur_perm) for axis in squeeze_axes: @@ -1145,18 +1165,39 @@ def _process_transpose_squeeze(node, node_list, node_transpose_pass_name, cur_pe temp_perm[idx_] = temp_perm[idx_] - sub_list[temp_perm[idx_]] target_perm = temp_perm new_node_name = node.origin.name + '_squeeze_' + str(PushTransposeSolution.transpose_number) - node.origin = helper.make_node('Squeeze', node.origin.input, node.origin.output, new_node_name, **attrs) + if node.target_opset < 13: + attrs = {'axes': squeeze_axes} + node.origin = helper.make_node('Squeeze', node.origin.input, node.origin.output, new_node_name, **attrs) + else: + squeeze_axes = np.asarray(squeeze_axes, dtype=np.int64) + add_initilizer = numpy_helper.from_array(squeeze_axes, name=node.origin.name + '_initializer_' + str( + PushTransposeSolution.transpose_number)) + node.initializers = [add_initilizer] + pred_1 = node.get_precedence_by_idx(1) + if pred_1 is not None: + node.precedence.remove(pred_1) + node.in_redirect(node.get_input_by_idx(1), add_initilizer.name) PushTransposeSolution.transpose_number += 1 cur_perm_map[node.unique_name] = target_perm return cur_perm_map def _process_transpose_unsqueeze(node, node_list, node_transpose_pass_name, cur_perm_map): - unsqueeze_axes = node.get_attribute('axes') + unsqueeze_axes = _get_axes_from_Squeeze_Unsqueeze(node) assert len(unsqueeze_axes) == 1 - attrs = {'axes': unsqueeze_axes} new_node_name = node.origin.name + '_unsqueeze_' + str(PushTransposeSolution.transpose_number) - node.origin = helper.make_node('Unsqueeze', node.origin.input, node.origin.output, new_node_name, **attrs) + if node.target_opset < 13: + attrs = {'axes': unsqueeze_axes} + node.origin = helper.make_node('Unsqueeze', node.origin.input, node.origin.output, new_node_name, **attrs) + else: + unsqueeze_axes = np.asarray(unsqueeze_axes, dtype=np.int64) + add_initilizer = numpy_helper.from_array(unsqueeze_axes, name=node.origin.name + '_initializer_' + str( + PushTransposeSolution.transpose_number)) + node.initializers = [add_initilizer] + pred_1 = node.get_precedence_by_idx(1) + if pred_1 is not None: + node.precedence.remove(pred_1) + node.in_redirect(node.get_input_by_idx(1), add_initilizer.name) PushTransposeSolution.transpose_number += 1 prev_perm = cur_perm_map[node.precedence[0].unique_name] cur_axes = unsqueeze_axes[0] @@ -1190,7 +1231,8 @@ def _process_transpose_pass_node(node, node_list, node_transpose_pass_name, cur_ node_list, cur_perm_map = _process_transpose_pass_broadcast(node, node_list, node_transpose_pass_name, cur_perm_map) elif node.origin.op_type in type_func_map: - cur_perm_map = type_func_map[node.origin.op_type](node, node_list, node_transpose_pass_name, cur_perm_map) + cur_perm_map = type_func_map[node.origin.op_type](node, node_list, node_transpose_pass_name, + cur_perm_map) else: for idx_ in range(len(node.precedence)): pred_name = node.get_precedence_by_idx(idx_).unique_name @@ -1238,7 +1280,7 @@ def apply(self, node_list): if not success: return None, False elif node.origin.op_type == 'Unsqueeze': - unsqueeze_axes = node.get_attribute('axes') + unsqueeze_axes = _get_axes_from_Squeeze_Unsqueeze(node) if unsqueeze_axes and len(unsqueeze_axes) > 1: return None, False @@ -1762,7 +1804,8 @@ def optimize_onnx(onnx_nodes, nchw_inputs=None, inputs=None, outputs=None, targe node_list = LinkedNode.build_from_onnx(onnx_nodelist, nchw_inputs if nchw_inputs else [], [] if inputs is None else [i_.name for i_ in inputs], - [] if outputs is None else [o_.name for o_ in outputs]) + [] if outputs is None else [o_.name for o_ in outputs], + target_opset=target_opset) node_list = _process_optimization(node_list, target_opset) if target_opset is None or target_opset < 9: @@ -1826,7 +1869,8 @@ def optimize_onnx_graph(onnx_nodes, nchw_inputs=None, inputs=None, outputs=None, nchw_inputs if nchw_inputs else [], [] if in_inputs is None else [i_.name for i_ in in_inputs], [] if outputs is None else [o_.name for o_ in outputs], - initializers) + initializers, + target_opset=target_opset) node_list = _process_optimization(node_list, target_opset) node_list = [n_ for n_ in node_list if n_.origin is not None]