def test_sub_graph_between_nodes_control_flow_included(self): """ Check that the function works correctly for case when control flow edges must be traversed (edge 5 -> 2). 6 -> 5-> \ 1 -> 2 -> 3 -> 4 """ graph = Graph() graph.add_nodes_from(list(range(1, 7))) graph.add_edges_from([(1, 2), (2, 3), (3, 4), (5, 2, { 'control_flow_edge': True }), (6, 5)]) sub_graph_nodes = sub_graph_between_nodes(graph, [1], [4], include_control_flow=True) self.assertIsNotNone(sub_graph_nodes) self.assertListEqual(sorted(sub_graph_nodes), sorted([1, 2, 3, 4, 5, 6]))
def find_and_replace_pattern(self, graph: Graph): global_poolings = graph.get_op_nodes(type='Pooling', global_pool=True) if len(global_poolings) == 0: return layout = graph.graph['layout'] assert layout != 'NHWC', 'Global pooling transformation depends on layout (NHWC not enabled)' for pooling in global_poolings: name = pooling.soft_get('name', pooling.id) assert pooling.has_valid('pool_method'), 'Global Pooling {} has no `pool_method` attribute'.format(name) method = pooling['pool_method'] assert method in self.pool_method_to_reduce_type, \ 'Unexpected Global Pooling method `{}` for node `{}`'.format(method, name) reduce_op_class = self.pool_method_to_reduce_type[method] reduce = reduce_op_class(graph, {'name': name + '/reduce', 'keep_dims': True}).create_node() pooling.out_port(0).get_connection().set_source(reduce.out_port(0)) src = pooling.in_port(0).get_connection().get_source() reduce.in_port(0).get_connection().set_source(src) start = Const(graph, {'value': int64_array(2)}).create_node() end = Rank(graph, {'name': name + '/input_rank'}).create_node() delta = Const(graph, {'value': int64_array(1)}).create_node() axis = Range(graph, {'name': name + '/global_pooling_reduce_axis'}).create_node() axis.in_port(0).connect(start.out_port(0)) src.connect(end.in_port(0)) axis.in_port(1).connect(end.out_port(0)) axis.in_port(2).connect(delta.out_port(0)) axis.out_port(0).connect(reduce.in_port(1)) log.debug('Global {} pooling was converted to reduce: `{}`'.format(method, name))
def find_and_replace_pattern(self, graph: Graph): for pool_v2_node in graph.get_op_nodes(op='PoolingV2'): pool_v2_name = pool_v2_node.soft_get('name', pool_v2_node.id) pool_v1_node = Pooling( graph, { 'window': pool_v2_node.in_port(1).data.get_value(), 'stride': pool_v2_node.in_port(2).data.get_value(), 'pad': pool_v2_node.pad, 'spatial_dims': pool_v2_node.spatial_dims, 'auto_pad': pool_v2_node.auto_pad, 'output_spatial_shape': pool_v2_node.output_spatial_shape, 'pad_spatial_shape': pool_v2_node.pad_spatial_shape, 'pool_method': pool_v2_node.pool_method, 'permute_attrs': pool_v2_node.permute_attrs, }).create_node() rename_nodes([(pool_v2_node, pool_v2_name + '/to_be_removed'), (pool_v1_node, pool_v2_name)]) pool_v2_node.in_port(0).get_connection().set_destination( pool_v1_node.in_port(0)) pool_v2_node.out_port(0).get_connection().set_source( pool_v1_node.out_port(0))
def find_and_replace_pattern(self, graph: Graph): for node in graph.get_op_nodes(): node_type = node.soft_get('type').lower() name = node.soft_get('name', node.id) if node.soft_get('version', None) == 'opset1' and node_type not in self.opset_1_types \ and node_type not in self.opset_2_legacy_ops: raise Error( 'Node {} has `version` attribute set to `{}`, but it is a reserved word, ' 'please use another'.format(name, node.version)) if not node.has_valid('version'): if node_type in self.opset_1_types: node['version'] = 'opset1' elif node_type in self.opset_1_experimental_ops: node['version'] = 'experimental' elif node_type in self.opset_2_legacy_ops: node['version'] = 'opset2' else: node['version'] = 'extension' log.error( 'Please set `version` attribute for node {} with type={}' ''.format(name, node.soft_get('type')), extra={'is_warning': True})
def convert_node_blobs(graph: Graph, node: Node, data_type: type): out_edges = graph.out_edges(node.node, data=True) # if the data.value is used as binary weights if any('bin' in d for _, __, d in out_edges): blob = node.value if blob.dtype != data_type: new_blob, finite_match_count, zero_match_count = convert_blob( blob, data_type) consumers = [ x.name if x.has_valid('name') else '<NO NAME>' for x in node.out_nodes() ] log.debug( 'Blob was converted to {} while dumping to the bin file. This blob is an input for {} nodes.' .format(data_type, consumers)) if finite_match_count: log.error(( "{} elements of {} were clipped to infinity while converting a blob for node [{}] to {}. " + refer_to_faq_msg(76)).format(finite_match_count, blob.size, consumers, data_type)) if zero_match_count: log.warning(( "{} elements of {} were clipped to zero while converting a blob for node [{}] to {}. " + refer_to_faq_msg(77)).format(zero_match_count, blob.size, consumers, data_type)) node.value = new_blob # for the constant node need to propagate the converted value to the node output because there is a fake # input data for the 'Const' nodes being generated in the CreateConstNodesReplacement if len(node.out_nodes()) == 1 and node.out_node(0).op == 'Const': const_node = node.out_node(0) const_node.value = new_blob const_node.infer(const_node) const_node.type_infer(const_node)
def override_batch(graph: Graph, batch: int): """ Overrides batch for nodes with 'op' param set to 'Parameter' Parameters ---------- graph: graph to operate on batch: user defined integer value to override batch """ if batch is not None: in_nodes = graph.get_op_nodes(op='Parameter') for node in in_nodes: if not node.soft_get('fixed_batch', False): name = node.soft_get('name', node.id) idx, has_layout = get_dim_from_layout(node, 'N') if has_layout: if idx is not None: node['shape'][idx] = batch else: log.warning( 'Layout for input {} doesn\'t have batch dimension. Skipping this input.' .format(name)) else: validate_batch_in_shape(node['shape'], name) node['shape'][0] = batch
def find_and_replace_pattern(self, graph: Graph): for concat in graph.get_op_nodes(type='Concat'): for in_port in concat.in_ports().values(): if not in_port.disconnected(): shape = in_port.data.get_shape() assert shape is not None if 0 in shape: concat.delete_input_port(in_port.idx) connected_ports = [port for port_idx, port in sorted(concat.in_ports().items()) if not port.disconnected()] assert len(connected_ports), 'Concat "{}" have no inputs after removing inputs with 0 dimensions' \ ''.format(concat.soft_get('name', concat.id)) max_port_index = max([port_idx for port_idx in concat.in_ports().keys()]) # re-connect input ports sequentially and remove all not used port_idx_to_connect = 0 for port_idx in range(max_port_index + 1): if concat.is_in_port_connected(port_idx): if port_idx != port_idx_to_connect: concat.add_input_port(port_idx_to_connect, skip_if_exist=True) concat.in_port(port_idx).get_connection().set_destination(concat.in_port(port_idx_to_connect)) port_idx_to_connect += 1 elif port_idx in concat.in_ports(): concat.delete_input_port(port_idx)
def test_sub_graph_between_nodes_include_incoming_edges_for_internal_nodes( self): """ Check that the function adds input nodes for the internal nodes of the graph. For example, we need to add node 5 and 6 in the case below if we find match from node 1 till node 4. 6 -> 5 -> \ 1 -> 2 -> 3 -> 4 :return: """ graph = Graph() graph.add_nodes_from(list(range(1, 7))) graph.add_edges_from([(1, 2), (2, 3), (3, 4), (5, 2), (6, 5)]) sub_graph_nodes = sub_graph_between_nodes(graph, [1], [4]) self.assertIsNotNone(sub_graph_nodes) self.assertListEqual(sorted(sub_graph_nodes), list(range(1, 7))) sub_graph_nodes = sub_graph_between_nodes(graph, [1], [2]) self.assertIsNotNone(sub_graph_nodes) self.assertListEqual(sorted(sub_graph_nodes), [1, 2, 5, 6])
def replace_pattern(graph: Graph, match: dict): """ Need to find the pattern: Memory -> Data -> Result It is needed to make Memory nodes appear in IR, but they are output nodes by default and we remove the Result node after each output memory. DO NOT use graph clean up after it otherwise Memory nodes would be removed as they are not on the path from input to output Parameters ---------- graph : Graph Graph with loaded model. match : dict Patterns which were found in graph structure. """ memory = match['memory_node'] data = match['data_node'] op_output = match['op_output'] graph.remove_edge(memory.id, data.id) graph.remove_node(data.id) graph.remove_node(op_output.id)
def transform_graph(self, graph: Graph, replacement_descriptions: dict): parameter_node = graph.get_op_nodes(op='Parameter')[0] parameter_node['data_type'] = data_type_str_to_np( parameter_node.graph.graph['cmd_params'].data_type) # remove existing Result operations to remove unsupported sub-graph graph.remove_nodes_from( [node.id for node in graph.get_op_nodes(op='Result')] + ['detections']) # determine if the op which is a input/final result of mean value and scale applying to the input tensor # then connect it to the input of the first convolution of the model, so we remove the image pre-processing # which includes padding and resizing from the model preprocessing_input_node_id = replacement_descriptions[ 'preprocessing_input_node'] assert preprocessing_input_node_id in graph.nodes, 'The node with name "{}" is not found in the graph. This ' \ 'should be a last node before image normalization and is specified' \ ' in the json file.'.format(preprocessing_input_node_id) preprocessing_input_node = Node(graph, preprocessing_input_node_id) consumer_node = preprocessing_input_node.out_port( 0).get_connection().get_destination().node consumer_node.in_port(0).get_connection().set_source( parameter_node.out_port(0)) preprocessing_output_node_id = replacement_descriptions[ 'preprocessing_output_node'] assert preprocessing_output_node_id in graph.nodes, 'The node with name "{}" is not found in the graph. This ' \ 'node should provide scaled image output and is specified' \ ' in the json file.'.format(preprocessing_output_node_id) preprocessing_output_node = Node(graph, preprocessing_output_node_id) preprocessing_output_node.out_port(0).disconnect() convolution_nodes = [ n for n in graph.pseudo_topological_sort() if n.soft_get('type') == 'Convolution' ] convolution_nodes[0].in_port(0).get_connection().set_source( preprocessing_output_node.out_port(0)) # create prior boxes (anchors) generator aspect_ratios = replacement_descriptions['aspect_ratios'] assert len(aspect_ratios) % 2 == 0 aspect_ratios = list(zip(aspect_ratios[::2], aspect_ratios[1::2])) priors_generator = self.AnchorGenerator( min_level=int(replacement_descriptions['min_level']), aspect_ratios=aspect_ratios, num_scales=int(replacement_descriptions['num_scales']), anchor_scale=replacement_descriptions['anchor_scale']) prior_boxes = [] for i in range(100): inp_name = 'box_net/box-predict{}/BiasAdd'.format('_%d' % i if i else '') if inp_name not in graph: break widths, heights = priors_generator.get(i) prior_box_op = PriorBoxClusteredOp( graph, { 'width': mo_array(widths), 'height': mo_array(heights), 'clip': 0, 'flip': 0, 'variance': replacement_descriptions['variance'], 'offset': 0.5 }) prior_boxes.append( prior_box_op.create_node( [Node(graph, inp_name), parameter_node])) # concatenate prior box operations concat_prior_boxes = Concat(graph, {'axis': -1}).create_node() for idx, node in enumerate(prior_boxes): concat_prior_boxes.add_input_port(idx) concat_prior_boxes.in_port(idx).connect(node.out_port(0)) conf = Sigmoid(graph, dict(name='concat/sigmoid')).create_node( [Node(graph, 'concat')]) reshape_size_node = Const(graph, { 'value': int64_array([0, -1]) }).create_node([]) logits = Reshape(graph, dict(name=conf.name + '/Flatten')).create_node( [conf, reshape_size_node]) deltas = Reshape(graph, dict(name='concat_1/Flatten')).create_node( [Node(graph, 'concat_1'), reshape_size_node]) # revert convolution boxes prediction weights from yxYX to xyXY (convolutions share weights and bias) weights = Node(graph, 'box_net/box-predict/pointwise_kernel') weights.value = weights.value.reshape(-1, 4)[:, [1, 0, 3, 2]].reshape( weights.shape) bias = Node(graph, 'box_net/box-predict/bias') bias.value = bias.value.reshape(-1, 4)[:, [1, 0, 3, 2]].reshape(bias.shape) detection_output_node = DetectionOutput( graph, dict( name='detections', share_location=1, background_label_id=int( replacement_descriptions['num_classes']) + 1, nms_threshold=replacement_descriptions['nms_threshold'], confidence_threshold=replacement_descriptions[ 'confidence_threshold'], top_k=100, keep_top_k=100, code_type='caffe.PriorBoxParameter.CENTER_SIZE', )).create_node([deltas, logits, concat_prior_boxes]) output_op = Result(graph, dict(name='output')) output_op.create_node([detection_output_node])
def find_and_replace_pattern(self, graph: Graph): for div in graph.get_op_nodes(op='Div'): self.div_to_mul_replacement(div)
def load(self, graph: Graph): graph.check_empty_graph('loading from framework')
def find_and_replace_pattern(self, graph: Graph): # Iterate over all data nodes and find all with >= 1 consumers for input_data in list(graph.get_data_nodes()): # We don't use constant data nodes if input_data.value is not None: continue if input_data.shape is None: continue input_shape = shape_array(input_data.shape) # Get all unique StridedSlice consumers out_nodes = [node for node in input_data.out_nodes() if node.op == 'StridedSlice' and node.in_node(0).id == input_data.id] if len(out_nodes) <= 1: continue valid_for_replacement = True for n in out_nodes: if any(not isinstance(s, slice) for s in n.slices): # this is a slice with dynamic dimension. Such operation is not valid for replacement valid_for_replacement = False if not valid_for_replacement: continue sorted_out_nodes = sorted(out_nodes, key=lambda n: list(n.slices)) out_nodes = unique_by(sorted_out_nodes, strided_slices_equality) for node in out_nodes: if len(node.slices) != len(out_nodes[0].slices): valid_for_replacement = False # Detect dimension for splitting split_channel_dim = None for dim_id, s in enumerate(out_nodes[0].slices): l, r, stride = s.start, s.stop, s.step # if both l and r are None then the dimension is not sliced if (l != 0 or r != input_shape[dim_id]) and (l is not None or r is not None): if split_channel_dim is None: split_channel_dim = dim_id else: valid_for_replacement = False if split_channel_dim is None: valid_for_replacement = False # split_dims contains tuples with split range and output data node split_dims = [] for out_id, node in enumerate(out_nodes): # Check that StridedSlice op has stride eq 1 and splits only feature channel for id, s in enumerate(node.slices): l, r, stride = s.start, s.stop, s.step # We don't support StridedSlice with stride != 1 if stride != 1: valid_for_replacement = False if id == split_channel_dim: split_dims.append((s.start, s.stop, node.out_node())) if not valid_for_replacement: continue # Check feature split intersection final_data_nodes_list = [] sorted_split_dims = sorted(split_dims, key=lambda item: (item[0], item[1])) # check if we have similar StridedSlice operations with different outputs prev_sd = sorted_split_dims[0] to_remove = [] for i in range(1, len(sorted_split_dims)): if sorted_split_dims[i][0] == prev_sd[0] and sorted_split_dims[i][1] == prev_sd[1] and sorted_split_dims[i][2].name != prev_sd[2].name: cur_node = sorted_split_dims[i][2] for out in cur_node.out_nodes(): attrs = deepcopy(graph.get_edge_data(cur_node.id, out.id)[0]) graph.remove_edge(cur_node.id, out.id) graph.add_edge(prev_sd[2].id, out.id, **attrs) to_remove.append(i) for ind in reversed(to_remove): sorted_split_dims.pop(ind) size_splits = [] prev_r = 0 for l, r, out in sorted_split_dims: # Split dims shouldn't intersect if l < prev_r: valid_for_replacement = False prev_r = r if prev_r > input_shape[split_channel_dim]: valid_for_replacement = False if not valid_for_replacement: continue prev_r = 0 for l, r, out in sorted_split_dims: # Save missing tensor part if l > prev_r: shape = mo_array(input_shape) size_splits.append(l - prev_r) shape[split_channel_dim] = l - prev_r data_node = Op._create_data_node(graph, 'fake_data_'+out_nodes[0].name, {'shape': shape}) add_opoutput(graph, data_node.id, 0, False, keep_output_port=True) final_data_nodes_list.append(data_node) prev_r = r size_splits.append(r - l) final_data_nodes_list.append(out) if prev_r < input_shape[split_channel_dim]: # Add last part of tensor shape = input_shape.copy() shape[split_channel_dim] = input_shape[split_channel_dim] - prev_r size_splits.append(input_shape[split_channel_dim] - prev_r) data_node = Op._create_data_node(graph, 'fake_data_'+out_nodes[0].name, {'shape': shape}) add_opoutput(graph, data_node.id, 0, False, keep_output_port=True) final_data_nodes_list.append(data_node) for node in out_nodes: if not np.all([x == 0 for x in node.shrink_axis_mask]): out_node = node.out_node() if np.any(node['shrink_axis_mask']): self.add_squeeze_for_shrink(graph, node) if np.any(node['new_axis_mask']): self.add_unsqueeze_for_new(graph, node) for i in range(len(final_data_nodes_list)): if final_data_nodes_list[i].name == out_node.name: final_data_nodes_list[i] = node.out_node() break # Insert Split layer and remove old StridedSlice layers # 1. Remove connections from input_data to StridedSlice ops out_data_nodes = [] name_for_future_split = out_nodes[0].name for node in out_nodes: out_data_nodes.append(node.out_node()) graph.remove_edge(input_data.id, node.id) graph.remove_edge(node.id, node.out_node().id) graph.remove_node(node.id) log.debug("Removed: {}".format(node.id)) # 2. Create Split layer and reorder outputs name = name_for_future_split + "/Split" axis_const = Const(graph, {'value': int64_array(split_channel_dim), 'name': name + '/Axis'}).create_node_with_data() size_splits_const = Const(graph, {'value': int64_array(size_splits), 'name': name + '/Sizes'}).create_node_with_data() split = VariadicSplit(graph, dict(name=name, out_ports_count=len(size_splits))) split.create_node_with_data(inputs=[input_data, axis_const, size_splits_const], data_nodes=final_data_nodes_list)
def apply_transform(graph: Graph, replacer_cls, **kwargs): """ Safely executes transform if it should be and validates graph after transform execution """ replacer = replacer_cls() replacement_id = 'REPLACEMENT_ID' if hasattr(replacer, 'replacement_id'): replacement_id = replacer.replacement_id if hasattr(replacer, 'enabled') and not replacer.enabled: log.info("Skip replacer {} (enabled = False)".format(replacer_cls)) return if hasattr(replacer, 'graph_condition') and \ not all([condition(graph) for condition in replacer.graph_condition]): log.info("Skip replacer {} (graph_condition not satisfied)".format( replacer_cls)) return log.debug("Run replacer {}".format(replacer_cls)) try: if hasattr(replacer, 'run_not_recursively') and replacer.run_not_recursively: replacer.find_and_replace_pattern(graph) else: for_graph_and_each_sub_graph_recursively( graph, replacer.find_and_replace_pattern) if hasattr(replacer, 'force_clean_up') and replacer.force_clean_up: for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) if hasattr(replacer, 'force_shape_inference') and replacer.force_shape_inference: shape_inference(graph) if hasattr(replacer, 'run_not_recursively') and replacer.run_not_recursively: graph.check_empty_graph(replacer_cls) graph.check_shapes_consistency() else: for_graph_and_each_sub_graph_recursively( graph, lambda _: graph.check_empty_graph(replacer_cls)) for_graph_and_each_sub_graph_recursively( graph, lambda _: graph.check_shapes_consistency()) except Error as err: raise Error( 'Exception occurred during running replacer "{}" ({}): {}'.format( replacement_id, replacer_cls, str(err).replace('[REPLACEMENT_ID]', replacement_id), )) from err except FrameworkError as err: raise FrameworkError('{}'.format(str(err))) from err except Exception as err: raise Exception( 'Exception occurred during running replacer "{} ({})": {}'.format( replacement_id, replacer_cls, str(err).replace('[REPLACEMENT_ID]', replacement_id), )) from err
def find_and_replace_pattern(self, graph: Graph): for permute_node in graph.get_op_nodes(type='Transpose'): if permute_node.id not in graph.nodes(): continue list_of_permutes = [permute_node] # Get sequence of permutations node = permute_node while True: next_ops = get_next_operation(node) if len(next_ops) != 1: break next_op = next_ops[0] if next_op.soft_get('type') == 'Transpose': list_of_permutes.append(next_op) node = next_op else: break final_permutation = int64_array([ x for x in range( len(list_of_permutes[0].in_port(1).data.get_value())) ]) for permute in list_of_permutes: order = permute.in_port(1).data.get_value() if order is None: raise Error( "Transpose node {} has wrong order for permute = None". format(permute.name)) final_permutation = final_permutation[int64_array(order)] if np.array_equal(final_permutation, [ x for x in range( len(list_of_permutes[0].in_port(1).data.get_value())) ]): first_data_node, last_data_node = list_of_permutes[0].in_node( ), list_of_permutes[-1].out_node() graph.remove_edge(first_data_node.id, list_of_permutes[0].id) else: if len(list_of_permutes) < 2: continue first_data_node, last_data_node = list_of_permutes[0].out_node( ), list_of_permutes[-1].out_node() list_of_permutes[0].in_port(1).data.set_value( final_permutation) graph.remove_edge(first_data_node.id, first_data_node.out_node().id) graph.remove_edge(last_data_node.in_node().id, last_data_node.id) merge_data_nodes(graph, first_data_node, last_data_node) graph.remove_node(last_data_node.id) graph.clean_up()
def find_and_replace_pattern(self, graph: Graph): for mx_fft in graph.get_op_nodes(op='MXFFT'): if mx_fft.soft_get('is_inverse', False): self.convert_ifft_to_dft(graph, mx_fft) else: self.convert_fft_to_dft(graph, mx_fft)
def test_send_op_names_info(self): graph = Graph() graph.add_nodes_from(['node1']) graph.op_names_statistic = Counter(['a', 'a', 'a', 'b', 'b']) sub_graph1 = Graph() sub_graph1.add_nodes_from(['node2']) sub_graph1.op_names_statistic = Counter(['a', 'c', 'c']) sub_graph2 = Graph() sub_graph2.op_names_statistic = Counter(['a', 'd']) node1 = Node(graph, 'node1') node1['sub_graphs'] = ['sub_graph1'] node1['sub_graph1'] = sub_graph1 node2 = Node(sub_graph1, 'node2') node2['sub_graphs'] = ['sub_graph2'] node2['sub_graph2'] = sub_graph2 self.init_telemetry_mocks() send_op_names_info('framework', graph) tm.Telemetry.send_event.assert_any_call('mo', 'op_count', 'framework_a', 5) tm.Telemetry.send_event.assert_any_call('mo', 'op_count', 'framework_b', 2) tm.Telemetry.send_event.assert_any_call('mo', 'op_count', 'framework_c', 2) tm.Telemetry.send_event.assert_any_call('mo', 'op_count', 'framework_d', 1)
def emit_ir(graph: Graph, argv: argparse.Namespace): NormalizeTI().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively( graph, RemoveConstOps().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively( graph, CreateConstNodesReplacement().find_and_replace_pattern) if 'feManager' in argv: del argv.feManager mean_data = deepcopy(graph.graph['mf']) if 'mf' in graph.graph else None input_names = deepcopy( graph.graph['input_names']) if 'input_names' in graph.graph else [] prepare_emit_ir(graph=graph, data_type=graph.graph['cmd_params'].data_type, output_dir=argv.output_dir, output_model_name=argv.model_name, mean_data=mean_data, input_names=input_names, meta_info=get_meta_info(argv), use_temporary_path=True) # This graph cleanup is required to avoid double memory consumption graph.clear() if not (argv.framework == 'tf' and argv.tensorflow_custom_operations_config_update): output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() orig_model_name = os.path.normpath( os.path.join(output_dir, argv.model_name)) return_code = "not executed" try: if not argv.legacy_ir_generation: from openvino.tools.mo.back.offline_transformations import apply_offline_transformations apply_offline_transformations(orig_model_name, argv) if "compress_fp16" in argv and argv.compress_fp16: # restore data_type cmd parameter argv.data_type = 'FP16' return_code = 0 except Exception as e: return_code = "failed" log.error(e) message = str( dict({ "platform": platform.system(), "mo_version": get_simplified_mo_version(), "ie_version": get_simplified_ie_version(env=os.environ), "python_version": sys.version, "return_code": return_code })) t = tm.Telemetry() t.send_event('mo', 'offline_transformations_status', message) if return_code != 0: raise Error("offline transformations step has failed.") for suf in [".xml", ".bin", ".mapping"]: # remove existing files path_to_file = orig_model_name + "_tmp" + suf if os.path.exists(path_to_file): os.remove(path_to_file) # add meta information to IR append_ir_info(file=orig_model_name, meta_info=get_meta_info(argv), mean_data=mean_data, input_names=input_names) print('[ SUCCESS ] Generated IR version {} model.'.format( get_ir_version(argv))) print('[ SUCCESS ] XML file: {}.xml'.format(orig_model_name)) print('[ SUCCESS ] BIN file: {}.bin'.format(orig_model_name)) return 0
def generate_sub_graph(self, graph: Graph, match: SubgraphMatch): reshape_classes_node = create_op_node_with_second_input(graph, Reshape, int64_array([0, -1]), dict(name='do_reshape_classes'), match.single_input_node(1)[0]) initial_priors_node = match.single_input_node(2)[0] priors_name = initial_priors_node.soft_get('name', initial_priors_node.id) # model calculates identical prior boxes for each batch, so we take first slice of them begin = Const(graph, {'value': mo_array([0, 0, 0], dtype=np.int32)}).create_node() end = Const(graph, {'value': mo_array([1, 0, 0], dtype=np.int32)}).create_node() stride = Const(graph, {'value': mo_array([1, 1, 1], dtype=np.int32)}).create_node() priors_node = StridedSlice(graph, {'name': priors_name + '/0_batch_slice', 'begin_mask': int64_array([1, 1, 1]), 'end_mask': int64_array([1, 0, 0]), 'new_axis_mask': int64_array([0]), 'shrink_axis_mask': int64_array([0]), 'ellipsis_mask': int64_array([0])}).create_node() initial_priors_node.out_port(0).connect(priors_node.in_port(0)) begin.out_port(0).connect(priors_node.in_port(1)) end.out_port(0).connect(priors_node.in_port(2)) stride.out_port(0).connect(priors_node.in_port(3)) placeholders = graph.get_op_nodes(type='Parameter') assert len(placeholders) == 1, "{} replacer requires model to have one Placeholder, but current model has " \ "{} placeholders".format(self.replacement_id, len(placeholders)) placeholder = placeholders[0] # scale prior boxes to the [0, 1] interval node_with_scales_for_prior_boxes = self.placeholder_scales(placeholder) priors_scale_node = Mul(graph, {'name': 'scale_priors'}).create_node() broadcast = Broadcast(graph, {'name': 'scales_broadcast'}).create_node() shape_of_priors = Shape(graph, {'name': 'priors_shape'}).create_node() priors_node.out_port(0).connect(shape_of_priors.in_port(0)) broadcast.in_port(1).connect(shape_of_priors.out_port(0)) broadcast.in_port(0).connect(node_with_scales_for_prior_boxes.out_port(0)) priors_scale_node.in_port(0).connect(priors_node.out_port(0)) priors_scale_node.in_port(1).connect(broadcast.out_port(0)) try: variance = match.custom_replacement_desc.custom_attributes['variance'] except: raise Error('There is no variance attribute in {} replacement config file `custom_attributes`' ''.format(self.replacement_id)) priors = self.append_variances(priors_scale_node, variance) # calculate prior boxes widths and heights split_node = create_op_with_const_inputs( graph, VariadicSplit, {1: int64_array(2), 2: int64_array([1, 1, 1, 1])}, {'out_ports_count': 4}, priors_scale_node) priors_width_node = Sub(graph, dict(name=split_node.name + '/sub_2-0_') ).create_node([(split_node, 2), (split_node, 0)]) priors_height_node = Sub(graph, dict(name=split_node.name + '/sub_3-1_') ).create_node([(split_node, 3), (split_node, 1)]) # concat weights and heights into a single tensor and multiple with the box coordinates regression values # WA with 3 Concats instead of 1 for keeping model reshapable # concat_width_height_node = Concat(graph, {'name': 'concat_priors_width_height', 'axis': -1, # 'in_ports_count': 4}).create_node( # [priors_width_node, priors_height_node, priors_width_node, priors_height_node]) concat_1 = Concat(graph, {'name': 'concat_width_height', 'axis': -1, 'in_ports_count': 2}).create_node([priors_width_node, priors_height_node]) concat_2 = Concat(graph, {'name': 'concat_width_height_width', 'axis': -1, 'in_ports_count': 2}).create_node([concat_1, priors_width_node]) concat_width_height_node = Concat(graph, {'name': 'concat_priors_width_height', 'axis': -1, 'in_ports_count': 2} ).create_node([concat_2, priors_height_node]) applied_width_height_regressions_node = Mul(graph, {'name': 'final_regressions'}).create_node( [concat_width_height_node, match.single_input_node(0)[0]]) # reshape to 2D tensor as Inference Engine Detection Output layer expects reshape_regression_node = create_op_node_with_second_input(graph, Reshape, int64_array([0, -1]), dict(name='reshape_regression'), applied_width_height_regressions_node) detection_output_op = DetectionOutput(graph, match.custom_replacement_desc.custom_attributes) # get nms from the original network iou_threshold = None nms_nodes = graph.get_op_nodes(op='NonMaxSuppression') if len(nms_nodes) > 0: # it is highly unlikely that for different classes NMS has different # moreover DetectionOutput accepts only scalar values for iou_threshold (nms_threshold) iou_threshold = nms_nodes[0].in_node(3).value if iou_threshold is None: raise Error('During {} `iou_threshold` was not retrieved from RetinaNet graph'.format(self.replacement_id)) detection_output_node = detection_output_op.create_node( [reshape_regression_node, reshape_classes_node, priors], dict(name=detection_output_op.attrs['type'], nms_threshold=iou_threshold, clip_after_nms=1, normalized=1, variance_encoded_in_target=0, background_label_id=1000)) # As outputs are replaced with a postprocessing node, outgoing tensor names are no longer # correspond to original tensors and should be removed from output->Result edges out_nodes = [] for out in range(match.outputs_count()): out_nodes.append(match.output_node(out)[0]) clear_tensor_names_info(out_nodes) return {'detection_output_node': detection_output_node}
def find_and_replace_pattern(self, graph: Graph): # we need to import these functions here to avoid circular dependent imports from openvino.tools.mo.front.tf.graph_utils import create_op_node_with_second_input if graph.graph['layout'] != 'NHWC': # we check it here because this transformation is called explicitly from the pipeline return # reshape from 4D-5D -> ND. Insert Transpose(NC(D)HW->N(D)HWC) before Reshape for reinterp_shape_node_id in graph.get_nodes_with_attributes( reinterp_shape=True): reinterp_shape_node = Node(graph, reinterp_shape_node_id) assert 0 in reinterp_shape_node.in_nodes( ), 'Node {} does not have 0 input. \n{}'.format( reinterp_shape_node_id, graph.dump_graph_for_graphviz()) input_shape = reinterp_shape_node.in_node(0).shape if self.is_nchw_to_nhwc_transpose_needed(reinterp_shape_node): permute_node = create_op_node_with_second_input( graph, Transpose, PermuteAttrs().get_nchw_to_nhwc_permutation( len(input_shape)).perm, { 'name': reinterp_shape_node.in_port(0).get_source().node.name + '/Transpose' }) reinterp_shape_node.in_port(0).get_connection().insert_node( permute_node) order_const = permute_node.in_port(1).get_source().node order_const.infer(order_const) # do not infer the Transpose node because it should have input data node in NCHW layout (but currently # it is NHWC because data node attributes has not been permuted yet) and produce output in NHWC layout # (which is true at this moment) permute_node['need_shape_inference'] = False # mark the Transpose output data node having correct layout so it's shape will not be permuted mark_output_as_in_correct_layout(permute_node, 0) # keep the reinterp_shape_node in NHWC layout for in_port_id, _ in reinterp_shape_node.in_ports().items(): mark_input_as_in_correct_layout(reinterp_shape_node, in_port_id) # reshape from ND -> 4D-5D. Insert Transpose(N(D)HWC->NC(D)HW) after Reshape for reinterp_shape_node_id in graph.get_nodes_with_attributes( reinterp_shape=True): reinterp_shape_node = Node(graph, reinterp_shape_node_id) assert 0 in reinterp_shape_node.out_nodes( ), 'Node {} does not have 0 output. \n{}'.format( reinterp_shape_node_id, graph.dump_graph_for_graphviz()) output_shape = reinterp_shape_node.out_node(0).shape if self.is_nhwc_to_nchw_transpose_needed(reinterp_shape_node): permute_node = create_op_node_with_second_input( graph, Transpose, PermuteAttrs().get_nhwc_to_nchw_permutation( len(output_shape)).perm, {'name': reinterp_shape_node.id + '/Transpose'}) reinterp_shape_node.out_port(0).get_connection().insert_node( permute_node) # the Reshape and Transpose operations should work in original (NHWC layout) so the Transpose # will convert it to the NCHW mark_input_as_in_correct_layout(permute_node, 0) mark_input_as_in_correct_layout(permute_node, 1) # do not set Transpose output data node 'correct_data_layout' attribute so the data node shape will be # permuted # keep the reinterp_shape_node in NHWC layout mark_output_as_in_correct_layout(reinterp_shape_node, 0) for in_port_id in reinterp_shape_node.in_ports().keys(): if in_port_id: mark_input_as_in_correct_layout( reinterp_shape_node, in_port_id) # do not re-infer the Transpose node because it output data node should be in NHWC layout to make the # rest of the graph consistent permute_node['need_shape_inference'] = False
def muladd_to_scaleshift_action(graph: Graph, match: dict): mul = match['mul'] add = match['add'] output = match['output'] # Pass works correctly only in case when node have only 1 output if len(mul.out_port(0).get_destinations()) > 1: return if mul.soft_get('can_be_scaleshift') is False or add.soft_get('can_be_scaleshift') is False: return mul_weights_id = get_value_id(mul) mul_input_id = get_tensor_id(mul) add_weights_id = get_value_id(add) if mul_weights_id is None: log.debug("Mul->Add to ScaleShift: Mul {} has no weights".format(mul.name)) return if mul_input_id is None: log.debug("Mul->Add to ScaleShift: Mul {} has no input".format(mul.name)) return if add_weights_id is None: log.debug("Mul->Add to ScaleShift: Add {} has no weights".format(add.name)) return input = mul.in_node(mul_input_id) weights = mul.in_node(mul_weights_id) bias = add.in_node(add_weights_id) # Transform values weights.value = np.squeeze(weights.value) weights.shape = int64_array(weights.value.shape) bias.value = np.squeeze(bias.value) bias.shape = int64_array(bias.value.shape) # Broadcast weights if they are scalar if weights.value.ndim == 0 and bias.value.ndim == 1: weights.value = np.full(bias.shape, weights.value.item(), dtype=weights.value.dtype) weights.shape = int64_array(weights.value.shape) if bias.shape != weights.shape: log.warning('Mul->Add to ScaleShift conversion stopped {} != {}'.format(weights.shape, bias.shape)) return if bias.value.ndim != weights.value.ndim or bias.value.size != weights.value.size: log.debug("Skipping Mul->Add to ScaleShift conversion for nodes {}, {} because of different weights " "and biases".format(mul.name, add.name)) return if bias.value.size == 1 and weights.value.size == 1: log.debug("Skipping Mul->Add to ScaleShift conversion for nodes {}, {}. Will be converted to Power" "".format(mul.name, add.name)) return op_name = "ScaleShift" log.debug("Fusing Mul->Add to {}. Input nodes: {} and {}, bias.shape = {}, weights.shape = {}" "".format(op_name, mul.id, add.id, bias.shape, weights.shape)) graph.remove_edge(input.node, mul.id) graph.remove_edge(weights.node, mul.id) graph.remove_edge(bias.node, add.id) graph.remove_edge(add.node, output.id) op_node = graph.unique_id(mul.name + '/Fused{}_'.format(op_name)) graph.add_node(op_node, **add_attrs_props(dict(kind='op', type=op_name, name=op_node, op=op_name, data_type=input.data_type))) scsh = Node(graph, op_node) scsh.add_input_port(0) scsh.add_input_port(1) scsh.add_input_port(2) scsh.add_output_port(0) update_ie_fields(graph.node[op_node]) graph.add_edges_from([ (input.node, op_node, {'in': 0}), (weights.node, op_node, {'in': 1, 'bin': 'weights'}), (bias.node, op_node, {'in': 2, 'bin': 'biases'}), (op_node, output.node, {'out': 0}) ]) return
def set_graph_attrs(self, graph: Graph, parameter_node_names: list): for node in graph.get_op_nodes(): if node.soft_get('name') in parameter_node_names: self.assertTrue(node.soft_get('type') == 'Parameter') out_node = node.out_node(0) out_node['fw_tensor_debug_info'] = ['fw_name', 0]
def find_and_replace_pattern(self, graph: Graph): for node in graph.get_op_nodes(op='timeheightconvolutioncomponent'): self.replace_timeheightconv(graph, node)
def find_and_replace_pattern(self, graph: Graph): for embedding_segments_mean in graph.get_op_nodes( op='EmbeddingSegmentsMean'): embedding_segments_mean_name = embedding_segments_mean.soft_get( 'name', embedding_segments_mean.id) embedding_table_input = embedding_segments_mean.in_port(0) segment_ids_input = embedding_segments_mean.in_port(2) num_segments_input = embedding_segments_mean.in_port(3) # TODO: support EmbeddingSegmentsMean with specified weights vector. # now this case has not appeared in models so far so EmbeddingSegmentsOperation fusion # transformations do not handle it either if embedding_segments_mean.is_in_port_connected(5): return # 1. compute indices membership matrix, i.e. which indices belong to some object # the shape of this matrix is [num_segments, num_indices] non_norm_range_1_to_num_segments = create_op_with_const_inputs( graph, Range, { 0: int64_array(0), 2: int64_array(1) }, { 'name': embedding_segments_mean_name + '/Range1ToNumSegments', 'output_type': np.int64 }) num_segments_input.get_connection().add_destination( non_norm_range_1_to_num_segments.in_port(1)) range_1_to_num_segments = ConvertLike(graph, { 'name': embedding_segments_mean_name + '/Range1ToNumSegmentsNorm' }).create_node() range_1_to_num_segments.in_port(0).connect( non_norm_range_1_to_num_segments.out_port(0)) num_segments_input.get_connection().add_destination( range_1_to_num_segments.in_port(1)) unsqueeze_range_1_to_num_segments = create_op_with_const_inputs( graph, Unsqueeze, {1: int64_array(1)}, { 'name': embedding_segments_mean_name + '/Range1ToNumSegmentsUnsqueeze' }) unsqueeze_range_1_to_num_segments.in_port(0).connect( range_1_to_num_segments.out_port(0)) unsqueeze_segment_ids = create_op_with_const_inputs( graph, Unsqueeze, {1: int64_array(0)}, { 'name': embedding_segments_mean_name + '/SegmentIdsUnsqueeze' }) segment_ids_input.get_connection().add_destination( unsqueeze_segment_ids.in_port(0)) boolean_membership_matrix = Equal(graph, { 'name': embedding_segments_mean_name + '/BooleanMembershipMatrix' }).create_node() boolean_membership_matrix.in_port(0).connect( unsqueeze_range_1_to_num_segments.out_port(0)) boolean_membership_matrix.in_port(1).connect( unsqueeze_segment_ids.out_port(0)) shape_of_membership_matrix = Shape(graph, { 'name': embedding_segments_mean_name + '/ShapeOfMembershipMatrix' }).create_node([boolean_membership_matrix]) one_scalar_constant = Const( graph, { 'name': embedding_segments_mean_name + '/OneScalar', 'value': int64_array([1]) }).create_node() one_constant = Broadcast(graph, { 'name': embedding_segments_mean_name + '/One' }).create_node([one_scalar_constant, shape_of_membership_matrix]) zero_constant = Const( graph, { 'name': embedding_segments_mean_name + '/Zero', 'value': int64_array(0) }).create_node() membership_matrix = Select( graph, { 'name': embedding_segments_mean_name + '/MembershipMatrix', 'auto_broadcast': 'numpy' }).create_node( [boolean_membership_matrix, one_constant, zero_constant]) # 2. compute a number of indices belong to each object from the batch # it computes the normalization coefficients num_indices_per_object = create_op_with_const_inputs( graph, ReduceSum, {1: int64_array(1)}, { 'name': embedding_segments_mean_name + '/NumIndicesPerObject' }) num_indices_per_object.in_port(0).connect( membership_matrix.out_port(0)) # 3. replace zero coefficient (zero number of indices belong to an object) with one # because for such object the single default embedding vector is used where_zero_number = Equal(graph, { 'name': embedding_segments_mean_name + '/WhereZeroIndicesNumber' }).create_node([num_indices_per_object, zero_constant]) normalized_num_indices_per_object = Select( graph, { 'name': embedding_segments_mean_name + '/NormNumIndicesPerObject', 'auto_broadcast': 'numpy' }).create_node([ where_zero_number, one_scalar_constant, num_indices_per_object ]) # 4. cast normalized_num_indices_per_object to the same type as embedding vector table norm_coefficients = ConvertLike( graph, { 'name': embedding_segments_mean_name + '/NormCoefficients' }).create_node() norm_coefficients.in_port(0).connect( normalized_num_indices_per_object.out_port(0)) embedding_table_input.get_connection().add_destination( norm_coefficients.in_port(1)) # 5. replace EmbeddingSegmentMean with EmbeddingSegmentSum embedding_segments_sum = EmbeddingSegmentsSum( graph, { 'name': embedding_segments_mean_name + '/EmbeddingSegmentsSum' }).create_node() for in_port in embedding_segments_mean.in_ports(): if embedding_segments_mean.is_in_port_connected(in_port): embedding_segments_mean.in_port( in_port).get_connection().set_destination( embedding_segments_sum.in_port(in_port)) # 6. normalize EmbeddingSegmentSum results by computed coefficients result_node = Div(graph, { 'name': embedding_segments_mean_name + '/Div' }).create_node([embedding_segments_sum, norm_coefficients]) embedding_segments_mean.out_port(0).get_connection().set_source( result_node.out_port(0)) rename_nodes([(embedding_segments_mean, embedding_segments_mean_name + '/AbandonedName'), (result_node, embedding_segments_mean_name)]) graph.remove_nodes_from([embedding_segments_mean.id])
def replace_timeheightconv(self, graph: Graph, node: Node): req_time_offsets = node.soft_get('time_offsets') offsets = node.soft_get("offsets", [[]]) all_time_offsets = list(set(offsets[:, 0])) all_time_offsets.sort() in_name = node.soft_get('name', node.id) rename_node(node, in_name + '/to_delete') # create memoryoffsets for context gathering # we need concat if time offsets more than 1 concat = Concat(graph, attrs={ 'name': in_name + '/Concat', 'in_ports_count': len(all_time_offsets) }).create_node() i = 0 for t in all_time_offsets: # if time offset included in required_time_offsets we don't need default value has_default = t not in req_time_offsets memoff = MemoryOffset(graph, attrs={ 'name': in_name + '/MemoryOffset_' + str(i), 't': t, 'has_default': has_default, 'splitted': False, 'pair_name': in_name + '/MemoryOffset_pair_' + str(i) }).create_node() concat.in_port(i).connect(memoff.out_port(0)) memoff.in_port(0).connect(node.in_port(0).get_source()) i = i + 1 stride = node.soft_get("height_subsample", 1) kernel = int64_array([0, 0]) kernel[0] = len(set(offsets[:, 0])) kernel[1] = len(set(offsets[:, 1])) pad_h = int64_array([0, 0]) pad_h[0] = -min(offsets[:, 1]) if min(offsets[:, 1]) < 0 else 0 pad_h[1] = stride * node.height_out - (node.height_in - max([max(offsets[:, 1]), 0])) dilation_t = (max(offsets[:, 0]) - min(offsets[:, 0])) / ( kernel[0] - 1) if kernel[0] > 1 else 1 dilation_h = (max(offsets[:, 1]) - min(offsets[:, 1])) / ( kernel[1] - 1) if kernel[0] > 1 else 1 conv_attrs = { 'name': in_name, 'output': node['out_channels'], 'height_in': node.height_in, 'bias_term': None, 'pad': int64_array([[0, 0], [0, 0], [0, 0], pad_h]), 'pad_spatial_shape': int64_array([[0, 0], pad_h]), 'dilation': int64_array([1, 1, dilation_t, dilation_h]), 'kernel': int64_array( [node.out_channels, node.in_channels, kernel[0], kernel[1]]), 'stride': int64_array([1, 1, 1, stride]), 'kernel_spatial': kernel, 'input_feature_channel': 1, 'output_feature_channel': 0, 'channel_dims': int64_array([1]), 'spatial_dims': int64_array([2, 3]), 'batch_dims': int64_array([0]), 'kernel_spatial_idx': int64_array([2, 3]), 'group': 1, 'reshape_kernel': True, 'bias_addable': True, } conv = Convolution(graph, attrs=conv_attrs).create_node() conv.in_port(0).connect(concat.out_port(0)) conv.in_port(1).connect(node.in_port(1).get_source()) # change layout for weights from OHWI to OIHW # in future should be replaced by common Permute mechanics weights = conv.in_port(1).get_source().node.value weights = weights.reshape( int64_array([node.out_channels, -1, node.in_channels])) weights = weights.transpose(int64_array([0, 2, 1])) weights = weights.flatten() conv.in_port(1).get_source().node.value = weights conv.in_port(2).connect(node.in_port(2).get_source()) node.out_port(0).get_connection().set_source(conv.out_port(0)) graph.remove_node(node.id)
def sub_graph_between_nodes(graph: Graph, start_nodes: list, end_nodes: list, detect_extra_start_node: callable = None, include_control_flow=True, allow_non_reachable_end_nodes=False): """ Finds nodes of the sub-graph between 'start_nodes' and 'end_nodes'. Input nodes for the sub-graph nodes are also added to the sub-graph. Constant inputs of the 'start_nodes' are also added to the sub-graph. :param graph: graph to operate on. :param start_nodes: list of nodes names that specifies start nodes. :param end_nodes: list of nodes names that specifies end nodes. :param detect_extra_start_node: callable function to add additional nodes to the list of start nodes instead of traversing the graph further. The list of additional start nodes is returned of the function is not None. :param include_control_flow: flag to specify whether to follow the control flow edges or not :param allow_non_reachable_end_nodes: do not fail if the end nodes are not reachable from the start nodes :return: list of nodes of the identified sub-graph or None if the sub-graph cannot be extracted. """ sub_graph_nodes = list() visited = set(start_nodes) d = deque(start_nodes) extra_start_nodes = [] nx.set_node_attributes(G=graph, name='prev', values=None) while len(d) != 0: cur_node_id = d.popleft() sub_graph_nodes.append(cur_node_id) if cur_node_id not in end_nodes: # do not add output nodes of the end_nodes for _, dst_node_name, attrs in graph.out_edges(cur_node_id, data=True): if dst_node_name not in visited and ( include_control_flow or not attrs.get('control_flow_edge', False)): d.append(dst_node_name) visited.add(dst_node_name) graph.node[dst_node_name]['prev'] = cur_node_id for src_node_name, _, attrs in graph.in_edges(cur_node_id, data=True): # add input nodes for the non-start_nodes if cur_node_id not in start_nodes and src_node_name not in visited and\ (include_control_flow or not attrs.get('control_flow_edge', False)): if detect_extra_start_node is not None and detect_extra_start_node( Node(graph, cur_node_id)): extra_start_nodes.append(cur_node_id) else: d.append(src_node_name) graph.node[src_node_name]['prev'] = cur_node_id visited.add(src_node_name) # use forward dfs to check that all end nodes are reachable from at least one of input nodes forward_visited = set() for start_node in start_nodes: graph.dfs(start_node, forward_visited) for end_node in end_nodes: if not allow_non_reachable_end_nodes and end_node not in forward_visited: raise Error('End node "{}" is not reachable from start nodes: {}. ' .format(end_node, start_nodes) + refer_to_faq_msg(74)) for node_id in sub_graph_nodes: # sub-graph should not contain Placeholder nodes if graph.node[node_id].get('op', '') == 'Parameter': path = list() cur_node = node_id while cur_node and 'prev' in graph.node[cur_node]: path.append(str(cur_node)) cur_node = graph.node[cur_node]['prev'] log.debug("The path from input node is the following: {}".format( '\n'.join(path))) raise Error( 'The matched sub-graph contains network input node "{}". '. format(node_id) + refer_to_faq_msg(75)) if detect_extra_start_node is None: return sub_graph_nodes else: return sub_graph_nodes, extra_start_nodes
def find_and_replace_pattern(self, graph: Graph): for node in graph.get_op_nodes(op='SpaceToBatch') + graph.get_op_nodes( op='BatchToSpace'): node.add_input_port(3, skip_if_exist=True) # convert TF representation of the pads/crops as [N, 2] to IE representation: [N] and [N] transposed_pads = create_op_with_const_inputs( graph, Transpose, {1: int64_array([1, 0])}) node.in_port(2).get_connection().set_destination( transposed_pads.in_port(0)) split_pads = create_op_with_const_inputs(graph, Split, {1: int64_array(0)}, {'num_splits': 2}) transposed_pads.out_port(0).connect(split_pads.in_port(0)) for port_ind in range(2): node.in_port(port_ind + 2).connect( split_pads.out_port(port_ind)) node.in_port(port_ind + 2).get_connection().insert_node( create_op_with_const_inputs(graph, Squeeze, {1: int64_array([0])})) # add zeros/ones to related inputs to align it with data input in0_rank = Rank(graph, { 'name': node.name + '/rank_0' }).create_node() in1_shape = Shape(graph, { 'name': node.name + '/rank_1' }).create_node() diff_size = Sub(graph, { 'name': node.name + '/sub_0' }).create_node() diff = Sub(graph, {'name': node.name + '/sub_1'}).create_node() const_begin = Const(graph, { 'value': int64_array([1]) }).create_node() const_pad_val = Const(graph, { 'value': int64_array(1) }).create_node() block_shape = Pad(graph, { 'name': node.name + '/aligned_block_shape', 'mode': 'constant' }).create_node() # in case of SpaceToBatch begin = pads_begin, end = pads_end # in case of BatchToSpace begin = crops_begin, end = crops_end new_begin_name = '/aligned_pads_begin' new_end_name = '/aligned_pads_end' if node.type == 'BatchToSpace': new_begin_name = '/aligned_crops_begin' new_end_name = '/aligned_crops_end' begin = Pad(graph, { 'name': node.name + new_begin_name, 'mode': 'constant' }).create_node() end = Pad(graph, { 'name': node.name + new_end_name, 'mode': 'constant' }).create_node() in0_rank_1d = create_op_node_with_second_input( graph, Unsqueeze, int64_array([0]), {'name': node.name + '/1d_rank_of_0'}, in0_rank) node.in_port(0).get_source().connect(in0_rank.in_port(0)) node.in_port(1).get_source().connect(in1_shape.in_port(0)) in0_rank_1d.out_port(0).connect(diff_size.in_port(0)) in1_shape.out_port(0).connect(diff_size.in_port(1)) diff_size.out_port(0).connect(diff.in_port(0)) const_begin.out_port(0).connect(diff.in_port(1)) const_pad_val.out_port(0).connect(block_shape.in_port(3)) inputs_array = [block_shape, begin, end] for idx, input_to_node in enumerate(inputs_array): name_of_input_to_node = input_to_node.name node.in_port(idx + 1).get_connection().set_destination( input_to_node.in_port(0)) const_begin.out_port(0).connect(input_to_node.in_port(1)) diff.out_port(0).connect(input_to_node.in_port(2)) input_to_node.out_port(0).connect(node.in_port(idx + 1)) convert = Cast(graph, { 'name': name_of_input_to_node + '/i64', 'dst_type': np.int64 }).create_node() input_to_node.in_port(0).get_connection().insert_node(convert)
def load(self, graph: Graph): argv = graph.graph['cmd_params'] if argv.tensorflow_custom_layer_libraries: libraries = argv.tensorflow_custom_layer_libraries.split(',') for library in libraries: log.info('Loading library "{}" with custom operations'.format( library)) tf_v1.load_op_library(library) graph_def, variables_values, framework, inputs_outputs_order = load_tf_graph_def( graph_file_name=argv.input_model, is_binary=not argv.input_model_is_text, checkpoint=argv.input_checkpoint, user_output_node_names_list=argv.output, model_dir=argv.saved_model_dir, meta_graph_file=argv.input_meta_graph, saved_model_tags=argv.saved_model_tags) if inputs_outputs_order is not None and isinstance( inputs_outputs_order, tuple): graph.inputs_order = inputs_outputs_order[0] graph.outputs_order = inputs_outputs_order[1] send_framework_info(framework) try: tf_v1.import_graph_def(graph_def, name='') except: log.warning( "TensorFlow post-processing of loaded model was unsuccessful. " "This is an optional step that Model Optimizer performs for any input model but it is not usually " "required for all models. " "It likely means that the original model is ill-formed. " "Model Optimizer will continue converting this model.") log.debug("Number of nodes in graph_def: {}".format(len( graph_def.node))) # pylint: disable=no-member if argv.tensorboard_logdir: tensorboard_util.dump_for_tensorboard(graph_def, argv.tensorboard_logdir) update_extractors_with_extensions(tf_op_extractors) try: protobuf2nx(graph, graph_def) except Exception as e: raise Error( 'Cannot pre-process TensorFlow graph after reading from model file "{}". ' \ 'File is corrupt or has unsupported format. Details: {}. ' + refer_to_faq_msg(44), argv.model_name, str(e) ) from e graph.__setattr__('name', argv.model_name) # 'layout' parameter change may cause an issue in EltwiseInputReshape replacer # and convert_nhwc_to_nchw(graph) graph.graph['layout'] = 'NCHW' if argv.disable_nhwc_to_nchw else 'NHWC' graph.graph['fw'] = 'tf' graph.graph['variables_values'] = variables_values del variables_values used_tensors = restore_edges(graph, get_tf_edges) # Tensor names information corresponding to a node is stored on outgoing edges. # As output nodes do not have outgoing edges, fake outputs are required. In the following code # for each output Identity node is added, and tensor name for the output is kept # on (output, fake output) edge. After Result nodes adding transformation fake outputs # are deleted from graph. add_outputs_identity( graph, graph.nodes - used_tensors, lambda g, output, fake_node_name: g.add_edges_from( [create_tf_edge(output, fake_node_name, 0)])) remove_control_dependency_inputs(graph) graph.check_empty_graph( 'protobuf2nx. It may happen due to problems with loaded model') extract_node_attrs( graph, lambda node: tf_op_extractor( node, check_for_duplicates(tf_op_extractors))) # try to detect layout from the nodes of the graph. If there are no convolution nodes in N(D)HWC layout then we # consider that the graph is in NCHW layout and no layout conversion should be performed if not argv.disable_nhwc_to_nchw and not graph_or_sub_graph_has_nhwc_ops( graph): if not argv.silent: log.debug('disable_nhwc_to_nchw" was automatically enabled.') for_graph_and_each_sub_graph_recursively( graph, update_cmd_params_and_layout) send_op_names_info(framework, graph) send_shapes_info(framework, graph)
def find_and_replace_pattern(self, graph: Graph): for node in graph.get_op_nodes(op='Slice'): node_name = node.soft_get('name', node.id) input_shape = node.in_port(0).data.get_shape() if node.is_in_port_connected(3): axes = node.in_port(3).data.get_value().copy() assert axes is not None, 'The input with axes is not constant for node {}'.format( node_name) for i, val in enumerate(axes): axes[i] = get_canonical_axis_index(input_shape, val) else: axes = int64_array(range(len(input_shape))) ss_begin = create_ss_interval_border(graph, node.in_port(1).get_source(), input_shape, axes, node_name) ss_end = create_ss_interval_border(graph, node.in_port(2).get_source(), input_shape, axes, node_name) node.in_port(1).disconnect() node.in_port(2).disconnect() rename_nodes([(ss_begin, node_name + '/Begin'), (ss_end, node_name + '/End')]) if node.is_in_port_connected(4): steps = node.in_port(4).data.get_value() assert steps is not None, 'The input with steps is not constant for node {}'.format( node_name) else: steps = np.ones([axes.size]) ss_begin_mask = np.zeros(len(input_shape), dtype=np.int64) ss_end_mask = np.zeros(len(input_shape), dtype=np.int64) ss_step = np.ones(len(input_shape), dtype=np.int64) for i, axis in enumerate(axes): ss_begin_mask[axis] = 1 ss_end_mask[axis] = 1 ss_step[axis] = steps[i] ss_strides = Const( graph, dict(name=node_name + '/Strides', value=ss_step)).create_node() ss = StridedSlice( graph, dict(name='ss', new_axis_mask=np.zeros(len(input_shape), dtype=np.int64), shrink_axis_mask=np.zeros(len(input_shape), dtype=np.int64), ellipsis_mask=np.zeros(len(input_shape), dtype=np.int64), begin_mask=ss_begin_mask, end_mask=ss_end_mask)).create_node() node.in_port(0).get_connection().set_destination(ss.in_port(0)) ss.in_port(1).connect(ss_begin.out_port(0)) ss.in_port(2).connect(ss_end.out_port(0)) ss.in_port(3).connect(ss_strides.out_port(0)) node.out_port(0).get_connection().set_source(ss.out_port(0)) rename_nodes([(node, node_name + '/ShouldBeDeleted'), (ss, node_name)])
def replace_sub_graph(self, graph: Graph, match: dict): identity_spw = match['identity_spw'] gather0_1 = match['gather0_1'] gather0_2 = match['gather0_2'] greaterequal0 = match['greaterequal0'] sparse_fill_empty_rows = match['sparse_fill_empty_rows'] gather = match['gather'] select = match['select'] where0 = match['where0'] sparse_segment_op = match['sparse_segment_op'] output_node_name = select.soft_get('name', select.id) log.debug('Found EmbeddingSparseSegmentsSingleFeature pattern after {} with name {}'.format( sparse_fill_empty_rows.op, sparse_fill_empty_rows.name)) split_for_indices = create_op_with_const_inputs(graph, Split, {1: int64_array(1)}, {'num_splits': 2}) squeeze_for_indices = create_op_with_const_inputs(graph, Squeeze, {1: int64_array([1])}) split_for_dense_shape = create_op_with_const_inputs(graph, Split, {1: int64_array(0)}, {'num_splits': 2}) squeeze_to_scalar = create_op_with_const_inputs(graph, Squeeze, {1: int64_array([0])}) # TODO: remove Cast nodes once we start to support EmbeddingSegmentSum (new version) with segment_ids, # indices, and num_segments of different integer type. # Because the real cases show that it is possible to have it in TensorFlow cast_indices = Cast(graph, {'name': output_node_name + '/CastIndices', 'dst_type': np.int32}).create_node() cast_segment_ids = Cast(graph, {'name': output_node_name + '/CastSegmentIds', 'dst_type': np.int32}).create_node() cast_default_value = Cast(graph, {'name': output_node_name + '/CastDefaultValue', 'dst_type': np.int32}).create_node() cast_num_segments = Cast(graph, {'name': output_node_name + '/CastSegmentsNumber', 'dst_type': np.int32}).create_node() if sparse_segment_op.op == 'SparseSegmentSum': embedding_segments_op = EmbeddingSegmentsSum(graph, {'name': output_node_name}).create_node() else: embedding_segments_op = EmbeddingSegmentsMean(graph, {'name': output_node_name}).create_node() rename_nodes([(select, output_node_name + '/AbandonedName'), (embedding_segments_op, output_node_name)]) # connect parameters table gather.in_port(0).get_connection().set_destination(embedding_segments_op.in_port(0)) # connect indices values greaterequal0.in_port(0).get_connection().set_destination(cast_indices.in_port(0)) embedding_segments_op.in_port(1).connect(cast_indices.out_port(0)) # split and connect segment ids gather0_1.in_port(0).get_connection().set_destination(split_for_indices.in_port(0)) squeeze_for_indices.in_port(0).connect(split_for_indices.out_port(0)) cast_segment_ids.in_port(0).connect(squeeze_for_indices.out_port(0)) embedding_segments_op.in_port(2).connect(cast_segment_ids.out_port(0)) # split and connect number of segments identity_spw.in_port(0).get_connection().set_destination(split_for_dense_shape.in_port(0)) squeeze_to_scalar.in_port(0).connect(split_for_dense_shape.out_port(0)) cast_num_segments.in_port(0).connect(squeeze_to_scalar.out_port(0)) embedding_segments_op.in_port(3).connect(cast_num_segments.out_port(0)) # connect default value sparse_fill_empty_rows.in_port(3).get_connection().set_destination(cast_default_value.in_port(0)) embedding_segments_op.in_port(4).connect(cast_default_value.out_port(0)) # no input port for per_sample_weight identity_spw.in_port(0).disconnect() gather0_1.in_port(0).disconnect() gather0_2.in_port(0).disconnect() greaterequal0.in_port(0).disconnect() sparse_fill_empty_rows.in_port(2).disconnect() gather.in_port(0).disconnect() select.out_port(0).get_connection().set_source(embedding_segments_op.out_port(0)) graph.remove_nodes_from( [gather0_1.id, gather0_2.id, greaterequal0.id, sparse_fill_empty_rows.id, select.id, where0.id])