Download lib/python3.9/site-packages/caffe2/python/onnx/backend.py from camenduru/slahmr-test: direct link, hf CLI and curl.
- Browser
- Download file 41.6 kB
-
https://huggingface.co/camenduru/slahmr-test/resolve/main/lib/python3.9/site-packages/caffe2/python/onnx/backend.py
- Command line
-
hf download hf://camenduru/slahmr-test/lib/python3.9/site-packages/caffe2/python/onnx/backend.py
-
curl -L -o backend.py https://huggingface.co/camenduru/slahmr-test/resolve/main/lib/python3.9/site-packages/caffe2/python/onnx/backend.py
41.6 kB
| ## @package onnx | |
| # Module caffe2.python.onnx.backend | |
| """Backend for running ONNX on Caffe2 | |
| To run this, you will need to have Caffe2 installed as well. | |
| """ | |
| import collections | |
| import sys | |
| import zipfile | |
| import itertools | |
| # When onnx is built against a version of protobuf that is older than | |
| # that which is vendored with caffe2, onnx will crash if caffe2's | |
| # vendored protobuf is loaded first. We can work around this by | |
| # importing onnx first, which will cause it to go out and pick up the | |
| # system protobuf. | |
| import onnx.backend | |
| from caffe2.python import core, workspace, rnn_cell, gru_cell | |
| from caffe2.python.model_helper import ModelHelper | |
| from caffe2.proto import caffe2_pb2 | |
| import caffe2.python.utils | |
| import numpy as np | |
| import onnx | |
| from onnx import TensorProto | |
| import onnx.numpy_helper | |
| import onnx.defs | |
| import onnx.shape_inference | |
| import onnx.utils | |
| from onnx.backend.base import Backend, Device, DeviceType, namedtupledict | |
| from caffe2.python.onnx.workspace import Workspace | |
| from caffe2.python.onnx.backend_rep import Caffe2Rep | |
| import caffe2.python._import_c_extension as C | |
| import warnings | |
| def force_unicode(s): | |
| try: | |
| return s.decode('utf-8') | |
| except AttributeError: | |
| return s | |
| def get_device_option(device): | |
| m = {DeviceType.CPU: caffe2_pb2.CPU, | |
| DeviceType.CUDA: workspace.GpuDeviceType} | |
| return core.DeviceOption(m[device.type], device.device_id) | |
| class OnnxAttributes(dict): | |
| """ | |
| This is a more convenient way to work with ONNX/Caffe2 attributes | |
| that is not the protobuf representation. | |
| """ | |
| def from_onnx(args): | |
| d = OnnxAttributes() | |
| for arg in args: | |
| d[arg.name] = convertAttributeProto(arg) | |
| return d | |
| def caffe2(self, kmap=lambda k: k): | |
| for k, v in self.items(): | |
| if kmap(k) != '': | |
| yield caffe2.python.utils.MakeArgument(kmap(k), v) | |
| # TODO: Move this into ONNX main library | |
| def convertAttributeProto(onnx_arg): | |
| """ | |
| Convert an ONNX AttributeProto into an appropriate Python object | |
| for the type. | |
| NB: Tensor attribute gets returned as the straight proto. | |
| """ | |
| if onnx_arg.HasField('f'): | |
| return onnx_arg.f | |
| elif onnx_arg.HasField('i'): | |
| return onnx_arg.i | |
| elif onnx_arg.HasField('s'): | |
| return onnx_arg.s | |
| elif onnx_arg.HasField('t'): | |
| return onnx_arg.t # this is a proto! | |
| elif onnx_arg.HasField('g'): | |
| return Caffe2Backend._graph_to_net(onnx_arg.g, Caffe2Backend._known_opset_version) | |
| elif len(onnx_arg.floats): | |
| return list(onnx_arg.floats) | |
| elif len(onnx_arg.ints): | |
| return list(onnx_arg.ints) | |
| elif len(onnx_arg.strings): | |
| return list(onnx_arg.strings) | |
| elif len(onnx_arg.graphs): | |
| retval = [] | |
| # TODO: this doesn't work with RNN ops | |
| for g in onnx_arg.graphs: | |
| retval.append(Caffe2Backend._graph_to_net(g, Caffe2Backend._known_opset_version)) | |
| return retval | |
| else: | |
| raise ValueError("Unsupported ONNX attribute: {}".format(onnx_arg)) | |
| # TODO: Move this into ONNX main library | |
| class OnnxNode(object): | |
| """ | |
| Reimplementation of NodeProto from ONNX, but in a form | |
| more convenient to work with from Python. | |
| We may temporarily edit these nodes to get them into Caffe2 form, | |
| before actually translating into the Caffe2 protobuf, since this | |
| is easier than decomposing everything, and putting it back together | |
| when we're ready. | |
| """ | |
| def __init__(self, node): | |
| self.name = str(node.name) | |
| self.op_type = str(node.op_type) | |
| self.attrs = OnnxAttributes.from_onnx(node.attribute) | |
| self.inputs = list(node.input) | |
| self.outputs = list(node.output) | |
| Caffe2Ops = collections.namedtuple('Caffe2Ops', ['ops', 'init_ops', 'interface_blobs']) | |
| class Caffe2Backend(Backend): | |
| # The greatest version of the ONNX operator set which we are aware of. | |
| # Models whose version is larger than this will cause us to emit a warning | |
| # that we are attempting to translate on a "best effort" basis. | |
| # | |
| # If you increase this, make SURE you cross-reference all BC-breaking | |
| # changes from one version to the next, and any that you did not | |
| # implement, mark as broken in _broken_operators | |
| _known_opset_version = 9 | |
| # This dictionary will record operators which are KNOWN to be | |
| # broken, so we give a good error message rather than do something | |
| # bogus and then fail. | |
| _broken_operators = { | |
| # 'BrokenOp': version_it_was_broken_in | |
| } | |
| # Operators that are different between Caffe2 and | |
| # ONNX but only in their name. | |
| # In most cases, this should be empty - as the effort of ONNX is | |
| # to unify the operator definitions. | |
| _renamed_operators = { | |
| 'GlobalMaxPool': 'MaxPool', | |
| 'GlobalAveragePool': 'AveragePool', | |
| 'Pad': 'PadImage', | |
| 'Neg': 'Negative', | |
| 'BatchNormalization': 'SpatialBN', | |
| 'InstanceNormalization': 'InstanceNorm', | |
| 'MatMul': 'BatchMatMul', | |
| 'Upsample': 'ResizeNearest', | |
| 'Identity': 'Copy', | |
| 'InstanceNormalization': 'InstanceNorm', | |
| 'Equal': 'EQ', | |
| 'Less': 'LT', | |
| 'Greater': 'GT', | |
| 'Unsqueeze': 'ExpandDims', | |
| 'Loop': 'ONNXWhile', | |
| 'Tile': 'NumpyTile', | |
| 'RandomNormal': 'GaussianFill', | |
| 'RandomUniform': 'UniformFill', | |
| } | |
| _global_renamed_attrs = {'kernel_shape': 'kernels'} | |
| _per_op_renamed_attrs = { | |
| 'Squeeze': {'axes': 'dims'}, | |
| 'Unsqueeze': {'axes': 'dims'}, | |
| 'Transpose': {'perm': 'axes'}, | |
| 'Upsample': {'mode': '', | |
| 'scales': ''}, | |
| 'ConvTranspose': {'output_padding': 'adjs'}, | |
| 'Selu': {'gamma': 'scale'}, | |
| 'If': {'then_branch': 'then_net', | |
| 'else_branch': 'else_net'}, | |
| 'RandomUniform': {'low': 'min', | |
| 'high': 'max'} | |
| } | |
| # operators whose behavior is different beyond renaming | |
| # the value is an attribute of this class that is a | |
| # function from ToffeIR node_def to caffe2 op_def | |
| _special_operators = { | |
| 'LSTM': '_create_rnn_variant', | |
| 'GRU': '_create_rnn_variant', | |
| 'RNN': '_create_rnn_variant', | |
| 'Loop': '_create_loop', | |
| 'If': '_create_if', | |
| 'Upsample': '_create_upsample', | |
| 'RandomNormal': '_create_gaussian_fill' | |
| } | |
| # Dummy name generator | |
| _dummy_name = C.DummyName() | |
| def dummy_name(cls): | |
| return cls._dummy_name.new_dummy_name() | |
| # NB: By default, you will use the LATEST definition of the operator, | |
| # so this interface MAY make BC-breaking changes. Specify an | |
| # opset_version if you don't want this to version. | |
| def run_node(cls, node, inputs, device='CPU', opset_version=_known_opset_version, outputs_info=None): | |
| super(Caffe2Backend, cls).run_node(node, inputs, device=device, | |
| outputs_info=outputs_info, opset_version=opset_version) | |
| value_infos = [] | |
| device_option = get_device_option(Device(device)) | |
| ws = Workspace() | |
| with core.DeviceScope(device_option): # temporary! | |
| if isinstance(inputs, dict): | |
| for key, value in inputs.items(): | |
| ws.FeedBlob(key, value) | |
| value_infos.append(onnx.helper.make_tensor_value_info( | |
| name=key, | |
| elem_type=onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[value.dtype], | |
| shape=value.shape).SerializeToString()) | |
| else: | |
| assert len(node.input) == len(inputs), "{}: expected {} but got {}".format( | |
| node.op_type, len(node.input), len(inputs)) | |
| for key, value in zip(node.input, inputs): | |
| ws.FeedBlob(key, value) | |
| value_infos.append(onnx.helper.make_tensor_value_info( | |
| name=key, | |
| elem_type=onnx.mapping.NP_TYPE_TO_TENSOR_TYPE[value.dtype], | |
| shape=value.shape).SerializeToString()) | |
| ops = [] | |
| cbackend = C.Caffe2Backend(cls._dummy_name) | |
| ops_str = cbackend.convert_node(node.SerializeToString(), value_infos, opset_version) | |
| for s in ops_str[0] + ops_str[1]: | |
| op = caffe2_pb2.OperatorDef() | |
| op.ParseFromString(s) | |
| op.device_option.CopyFrom(device_option) | |
| ops.append(op) | |
| ws.RunOperatorsOnce(ops) | |
| output_values = [ws.FetchBlob(name) for name in node.output] | |
| return namedtupledict('Outputs', node.output)(*output_values) | |
| def _create_tensor_filling_op(cls, onnx_tensor, name=None): | |
| """ | |
| Given an Onnx TensorProto, translate it into a Caffe2 operator | |
| which produces the given tensor filling op. | |
| """ | |
| assert name or onnx_tensor.name | |
| name = name or onnx_tensor.name | |
| c2_op = caffe2_pb2.OperatorDef() | |
| c2_values = c2_op.arg.add() | |
| c2_values.name = "values" | |
| def tensor2list(onnx_tensor): | |
| # Use the onnx.numpy_helper because the data may be raw | |
| return onnx.numpy_helper.to_array(onnx_tensor).flatten().tolist() | |
| if onnx_tensor.data_type in [TensorProto.FLOAT]: | |
| c2_op.type = 'GivenTensorFill' | |
| c2_values.floats.extend(tensor2list(onnx_tensor)) | |
| elif onnx_tensor.data_type in [TensorProto.DOUBLE]: | |
| c2_op.type = 'GivenTensorDoubleFill' | |
| c2_values.floats.extend(tensor2list(onnx_tensor)) | |
| elif onnx_tensor.data_type in [TensorProto.INT64, | |
| TensorProto.UINT32]: | |
| c2_op.type = 'GivenTensorInt64Fill' | |
| c2_values.ints.extend(tensor2list(onnx_tensor)) | |
| elif onnx_tensor.data_type in [TensorProto.UINT8, | |
| TensorProto.INT8, | |
| TensorProto.UINT16, | |
| TensorProto.INT16, | |
| TensorProto.INT32]: | |
| c2_op.type = 'GivenTensorIntFill' | |
| c2_values.ints.extend(tensor2list(onnx_tensor)) | |
| elif onnx_tensor.data_type == TensorProto.BOOL: | |
| c2_op.type = 'GivenTensorBoolFill' | |
| c2_values.ints.extend(tensor2list(onnx_tensor)) | |
| elif onnx_tensor.data_type == TensorProto.STRING: | |
| c2_op.type = 'GivenTensorStringFill' | |
| c2_values.strings.extend(onnx_tensor.string_data) | |
| else: | |
| raise RuntimeError( | |
| "unrecognized tensor type {}".format(onnx_tensor.data_type)) | |
| c2_shape = c2_op.arg.add() | |
| c2_shape.name = "shape" | |
| c2_shape.ints.extend(onnx_tensor.dims) | |
| c2_op.output.append(name) | |
| return c2_op | |
| def _rnn_reform_weights(cls, reforms, name, hidden_size, init_net, gates, reorder_indices): | |
| for name_from, name_to, do_concat, extra_dims in reforms: | |
| gate_blobs = ['%s/%s_%s' % (name, prefix, name_to) for prefix in gates] | |
| for i, x in enumerate(gate_blobs): | |
| dim0 = i * hidden_size, (i+1) * hidden_size | |
| starts, ends = zip(dim0, *extra_dims) | |
| init_net.Slice(name_from, x, starts=starts, ends=ends) | |
| if do_concat: | |
| reordered_gate_blobs = [gate_blobs[i] for i in reorder_indices] | |
| init_net.Concat(reordered_gate_blobs, ['%s/%s' % (name, name_to), cls.dummy_name()], axis=0) | |
| def _make_rnn_direction(cls, input_blob, B, W, R, initial_states_and_names, sequence_lens, | |
| pred_mh, init_net, | |
| input_size, hidden_size, num_gates, direction_offset, | |
| Bi, Br, W_, R_, | |
| reform, make_cell, keep_outputs): | |
| name = cls.dummy_name() | |
| # input and recurrence biases are squashed together in onnx | |
| # but not in caffe2 | |
| gates_hidden_size = num_gates * hidden_size | |
| bias_offset = 2 * direction_offset * gates_hidden_size | |
| weight_offset = direction_offset * gates_hidden_size | |
| Bi = init_net.Slice(B, name + Bi, | |
| starts=[bias_offset + 0 * gates_hidden_size], | |
| ends =[bias_offset + 1 * gates_hidden_size]) | |
| Br = init_net.Slice(B, name + Br, | |
| starts=[bias_offset + 1 * gates_hidden_size], | |
| ends =[bias_offset + 2 * gates_hidden_size]) | |
| W_ = init_net.Slice(W, name + W_, | |
| starts=[weight_offset + 0 * gates_hidden_size, 0], | |
| ends =[weight_offset + 1 * gates_hidden_size,-1]) | |
| R_ = init_net.Slice(R, name + R_, | |
| starts=[weight_offset + 0 * gates_hidden_size, 0], | |
| ends =[weight_offset + 1 * gates_hidden_size,-1]) | |
| initial_states_sliced = [] | |
| for initial_state, name_suffix in initial_states_and_names: | |
| initial_states_sliced.append( | |
| pred_mh.net.Slice(initial_state, name + name_suffix, | |
| starts=[direction_offset + 0, 0, 0], | |
| ends =[direction_offset + 1,-1,-1])) | |
| if direction_offset == 1: | |
| if sequence_lens is not None: | |
| seq_lens_for_reverse = sequence_lens | |
| else: | |
| input_shape = pred_mh.net.Shape(input_blob, name + '/input_shape') | |
| batch_size = pred_mh.net.Slice(input_shape, name + '/batch_size_slice', starts=[1], ends=[2]) | |
| seq_len = pred_mh.net.Slice(input_shape, name + '/seq_len_slice', starts=[0], ends=[1]) | |
| dummy_sequence_lens = pred_mh.net.Tile([seq_len, batch_size], name + '/dummy_sequence_lens', axis=0) | |
| pred_mh.net.Reshape(dummy_sequence_lens, [dummy_sequence_lens, cls.dummy_name()], shape=[-1]) | |
| seq_lens_for_reverse = pred_mh.net.Cast(dummy_sequence_lens, name + '/seq_lens_for_reverse', to=core.DataType.INT32) | |
| reform(Bi, Br, W_, R_, name, hidden_size, init_net) | |
| if direction_offset == 1: | |
| input = pred_mh.net.ReversePackedSegs( | |
| [input_blob, seq_lens_for_reverse], name + "/input-reversed") | |
| else: | |
| input = input_blob | |
| outputs = keep_outputs(list(make_cell( | |
| pred_mh, | |
| input, | |
| sequence_lens, | |
| initial_states_sliced, | |
| input_size, | |
| hidden_size, | |
| name, | |
| drop_states=False, | |
| forward_only=True, | |
| ))) | |
| if direction_offset == 1: | |
| outputs[0] = pred_mh.net.ReversePackedSegs( | |
| [outputs[0], seq_lens_for_reverse], name + "/output-reversed") | |
| return outputs | |
| def _create_rnn_variant(cls, init_model, pred_model, n, opset_version): | |
| assert init_model is not None, "cannot convert RNNs without access to the full model" | |
| assert pred_model is not None, "cannot convert RNNs without access to the full model" | |
| attrs = dict(n.attrs) # make a copy, which is safe to mutate | |
| hidden_size = attrs.pop('hidden_size') | |
| direction = force_unicode(attrs.pop('direction', 'forward')) | |
| if n.op_type == 'RNN': | |
| activation = force_unicode(attrs.pop('activations', ('tanh',))[0].lower()) | |
| elif n.op_type == 'GRU': | |
| linear_before_reset = attrs.pop('linear_before_reset', 0) | |
| assert not attrs, "unsupported RNN attributes: " + str(attrs.keys()) | |
| assert direction in ['forward', 'bidirectional'], "unsupported backwards RNN/GRU/LSTM" | |
| if n.op_type in ['RNN', 'GRU']: | |
| input_blob, W, R, B, sequence_lens, initial_h = n.inputs | |
| elif n.op_type == 'LSTM': | |
| input_blob, W, R, B, sequence_lens, initial_h, initial_c = n.inputs | |
| if sequence_lens == "": | |
| sequence_lens = None | |
| for x in itertools.chain(init_model.graph.input, | |
| init_model.graph.value_info, | |
| pred_model.graph.input, | |
| pred_model.graph.value_info): | |
| if x.name == W: | |
| input_size = x.type.tensor_type.shape.dim[2].dim_value | |
| break | |
| else: | |
| raise RuntimeError("best-effort shape inference for RNN/GRU/LSTM failed") | |
| pred_mh = ModelHelper() | |
| init_net = core.Net("init-net") | |
| init_net.Reshape(W, [W, cls.dummy_name()], shape=[1,-1,0]) | |
| init_net.Squeeze(W, W, dims=[0]) | |
| init_net.Reshape(R, [R, cls.dummy_name()], shape=[1,-1,0]) | |
| init_net.Squeeze(R, R, dims=[0]) | |
| init_net.Reshape(B, [B, cls.dummy_name()], shape=[1,-1]) | |
| init_net.Squeeze(B, B, dims=[0]) | |
| if n.op_type == 'RNN': | |
| def reform(*args): | |
| pass | |
| def make_cell(*args, **kwargs): | |
| return rnn_cell.BasicRNN(*args, activation=activation, **kwargs) | |
| def make_rnn(direction_offset): | |
| return cls._make_rnn_direction( | |
| input_blob, B, W, R, [(initial_h, '/initial_h')], sequence_lens, | |
| pred_mh, init_net, input_size, hidden_size, 1, direction_offset, | |
| "/i2h_b", "/gates_t_b", "/i2h_w", "/gates_t_w", | |
| reform, make_cell, lambda x: x) | |
| elif n.op_type == 'GRU': | |
| def reform(Bi, Br, W_, R_, name, hidden_size, init_net): | |
| # caffe2 has a different order from onnx. We need to rearrange | |
| # z r h -> r z h | |
| reforms = ((W_, 'i2h_w', True, [(0,-1)]), | |
| (R_, 'gate_t_w', False, [(0,-1)]), | |
| (Bi, 'i2h_b', True, []), | |
| (Br, 'gate_t_b', False, [])) | |
| cls._rnn_reform_weights(reforms, name, hidden_size, init_net, | |
| ['update', 'reset', 'output'], [1, 0, 2]) | |
| def make_cell(*args, **kwargs): | |
| return gru_cell.GRU(*args, linear_before_reset=linear_before_reset, **kwargs) | |
| def make_rnn(direction_offset): | |
| return cls._make_rnn_direction( | |
| input_blob, B, W, R, [(initial_h, '/initial_h')], sequence_lens, | |
| pred_mh, init_net, input_size, hidden_size, 3, direction_offset, | |
| "_bias_i2h", "_bias_gates", "/i2h_w_pre", "/gates_t_w_pre", | |
| reform, make_cell, lambda x: x) | |
| elif n.op_type == 'LSTM': | |
| def reform(Bi, Br, W_, R_, name, hidden_size, init_net): | |
| # caffe2 has a different order from onnx. We need to rearrange | |
| # i o f c -> i f o c | |
| reforms = ((W_, 'i2h_w', True, [(0, -1)]), | |
| (R_, 'gates_t_w', True, [(0, -1)]), | |
| (Bi, 'i2h_b' , True, []), | |
| (Br, 'gates_t_b', True, [])) | |
| cls._rnn_reform_weights(reforms, name, hidden_size, init_net, | |
| ['input', 'output', 'forget', 'cell'], [0, 2, 1, 3]) | |
| def make_cell(*args, **kwargs): | |
| return rnn_cell.LSTM(*args, **kwargs) | |
| def make_rnn(direction_offset): | |
| return cls._make_rnn_direction( | |
| input_blob, B, W, R, [(initial_h, '/initial_h'), (initial_c, '/initial_c')], sequence_lens, | |
| pred_mh, init_net, input_size, hidden_size, 4, direction_offset, | |
| "/i2h_b", "/gates_t_b", "/i2h_w", "/gates_t_w", | |
| reform, make_cell, lambda x: [x[0], x[1], x[3]]) | |
| if direction == 'forward': | |
| outputs = make_rnn(0) | |
| # in the forward case, storage is shared between the | |
| # last outputs. We need to decouple them so that the | |
| # VariableLengthSequencePadding only mutates | |
| # n.outputs[0] | |
| for i in range(1, len(outputs)): | |
| pred_mh.net.Copy(outputs[i], n.outputs[i]) | |
| if sequence_lens is not None: | |
| pred_mh.net.VariableLengthSequencePadding( | |
| [outputs[0], sequence_lens], [outputs[0]]) | |
| pred_mh.net.ExpandDims([outputs[0]], [n.outputs[0]], dims=[1]) | |
| elif direction == 'bidirectional': | |
| outputs_f = make_rnn(0) | |
| outputs_b = make_rnn(1) | |
| concatted_output, _ = pred_mh.net.Concat( | |
| [outputs_f[0], outputs_b[0]], [cls.dummy_name(), cls.dummy_name()], axis=2) | |
| if sequence_lens is not None: | |
| pred_mh.net.VariableLengthSequencePadding( | |
| [concatted_output, sequence_lens], [concatted_output]) | |
| reshaped_output, _ = pred_mh.net.Reshape(concatted_output, [cls.dummy_name(), cls.dummy_name()], shape=[0,0,-1,2]) | |
| pred_mh.net.Transpose(reshaped_output, n.outputs[0], axes=[0,2,1,3]) | |
| for i in range(1, len(n.outputs)): | |
| pred_mh.net.Concat([outputs_f[i], outputs_b[i]], | |
| [n.outputs[i], cls.dummy_name()], axis=0) | |
| # We want to decide whether to put all of our weight-reshaping | |
| # operators in the init net or the predict net. We can put | |
| # them in the init net iff the inputs to those operators are | |
| # already available, either as graph initializers, or as the | |
| # output of other operators in the init net. The latter case | |
| # occurs, for example, when exporting from pytorch to onnx. | |
| # In most production use, we expect has_initializers to be | |
| # true. | |
| initializers = {i.name for i in init_model.graph.initializer} | |
| outputs = {output for node in init_model.graph.node for output in node.output} | |
| has_initializers = all(x in initializers or x in outputs for x in (W, R, B)) | |
| pred_ops = [] | |
| init_ops = [] | |
| (init_ops if has_initializers else pred_ops).extend(init_net.Proto().op) | |
| pred_ops.extend(pred_mh.Proto().op) | |
| return Caffe2Ops(pred_ops, init_ops, list(pred_mh.Proto().external_input)) | |
| def _create_control_op(cls, init_model, pred_model, n, opset_version): | |
| control_inputs = [] | |
| if '__control_inputs' in n.attrs: | |
| control_inputs.extend(n.attrs['__control_inputs']) | |
| node = cls._common_onnx_node_to_caffe2_op(init_model, pred_model, n, opset_version) | |
| node.control_input.extend(control_inputs) | |
| return Caffe2Ops([node], [], []) | |
| def _remove_ssa(cls, net, remap_dict): | |
| for op in net.op: | |
| for i, name in enumerate(op.output): | |
| if name in remap_dict: | |
| op.output[i] = remap_dict[name] | |
| for i, out in enumerate(net.external_output): | |
| if out in remap_dict: | |
| net.external_output[i] = remap_dict[out] | |
| def _create_if(cls, init_model, pred_model, n, opset_version): | |
| ops = cls._create_control_op(init_model, pred_model, n, opset_version) | |
| assert ops[0][0].type == 'If' | |
| if_op = ops[0][0] | |
| then_net = else_net = None | |
| control_inputs = [] | |
| for arg in if_op.arg: | |
| if arg.name == 'then_net': | |
| then_net = arg.n | |
| if arg.name == 'else_net': | |
| else_net = arg.n | |
| if arg.name == '__control_inputs': | |
| control_inputs = arg.strings | |
| assert then_net and else_net | |
| then_net_outs = then_net.external_output | |
| else_net_outs = else_net.external_output | |
| op_outputs = if_op.output | |
| assert len(then_net_outs) == len(else_net_outs) | |
| assert len(else_net_outs) == len(op_outputs) | |
| for arg in if_op.arg: | |
| if arg.name == 'then_net': | |
| arg.n.external_input.extend(control_inputs) | |
| if arg.name == 'else_net': | |
| arg.n.external_input.extend(control_inputs) | |
| return ops | |
| def _create_loop(cls, init_model, pred_model, n, opset_version): | |
| ops = cls._create_control_op(init_model, pred_model, n, opset_version) | |
| assert ops[0][0].type == 'ONNXWhile' | |
| while_op = ops[0][0] | |
| while_op.arg.extend([caffe2.python.utils.MakeArgument('has_trip_count', True)]) | |
| while_op.arg.extend([caffe2.python.utils.MakeArgument('has_cond', True)]) | |
| while_op.arg.extend([caffe2.python.utils.MakeArgument('disable_scopes', True)]) | |
| control_inputs = [] | |
| for arg in while_op.arg: | |
| if arg.name == '__control_inputs': | |
| control_inputs = arg.strings | |
| num_loop_carried_deps = 0 | |
| for arg in while_op.arg: | |
| if arg.name == 'body': | |
| num_loop_carried_deps = len(arg.n.external_input) - 2 | |
| arg.n.external_input.extend(control_inputs) | |
| while_op.arg.extend([ | |
| caffe2.python.utils.MakeArgument('num_loop_carried_deps', | |
| num_loop_carried_deps) | |
| ]) | |
| return ops | |
| def _substitute_raw_value(cls, tp, raw_values_dict): | |
| if tp.HasField('raw_data') and tp.raw_data == bytes(b'__EXTERNAL'): | |
| if tp.name not in raw_values_dict: | |
| raise RuntimeError('TensorProto for value {} referenced raw data but it was not found!'.format(tp.name)) | |
| else: | |
| tp.raw_data = raw_values_dict[tp.name] | |
| def _visit_and_substitute_raw_values(cls, nodes, raw_values_dict): | |
| for node in nodes: | |
| for attr in node.attribute: | |
| if attr.HasField('t'): | |
| cls._substitute_raw_value(attr.t, raw_values_dict) | |
| for t in attr.tensors: | |
| cls._substitute_raw_value(t, raw_values_dict) | |
| if attr.HasField('g'): | |
| cls._visit_and_substitute_raw_values(attr.g.node, raw_values_dict) | |
| for g in attr.graphs: | |
| cls._visit_and_substitute_raw_values(g.node, raw_values_dict) | |
| def _external_value_resolution_pass(cls, model, raw_values_dict): | |
| for init in model.graph.initializer: | |
| cls._substitute_raw_value(init, raw_values_dict) | |
| cls._visit_and_substitute_raw_values(model.graph.node, raw_values_dict) | |
| def _direct_initialize_parameters(cls, initializer, ws, device_option): | |
| for tp in initializer: | |
| ws.FeedBlob(tp.name, onnx.numpy_helper.to_array(tp), device_option) | |
| def _direct_initialize_inputs(cls, inputs, initialized, ws, device_option): | |
| for value_info in inputs: | |
| if value_info.name in initialized: | |
| continue | |
| shape = list(d.dim_value for d in value_info.type.tensor_type.shape.dim) | |
| ws.FeedBlob( | |
| value_info.name, | |
| np.ones(shape, dtype=onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[value_info.type.tensor_type.elem_type]), | |
| device_option) | |
| def optimize_onnx(input, init=False, predict=False): | |
| passes = ['fuse_consecutive_transposes', | |
| 'eliminate_nop_transpose', | |
| 'fuse_transpose_into_gemm', | |
| 'lift_lexical_references'] | |
| if init: | |
| passes.append('split_init') | |
| if predict: | |
| passes.append('split_predict') | |
| try: | |
| out = onnx.optimizer.optimize(input, passes) | |
| except AttributeError: | |
| warnings.warn("OptimizerWarning: optimizer module not found in ONNX version {}".format(onnx.__version__)) | |
| # ONNX does no ship onnx.optimizer since version 1.9+ | |
| import onnxoptimizer | |
| out = onnxoptimizer.optimize(input, passes) | |
| return out | |
| def prepare_zip_archive(cls, file, device='CPU', **kwargs): | |
| with zipfile.ZipFile(file, mode='r') as z: | |
| with z.open('__MODEL_PROTO', 'r') as f: | |
| model = onnx.load(f); | |
| blob_names = set(z.namelist()) - set('__MODEL_PROTO') | |
| # TODO: make this more efficient | |
| raw_values_dict = {} | |
| for name in blob_names: | |
| with z.open(name, 'r') as blob_file: | |
| raw_values_dict[name] = blob_file.read() | |
| return cls.prepare(model, device, raw_values_dict=raw_values_dict, **kwargs) | |
| def prepare(cls, model, device='CPU', raw_values_dict=None, **kwargs): | |
| ''' | |
| For Onnx Caffe2Backend, we require that init_graph don't initialize the actual input of the predict_graph, | |
| for example, if "img" is the input blob for the predict_net, we require that in init_graph and in | |
| initializer of the predict_graph, "img" is not initalized. We don't have a check for this, since | |
| there is no way we can know which blob is the input of the predict_graph. | |
| ''' | |
| if not kwargs.pop('no_check_UNSAFE', False): | |
| super(Caffe2Backend, cls).prepare(model, device, **kwargs) | |
| opset_version = None | |
| for imp in model.opset_import: | |
| if not imp.HasField("domain") or imp.domain == "": | |
| opset_version = imp.version | |
| if imp.version > cls._known_opset_version: | |
| warnings.warn("This version of onnx-caffe2 targets ONNX operator set version {}, but the model we are trying to import uses version {}. We will try to import it anyway, but if the model uses operators which had BC-breaking changes in the intervening versions, import will fail.".format(cls._known_opset_version, imp.version)) | |
| else: | |
| warnings.warn("Unrecognized operator set {}".format(imp.domain)) | |
| if opset_version is None: | |
| if model.ir_version >= 0x00000003: | |
| raise RuntimeError("Model with IR version >= 3 did not specify ONNX operator set version (onnx-caffe2 requires it)") | |
| else: | |
| opset_version = 1 | |
| # Prior to onnx version update to onnx-1.8.0, errors caused by failures in | |
| # in the onnx shape inference call were being supressed. Hence a try-catch block | |
| # is added around the infer_shapes call to avoid these failures and preserve status | |
| try: | |
| model = onnx.shape_inference.infer_shapes(model) | |
| except RuntimeError: | |
| warnings.warn("ShapeInferenceWarning: Inferred shape and existing shape differ in rank") | |
| ws = Workspace() | |
| device_option = get_device_option(Device(device)) | |
| init_net, predict_net = cls._onnx_model_to_caffe2_net(model, device, opset_version, False) | |
| if raw_values_dict: | |
| cls._external_value_resolution_pass(model, raw_values_dict) | |
| # Directly load initializer data into blobs in workspace | |
| cls._direct_initialize_parameters( | |
| model.graph.initializer, | |
| ws, | |
| device_option, | |
| ) | |
| initialized = {init.name for init in model.graph.initializer} | |
| cls._direct_initialize_inputs( | |
| model.graph.input, | |
| initialized, | |
| ws, | |
| device_option, | |
| ) | |
| uninitialized = [value_info.name for value_info in model.graph.input if value_info.name not in initialized] | |
| retval = Caffe2Rep(init_net, predict_net, ws, uninitialized) | |
| return retval | |
| # TODO: This method needs a refactor for clarity | |
| def _onnx_node_to_caffe2_op(cls, init_model, pred_model, node_def, opset_version): | |
| cbackend = C.Caffe2Backend(cls._dummy_name) | |
| if cbackend.support_onnx_import(node_def.op_type): | |
| # extract value infos from pred model (value infos of | |
| # node's inputs that are in init model should be all | |
| # available in pred model) | |
| value_infos = [] | |
| for name in node_def.input: | |
| if pred_model is not None: | |
| for vi in itertools.chain(pred_model.graph.input, | |
| pred_model.graph.output, | |
| pred_model.graph.value_info): | |
| if vi.name == name: | |
| value_infos.append(vi.SerializeToString()) | |
| op_strs = cbackend.convert_node(node_def.SerializeToString(), value_infos, opset_version) | |
| init_ops = [] | |
| for s in op_strs[0]: | |
| op = caffe2_pb2.OperatorDef() | |
| op.ParseFromString(s) | |
| init_ops.append(op) | |
| ops = [] | |
| for s in op_strs[1]: | |
| op = caffe2_pb2.OperatorDef() | |
| op.ParseFromString(s) | |
| ops.append(op) | |
| return Caffe2Ops(ops, init_ops, []) | |
| if node_def.op_type in cls._special_operators: | |
| translator = getattr(cls, cls._special_operators[node_def.op_type]) | |
| else: | |
| translator = cls._common_onnx_node_to_caffe2_op | |
| ops = translator(init_model, pred_model, OnnxNode(node_def), opset_version) | |
| if isinstance(ops, Caffe2Ops): | |
| return ops | |
| if not isinstance(ops, collections.abc.Iterable): | |
| ops = [ops] | |
| return Caffe2Ops(ops, [], []) | |
| _broadcast_operators = { | |
| 'Add', | |
| 'Sub', | |
| } | |
| def _common_onnx_node_to_caffe2_op(cls, init_model, pred_model, onnx_node, opset_version): | |
| """ | |
| This translator performs the basic translation of ONNX nodes into | |
| Caffe2 operators. Besides doing a straightforward marshalling from | |
| one format to another, it also does these extra things: | |
| - Renames operators based on '_renamed_operators' | |
| - Renames attributes based on '_global_renamed_attrs' and | |
| '_per_op_renamed_attrs' | |
| If you're writing a custom translator, consider calling this first, | |
| and then fixing things up further. | |
| """ | |
| c2_op = caffe2_pb2.OperatorDef() | |
| c2_op.input.extend(onnx_node.inputs) | |
| c2_op.output.extend(onnx_node.outputs) | |
| c2_op.name = onnx_node.name | |
| onnx_op_type = onnx_node.op_type | |
| broken_version = cls._broken_operators.get(onnx_op_type, float('Inf')) | |
| if broken_version <= opset_version: | |
| raise ValueError( | |
| "Don't know how to translate op {} in ONNX operator set v{} (I only support prior to v{})".format(onnx_op_type, opset_version, broken_version)) | |
| c2_op.type = cls._renamed_operators.get(onnx_op_type, onnx_op_type) | |
| if not core.IsOperator(c2_op.type): | |
| raise ValueError( | |
| "Don't know how to translate op {}".format(onnx_op_type)) | |
| def kmap(k): | |
| if (onnx_op_type in cls._per_op_renamed_attrs and | |
| k in cls._per_op_renamed_attrs[onnx_op_type]): | |
| return cls._per_op_renamed_attrs[onnx_op_type][k] | |
| if k in cls._global_renamed_attrs: | |
| return cls._global_renamed_attrs[k] | |
| return k | |
| c2_op.arg.extend(onnx_node.attrs.caffe2(kmap=kmap)) | |
| if opset_version < 7: | |
| # onnx opset 7 and newest caffe2 have adopted full onnx broadcast semantics | |
| # so we don't need this hack anymore | |
| if c2_op.type in cls._broadcast_operators: | |
| already_broadcast = False | |
| for arg in c2_op.arg: | |
| if arg.name == 'broadcast': | |
| already_broadcast = True | |
| if not already_broadcast: | |
| c2_op.arg.extend([caffe2.python.utils.MakeArgument('broadcast', 1)]) | |
| return c2_op | |
| def _all_names_in_graph(graph): | |
| if graph is None: | |
| return set() | |
| names = set() | |
| names.update(value_info.name for value_info in graph.input) | |
| names.update(value_info.name for value_info in graph.output) | |
| for node in graph.node: | |
| names.update(node.input) | |
| names.update(node.output) | |
| return names | |
| def _graph_to_net(cls, onnx_graph, opset_version): | |
| net = caffe2_pb2.NetDef() | |
| for node in onnx_graph.node: | |
| try: | |
| c2ops = cls._onnx_node_to_caffe2_op( | |
| None, None, node, opset_version) | |
| except Exception as e: | |
| print('ONNX FATAL:', e) | |
| continue | |
| net.op.extend(c2ops.init_ops) | |
| net.op.extend(c2ops.ops) | |
| net.external_input.extend(c2ops.interface_blobs) | |
| net.external_output.extend( | |
| value_info.name for value_info in onnx_graph.output) | |
| net.external_input.extend( | |
| value_info.name for value_info in onnx_graph.input) | |
| return net | |
| def _onnx_model_to_caffe2_net(cls, onnx_model, device, opset_version, include_initializers): | |
| device_option = get_device_option(Device(device)) | |
| # Prior to onnx version update to onnx-1.8.0, errors caused by failures in | |
| # in the onnx shape inference call were being supressed. Hence a try-catch block | |
| # is added around the infer_shapes call to avoid these failures and preserve status | |
| try: | |
| onnx_model = onnx.utils.polish_model(onnx_model) | |
| except RuntimeError: | |
| warnings.warn("ShapeInferenceWarning: Inferred shape and existing shape differ in rank") | |
| except AttributeError: | |
| warnings.warn("ShapeInferenceWarning: utils module not found in ONNX version {}".format(onnx.__version__)) | |
| # Optimizer module has been removed in ONNX-1.9 or later, warn caller if that is the case | |
| try: | |
| init_model = cls.optimize_onnx(onnx_model, init=True) | |
| pred_model = cls.optimize_onnx(onnx_model, predict=True) | |
| except ModuleNotFoundError: | |
| warnings.warn("OptimizerWarning: onnxoptimizer module not installed. " | |
| "init_model and pred_model models will not be splitted, which can cause a runtime error") | |
| init_model = onnx_model | |
| pred_model = onnx_model | |
| init_net = caffe2_pb2.NetDef() | |
| pred_net = caffe2_pb2.NetDef() | |
| init_net.name = onnx_model.graph.name + '_init' | |
| pred_net.name = onnx_model.graph.name + '_predict' | |
| if include_initializers: | |
| init_net.op.extend(cls._create_tensor_filling_op(tp) for tp in onnx_model.graph.initializer) | |
| cls._dummy_name.reset(cls._all_names_in_graph(init_model.graph) | cls._all_names_in_graph(pred_model.graph)) | |
| errors = [] | |
| for net, model in ( (init_net, init_model), (pred_net, pred_model) ): | |
| net.device_option.CopyFrom(device_option) | |
| for node in model.graph.node: | |
| try: | |
| c2ops = cls._onnx_node_to_caffe2_op( | |
| init_model, pred_model, node, opset_version) | |
| except Exception as e: | |
| msg = 'Error while processing node: {}. Exception: {}'.format(node, e) | |
| errors.append(msg) | |
| print('ONNX FATAL:', msg, file=sys.stderr) | |
| continue | |
| init_net.op.extend(c2ops.init_ops) | |
| net.op.extend(c2ops.ops) | |
| net.external_input.extend(c2ops.interface_blobs) | |
| net.external_output.extend( | |
| value_info.name for value_info in model.graph.output) | |
| net.external_input.extend( | |
| value_info.name for value_info in model.graph.input) | |
| if len(errors) > 0: | |
| raise RuntimeError( | |
| "ONNX conversion failed, encountered {} errors:\n\n{}".format( | |
| len(errors), "\n\n".join(errors))) | |
| return init_net, pred_net | |
| # wrapper for backwards compatibility | |
| def onnx_graph_to_caffe2_net(cls, model, device="CPU", opset_version=_known_opset_version): | |
| return cls._onnx_model_to_caffe2_net(model, device=device, opset_version=opset_version, include_initializers=True) | |
| def supports_device(cls, device_str): | |
| device = Device(device_str) | |
| if device.type == DeviceType.CPU: | |
| return True | |
| elif core.IsGPUDeviceType(device.type): | |
| return workspace.has_gpu_support | |
| return False | |
| def is_compatible(cls, model, device='CPU', **kwargs): | |
| if hasattr(super(Caffe2Backend, cls), 'is_compatible') \ | |
| and callable(super(Caffe2Backend, cls).is_compatible): | |
| if not super(Caffe2Backend, cls).is_compatible(model, device, **kwargs): | |
| return False | |
| # TODO: should have an unspported list of operators, be optimistic for now | |
| return True | |
| prepare = Caffe2Backend.prepare | |
| prepare_zip_archive = Caffe2Backend.prepare_zip_archive | |
| run_node = Caffe2Backend.run_node | |
| run_model = Caffe2Backend.run_model | |
| supports_device = Caffe2Backend.supports_device # noqa | |
| is_compatible = Caffe2Backend.is_compatible | |