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): op_strs = cbackend.convert_node(node_def.SerializeToString(), 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.Iterable): ops = [ops] return Caffe2Ops(ops, [], [])
def test_tensor_filling_ops_c_backend(self): for dtype in [ onnx.TensorProto.FLOAT, onnx.TensorProto.DOUBLE, onnx.TensorProto.BOOL, onnx.TensorProto.INT8, onnx.TensorProto.INT16, onnx.TensorProto.INT32, onnx.TensorProto.INT64, onnx.TensorProto.UINT8, onnx.TensorProto.UINT16, onnx.TensorProto.UINT32, ]: shape = (1, 2, 3) vals = np.random.randn(*shape) if dtype != onnx.TensorProto.BOOL: vals *= 5 vals = vals.astype(mapping.TENSOR_TYPE_TO_NP_TYPE[dtype]) tensor = make_tensor( name='test-tensor-{}'.format(dtype), data_type=dtype, dims=[1, 2, 3], vals=vals.flatten().tolist(), ) b = C.Caffe2Backend() op = caffe2_pb2.OperatorDef() op.ParseFromString( b._build_tensor_filling_op(tensor.SerializeToString(), '')) self.assertEqual(len(op.input), 0) self.assertEqual(op.output, [tensor.name]) ws, output = c2_native_run_op(op, inputs=[]) self.assertEqual(len(output), 1) np.testing.assert_almost_equal(output[0], vals) np.testing.assert_almost_equal(ws.FetchBlob(op.output[0]), vals)
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) 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) 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) ops = [] cbackend = C.Caffe2Backend(cls._dummy_name) ops_str = cbackend.convert_node(node.SerializeToString(), 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) # For testing if "ONNX_CAFFE2_DEBUG" in os.environ: init_ops, ops2, _ = cls._onnx_node_to_caffe2_op( None, None, node, opset_version or cls._known_opset_version) ops2 = init_ops + ops2 for op in ops2: op.device_option.CopyFrom(device_option) print("\nC++:\n{}\nPython:\n{}".format(ops, ops2)) ws.RunOperatorsOnce(ops) output_values = [ws.FetchBlob(name) for name in node.output] return namedtupledict('Outputs', node.output)(*output_values)
def test_check_arguments(self): b2 = C.Caffe2Backend() node_def = make_node("Add", inputs=["X", "Y"], outputs=["Z"]) b2.convert_node(node_def.SerializeToString()) bad_node_def = make_node("Add", inputs=["X", "Y"], outputs=["Z"], foo=42, bar=56) with self.assertRaisesRegexp( RuntimeError, "Don't know how to map unexpected argument (foo|bar)"): b2.convert_node(bad_node_def.SerializeToString())
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 test_check_arguments(self): X = np.random.randn(3, 2).astype(np.float32) Y = np.random.randn(3, 2).astype(np.float32) Z = np.zeros((3, 2)).astype(np.float32) b2 = C.Caffe2Backend() node_def = make_node("Add", inputs = ["X", "Y"], outputs = ["Z"], broadcast = 0) output = b2.convert_node(node_def.SerializeToString(), 6) bad_node_def = make_node("Add", inputs = ["X", "Y"], outputs = ["Z"], foo = 42, bar = 56) with self.assertRaisesRegexp( RuntimeError, ".*?Don't know how to map unexpected argument (foo|bar) \(from operator .*?\).*$"): output = b2.convert_node(bad_node_def.SerializeToString(), 6)
def test_gemm_conversion(self): node_def = make_node('Gemm', ['A', 'B', 'C'], ["Y"], alpha=2., beta=3., transB=True) backend = C.Caffe2Backend() # without broadcast and without shape info, gemm will be # converted to matmul + add _, op_strs = backend.convert_node(node_def.SerializeToString()) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'MatMul', 'Add']) # with shape info (that indicates C is 1D), gemm will be # converted to FC _, op_strs = backend.convert_node(node_def.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (1, )).SerializeToString() ]) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FC']) # or with broadcast, gemm will be converted to fc node_def = make_node('Gemm', ['A', 'B', 'C'], ["Y"], transB=True, broadcast=1) _, op_strs = backend.convert_node(node_def.SerializeToString()) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['FC'])
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, [], [])
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 model = onnx.shape_inference.infer_shapes(model) # Check whether we have RNN related ops pred_model = cls.optimize_onnx(model, predict=True) rnn_nodes = [] for node in pred_model.graph.node: if node.op_type in {'LSTM', 'GRU', 'RNN'}: rnn_nodes.append(node) # Build the C++ backend # TODO: build a predictor that supports GPU # And for RNN nets, we need to avoid adding init_net use_cpp_backend = device == 'CPU' and not rnn_nodes # use python backend for now use_cpp_backend = False if use_cpp_backend: c2_rnn_ops = [] if rnn_nodes: init_model = cls.optimize_onnx(model, init=True) for node in rnn_nodes: c2ops = cls._onnx_node_to_caffe2_op( init_model, pred_model, node, opset_version) init_ops = [x.SerializeToString() for x in c2ops.init_ops] ops = [x.SerializeToString() for x in c2ops.ops] external_inputs = c2ops.interface_blobs c2_rnn_ops.append( C.Caffe2Ops(init_ops, ops, external_inputs)) del init_model cbackend = C.Caffe2Backend(cls._dummy_name) if raw_values_dict: cls._external_value_resolution_pass(model, raw_values_dict) rep = cbackend.prepare(model.SerializeToString(), device, c2_rnn_ops) # For testing # Dump the net descriptions to file for comparison with the Python ones if "ONNX_CAFFE2_DEBUG" in os.environ: pred_net_str = rep.pred_net() pn = caffe2_pb2.NetDef() pn.ParseFromString(pred_net_str) init_net_str = rep.init_net() inn = caffe2_pb2.NetDef() inn.ParseFromString(init_net_str) with open("cpp.txt", "w") as f: f.write("pred_net: \n{}".format(pn)) rep_wrapper = Caffe2CppRep(rep) return rep_wrapper else: 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 ] if "ONNX_CAFFE2_DEBUG" in os.environ: with open("python.txt", "w") as f: f.write("pred_net: \n{}".format(predict_net)) retval = Caffe2Rep(init_net, predict_net, ws, uninitialized) return retval
def test_gemm_conversion(self): node_def = make_node('Gemm', ['A', 'B', 'C'], ["Y"], alpha=2., beta=3.) node_def_broadcast = make_node('Gemm', ['A', 'B', 'C'], ["Y"], alpha=2., beta=3., broadcast=1) node_def_transpose_b = make_node('Gemm', ['A', 'B', 'C'], ["Y"], alpha=2., beta=3., transB=1) node_def_transpose_b_broadcast = make_node('Gemm', ['A', 'B', 'C'], ["Y"], alpha=2., beta=3., transB=1, broadcast=1) backend = C.Caffe2Backend() # without broadcast and without shape info, gemm will be # converted to matmul + add _, op_strs = backend.convert_node(node_def.SerializeToString()) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'MatMul', 'Add']) # opset7 # If C is a 1d tensor, gemm will be converted to FC/FCTransposed _, op_strs = backend.convert_node( node_def_transpose_b.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (3, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FC']) _, op_strs = backend.convert_node(node_def.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (3, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FCTransposed']) # opset6 without broadcast(C should match A*B's dim) # The gemm will be converted to matmul + add, since the FC requires c # to be 1d tensor. _, op_strs = backend.convert_node(node_def.SerializeToString(), [ make_tensor_value_info("A", onnx.TensorProto.FLOAT, (3, 2)).SerializeToString(), make_tensor_value_info("B", onnx.TensorProto.FLOAT, (2, 3)).SerializeToString(), make_tensor_value_info("C", onnx.TensorProto.FLOAT, (3, 3)).SerializeToString() ], 6) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'MatMul', 'Add']) # opset6 with broadcast # If C is a 1d tensor, gemm will be converted to FC/FCTransposed _, op_strs = backend.convert_node( node_def_transpose_b_broadcast.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (3, )).SerializeToString() ], 6) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FC']) _, op_strs = backend.convert_node( node_def_broadcast.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (3, )).SerializeToString() ], 6) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FCTransposed']) # opset7 # If C is a scalar and B's last dim is 1, gemm will be converted to FC/FCTransposed _, op_strs = backend.convert_node( node_def_transpose_b.SerializeToString(), [ make_tensor_value_info("B", onnx.TensorProto.FLOAT, (1, 2)).SerializeToString(), make_tensor_value_info("C", onnx.TensorProto.FLOAT, (1, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FC']) _, op_strs = backend.convert_node(node_def.SerializeToString(), [ make_tensor_value_info("B", onnx.TensorProto.FLOAT, (2, 1)).SerializeToString(), make_tensor_value_info("C", onnx.TensorProto.FLOAT, (1, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'FCTransposed']) # If C is a scalar and B's last dim is not 1, gemm will be converted # to matmul + add. _, op_strs = backend.convert_node( node_def_transpose_b.SerializeToString(), [ make_tensor_value_info("B", onnx.TensorProto.FLOAT, (2, 2)).SerializeToString(), make_tensor_value_info("C", onnx.TensorProto.FLOAT, (1, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'MatMul', 'Add']) # If C is a scalar and B's shape info is not available, # gemm will be converted to matmul + add. _, op_strs = backend.convert_node( node_def_transpose_b.SerializeToString(), [ make_tensor_value_info("C", onnx.TensorProto.FLOAT, (1, )).SerializeToString() ], 7) op_names = [] for s in op_strs: op = caffe2_pb2.OperatorDef() op.ParseFromString(s) op_names.append(op.type) self.assertEqual(op_names, ['Scale', 'Scale', 'MatMul', 'Add'])