def save_restored_graph(graph: Graph, path: str, meta_data, name=None): """ Function to apply all necessary transforms from back stage to prepare and save restored graph and metadata. :param graph: Graph to save :param path: Path to saved IR :param meta_data: Namespace with converting parameters restored from IR :param name: Name for saved IR :return: """ if name is None: name = graph.name precision = data_type_str_to_precision(graph.graph['cmd_params'].data_type) assert precision in ['FP16', 'FP32'], 'Cannot define precision for restored model!' # List items order matters, do not change it. transformation_list = [ ConvolutionWithGroupsResolver, StridedSliceMasksNormalizer, PackBinaryWeights, BlobNormalizer, ConvolutionNormalizer, KaldiRemoveMemoryOutputBackReplacementPattern, ] # We need to run some specific passes from MO back stage. apply_replacements_list(graph, transformation_list) # Transformations with enabled=False should be run manually. 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) prepare_emit_ir(graph, precision, path, name, meta_info=meta_data)
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) 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=graph.graph['mf'] if 'mf' in graph.graph else None, input_names=graph.graph['input_names'] if 'input_names' in graph.graph else [], meta_info=get_meta_info(argv)) if not (argv.framework == 'tf' and argv.tensorflow_custom_operations_config_update): output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() print('\n[ SUCCESS ] Generated IR version {} model.'.format( get_ir_version(argv))) print('[ SUCCESS ] XML file: {}.xml'.format( os.path.join(output_dir, argv.model_name))) print('[ SUCCESS ] BIN file: {}.bin'.format( os.path.join(output_dir, argv.model_name))) return 0
def save_restored_graph(graph: Graph, path: str, meta_data, name=None): """ Function to apply all necessary transforms from back stage to prepare and save restored graph and metadata. :param graph: Graph to save :param path: Path to saved IR :param meta_data: Namespace with converting parameters restored from IR :param name: Name for saved IR :return: """ if name is None: name = graph.name if 'data_type' not in meta_data: log.debug( 'Provided `meta_data` does not contain `data_type` parameter. Set `data_type`' ' parameter value to `FP32`.') # Set data_type to FP32. All restored constants will be saved in provided data type. data_type = 'FP32' # We need to specify this attribute to pass graph transformations. This information will not be saved into IR. # All constants and placeholders will be saved with same types as restored from IR graph.graph['cmd_params'].data_type = data_type else: data_type = data_type_str_to_precision( graph.graph['cmd_params'].data_type) assert data_type in ['FP16', 'FP32'], '`data_type` value {} is not supported by MO,' \ ' cannot save graph'.format(data_type) # List items order matters, do not change it. transformation_list = [ ConvolutionWithGroupsResolver, StridedSliceMasksNormalizer, PackBinaryWeights, BlobNormalizer, ConvolutionNormalizer, MarkNodesWithShapeValues, ] # We need to run some specific passes from MO back stage. apply_replacements_list(graph, transformation_list) # Transformations with enabled=False should be run manually. 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) prepare_emit_ir(graph, data_type, path, name, meta_info=meta_data, used_by_ir_reader=True)
def find_and_replace_pattern(self, graph: Graph): for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) EltwiseChecker().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, convert_muladd_to_scaleshift) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) for_graph_and_each_sub_graph_recursively(graph, convert_add_or_mul_to_scaleshift) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up())
def find_and_replace_pattern(self, graph: Graph): for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) EltwiseChecker().find_and_replace_pattern(graph) # turn off run of transformation for body of TI due to error on TF_Multicell topology convert_muladd_to_scaleshift(graph) graph.clean_up() # turn off run of transformation for body of TI due to error on TF_Multicell topology convert_add_or_mul_to_scaleshift(graph) graph.clean_up()
def get_all_operation_nodes(graph: Graph, recursively: bool = False): """ Returns sequence of all nodes in graph :param graph: NetworkX model to take nodes :param recursively: whether return all nodes from the graph and each subgraph or only from the external graph :return list of all nodes """ if recursively: get_all_op_nodes_func = FunctionResultsAccumulator(lambda graph: graph.get_op_nodes()) for_graph_and_each_sub_graph_recursively(graph, get_all_op_nodes_func) return get_all_op_nodes_func.results return graph.get_op_nodes()
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) 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=graph.graph['mf'] if 'mf' in graph.graph else None, input_names=graph.graph['input_names'] if 'input_names' in graph.graph else [], meta_info=get_meta_info(argv)) 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)) # This try-except is additional reinsurance that the IE # dependency search does not break the MO pipeline try: if find_ie_version(silent=True): path_to_offline_transformations = os.path.join( os.path.realpath(os.path.dirname(__file__)), 'back', 'offline_transformations.py') status = subprocess.run([ sys.executable, path_to_offline_transformations, orig_model_name ], env=os.environ, timeout=100) if status.returncode != 0 and not argv.silent: print("[ WARNING ] offline_transformations return code {}". format(status.returncode)) except Exception as e: # TODO: send error message pass 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 prepare_emit_ir(graph: Graph, data_type: str, output_dir: str, output_model_name: str, mean_data: [list, None] = None, input_names: list = None, meta_info: dict = None): if input_names is None: input_names = [] if meta_info is None: meta_info = {} graph.strict_mode = False # convert Parameter data types convert_data_type.convert_parameters_data_type(graph, data_type) # convert blobs (usually weights and biases) for sub_graph in [graph] + collect_sub_graphs(graph): convert_data_type.convert_blobs(sub_graph, data_type) # restore data type for specific inputs/outputs of specific ops to the data types required by nGraph if not graph.graph['cmd_params'].generate_deprecated_IR_V7: for_graph_and_each_sub_graph_recursively(graph, convert_inputs_of_specific_ops) if graph.graph['cmd_params'].generate_experimental_IR_V10: for_graph_and_each_sub_graph_recursively(graph, OpVersioning().find_and_replace_pattern) # do not run the type inference in sub-graphs. It will be called automatically as part of the type inference of # the TensorIterator nodes type_infer(graph) RemoveUselessConvert().find_and_replace_pattern(graph) for sub_graph in [graph] + collect_sub_graphs(graph): op_order, data_order = determined_sort(get_sorted_outputs(sub_graph)) mapping = {v: u for u, v in enumerate(op_order)} mapping.update({v: u for u, v in enumerate(data_order, start=len(sub_graph))}) relabel_nodes_inplace_safe(sub_graph, mapping) port_renumber(sub_graph) tensor_names.propagate_op_name_to_tensor(graph) bin_file = os.path.join(output_dir, '{}.bin'.format(output_model_name)) serialize_constants(graph, bin_file) mean_offset = None mean_size = None if mean_data: mean_offset, mean_size = serialize_mean_image(bin_file, mean_data=mean_data) generate_ie_ir(graph=graph, file_name=os.path.join(output_dir, '{}.xml'.format(output_model_name)), input_names=input_names, mean_offset=mean_offset, mean_size=mean_size, meta_info=meta_info) tensor_names.output_tensor_names_map(graph, os.path.join(output_dir, '{}.mapping'.format(output_model_name)))
def need_to_repeat_conversion(graph: nx.MultiDiGraph): """ Detects if another round of conversion is required for the entire graph. It traverses a given `graph` and all sub-graphs recursively and searches for 'repeat_conversion' graph attribute. If at least one is found and its value is True, this function returns True. """ result = False def check_for_repeat(graph: nx.MultiDiGraph): if 'repeat_conversion' in graph.graph and graph.graph['repeat_conversion']: nonlocal result result = True for_graph_and_each_sub_graph_recursively(graph, check_for_repeat) return result
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'): 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) 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 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 send_op_names_info(framework: str, graph: Graph): """ This function sends information about operations in model. :param framework: framework name. :param graph: model graph. """ op_counter = Counter() def gather_op_statistics(g: Graph, op_c: Counter = op_counter): if hasattr(g, 'op_names_statistic'): op_c += g.op_names_statistic for_graph_and_each_sub_graph_recursively(graph, gather_op_statistics) t = tm.Telemetry() for op_name in op_counter: t.send_event('mo', 'op_count', "{}_{}".format(framework, op_name), op_counter[op_name])
def get_nodes_by_type(graph: Graph, types: list, recursively: bool = False) -> list: """ Returns all nodes with type from types collection :param graph: NetworkX model to collect nodes :param types: list of required types :param recursively: whether return all nodes from the graph and each subgraph or only from the main graph :return list of nodes filtered by 'types' collection """ def get_nodes_by_type_from_main_graph(graph, types): return [node for t in types for node in graph.get_op_nodes(type=t)] if recursively: partial_get_nodes_by_type = partial(get_nodes_by_type_from_main_graph, types=types) get_nodes_by_type_recursively = FunctionResultsAccumulator(partial_get_nodes_by_type) for_graph_and_each_sub_graph_recursively(graph, get_nodes_by_type_recursively) nodes = [node for node in get_nodes_by_type_recursively.results if node] else: nodes = get_nodes_by_type_from_main_graph(graph, types) return nodes
def get_node_by_name(graph: Graph, name: str, recursively: bool = False) -> Node: """ Returns node by name :param graph: NetworkX model to take node :param name: name of the node :param recursively: whether return all nodes from the graph and each subgraph or only from the external graph :return node from NetworkX model (of type Node or None if there's no such node) """ if recursively: def get_node_by_fullname(graph: Graph, name: str) -> Node: nodes = graph.get_nodes_with_attributes(**dict(kind='op', fullname=name)) return [Node(graph, nodes[0])] if nodes else None partial_get_node_by_fullname = partial(get_node_by_fullname, name=name) get_node_by_fullname_func = FunctionResultsAccumulator(partial_get_node_by_fullname) for_graph_and_each_sub_graph_recursively(graph, get_node_by_fullname_func) node = get_node_by_fullname_func.results else: node = graph.get_op_nodes(name=name) return node[0] if node else None
def apply_replacements(graph: Graph, replacements_type): """ Apply all patterns that do not have 'op' first, then apply patterns from registered_ops. If two or more classes replaces the same op (both have op class attribute and values match), such pattern is not applied (while registration it will warn user that we have a conflict). """ dependency_graph = Graph() for class_type, classes_set in _registered_classes_dict.items(): if class_type == replacements_type: for cls in classes_set: replacers = [c for c in cls.registered_cls if not hasattr(c, 'op')] + \ [c for op, c in cls.registered_ops.items() if c] for replacer_cls in replacers: if replacer_cls in cls.excluded_replacers: # skip infrastructure classes continue dependency_graph.add_node(replacer_cls) for cls_after in replacer_cls().run_before(): log.debug("Replacer {} will be run before {}".format( replacer_cls, cls_after)) dependency_graph.add_edge(replacer_cls, cls_after) for cls_before in replacer_cls().run_after(): log.debug("Replacer {} will be run after {}".format( replacer_cls, cls_before)) dependency_graph.add_edge(cls_before, replacer_cls) try: replacers_order = list(nx.topological_sort(dependency_graph)) except nx.NetworkXUnfeasible as exception: cycles = nx.simple_cycles(dependency_graph) raise Error( 'There is(are) cyclic dependency(ies) between replacers. One of the cycles is the following: {}', ' -> '.join([str(node) for node in list(cycles)[0]])) from exception for replacer_cls in replacers_order: 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)) continue 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)) continue log.debug("Run replacer {}".format(replacer_cls)) try: replacer.find_and_replace_pattern(graph) if hasattr(replacer, 'force_clean_up') and replacer.force_clean_up: for_graph_and_each_sub_graph_recursively( graph, graph_clean_up_tf if graph.graph['fw'] == 'tf' else graph_clean_up_onnx if graph.graph['fw'] == 'onnx' else graph_clean_up) 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 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 driver_R1(onnx_modelproto_bytes, precision: str, output_model_name: str, outputs: list, output_dir: str, scale: float, user_shapes: [None, list, np.array] = None, mean_scale_values: [dict, list] = ()): try: model_proto = onnx.load_from_string(bytes(onnx_modelproto_bytes)) except Exception as e: print("[python] onnx exception: ", str(e)) model_graph = model_proto.graph # pylint: disable=no-member update_extractors_with_extensions(onnx_op_extractors) try: graph = protobuf2nx(model_proto) log.debug("Number of nodes in NX graph: {}".format( graph.number_of_nodes())) graph.__setattr__( 'name', output_model_name if output_model_name else model_proto.graph.name) # pylint: disable=no-member graph.graph['layout'] = 'NCHW' graph.graph['cmd_params'] = argparse.Namespace( batch=None, data_type='float', disable_fusing=False, disable_gfusing=False, disable_resnet_optimization=False, enable_concat_optimization=False, extensions=mo_extensions, finegrain_fusing=None, framework='onnx', freeze_placeholder_with_value=None, generate_deprecated_IR_V2=False, input=None, input_model=None, input_shape=None, keep_shape_ops=False, log_level='ERROR', mean_scale_values={}, mean_values=(), model_name=None, move_to_preprocess=False, output=None, output_dir='.', placeholder_shapes=None, reverse_input_channels=False, scale=None, scale_values=(), silent=False, version=False) graph.graph['fw'] = 'onnx' graph.graph[ 'feature_dim'] = 1 if graph.graph['layout'] == 'NCHW' else 3 graph.graph['ir_version'] = 5 except Exception as e: raise Error( 'Cannot pre-process ONNX graph after reading from model file "{}". ' 'File is corrupt or has unsupported format. Details: {}. ' + refer_to_faq_msg(44), model_file_name, str(e)) from e graph.check_empty_graph( 'protobuf2nx. It may happen due to problems with loaded model') extract_node_attrs( graph, lambda node: onnx_op_extractor( node, check_for_duplicates(onnx_op_extractors))) # --------------------------------- LOAD END ------------------------------------------------------ class_registration.apply_replacements( graph, class_registration.ClassType.FRONT_REPLACER) class_registration.apply_replacements( graph, class_registration.ClassType.MIDDLE_REPLACER) fuse_pad(graph) graph_clean_up_onnx(graph) mark_unfused_nodes(graph, 'False') convert_batch_norm(graph) graph_clean_up_onnx(graph) convert_scale_shift_to_mul_add(graph) graph_clean_up_onnx(graph) fuse_mul_add_sequence(graph) graph_clean_up_onnx(graph) fuse_linear_ops(graph) graph_clean_up_onnx(graph) grouped_convolutions_fusing(graph) graph_clean_up_onnx(graph) fuse_linear_ops(graph) graph_clean_up_onnx(graph) convert_muladd_to_scaleshift_or_power(graph) graph_clean_up_onnx(graph) convert_mul_add_to_power(graph) graph_clean_up_onnx(graph) convert_reshape(graph) graph_clean_up_onnx(graph) convert_add_or_mul_to_scaleshift(graph) # scale = 1 graph_clean_up_onnx(graph) fuse_pad(graph) graph_clean_up_onnx(graph) fuse_sequence_of_reshapes(graph) graph_clean_up_onnx(graph) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) merge_nodes_permutations(graph) permute_data_nodes_attrs(graph) permute_op_nodes_attrs(graph) class_registration.apply_replacements( graph, class_registration.ClassType.BACK_REPLACER) for_graph_and_each_sub_graph_recursively(graph, remove_const_ops) CreateConstNodesReplacement().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, remove_output_ops) weights, xml_string = prepare_emit_ir(graph=graph, data_type=precision, output_dir=output_dir, output_model_name=output_model_name, meta_info={'unset': []}) return weights, xml_string
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) 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 [] # Remove temporary ie_is_available key from argv no to have it in IR ie_is_available = argv.ie_is_available del argv.ie_is_available 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" # This try-except is additional reinsurance that the IE # dependency search does not break the MO pipeline try: if not argv.legacy_ir_generation and ie_is_available: path_to_offline_transformations = os.path.join( os.path.realpath(os.path.dirname(__file__)), 'back', 'offline_transformations.py') status = subprocess.run([ sys.executable, path_to_offline_transformations, "--input_model", orig_model_name, "--framework", argv.framework, "--transform", argv.transform ], env=os.environ) return_code = status.returncode except Exception as e: return_code = "failed" log.error(e, extra={'is_warning': True}) 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 IR wasn't produced by offline_transformations step we need to fallback to IR # produced by prepare_ir. This IR needs to be renamed from XXX_tmp.xml to XXX.xml suffixes = [".xml", ".bin", ".mapping"] if return_code != 0: if len(argv.transform) != 0: # Remove temporary IR before throwing exception for suf in suffixes: path_to_file = orig_model_name + "_tmp" + suf if os.path.exists(path_to_file): os.remove(path_to_file) raise Error("Failed to apply transformations: {}".format( argv.transform)) log.error("Using fallback to produce IR.", extra={'is_warning': True}) for suf in suffixes: # remove existing files path_to_file = orig_model_name + suf if os.path.exists(path_to_file): os.remove(path_to_file) # rename tmp IR to original name os.rename(orig_model_name + "_tmp" + suf, orig_model_name + suf) else: for suf in suffixes: # 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 find_and_replace_pattern(self, graph: Graph): fw = graph.graph['fw'] argv = graph.graph['cmd_params'] layout = graph.graph['layout'] for_graph_and_each_sub_graph_recursively(graph, fuse_pad) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes for_graph_and_each_sub_graph_recursively(graph, lambda graph: mark_unfused_nodes(graph, argv.finegrain_fusing)) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence # IE doesn't support batchNormInference with 4 inputs, so we have to split it to two ScaleShift for_graph_and_each_sub_graph_recursively(graph, convert_batch_norm) if fw == 'caffe': # Converting ScaleShift layer to Mul->Add for_graph_and_each_sub_graph_recursively(graph, convert_scale_shift_to_mul_add) for_graph_and_each_sub_graph_recursively(graph, Div().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively(graph, Sub().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) if not argv.disable_fusing: if fw != 'caffe': # Converting ScaleShift layer to Mul->Add for_graph_and_each_sub_graph_recursively(graph, convert_scale_shift_to_mul_add) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) # Fusing the sequences of Mul/Add operations for_graph_and_each_sub_graph_recursively(graph, fuse_mul_add_sequence) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) normalize_eltwise_inputs(graph) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) # Fusing linear operation to Convolution for_graph_and_each_sub_graph_recursively(graph, fuse_linear_ops) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) if not argv.disable_gfusing: for_graph_and_each_sub_graph_recursively(graph, grouped_convolutions_fusing) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) if not argv.disable_fusing: for_graph_and_each_sub_graph_recursively(graph, fuse_linear_ops) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) for_graph_and_each_sub_graph_recursively(graph, normalize_eltwise_inputs) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) MarkNodesToFuseUpToFakeQuantize().find_and_replace_pattern(graph) FakeQuantizeFuse().find_and_replace_pattern(graph) AddFakeQuantizeFuse().find_and_replace_pattern(graph) MulFakeQuantizeFuse().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) for_graph_and_each_sub_graph_recursively(graph, fuse_pad) for_graph_and_each_sub_graph_recursively(graph, lambda G: G.clean_up()) if layout != 'NHWC' and not argv.disable_resnet_optimization: stride_optimization(graph)
def driver(argv: argparse.Namespace, model_file_name: str, output_model_name: str, output_dir: str): meta_info = get_meta_info(argv) model_proto = load_onnx_model(model_file_name) model_graph = model_proto.graph # pylint: disable=no-member # print(model_graph) # assert len(model_graph) == 1, "An ONNX model contains more than 1 graph: unsupported" log.debug("Number of nodes in graph_def: {}".format(len(model_graph.node))) log.debug("Number of all input ports (not true inputs) in graph_def: {}".format(len(model_graph.input))) log.debug("Number of initializers in graph_def: {}".format(len(model_graph.initializer))) log.debug("Number of real inputs in graph_def: {}".format(len(model_graph.input) - len(model_graph.initializer))) update_extractors_with_extensions(onnx_op_extractors) try: graph = protobuf2nx(model_proto) log.debug("Number of nodes in NX graph: {}".format(graph.number_of_nodes())) graph.__setattr__('name', output_model_name if output_model_name else model_proto.graph.name) # pylint: disable=no-member graph.graph['layout'] = 'NCHW' graph.graph['cmd_params'] = argv graph.graph['fw'] = 'onnx' graph.graph['feature_dim'] = 1 if graph.graph['layout'] == 'NCHW' else 3 graph.graph['ir_version'] = 2 if argv.generate_deprecated_IR_V2 else 5 except Exception as e: raise Error( 'Cannot pre-process ONNX graph after reading from model file "{}". ' \ 'File is corrupt or has unsupported format. Details: {}. ' + refer_to_faq_msg(44), model_file_name, str(e) ) from e graph.check_empty_graph('protobuf2nx. It may happen due to problems with loaded model') extract_node_attrs(graph, lambda node: onnx_op_extractor(node, check_for_duplicates(onnx_op_extractors))) # --------------------------------- LOAD END ------------------------------------------------------ class_registration.apply_replacements(graph, class_registration.ClassType.FRONT_REPLACER) class_registration.apply_replacements(graph, class_registration.ClassType.MIDDLE_REPLACER) fuse_pad(graph) graph_clean_up_onnx(graph) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes mark_unfused_nodes(graph, argv.finegrain_fusing) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence # IE doesn't support BN with 4 inputs, so we have to split it to two ScaleShift convert_batch_norm(graph) graph_clean_up_onnx(graph) if not argv.disable_fusing: # Converting ScaleShift layer to Mul->Add convert_scale_shift_to_mul_add(graph) graph_clean_up_onnx(graph) # Fusing the sequences of Mul/Add operations fuse_mul_add_sequence(graph) graph_clean_up_onnx(graph) # Fusing linear operation to Convolution fuse_linear_ops(graph) graph_clean_up_onnx(graph) if not argv.disable_gfusing: grouped_convolutions_fusing(graph) graph_clean_up_onnx(graph) if not argv.disable_fusing: fuse_linear_ops(graph) graph_clean_up_onnx(graph) AddQuantizeFuse().find_and_replace_pattern(graph) MulQuantizeFuse().find_and_replace_pattern(graph) convert_muladd_to_scaleshift_or_power(graph) graph_clean_up_onnx(graph) convert_mul_add_to_power(graph) graph_clean_up_onnx(graph) convert_reshape(graph) graph_clean_up_onnx(graph) convert_add_or_mul_to_scaleshift(graph) # scale = 1 graph_clean_up_onnx(graph) fuse_pad(graph) graph_clean_up_onnx(graph) if argv.reverse_input_channels: reverse_input_channels(graph) if argv.move_to_preprocess: move_scaleshift_to_preprocess(graph) graph_clean_up_onnx(graph) fuse_sequence_of_reshapes(graph) graph_clean_up_onnx(graph) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) merge_nodes_permutations(graph) permute_data_nodes_attrs(graph) permute_op_nodes_attrs(graph) class_registration.apply_replacements(graph, class_registration.ClassType.BACK_REPLACER) for_graph_and_each_sub_graph_recursively(graph, remove_const_ops) CreateConstNodesReplacement().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, remove_output_ops) prepare_emit_ir(graph=graph, data_type=argv.data_type, output_dir=output_dir, output_model_name=output_model_name, meta_info=meta_info) return 0
def driver(onnx_modelproto_bytes, precision: str, output_model_name: str, output_dir: str): try: model_proto = onnx.load_from_string(bytes(onnx_modelproto_bytes)) except Exception as e: print("[python] onnx exception: ", str(e)) model_graph = model_proto.graph # pylint: disable=no-member log.debug("Number of nodes in graph_def: {}".format(len(model_graph.node))) log.debug( "Number of all input ports (not true inputs) in graph_def: {}".format( len(model_graph.input))) log.debug("Number of initializers in graph_def: {}".format( len(model_graph.initializer))) log.debug("Number of real inputs in graph_def: {}".format( len(model_graph.input) - len(model_graph.initializer))) update_extractors_with_extensions(onnx_op_extractors) try: graph = protobuf2nx(model_proto) log.debug("Number of nodes in NX graph: {}".format( graph.number_of_nodes())) graph.__setattr__( 'name', output_model_name if output_model_name else model_proto.graph.name) # pylint: disable=no-member graph.graph['layout'] = 'NCHW' graph.graph['fw'] = 'onnx' graph.graph[ 'feature_dim'] = 1 if graph.graph['layout'] == 'NCHW' else 3 graph.graph['cmd_params'] = argparse.Namespace( batch=None, data_type='float', disable_fusing=False, disable_gfusing=False, disable_resnet_optimization=False, enable_concat_optimization=False, extensions=mo_extensions, finegrain_fusing=None, framework='onnx', freeze_placeholder_with_value=None, generate_deprecated_IR_V2=False, input=None, input_model=None, input_shape=None, keep_shape_ops=False, log_level='ERROR', mean_scale_values={}, mean_values=(), model_name=None, move_to_preprocess=False, output=None, output_dir='.', placeholder_shapes=None, reverse_input_channels=False, scale=None, scale_values=(), silent=False, version=False, blobs_as_inputs=False, keep_quantize_ops_in_IR=False, generate_experimental_IR_V10=False) graph.graph['ir_version'] = 6 except Exception as e: raise Error( 'Cannot pre-process ONNX graph after reading from model file "{}". ' \ 'File is corrupt or has unsupported format. Details: {}. ' + refer_to_faq_msg(44), model_file_name, str(e) ) from e graph.check_empty_graph( 'protobuf2nx. It may happen due to problems with loaded model') extract_node_attrs( graph, lambda node: onnx_op_extractor( node, check_for_duplicates(onnx_op_extractors))) # --------------------------------- LOAD END ------------------------------------------------------ class_registration.apply_replacements( graph, class_registration.ClassType.FRONT_REPLACER) class_registration.apply_replacements( graph, class_registration.ClassType.MIDDLE_REPLACER) fuse_pad(graph) graph_clean_up_onnx(graph) for_graph_and_each_sub_graph_recursively( graph, convert_matmul_to_fully_connected) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes mark_unfused_nodes(graph, False) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence # IE doesn't support BN with 4 inputs, so we have to split it to two ScaleShift convert_batch_norm(graph) graph_clean_up_onnx(graph) # Converting ScaleShift layer to Mul->Add convert_scale_shift_to_mul_add(graph) graph_clean_up_onnx(graph) # Fusing the sequences of Mul/Add operations fuse_mul_add_sequence(graph) graph_clean_up_onnx(graph) # Fusing linear operation to Convolution fuse_linear_ops(graph) graph_clean_up_onnx(graph) grouped_convolutions_fusing(graph) graph_clean_up_onnx(graph) fuse_linear_ops(graph) graph_clean_up_onnx(graph) MarkNodesToFuseUpToFakeQuantize().find_and_replace_pattern(graph) FakeQuantizeFuse().find_and_replace_pattern(graph) AddFakeQuantizeFuse().find_and_replace_pattern(graph) MulFakeQuantizeFuse().find_and_replace_pattern(graph) convert_muladd_to_scaleshift(graph) graph_clean_up_onnx(graph) graph_clean_up_onnx(graph) convert_add_or_mul_to_scaleshift(graph) # scale = 1 graph_clean_up_onnx(graph) fuse_pad(graph) graph_clean_up_onnx(graph) FuseReshapesSequence().find_and_replace_pattern(graph) RemoveRedundantReshapes().find_and_replace_pattern(graph) graph_clean_up_onnx(graph) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) merge_nodes_permutations(graph) permute_data_nodes_attrs(graph) permute_op_nodes_attrs(graph) graph_clean_up_onnx(graph) class_registration.apply_replacements( graph, class_registration.ClassType.BACK_REPLACER) for_graph_and_each_sub_graph_recursively(graph, remove_const_ops) CreateConstNodesReplacement().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, remove_output_ops) weights, xml_string = prepare_emit_ir(graph=graph, data_type=precision, output_dir=output_dir, output_model_name=output_model_name, meta_info={'unset': []}) return weights, xml_string
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 = 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) 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 driver(argv, input_model, output_model_name, output_dir): log_step(argv.steps, 'LOAD') meta_info = get_meta_info(argv) EltwiseChecker.enabled = False try: graph = load_kaldi_model(input_model) except Exception as e: raise Error('Model Optimizer is not able to parse Kaldi model {}. '.format(input_model) + refer_to_faq_msg(91)) from e graph.check_empty_graph('load_kaldi_nnet_model') graph.graph['cmd_params'] = argv graph.graph['fw'] = 'kaldi' if graph.graph['cmd_params'].generate_experimental_IR_V10: version = 10 else: version = 6 graph.graph['ir_version'] = 2 if argv.generate_deprecated_IR_V2 else version update_extractors_with_extensions(kaldi_type_extractors) extract_node_attrs(graph, lambda node: kaldi_extractor(node)) # --------------------------------- LOAD END ------------------------------------------------------ log_step(argv.steps, 'FRONT') ReplaceLSTMNodePattern().find_and_replace_pattern(graph) class_registration.apply_replacements(graph, class_registration.ClassType.FRONT_REPLACER) log_step(argv.steps, 'MIDDLE') graph = partial_infer(graph) ReplacePNormNodePattern().find_and_replace_pattern(graph) ReplaceMemoryOffsetNodePattern().find_and_replace_pattern(graph) ReplaceMemoryOffsetWithMemoryNodePattern().find_and_replace_pattern(graph) RemoveMemoryDuplicationPattern().find_and_replace_pattern(graph) MergeNeighborSplicePattern().find_and_replace_pattern(graph) RemoveUselessCropsPattern().find_and_replace_pattern(graph) RemoveIdentity().find_and_replace_pattern(graph) graph_clean_up(graph) AddSelectBeforeMemoryNodePattern().find_and_replace_pattern(graph) ReplaceSpliceNodePattern().find_and_replace_pattern(graph) graph_clean_up(graph) # The order is intentional, firstly eliminate repeated, then remove redundant FuseRepeatedReshapes().find_and_replace_pattern(graph) EliminateRedundantReshape().find_and_replace_pattern(graph) graph_clean_up(graph) graph.check_empty_graph('partial_infer') if argv.counts: try: counts = read_counts_file(argv.counts) except Exception as e: raise Error('Model Optimizer is not able to read counts file {}'.format(argv.counts) + refer_to_faq_msg(92)) from e apply_biases_to_last_layer(graph, counts) if argv.remove_output_softmax: RemoveLastSoftMaxPattern().find_and_replace_pattern(graph) graph_clean_up(graph) log.debug("After removing softmax") graph.print_graph_stat() log_step(argv.steps, 'BACK') LeakyReluToReluWithNegativeSlope().find_and_replace_pattern(graph) TransposeToPermute().find_and_replace_pattern(graph) DivideToEltwises().find_and_replace_pattern(graph) SubtractToEltwises().find_and_replace_pattern(graph) SimpleEltwiseToEltwiseOp().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, convert_matmul_to_fully_connected) # Intentionally after all transformations if argv.remove_memory: CutMemory().find_and_replace_pattern(graph) graph_clean_up(graph) ParameterToInput().find_and_replace_pattern(graph) KaldiRemoveMemoryOutputBackReplacementPattern().find_and_replace_pattern(graph) ForceStrictPrecision().find_and_replace_pattern(graph) remove_const_ops(graph) CreateConstNodesReplacement().find_and_replace_pattern(graph) remove_output_ops(graph) log_step(argv.steps, 'EMIT') prepare_emit_ir(graph, argv.data_type, output_dir, output_model_name, meta_info=meta_info) return 0
def save_restored_graph(graph: Graph, path: str, meta_data, name=None): """ Function to apply all necessary transforms from back stage to prepare and save restored graph and metadata. :param graph: Graph to save :param path: Path to saved IR :param meta_data: Namespace with converting parameters restored from IR :param name: Name for saved IR :return: """ if name is None: name = graph.name precisions = set() for op in graph.get_op_nodes(): if op.type in ('Convolution', 'MatMul'): if op.in_port(1).get_source().node.type == 'FakeQuantize': data_type = op.in_port(1).get_source().node.in_port( 0).get_source().node.soft_get('data_type', None) else: data_type = op.in_port(1).get_source().node.soft_get( 'data_type', None) if data_type is not None: precisions.add(np_data_type_to_precision(data_type)) else: log.warning( 'Cannot check data type for node {} with type {}, skip it.' .format(op.name, op.type)) precision = 'FP16' if 'FP16' in precisions else 'FP32' # We need to run some specific passes from MO back stage. # After some of them we need to clean up graph! for_graph_and_each_sub_graph_recursively( graph, ConvolutionWithGroupsResolver().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively( graph, TopKNormalizer().find_and_replace_pattern) graph.clean_up() for_graph_and_each_sub_graph_recursively( graph, StridedSliceMasksNormalizer().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively( graph, BlobNormalizer().find_and_replace_pattern) for_graph_and_each_sub_graph_recursively( graph, ConvolutionNormalizer().find_and_replace_pattern) 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) prepare_emit_ir(graph, precision, path, name, meta_info=meta_data)
def driver(argv: argparse.Namespace, input_model: str, output_model_name: str, output_dir: str): meta_info = get_meta_info(argv) try: model_nodes, model_params, model_name, iteration_number = load_symbol_def( input_model, argv.input_symbol, argv.input, argv.nd_prefix_name, argv.pretrained_model_name, argv.legacy_mxnet_model) except (ValueError, mxnet.base.MXNetError) as e: raise FrameworkError( 'The following error happened while loading mxnet model {}: {}. ' + refer_to_faq_msg(53), input_model, str(e)) from e if argv.nd_prefix_name and argv.pretrained_model_name and argv.save_params_from_nd: save_params_file(model_name, model_params._arg_params, model_params._aux_params, iteration_number) update_extractors_with_extensions(mxnet_op_extractors) graph = symbol2nx(model_nodes, model_params, argv.input) graph.check_empty_graph( 'symbol2nx. It may happen due to problems with loaded model') graph.__setattr__('name', output_model_name) graph.graph['layout'] = 'NCHW' graph.graph['cmd_params'] = argv graph.graph['fw'] = 'mxnet' graph.graph['feature_dim'] = 1 if graph.graph['layout'] == 'NCHW' else 3 graph.graph['ir_version'] = 2 if argv.generate_deprecated_IR_V2 else 5 extract_node_attrs(graph, mxnet_op_extractor) # --------------------------------- LOAD END ------------------------------------------------------ class_registration.apply_replacements( graph, class_registration.ClassType.FRONT_REPLACER) class_registration.apply_replacements( graph, class_registration.ClassType.MIDDLE_REPLACER) fuse_pad(graph) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes mark_unfused_nodes(graph, argv.finegrain_fusing) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence convert_batch_norm(graph) graph_clean_up(graph) if not argv.disable_fusing: # Converting ScaleShift layer to Mul->Add convert_scale_shift_to_mul_add(graph) graph_clean_up(graph) # Fusing the sequences of Mul/Add operations fuse_mul_add_sequence(graph) graph_clean_up(graph) # Fusing linear operation to Convolution fuse_linear_ops(graph) graph_clean_up(graph) if not argv.disable_resnet_optimization: stride_optimization(graph) fuse_pad(graph) # Converting Mul->Add to ScaleShift node convert_muladd_to_scaleshift_or_power(graph) graph_clean_up(graph) convert_mul_add_to_power(graph) graph_clean_up(graph) convert_add_or_mul_to_scaleshift(graph) # scale = 1 graph_clean_up(graph) if argv.reverse_input_channels: reverse_input_channels(graph) if argv.move_to_preprocess: move_scaleshift_to_preprocess(graph) graph_clean_up(graph) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) class_registration.apply_replacements( graph, class_registration.ClassType.BACK_REPLACER) for_graph_and_each_sub_graph_recursively(graph, remove_const_ops) CreateConstNodesReplacement().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, remove_output_ops) prepare_emit_ir(graph=graph, data_type=argv.data_type, output_dir=output_dir, output_model_name=output_model_name, meta_info=meta_info) return 0
def tf2nx(argv: argparse.Namespace, model_file_name: str, output_model_name: str, outputs: list, output_dir: str, scale: float, is_binary: bool, user_shapes: [None, list, np.array] = None, mean_scale_values: [dict, list] = ()): """ Convert TF GraphDef object to NetworkX representation. The resulting graph is still TF-specific and needs normalization passes to be applied. The specific TF structure assumes each GraphDef node is converted to a single NetworkX node, node id is an original TF node name, and edges go directly from one op to another op. """ meta_info = get_meta_info(argv) 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.load_op_library(library) graph_def, variables_values = load_tf_graph_def(graph_file_name=model_file_name, is_binary=is_binary, checkpoint=argv.input_checkpoint, user_output_node_names_list=outputs, model_dir=argv.saved_model_dir, meta_graph_file=argv.input_meta_graph, saved_model_tags=argv.saved_model_tags) try: tf.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.dump_for_tensorboard(graph_def, argv.tensorboard_logdir) update_extractors_with_extensions(tf_op_extractors) try: graph = protobuf2nx(graph_def) graph.__setattr__('name', output_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['cmd_params'] = argv graph.graph['fw'] = 'tf' graph.graph['ir_version'] = 2 if argv.generate_deprecated_IR_V2 else 4 if graph.graph['ir_version'] == 2: # When the deprecated IR version was requested, # we configure only those phases that can lead to # functional regressions in the version 2. # BasicLSTMCell is one such transformation; when it is turned off, # the body of TF basic_lstm_cell is converted as-is in a decomposed form, # and should work in version 2. BasicLSTMCell.enabled = False # placeholder for request from a transformation pass to repeat the entire conversion graph.graph['repeat_conversion'] = False graph = restore_edges(graph, get_tf_edges) graph = remove_control_dependency_inputs(graph) # extract basic attributes earlier to enable some passes that relies on them before full attribute # extractor is called extract_node_attrs(graph, lambda node: (True, common_tf_fields(node))) 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), model_file_name, str(e) ) from e check_empty_graph(graph, 'protobuf2nx. It may happen due to problems with loaded model') packed_user_shapes, packed_outputs, freeze_placeholder = user_data_repack(graph, user_shapes, outputs, argv.freeze_placeholder_with_value) if freeze_placeholder is not None: FreezePlaceholderValue.enabled = True FreezePlaceholderValue.replacement_dict = freeze_placeholder update_registration() GemmResolver.enabled = False inputs = list(packed_user_shapes.keys()) if packed_user_shapes is not None and isinstance(packed_user_shapes, dict) else None graph.graph['inputs'] = inputs # save user defined inputs for other extensions output_op_nodes = add_output_ops(graph, packed_outputs, inputs=packed_user_shapes) input_op_nodes = add_input_ops(graph, packed_user_shapes, True) # this call of 'graph_clean_up' removes child nodes of outputs which is useful when custom output is specified graph_clean_up_tf(graph) check_empty_graph(graph, 'add_output_ops and add_input_ops. It may happen due to absence of \'Placeholder\' layer ' 'in the model') variables_to_constants(graph, variables_values) del variables_values graph_clean_up_tf(graph) if argv.tensorflow_custom_operations_config_update: if update_custom_replacement_config_file(graph, argv.tensorflow_custom_operations_config_update): return 0 else: return 1 unsupported_ops_to_offload_to_tf = list() MAX_ITERATIONS = 5 cur_iteration = 0 while cur_iteration < MAX_ITERATIONS: graph_copy = copy.deepcopy(graph) # create a copy of graph for the case when some ops are unsupported if argv.tensorflow_subgraph_patterns is not None: csc.replace_subgraph_calls(graph, argv.tensorflow_subgraph_patterns) if argv.tensorflow_operation_patterns is not None: csc.offload_operations_to_tf(graph, argv.tensorflow_operation_patterns) if argv.offload_unsupported_operations_to_tf and len(unsupported_ops_to_offload_to_tf): csc.offload_unsupported_operations_to_tf(graph, unsupported_ops_to_offload_to_tf) extract_node_attrs(graph, lambda node: tf_op_extractor(node, check_for_duplicates(tf_op_extractors))) if argv.tensorflow_use_custom_operations_config is not None: registry = CustomReplacementRegistry() registry.add_custom_replacement_description_from_config(argv.tensorflow_use_custom_operations_config) # automatically generate sub-classes for custom replacements that replace sub-graph with a single node for replacement_desc in registry.get_all_replacements_descriptions(): if replacement_desc.has('op'): type('FrontReplacementFromConfigFileOp' + replacement_desc.op, (FrontReplacementFromConfigFileOp,), {'replacement_id': replacement_desc.id}) update_registration() override_placeholder_shapes(graph, packed_user_shapes) # the user shapes are used to convert TensorFlow Object Detection API models graph.graph['user_shapes'] = packed_user_shapes class_registration.apply_replacements(graph, class_registration.ClassType.FRONT_REPLACER) override_batch(graph, argv.batch) create_tensor_nodes(graph) graph_clean_up_tf(graph) remove_output_ops(graph) partial_infer(graph) delete_control_flow_edges(graph) replacer = AddIsCyclicAttribute() replacer.find_and_replace_pattern(graph) # TENSOR ITERATOR CREATING BEGINS if graph.graph['is_cyclic']: replacer = DeleteSelect() replacer.find_and_replace_pattern(graph) replacer = SmartInputMatcher() replacer.find_and_replace_pattern(graph) replacer = SmartOutputMatcher() replacer.find_and_replace_pattern(graph) replacer = LoopConditionMatcher() replacer.find_and_replace_pattern(graph) replacer = SimpleConditionMather() replacer.find_and_replace_pattern(graph) replacer = BackEdgesMatching() replacer.find_and_replace_pattern(graph) replacer = ConditionChecks() replacer.find_and_replace_pattern(graph) delete_not_executable(graph) graph_clean_up_tf(graph) if graph.graph['is_cyclic']: replacer = SimpleInputMatcher() replacer.find_and_replace_pattern(graph) replacer = BackEdgeSimpleInputMatcher() replacer.find_and_replace_pattern(graph) # Here will be optimizing path (ops after Enter and before body take out of body) replacer = TensorIteratorMerge() replacer.find_and_replace_pattern(graph) # TENSOR ITERATOR CREATING ENDS check_for_cycle(graph) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) check_empty_graph(graph, 'partial_infer') csc.prepare_tf_call_nodes(graph) graph_clean_up_tf(graph) duplicate_shared_weights(graph) input_op_nodes = add_input_ops(graph, packed_user_shapes, False) graph_clean_up_tf(graph) check_empty_graph(graph, 'add_input_ops') change_placeholders_types_to_FP32(graph) scale_input(graph, scale) add_mean_scale_values(graph, mean_scale_values) convert_dilated_convolution(graph) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) l2_norm_to_norm(graph) graph_clean_up_tf(graph) remove_op_nodes(graph, {'identity': True}) remove_useless_split(graph) class_registration.apply_replacements(graph, class_registration.ClassType.MIDDLE_REPLACER) mean_to_avgpool(graph) convert_nasnet(graph) fuse_pad(graph) graph_clean_up_tf(graph) convert_matmul_to_fully_connected(graph) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes for_graph_and_each_sub_graph_recursively(graph, lambda graph: mark_unfused_nodes(graph, argv.finegrain_fusing)) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence # IE doesn't support BN with 4 inputs, so we have to split it to two ScaleShift convert_batch_norm(graph) graph_clean_up_tf(graph) if not argv.disable_fusing: # Converting ScaleShift layer to Mul->Add for_graph_and_each_sub_graph_recursively(graph, convert_scale_shift_to_mul_add) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) # Fusing the sequences of Mul/Add operations for_graph_and_each_sub_graph_recursively(graph, fuse_mul_add_sequence) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) # Fusing linear operation to Convolution for_graph_and_each_sub_graph_recursively(graph, fuse_linear_ops) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) if not argv.disable_gfusing: grouped_convolutions_fusing(graph) graph_clean_up_tf(graph) if not argv.disable_fusing: fuse_linear_ops(graph) graph_clean_up_tf(graph) # Converting Mul->Add to ScaleShift node for_graph_and_each_sub_graph_recursively(graph, convert_muladd_to_scaleshift_or_power) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, convert_mul_add_to_power) # Need to eliminate dead nodes before doing update_fully_connected_shapes # because update_fully_connected_shapes does partial inference and dead # nodes will lead to sporadic failures. for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, update_fully_connected_shapes) for_graph_and_each_sub_graph_recursively(graph, convert_mul_eltwise_to_leaky_relu) graph_clean_up_tf(graph) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, fuse_pad) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, convert_reshape) for_graph_and_each_sub_graph_recursively(graph, convert_squeeze) for_graph_and_each_sub_graph_recursively(graph, convert_add_to_scaleshift) # scale = 1 for_graph_and_each_sub_graph_recursively(graph, convert_mul_to_scaleshift) # biases = 0 if argv.reverse_input_channels: reverse_input_channels(graph) if argv.move_to_preprocess: move_scaleshift_to_preprocess(graph) graph_clean_up_tf(graph) for_graph_and_each_sub_graph_recursively(graph, fuse_sequence_of_reshapes) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) conv_flatten_concat(graph) for_graph_and_each_sub_graph_recursively(graph, apply_nhwc_to_nchw_permutation) for_graph_and_each_sub_graph_recursively(graph, merge_nodes_permutations) for_graph_and_each_sub_graph_recursively(graph, permute_data_nodes_attrs) for_graph_and_each_sub_graph_recursively(graph, permute_op_nodes_attrs) for_graph_and_each_sub_graph_recursively(graph, repack_fully_connected_weights_nhwc_to_nchw) for_graph_and_each_sub_graph_recursively(graph, transpose_fully_connected_weights) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) if argv.offload_unsupported_operations_to_tf: unsupported_ops_to_offload_to_tf = find_unsupported_ops(graph) if len(unsupported_ops_to_offload_to_tf) == 0: log.info('All operations are supported! Exit from the loop.') if not need_to_repeat_conversion(graph): break else: print('After {} iteration there are {} unsupported ops'.format(cur_iteration + 1, len(unsupported_ops_to_offload_to_tf))) else: if not need_to_repeat_conversion(graph): break graph = graph_copy cur_iteration += 1 class_registration.apply_replacements(graph, class_registration.ClassType.BACK_REPLACER) prepare_emit_ir(graph=graph, data_type=argv.data_type, output_dir=output_dir, output_model_name=output_model_name, meta_info=meta_info) return 0
def tf2nx(argv: argparse.Namespace, model_file_name: str, output_model_name: str, output_dir: str, is_binary: bool): """ Convert TF GraphDef object to NetworkX representation. The resulting graph is still TF-specific and needs normalization passes to be applied. The specific TF structure assumes each GraphDef node is converted to a single NetworkX node, node id is an original TF node name, and edges go directly from one op to another op. """ meta_info = get_meta_info(argv) 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.load_op_library(library) graph_def, variables_values = load_tf_graph_def(graph_file_name=model_file_name, is_binary=is_binary, 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) try: tf.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.dump_for_tensorboard(graph_def, argv.tensorboard_logdir) update_extractors_with_extensions(tf_op_extractors) try: graph = protobuf2nx(graph_def) graph.__setattr__('name', output_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['cmd_params'] = argv graph.graph['fw'] = 'tf' graph.graph['ir_version'] = 2 if argv.generate_deprecated_IR_V2 else 5 graph.graph['variables_values'] = variables_values del variables_values graph = restore_edges(graph, get_tf_edges) graph = remove_control_dependency_inputs(graph) 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), model_file_name, str(e) ) from e 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))) # --------------------------------- LOAD END ------------------------------------------------------ class_registration.apply_replacements(graph, class_registration.ClassType.FRONT_REPLACER) class_registration.apply_replacements(graph, class_registration.ClassType.MIDDLE_REPLACER) fuse_pad(graph) graph_clean_up_tf(graph) convert_matmul_to_fully_connected(graph) # Mark nodes with attr 'can_be_fused': False to disable fusing for specified nodes for_graph_and_each_sub_graph_recursively(graph, lambda graph: mark_unfused_nodes(graph, argv.finegrain_fusing)) # Converting FusedBatchNorm layer to Mul->Add->Mul->Add sequence # IE doesn't support BN with 4 inputs, so we have to split it to two ScaleShift convert_batch_norm(graph) graph_clean_up_tf(graph) if not argv.disable_fusing: # Converting ScaleShift layer to Mul->Add for_graph_and_each_sub_graph_recursively(graph, convert_scale_shift_to_mul_add) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) # Fusing the sequences of Mul/Add operations for_graph_and_each_sub_graph_recursively(graph, fuse_mul_add_sequence) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) # Fusing linear operation to Convolution for_graph_and_each_sub_graph_recursively(graph, fuse_linear_ops) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) if not argv.disable_gfusing: grouped_convolutions_fusing(graph) graph_clean_up_tf(graph) if not argv.disable_fusing: fuse_linear_ops(graph) graph_clean_up_tf(graph) # Converting Mul->Add to ScaleShift node for_graph_and_each_sub_graph_recursively(graph, convert_muladd_to_scaleshift_or_power) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, convert_mul_add_to_power) # Need to eliminate dead nodes before doing update_fully_connected_shapes # because update_fully_connected_shapes does partial inference and dead # nodes will lead to sporadic failures. for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, update_fully_connected_shapes) for_graph_and_each_sub_graph_recursively(graph, convert_mul_eltwise_to_leaky_relu) graph_clean_up_tf(graph) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, fuse_pad) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, convert_reshape) for_graph_and_each_sub_graph_recursively(graph, convert_squeeze) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, convert_add_or_mul_to_scaleshift) # scale = 1 for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) if argv.reverse_input_channels: reverse_input_channels(graph) if argv.move_to_preprocess: move_scaleshift_to_preprocess(graph) graph_clean_up_tf(graph) fuse_sequence_of_reshapes(graph) pattern = EltwiseInputNormalize() pattern.find_and_replace_pattern(graph) conv_flatten_concat(graph) if argv.enable_concat_optimization: ConcatOptimization().find_and_replace_pattern(graph) LayoutChangeForConstantShapePaths().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) for_graph_and_each_sub_graph_recursively(graph, apply_nhwc_to_nchw_permutation) for_graph_and_each_sub_graph_recursively(graph, merge_nodes_permutations) for_graph_and_each_sub_graph_recursively(graph, permute_data_nodes_attrs) for_graph_and_each_sub_graph_recursively(graph, permute_op_nodes_attrs) for_graph_and_each_sub_graph_recursively(graph, repack_fully_connected_weights_nhwc_to_nchw) for_graph_and_each_sub_graph_recursively(graph, transpose_fully_connected_weights) for_graph_and_each_sub_graph_recursively(graph, graph_clean_up_tf) class_registration.apply_replacements(graph, class_registration.ClassType.BACK_REPLACER) for_graph_and_each_sub_graph_recursively(graph, remove_const_ops) CreateConstNodesReplacement().find_and_replace_pattern(graph) for_graph_and_each_sub_graph_recursively(graph, remove_output_ops) prepare_emit_ir(graph=graph, data_type=argv.data_type, output_dir=output_dir, output_model_name=output_model_name, meta_info=meta_info) return 0
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" # This try-except is additional reinsurance that the IE # dependency search does not break the MO pipeline try: if not argv.legacy_ir_generation: path_to_offline_transformations = os.path.join( os.path.realpath(os.path.dirname(__file__)), 'back', 'offline_transformations.py') cmd = [ sys.executable, path_to_offline_transformations, "--input_model", orig_model_name, "--framework", argv.framework, "--transform", argv.transform ] if "compress_fp16" in argv and argv.compress_fp16: cmd += ["--compress_fp16"] # restore data_type cmd parameter argv.data_type = 'FP16' status = subprocess.run(cmd, env=os.environ) return_code = status.returncode 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 apply_replacements(graph: Graph, replacements_type): """ Apply all patterns that do not have 'op' first, then apply patterns from registered_ops. If two or more classes replaces the same op (both have op class attribute and values match), such pattern is not applied (while registration it will warn user that we have a conflict). """ dependency_graph = DependencyGraph(name=ClassType(replacements_type).name) for class_type, classes_set in _registered_classes_dict.items(): if class_type == replacements_type: replacers = [] for cls in classes_set: cur_cls_replacers = [c for c in cls.registered_cls if not hasattr(c, 'op')] + \ [c for op, c in cls.registered_ops.items() if c] replacers.extend([ replacer for replacer in cur_cls_replacers if replacer not in cls.excluded_replacers ]) for replacer_cls in replacers: dependency_graph.add_node(replacer_cls) for replacer_cls in replacers: for cls_after in replacer_cls().run_before(): log.debug("Replacer {} will be run before {}".format( replacer_cls, cls_after)) dependency_graph.add_edge(replacer_cls, cls_after) for cls_before in replacer_cls().run_after(): log.debug("Replacer {} will be run after {}".format( replacer_cls, cls_before)) dependency_graph.add_edge(cls_before, replacer_cls) replacers_order = dependency_graph.determined_sort() for replacer_cls in replacers_order: 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)) continue 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)) continue log.debug("Run replacer {}".format(replacer_cls)) try: if hasattr(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, graph_clean_up_tf if graph.graph['fw'] == 'tf' else graph_clean_up_onnx if graph.graph['fw'] == 'onnx' else graph_clean_up) if hasattr(replacer, 'force_shape_inference' ) and replacer.force_shape_inference: shape_inference(graph) 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 Exception as err: raise Exception( 'Exception occurred during running replacer "{} ({})": {}'. format( replacement_id, replacer_cls, str(err).replace('[REPLACEMENT_ID]', replacement_id), )) from err