350 lines
15 KiB
Python
350 lines
15 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 logging as log
|
|
from collections import defaultdict
|
|
from copy import copy
|
|
|
|
import numpy as np
|
|
|
|
from extensions.back.pass_separator import BackFinish
|
|
from extensions.ops.tensor_iterator import TensorIterator, get_internal_node_by_layer_id
|
|
from mo.back.replacement import BackReplacementPattern
|
|
from mo.graph.graph import Graph
|
|
from mo.ops.const import Const
|
|
from mo.utils.error import Error
|
|
from mo.utils.utils import refer_to_faq_msg
|
|
|
|
|
|
class RemoveConstOps(BackReplacementPattern):
|
|
enabled = False
|
|
|
|
def run_after(self):
|
|
return [BackFinish]
|
|
|
|
def run_before(self):
|
|
return []
|
|
|
|
def find_and_replace_pattern(self, graph: Graph):
|
|
for node in graph.get_op_nodes(type='Const'):
|
|
graph.remove_edge(node.id, node.out_node().id)
|
|
graph.remove_node(node.id)
|
|
|
|
|
|
class CreateConstNodesReplacement(BackReplacementPattern):
|
|
enabled = False
|
|
|
|
def run_before(self):
|
|
return []
|
|
|
|
def run_after(self):
|
|
return [RemoveConstOps]
|
|
|
|
@staticmethod
|
|
def pattern():
|
|
return dict(
|
|
nodes=[
|
|
('data', dict(kind='data'))
|
|
],
|
|
edges=[]
|
|
)
|
|
|
|
@staticmethod
|
|
def _check_bin_attrs(node):
|
|
"""Check that at least one output edge from node without 'bin' attribute."""
|
|
out_edges = node.out_edges()
|
|
bin_in_out_ports = ['bin' in edge for edge in out_edges]
|
|
out_node = [node.has('op') and node.op == 'Result' for node in node.out_nodes()]
|
|
return np.any(out_node) or not np.all(bin_in_out_ports)
|
|
|
|
@staticmethod
|
|
def _check_that_node_from_body(node):
|
|
"""Check that all output edges from node have 'internal_port_id'
|
|
(that shows that this node is from TI body)"""
|
|
n_ports = len(node.out_edges())
|
|
internal_port_in_out_ports = ['internal_port_id' in edge for edge in node.out_edges()]
|
|
return np.any(internal_port_in_out_ports) and n_ports
|
|
|
|
def replace_pattern(self, graph: Graph, match: dict):
|
|
"""
|
|
Adds layers with type 'Const' that produce blob from 'bin' file. The pass finds data nodes with one output which
|
|
doesn't have edge with 'bin' attribute (or with two outputs and at least one output havent 'bin' attr)
|
|
and generate Const op node before the node and data node before the Const node. The data node before 'Const'
|
|
node is needed because the op node dumps input tensors to bin file.
|
|
"""
|
|
node = match['data']
|
|
if len(node.in_nodes()) > 0:
|
|
return
|
|
|
|
if self._check_bin_attrs(node):
|
|
if node.has_valid('value'):
|
|
const_node_name = graph.unique_id(node.id + '_const')
|
|
log.debug("Added Const node '{}'".format(const_node_name))
|
|
const_node = Const(graph, {'name': const_node_name, 'value': node.value,
|
|
'force_shape': node.soft_get('force_shape', None),
|
|
'override_output_shape': node.has_valid('force_shape'),
|
|
'force_type': node.soft_get('force_type', None),
|
|
'correct_data_type': node.soft_get('correct_data_type', None),
|
|
}).create_node()
|
|
const_node.add_input_port(0)
|
|
graph.add_edges_from([(const_node_name, node.id, {'out': 0})])
|
|
|
|
node_copy = node.copy_node()
|
|
const_node.type_infer(const_node)
|
|
graph.add_edges_from([(node_copy.id, const_node_name, {'in': 0, 'bin': 'custom'})])
|
|
elif not self._check_that_node_from_body(node):
|
|
log.debug('node = {}'.format(node.graph.node[node.id]))
|
|
raise Error(
|
|
'Discovered data node without inputs and value, node.name = {}, consumer.name = {}. ' +
|
|
refer_to_faq_msg(23),
|
|
node.soft_get('name'),
|
|
node.out_node().soft_get('name') if len(node.out_nodes()) else "<no consumer>"
|
|
)
|
|
|
|
|
|
class RemoveOutputOps(BackReplacementPattern):
|
|
enabled = False
|
|
|
|
def run_after(self):
|
|
return [CreateConstNodesReplacement]
|
|
|
|
def run_before(self):
|
|
return []
|
|
|
|
def find_and_replace_pattern(self, graph: Graph):
|
|
for node in list(graph.get_op_nodes(op='Result')):
|
|
if len(node.in_nodes()) > 0:
|
|
assert (len(node.in_nodes()) == 1)
|
|
graph.remove_node(node.id)
|
|
|
|
|
|
class RemoveConstToResult(BackReplacementPattern):
|
|
"""
|
|
Transformation looks for a sub-graph "Const->Result" and removes Result node.
|
|
Currently IE is unable to handle such graph so this transformation removes to work around this case.
|
|
For instance, this case appears for Wide and Deep model.
|
|
"""
|
|
enabled = True
|
|
|
|
@staticmethod
|
|
def pattern():
|
|
return dict(
|
|
nodes=[
|
|
('const_node', {'type': 'Const', 'kind': 'op'}),
|
|
('const_data', {'kind': 'data'}),
|
|
('result_node', {'type': 'Result', 'kind': 'op'}),
|
|
],
|
|
edges=[
|
|
('const_node', 'const_data'),
|
|
('const_data', 'result_node')
|
|
]
|
|
)
|
|
|
|
@staticmethod
|
|
def replace_pattern(graph: Graph, match: dict):
|
|
const_node = match['const_node']
|
|
const_data_node = match['const_data']
|
|
result_node = match['result_node']
|
|
nodes_to_remove = [result_node.id]
|
|
|
|
# in case only const data consumer that is the result node, remove the whole sub-graph
|
|
if len(const_node.out_port(0).get_destinations()) == 1:
|
|
nodes_to_remove.append(const_node.id)
|
|
nodes_to_remove.append(const_data_node.id)
|
|
|
|
graph.remove_node(nodes_to_remove)
|
|
|
|
|
|
class NormalizeTI(BackReplacementPattern):
|
|
"""
|
|
This transformation is used while generating IR of lower than 10 version
|
|
|
|
Changes linking mechanism of TensorIterator outer graph with inner graph
|
|
from linking outer graph node ports with inner Parameter and Result operations
|
|
to linking outer graph node ports with functional operations and their input/output ports
|
|
|
|
1. Updating `input_port_map`, `output_port_map` and `back_edges` maps
|
|
2. Removing Parameter/Input operation nodes
|
|
|
|
NOTE: Result operation will be removed by a separate transformation
|
|
"""
|
|
enabled = False
|
|
|
|
@staticmethod
|
|
def maps_uniqueization(ti):
|
|
assert ti.has_valid('input_port_map')
|
|
assert ti.has_valid('output_port_map')
|
|
assert ti.has_valid('back_edges')
|
|
|
|
ti.input_port_map = [dict(unique_r) for unique_r in set([tuple(rec.items()) for rec in ti.input_port_map])]
|
|
ti.output_port_map = [dict(unique_r) for unique_r in set([tuple(rec.items()) for rec in ti.output_port_map])]
|
|
ti.back_edges = [dict(unique_rec) for unique_rec in set([tuple(rec.items()) for rec in ti.back_edges])]
|
|
|
|
@staticmethod
|
|
def normalize_ti(ti):
|
|
assert ti.has_valid('input_port_map')
|
|
assert ti.has_valid('output_port_map')
|
|
assert ti.has_valid('back_edges')
|
|
|
|
body = ti.body
|
|
|
|
for record in ti.input_port_map:
|
|
assert 'internal_layer_id' in record
|
|
assert 'internal_port_id' not in record
|
|
assert 'external_port_id' in record
|
|
internal_layer_id = copy(record['internal_layer_id'])
|
|
parameter = get_internal_node_by_layer_id(ti, internal_layer_id)
|
|
|
|
dst = parameter.out_port(0).get_destination()
|
|
in_port_idx = dst.idx
|
|
internal_node = dst.node
|
|
internal_port_id = internal_node.in_edge(in_port_idx)['internal_port_id']
|
|
|
|
record['internal_layer_id'] = internal_node.internal_layer_id
|
|
record['internal_port_id'] = internal_port_id
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='to', old_layer_id=internal_layer_id, old_port_id=None,
|
|
new_layer_id=internal_node.internal_layer_id,
|
|
new_port_id=internal_port_id)
|
|
|
|
for record in ti.output_port_map:
|
|
assert 'internal_layer_id' in record
|
|
assert 'internal_port_id' not in record
|
|
assert 'external_port_id' in record
|
|
internal_layer_id = copy(record['internal_layer_id'])
|
|
result = get_internal_node_by_layer_id(ti, internal_layer_id)
|
|
|
|
out_port_idx = result.in_port(0).get_source().idx
|
|
internal_node = result.in_port(0).get_source().node
|
|
internal_port_id = internal_node.out_edge(out_port_idx)['internal_port_id']
|
|
|
|
record['internal_layer_id'] = internal_node.internal_layer_id
|
|
record['internal_port_id'] = internal_port_id
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='from', old_layer_id=internal_layer_id,
|
|
old_port_id=None, new_layer_id=internal_node.internal_layer_id,
|
|
new_port_id=internal_port_id)
|
|
|
|
for record in ti.back_edges:
|
|
assert 'from_layer' in record
|
|
assert 'to_layer' in record
|
|
|
|
internal_layer_id = record['from_layer']
|
|
result = get_internal_node_by_layer_id(ti, internal_layer_id)
|
|
|
|
if result.soft_get('type') == 'Result':
|
|
assert 'from_port' not in record
|
|
|
|
out_port_idx = result.in_port(0).get_source().idx
|
|
internal_node = result.in_port(0).get_source().node
|
|
internal_port_id = internal_node.out_edge(out_port_idx)['internal_port_id']
|
|
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='from', old_layer_id=internal_layer_id,
|
|
old_port_id=None, new_layer_id=internal_node.internal_layer_id,
|
|
new_port_id=internal_port_id)
|
|
|
|
body.remove_nodes_from([n.id for n in body.get_op_nodes(type='Input')])
|
|
body.remove_nodes_from([n.id for n in body.get_op_nodes(type='Parameter')])
|
|
|
|
@staticmethod
|
|
def external_nodes_normalization(ti):
|
|
"""
|
|
TensorIterator external ports may have several internal layer connections.
|
|
|
|
Current transformation does the following:
|
|
- normalizes port maps (eliminating duplicated records)
|
|
- replicates external input/output port for each internal Parameter/Result it is connected to
|
|
- updates input and output port maps according to previous step replications
|
|
"""
|
|
|
|
def update_external_port_id(ti, port_type, old_external_port_id, new_external_port_id, internal_layer_id):
|
|
assert port_type in ['in', 'out']
|
|
|
|
port_map = ti.input_port_map if port_type == 'in' else ti.output_port_map
|
|
for record in port_map:
|
|
if record['external_port_id'] == old_external_port_id and \
|
|
record['internal_layer_id'] == internal_layer_id:
|
|
record['external_port_id'] = new_external_port_id
|
|
|
|
NormalizeTI.maps_uniqueization(ti)
|
|
|
|
body = ti.body
|
|
|
|
external_input_ports = defaultdict(list)
|
|
for record in ti.input_port_map:
|
|
assert 'external_port_id' in record
|
|
external_input_ports[record['external_port_id']].append(record)
|
|
|
|
for external_port_id, record_list in external_input_ports.items():
|
|
if len(record_list) == 1:
|
|
continue
|
|
|
|
real_external_port_id = TensorIterator.special_port_to_real_port(ti, external_port_id, 'in')
|
|
source = ti.in_port(real_external_port_id).get_source()
|
|
|
|
for record in record_list[1:]:
|
|
assert 'internal_layer_id' in record
|
|
|
|
new_real_input_port_id = max(map(int, ti.in_ports().keys())) + 1
|
|
new_external_port_id = max([int(d['external_port_id']) for d in
|
|
list(ti.in_edges().values()) + list(ti.out_edges().values())]) + 1
|
|
|
|
ti.add_input_port(new_real_input_port_id)
|
|
source.connect(ti.in_port(new_real_input_port_id))
|
|
|
|
ti.in_edge(new_real_input_port_id)['external_port_id'] = new_external_port_id
|
|
update_external_port_id(ti, 'in', external_port_id, new_external_port_id, record['internal_layer_id'])
|
|
|
|
external_output_ports = defaultdict(list)
|
|
for record in ti.output_port_map:
|
|
assert 'external_port_id' in record
|
|
external_output_ports[record['external_port_id']].append(record)
|
|
|
|
for external_port_id, record_list in external_output_ports.items():
|
|
if len(record_list) == 1:
|
|
continue
|
|
|
|
real_external_port_id = TensorIterator.special_port_to_real_port(ti, external_port_id, 'out')
|
|
dsts = ti.out_port(real_external_port_id).get_destinations()
|
|
|
|
for record in record_list[1:]:
|
|
assert 'internal_layer_id' in record
|
|
|
|
new_real_output_port_id = max(map(int, ti.out_ports().keys())) + 1
|
|
new_external_port_id = max([int(d['external_port_id']) for d in
|
|
list(ti.in_edges().values()) + list(ti.out_edges().values())]) + 1
|
|
|
|
ti.add_output_port(new_real_output_port_id)
|
|
for dst in dsts:
|
|
ti.out_port(new_real_output_port_id).connect(dst)
|
|
|
|
update_external_port_id(ti, 'out', external_port_id, new_external_port_id, record['internal_layer_id'])
|
|
|
|
body.clean_up()
|
|
|
|
def find_and_replace_pattern(self, graph: Graph):
|
|
for ti in graph.get_op_nodes(type='TensorIterator'):
|
|
if not graph.graph['cmd_params'].generate_experimental_IR_V10:
|
|
self.normalize_ti(ti)
|
|
else:
|
|
self.external_nodes_normalization(ti)
|
|
|
|
if len([record for record in ti.input_port_map if record.get('axis') is not None]) == 0:
|
|
for record in ti.output_port_map:
|
|
if record.get('axis') is not None:
|
|
record['start'] = 0
|
|
real_output_port = TensorIterator.special_port_to_real_port(ti, record['external_port_id'], 'out')
|
|
output_shape = ti.out_port(real_output_port).data.get_shape()
|
|
assert output_shape is not None
|
|
record['end'] = output_shape[record['axis']]
|