From 6d11992de8824693617955c72d08b7f213d4247e Mon Sep 17 00:00:00 2001 From: channingss Date: Wed, 11 Sep 2019 14:46:36 +0800 Subject: [PATCH] support transformer --- setup.py | 7 +- x2paddle/convert.py | 5 +- x2paddle/decoder/onnx_decoder.py | 68 +++++----- .../InstanceNormalization.py | 2 +- .../op_mapper/onnx_custom_layer/__init__.py | 4 +- x2paddle/op_mapper/onnx_directly_map.py | 10 +- x2paddle/op_mapper/onnx_op_mapper.py | 119 +++++++++++------- 7 files changed, 125 insertions(+), 90 deletions(-) diff --git a/setup.py b/setup.py index 3470161..b68f1a8 100644 --- a/setup.py +++ b/setup.py @@ -23,4 +23,9 @@ setuptools.setup( "Operating System :: OS Independent", ], license='Apache 2.0', - entry_points={'console_scripts': ['x2paddle=x2paddle.convert:main']}) + entry_points={ + 'console_scripts': [ + 'x2paddle=x2paddle.convert:main', + 'onnx_infer=x2paddle.decoder.onnx_infer:main' + ] + }) diff --git a/x2paddle/convert.py b/x2paddle/convert.py index b80e7a9..a682357 100644 --- a/x2paddle/convert.py +++ b/x2paddle/convert.py @@ -120,9 +120,6 @@ def tf2paddle(model_path, mapper.save_inference_model(save_dir) -0 - - def caffe2paddle(proto, weight, save_dir, caffe_proto): from x2paddle.decoder.caffe_decoder import CaffeDecoder from x2paddle.op_mapper.caffe_op_mapper import CaffeOpMapper @@ -154,7 +151,7 @@ def onnx2paddle(model_path, save_dir): print("Now translating model from onnx to paddle.") from x2paddle.decoder.onnx_decoder import ONNXDecoder - model = ONNXDecoder(model_path) + model = ONNXDecoder(model_path, save_dir) from x2paddle.op_mapper.onnx_op_mapper import ONNXOpMapper mapper = ONNXOpMapper(model) diff --git a/x2paddle/decoder/onnx_decoder.py b/x2paddle/decoder/onnx_decoder.py index 4dff108..17b8682 100644 --- a/x2paddle/decoder/onnx_decoder.py +++ b/x2paddle/decoder/onnx_decoder.py @@ -29,6 +29,7 @@ from onnx.helper import ValueInfoProto import numpy as np from copy import deepcopy import logging as _logging +import os default_op_domain = 'ai.onnx' _logger = _logging.getLogger(__name__) @@ -131,15 +132,16 @@ class ONNXGraphDataNode(GraphNode): class ONNXGraph(Graph): - def __init__(self, graph, onnx_model): - super(ONNXGraph, self).__init__(graph) + def __init__(self, onnx_model, save_dir): + super(ONNXGraph, self).__init__(onnx_model.graph) self.onnx_model = onnx_model self.initializer = {} self.place_holder_nodes = list() self.get_place_holder_nodes() - - self.value_infos = self.inferred_model_value_info(graph) + self.save_dir = save_dir + self.value_infos = self.inferred_model_value_info(self.model) self.results_of_inference = dict() + self.is_inference = False def get_inner_nodes(self): """ @@ -176,9 +178,6 @@ class ONNXGraph(Graph): """ build topo_sort of ONNX model """ - data_nodes = self.place_holder_nodes - self.get_results_of_inference_rt(self.onnx_model, data_nodes) - for layer in self.model.node: node = ONNXGraphNode(layer) self.node_map[layer.name] = node @@ -187,13 +186,21 @@ class ONNXGraph(Graph): value_info = self.value_infos[opt] if len(value_info['shape'] ) == 0 or value_info['dtype'] is None: + if self.is_inference == False: + self.get_results_of_inference_rt( + self.onnx_model, self.place_holder_nodes) + self.is_inference = True _, dtype, shape = self.get_dynamic_shape(opt) - node.dtype = dtype node.out_shapes.append(shape) + node.dtype = dtype else: node.dtype = value_info['dtype'] node.out_shapes.append(value_info['shape']) else: + if self.is_inference == False: + self.get_results_of_inference_rt( + self.onnx_model, self.place_holder_nodes) + self.is_inference = True _, dtype, shape = self.get_dynamic_shape(opt) node.dtype = dtype node.out_shapes.append(shape) @@ -232,8 +239,8 @@ class ONNXGraph(Graph): if opt == in_node: self.connect(nd.name, layer_name) flag = 1 - print(nd.name + '->' + layer_name) node.which_child[nd.name] = idx + self.node_map[nd.name].index = 0 break if flag == 1: break @@ -250,7 +257,9 @@ class ONNXGraph(Graph): def get_input_node(self, node, idx=0, copy=False): if len(node.which_child) == 0: - return super(ONNXGraph, self).get_node(node.inputs[idx], copy) + ipt_node = super(ONNXGraph, self).get_node(node.inputs[idx], copy) + return ipt_node + else: ipt_node = super(ONNXGraph, self).get_node(node.inputs[idx], copy) if ipt_node.layer_name in node.which_child: @@ -312,11 +321,13 @@ class ONNXGraph(Graph): import torch version = torch.__version__ if '1.1.0' not in version: - print("your model have dynamic graph, torch==1.1.0 is required") + print( + "shape of somenode need inference, torch==1.1.0 is required" + ) return except: print( - "your model have dynamic graph, we use caff2 to inference graph, please use \"pip install torch==1.1.0\"." + "shape of somenode need inference, we use caffe2 to inference graph, please use \"pip install torch==1.1.0\"." ) return from x2paddle.decoder.onnx_backend import prepare @@ -326,6 +337,7 @@ class ONNXGraph(Graph): value_info = self.value_infos[data_node] ipt = np.random.random(value_info['shape']).astype('float32') inputs.append(ipt) + print(ipt.shape) outputs = [] for node in model.graph.node: value_info = helper.make_tensor_value_info(node.name, @@ -347,13 +359,11 @@ class ONNXGraph(Graph): return def get_results_of_inference_rt(self, model, data_nodes): - - import onnxruntime as rt - inputs = [] for data_node in data_nodes: value_info = self.value_infos[data_node] - ipt = np.random.random(value_info['shape']).astype('float32') + ipt = np.random.random(value_info['shape']).astype( + value_info['dtype']) inputs.append(ipt) model = onnx.shape_inference.infer_shapes(model) @@ -363,30 +373,26 @@ class ONNXGraph(Graph): model.graph.ClearField('output') model.graph.output.MergeFrom(outputs) - onnx.save(model, './onnx_model_infer.onnx') - - sess = rt.InferenceSession('./onnx_model_infer.onnx') - inputs_dict = {} - - for i, ipt in enumerate(inputs): - inputs_dict[sess.get_inputs()[i].name] = ipt - - res = sess.run(None, input_feed=inputs_dict) - - for idx, info in enumerate(outputs): - self.results_of_inference[info.name] = res[idx] + if not os.path.exists(self.save_dir): + os.makedirs(self.save_dir) + onnx.save(model, os.path.join(self.save_dir, 'onnx_model_infer.onnx')) + np.save(os.path.join(self.save_dir, 'input_data.npy'), inputs) + os.system('onnx_infer --save_dir=' + self.save_dir) + # res = np.load(os.path.join(self.save_dir, 'results_of_inference.npy'),allow_pickle=True) + # for idx, info in enumerate(outputs): + # self.results_of_inference[info.name] = res[idx] return def get_dynamic_shape(self, layer): """ get dynamic shape from infer_result """ - output = self.results_of_inference[layer] + output = np.load(os.path.join(self.save_dir, layer + '.npy')) return output.tolist(), output.dtype, output.shape class ONNXDecoder(object): - def __init__(self, onnx_model): + def __init__(self, onnx_model, save_dir): model = onnx.load(onnx_model) print('model ir_version: {}, op version: {}'.format( model.ir_version, model.opset_import[0].version)) @@ -405,7 +411,7 @@ class ONNXDecoder(object): self.model = model graph = model.graph - self.onnx_graph = ONNXGraph(graph, model) + self.onnx_graph = ONNXGraph(model, save_dir) self.onnx_graph.build() def build_value_refs(self, nodes): diff --git a/x2paddle/op_mapper/onnx_custom_layer/InstanceNormalization.py b/x2paddle/op_mapper/onnx_custom_layer/InstanceNormalization.py index 569727e..0d767d7 100644 --- a/x2paddle/op_mapper/onnx_custom_layer/InstanceNormalization.py +++ b/x2paddle/op_mapper/onnx_custom_layer/InstanceNormalization.py @@ -24,7 +24,7 @@ def InstanceNormalization_layer(inputs, name=None): epsilon = 1e-5 input_ = inputs[0] mean = fluid.layers.reduce_mean(input_, dim=[2, 3], keep_dim=True) - var = fluid.layers.reduce_mean(fluid.layers.square(inputs - mean), + var = fluid.layers.reduce_mean(fluid.layers.square(input_ - mean), dim=[2, 3], keep_dim=True) if name is not None: diff --git a/x2paddle/op_mapper/onnx_custom_layer/__init__.py b/x2paddle/op_mapper/onnx_custom_layer/__init__.py index eabb907..9246054 100644 --- a/x2paddle/op_mapper/onnx_custom_layer/__init__.py +++ b/x2paddle/op_mapper/onnx_custom_layer/__init__.py @@ -100,9 +100,9 @@ def make_custom_child_func(node): """ get the code which implement the custom layer function """ layer_type = node.layer_type - assert layer_type in custom_layers, "layer[%s] not exist in custom layers" % ( - layer_type) child_func = custom_layers[layer_type]['child_func'] + if child_func is None: + return None, child_func import inspect return inspect.getsource(child_func), child_func diff --git a/x2paddle/op_mapper/onnx_directly_map.py b/x2paddle/op_mapper/onnx_directly_map.py index b04c632..48963d1 100644 --- a/x2paddle/op_mapper/onnx_directly_map.py +++ b/x2paddle/op_mapper/onnx_directly_map.py @@ -29,12 +29,6 @@ default_op_mapping = { 'Gather': ['gather', ['X'], ['Out'], dict(axis='')], 'Shape': ['shape', ['X'], ['Out']], - 'Mul': ['elementwise_mul', ['X', 'Y'], ['Out'], - dict(), - dict(axis=-1)], - 'Sub': ['elementwise_sub', ['X', 'Y'], ['Out'], - dict(), - dict(axis=-1)], 'Clip': [ 'clip', ['X'], ['Out'], dict(), @@ -74,9 +68,6 @@ default_op_mapping = { ], 'Tanh': ['tanh', ['X'], ['Out']], 'Sigmoid': ['sigmoid', ['X'], ['Out']], - 'Pow': ['elementwise_pow', ['X', 'Y'], ['Out'], - dict(), - dict(axis=-1)], # TODO: pow for scalar exponent 'HardSigmoid': [ 'hard_sigmoid', ['X'], ['Out'], dict(alpha='slope', beta='offset'), @@ -87,6 +78,7 @@ default_op_mapping = { 'Exp': ['exp', ['X'], ['Out']], 'Softmax': ['softmax', ['X'], ['Out'], dict(), dict(axis=1)], + 'Sqrt': ['sqrt', ['X'], ['Out']], } activefunc_op_mapping = { diff --git a/x2paddle/op_mapper/onnx_op_mapper.py b/x2paddle/op_mapper/onnx_op_mapper.py index a8cb7e2..e7ffc09 100644 --- a/x2paddle/op_mapper/onnx_op_mapper.py +++ b/x2paddle/op_mapper/onnx_op_mapper.py @@ -101,7 +101,6 @@ class ONNXOpMapper(OpMapper): outputs = node.layer.output op_type = node.layer_type attrs = node.attr_map - info = default_op_mapping[op_type] info.extend(list(default_op_mapping_field_values.values())[len(info):]) ( @@ -138,10 +137,11 @@ class ONNXOpMapper(OpMapper): val_outs = outputs if output_perm is None else list( map(lambda i: outputs[i], output_perm)) attr = fluid_attrs - if fluid_op not in ['shape', 'gather']: + assert len(val_inps) == 1, 'directly_map error with multi inputs' + if fluid_op not in ['shape']: attr['name'] = string(node.layer_name) node.fluid_code.add_layer(fluid_op, - inputs=val_inps, + inputs=val_inps[0], output=val_outs[0], param_attr=attr) @@ -160,7 +160,9 @@ class ONNXOpMapper(OpMapper): if op not in self.used_custom_layers: self.used_custom_layers[op] = custom_code if op + '_child_func' not in self.used_custom_layers: - self.used_custom_layers[op + '_child_func'] = child_func_code + if child_func_code is not None: + self.used_custom_layers[op + + '_child_func'] = child_func_code def place_holder(self, node): self.input_shapes.append(node.out_shapes[0]) @@ -422,18 +424,21 @@ class ONNXOpMapper(OpMapper): indices = self.graph.get_input_node(node, idx=1, copy=True) indices_shape = indices.out_shapes[0] axis = node.get_attr('axis') - print(indices.layer_name) - print(indices_shape) assert len( - indices_shape) == 1, "Gather op don't support dim of indice >1 " - if axis == 0 and len(indices_shape) == 1: + indices_shape) <= 1, "Gather op don't support dim of indice >1 " + if axis == 0 and len(indices_shape) <= 1: node.fluid_code.add_layer('gather', - inputs=[val_x, indices], + inputs={ + 'input': val_x, + 'index': indices + }, output=node, param_attr=None) - elif axis > 0 and len(indices_shape) == 1: - perm = [range(len(indices_shape))] + elif axis > 0 and len(indices_shape) <= 1: + perm = list(range(len(val_x.out_shapes[0]))) + print(val_x.out_shapes[0]) perm = [axis] + perm[:axis] + perm[axis + 1:] + # perm = [0] attr_trans = {'perm': perm} name_trans = val_x.layer_name + '_trans' node.fluid_code.add_layer('transpose', @@ -441,7 +446,10 @@ class ONNXOpMapper(OpMapper): output=name_trans, param_attr=attr_trans) node.fluid_code.add_layer('gather', - inputs=[name_trans, indices], + inputs={ + 'input': name_trans, + 'index': indices + }, output=node, param_attr=None) node.fluid_code.add_layer('transpose', @@ -451,23 +459,29 @@ class ONNXOpMapper(OpMapper): def Slice(self, node): val_x = self.graph.get_input_node(node, idx=0, copy=True) - val_starts = self.graph.get_input_node(node, idx=1, copy=True) - val_ends = self.graph.get_input_node(node, idx=2, copy=True) - val_axes = self.graph.get_input_node(node, idx=3, copy=True) - val_steps = self.graph.get_input_node(node, idx=4, copy=True) + val_starts, val_ends, val_axes, val_steps = None, None, None, None + if len(node.inputs) > 1: + starts = self.graph.get_input_node(node, idx=1, copy=True) + ends = self.graph.get_input_node(node, idx=2, copy=True) + axes = self.graph.get_input_node(node, idx=3, copy=True) + steps = self.graph.get_input_node(node, idx=4, copy=True) + + self.omit_nodes.append(starts.layer_name) + self.omit_nodes.append(ends.layer_name) + self.omit_nodes.append(axes.layer_name) + self.omit_nodes.append(steps.layer_name) + + starts = _const_weight_or_none(starts).copy() + ends = _const_weight_or_none(ends).copy() + axes = _const_weight_or_none(axes) + steps = _const_weight_or_none(steps) + else: + starts = node.get_attr('starts') + ends = node.get_attr('ends') + axes = node.get_attr('axes') val_y = self.graph.get_node(node.layer.output[0], copy=True) - starts = _const_weight_or_none(val_starts).copy() - ends = _const_weight_or_none(val_ends).copy() - axes = _const_weight_or_none(val_axes) - steps = _const_weight_or_none(val_steps) - - self.omit_nodes.append(val_starts.layer_name) - self.omit_nodes.append(val_ends.layer_name) - self.omit_nodes.append(val_axes.layer_name) - self.omit_nodes.append(val_steps.layer_name) - shape = val_x.out_shapes[0] if shape is not None: @@ -510,17 +524,21 @@ class ONNXOpMapper(OpMapper): param_attr=attr) def Split(self, node): - val_input = self.graph.get_input_node(node, idx=0, copy=True) - var_outs = [val for val in node.layer.input] + val_x = self.graph.get_input_node(node, idx=0, copy=True) + val_y = self.graph.get_node(node.layer.output[0], copy=True) fluid_op = 'split' - split = node.get_attr['split'] + split = node.get_attr('split') axis = node.get_attr('axis', 0) - attr = {'split': split, 'axis': axis, 'name': string(node.layer_name)} + attr = { + 'num_or_sections': split, + 'dim': axis, + 'name': string(node.layer_name) + } # generation node.fluid_code.add_layer('split', - inputs=val_input, - output=var_outs, + inputs=val_x, + output=val_y, param_attr=attr) def Reshape(self, node): @@ -536,6 +554,7 @@ class ONNXOpMapper(OpMapper): if isinstance(val_shape, ONNXGraphNode): shape, _, _ = self.decoder.onnx_graph.get_dynamic_shape( val_shape.layer_name) + if shape is None: shape = val_reshaped.out_shapes[0] @@ -698,6 +717,32 @@ class ONNXOpMapper(OpMapper): output=node, param_attr=attr) + def Sub(self, node): + val_x = self.graph.get_input_node(node, idx=0, copy=True) + val_y = self.graph.get_input_node(node, idx=1, copy=True) + inputs = { + "x": val_x, + "y": val_y, + } + attr = {"name": string(node.layer_name)} + node.fluid_code.add_layer("elementwise_sub", + inputs=inputs, + output=node, + param_attr=attr) + + def Pow(self, node): + val_x = self.graph.get_input_node(node, idx=0, copy=True) + val_y = self.graph.get_input_node(node, idx=1, copy=True) + inputs = { + "x": val_x, + "y": val_y, + } + attr = {"name": string(node.layer_name)} + node.fluid_code.add_layer("elementwise_pow", + inputs=inputs, + output=node, + param_attr=attr) + def Sum(self, node): val_inps = node.layer.input inputs = { @@ -917,16 +962,6 @@ class ONNXOpMapper(OpMapper): output=node, param_attr=attr) - -# def Tile(self, node): -# pass - -# def Loop(self, node): -# pass - -# def NonMaxSuppression(self, node): -# pass - def GlobalAveragePool(self, node): val_x = self.graph.get_input_node(node, idx=0, copy=True) val_y = self.graph.get_node(node.layer.output[0], copy=True) -- GitLab