def get_all_backend_handlers(opset_dict): """ Get a dict of all backend handler classes. e.g. {'domain': {'Abs': Abs handler class}, ...}, }. :param opset_dict: A dict of opset. e.g. {'domain': version, ...} :return: Dict. """ handlers = {} for handler in BackendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] handler.VERSION = version since_version = 1 if defs.has(handler.ONNX_OP, domain=handler.DOMAIN): try: since_version = defs.get_schema( handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version).since_version except RuntimeError: warnings.warn( "Fail to get since_version of {} in domain `{}` " "with max_inclusive_version={}. Set to 1.".format( handler.ONNX_OP, handler.DOMAIN, version)) else: warnings.warn("Unknown op {} in domain `{}`.".format( handler.ONNX_OP, handler.DOMAIN or "ai.onnx")) handler.SINCE_VERSION = since_version handlers.setdefault(domain, {})[handler.ONNX_OP] = handler return handlers
def get_all_backend_handlers(opset_dict): """ Get a dict of all backend handler classes. e.g. {'domain': {'Abs': Abs handler class}, ...}, }. :param opset_dict: A dict of opset. e.g. {'domain': version, ...} :return: Dict. """ handlers = {} for handler in BackendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] if domain in opset_dict else 1 handler.VERSION = version since_version = 1 if defs.has(handler.ONNX_OP, domain=handler.DOMAIN): try: since_version = defs.get_schema( # @IgnoreException handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version).since_version except RuntimeError: common.logger.debug("Fail to get since_version of %s in domain `%s` " "with max_inclusive_version=%s. Set to 1.", handler.ONNX_OP, handler.DOMAIN, version) else: common.logger.debug("Unknown op %s in domain `%s`.", handler.ONNX_OP, handler.DOMAIN or "ai.onnx") handler.SINCE_VERSION = since_version handlers.setdefault(domain, {})[handler.ONNX_OP] = handler return handlers
def get_all_backend_handlers(opset_dict): """Get a dict of all backend handler classes. e.g. {'domain': {'Abs': Abs handler class}, ...}, }. :param opset_dict: A dict of opset. e.g. {'domain': version, ...} :return: Dict. """ handlers = {} for handler in BackendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] if domain in opset_dict else 1 handler.VERSION = version since_version = 1 if defs.has(handler.ONNX_OP, domain=handler.DOMAIN): try: since_version = defs.get_schema( handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version, ).since_version except: logger.warning( f"Fail to get since_version of {handler.ONNX_OP} " f"in domain `{handler.DOMAIN}` " f"with max_inclusive_version={version}. Set to 1.") else: logger.warning( f"Unknown op {handler.ONNX_OP} in domain `{handler.DOMAIN}`.") handler.SINCE_VERSION = since_version handlers.setdefault(domain, {})[handler.ONNX_OP] = handler return handlers
def check_node(node): """Checks if a node is legal. Inputs: node: a NodeProto object. Returns: None An exception is thrown if it does not pass the test. """ # General checks. if not isinstance(node, NodeProto): raise RuntimeError('You cannot pass an object that is not NodeProto.') if not node.op_type: raise NameError('NodeProto does not have a proper op_type set.') if not node.input and not node.output: raise ValueError('NodeProto has zero input and zero output.') if not defs.has(node.op_type): raise NameError('Node op_type {} not recognized by onnx.'.format( node.op_type)) if not defs.get_schema(node.op_type).verify(node.SerializeToString()): raise ValueError( 'NodeProto of type {} did not pass defs schema check.'.format( str(node.op_type))) for attr in node.attribute: if attr.HasField('t'): check_tensor(attr.t) elif attr.HasField('tensors'): for tensor in attr.tensors: check_tensor(tensor)
def get_all_backend_handlers(opset_dict): """ Get a dict of all backend handler classes. e.g. {'domain': {'Abs': Abs handler class}, ...}, }. :param opset_dict: A dict of opset. e.g. {'domain': version, ...} :return: Dict. """ handlers = {} for handler in BackendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] if domain in opset_dict else 1 handler.VERSION = version since_version = 1 if defs.has(handler.ONNX_OP, domain=handler.DOMAIN): try: since_version = defs.get_schema( handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version).since_version except RuntimeError: # ONNX throws RuntimeError up to 1.8 common_logging(handler, version) except defs.SchemaError: # ONNX changed to defs.SchemaError since 1.9 common_logging(handler, version) else: common.logger.debug("Unknown op {} in domain `{}`.".format( handler.ONNX_OP, handler.DOMAIN or "ai.onnx")) handler.SINCE_VERSION = since_version handlers.setdefault(domain, {})[handler.ONNX_OP] = handler return handlers
def get_all_frontend_handlers(opset_dict): """ Get a dict of all frontend handler classes. e.g. {'domain': {'Abs': Abs handler class}, ...}, }. :param opset_dict: A dict of opset. e.g. {'domain': version, ...} :return: Dict. """ handlers = {} for handler in FrontendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] handler.VERSION = version since_version = 1 if handler.ONNX_OP and defs.has(handler.ONNX_OP, domain=handler.DOMAIN): since_version = defs.get_schema( handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version).since_version else: warnings.warn("Unknown op {} in domain `{}`. " "Can't check specification by ONNX. " "Please set should_check flag to False " "when call make_node method in handler.".format( handler.ONNX_OP or "Undefined", handler.DOMAIN or "ai.onnx")) handler.SINCE_VERSION = since_version for tf_op in handler.TF_OP: handlers.setdefault(domain, {})[tf_op] = handler return handlers
def get_all_backend_handlers(opset_dict): handlers = {} for handler in BackendHandler.__subclasses__(): handler.check_cls() domain = handler.DOMAIN version = opset_dict[domain] handler.VERSION = version since_version = 1 if defs.has(handler.ONNX_OP, domain=handler.DOMAIN): try: since_version = defs.get_schema( handler.ONNX_OP, domain=handler.DOMAIN, max_inclusive_version=version, ).since_version except RuntimeError: print( "Fail to get since_version of {} in domain `{}` " "with max_inclusive_version={}. Set to 1.".format( handler.ONNX_OP, handler.DOMAIN, version ) ) else: print( "Unknown op {} in domain `{}`.".format( handler.ONNX_OP, handler.DOMAIN or "ai.onnx" ) ) handler.SINCE_VERSION = since_version handlers.setdefault(domain, {})[handler.ONNX_OP] = handler return handlers
def add(self, node): assert self.op_type in [None, node.op_type] if self.op_type is None: self.op_type = node.op_type self.schema = defs.get_schema(self.op_type) for attr in node.attribute: self.attr_coverages[attr.name].add(attr)
def add(self, node): # type: (onnx.NodeProto) -> None assert self.op_type in [None, node.op_type] if self.op_type is None: self.op_type = node.op_type assert self.op_type is not None self.schema = defs.get_schema(self.op_type) for attr in node.attribute: self.attr_coverages[attr.name].add(attr)
def _annotate_consumed(cls, graph_def): for node in graph_def.node: schema = defs.get_schema(node.op_type) consumes = [] for i, _input_name in enumerate(node.input): consume_type, output_idx = schema.consumed(i) if consume_type == defs.OpSchema.UseType.CONSUME_ENFORCED: consumes.append(1) else: consumes.append(0) if any(consumes): node.attribute.extend([helper.make_attribute( 'consumed_inputs', consumes, )])
def _annotate_consumed(cls, graph_def): for node in graph_def.node: schema = defs.get_schema(node.op_type) consumes = [] for i, input_name in enumerate(node.input): consume_type, output_idx = schema.consumed(i) if consume_type == defs.OpSchema.UseType.CONSUME_ENFORCED: consumes.append(1) else: consumes.append(0) if any(consumes): consumes_attr = AttributeProto() consumes_attr.name = "consumed_inputs" consumes_attr.ints.extend(consumes) node.attribute.extend([consumes_attr])
def test_typecheck(self): schema = defs.get_schema("Conv") # kernels should be [int] node_def = helper.make_node(op_type="Conv", inputs=["X", "w"], outputs=["Y"], name="test", kernel_shape=["a"]) self.assertFalse(defs.verify(schema, node_def)) node_def = helper.make_node(op_type="Conv", inputs=["X", "w"], outputs=["Y"], name="test", kernel_shape=[1, 2]) self.assertTrue(defs.verify(schema, node_def))
def test_get_schema(self) -> None: defs.get_schema("Relu")
def test_attr_default_value(self) -> None: v = defs.get_schema( "BatchNormalization").attributes['epsilon'].default_value self.assertEqual(type(v), AttributeProto) self.assertEqual(v.type, AttributeProto.FLOAT)
def test_typecheck(self) -> None: defs.get_schema("Conv")
def test_verify(self): schema = defs.get_schema("Relu") node_def = helper.make_node("Relu", ["X"], ["Y"], name="test") self.assertTrue(defs.verify(schema, node_def))
def generate_onnx_node(node, graph): """ Convert an MDF node into an ONNX node. Takes an MDF node, MDF graph and returns the ONNX node, any inputs to the node coming from outside the graph, any outputs from the node going outside the graph, and an initializer for constants """ onnx_graph_inputs = [] onnx_graph_outputs = [] onnx_initializer = [] sender_port_name = {} # Names of ports that send values to this node # Go over all parameters # Assumption: there may be multiple parameters but there will only be one function. # If there are multiple functions, the code should change so that each function should become its own node. for param in node.parameters: # If this is a constant if param.value: # Create a constant onnx node name = node.id + "_" + param.id constant = helper.make_tensor(name, data_type=TensorProto.FLOAT, dims=[], vals=[param.value]) onnx_initializer.append(constant) # The following will be the sender port from the constant onnx node for this parameter sender_port_name[param.id] = name elif param.function: # This is a function and will be part of an onnx node corresponding to this MDF node function_name = param.function onnx_function_prefix = "onnx::" pattern = re.compile(onnx_function_prefix) if re.match(pattern, function_name): # This is an onnx function function_name = function_name[len(onnx_function_prefix):] # Get the arguments that this onnx function expects schema = get_schema(function_name) # The MDF description would have specified all the expected arguments of ths function function_input_names = [ param.args[arg.name] for arg in schema.inputs ] else: # Error raise "Cannot generate onnx function for the unknown function: {} specfied in the MDF node {}".format( function_name, node.id, ) # Find the inputs to the new ONNX node. These are the senders of the in edges to this node node_in_edges = [edge for edge in graph.edges if edge.receiver == node.id] for in_edge in node_in_edges: sender_port_name[in_edge.receiver_port] = (in_edge.sender + "_" + in_edge.sender_port) onnx_node_input_names = [ sender_port_name[function_input_name] if function_input_name in sender_port_name else function_input_name for function_input_name in function_input_names ] # No parameters. Constants became their own nodes earlier onnx_node_parameters = {} # Find the outputs of the new ONNX node. These are the output ports of the node onnx_node_output_names = [ node.id + "_" + port.id for port in node.output_ports ] # print(node.id, node_in_edges,node_out_edges) # print(function_name, onnx_node_input_names, onnx_node_output_names) # Create an ONNX node onnx_node = helper.make_node( function_name, onnx_node_input_names, onnx_node_output_names, name=node.id, **onnx_node_parameters, ) # Check if any of the node's inputs are the inputs to the ONNX graph itself. # These are the node's inputs that don't have an incoming edge. input_ports_with_edge = [ in_edge.receiver_port for in_edge in node_in_edges ] input_ports_without_edge = [ input_port for input_port in node.input_ports if input_port.id not in input_ports_with_edge ] if input_ports_without_edge: # Create ONNX graph input ports for input_port in input_ports_without_edge: shape = literal_eval(input_port.shape) value_info = helper.make_tensor_value_info(input_port.id, TensorProto.FLOAT, shape) onnx_graph_inputs.append(value_info) # Check if any of the node's outputs are the outputs of the ONNX graph. # These are the node's outputs that don't have an outgoing edge node_out_edges = [edge for edge in graph.edges if edge.sender == node.id] output_ports_with_edge = [ out_edge.sender_port for out_edge in node_out_edges ] output_ports_without_edge = [ output_port for output_port in node.output_ports if output_port.id not in output_ports_with_edge ] if output_ports_without_edge: # Create ONNX graph output ports for output_port in output_ports_without_edge: # No need to create output shapes because they are inferred by ONNX value_info = helper.make_empty_tensor_value_info(node.id + "_" + output_port.id) onnx_graph_outputs.append(value_info) # print("Graph ip op", input_ports_without_edge, output_ports_without_edge) return onnx_node, onnx_graph_inputs, onnx_graph_outputs, onnx_initializer
def test_get_schema(self): # type: () -> None defs.get_schema("Relu")
def test_get_schema(self): defs.get_schema("Relu")
def onnx_node_to_mdf(node: typing.Union[onnx.NodeProto, onnx.ValueInfoProto], onnx_initializer: typing.Dict[str, typing.Dict[str, typing.Any]]) -> Node: """ Construct an MDF node (and function) from an ONNX NodeProto or ValueInfoProto Args: node: The ONNX node to use to form the MDF node. Can be a node from the model or a ValueInfoProto specifying an input or output. onnx_initializer: A specification of values in the graph that ONNX has marked as initializer's. This dict is keyed on the name of the parameter, the value is another dict with two entries: - 'shape': The shape of the parameter - 'type': The data type of the parameter - 'value': The actual value if present. Returns: The equivalent MDF node for the ONNX node passed in as argument. """ # If this is a ONNX Node, if type(node) == onnx.NodeProto: # Create and MDF node with parameters # FIXME: We need to preserve type info somewhere params_dict = {a.name: get_onnx_attribute(a) for a in node.attribute} # For any attributes that are sub-graphs, we need to recurse for aname, val in params_dict.items(): if type(val) == GraphProto: params_dict[aname] = onnx_to_mdf(val, onnx_initializer=onnx_initializer) # If we have we have value constants that feed into this node. Make them parameters # instead of input ports non_constant_inputs = [] for inp_i, inp in enumerate(node.input): if inp in onnx_initializer and 'value' in onnx_initializer[inp]: # Get the name of the formal argument that corresponds to this input. # We need to go to the schema for this. # FIXME: We need to make sure we are going the correct schema here ... yuck! arg_name = get_schema(node.op_type).inputs[inp_i].name params_dict[arg_name] = onnx_initializer[inp]['value'] else: non_constant_inputs.append(inp) # FIXME: parameters must be set or we get JSON serialization error later mdf_node = Node(id=node.name, parameters=params_dict) if bool(params_dict) else Node(id=node.name) # Add the function # FIXME: There is probably more stuff we need to preserve for ONNX Ops func = Function(id=node.name, function=node.op_type) mdf_node.functions.append(func) # Recreate inputs and outputs of ONNX node as InputPorts and OutputPorts for inp in non_constant_inputs: param_info = onnx_initializer.get(inp, None) shape = param_info['shape'] if param_info else "" ip = InputPort(id=inp, shape=shape) mdf_node.input_ports.append(ip) for out in node.output: op = OutputPort(id=out, value=func.get_id()) mdf_node.output_ports.append(op) elif type(node) == onnx.ValueInfoProto: raise NotImplementedError() # # Lets start with an MDF node that uses the ONNX node name as its id. No parameters # mdf_node = Node(id=node.name) # # # This is an input or output node. No Op\Function or parameters. This is just # # a simple pass through node with an input and output port with the correct # # shape. # # FIXME: Should this be necessary? ONNX treats input and output nodes as simple named values. # ip1 = InputPort(id=f"in_port", # shape=str(get_shape_params(node.type.tensor_type.shape))) # FIXME: Why string? # mdf_node.input_ports.append(ip1) # op1 = OutputPort(id=node.name) # op1.value = f"in_port" # mdf_node.output_ports.append(op1) return mdf_node
def test_typecheck(self): # type: () -> None defs.get_schema("Conv")
def test_attr_default_value(self): # type: () -> None v = defs.get_schema( "BatchNormalization").attributes['epsilon'].default_value self.assertEqual(type(v), AttributeProto) self.assertEqual(v.type, AttributeProto.FLOAT)