ONNX Loop operation support (#2756)
* Generate TensorIterator without back edges from TensorFlow models * Added a check in the MarkSubgraphsWithCorrectLayout to not fail when port is not connected * Updated the 'protobuf2nx' to consume the graph protobuf message * Cleanup TI from the IRv7 specific code * Do not run some front transformations recursively * Draft support for the ONNX Loop operation when 'cond' = True * LoopToTI transformation changes * Added draft of Loop operation and parser for ONNX Loop operation body * Updated Loop body parser + added shape and type infer for the Loop operation * Fixes for ONNX Loop operation parser * Moved Loop parsing to Loop op extractor. Added generation of external edges for the Loop body ops * Added support for ThresholdedRelu using decomposition * Added support for Min ONNX operation * Draft fixes for port_map generation for the Loop * Rename transformation file and fix BOM * Fixed shape inference for Loop scan outputs (axis is not None) * Fixed shape inference for ONNX Loop operation * Refactor checks in the TensorIteratorMerge transformation * Code refactoring. Enabled commented transformations * Documentation update for ONNX Loop, ThresholdedRelu and Min * Fixed typo in the Loop front transformation where execution condition input is connected. Other refactorings * Fixed in the Loop extractor * Added printing 'internal_layer_id' attribute in the graph dumper * Updated calculation of iterations number for the Loop * Added missing code * Fixed output port shapes and types generation for Loop operation * Update function names and variable names in the Loop operation * Fixed type inference for iteration count input * Added removal of input/output ports of the Loop if they are not used * Fixed renumbering Loop operations input/output ports to keep mandatory * Fixed ThresholdedReluDecomposition transformation * Updated MO IR Reader to know about Loop operation. But it is still not supported by the MO IR Reader * Added unit test for Slice op shape infer (reverse the sequence of elements) * Reverted changes in the ONNX loader function call to protobuf2nx * Enable Reshape0DToSqueeze transformation recursively * Refactored Loop operation support implementation * Changed ThresholdedReluDecomposition to generate Const with shape [1] instead of scalar * Code style and wording fixes * Restored accidentally removed 'return' statement in the TI shape infer function * Fixed comments * Fixed comment Co-authored-by: Evgeny Lazarev <elazarev.nnov@gmail.com>
This commit is contained in:
1 parent
f6842863c7
commit
cabf8d8534
28 files changed
+1022
-110
No files matched your search
@@ -357,9 +357,11 @@ Standard ONNX\* operators:
|
||||
| LogicalAnd | No |
|
||||
| LogicalOr | No |
|
||||
| LogSoftmax | No |
|
||||
| Loop | No |
|
||||
| MatMul | No |
|
||||
| MaxPool | No |
|
||||
| MeanVarianceNormalization | Reduction over the batch dimension is not supported, reduction over all dimensions except batch and channel ones is obligatory |
|
||||
| Min | No |
|
||||
| Mul | No |
|
||||
| Neg | No |
|
||||
| NonMaxSuppression | No |
|
||||
@@ -406,6 +408,7 @@ Standard ONNX\* operators:
|
||||
| Sum | No |
|
||||
| Tan | No |
|
||||
| Tanh | No |
|
||||
| ThresholdedRelu | No |
|
||||
| TopK | No |
|
||||
| Transpose | No |
|
||||
| Unsqueeze | No |
|
||||
|
||||
@@ -85,7 +85,7 @@ Loop operation description in the IR also has several special sections: `body`,
|
||||
* **Port map attributes**:
|
||||
|
||||
* *external_port_id*
|
||||
* **Description**: *external_port_id* is a port ID of the `Loop` operation.
|
||||
* **Description**: *external_port_id* is a port ID of the `Loop` operation. The value `-1` means that the body node is not connected to the `Loop` operation.
|
||||
* **Range of values**: IDs of the *Loop* outputs
|
||||
* **Type**: `int`
|
||||
* **Default value**: None
|
||||
|
||||
@@ -266,6 +266,8 @@ extensions/front/onnx/hard_sigmoid_ext.py
|
||||
extensions/front/onnx/image_scaler_ext.py
|
||||
extensions/front/onnx/instance_normalization_ext.py
|
||||
extensions/front/onnx/logsoftmaxONNX_to_logsoftmax.py
|
||||
extensions/front/onnx/loop_ext.py
|
||||
extensions/front/onnx/LoopNormalize.py
|
||||
extensions/front/onnx/lp_normalization_ext.py
|
||||
extensions/front/onnx/lrn_ext.py
|
||||
extensions/front/onnx/lstm_ext.py
|
||||
@@ -476,6 +478,7 @@ extensions/front/tf/yolo_v2_voc.json
|
||||
extensions/front/tf/yolo_v3.json
|
||||
extensions/front/tf/yolo_v3_tiny.json
|
||||
extensions/front/tf/yolo_v3_voc.json
|
||||
extensions/front/ThresholdedReluDecomposition.py
|
||||
extensions/front/TopKNormalize.py
|
||||
extensions/front/transformations_config.py
|
||||
extensions/front/TransposeOrderNormalizer.py
|
||||
@@ -633,6 +636,7 @@ extensions/ops/instance_normalization.py
|
||||
extensions/ops/interp.py
|
||||
extensions/ops/interpolate.py
|
||||
extensions/ops/LookupTableInsert.py
|
||||
extensions/ops/loop.py
|
||||
extensions/ops/LSTM.py
|
||||
extensions/ops/lstm_cell.py
|
||||
extensions/ops/lstm_sequence.py
|
||||
|
||||
@@ -31,7 +31,6 @@ class Reshape0DToSqueeze(BackReplacementPattern):
|
||||
overcome issue the IE doesn't 1D constants with value [0] which is generated for the Reshape to OD case.
|
||||
"""
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
force_shape_inference = True
|
||||
|
||||
def run_before(self):
|
||||
|
||||
@@ -24,6 +24,7 @@ from mo.graph.graph import Graph, Node
|
||||
|
||||
class ChangePlaceholderTypes(FrontReplacementPattern):
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
|
||||
@staticmethod
|
||||
def is_node_casts_to_float_or_shapeof(node: Node):
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
from extensions.ops.Cast import Cast
|
||||
from extensions.ops.elementwise import Greater, Mul
|
||||
from mo.front.common.partial_infer.utils import float_array
|
||||
from mo.front.common.replacement import FrontReplacementPattern
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph, rename_nodes
|
||||
from mo.middle.passes.convert_data_type import data_type_str_to_np
|
||||
|
||||
|
||||
class ThresholdedReluDecomposition(FrontReplacementPattern):
|
||||
"""
|
||||
ThresholdedRelu(x, alpha) = x ? x > alpha : 0
|
||||
|
||||
is replaced with
|
||||
|
||||
ThresholdedRelu(x, alpha) = Mul(x, Cast(Greater(x, alpha), type=float))
|
||||
"""
|
||||
enabled = True
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
for node in graph.get_op_nodes(op='ThresholdedRelu'):
|
||||
name = node.soft_get('name', node.id)
|
||||
|
||||
greater = create_op_with_const_inputs(graph, Greater, {1: float_array([node.alpha])})
|
||||
greater.in_port(0).connect(node.in_port(0).get_source())
|
||||
float_greater = Cast(graph,
|
||||
{'dst_type': data_type_str_to_np(graph.graph['cmd_params'].data_type)}).create_node()
|
||||
greater.out_port(0).connect(float_greater.in_port(0))
|
||||
|
||||
mul = Mul(graph, {}).create_node()
|
||||
node.out_port(0).get_connection().set_source(mul.out_port(0))
|
||||
mul.in_port(0).connect(node.in_port(0).get_source())
|
||||
mul.in_port(1).connect(float_greater.out_port(0))
|
||||
|
||||
rename_nodes([(node, name + '/TBR'), (mul, name)])
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Copyright (C) 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 unittest
|
||||
|
||||
from extensions.front.ThresholdedReluDecomposition import ThresholdedReluDecomposition
|
||||
from mo.front.common.partial_infer.utils import float_array
|
||||
from mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
from mo.utils.unittest.graph import build_graph, const
|
||||
|
||||
nodes_attributes = {
|
||||
'parameter': {'shape': None, 'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
|
||||
'trelu': {'type': None, 'kind': 'op', 'op': 'ThresholdedRelu', 'alpha': 0.75, 'name': 'my_trelu'},
|
||||
'result': {'type': 'Result', 'value': None, 'kind': 'op', 'op': 'Result'},
|
||||
|
||||
'cast': {'type': 'Convert', 'kind': 'op', 'op': 'Cast'},
|
||||
'greater': {'type': 'Greater', 'kind': 'op', 'op': 'Greater'},
|
||||
'mul': {'type': 'Multiply', 'kind': 'op', 'op': 'Mul', 'name': 'my_trelu'},
|
||||
'squeeze2': {'type': 'Squeeze', 'kind': 'op', 'op': 'Squeeze'},
|
||||
**const('alpha', float_array([0.75])),
|
||||
}
|
||||
|
||||
|
||||
class ThresholdedReluDecompositionTest(unittest.TestCase):
|
||||
def test_trelu(self):
|
||||
graph = build_graph(nodes_attributes,
|
||||
[('parameter', 'trelu', {'in': 0, 'out': 0}),
|
||||
('trelu', 'result', {'in': 0, 'out': 0}),
|
||||
], nodes_with_edges_only=True)
|
||||
|
||||
graph_ref = build_graph(nodes_attributes,
|
||||
[('parameter', 'greater', {'in': 0, 'out': 0}),
|
||||
('alpha', 'greater', {'in': 1, 'out': 0}),
|
||||
('greater', 'cast', {'in': 0, 'out': 0}),
|
||||
('parameter', 'mul', {'in': 0, 'out': 0}),
|
||||
('cast', 'mul', {'in': 1, 'out': 0}),
|
||||
('mul', 'result', {'in': 0, 'out': 0}),
|
||||
], nodes_with_edges_only=True)
|
||||
|
||||
graph.graph['layout'] = 'NCHW'
|
||||
graph.stage = 'front'
|
||||
|
||||
ThresholdedReluDecomposition().find_and_replace_pattern(graph)
|
||||
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'result', check_op_attrs=True)
|
||||
self.assertTrue(flag, resp)
|
||||
self.assertTrue(len(graph.get_op_nodes(name='my_trelu')) == 1 and
|
||||
graph.get_op_nodes(name='my_trelu')[0].op == 'Mul')
|
||||
@@ -15,7 +15,7 @@
|
||||
"""
|
||||
|
||||
|
||||
from extensions.ops.elementwise import Add, Maximum, Mul
|
||||
from extensions.ops.elementwise import Add, Maximum, Minimum, Mul
|
||||
from mo.front.common.replacement import FrontReplacementOp
|
||||
from mo.graph.graph import Node, Graph
|
||||
|
||||
@@ -30,6 +30,7 @@ class EltwiseNReplacement(FrontReplacementOp):
|
||||
|
||||
op_to_class_map = {
|
||||
'sum': Add,
|
||||
'min': Minimum,
|
||||
'max': Maximum,
|
||||
'mul': Mul,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class FreezePlaceholderValue(FrontReplacementSubgraph):
|
||||
a string and casts it to actual node data type
|
||||
"""
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
graph_condition = [lambda graph: graph.graph['freeze_placeholder'] is not None]
|
||||
|
||||
def run_after(self):
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
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 numpy as np
|
||||
|
||||
from extensions.front.pass_separator import FrontStart
|
||||
from extensions.front.restore_ports import RestorePorts
|
||||
from extensions.ops.loop import Loop
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.common.replacement import FrontReplacementSubgraph
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph, Node
|
||||
from mo.ops.const import Const
|
||||
from mo.ops.unsqueeze import Unsqueeze
|
||||
|
||||
|
||||
class ONNXLoopNormalize(FrontReplacementSubgraph):
|
||||
enabled = True
|
||||
|
||||
def run_before(self):
|
||||
return [FrontStart]
|
||||
|
||||
def run_after(self):
|
||||
return [RestorePorts]
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
for node in graph.get_op_nodes(op='Loop'):
|
||||
self.normalize_body_graph(node)
|
||||
|
||||
@staticmethod
|
||||
def normalize_body_graph(loop_node: Node):
|
||||
loop_name = loop_node.soft_get('name', loop_node.id)
|
||||
# connect "trip count" input if it is not connected with default value "Infinity" (-1)
|
||||
if not loop_node.is_in_port_connected(0):
|
||||
loop_node.add_input_port(0, skip_if_exist=True)
|
||||
Const(loop_node.graph, {'name': loop_name + '/trip_count', 'value': int64_array(-1)}).\
|
||||
create_node().out_port(0).connect(loop_node.in_port(0))
|
||||
|
||||
# connect "execution condition" input if it is not connected with default value True
|
||||
if not loop_node.is_in_port_connected(1):
|
||||
loop_node.add_input_port(1, skip_if_exist=True)
|
||||
Const(loop_node.graph, {'name': loop_name + '/execution_cond', 'value': np.array(True, dtype=np.bool)}).\
|
||||
create_node().out_port(0).connect(loop_node.in_port(1))
|
||||
|
||||
# scan output need Unsqueeze over axis 0
|
||||
for record in loop_node.output_port_map:
|
||||
body_node = Loop.get_body_node_by_internal_id(loop_node, record['internal_layer_id'])
|
||||
assert body_node is not None
|
||||
assert body_node.soft_get('type') == 'Result'
|
||||
|
||||
if record['axis'] is not None:
|
||||
unsqueeze = create_op_with_const_inputs(loop_node.body, Unsqueeze, {1: int64_array([0])})
|
||||
body_node.in_port(0).get_connection().insert_node(unsqueeze)
|
||||
|
||||
Loop.normalize_input_output_ports(loop_node)
|
||||
@@ -159,6 +159,17 @@ class FloorExtractor(FrontExtractorOp):
|
||||
return cls.enabled
|
||||
|
||||
|
||||
class ThresholdedReluExtractor(FrontExtractorOp):
|
||||
op = 'ThresholdedRelu'
|
||||
enabled = True
|
||||
|
||||
@classmethod
|
||||
def extract(cls, node):
|
||||
alpha = onnx_attr(node, 'alpha', 'f', default=1.0)
|
||||
ThresholdedRelu.update_node_stat(node, {'alpha': alpha})
|
||||
return cls.enabled
|
||||
|
||||
|
||||
class LeakyReLUExtractor(FrontExtractorOp):
|
||||
op = 'LeakyRelu'
|
||||
enabled = True
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from extensions.ops.elementwise import Add, Sub, Mul, Div, Pow, Less, Equal, Greater, \
|
||||
LogicalAnd, LogicalOr, LogicalXor, Round
|
||||
from extensions.ops.elementwise import Add, Sub, Mul, Div, Pow, Less, Equal, Greater, LogicalAnd, LogicalOr, LogicalXor, \
|
||||
Round
|
||||
from mo.front.extractor import FrontExtractorOp
|
||||
from mo.front.onnx.extractors.utils import onnx_attr
|
||||
from mo.graph.graph import Node
|
||||
from mo.ops.eltwise_n import EltwiseNAdd, EltwiseNMax
|
||||
from mo.ops.eltwise_n import EltwiseNAdd, EltwiseNMax, EltwiseNMin
|
||||
from mo.ops.power import AttributedPower
|
||||
|
||||
|
||||
@@ -130,6 +130,15 @@ class MaxExtractor(FrontExtractorOp):
|
||||
return cls.enabled
|
||||
|
||||
|
||||
class MinExtractor(FrontExtractorOp):
|
||||
op = 'Min'
|
||||
enabled = True
|
||||
|
||||
@classmethod
|
||||
def extract(cls, node: Node):
|
||||
EltwiseNMin.update_node_stat(node)
|
||||
return cls.enabled
|
||||
|
||||
class EqualExtractor(FrontExtractorOp):
|
||||
op = 'Equal'
|
||||
enabled = True
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
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 copy
|
||||
import logging as log
|
||||
|
||||
from extensions.ops.loop import Loop
|
||||
from extensions.ops.parameter import Parameter
|
||||
from mo.front.common.register_custom_ops import check_for_duplicates
|
||||
from mo.front.extractor import FrontExtractorOp
|
||||
from mo.front.extractor import extract_node_attrs
|
||||
from mo.front.onnx.extractor import onnx_op_extractor, onnx_op_extractors
|
||||
from mo.front.onnx.extractors.utils import onnx_attr
|
||||
from mo.front.onnx.loader import node_id, add_initializers_and_inputs_to_graph
|
||||
from mo.graph.graph import Graph, Node, add_opoutput
|
||||
from mo.utils.error import Error
|
||||
|
||||
|
||||
def connect_body_output(loop_node: Node, loop_output_port_idx: int, internal_result: Node, axis: [int, None] = None,
|
||||
start: [int, None] = None, end: [int, None] = None, stride: [int, None] = None,
|
||||
part_size: [int, None] = None):
|
||||
assert loop_node.soft_get('op') == 'Loop'
|
||||
assert internal_result.soft_get('op') == 'Result'
|
||||
assert internal_result.id in loop_node.body
|
||||
|
||||
loop_node.output_port_map.append({'axis': axis, 'stride': stride, 'part_size': part_size, 'start': start,
|
||||
'end': end, 'external_port_id': loop_output_port_idx,
|
||||
'internal_layer_id': internal_result['internal_layer_id']})
|
||||
|
||||
|
||||
def connect_body_input(loop_node: Node, loop_input_port_idx: int, body_parameter: Node,
|
||||
axis: [int, None] = None, start: [int, None] = None, end: [int, None] = None,
|
||||
stride: [int, None] = None, part_size: [int, None] = None):
|
||||
assert loop_node.soft_get('op') == 'Loop'
|
||||
assert body_parameter.soft_get('op') == 'Parameter'
|
||||
assert body_parameter.id in loop_node.body
|
||||
|
||||
loop_node.input_port_map.append({'axis': axis, 'stride': stride, 'part_size': part_size, 'start': start,
|
||||
'end': end, 'external_port_id': loop_input_port_idx,
|
||||
'internal_layer_id': body_parameter['internal_layer_id']})
|
||||
|
||||
|
||||
class LoopExtractor(FrontExtractorOp):
|
||||
op = 'Loop'
|
||||
enabled = True
|
||||
|
||||
@classmethod
|
||||
def extract(cls, loop_node):
|
||||
Loop.update_node_stat(loop_node, {})
|
||||
|
||||
body_graph_proto = onnx_attr(loop_node, 'body', 'g', None)
|
||||
main_graph = loop_node.graph
|
||||
|
||||
# create a Graph object for the body and take graph attributes from the main graph
|
||||
body_graph = Graph()
|
||||
main_graph_attrs_copy = copy.deepcopy(main_graph.graph)
|
||||
del main_graph_attrs_copy['tensor_mapping']
|
||||
body_graph.graph.update(main_graph_attrs_copy)
|
||||
loop_node['body'] = body_graph
|
||||
|
||||
# maps a tensor name to a node produced it and the node port: str -> (node_id, node_port)
|
||||
data_nodes_map = {}
|
||||
body_graph.graph['tensor_mapping'] = data_nodes_map # save mapping for possible Loop inside the Loop
|
||||
|
||||
body_parameters = add_initializers_and_inputs_to_graph(body_graph, body_graph_proto, data_nodes_map)
|
||||
|
||||
external_edges = [] # (src_node, src_out_port), dest_body_parameter_node
|
||||
additional_params = {} # (src_node, src_out_port) -> parameter_node (for manually added Parameters)
|
||||
# Go through all nodes in the original model order because data nodes are defined on-the-fly and order matters
|
||||
for pb_node in body_graph_proto.node:
|
||||
# create an NX node
|
||||
id = body_graph.unique_id(node_id(pb_node))
|
||||
body_graph.add_node(id, pb=pb_node, kind='op')
|
||||
|
||||
# add incoming edges based on data_nodes_map
|
||||
for dst_port, inp in enumerate(pb_node.input):
|
||||
# should add edge inp --> id
|
||||
if inp not in data_nodes_map:
|
||||
if inp == '':
|
||||
# input is omitted; most likely it corresponds to an optional input for an operator
|
||||
continue
|
||||
elif inp in main_graph.graph['tensor_mapping']:
|
||||
log.debug('The edge between outer and inner graphs detected: {} -> {}'.format(inp, id))
|
||||
if main_graph.graph['tensor_mapping'][inp] not in additional_params:
|
||||
# create new Parameter body node and connect the body node with the outer graph using it
|
||||
param_id = str(inp)
|
||||
body_graph.add_node(param_id, kind='op', op='Parameter', name=param_id, pb=None, shape=None)
|
||||
parameter_node = Node(body_graph, param_id)
|
||||
# need to manually update necessary attrs for the node because extractor will not be called
|
||||
# for it because the node does not have .pb attribute
|
||||
Parameter.update_node_stat(parameter_node, {})
|
||||
external_edges.append((main_graph.graph['tensor_mapping'][inp], parameter_node))
|
||||
src_id, src_port = param_id, 0
|
||||
additional_params[main_graph.graph['tensor_mapping'][inp]] = parameter_node
|
||||
else:
|
||||
src_id, src_port = additional_params[main_graph.graph['tensor_mapping'][inp]].id, 0
|
||||
else:
|
||||
raise Error('Reference to "{}" is not satisfied. A node refer not existing data tensor. ONNX '
|
||||
'model is not consistent. Protobuf fragment: {}', inp, pb_node)
|
||||
else:
|
||||
src_id, src_port = data_nodes_map[inp]
|
||||
|
||||
assert (body_graph.has_node(src_id))
|
||||
edge_attrs = {
|
||||
'out': src_port,
|
||||
'in': dst_port,
|
||||
'name': inp,
|
||||
'fw_tensor_debug_info': [(inp, inp)],
|
||||
'in_attrs': ['in', 'name'],
|
||||
'out_attrs': ['out', 'name'],
|
||||
'data_attrs': ['fw_tensor_debug_info']
|
||||
}
|
||||
body_graph.add_edge(src_id, id, **edge_attrs)
|
||||
|
||||
# add outgoing edges to data_nodes_map
|
||||
for src_port, out in enumerate(pb_node.output):
|
||||
if out in data_nodes_map:
|
||||
log.debug("Detected reuse of blob {}.".format(out))
|
||||
data_nodes_map[out] = (id, src_port)
|
||||
|
||||
body_results = []
|
||||
for output in body_graph_proto.output:
|
||||
tensor_name = str(output.name)
|
||||
node_name, output_port = data_nodes_map[tensor_name]
|
||||
assert body_graph.has_node(node_name), 'The body graph does not contain output with name "{}"'.format(
|
||||
node_name)
|
||||
body_results.append(Node(body_graph, add_opoutput(body_graph, node_name, output_port, False)))
|
||||
|
||||
# add 'internal_layer_id' attribute which is a must have attribute for the loop body node
|
||||
for idx, body_node in enumerate(body_graph.get_op_nodes()):
|
||||
body_node['internal_layer_id'] = idx
|
||||
|
||||
loop_carried_dependencies_count = len(body_graph_proto.input) - 2
|
||||
scan_outputs_count = len(body_graph_proto.output) - 1 - loop_carried_dependencies_count
|
||||
|
||||
# Loop inputs:
|
||||
# 0 - trip count
|
||||
# 1 - execution condition
|
||||
# 2 .. - loop carried dependencies
|
||||
|
||||
# Loop outputs:
|
||||
# 0 .. loop_carried_dependencies_count - 1 - loop carried dependencies
|
||||
# loop_carried_dependencies_count .. - scan outputs
|
||||
|
||||
# Body inputs:
|
||||
# 0 - iteration number
|
||||
# 1 - execution condition
|
||||
# 2 .. - loop carried dependencies
|
||||
|
||||
# Body outputs:
|
||||
# 0 - execution condition
|
||||
# 1 .. loop_carried_dependencies_count - loop carried dependencies
|
||||
# loop_carried_dependencies_count + 1 .. - scan outputs
|
||||
|
||||
body_graph.stage = 'front'
|
||||
# some of the inputs/outputs may not be connected but the normalization transformation will take care of it
|
||||
# connection Loop body nodes with external input edges
|
||||
next_loop_input_port_idx = sorted(loop_node.in_edges().keys())[-1] + 1
|
||||
for (src_node, src_port), body_node in external_edges:
|
||||
main_graph.add_edge(src_node, loop_node.id, **{'out': src_port,
|
||||
'in': next_loop_input_port_idx,
|
||||
'name': src_node,
|
||||
'fw_tensor_debug_info': [(src_node, src_node)],
|
||||
'in_attrs': ['in', 'name'],
|
||||
'out_attrs': ['out', 'name'],
|
||||
'data_attrs': ['fw_tensor_debug_info']}
|
||||
)
|
||||
connect_body_input(loop_node, next_loop_input_port_idx, body_node)
|
||||
next_loop_input_port_idx += 1
|
||||
|
||||
# mark current iteration input Parameter node
|
||||
Loop.mark_current_iteration_parameter_node(loop_node, body_parameters[0])
|
||||
|
||||
# connect initial value for "execution condition" input of the loop
|
||||
connect_body_input(loop_node, 1, body_parameters[1])
|
||||
# add back edge with "execution condition"
|
||||
Loop.add_back_edge(loop_node, body_parameters[1], body_results[0])
|
||||
# mark "execution condition" Result node
|
||||
Loop.mark_execution_condition_result_node(loop_node, body_results[0])
|
||||
|
||||
# connect initial value for "loop carried" dependencies variables
|
||||
for idx in range(loop_carried_dependencies_count):
|
||||
connect_body_input(loop_node, idx + 2, body_parameters[idx + 2])
|
||||
# add back edge for "loop carried" dependencies variables
|
||||
for idx in range(loop_carried_dependencies_count):
|
||||
Loop.add_back_edge(loop_node, body_parameters[idx + 2], body_results[idx + 1])
|
||||
# connect final value for "loop carried" dependencies variables
|
||||
for idx in range(loop_carried_dependencies_count):
|
||||
connect_body_output(loop_node, idx, body_results[idx + 1])
|
||||
|
||||
# connect "scan outputs" and mark axis for concatenation
|
||||
for idx in range(loop_carried_dependencies_count, loop_carried_dependencies_count + scan_outputs_count):
|
||||
connect_body_output(loop_node, idx, body_results[idx + 1], axis=0)
|
||||
|
||||
# run function to parse body nodes attributes similar to the main graph
|
||||
extract_node_attrs(body_graph, lambda node: onnx_op_extractor(node, check_for_duplicates(onnx_op_extractors)))
|
||||
return cls.enabled
|
||||
@@ -20,6 +20,7 @@ from mo.middle.passes.infer import override_batch
|
||||
|
||||
class OverrideBatch(FrontReplacementPattern):
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
override_batch(graph, graph.graph['cmd_params'].batch)
|
||||
@@ -32,6 +32,7 @@ from mo.utils.utils import refer_to_faq_msg
|
||||
|
||||
class ONNXLoader(Loader):
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
|
||||
def load(self, graph: Graph):
|
||||
argv = graph.graph['cmd_params']
|
||||
|
||||
@@ -51,14 +51,15 @@ class MarkSubGraphsWithCorrectLayout(MiddleReplacementPattern):
|
||||
|
||||
@staticmethod
|
||||
def get_input_nodes(node: Node):
|
||||
return [src_port.get_source().node for src_port in node.in_ports().values()]
|
||||
return [src_port.get_source().node for src_port in node.in_ports().values() if not src_port.disconnected()]
|
||||
|
||||
@staticmethod
|
||||
def get_output_nodes(node: Node):
|
||||
result = []
|
||||
for out_port in node.out_ports().values():
|
||||
for dest_port in out_port.get_destinations():
|
||||
result.append(dest_port.node)
|
||||
if not out_port.disconnected():
|
||||
for dest_port in out_port.get_destinations():
|
||||
result.append(dest_port.node)
|
||||
return result
|
||||
|
||||
def bfs(self, start_nodes: list, visited: set, condition: callable = None, forward: bool = True):
|
||||
|
||||
@@ -20,6 +20,7 @@ from mo.middle.replacement import MiddleReplacementPattern
|
||||
|
||||
class PartialInfer(MiddleReplacementPattern):
|
||||
enabled = True
|
||||
run_not_recursively = True
|
||||
|
||||
def run_after(self):
|
||||
from extensions.front.create_tensor_nodes import CreateTensorNodes
|
||||
|
||||
@@ -120,21 +120,22 @@ class TensorIteratorMerge(MiddleReplacementPattern):
|
||||
def replace_pattern(graph, match: dict):
|
||||
# Here we will found all parts of TI: condition, inputs/outputs, back edges, body and create TensorIterator Op
|
||||
# and make all checks needed for TensorIteator work
|
||||
cond_data = match['condition'].out_node(0)
|
||||
time_data = match['condition'].out_node(1) if len(match['condition'].out_nodes()) > 1 else None
|
||||
cond_data = match['condition'].out_node(0) if not match['condition'].out_port(0).disconnected() else None
|
||||
time_data = match['condition'].out_node(1) if len(match['condition'].out_nodes()) >= 1 else None
|
||||
name = match['condition'].name
|
||||
|
||||
back_edges = []
|
||||
inputs = []
|
||||
outputs = []
|
||||
|
||||
for node in cond_data.out_nodes():
|
||||
if node['kind'] == 'op' and node['op'] == 'TensorIteratorBackEdge':
|
||||
back_edges.append(node.id)
|
||||
elif node['kind'] == 'op' and node['op'] == 'TensorIteratorInput':
|
||||
inputs.append(node.id)
|
||||
elif node['kind'] == 'op' and node['op'] == 'TensorIteratorOutput':
|
||||
outputs.append(node.id)
|
||||
if cond_data is not None:
|
||||
for node in cond_data.out_nodes():
|
||||
if node['kind'] == 'op' and node['op'] == 'TensorIteratorBackEdge':
|
||||
back_edges.append(node.id)
|
||||
elif node['kind'] == 'op' and node['op'] == 'TensorIteratorInput':
|
||||
inputs.append(node.id)
|
||||
elif node['kind'] == 'op' and node['op'] == 'TensorIteratorOutput':
|
||||
outputs.append(node.id)
|
||||
|
||||
if time_data is not None:
|
||||
for node in time_data.out_nodes():
|
||||
@@ -147,12 +148,14 @@ class TensorIteratorMerge(MiddleReplacementPattern):
|
||||
assert False
|
||||
condition = match['condition']
|
||||
tensor_sequence_length = condition.in_node(0)
|
||||
graph.remove_nodes_from([condition.id, cond_data.id, tensor_sequence_length.id])
|
||||
if time_data is not None:
|
||||
graph.remove_nodes_from([time_data.id])
|
||||
|
||||
nodes_to_remove = [n.id for n in (condition, cond_data, time_data, tensor_sequence_length) if n is not None]
|
||||
graph.remove_nodes_from(nodes_to_remove)
|
||||
|
||||
body_nodes, extra_inputs = get_body(graph, inputs, outputs)
|
||||
body_nodes = list(set(body_nodes) - set([cond_data]))
|
||||
|
||||
if cond_data is not None:
|
||||
body_nodes = list(set(body_nodes) - set([cond_data]))
|
||||
|
||||
inputs += extra_inputs
|
||||
|
||||
@@ -281,11 +284,7 @@ class TensorIteratorMerge(MiddleReplacementPattern):
|
||||
assert not ext_inp['internal_data_id'].has_valid('value')
|
||||
new_input_data = Op._create_data_node(body, ext_inp['internal_data_id'].name + '/UnsqueezedInput',
|
||||
dict(shape=np.insert(shape, ext_inp['axis'], 1)))
|
||||
dim = shape.copy()
|
||||
# try to do it dynamically reshapable along one of the axis
|
||||
# it is practically useful to reshape along batch dimension, but here we cannot detect where it is
|
||||
# so, we are guessing based on other transformations that it is the major dimension
|
||||
dim[0] = -1
|
||||
|
||||
reshape_op = Squeeze(body, dict(name=ext_inp['internal_data_id'].name + '/InputSqueeze'))
|
||||
reshape_dim_data = Const(body, {'name': ext_inp['internal_data_id'].name + '/ReshapeDim',
|
||||
'value': ext_inp['axis']}).create_node_with_data()
|
||||
@@ -318,9 +317,6 @@ class TensorIteratorMerge(MiddleReplacementPattern):
|
||||
|
||||
if ext_out['axis'] is not None:
|
||||
# Insert unsqueezing resize at output port that has partitioning
|
||||
dim = ext_out['internal_data_id'].shape.copy()
|
||||
# trying to make it dynamically reshapable (see related comment above for the first Reshape)
|
||||
dim[0] = -1
|
||||
assert not ext_out['internal_data_id'].has_valid('value')
|
||||
reshape_op = Unsqueeze(body, dict(name=ext_out['internal_data_id'].name + '/OutputUnsqueeze'))
|
||||
reshape_dim_data = Const(body, {'name': ext_out['internal_data_id'].name + '/ReshapeDim',
|
||||
|
||||
@@ -180,6 +180,27 @@ class Elu(Activation):
|
||||
return ['alpha']
|
||||
|
||||
|
||||
class ThresholdedRelu(Activation):
|
||||
# The operation will be decomposed to primitive operations
|
||||
op = 'ThresholdedRelu'
|
||||
|
||||
def __init__(self, graph: Graph, attrs):
|
||||
trelu_attrs = {'alpha': 1.0, 'type': None}
|
||||
trelu_attrs.update(attrs)
|
||||
super().__init__(graph, trelu_attrs)
|
||||
|
||||
@staticmethod
|
||||
def thresholded_relu(values: np.ndarray, alpha: float):
|
||||
values = values.astype(float)
|
||||
for index, x in np.ndenumerate(values):
|
||||
values[index] = values[index] * (x > alpha)
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def infer(cls, node: Node):
|
||||
return eltwise_infer(node, lambda x, alpha: ThresholdedRelu.thresholded_relu(x, alpha), alpha=node.alpha)
|
||||
|
||||
|
||||
class LeakyReLU(Op):
|
||||
op = 'LeakyReLU'
|
||||
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
import logging as log
|
||||
|
||||
import numpy as np
|
||||
|
||||
from extensions.ops.tensor_iterator import TensorIterator
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.graph.graph import Node, Graph
|
||||
from mo.graph.port import Port
|
||||
from mo.middle.passes.infer import partial_infer
|
||||
from mo.ops.const import Const
|
||||
|
||||
|
||||
class Loop(TensorIterator):
|
||||
"""
|
||||
Loop layer that iterates over tensors and execute embedded sub-graph. The main difference from the TensorIterator is
|
||||
that Loop operation performs explicit slicing of data using special input called "current_iteration". Also the Loop
|
||||
has special input determining the execution condition and special output producing execution condition for the next
|
||||
iteration.
|
||||
"""
|
||||
op = 'Loop'
|
||||
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
base_attrs = {
|
||||
'type': self.op,
|
||||
'op': self.op,
|
||||
'version': 'opset5',
|
||||
'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-graphs
|
||||
'infer': self.infer,
|
||||
'type_infer': self.type_infer,
|
||||
}
|
||||
base_attrs.update(attrs)
|
||||
super().__init__(graph, base_attrs)
|
||||
|
||||
def port_map_attrs(self):
|
||||
return super().port_map_attrs() + ['purpose']
|
||||
|
||||
@staticmethod
|
||||
def generate_port_map(node: Node, src_port_map, dir: str):
|
||||
result_list = []
|
||||
for record in src_port_map:
|
||||
# do not update ids for not-connected output which is used in the Loop operation only
|
||||
if record['external_port_id'] != -1:
|
||||
if dir == 'out': # increase the output port id by the number of input ports
|
||||
# update the port id for proper generation of a "ports" section
|
||||
record['external_port_id'] += len(node.in_ports())
|
||||
record['internal_layer_id'] = TensorIterator.find_internal_layer_id(node.body, record['internal_layer_id'])
|
||||
result_list.append(record)
|
||||
return result_list
|
||||
|
||||
@staticmethod
|
||||
def get_body_node_by_internal_id(loop_node: Node, internal_id: int):
|
||||
suitable_nodes = loop_node.body.get_op_nodes(internal_layer_id=internal_id)
|
||||
assert len(suitable_nodes) <= 1, \
|
||||
'Expected 0 or 1 node with `internal_layer_id`={}, {} found'.format(internal_id, len(suitable_nodes))
|
||||
return suitable_nodes[0] if len(suitable_nodes) == 1 else None
|
||||
|
||||
@staticmethod
|
||||
def updated_body_parameters_shape(loop_node: Node):
|
||||
"""
|
||||
Update shape for Loop body parameters.
|
||||
The input shape of the "current_iteration" number input is handled separately because it is not connected with
|
||||
the Loop operation.
|
||||
|
||||
:param loop_node: The Loop node
|
||||
:return: None
|
||||
"""
|
||||
for record in loop_node.input_port_map:
|
||||
body_node = Loop.get_body_node_by_internal_id(loop_node, record['internal_layer_id'])
|
||||
# the Parameter may be removed because it was not used in the body, for example, the current iteration
|
||||
# number input
|
||||
if body_node is not None:
|
||||
assert body_node.soft_get('type') == 'Parameter'
|
||||
|
||||
input_shape = int64_array([]) # this is a current iteration number input shape
|
||||
loop_port_idx = record['external_port_id']
|
||||
if loop_port_idx != -1:
|
||||
input_shape = loop_node.in_port(loop_port_idx).get_connection().get_source().data.get_shape()
|
||||
body_node.shape = input_shape
|
||||
log.debug('Updated shape for the body node with internal_id "{}" with value {}'
|
||||
''.format(record['internal_layer_id'], body_node.shape))
|
||||
|
||||
@staticmethod
|
||||
def updated_loop_output_ports_shape_and_value(loop_node: Node):
|
||||
"""
|
||||
Update shape and values for Loop output ports. If the number of iterations is dynamic then the corresponding
|
||||
dimension for the scan outputs (having "axis" attribute) are set to 1 because MO cannot generate IR with
|
||||
undefined dimensions.
|
||||
|
||||
:param loop_node: The Loop node
|
||||
:return: None
|
||||
"""
|
||||
loop_name = loop_node.soft_get('name', loop_node.id)
|
||||
for record in loop_node.output_port_map:
|
||||
body_node = Loop.get_body_node_by_internal_id(loop_node, record['internal_layer_id'])
|
||||
assert body_node is not None
|
||||
assert body_node.soft_get('type') == 'Result'
|
||||
|
||||
loop_port_idx = record['external_port_id']
|
||||
if loop_port_idx != -1: # the id = -1 for execution condition output which is not connected anywhere
|
||||
output_value = body_node.in_port(0).data.get_value()
|
||||
output_shape = body_node.in_port(0).data.get_shape()
|
||||
concat_axis = record['axis']
|
||||
if concat_axis is not None:
|
||||
assert output_shape[concat_axis] == 1, 'Dimension for concatenation is not equal to 1 for scan ' \
|
||||
'output for Loop node "{}" for loop output port "{}"'.\
|
||||
format(loop_name, loop_port_idx)
|
||||
num_iters = Loop.iterations_count(loop_node)
|
||||
if num_iters is None:
|
||||
log.error('Dynamic number of iterations for Loop node "{}". Consider number to be 1 to be able'
|
||||
' to generate the IR.'.format(loop_name), extra={'is_warning': True})
|
||||
num_iters = 1
|
||||
output_shape[concat_axis] = num_iters
|
||||
# MO does not support evaluation of Loop scan outputs with const values
|
||||
if concat_axis is None and output_value is not None:
|
||||
loop_node.out_port(loop_port_idx).data.set_value(output_value)
|
||||
else:
|
||||
loop_node.out_port(loop_port_idx).data.set_shape(output_shape)
|
||||
|
||||
@staticmethod
|
||||
def iterations_count(loop_node: Node):
|
||||
"""
|
||||
Try to determine the number of loop iterations. If we detect that the number is dynamic then return None.
|
||||
|
||||
:param loop_node: Loop operation node
|
||||
:return: number of iterations or None if the number depends on runtime values.
|
||||
"""
|
||||
assert loop_node.soft_get('type') == 'Loop'
|
||||
|
||||
if loop_node.is_in_port_connected(1):
|
||||
execution_condition = loop_node.in_port(1).data.get_value()
|
||||
if execution_condition is None: # dynamic execution condition
|
||||
return None
|
||||
execution_condition = execution_condition.item()
|
||||
if not execution_condition: # 0 iterations
|
||||
return 0
|
||||
num_iterations = loop_node.in_port(0).data.get_value()
|
||||
if num_iterations is not None:
|
||||
num_iterations = num_iterations.item(0)
|
||||
return num_iterations
|
||||
|
||||
@staticmethod
|
||||
def update_body_parameters_type(loop_node: Node):
|
||||
"""
|
||||
Update the data type for Loop body Parameter nodes based on data type of the outer graph nodes producing data
|
||||
for them.
|
||||
|
||||
:param loop_node: The Loop node
|
||||
:return: None
|
||||
"""
|
||||
assert loop_node.soft_get('type') == 'Loop'
|
||||
for record in loop_node.input_port_map:
|
||||
body_node = Loop.get_body_node_by_internal_id(loop_node, record['internal_layer_id'])
|
||||
# the Parameter may be removed because it was not used in the body, for example, the current iteration
|
||||
# number input
|
||||
if body_node is not None:
|
||||
assert body_node.soft_get('type') == 'Parameter'
|
||||
|
||||
loop_port_idx = record['external_port_id']
|
||||
if loop_port_idx != -1:
|
||||
input_type = loop_node.in_port(loop_port_idx).get_data_type()
|
||||
else: # this is a current iteration number input node which is not connected to the Loop node
|
||||
assert record['purpose'] == 'current_iteration'
|
||||
input_type = np.int64
|
||||
|
||||
body_node.data_type = input_type
|
||||
log.debug('Updated data type for the body node with internal_id "{}" with value {}'
|
||||
''.format(record['internal_layer_id'], body_node.data_type))
|
||||
|
||||
@staticmethod
|
||||
def update_loop_output_ports_type(loop_node: Node):
|
||||
"""
|
||||
Update the data type for Loop output ports.
|
||||
|
||||
:param loop_node: The Loop node
|
||||
:return: None
|
||||
"""
|
||||
assert loop_node.soft_get('type') == 'Loop'
|
||||
for record in loop_node.output_port_map:
|
||||
body_node = Loop.get_body_node_by_internal_id(loop_node, record['internal_layer_id'])
|
||||
assert body_node is not None
|
||||
assert body_node.soft_get('type') == 'Result'
|
||||
|
||||
loop_port_idx = record['external_port_id']
|
||||
if loop_port_idx != -1: # the id = -1 for execution condition output which is not connected anywhere
|
||||
output_type = body_node.in_port(0).get_data_type()
|
||||
loop_node.out_port(loop_port_idx).set_data_type(output_type)
|
||||
|
||||
@staticmethod
|
||||
def mark_current_iteration_parameter_node(loop_node: Node, body_parameter_node: Node):
|
||||
assert body_parameter_node.id in loop_node.body
|
||||
assert body_parameter_node.soft_get('op') == 'Parameter'
|
||||
assert body_parameter_node.has_valid('internal_layer_id')
|
||||
assert len(loop_node.body.get_op_nodes(purpose='current_iteration')) == 0
|
||||
|
||||
loop_node.input_port_map.append({'axis': None, 'stride': None, 'part_size': None, 'start': None, 'end': None,
|
||||
'external_port_id': -1, 'purpose': 'current_iteration',
|
||||
'internal_layer_id': body_parameter_node['internal_layer_id']})
|
||||
|
||||
@staticmethod
|
||||
def mark_execution_condition_result_node(loop_node: Node, body_result_node: Node):
|
||||
assert body_result_node.id in loop_node.body
|
||||
assert body_result_node.soft_get('op') == 'Result'
|
||||
assert body_result_node.has_valid('internal_layer_id')
|
||||
assert len(loop_node.body.get_op_nodes(purpose='execution_condition')) == 0
|
||||
|
||||
loop_node.output_port_map.append({'axis': None, 'stride': None, 'part_size': None, 'start': None, 'end': None,
|
||||
'external_port_id': -1, 'purpose': 'execution_condition',
|
||||
'internal_layer_id': body_result_node['internal_layer_id']})
|
||||
|
||||
@staticmethod
|
||||
def external_port_id_to_body_node(loop_node: Node, external_port_id: int, port_map: dict):
|
||||
"""
|
||||
Return the body Node connected to the Loop port with number "external_port_id".
|
||||
|
||||
:param loop_node: the Loop node
|
||||
:param external_port_id: the Loop node port idx
|
||||
:param port_map: the port_map value to look for external_port_id
|
||||
:return: the corresponding body Node
|
||||
"""
|
||||
assert loop_node.soft_get('type') == 'Loop'
|
||||
body_graph = loop_node.body
|
||||
result_nodes = []
|
||||
for record in port_map:
|
||||
if record['external_port_id'] == external_port_id:
|
||||
result_nodes.extend(body_graph.get_op_nodes(internal_layer_id=record['internal_layer_id']))
|
||||
assert len(result_nodes) == 1, 'There should be just one body node for external port "{}", but there "{}"' \
|
||||
''.format(external_port_id, len(result_nodes))
|
||||
return result_nodes[0]
|
||||
|
||||
@staticmethod
|
||||
def connect_body_input(loop_input_port: Port, internal_parameter: Node, external_node_out_port: Port = None,
|
||||
axis: [int, None] = None, start: [int, None] = None, end: [int, None] = None,
|
||||
stride: [int, None] = None, part_size: [int, None] = None):
|
||||
loop_node = loop_input_port.node
|
||||
assert loop_node.soft_get('op') == 'Loop'
|
||||
assert loop_input_port.type == 'in'
|
||||
assert internal_parameter.soft_get('op') == 'Parameter'
|
||||
assert internal_parameter.id in loop_node.body
|
||||
|
||||
if external_node_out_port is not None:
|
||||
assert loop_input_port.disconnected()
|
||||
assert external_node_out_port.node.id not in loop_node.body
|
||||
loop_input_port.connect(external_node_out_port)
|
||||
|
||||
loop_node.input_port_map.append({'axis': axis, 'stride': stride, 'part_size': part_size, 'start': start,
|
||||
'end': end, 'external_port_id': loop_input_port.idx,
|
||||
'internal_layer_id': internal_parameter['internal_layer_id']})
|
||||
|
||||
@staticmethod
|
||||
def connect_body_output(loop_output_port: Port, internal_result: Node, external_node_input_ports: list = None,
|
||||
axis: [int, None] = None, start: [int, None] = None, end: [int, None] = None,
|
||||
stride: [int, None] = None, part_size: [int, None] = None):
|
||||
loop_node = loop_output_port.node
|
||||
assert loop_node.soft_get('op') == 'Loop'
|
||||
assert loop_output_port.type == 'out'
|
||||
assert internal_result.soft_get('op') == 'Result'
|
||||
assert internal_result.id in loop_node.body
|
||||
|
||||
if external_node_input_ports is not None:
|
||||
assert loop_output_port.disconnected()
|
||||
assert all([port.node.id not in loop_node.body for port in external_node_input_ports])
|
||||
for port in external_node_input_ports:
|
||||
port.disconnect()
|
||||
loop_output_port.connect(port)
|
||||
loop_node.output_port_map.append({'axis': axis, 'stride': stride, 'part_size': part_size, 'start': start,
|
||||
'end': end, 'external_port_id': loop_output_port.idx,
|
||||
'internal_layer_id': internal_result['internal_layer_id']})
|
||||
|
||||
@staticmethod
|
||||
def add_back_edge(loop_node: Node, internal_parameter: Node, internal_result: Node):
|
||||
assert internal_parameter.id in loop_node.body
|
||||
assert internal_parameter.soft_get('op') == 'Parameter'
|
||||
assert internal_result.id in loop_node.body
|
||||
assert internal_result.soft_get('op') == 'Result'
|
||||
|
||||
loop_node.back_edges.append({'from_layer': internal_result['internal_layer_id'],
|
||||
'to_layer': internal_parameter['internal_layer_id'],
|
||||
'from_port': 0,
|
||||
'to_port': 0})
|
||||
|
||||
@staticmethod
|
||||
def pull_constant_inputs_into_body(loop_node: Node):
|
||||
for port_idx, in_port in reversed(loop_node.in_ports().items()):
|
||||
# TODO add a check that the input does not correspond to execution_condition
|
||||
if not in_port.disconnected() and in_port.get_source().node.soft_get('type') == 'Const':
|
||||
original_const_node = in_port.get_source().node
|
||||
new_const_node = Const(loop_node.body, original_const_node.attrs()).create_node()
|
||||
|
||||
body_parameter = Loop.external_port_id_to_body_node(loop_node, port_idx, loop_node.input_port_map)
|
||||
body_parameter.out_port(0).get_connection().set_source(new_const_node.out_port(0))
|
||||
loop_node.body.remove_nodes_from([body_parameter.id])
|
||||
loop_node.delete_input_port(port_idx)
|
||||
|
||||
@staticmethod
|
||||
def update_port_map_value(port_map: dict, attr: str, original_value: int, new_value: int):
|
||||
matched = 0
|
||||
for record in port_map:
|
||||
if record[attr] == original_value:
|
||||
record[attr] = new_value
|
||||
matched += 1
|
||||
assert matched == 1, 'More than one record in the portmap for attr "{}" wil original value "{}"' \
|
||||
''.format(attr, original_value)
|
||||
|
||||
@staticmethod
|
||||
def re_numerate_input_ports(loop_node: Node):
|
||||
"""
|
||||
Update input ports ids to be consecutive from 0 to num_input_ports - 1 and update the port_map values of the
|
||||
Loop node.
|
||||
|
||||
:param loop_node: the Loop node
|
||||
:return: None
|
||||
"""
|
||||
def re_number_input_port(loop_node: Node, old_port_id: int, new_port_id: int):
|
||||
loop_node.add_input_port(new_port_id, skip_if_exist=True)
|
||||
loop_node.in_port(old_port_id).get_connection().set_destination(loop_node.in_port(new_port_id))
|
||||
Loop.update_port_map_value(loop_node.input_port_map, 'external_port_id', old_port_id, new_port_id)
|
||||
|
||||
if len(loop_node.in_ports()) > 0:
|
||||
max_port_id = sorted(loop_node.in_ports().keys())[-1]
|
||||
new_port_id = 0
|
||||
for port_id in range(max_port_id + 1):
|
||||
if port_id in loop_node.in_ports():
|
||||
if port_id != new_port_id:
|
||||
re_number_input_port(loop_node, port_id, new_port_id)
|
||||
new_port_id += 1
|
||||
|
||||
for port_idx_to_remove in reversed(range(new_port_id, max_port_id + 1)):
|
||||
loop_node.delete_input_port(port_idx_to_remove)
|
||||
|
||||
@staticmethod
|
||||
def re_numerate_output_ports(loop_node: Node):
|
||||
"""
|
||||
Update output ports ids to be consecutive from 0 to num_output_ports - 1 and update the port_map values of the
|
||||
Loop node.
|
||||
|
||||
:param loop_node: the Loop node
|
||||
:return: None
|
||||
"""
|
||||
assert loop_node.soft_get('type') == 'Loop'
|
||||
|
||||
def re_number_output_port(loop_node: Node, old_port_id: int, new_port_id: int):
|
||||
loop_node.add_output_port(new_port_id, skip_if_exist=True)
|
||||
loop_node.out_port(old_port_id).get_connection().set_source(loop_node.out_port(new_port_id))
|
||||
Loop.update_port_map_value(loop_node.output_port_map, 'external_port_id', old_port_id, new_port_id)
|
||||
|
||||
if len(loop_node.out_ports()) > 0:
|
||||
max_port_id = sorted(loop_node.out_ports().keys())[-1]
|
||||
new_port_id = 0
|
||||
for port_id in range(max_port_id + 1):
|
||||
if port_id in loop_node.out_ports():
|
||||
if port_id != new_port_id:
|
||||
re_number_output_port(loop_node, port_id, new_port_id)
|
||||
new_port_id += 1
|
||||
|
||||
for port_idx_to_remove in reversed(range(new_port_id, max_port_id + 1)):
|
||||
loop_node.delete_output_port(port_idx_to_remove)
|
||||
|
||||
@staticmethod
|
||||
def remove_unused_ops_from_port_map(loop_node: Node, port_map: dict, port_map_attr: str, dir: [None, str] = None):
|
||||
"""
|
||||
Find unused operations in the Loop body referenced in the port_map and removes Loop ports connected to it.
|
||||
Loop input port with index 0 and 1 are mandatory so cannot be removed.
|
||||
Output ports of the Loop may not be connected so check for that case also and remove such an ops from the
|
||||
port_map. The only exception is the "execution_condition" output which is a mandatory.
|
||||
|
||||
:param loop_node: the Loop node to update
|
||||
:param port_map: the port_map (input, output or back edges)
|
||||
:param port_map_attr: the port_map attribute containing the `internal_layer_id`
|
||||
:param dir: the direction of the port_map meaning 'in' or 'out' port of the Loop
|
||||
:return:
|
||||
"""
|
||||
record_ids_to_remove = []
|
||||
for record_id, record in enumerate(port_map):
|
||||
if len(loop_node.body.get_op_nodes(internal_layer_id=record[port_map_attr])) == 0 or \
|
||||
(dir == 'out' and record.get('purpose', "") != 'execution_condition' and
|
||||
record['external_port_id'] not in loop_node.out_ports()):
|
||||
record_ids_to_remove.append(record_id)
|
||||
for record_id_to_remove in reversed(record_ids_to_remove):
|
||||
if dir in ['in', 'out']:
|
||||
port_to_remove = port_map[record_id_to_remove]['external_port_id']
|
||||
if port_to_remove != -1:
|
||||
if dir == 'in':
|
||||
if port_to_remove not in [0, 1]: # input port 0 and 1 are mandatory for the Loop node
|
||||
loop_node.delete_input_port(port_to_remove)
|
||||
elif dir == 'out' and port_to_remove in loop_node.out_ports():
|
||||
loop_node.delete_output_port(port_to_remove)
|
||||
del port_map[record_id_to_remove]
|
||||
|
||||
@staticmethod
|
||||
def normalize_input_output_ports(loop_node: Node):
|
||||
"""
|
||||
Remove inputs, outputs, back edges from the port map which are not used in the body and were removed by a
|
||||
graph clean up, for example, in case of not used current_iteration body Parameter. Then renumber input/output
|
||||
ports.
|
||||
|
||||
:param loop_node: the Loop node to normalize
|
||||
:return: None
|
||||
"""
|
||||
Loop.remove_unused_ops_from_port_map(loop_node, loop_node.input_port_map, 'internal_layer_id', 'in')
|
||||
Loop.remove_unused_ops_from_port_map(loop_node, loop_node.output_port_map, 'internal_layer_id', 'out')
|
||||
Loop.remove_unused_ops_from_port_map(loop_node, loop_node.back_edges, 'to_layer')
|
||||
|
||||
# remove not connected input/output ports
|
||||
Loop.re_numerate_input_ports(loop_node)
|
||||
Loop.re_numerate_output_ports(loop_node)
|
||||
|
||||
@staticmethod
|
||||
def infer(loop_node: Node):
|
||||
Loop.updated_body_parameters_shape(loop_node)
|
||||
partial_infer(loop_node.body)
|
||||
Loop.updated_loop_output_ports_shape_and_value(loop_node)
|
||||
|
||||
@staticmethod
|
||||
def type_infer(loop_node: Node):
|
||||
from mo.middle.passes.infer import type_infer
|
||||
Loop.update_body_parameters_type(loop_node)
|
||||
type_infer(loop_node.body)
|
||||
Loop.update_loop_output_ports_type(loop_node)
|
||||
@@ -119,8 +119,7 @@ class TensorIterator(Op):
|
||||
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:
|
||||
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]
|
||||
@@ -233,13 +232,8 @@ class TensorIterator(Op):
|
||||
|
||||
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 = [
|
||||
def port_map_attrs(self):
|
||||
return [
|
||||
'external_port_id',
|
||||
'internal_layer_id',
|
||||
'internal_port_id',
|
||||
@@ -247,9 +241,17 @@ class TensorIterator(Op):
|
||||
'start',
|
||||
'stride',
|
||||
'end',
|
||||
'part_size'
|
||||
'part_size',
|
||||
]
|
||||
|
||||
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 = self.port_map_attrs()
|
||||
|
||||
back_edges_attrs = [
|
||||
('from-layer', 'from_layer'),
|
||||
('from-port', 'from_port'),
|
||||
@@ -265,9 +267,9 @@ class TensorIterator(Op):
|
||||
('data', self.backend_attrs() + self.default_backend_attrs, []),
|
||||
'@ports',
|
||||
('port_map', [], [
|
||||
('@list', lambda node: self.generate_port_map(node, node.input_port_map),
|
||||
('@list', lambda node: self.generate_port_map(node, node.input_port_map, 'in'),
|
||||
('input', port_map_attrs, [])),
|
||||
('@list', lambda node: self.generate_port_map(node, node.output_port_map),
|
||||
('@list', lambda node: self.generate_port_map(node, node.output_port_map, 'out'),
|
||||
('output', port_map_attrs, [])),
|
||||
]),
|
||||
('back_edges', [], [
|
||||
@@ -278,7 +280,7 @@ class TensorIterator(Op):
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def find_port_id(node: Node, virtual_id, attr):
|
||||
def find_port_id(node: Node, virtual_id: str, attr: str):
|
||||
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']
|
||||
@@ -291,13 +293,7 @@ class TensorIterator(Op):
|
||||
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):
|
||||
def generate_port_map(node: Node, src_port_map, dir: str):
|
||||
""" Extract port_map attributes from node and node.body attributes.
|
||||
|
||||
It iterates over src_port_map and substitute external_port_id, internal_port_id and
|
||||
|
||||
@@ -22,7 +22,7 @@ import logging as log
|
||||
|
||||
import onnx
|
||||
|
||||
from mo.graph.graph import fill_graph_with_nodes, Graph
|
||||
from mo.graph.graph import fill_graph_with_nodes, Graph, Node
|
||||
from mo.utils.error import Error, FrameworkError
|
||||
|
||||
|
||||
@@ -54,35 +54,24 @@ def node_id(pb):
|
||||
return 'NoNamed'
|
||||
|
||||
|
||||
def protobuf2nx(graph, pb):
|
||||
'''Convert proto message with ONNX model to equivalent NX representation.
|
||||
All nodes and edges are restored here as ONNX model has op/data representation,
|
||||
that means that nodes are connected via tensor names. Name of tensors are defined
|
||||
on demand in nodes, so we have a code similar to Caffe here. '''
|
||||
# graph = fill_graph_with_nodes(graph, pb.graph.node, get_id=node_id, get_attrs=protobuf_attrs)
|
||||
# convert initializers to a NX graph for easier control of model consistency and to use it as a dictionary later
|
||||
initializers = Graph()
|
||||
fill_graph_with_nodes(initializers, pb.graph.initializer, get_id=lambda pb: pb.name, get_attrs=protobuf_attrs)
|
||||
def protobuf2nx(graph: Graph, pb):
|
||||
"""
|
||||
Convert proto message with ONNX model to equivalent NX representation. All nodes and edges are restored here as
|
||||
ONNX model has op/data representation, that means that nodes are connected via tensor names. Name of tensors are
|
||||
defined on demand in nodes, so we have a code similar to Caffe here.
|
||||
|
||||
:param graph: the Graph object to load the graph into
|
||||
:param pb: the ONNX file protobuf message
|
||||
:return: None
|
||||
"""
|
||||
# maps a tensor name to a node produced it and the node port: str -> (node_id, node_port)
|
||||
data_nodes_map = {}
|
||||
|
||||
# first go through all inputs and separate constant from placeholders
|
||||
for inp in pb.graph.input:
|
||||
name = str(inp.name)
|
||||
if graph.has_node(name):
|
||||
raise Error('Name {} of input node already exists, input names are duplicated.', name)
|
||||
elif initializers.has_node(name):
|
||||
# this is a constant
|
||||
graph.add_node(name, kind='op', op='Const', pb=inp, pb_init=initializers.node[name]['pb'])
|
||||
else:
|
||||
# this is a placeholder
|
||||
graph.add_node(name, kind='op', op='Parameter', pb=inp)
|
||||
# add to a tensors map
|
||||
assert not name in data_nodes_map, 'Inconsistency between data_nodes_map and graph.nodes'
|
||||
data_nodes_map[name] = (name, 0)
|
||||
graph_pb = pb.graph
|
||||
add_initializers_and_inputs_to_graph(graph, graph_pb, data_nodes_map)
|
||||
|
||||
output_ids = []
|
||||
for outp in pb.graph.output:
|
||||
for outp in graph_pb.output:
|
||||
name = str(outp.name)
|
||||
if graph.has_node(name):
|
||||
log.error('Name {} of output node already exists in graph. Ignoring this output. If the output is required,'
|
||||
@@ -93,17 +82,9 @@ def protobuf2nx(graph, pb):
|
||||
graph.add_node(name, kind='op', op='FakeOutput', pb=outp)
|
||||
output_ids.append(name)
|
||||
|
||||
# go over all initializer and make sure that all of them are added to the graph
|
||||
for initializer in initializers.nodes():
|
||||
initializer_id = 'onnx_initializer_node_' + initializer
|
||||
if not graph.has_node(initializer_id):
|
||||
graph.add_node(initializer_id, kind='op', op='Const', pb=initializers.node[initializer]['pb'],
|
||||
pb_init=initializers.node[initializer]['pb'])
|
||||
data_nodes_map[initializer] = (initializer_id, 0)
|
||||
|
||||
# Go through all nodes in the original model order (because data nodes are defined on-the-fly and order is
|
||||
# important)
|
||||
for node in pb.graph.node:
|
||||
for node in graph_pb.node:
|
||||
# create an NX node
|
||||
id = graph.unique_id(node_id(node))
|
||||
graph.add_node(id, pb=node, kind='op')
|
||||
@@ -149,3 +130,41 @@ def protobuf2nx(graph, pb):
|
||||
if out in data_nodes_map:
|
||||
log.debug("Detected reuse of blob {}.".format(out))
|
||||
data_nodes_map[out] = (id, src_port)
|
||||
|
||||
graph.graph['tensor_mapping'] = data_nodes_map # save main graph tensor names mapping for Loop op parsing
|
||||
|
||||
|
||||
def add_initializers_and_inputs_to_graph(graph: Graph, graph_pb, data_nodes_map: dict):
|
||||
"""
|
||||
The function adds nodes specified in the 'initializer' attribute of the pb and input nodes.
|
||||
:param graph: the Graph to add nodes to
|
||||
:param graph_pb: the graph protobuf message
|
||||
:param data_nodes_map: the dictionary with mapping of tensor names to node id and port
|
||||
:return: the list of Parameter nodes
|
||||
"""
|
||||
initializers = Graph()
|
||||
fill_graph_with_nodes(initializers, graph_pb.initializer, get_id=lambda pb: pb.name, get_attrs=protobuf_attrs)
|
||||
|
||||
parameters = []
|
||||
# first go through all inputs and separate constant from placeholders
|
||||
for inp in graph_pb.input:
|
||||
name = str(inp.name)
|
||||
if graph.has_node(name):
|
||||
raise Error('Name {} of input node already exists, input names are duplicated.', name)
|
||||
elif initializers.has_node(name):
|
||||
graph.add_node(name, kind='op', op='Const', pb=inp, pb_init=initializers.node[name]['pb'])
|
||||
else:
|
||||
graph.add_node(name, kind='op', op='Parameter', pb=inp)
|
||||
parameters.append(Node(graph, name))
|
||||
|
||||
assert name not in data_nodes_map, 'Inconsistency between data_nodes_map and graph.nodes'
|
||||
data_nodes_map[name] = (name, 0)
|
||||
|
||||
# go over all initializers and make sure that all of them are added to the graph
|
||||
for initializer in initializers.nodes():
|
||||
initializer_id = initializer
|
||||
if not graph.has_node(initializer_id):
|
||||
graph.add_node(initializer_id, kind='op', op='Const', pb=initializers.node[initializer]['pb'],
|
||||
pb_init=initializers.node[initializer]['pb'])
|
||||
data_nodes_map[initializer] = (initializer_id, 0)
|
||||
return parameters
|
||||
@@ -112,6 +112,21 @@ class Node:
|
||||
# update in_ports_count for consistency but it is unlikely have any effect somewhere in the code
|
||||
self['in_ports_count'] = len(self._in_ports)
|
||||
|
||||
def delete_output_port(self, idx, skip_if_absent=False):
|
||||
if not self.has_valid('_out_ports'):
|
||||
raise Error(
|
||||
'Cannot removed ports with indices {} from node {} because node doesn\'t '
|
||||
'have _out_ports attribute'.format(idx, self.soft_get('name')))
|
||||
# no handling of control flow edges -- TODO
|
||||
control_flow = False
|
||||
if not skip_if_absent and idx not in self.out_ports(control_flow=control_flow):
|
||||
raise Error("Input port with index {} doesn't exist in node {}.".format(idx, self.soft_get('name')))
|
||||
if not self.out_port(idx).disconnected():
|
||||
self.out_port(idx).disconnect()
|
||||
del self._out_ports[idx]
|
||||
# update in_ports_count for consistency but it is unlikely have any effect somewhere in the code
|
||||
self['out_ports_count'] = len(self._out_ports)
|
||||
|
||||
def add_output_port(self, idx, skip_if_exist=False, **kwargs):
|
||||
if not self.has_valid('_out_ports'):
|
||||
Node(self.graph, self.id)['_out_ports'] = {}
|
||||
@@ -580,21 +595,27 @@ class Graph(nx.MultiDiGraph):
|
||||
# Also check that all necessary ports are exists
|
||||
message = "Attempt to connect {} to {}.".format(u_for_edge, v_for_edge)
|
||||
if self.stage == 'front':
|
||||
assert unode.kind == 'op' and vnode.kind == 'op', "{} Wrong add_adge usage! You can connect only two operations in front phase".format(message)
|
||||
assert 'in' in attr and 'out' in attr, "Missing necessary attribute in or out when adding edge between {} and {}".format(u_for_edge, v_for_edge)
|
||||
assert unode.kind == 'op' and vnode.kind == 'op', "{} Wrong add_adge usage! You can connect only two " \
|
||||
"operations in front phase".format(message)
|
||||
assert 'in' in attr and 'out' in attr, "Missing necessary attribute in or out when adding edge " \
|
||||
"between {} and {}".format(u_for_edge, v_for_edge)
|
||||
is_control_flow = 'control_flow_edge' in attr and attr['control_flow_edge'] is True
|
||||
in_port = 'control_flow_{}'.format(attr['in']) if is_control_flow else attr['in']
|
||||
out_port = 'control_flow_{}'.format(attr['out']) if is_control_flow else attr['out']
|
||||
assert unode.has_port('out', out_port, control_flow=is_control_flow), "{} Missing out port ({}) in {} node".format(message, out_port, unode.name)
|
||||
assert vnode.has_port('in', in_port, control_flow=is_control_flow), "{} Missing in port ({}) in {} node".format(message, in_port, vnode.name)
|
||||
assert unode.has_port('out', out_port, control_flow=is_control_flow), \
|
||||
"{} Missing out port ({}) in {} node".format(message, out_port, unode.soft_get('name', unode.id))
|
||||
assert vnode.has_port('in', in_port, control_flow=is_control_flow), \
|
||||
"{} Missing in port ({}) in {} node".format(message, in_port, vnode.soft_get('name', vnode.id))
|
||||
elif self.stage in ['middle', 'back']:
|
||||
assert (unode.kind == 'data' and vnode.kind == 'op') or (unode.kind == 'op' and vnode.kind == 'data')
|
||||
if unode.kind == 'data' and vnode.kind == 'op':
|
||||
assert 'in' in attr, "Attribute in is missing when adding edge to {}".format(v_for_edge)
|
||||
assert vnode.has_port('in', attr['in']), "{} Node {} has no in port ({})".format(message, vnode.name, attr['in'])
|
||||
assert vnode.has_port('in', attr['in']), "{} Node {} has no in port ({})" \
|
||||
"".format(message, vnode.name, attr['in'])
|
||||
if unode.kind == 'op' and vnode.kind == 'data':
|
||||
assert 'out' in attr, "Attribute out is missing when adding edge from {}".format(u_for_edge)
|
||||
assert unode.has_port('out', attr['out']), "{} Node {} has no out port ({})".format(message, unode.name, attr['out'])
|
||||
assert unode.has_port('out', attr['out']), "{} Node {} has no out port ({})" \
|
||||
"".format(message, unode.name, attr['out'])
|
||||
|
||||
return super().add_edge(u_for_edge, v_for_edge, key=key, **attr)
|
||||
|
||||
@@ -759,7 +780,8 @@ class Graph(nx.MultiDiGraph):
|
||||
list(
|
||||
node.out_ports().keys())))
|
||||
|
||||
def dump_graph_for_graphviz(self, node_attrs: list = ['kind', 'op', 'shape', 'correct_data_layout', 'nchw_layout'],
|
||||
def dump_graph_for_graphviz(self, node_attrs: list = ['kind', 'op', 'shape', 'correct_data_layout', 'nchw_layout',
|
||||
'internal_layer_id'],
|
||||
edge_attrs: list = ['in', 'out'], nodes_to_dump: list = None,
|
||||
save_to_svg=False, highlight_nodes: list = None):
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ class EltwiseN(Op):
|
||||
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
super().__init__(graph, {
|
||||
'op': __class__.op,
|
||||
'op': self.op,
|
||||
'type': None, # type is None because this operation should not appear in IR
|
||||
'infer': None,
|
||||
'out_ports_count': 1,
|
||||
}, attrs)
|
||||
if 'operation' not in self.attrs:
|
||||
raise Error('"operation" attribute is not set for operation "{}".'.format(__class__.op))
|
||||
raise Error('"operation" attribute is not set for operation "{}".'.format(self.op))
|
||||
|
||||
|
||||
class EltwiseNMul(EltwiseN):
|
||||
@@ -42,6 +42,11 @@ class EltwiseNMul(EltwiseN):
|
||||
super().__init__(graph, {'operation': 'mul'})
|
||||
|
||||
|
||||
class EltwiseNMin(EltwiseN):
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
super().__init__(graph, {'operation': 'min'})
|
||||
|
||||
|
||||
class EltwiseNMax(EltwiseN):
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
super().__init__(graph, {'operation': 'max'})
|
||||
|
||||
@@ -125,7 +125,7 @@ class Slice(Op):
|
||||
'op': 'Slice',
|
||||
'in_ports_count': 5,
|
||||
'out_ports_count': 1,
|
||||
'infer': __class__.infer
|
||||
'infer': self.infer
|
||||
}, attrs)
|
||||
|
||||
@staticmethod
|
||||
@@ -172,17 +172,5 @@ def get_shape_after_slice(input_shape: np.ndarray, slice_idx: List[slice]) -> np
|
||||
"""
|
||||
output_shape = np.zeros(len(input_shape), dtype=np.int32)
|
||||
for i, s in enumerate(slice_idx):
|
||||
start, end, step = normalize_slice_indices(input_shape[i], s.start, s.stop, s.step)
|
||||
output_shape[i] = (end - start + step - 1) / step
|
||||
output_shape[i] = len(range(*s.indices(input_shape[i])))
|
||||
return output_shape
|
||||
|
||||
|
||||
def normalize_slice_indices(size: int, start: int, end: int, step: int) -> (int, int, int):
|
||||
# converts slice indices to format in which size of Slice can be calculated
|
||||
start = size + start if start < 0 else start
|
||||
end = size + end if end < 0 else end
|
||||
start = np.clip(start, 0, size)
|
||||
end = np.clip(end, 0, size)
|
||||
if step < 0:
|
||||
start, end, step = end, start, -step # if calculate size without values we can do that
|
||||
return start, end, step
|
||||
@@ -51,6 +51,8 @@ class TestSliceOp(unittest.TestCase):
|
||||
(None, [1], [3], [0], [4], [1], [4]),
|
||||
# with rounding and negative steps (e.g. take from 1st to 3rd with step 4 should give shape 1 not 0)
|
||||
(None, [7], [3], [0], [-7], [1], [10]),
|
||||
# reversing the sequence of elements
|
||||
(None, [-1], [np.iinfo(np.int32).min], [0], [-1], [10], [10]),
|
||||
])
|
||||
def test_slice_infer(self, inp_value, starts, ends, axes, steps, expected, inp_shape=None):
|
||||
if inp_value is None:
|
||||
|
||||
@@ -282,7 +282,7 @@ def apply_transform(graph: Graph, replacer_cls, **kwargs):
|
||||
log.debug("Run replacer {}".format(replacer_cls))
|
||||
|
||||
try:
|
||||
if hasattr(replacer, 'run_not_recursively'):
|
||||
if hasattr(replacer, 'run_not_recursively') and replacer.run_not_recursively:
|
||||
replacer.find_and_replace_pattern(graph)
|
||||
else:
|
||||
for_graph_and_each_sub_graph_recursively(graph, replacer.find_and_replace_pattern)
|
||||
@@ -293,8 +293,12 @@ def apply_transform(graph: Graph, replacer_cls, **kwargs):
|
||||
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())
|
||||
if hasattr(replacer, 'run_not_recursively') and replacer.run_not_recursively:
|
||||
graph.check_empty_graph(replacer_cls)
|
||||
graph.check_shapes_consistency()
|
||||
else:
|
||||
for_graph_and_each_sub_graph_recursively(graph, lambda _: graph.check_empty_graph(replacer_cls))
|
||||
for_graph_and_each_sub_graph_recursively(graph, lambda _: graph.check_shapes_consistency())
|
||||
|
||||
except Error as err:
|
||||
raise Error('Exception occurred during running replacer "{}" ({}): {}'.format(
|
||||
|
||||
@@ -25,6 +25,7 @@ from extensions.ops.ReduceOps import ReduceOp
|
||||
from extensions.ops.activation_ops import Activation
|
||||
from extensions.ops.elementwise import Elementwise, UnaryElementwise, LogicalElementwise, BiasAdd, Div, Mul, Pow, Sub
|
||||
from extensions.ops.embedding_bag import EmbeddingBagBase
|
||||
from extensions.ops.loop import Loop
|
||||
from extensions.ops.psroipooling import DeformablePSROIPoolingOp
|
||||
from extensions.ops.scatter import Scatter
|
||||
from extensions.ops.scatternd import ScatterNDBase
|
||||
@@ -51,6 +52,7 @@ custom_ops = {
|
||||
'Divide': Div,
|
||||
'GroupConvolution': Convolution,
|
||||
'GroupConvolutionBackpropData': Deconvolution,
|
||||
'Loop': Loop,
|
||||
'MaxPool': Pooling,
|
||||
'Multiply': Mul,
|
||||
'Power': Pow,
|
||||
|
||||
Reference in new issue
Block a user