501 lines
23 KiB
Python
501 lines
23 KiB
Python
"""
|
|
Copyright (C) 2017-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.
|
|
"""
|
|
from copy import copy, deepcopy
|
|
|
|
import numpy as np
|
|
|
|
from extensions.ops.parameter import Parameter
|
|
from mo.graph.graph import Node, dict_includes, Graph
|
|
from mo.ops.const import Const
|
|
from mo.ops.op import Op
|
|
from mo.utils.error import Error
|
|
|
|
|
|
class TensorIterator(Op):
|
|
''' Loop layer that iterates over tensors and execute embedded sub-graph.
|
|
'''
|
|
|
|
op = 'TensorIterator'
|
|
|
|
def __init__(self, graph: Graph, attrs: dict):
|
|
mandatory_props = {
|
|
'type': __class__.op,
|
|
'op': __class__.op,
|
|
'version': 'opset1',
|
|
'input_port_map': [], # a list of dicts with such attrs as external_port_id, etc.
|
|
'output_port_map': [], # a list of dicts with such attrs as external_port_id, etc.
|
|
'back_edges': [], # a list of dicts with such attrs as from_layer, from_port, etc.
|
|
'body': None, # an Graph object with a body sub-graph
|
|
'sub_graphs': ['body'], # built-in attribute with all sub-graphg
|
|
'infer': self.infer,
|
|
'type_infer': self.ti_type_infer,
|
|
}
|
|
super().__init__(graph, mandatory_props, attrs)
|
|
|
|
@staticmethod
|
|
def cover_body_input_data_nodes_with_parameter_ops(ti: Node):
|
|
body = ti.body
|
|
|
|
op_port_map = []
|
|
for record in ti.input_port_map:
|
|
operation_node = get_internal_node_by_layer_id(ti, record['internal_layer_id'])
|
|
real_in_port = TensorIterator.special_port_to_real_port(operation_node, copy(record['internal_port_id']))
|
|
op_port_map.append((operation_node, real_in_port))
|
|
|
|
for operation_node, in_port in op_port_map:
|
|
data_node = operation_node.in_node(in_port)
|
|
|
|
attrs = deepcopy(body.get_edge_data(data_node.id, operation_node.id)[0])
|
|
body.remove_edge(data_node.id, operation_node.id)
|
|
|
|
assert data_node.has_valid('shape'), \
|
|
'Data node should have `shape` attribute set, but it`s not for node {}'.format(data_node.id)
|
|
shape = data_node['shape'].copy()
|
|
parameter_data_node = Parameter(body, {'shape': shape}).create_node_with_data()
|
|
|
|
body.create_edge(src_node=parameter_data_node, dst_node=operation_node,
|
|
out_port=0, in_port=in_port, edge_attrs=attrs)
|
|
del body.get_edge_data(parameter_data_node.id, operation_node.id)[0]['out']
|
|
|
|
@staticmethod
|
|
def cover_body_constant_data_nodes_with_const_ops(ti: Node):
|
|
body = ti.body
|
|
for data_node in body.get_data_nodes():
|
|
if len(data_node.in_nodes()) == 0 and len(data_node.out_nodes()) != 0:
|
|
assert data_node.has_valid('shape'), \
|
|
'Data node should have `shape` attribute set, but it`s not for node {}'.format(data_node.id)
|
|
assert data_node.has_valid('value'), \
|
|
'Data node should have `value` attribute set, but it`s not for node {}'.format(data_node.id)
|
|
shape = data_node['shape'].copy()
|
|
value = data_node['value'].copy()
|
|
const_node = Const(body, {'shape': shape, 'value': value}).create_node()
|
|
body.create_edge(src_node=const_node, dst_node=data_node, out_port=0, in_port=0)
|
|
|
|
@staticmethod
|
|
def special_port_to_real_port(node: Node, special_port_id: int, direction: str = 'in'):
|
|
assert node.kind == 'op'
|
|
assert direction in ['in', 'out']
|
|
|
|
port_type = 'external_port_id' if node.has_valid('body') else 'internal_port_id'
|
|
|
|
if direction == 'in':
|
|
edges = node.in_edges()
|
|
if direction == 'out':
|
|
edges = node.out_edges()
|
|
|
|
suitable_edges = {}
|
|
for idx, attrs in edges.items():
|
|
if port_type in attrs and attrs[port_type] == special_port_id:
|
|
suitable_edges[idx] = attrs
|
|
assert len(suitable_edges) == 1
|
|
return list(suitable_edges.keys())[0]
|
|
|
|
@staticmethod
|
|
def set_internal_layer_id_for_nodes(ti: Node, nodes: list):
|
|
max_internal_layer_id_used = max([n.soft_get('internal_layer_id', 0) for n in ti.body.get_op_nodes()])
|
|
|
|
for node in nodes:
|
|
if not node.has_valid('internal_layer_id'):
|
|
node['internal_layer_id'] = max_internal_layer_id_used = max_internal_layer_id_used + 1
|
|
|
|
@staticmethod
|
|
def update_back_edge_map(ti, direction, old_layer_id, old_port_id, new_layer_id, new_port_id=None):
|
|
assert direction in ['from', 'to']
|
|
layer_attr_name = direction + '_layer'
|
|
port_attr_name = direction + '_port'
|
|
|
|
for record in ti.back_edges:
|
|
if record[layer_attr_name] != old_layer_id:
|
|
continue
|
|
if (port_attr_name in record and record[port_attr_name] == old_port_id) \
|
|
or new_port_id is not None:
|
|
record[layer_attr_name] = new_layer_id
|
|
if new_port_id is None:
|
|
del record[port_attr_name]
|
|
else:
|
|
record[port_attr_name] = new_port_id
|
|
|
|
@staticmethod
|
|
def validate_maps(ti):
|
|
def check_by_attribute(port_map, appropriate_attribute, inappropriate_attribute, node_type):
|
|
for record in port_map:
|
|
node = get_internal_node_by_layer_id(ti, record[appropriate_attribute])
|
|
assert node.soft_get('type') == node_type
|
|
assert inappropriate_attribute not in record, record[inappropriate_attribute]
|
|
|
|
check_by_attribute(ti.input_port_map, 'internal_layer_id', 'internal_port_id', 'Parameter')
|
|
check_by_attribute(ti.output_port_map, 'internal_layer_id', 'internal_port_id', 'Result')
|
|
check_by_attribute(ti.back_edges, 'from_layer', 'from_port', 'Result')
|
|
check_by_attribute(ti.back_edges, 'to_layer', 'to_port', 'Parameter')
|
|
|
|
@staticmethod
|
|
def normalize_internal_ids(ti):
|
|
assert ti.has_valid('input_port_map')
|
|
assert ti.has_valid('output_port_map')
|
|
assert ti.has_valid('back_edges')
|
|
|
|
body = ti.body
|
|
|
|
TensorIterator.set_internal_layer_id_for_nodes(ti, body.get_op_nodes(type='Parameter'))
|
|
TensorIterator.set_internal_layer_id_for_nodes(ti, body.get_op_nodes(type='Result'))
|
|
|
|
node_map = {copy(node.internal_layer_id): node for node in body.get_op_nodes() if
|
|
node.has_valid('internal_layer_id')}
|
|
|
|
for record in ti.input_port_map:
|
|
assert 'internal_layer_id' in record
|
|
assert 'internal_port_id' in record
|
|
assert 'external_port_id' in record
|
|
|
|
internal_node_id = copy(record['internal_layer_id'])
|
|
assert internal_node_id in node_map
|
|
internal_node = node_map[internal_node_id]
|
|
|
|
in_port = TensorIterator.special_port_to_real_port(internal_node, copy(record['internal_port_id']))
|
|
assert in_port in internal_node.in_ports() and not internal_node.in_port(in_port).disconnected()
|
|
|
|
internal_input_node = internal_node.in_port(in_port).get_source().node
|
|
assert internal_input_node.soft_get('type') == 'Parameter'
|
|
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='to', old_layer_id=internal_node_id,
|
|
old_port_id=record['internal_port_id'],
|
|
new_layer_id=internal_input_node.internal_layer_id)
|
|
del record['internal_port_id']
|
|
record['internal_layer_id'] = internal_input_node['internal_layer_id']
|
|
|
|
for record in ti.output_port_map:
|
|
assert 'internal_layer_id' in record
|
|
assert 'internal_port_id' in record
|
|
assert 'external_port_id' in record
|
|
|
|
internal_node_id = copy(record['internal_layer_id'])
|
|
assert internal_node_id in node_map
|
|
internal_node = node_map[internal_node_id]
|
|
|
|
out_port = TensorIterator.special_port_to_real_port(internal_node, copy(record['internal_port_id']), 'out')
|
|
assert out_port in internal_node.out_ports() and not internal_node.out_port(out_port).disconnected()
|
|
|
|
assert len(internal_node.out_port(out_port).get_destinations()) >= 1
|
|
|
|
internal_output_node = None
|
|
for dst in internal_node.out_port(out_port).get_destinations():
|
|
possible_output_node = dst.node
|
|
if possible_output_node.soft_get('type') == 'Result':
|
|
assert internal_output_node is None, 'Several Result operations on the same output port of {}'.format(
|
|
internal_node)
|
|
internal_output_node = possible_output_node
|
|
assert internal_output_node is not None
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='from', old_layer_id=internal_node_id,
|
|
old_port_id=record['internal_port_id'],
|
|
new_layer_id=internal_output_node.internal_layer_id)
|
|
|
|
del record['internal_port_id']
|
|
record['internal_layer_id'] = internal_output_node.internal_layer_id
|
|
|
|
for record in ti.back_edges:
|
|
assert 'from_layer' in record
|
|
assert 'to_layer' in record
|
|
|
|
internal_node_id = record['from_layer']
|
|
assert internal_node_id in node_map
|
|
internal_node = node_map[internal_node_id]
|
|
|
|
if internal_node.soft_get('type') != 'Result':
|
|
# this output wont get out of the body, but it is still Result and needed on non first iterations of TI
|
|
assert 'from_port' in record
|
|
out_port = TensorIterator.special_port_to_real_port(internal_node, record['from_port'], 'out')
|
|
assert out_port in internal_node.out_ports() and not internal_node.out_port(out_port).disconnected()
|
|
assert len(internal_node.out_port(out_port).get_destinations()) >= 1
|
|
|
|
internal_output_node = None
|
|
for dst in internal_node.out_port(out_port).get_destinations():
|
|
possible_output_node = dst.node
|
|
if possible_output_node.soft_get('type') == 'Result':
|
|
assert internal_output_node is None, 'Several Result operations on the same output port of {}'.format(
|
|
internal_node)
|
|
internal_output_node = possible_output_node
|
|
assert internal_output_node is not None
|
|
TensorIterator.update_back_edge_map(ti=ti, direction='from', old_layer_id=internal_node_id,
|
|
old_port_id=record['from_port'],
|
|
new_layer_id=internal_output_node.internal_layer_id)
|
|
|
|
TensorIterator.validate_maps(ti)
|
|
|
|
def substitute_ie_attrs(self, new_attrs: dict):
|
|
"""
|
|
Replace standard list of attribute in layer/data by attributes
|
|
delivered by backend_attrs
|
|
"""
|
|
|
|
port_map_attrs = [
|
|
'external_port_id',
|
|
'internal_layer_id',
|
|
'internal_port_id',
|
|
'axis',
|
|
'start',
|
|
'stride',
|
|
'end',
|
|
'part_size'
|
|
]
|
|
|
|
back_edges_attrs = [
|
|
('from-layer', 'from_layer'),
|
|
('from-port', 'from_port'),
|
|
('to-layer', 'to_layer'),
|
|
('to-port', 'to_port'),
|
|
]
|
|
|
|
gen_port_map = lambda node, port_map: self.generate_port_map_v10(node, port_map) \
|
|
if self.ir_version == 10 else self.generate_port_map(node, port_map)
|
|
|
|
gen_back_map = lambda node: self.generate_back_edges_v10(node) \
|
|
if self.ir_version == 10 else self.generate_back_edges(node)
|
|
|
|
new_attrs.update({
|
|
'IE': [(
|
|
'layer',
|
|
[('id', lambda node: node.node), 'name', 'type', 'version'],
|
|
[
|
|
('data', self.backend_attrs() + self.default_backend_attrs, []),
|
|
'@ports',
|
|
('port_map', [], [
|
|
('@list', lambda node: gen_port_map(node, node.input_port_map),
|
|
('input', port_map_attrs, [])),
|
|
('@list', lambda node: gen_port_map(node, node.output_port_map),
|
|
('output', port_map_attrs, [])),
|
|
]),
|
|
('back_edges', [], [
|
|
('@list', lambda node: gen_back_map(node), ('edge', back_edges_attrs, [])),
|
|
]),
|
|
('body', [], [('@network', 'body')]),
|
|
])]
|
|
})
|
|
|
|
@staticmethod
|
|
def find_port_id(node: Node, virtual_id, attr):
|
|
attrs = node.edge({attr: virtual_id})[2]
|
|
assert bool('in' in attrs) != bool('out' in attrs), attrs
|
|
return attrs['in' if 'in' in attrs else 'out']
|
|
|
|
@staticmethod
|
|
def find_internal_layer_id(graph: Graph, virtual_id):
|
|
internal_nodes = list(
|
|
filter(lambda d: dict_includes(d[1], {'internal_layer_id': virtual_id}), graph.nodes(data=True)))
|
|
assert len(internal_nodes) == 1, 'Nodes: {}, virtual_id: {}'.format(internal_nodes, virtual_id)
|
|
return internal_nodes[0][0]
|
|
|
|
@staticmethod
|
|
def find_internal_layer_and_port(graph: Graph, virtual_layer_id, virtual_port_id):
|
|
internal_layer_id = __class__.find_internal_layer_id(graph, virtual_layer_id)
|
|
internal_port_id = __class__.find_port_id(Node(graph, internal_layer_id), virtual_port_id, 'internal_port_id')
|
|
return internal_layer_id, internal_port_id
|
|
|
|
@staticmethod
|
|
def generate_port_map(node: Node, src_port_map):
|
|
""" Extract port_map attributes from node and node.body attributes.
|
|
|
|
It iterates over src_port_map and substitude external_port_id, internal_port_id and
|
|
internal_layer_id by real values queried from node ports and node.body attributes.
|
|
"""
|
|
result_list = []
|
|
for map_item in src_port_map:
|
|
result = dict(map_item)
|
|
assert result is not map_item
|
|
result['external_port_id'] = __class__.find_port_id(node, result['external_port_id'], 'external_port_id')
|
|
result['internal_layer_id'], result['internal_port_id'] = __class__.find_internal_layer_and_port(
|
|
node.body, result['internal_layer_id'], result['internal_port_id'])
|
|
result_list.append(result)
|
|
return result_list
|
|
|
|
@staticmethod
|
|
def generate_port_map_v10(node: Node, src_port_map):
|
|
""" Extract port_map attributes from node and node.body attributes.
|
|
|
|
It iterates over src_port_map and substitude external_port_id, internal_port_id and
|
|
internal_layer_id by real values queried from node ports and node.body attributes.
|
|
"""
|
|
result_list = []
|
|
for map_item in src_port_map:
|
|
result = dict(map_item)
|
|
assert result is not map_item
|
|
result['external_port_id'] = __class__.find_port_id(node, result['external_port_id'], 'external_port_id')
|
|
result['internal_layer_id'] = __class__.find_internal_layer_id(node.body, result['internal_layer_id'])
|
|
result_list.append(result)
|
|
return result_list
|
|
|
|
@staticmethod
|
|
def generate_back_edges(node: Node):
|
|
''' Extract back_edges attributes from node and node.body attributes. '''
|
|
result_list = []
|
|
for back_edge in node.back_edges:
|
|
result = dict(back_edge)
|
|
assert result is not back_edge
|
|
result['from_layer'], result['from_port'] = __class__.find_internal_layer_and_port(
|
|
node.body, result['from_layer'], result['from_port'])
|
|
result['to_layer'], result['to_port'] = __class__.find_internal_layer_and_port(
|
|
node.body, result['to_layer'], result['to_port'])
|
|
result_list.append(result)
|
|
return result_list
|
|
|
|
@staticmethod
|
|
def generate_back_edges_v10(node: Node):
|
|
''' Extract back_edges attributes from node and node.body attributes. '''
|
|
result_list = []
|
|
for back_edge in node.back_edges:
|
|
result = dict(back_edge)
|
|
assert result is not back_edge
|
|
result['from_layer'] = __class__.find_internal_layer_id(node.body, result['from_layer'])
|
|
result['to_layer'] = __class__.find_internal_layer_id(node.body, result['to_layer'])
|
|
result_list.append(result)
|
|
return result_list
|
|
|
|
@staticmethod
|
|
def infer(node: Node):
|
|
return
|
|
raise Error('TensorIterator.infer is not implemented. '
|
|
'Do not insert TensorIterator before middle-end in Model Optimizer')
|
|
|
|
@staticmethod
|
|
def ti_type_infer(node):
|
|
if node.graph.graph['cmd_params'].generate_experimental_IR_V10:
|
|
TensorIterator.ti_type_infer_v10(node)
|
|
else:
|
|
TensorIterator._ti_type_infer(node)
|
|
|
|
@staticmethod
|
|
def _ti_type_infer(node):
|
|
from mo.middle.passes.infer import type_infer
|
|
ti_graph = node.body
|
|
|
|
# create fake const node to make type inference work correctly for all TI input nodes
|
|
fake_input_const_nodes = []
|
|
for port_map in __class__.generate_port_map(node, node.input_port_map):
|
|
internal_input_data = Node(ti_graph, port_map['internal_layer_id']).in_node(port_map['internal_port_id'])
|
|
if len(internal_input_data.in_nodes()) == 0:
|
|
input_producer_port = node.in_port(port_map['external_port_id']).get_connection().get_source()
|
|
input_type = input_producer_port.get_data_type()
|
|
const_node = Const(ti_graph, {'name': 'fake_const_',
|
|
'value': np.ones([1], dtype=input_type)}).create_node()
|
|
fake_input_const_nodes.append(const_node)
|
|
ti_graph.create_edge(const_node, internal_input_data)
|
|
|
|
# create const Op node for constant data nodes inside the TI
|
|
for data_node in ti_graph.get_data_nodes(has_value=True):
|
|
if len(data_node.in_nodes()) == 0:
|
|
const_node = Const(ti_graph, {'name': 'const_', 'value': data_node.value}).create_node()
|
|
fake_input_const_nodes.append(const_node)
|
|
ti_graph.create_edge(const_node, data_node)
|
|
|
|
type_infer(ti_graph)
|
|
|
|
# propagate data types to the TI output ports
|
|
output_port_map = __class__.generate_port_map(node, node.output_port_map)
|
|
for port_map in output_port_map:
|
|
internal_output_port = Node(ti_graph, port_map['internal_layer_id']).out_port(port_map['internal_port_id'])
|
|
ti_output_port = node.out_port(port_map['external_port_id'])
|
|
ti_output_port.set_data_type(internal_output_port.get_data_type())
|
|
|
|
ti_graph.remove_nodes_from([node.id for node in fake_input_const_nodes])
|
|
|
|
@staticmethod
|
|
def ti_type_infer_v10(node):
|
|
from mo.middle.passes.infer import type_infer
|
|
ti_graph = node.body
|
|
|
|
for record in node.input_port_map:
|
|
internal_node = get_internal_node_by_layer_id(node, record['internal_layer_id'])
|
|
assert internal_node.soft_get('type') == 'Parameter', internal_node.soft_get('type')
|
|
|
|
real_external_port_idx = TensorIterator.special_port_to_real_port(node, record['external_port_id'])
|
|
external_data_type = node.in_port(real_external_port_idx).get_connection().get_source().get_data_type()
|
|
internal_node.data_type = external_data_type
|
|
|
|
fake_input_const_nodes = []
|
|
# create fake const node to make type inference work correctly for all TI input nodes
|
|
for data_node in ti_graph.get_data_nodes(has_value=True):
|
|
if len(data_node.in_nodes()) == 0:
|
|
const_node = Const(ti_graph, {'name': 'const_', 'value': data_node.value}).create_node()
|
|
fake_input_const_nodes.append(const_node)
|
|
ti_graph.create_edge(const_node, data_node)
|
|
|
|
type_infer(ti_graph)
|
|
|
|
# propagate data types to the TI output ports
|
|
for record in node.output_port_map:
|
|
internal_node = get_internal_node_by_layer_id(node, record['internal_layer_id'])
|
|
assert internal_node.soft_get('type') == 'Result', internal_node.soft_get('type')
|
|
|
|
internal_data_type = internal_node.in_port(0).get_data_type()
|
|
real_external_port_idx = TensorIterator.special_port_to_real_port(node, record['external_port_id'], 'out')
|
|
node.out_port(real_external_port_idx).set_data_type(internal_data_type)
|
|
|
|
ti_graph.remove_nodes_from([node.id for node in fake_input_const_nodes])
|
|
|
|
|
|
def get_internal_node_by_layer_id(ti, internal_layer_id):
|
|
suitable_nodes = ti.body.get_op_nodes(internal_layer_id=internal_layer_id)
|
|
assert len(suitable_nodes) == 1, \
|
|
'Expected 1 node with `internal_layer_id`={}, {} found'.format(internal_layer_id, len(suitable_nodes))
|
|
return suitable_nodes[0]
|
|
|
|
|
|
# Some utils for TI
|
|
def _get_internal_idxs_to_names_dict(graph: Graph, ports_type='in'):
|
|
"""
|
|
Create mapping from (internal_layer_id, internal_port_id) to layer id in body of TensorIterator.
|
|
"""
|
|
v10 = graph.graph['cmd_params'].generate_experimental_IR_V10
|
|
mapping = {}
|
|
ordered_nodes = graph.pseudo_topological_sort()
|
|
for node in ordered_nodes:
|
|
if node.kind == 'op' and node.has_valid('internal_layer_id'):
|
|
if v10:
|
|
mapping[node.internal_layer_id] = node.id
|
|
else:
|
|
edges = node.out_edges() if ports_type == 'out' else node.in_edges()
|
|
for port in edges:
|
|
if 'internal_port_id' in edges[port]:
|
|
internal_port = edges[port]['internal_port_id']
|
|
mapping[(node.internal_layer_id, internal_port)] = node.out_node(port).id if ports_type == 'out' \
|
|
else node.in_node(port).id
|
|
return mapping
|
|
|
|
|
|
def _get_internal_output_node_id(graph: Graph, ti_node_id: str, external_port: int):
|
|
node = Node(graph, ti_node_id)
|
|
outputs = node['output_port_map']
|
|
v10 = graph.graph['cmd_params'].generate_experimental_IR_V10
|
|
mapping = _get_internal_idxs_to_names_dict(node['body'], 'out')
|
|
for out in outputs:
|
|
if out['external_port_id'] == external_port:
|
|
if v10:
|
|
return mapping[out['internal_layer_id']]
|
|
else:
|
|
return mapping[(out['internal_layer_id'], out['internal_port_id'])]
|
|
|
|
|
|
def _get_internal_input_node_id(graph: Graph, ti_node_id: str, external_port: int):
|
|
node = Node(graph, ti_node_id)
|
|
inputs = node['input_port_map']
|
|
v10 = graph.graph['cmd_params'].generate_experimental_IR_V10
|
|
mapping = _get_internal_idxs_to_names_dict(node['body'], 'in')
|
|
for inp in inputs:
|
|
if inp['external_port_id'] == external_port:
|
|
if v10:
|
|
return mapping[inp['internal_layer_id']]
|
|
else:
|
|
return mapping[(inp['internal_layer_id'], inp['internal_port_id'])]
|