Files
openvino/model-optimizer/mo/pipeline/common.py
T

244 lines
10 KiB
Python

"""
Copyright (C) 2018-2020 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import argparse
import logging as log
import os
from operator import itemgetter
import networkx as nx
from extensions.back.RemoveUselessConvert import RemoveUselessConvert
from extensions.back.op_versioning import OpVersioning
from extensions.ops.Cast import Cast
from mo.back.ie_ir_ver_2.emitter import port_renumber, serialize_constants, generate_ie_ir, serialize_mean_image
from mo.graph.graph import Node, Graph
from mo.middle.passes import tensor_names, convert_data_type
from mo.middle.passes.convert_data_type import data_type_str_to_np
from mo.middle.passes.infer import type_infer
from mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively
from mo.utils.error import Error
def determined_sort(outputs: list):
op_order = []
data_order = []
stack = list(outputs)
visited = set()
while len(stack) != 0:
node = stack[0]
node_id = node.id
stack.pop(0)
visited.add(node_id)
has_child = False
in_names = [n.id if isinstance(node.in_nodes(), list) else node.in_node(n).id for n in node.in_nodes()]
for in_node_name in in_names:
if in_node_name not in visited:
stack.insert(0, node)
stack.insert(0, Node(node.graph, in_node_name))
has_child = True
break
if not has_child:
if node.kind == 'op':
op_order.append(node_id)
if node.kind == 'data':
data_order.append(node_id)
return op_order, data_order
def get_fw_tensor_debug_info(node: Node):
while not node.has_valid('fw_tensor_debug_info') and not node.has_valid('output_sort_order') \
and len(node.in_nodes()):
try:
node = node.in_node()
except Exception as e:
log.warning('Was not able to determine tensor debug info for node {}'.format(node.name))
return "dummy_node_name"
if node.has_valid('output_sort_order'):
return node.soft_get('output_sort_order')
return node.soft_get('fw_tensor_debug_info')
def get_sorted_outputs(graph: Graph):
outputs = []
outputs_for_sort = {}
for node in graph.nodes():
if len(graph.out_edges(node)) == 0:
outputs.append(Node(graph, node))
if len(outputs) == 1:
return outputs
for node in outputs:
debug_info = get_fw_tensor_debug_info(node)
if isinstance(debug_info, str):
outputs_for_sort[node.id] = debug_info
elif isinstance(debug_info, list):
outputs_for_sort[node.id] = debug_info[0][0] + '_' + str(debug_info[0][1])
else:
raise Error('Unsupported type of the variable with debug information used to sort output nodes')
if len(outputs_for_sort) != len(set(outputs_for_sort.values())):
log.warning('There are at least two output nodes with the same key used to sort the outputs. This means that '
'IRs with different order of nodes may be generated between Model Optimizer runs. The dictionary '
'with outputs is: {}'.format(outputs_for_sort))
return [Node(graph, key) for key, value in sorted(outputs_for_sort.items(), key=itemgetter(1))]
def collect_sub_graphs(graph: Graph):
""" Go over all nodes and sub_graphs in the graph recursively; returns all found sub-graphs. """
result = []
for node in graph.nodes():
node = Node(graph, node)
if node.has_valid('sub_graphs'):
for sub_graph in node.sub_graphs:
result.append(node[sub_graph])
result += collect_sub_graphs(node[sub_graph])
return result
def relabel_nodes_inplace_safe(graph: Graph, new_labels: dict):
""" Safely relabels graph in-place without graph copy.
Safety in this place means that it is guaranteed that
there won't be collisions during relabeling process.
"""
# Relabel nodes in two stages
intermediate_map = {node: graph.unique_id('__relabel__{}__'.format(str(i))) for i, node in enumerate(graph.nodes())}
final_map = {dst: new_labels[src] for src, dst in intermediate_map.items()}
assert len(set(intermediate_map.keys()).intersection(set(intermediate_map.values()))) == 0
assert len(set(final_map.keys()).intersection(set(final_map.values()))) == 0
nx.relabel_nodes(graph, intermediate_map, copy=False)
nx.relabel_nodes(graph, final_map, copy=False)
def convert_const_node_value_type(const_node: Node, np_data_type):
assert const_node.type == 'Const'
log.warning('Converting type of Const node "{}" to "{}"'.format(const_node.name, np_data_type))
const_node.value = const_node.value.astype(np_data_type)
const_node.data_type = np_data_type
const_node.infer(const_node)
const_node.type_infer(const_node)
# if the Const node has an input data node then need to update it also
if len(const_node.in_nodes()) == 1:
input_data = const_node.in_node(0)
assert input_data.kind == 'data'
input_data.value = input_data.value.astype(const_node.data_type)
input_data.data_type = const_node.data_type
def convert_inputs_of_specific_ops(graph: Graph):
type_port = {'Broadcast': {1: 'int64', 2: 'int64'},
'ConvolutionBackpropData': {2: 'int64'},
'Deconvolution': {2: 'int64'},
'Gather': {2: 'int64'},
'GroupConvolutionBackpropData': {2: 'int64'},
'Interpolate': {1: 'int64'},
'LRN': {1: 'int64'},
'NonMaxSuppression': {2: 'int64'},
'NormalizeL2': {1: 'int64'},
'OneHot': {1: 'int64'},
'Pad': {1: 'int64', 2: 'int64'},
'PriorBox': {0: 'int64', 1: 'int64'},
'PriorBoxClustered': {0: 'int64', 1: 'int64'},
'ReduceLogicalAnd': {1: 'int64'},
'ReduceLogicalOr': {1: 'int64'},
'ReduceMax': {1: 'int64'},
'ReduceMean': {1: 'int64'},
'ReduceMin': {1: 'int64'},
'ReduceProd': {1: 'int64'},
'ReduceSum': {1: 'int64'},
'Reshape': {1: 'int64'},
'Squeeze': {1: 'int64'},
'StridedSlice': {1: 'int64', 2: 'int64', 3: 'int64'},
'Split': {1: 'int64'},
'Tile': {1: 'int64'},
'Transpose': {1: 'int64'},
'Unsqueeze': {1: 'int64'},
'VariadicSplit': {1: 'int64', 2: 'int64'},
}
for node in graph.get_op_nodes():
if node.soft_get('type') in type_port:
ports_to_update = type_port[node.soft_get('type')]
for port_id, precision in ports_to_update.items():
if port_id in node.in_ports() and not node.in_port(port_id).disconnected():
log.debug('Converting value for the input port "{}" of op "{}" to "{}".'
''.format(port_id, node.soft_get('name', node.id), precision))
in_port = node.in_port(port_id)
np_type = data_type_str_to_np(precision)
if in_port.get_source().node.type == 'Const':
convert_const_node_value_type(node.in_port(port_id).get_source().node, np_type)
else:
in_port.get_connection().insert_node(Cast(graph, {'dst_type': np_type}).create_node())
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
for_graph_and_each_sub_graph_recursively(graph, convert_inputs_of_specific_ops)
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 get_ir_version(argv: argparse.Namespace):
"""
Determine IR version based on command line arguments and the default version.
:param argv: the parsed command line arguments
:return: the IR version
"""
return 10