def extract(cls, node): attrs = { 'shape': node.shape, 'data_type': np.float32, # TODO: other types? } Parameter.update_node_stat(node, {}) return cls.enabled
def extract(node): attrs = { 'data_type': tf_dtype_extractor(node.pb.attr["dtype"].type), 'shape': tf_tensor_shape(node.pb.attr["shape"].shape), 'permute_attrs': PermuteAttrs().update_attrs(attrs=[('shape', 'output:0')]) } Parameter.update_node_stat(node, attrs) return __class__.enabled
def extract(node): attrs = { 'shape': np.array([d.dim_value for d in node.pb.type.tensor_type.shape.dim], dtype=np.int64), } Parameter.update_node_stat(node, attrs) return __class__.enabled
def insert_do(graph: Graph, replacement_descriptions: dict): do_outputs = replacement_descriptions['do_outputs'] prior_boxes_node = Node(graph, 'ROIFeatureExtractor_2') num_classes = 81 box_regressions_input_node = Node( graph, replacement_descriptions['box_regressions_input_node']) box_regressions_node = create_op_node_with_second_input( graph, Reshape, int64_array([-1, 4 * num_classes]), dict(name='box_regressions'), box_regressions_input_node) class_predicitons_node = Node( graph, replacement_descriptions['class_predicitons_node']) im_info_node = Parameter(graph, { "name": 'im_info', 'shape': int64_array([1, 3]) }).create_node() do_node = ExperimentalDetectronDetectionOutput( graph, { 'name': 'DetectionOutput', 'class_agnostic_box_regression': 0, 'deltas_weights': np.array([10.0, 10.0, 5.0, 5.0]), 'max_delta_log_wh': replacement_descriptions['max_delta_log_wh'], 'nms_threshold': replacement_descriptions['nms_threshold'], 'score_threshold': replacement_descriptions['score_threshold'], 'num_classes': num_classes, 'max_detections_per_image': replacement_descriptions['max_detections_per_image'], 'post_nms_count': replacement_descriptions['post_nms_count'] }).create_node() prior_boxes_node.out_port(1).connect(do_node.in_port(0)) box_regressions_node.out_port(0).connect(do_node.in_port(1)) class_predicitons_node.out_port(0).connect(do_node.in_port(2)) im_info_node.out_port(0).connect(do_node.in_port(3)) do_output_ports = [ do_node.out_port(0), do_node.out_port(1), do_node.out_port(2) ] old_do_output_nodes = [Node(graph, node_id) for node_id in do_outputs] for old_node, new_port in zip(old_do_output_nodes, do_output_ports): old_node.out_port(0).get_connection().set_source(new_port) # the consumer of the second output port of the ExperimentalDetectronDetectionOutput is the Mul node which second # input is of type int64 so it is necessary to insert Cast to have data types match do_node.out_port(1).get_connection().insert_node( Cast(graph, { 'dst_type': np.int64 }).create_node())
def create_parameter_with_empty_attrs(graph, param_name): graph.add_node(param_name, kind='op', op='Parameter', name=param_name, pb=None, shape=None) parameter_node = Node(graph, param_name) # need to manually update necessary attrs for the node because extractor will not be called # for it because the node does not have .pb attribute Parameter.update_node_stat(parameter_node, {}) parameter_node['internal_layer_id'] = len(graph.nodes) return parameter_node
def extract(cls, node): t_type = node.pb.type.tensor_type attrs = { 'shape': np.array([d.dim_value for d in t_type.shape.dim], dtype=np.int64), 'data_type': TENSOR_TYPE_TO_NP_TYPE[t_type.elem_type] } Parameter.update_node_stat(node, attrs) return cls.enabled
def replace_sub_graph(graph: Graph, match: dict, **kwargs): inputs_dict = {} for u, v, edge_attrs in graph.out_edges(match['queue_deque'].id, data=True): out_port = edge_attrs['out'] shape = match['fifo_queue'].shapes[out_port] if out_port not in inputs_dict: input_op = Parameter(graph, {'shape': shape.copy()}) inputs_dict[out_port] = input_op.create_node([]) graph.create_edge(inputs_dict[out_port], Node(graph, v), edge_attrs['out'], edge_attrs['in'], edge_attrs) graph.remove_node(match['queue_deque'].id) graph.remove_node(match['fifo_queue'].id)
def create_offsets_for_weighted_sum(self, graph, weighted_sum_nodes, merge_offsets, index_shape): new_offsets = None for i, (node, ind_shape) in enumerate(weighted_sum_nodes): if merge_offsets and len(weighted_sum_nodes) > 1: # generate single offsets input if possible if new_offsets is None: shape = int64_array([len(weighted_sum_nodes), index_shape, 2]) new_offsets = Parameter(graph, {'name': 'Emb_Bag/offsets', 'shape': shape, 'data_type': np.int32}).create_node() log.error( 'Pre-process of offsets is needed for generated input "Emb_Bag/offsets" of shape: {}. ' 'Refer to the documentation on how to convert the ONNX* DLRM model'.format(shape), extra={'is_warning': True}) gather = create_op_with_const_inputs(graph, Gather, {1: int64_array(i), 2: int64_array(0)}, {'name': node.name + '/Gather_'}) new_offsets.out_port(0).connect(gather.in_port(0)) gather.out_port(0).connect(node.in_port(0)) else: shape = int64_array([ind_shape, 2]) new_offsets = Parameter(graph, {'name': 'Emb_Bag/offsets{}'.format(i), 'shape': shape, 'data_type': np.int32}).create_node() new_offsets.out_port(0).connect(node.in_port(0)) log.error( 'Pre-process of offsets is needed for generated input "Emb_Bag/offsets{}" of shape: {}. ' 'Refer to the documentation on how to convert the ONNX* DLRM model'.format(i, shape), extra={'is_warning': True})
def extract(cls, node): t_type = node.pb.type.tensor_type attrs = { 'shape': shape_array([ d.dim_value if (not hasattr(d, 'dim_param') or d.dim_param == '') and d.dim_value != 0 else dynamic_dimension_value for d in t_type.shape.dim ]), 'data_type': TENSOR_TYPE_TO_NP_TYPE[t_type.elem_type] } Parameter.update_node_stat(node, attrs) return cls.enabled
def insert_do(graph: Graph, replacement_descriptions): do_outputs = ['6530', '6532', '6534'] prior_boxes_node = Node(graph, 'ROIFeatureExtractor_2') num_classes = 81 box_regressions_node = create_op_node_with_second_input( graph, Reshape, int64_array([-1, 4 * num_classes]), dict(name='box_regressions'), Node(graph, '2773')) class_predicitons_node = Node(graph, '2774') im_info_node = Parameter(graph, { "name": 'im_info', 'shape': int64_array([1, 3]) }).create_node() do_node = ExperimentalDetectronDetectionOutput( graph, { 'name': 'DetectionOutput', 'class_agnostic_box_regression': 0, 'deltas_weights': np.array([10.0, 10.0, 5.0, 5.0]), 'max_delta_log_wh': replacement_descriptions['max_delta_log_wh'], 'nms_threshold': replacement_descriptions['nms_threshold'], 'score_threshold': replacement_descriptions['score_threshold'], 'num_classes': num_classes, 'max_detections_per_image': replacement_descriptions['max_detections_per_image'], 'post_nms_count': replacement_descriptions['post_nms_count'] }).create_node() prior_boxes_node.out_port(1).connect(do_node.in_port(0)) box_regressions_node.out_port(0).connect(do_node.in_port(1)) class_predicitons_node.out_port(0).connect(do_node.in_port(2)) im_info_node.out_port(0).connect(do_node.in_port(3)) do_output_ports = [ do_node.out_port(0), do_node.out_port(1), do_node.out_port(2) ] old_do_output_nodes = [Node(graph, node_id) for node_id in do_outputs] for old_node, new_port in zip(old_do_output_nodes, do_output_ports): old_node.out_port(0).get_connection().set_source(new_port)
def cover_body_input_data_nodes_with_parameter_ops(ti: Node): body = ti.body op_port_map = [] for record in ti.input_port_map: operation_node = get_internal_node_by_layer_id( ti, record['internal_layer_id']) real_in_port = TensorIterator.special_port_to_real_port( operation_node, copy(record['internal_port_id'])) op_port_map.append((operation_node, real_in_port)) for operation_node, in_port in op_port_map: data_node = operation_node.in_node(in_port) attrs = deepcopy( body.get_edge_data(data_node.id, operation_node.id)[0]) body.remove_edge(data_node.id, operation_node.id) assert data_node.has_valid('shape'), \ 'Data node should have `shape` attribute set, but it`s not for node {}'.format(data_node.id) shape = data_node['shape'].copy() parameter_data_node = Parameter(body, { 'shape': shape }).create_node_with_data() body.create_edge(src_node=parameter_data_node, dst_node=operation_node, out_port=0, in_port=in_port, edge_attrs=attrs) del body.get_edge_data(parameter_data_node.id, operation_node.id)[0]['out']
def replace_pattern(graph: Graph, match: dict): node = match['op'] node_id = node['variable_id'] i = 0 node.in_port(0).disconnect() for dest in node.out_port(0).get_destinations(): new_in = Parameter( graph, { 'name': "Parameter_" + str(i) + "_for_" + node_id, 'shape': dest.data.get_shape() }).create_node() i += 1 dest.disconnect() new_in.out_port(0).connect(dest) log.error("Add input/output mapped {} -> {} ".format( new_in.name, "Result_for_" + node_id), extra={'is_warning': True})
def replace_pattern(graph: Graph, match: dict): node = match['op'] node_id = node['id'] if node.in_port(0).disconnected(): i = 0 for dest in node.out_port(0).get_destinations(): new_in = Parameter(graph, {'name': "Parameter_"+str(i)+"_for_"+node_id, 'shape': dest.data.get_shape()}).create_node() i += 1 dest.disconnect() new_in.out_port(0).connect(dest) log.error("Add input/output mapped {} -> {} ".format(new_in.name, "Result_for_"+node_id), extra={'is_warning': True}) else: out_node_port = node.out_port(0).get_destination() in_node_port = node.in_port(0).get_source() node.in_port(0).disconnect() node.out_port(0).disconnect() crop = Crop(graph, {'name': 'Result_for_'+node_id, 'dim': np.array([1]), 'offset': np.array([0]), 'axis': np.array([0])}).create_node() in_node_port.connect(crop.in_port(0)) crop.out_port(0).connect(out_node_port)
def convert_graph_inputs_to_parameters(internal_graph, internal_graph_proto): # create Parameter nodes for the body graph body_parameters = [] body_parameter_names = [] for idx, pb_node in enumerate(internal_graph_proto['input_arg']): param_id = internal_graph.unique_id(pb_node.name) internal_graph.add_node(param_id, name=param_id, kind='op', op='Parameter', pb=None, shape=None) parameter_node = Node(internal_graph, pb_node.name) Parameter.update_node_stat( parameter_node, { 'data_type': tf_dtype_extractor(pb_node.type), 'permute_attrs': PermuteAttrs().update_attrs(attrs=[('shape', 'output:0')]) }) body_parameters.append(parameter_node) body_parameter_names.append(param_id) return body_parameters, body_parameter_names
def replace_sub_graph(graph: Graph, match: dict, **kwargs): """ Usually graph looks like: main_graph ... Result | | image_batch label_batch \ / batch_join / \ placeholder fifo_queue Replacer works for both cases (that's why we have loop - 68 line): label_batch was marked as output there is no label_batch node """ true_placeholder_shape = match['placeholder'].shape placeholder_shape = match['fifo_queue'].shapes[0] placeholder_data_type = match['fifo_queue'].types[0] assert true_placeholder_shape.ndim <= 1 if true_placeholder_shape.ndim == 1 and len( true_placeholder_shape) > 1: log.warning( 'Placeholder \'{}\' got non 0-dimensional shape {} in FIFOQueue pattern. Placeholder will have the ' 'same shape after folding the pattern instead of {} shape which is original for the network.' ''.format(match['placeholder'].id, true_placeholder_shape, placeholder_shape)) placeholder_shape = true_placeholder_shape placeholder_name = match['fifo_queue'].name graph.erase_node(match['fifo_queue']) graph.erase_node(match['placeholder']) for _, out in match['batch_join'].out_nodes().items(): if out.id != match['image_batch'].id: if out.out_node().op == 'Result': graph.remove_node(out.out_node().id) graph.remove_node(out.id) graph.remove_node(match['batch_join'].id) placeholder = Parameter( graph, { 'name': placeholder_name, 'shape': placeholder_shape, 'data_type': placeholder_data_type }).create_node() graph.create_edge(placeholder, match['image_batch']) log.info( "FIFOQueueV2 pattern was detected. New shape of placeholder {} is {}. Use -b to set batch size if " "needed".format(placeholder.id, placeholder['shape']))
def extract(node): Parameter.update_node_stat(node) return __class__.enabled
def extract(cls, node): if 'value' in node.symbol_dict: Const.update_node_stat(node, {'value': node.symbol_dict['value']}) else: Parameter.update_node_stat(node, {}) return cls.enabled
def replace_sub_graph(self, graph: Graph, match: dict): seq_len_tf = match['seq_len'] transpose_tf = match['transpose'] ctc_greedy_decoder_tf = match['ctc_greedy_decoder'] cast_tf = match['cast'] ctc_loss_tf = match['ctc_loss'] sparse_to_dense_tf = match['sparse_to_dense'] output_sparse_to_dense_name = sparse_to_dense_tf.soft_get( 'name', sparse_to_dense_tf.id) output_ctc_loss_name = ctc_loss_tf.soft_get('name', ctc_loss_tf.id) ctc_greedy_decoder_tf_name = ctc_greedy_decoder_tf.soft_get( 'name', ctc_greedy_decoder_tf.id) log.debug( 'Found CTCLossFrontReplacer pattern after {} with name {}'.format( ctc_greedy_decoder_tf.op, ctc_greedy_decoder_tf.name)) # create sequence mask node, sub-graph for transforming into sequence length and connect with consumers seq_len_tf_shape = seq_len_tf.soft_get('shape', None) if seq_len_tf_shape is None or len(seq_len_tf_shape) != 2: raise Error( 'The sequence length that is the second input to the CTCGreedyDecoder node "{}"' ' must be specified in a mask format.'.format( ctc_greedy_decoder_tf_name)) log.error( 'The format of input sequence length has been changed to a mask format', extra={'is_warning': True}) seq_len_tf_type = seq_len_tf.soft_get('data_type', None) seq_len_tf_name = seq_len_tf.soft_get('name', seq_len_tf.id) seq_mask_placeholder = Parameter( graph, { 'name': seq_len_tf_name, 'shape': seq_len_tf_shape, 'data_type': seq_len_tf_type }).create_node() reduce_to_seq_len_node = create_op_with_const_inputs( graph, ReduceSum, {1: np.array(1, dtype=np.int32)}, { 'name': seq_len_tf_name + '/ReduceToSeqLen', 'keep_dims': False }) reduce_to_seq_len_node.in_port(0).connect( seq_mask_placeholder.out_port(0)) seq_len_tf.out_port(0).get_connection().set_source( reduce_to_seq_len_node.out_port(0)) cast_fp_type = data_type_str_to_np(graph.graph['cmd_params'].data_type) casted_seq_mask_node = Cast(graph, { 'name': seq_len_tf_name + '/CastToFP32', 'dst_type': cast_fp_type }).create_node() casted_seq_mask_node.in_port(0).connect( seq_mask_placeholder.out_port(0)) permuted_casted_seq_mask = create_op_with_const_inputs( graph, Transpose, {1: int64_array([1, 0])}, {'name': seq_len_tf_name + '/Permute'}) permuted_casted_seq_mask.in_port(0).connect( casted_seq_mask_node.out_port(0)) rename_nodes([(seq_len_tf, seq_len_tf_name + '/AbandonedName'), (seq_mask_placeholder, seq_len_tf_name)]) # create CTCGreedyDecoder node and set mask node ctc_merge_repeated_i = ctc_greedy_decoder_tf.soft_get( 'ctc_merge_repeated', ctc_greedy_decoder_tf.id) ctc_greedy_decoder = CTCGreedyDecoderOp( graph, { 'name': output_sparse_to_dense_name, 'ctc_merge_repeated': ctc_merge_repeated_i }).create_node() ctc_greedy_decoder.in_port(1).connect( permuted_casted_seq_mask.out_port(0)) rename_nodes([(sparse_to_dense_tf, output_sparse_to_dense_name + '/AbandonedName'), (ctc_greedy_decoder, output_sparse_to_dense_name)]) # create CTCLoss node and set attributes assert ctc_loss_tf.has_valid('preprocess_collapse_repeated'), \ 'The CTCLoss node "{}" misses "preprocess_collapse_repeated" attribute'.format(output_ctc_loss_name) assert ctc_loss_tf.has_valid('ctc_merge_repeated'), \ 'The CTCLoss node "{}" misses "ctc_merge_repeated" attribute'.format(output_ctc_loss_name) assert ctc_loss_tf.has_valid('unique'), \ 'The CTCLoss node "{}" misses "unique" attribute'.format(output_ctc_loss_name) preprocess_collapse_repeated = ctc_loss_tf.preprocess_collapse_repeated ctc_merge_repeated = ctc_loss_tf.ctc_merge_repeated unique = ctc_loss_tf.unique ctc_loss = CTCLoss( graph, { 'name': output_ctc_loss_name, 'preprocess_collapse_repeated': preprocess_collapse_repeated, 'ctc_merge_repeated': ctc_merge_repeated, 'unique': unique }).create_node() rename_nodes([(ctc_loss_tf, output_ctc_loss_name + '/AbandonedName'), (ctc_loss, output_ctc_loss_name)]) # connect logits ctc_greedy_decoder_tf.in_port(0).get_connection().set_destination( ctc_greedy_decoder.in_port(0)) ctc_loss.in_port(0).disconnect() transpose_tf.in_port(0).get_connection().add_destination( ctc_loss.in_port(0)) # connect logit lengths ctc_greedy_decoder_tf.in_port(1).disconnect() ctc_loss.in_port(1).connect(reduce_to_seq_len_node.out_port(0)) # connect labels to ctc_loss squeeze_op = create_op_with_const_inputs(graph, Squeeze, {1: int64_array([2, 3])}) cast_labels_op = Cast( graph, { 'name': output_sparse_to_dense_name + '/CastLabels', 'dst_type': np.int32 }).create_node() squeeze_op.in_port(0).connect(ctc_greedy_decoder.out_port(0)) cast_labels_op.in_port(0).connect(squeeze_op.out_port(0)) ctc_loss.in_port(2).connect(cast_labels_op.out_port(0)) # connect label lengths equal_op = create_op_with_const_inputs( graph, Equal, {1: np.array([-1], dtype=np.int32)}, {'name': output_sparse_to_dense_name + '/Equal'}) equal_op.in_port(0).connect(cast_labels_op.out_port(0)) labels_shape_op = Shape( graph, { 'name': output_sparse_to_dense_name + '/ShapeOf' }).create_node() labels_shape_op.in_port(0).connect(equal_op.out_port(0)) broadcast_one = create_op_with_const_inputs( graph, Broadcast, {0: np.array([1], dtype=np.int32)}, { 'mode': 'numpy', 'name': output_sparse_to_dense_name + '/One' }) broadcast_one.in_port(1).connect(labels_shape_op.out_port(0)) broadcast_zero = create_op_with_const_inputs( graph, Broadcast, {0: np.array([0], dtype=np.int32)}, { 'mode': 'numpy', 'name': output_sparse_to_dense_name + '/Zero' }) broadcast_zero.in_port(1).connect(labels_shape_op.out_port(0)) select_node = Select(graph, { 'name': output_sparse_to_dense_name + '/Select' }).create_node() select_node.in_port(0).connect(equal_op.out_port(0)) select_node.in_port(1).connect(broadcast_zero.out_port(0)) select_node.in_port(2).connect(broadcast_one.out_port(0)) label_length_node = create_op_with_const_inputs( graph, ReduceSum, {1: int64_array([1])}, op_attrs={ 'name': output_sparse_to_dense_name + '/LabelLength', 'keep_dims': False }) label_length_node.in_port(0).connect(select_node.out_port(0)) ctc_loss.in_port(3).connect(label_length_node.out_port(0)) # set source for output of new sub-graph and remove old nodes ctc_loss_tf.out_port(0).get_connection().set_source( ctc_loss.out_port(0)) graph.remove_nodes_from([ ctc_greedy_decoder_tf.id, ctc_loss_tf.id, cast_tf.id, sparse_to_dense_tf.id ])
def extract(cls, loop_node): Loop.update_node_stat(loop_node, {}) loop_name = loop_node.soft_get('name', loop_node.id) # check that required body and condition functions exist in the graph library main_graph = loop_node.graph body_graph_name = loop_node.pb.attr['body'].func.name cond_graph_name = loop_node.pb.attr['cond'].func.name assert 'library' in main_graph.graph, 'The graph does not contain a library that is required ' \ 'by node with name "{}".'.format(loop_name) library_graph = main_graph.graph['library'] assert body_graph_name in library_graph, 'The library does not contain a function with name "{}" ' \ 'that is required by node ' \ 'with name "{}".'.format(body_graph_name, loop_name) body_graph_proto = library_graph[body_graph_name] assert cond_graph_name in library_graph, 'The library does not contain a function with name "{}" ' \ 'that is required by node ' \ 'with name "{}".'.format(cond_graph_name, loop_name) cond_graph_proto = library_graph[cond_graph_name] body_graph = Graph() # fill the body graph for attr_key in main_graph.graph.keys(): if attr_key != 'library': body_graph.graph[attr_key] = copy.deepcopy(main_graph.graph[attr_key]) else: # it is sufficient to have a link to the library body_graph.graph['library'] = main_graph.graph['library'] loop_node['body'] = body_graph # create Parameter nodes for the body graph body_parameters = [] body_parameter_names = [] for idx, pb_node in enumerate(body_graph_proto['input_arg']): param_id = body_graph.unique_id(pb_node.name) body_graph.add_node(param_id, name=param_id, kind='op', op='Parameter', pb=None, shape=None) parameter_node = Node(body_graph, pb_node.name) Parameter.update_node_stat(parameter_node, {'data_type': tf_dtype_extractor(pb_node.type), 'permute_attrs': PermuteAttrs().update_attrs(attrs=[('shape', 'output:0')])} ) body_parameters.append(parameter_node) body_parameter_names.append(param_id) # update the loop body graph with the body function graph body_results = [] update_body_graph(body_graph, body_graph_proto, body_parameter_names, body_results) # update the loop body graph with the condition function graph update_body_graph(body_graph, cond_graph_proto, body_parameter_names, body_results) # add 'internal_layer_id' attribute which is a must have attribute for the loop body node for idx, body_node in enumerate(body_graph.get_op_nodes()): body_node['internal_layer_id'] = idx body_graph.stage = 'front' # Currently, # Loop Inputs Order: # 0 - current iteration # 1 - trip count # 2.. - "loop carried" dependencies variables # # Body Inputs Order: # 0 - current iteration # 1 - trip count # 2.. - "loop carried" dependencies variables # # Body Outputs Order: # 0 - current iteration # 1 - trip count # 2.. - "loop carried" dependencies variables # # Loop Outputs Order: # 0 - current iteration # 1 - trip count # 2.. - "loop carried" dependencies variables # # so inputs must be reordered and execution condition must be created in the front transformation # to be aligned with the specification # connect external input ports with body parameter nodes except current iteration # since it must be disconnected from external port for idx in range(1, len(body_parameters)): Loop.connect_body_input(loop_node, idx, body_parameters[idx]) # mark current iteration input Parameter node and execution condition Result node Loop.mark_current_iteration_parameter_node(loop_node, body_parameters[0]) Loop.mark_execution_condition_result_node(loop_node, body_results[-1]) # connect back edges in the body except current iteration for idx in range(1, len(body_parameters)): Loop.add_back_edge(loop_node, body_parameters[idx], body_results[idx]) # connect body outputs with Loop operation output ports except the execution condition result for idx in range(len(body_results)-1): Loop.connect_body_output(loop_node, idx, body_results[idx]) # run function to parse body nodes attributes similar to the main graph extract_node_attrs(body_graph, lambda node: tf_op_extractor(node, check_for_duplicates(tf_op_extractors))) return cls.enabled
def extract(cls, node): Parameter.update_node_stat(node) return cls.enabled
def extract(node): Parameter.update_node_stat( node, {'shape': dim_to_shape(node.pb.input_param.shape[0].dim)}) return __class__.enabled
def extract(cls, loop_node): Loop.update_node_stat(loop_node, {}) body_graph_proto = onnx_attr(loop_node, 'body', 'g', None) main_graph = loop_node.graph # create a Graph object for the body and take graph attributes from the main graph body_graph = Graph() main_graph_attrs_copy = copy.deepcopy(main_graph.graph) del main_graph_attrs_copy['tensor_mapping'] body_graph.graph.update(main_graph_attrs_copy) loop_node['body'] = body_graph # maps a tensor name to a node produced it and the node port: str -> (node_id, node_port) data_nodes_map = {} body_graph.graph[ 'tensor_mapping'] = data_nodes_map # save mapping for possible Loop inside the Loop body_parameters = add_initializers_and_inputs_to_graph( body_graph, body_graph_proto, data_nodes_map) external_edges = [ ] # (src_node, src_out_port), dest_body_parameter_node additional_params = { } # (src_node, src_out_port) -> parameter_node (for manually added Parameters) # Go through all nodes in the original model order because data nodes are defined on-the-fly and order matters for pb_node in body_graph_proto.node: # create an NX node id = body_graph.unique_id(node_id(pb_node)) body_graph.add_node(id, pb=pb_node, kind='op') # add incoming edges based on data_nodes_map for dst_port, inp in enumerate(pb_node.input): # should add edge inp --> id if inp not in data_nodes_map: if inp == '': # input is omitted; most likely it corresponds to an optional input for an operator continue elif inp in main_graph.graph['tensor_mapping']: log.debug( 'The edge between outer and inner graphs detected: {} -> {}' .format(inp, id)) if main_graph.graph['tensor_mapping'][ inp] not in additional_params: # create new Parameter body node and connect the body node with the outer graph using it param_id = str(inp) body_graph.add_node(param_id, kind='op', op='Parameter', name=param_id, pb=None, shape=None) parameter_node = Node(body_graph, param_id) # need to manually update necessary attrs for the node because extractor will not be called # for it because the node does not have .pb attribute Parameter.update_node_stat(parameter_node, {}) external_edges.append( (main_graph.graph['tensor_mapping'][inp], parameter_node)) src_id, src_port = param_id, 0 additional_params[main_graph.graph[ 'tensor_mapping'][inp]] = parameter_node else: src_id, src_port = additional_params[ main_graph.graph['tensor_mapping'][inp]].id, 0 else: raise Error( 'Reference to "{}" is not satisfied. A node refer not existing data tensor. ONNX ' 'model is not consistent. Protobuf fragment: {}', inp, pb_node) else: src_id, src_port = data_nodes_map[inp] assert (body_graph.has_node(src_id)) edge_attrs = { 'out': src_port, 'in': dst_port, 'name': inp, 'fw_tensor_debug_info': [(inp, inp)], 'in_attrs': ['in', 'name'], 'out_attrs': ['out', 'name'], 'data_attrs': ['fw_tensor_debug_info'] } body_graph.add_edge(src_id, id, **edge_attrs) # add outgoing edges to data_nodes_map for src_port, out in enumerate(pb_node.output): if out in data_nodes_map: log.debug("Detected reuse of blob {}.".format(out)) data_nodes_map[out] = (id, src_port) body_results = [] for output in body_graph_proto.output: tensor_name = str(output.name) node_name, output_port = data_nodes_map[tensor_name] assert body_graph.has_node( node_name ), 'The body graph does not contain output with name "{}"'.format( node_name) body_results.append( Node(body_graph, add_opoutput(body_graph, node_name, output_port, False))) # add 'internal_layer_id' attribute which is a must have attribute for the loop body node for idx, body_node in enumerate(body_graph.get_op_nodes()): body_node['internal_layer_id'] = idx loop_carried_dependencies_count = len(body_graph_proto.input) - 2 scan_outputs_count = len( body_graph_proto.output) - 1 - loop_carried_dependencies_count # Loop inputs: # 0 - trip count # 1 - execution condition # 2 .. - loop carried dependencies # Loop outputs: # 0 .. loop_carried_dependencies_count - 1 - loop carried dependencies # loop_carried_dependencies_count .. - scan outputs # Body inputs: # 0 - iteration number # 1 - execution condition # 2 .. - loop carried dependencies # Body outputs: # 0 - execution condition # 1 .. loop_carried_dependencies_count - loop carried dependencies # loop_carried_dependencies_count + 1 .. - scan outputs body_graph.stage = 'front' # some of the inputs/outputs may not be connected but the normalization transformation will take care of it # connection Loop body nodes with external input edges next_loop_input_port_idx = sorted(loop_node.in_edges().keys())[-1] + 1 for (src_node, src_port), body_node in external_edges: main_graph.add_edge( src_node, loop_node.id, **{ 'out': src_port, 'in': next_loop_input_port_idx, 'name': src_node, 'fw_tensor_debug_info': [(src_node, src_node)], 'in_attrs': ['in', 'name'], 'out_attrs': ['out', 'name'], 'data_attrs': ['fw_tensor_debug_info'] }) connect_body_input(loop_node, next_loop_input_port_idx, body_node) next_loop_input_port_idx += 1 # mark current iteration input Parameter node Loop.mark_current_iteration_parameter_node(loop_node, body_parameters[0]) # connect initial value for "execution condition" input of the loop connect_body_input(loop_node, 1, body_parameters[1]) # add back edge with "execution condition" Loop.add_back_edge(loop_node, body_parameters[1], body_results[0]) # mark "execution condition" Result node Loop.mark_execution_condition_result_node(loop_node, body_results[0]) # connect initial value for "loop carried" dependencies variables for idx in range(loop_carried_dependencies_count): connect_body_input(loop_node, idx + 2, body_parameters[idx + 2]) # add back edge for "loop carried" dependencies variables for idx in range(loop_carried_dependencies_count): Loop.add_back_edge(loop_node, body_parameters[idx + 2], body_results[idx + 1]) # connect final value for "loop carried" dependencies variables for idx in range(loop_carried_dependencies_count): connect_body_output(loop_node, idx, body_results[idx + 1]) # connect "scan outputs" and mark axis for concatenation for idx in range(loop_carried_dependencies_count, loop_carried_dependencies_count + scan_outputs_count): connect_body_output(loop_node, idx, body_results[idx + 1], axis=0) # run function to parse body nodes attributes similar to the main graph extract_node_attrs( body_graph, lambda node: onnx_op_extractor( node, check_for_duplicates(onnx_op_extractors))) return cls.enabled