[MO] fix MarkNodesWithShapeValues (#5808)
* fix marking of nodes with shape values: corrected 'leaky' starting condition
* cleaned up code
* fixed t2t type collision error: removed strict assert from Pow; now type_infer is same as for other eltwise ops; added warnings about type alignment
* type cast for Pow according to ONNX spec
* fix highlight in dump_graph_for_graphviz
* soft_get('op')
* Revert "soft_get('op')"
This reverts commit cadfe18f
* Revert "type cast for Pow according to ONNX spec"
This reverts commit bf85ebf8
* applied review comments
* comment for ShapeOf bfs, removed Error from elementwise op
* reverted back dump_graph_for_graphviz: fill_color -> node_attrs
* applied last review comments: returned back Error, added type_infer into unit-test utils
This commit is contained in:
1 parent
06811924f4
commit
d9d63d0d7e
5 files changed
+193
-63
No files matched your search
@@ -2,12 +2,30 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import logging as log
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
|
||||
from extensions.middle.MarkSubgraphsWithCorrectLayout import MarkSubGraphsWithCorrectLayout
|
||||
from mo.back.replacement import BackReplacementPattern
|
||||
from mo.graph.graph import Graph
|
||||
from mo.graph.graph import Graph, Node
|
||||
|
||||
shape_accepting_ops = {
|
||||
'Interpolate': [1, 2], # sizes, scales inputs
|
||||
'Reshape': [1], # shape
|
||||
'Broadcast': [1], # target_shape
|
||||
'ConvBackPropData ': [2], # output_shape
|
||||
'GroupConvolutionBackpropData ': [2], # output_shape
|
||||
'BatchToSpace': [1, 2, 3], # block_shape, crops_begin, crops_end
|
||||
'SpaceToBatch': [1, 2, 3], # block_shape, pads_begin, pads_end
|
||||
'StridedSlice': [1, 2, 3], # begin, end, strides
|
||||
'VariadicSplit': [2], # split_lengths
|
||||
'Tile': [1], # repeats input
|
||||
'TopK': [1], # K input
|
||||
'Pad': [1, 2], # pads_begin, pads_end
|
||||
'Range': [0, 1, 2], # start, stop, step inputs
|
||||
'OneHot': [1], # depth input
|
||||
}
|
||||
|
||||
|
||||
class MarkNodesWithShapeValues(BackReplacementPattern):
|
||||
@@ -34,45 +52,44 @@ class MarkNodesWithShapeValues(BackReplacementPattern):
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_operations_with_shape_inputs():
|
||||
return {
|
||||
'Interpolate': [1, 2], # sizes, scales inputs
|
||||
'Reshape': [1], # shape
|
||||
'Broadcast': [1], # target_shape
|
||||
'ConvBackPropData ': [2], # output_shape
|
||||
'GroupConvolutionBackpropData ': [2], # output_shape
|
||||
'BatchToSpace': [1, 2, 3], # block_shape, crops_begin, crops_end
|
||||
'SpaceToBatch': [1, 2, 3], # block_shape, pads_begin, pads_end
|
||||
'StridedSlice': [1, 2, 3], # begin, end, strides
|
||||
'VariadicSplit': [2], # split_lengths
|
||||
'Tile': [1], # repeats input
|
||||
'TopK': [1], # K input
|
||||
'Pad': [1, 2], # pads_begin, pads_end
|
||||
'Range': [0, 1, 2], # start, stop, step inputs
|
||||
'OneHot': [1], # depth input
|
||||
}
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
shape_input_ops_map = self.get_operations_with_shape_inputs()
|
||||
|
||||
nodes_with_shape_inputs = []
|
||||
def get_nodes_with_shape_inputs(graph: Graph) -> List[Node]:
|
||||
shape_accepting_nodes = []
|
||||
for node in graph.get_op_nodes():
|
||||
if node.soft_get('type') in shape_input_ops_map:
|
||||
nodes_with_shape_inputs.append(node)
|
||||
if node.soft_get('type') in shape_accepting_ops:
|
||||
shape_accepting_nodes.append(node)
|
||||
return shape_accepting_nodes
|
||||
|
||||
start_nodes = []
|
||||
@staticmethod
|
||||
def get_shape_returning_start_nodes(nodes_with_shape_inputs: List[Node]) -> List[Node]:
|
||||
sources = []
|
||||
for node in nodes_with_shape_inputs:
|
||||
start_nodes.extend(
|
||||
[node.in_port(port_idx).get_source().node for port_idx in shape_input_ops_map[node.type] if
|
||||
node.is_in_port_connected(port_idx)])
|
||||
assert node.soft_get('type') in shape_accepting_ops
|
||||
for port_idx in shape_accepting_ops[node.soft_get('type')]:
|
||||
if not node.is_in_port_connected(port_idx):
|
||||
continue
|
||||
source_node = node.in_port(port_idx).get_source().node
|
||||
# no need to start BFS for ShapeOf nodes, indeed if there is a ShapeOf it's an end of BFS
|
||||
if source_node.soft_get('type') != 'ShapeOf':
|
||||
sources.append(source_node)
|
||||
return sources
|
||||
|
||||
condition = lambda node: node.soft_get('type') != 'ShapeOf'
|
||||
nodes_with_shape_values = MarkSubGraphsWithCorrectLayout.bfs(start_nodes, set(), condition, forward=False)
|
||||
for node in nodes_with_shape_values:
|
||||
@staticmethod
|
||||
def mark_nodes(shape_returning_nodes: List[Node]):
|
||||
for node in shape_returning_nodes:
|
||||
node['returns_shape_value'] = True
|
||||
if node.soft_get('type') == 'Const':
|
||||
if node.value.dtype == np.float32:
|
||||
node.out_node(0)['correct_data_type'] = True
|
||||
elif node.value.dtype in [np.float16, np.float64]:
|
||||
log.debug('Const nodes {} with shape values have {} type'.format(node.soft_get('name', node.id),
|
||||
node.value.dtype))
|
||||
log.error("Const node '{}' returns shape values of '{}' type but it must be integer or float32. "
|
||||
"During Elementwise type inference will attempt to cast to float32".
|
||||
format(node.soft_get('name', node.id), node.value.dtype), extra={'is_warning': True})
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
shape_accepting_nodes = self.get_nodes_with_shape_inputs(graph)
|
||||
|
||||
condition = lambda node: node.soft_get('type') != 'ShapeOf'
|
||||
shape_returning_start_nodes = self.get_shape_returning_start_nodes(shape_accepting_nodes)
|
||||
shape_returning_nodes = MarkSubGraphsWithCorrectLayout.bfs(shape_returning_start_nodes, set(),
|
||||
condition, forward=False)
|
||||
self.mark_nodes(shape_returning_nodes)
|
||||
@@ -10,6 +10,7 @@ from mo.graph.graph import Graph, Node
|
||||
from mo.middle.passes.infer import copy_type_infer
|
||||
from mo.ops.op import Op
|
||||
from mo.pipeline.common import convert_const_node_value_type
|
||||
from mo.utils.error import Error
|
||||
|
||||
|
||||
def override_data_type_of_constant(node: Node):
|
||||
@@ -22,13 +23,19 @@ def override_data_type_of_constant(node: Node):
|
||||
# power value 2, or when replacing Neg operation with Mul with -1 as second input.
|
||||
in_node_0 = node.in_port(0).get_source().node
|
||||
in_node_1 = node.in_port(1).get_source().node
|
||||
|
||||
if in_node_0.op != 'Const' and in_node_1.op != 'Const':
|
||||
raise Error("Elementwise operation '{}' has inputs of different data types: '{}' and '{}' "
|
||||
"that cannot be aligned".format(node.soft_get('name'), in_type_0, in_type_1))
|
||||
|
||||
if in_node_0.op == 'Const':
|
||||
convert_const_node_value_type(in_node_0, in_type_1)
|
||||
elif in_node_1.op == 'Const':
|
||||
convert_const_node_value_type(in_node_1, in_type_0)
|
||||
node_to_convert, src_type, dst_type = in_node_0, in_type_0, in_type_1
|
||||
else:
|
||||
log.error('Elementwise operation {} has inputs of different data types: {} and {}'.format(
|
||||
node.soft_get('name'), in_type_0, in_type_1))
|
||||
node_to_convert, src_type, dst_type = in_node_1, in_type_1, in_type_0
|
||||
log.error("Changing Const node '{}' data type from {} to {} for Elementwise operation".format(
|
||||
node_to_convert.soft_get('name', node_to_convert.id), src_type, dst_type),
|
||||
extra={'is_warning': True})
|
||||
convert_const_node_value_type(node_to_convert, dst_type)
|
||||
|
||||
|
||||
class Elementwise(Op):
|
||||
@@ -129,15 +136,6 @@ class Pow(Elementwise):
|
||||
return np.array(a.astype(np.float32) ** b, dtype=np.float32)
|
||||
return a ** b
|
||||
|
||||
@staticmethod
|
||||
def type_infer(node):
|
||||
in_type_0 = node.in_port(0).get_data_type()
|
||||
in_type_1 = node.in_port(1).get_data_type()
|
||||
assert in_type_0 == in_type_1, \
|
||||
'Power operation {} has inputs of different data types: {} and {}'.format(
|
||||
node.soft_get('name'), in_type_0, in_type_1)
|
||||
node.out_port(0).set_data_type(in_type_0)
|
||||
|
||||
|
||||
class LogicalElementwise(Elementwise):
|
||||
@staticmethod
|
||||
|
||||
@@ -10,7 +10,7 @@ from mo.front.common.partial_infer.utils import int64_array, float32_array
|
||||
from mo.graph.graph import Node
|
||||
from mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
from unit_tests.utils.graph import build_graph, result, regular_op_with_empty_data, shaped_const_with_data, connect, \
|
||||
regular_op
|
||||
regular_op, regular_op_with_shaped_data
|
||||
|
||||
|
||||
class TestMarkDataTypeInShapeOfSubgraphs(unittest.TestCase):
|
||||
@@ -99,3 +99,40 @@ class TestMarkDataTypeInShapeOfSubgraphs(unittest.TestCase):
|
||||
graph_ref = build_graph(nodes_ref, edges)
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'res', check_op_attrs=True)
|
||||
self.assertTrue(flag, resp)
|
||||
|
||||
def test_run_with_solitary_shapeof_in_shape_value_subgraph(self):
|
||||
# in this case MarkNodesWithShapeValues must leave graph unchanged
|
||||
# so reference nodes are exactly the same
|
||||
|
||||
inp_shape_1 = int64_array((1, 3, 100, 100))
|
||||
inp_shape_2 = int64_array((1, 3, 100, 50)) # inp_2 and const will be concatenated to (1, 3, 200, 50)
|
||||
const_shape = int64_array((1, 3, 100, 50))
|
||||
|
||||
nodes = {
|
||||
**regular_op_with_shaped_data('input_1', inp_shape_1, {'op': 'Parameter', 'type': 'Parameter'}),
|
||||
**regular_op_with_shaped_data('input_2', inp_shape_2, {'op': 'Parameter', 'type': 'Parameter',
|
||||
'returns_shape_value': False}),
|
||||
**shaped_const_with_data('const', const_shape),
|
||||
**regular_op_with_empty_data('concat', {'op': 'Concat', 'type': 'Concat', 'axis': 2,
|
||||
'returns_shape_value': False}),
|
||||
**regular_op_with_empty_data('shapeof', {'op': 'ShapeOf', 'type': 'ShapeOf'}),
|
||||
**regular_op_with_empty_data('reshape', {'op': 'Reshape', 'type': 'Reshape'}),
|
||||
**result('res'),
|
||||
}
|
||||
|
||||
edges = [
|
||||
*connect('input_1', '0:reshape'),
|
||||
*connect('input_2', '0:concat'),
|
||||
*connect('const', '1:concat'),
|
||||
*connect('concat', 'shapeof'),
|
||||
*connect('shapeof', '1:reshape'),
|
||||
*connect('reshape', 'res'),
|
||||
]
|
||||
|
||||
graph = build_graph(nodes, edges)
|
||||
|
||||
MarkNodesWithShapeValues().find_and_replace_pattern(graph)
|
||||
|
||||
graph_ref = build_graph(nodes, edges)
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'res', check_op_attrs=True)
|
||||
self.assertTrue(flag, "'returns_shape_value' should be False or unset for ShapeOf input nodes" + ': ' + str(resp))
|
||||
@@ -5,9 +5,13 @@ import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from extensions.ops.elementwise import Round
|
||||
from extensions.ops.elementwise import Round, Elementwise
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.graph.graph import Node
|
||||
from unit_tests.utils.graph import build_graph
|
||||
from mo.middle.passes.infer import type_infer
|
||||
from unit_tests.utils.graph import valued_const_with_data, result, regular_op_with_empty_data, connect, \
|
||||
shaped_parameter, build_graph
|
||||
|
||||
|
||||
def round_test_graph(nodes_attributes, value, mode: str):
|
||||
graph = build_graph(nodes_attributes,
|
||||
@@ -57,7 +61,7 @@ class TestElementwiseOp(unittest.TestCase):
|
||||
exp_shape = np.array([13])
|
||||
res_shape = graph.node['node_3']['shape']
|
||||
res_value = graph.node['node_3']['value']
|
||||
exp_value = np.array([-24., -22., -2., -2., -0., 0., 1., 2., 2., 2., 4., 22., 24.,])
|
||||
exp_value = np.array([-24., -22., -2., -2., -0., 0., 1., 2., 2., 2., 4., 22., 24., ])
|
||||
for i, value in enumerate(exp_shape):
|
||||
self.assertEqual(res_shape[i], value)
|
||||
for i, value in enumerate(exp_value):
|
||||
@@ -77,3 +81,71 @@ class TestElementwiseOp(unittest.TestCase):
|
||||
self.assertEqual(res_shape[i], value)
|
||||
for i, value in enumerate(exp_value):
|
||||
self.assertAlmostEqual(res_value[i], value)
|
||||
|
||||
|
||||
class TestElementwiseTypeAlignment(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def build_graph_to_test_type_alignment(edges,
|
||||
input_1_type=np.float32,
|
||||
input_2_type=np.float32,
|
||||
const_type=np.float32):
|
||||
input_shape = int64_array([1, 3, 255, 255])
|
||||
const_value = np.array([1], dtype=const_type)
|
||||
|
||||
nodes = {
|
||||
**shaped_parameter('input_1', input_shape, {'data_type': input_1_type}),
|
||||
**shaped_parameter('input_2', input_shape, {'data_type': input_2_type}),
|
||||
**regular_op_with_empty_data('add', {'op': 'Add', 'type': 'Add', 'type_infer': Elementwise.type_infer}),
|
||||
**valued_const_with_data('const', const_value, {'data_type': const_type}),
|
||||
**result('result'),
|
||||
}
|
||||
graph = build_graph(nodes, edges, nodes_with_edges_only=True)
|
||||
graph.stage = 'back'
|
||||
return graph
|
||||
|
||||
def test_first_input_const(self):
|
||||
edges = [
|
||||
*connect('const', '0:add'),
|
||||
*connect('input_1', '1:add'),
|
||||
*connect('add', 'result')
|
||||
]
|
||||
graph = self.build_graph_to_test_type_alignment(edges, const_type=np.float16, input_1_type=np.float32)
|
||||
|
||||
type_infer(graph)
|
||||
const_node = Node(graph, 'const')
|
||||
self.assertEquals(const_node.out_port(0).get_data_type(), np.float32)
|
||||
|
||||
def test_second_input_const(self):
|
||||
edges = [
|
||||
*connect('input_1', '0:add'),
|
||||
*connect('const', '1:add'),
|
||||
*connect('add', 'result')
|
||||
]
|
||||
graph = self.build_graph_to_test_type_alignment(edges, input_1_type=np.float32, const_type=np.float16)
|
||||
|
||||
type_infer(graph)
|
||||
const_node = Node(graph, 'const')
|
||||
self.assertEquals(const_node.out_port(0).get_data_type(), np.float32)
|
||||
|
||||
def test_raises(self):
|
||||
edges = [
|
||||
*connect('input_1', '0:add'),
|
||||
*connect('input_2', '1:add'),
|
||||
*connect('add', 'result')
|
||||
]
|
||||
graph = self.build_graph_to_test_type_alignment(edges, input_1_type=np.float32, input_2_type=np.float16)
|
||||
|
||||
self.assertRaises(Exception, type_infer, graph)
|
||||
|
||||
def test_not_raises(self):
|
||||
edges = [
|
||||
*connect('input_1', '0:add'),
|
||||
*connect('input_2', '1:add'),
|
||||
*connect('add', 'result')
|
||||
]
|
||||
graph = self.build_graph_to_test_type_alignment(edges, input_1_type=np.float32, input_2_type=np.float32)
|
||||
|
||||
type_infer(graph)
|
||||
add_node = Node(graph, 'add')
|
||||
self.assertEquals(add_node.out_port(0).get_data_type(), np.float32)
|
||||
@@ -276,25 +276,31 @@ shaped_data = lambda name, shape: {name: {'kind': 'data', 'value': None,
|
||||
'shape': int64_array(shape) if shape is not None else None}}
|
||||
empty_data = lambda name: valued_data(name, None)
|
||||
|
||||
shaped_parameter = lambda name, shape: {**regular_op(name, {'op': 'Parameter', 'shape': shape,
|
||||
'infer': Parameter.infer}),
|
||||
**shaped_data(name + '_d', shape)}
|
||||
shaped_parameter = lambda name, shape, kwargs={}: {**regular_op(name, {'op': 'Parameter', 'type': 'Parameter',
|
||||
'shape': shape, 'infer': Parameter.infer,
|
||||
**kwargs}),
|
||||
**shaped_data(name + '_d', shape)}
|
||||
|
||||
result = lambda name=None: {name if name is not None else 'output': {'kind': 'op', 'type': 'Result', 'op': 'Result',
|
||||
'infer': lambda x: 0}}
|
||||
result = lambda name='output': {name: {'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0}}
|
||||
|
||||
regular_op_with_shaped_data = lambda name, shape, kwargs: {**regular_op(name, kwargs),
|
||||
**shaped_data(name + '_d', shape)}
|
||||
regular_op_with_empty_data = lambda name, kwargs: {**regular_op(name, kwargs), **empty_data(name + '_d')}
|
||||
|
||||
# constants
|
||||
const = lambda name, value: {name: {'kind': 'op', 'value': value, 'shape': int64_array(value.shape),
|
||||
'type': 'Const', 'infer': Const.infer, 'op': 'Const'}}
|
||||
fake_const = lambda name, shape: {name: {'kind': 'op', 'value': None, 'infer': Const.infer,
|
||||
'shape': int64_array(shape) if shape is not None else None}}
|
||||
shaped_const_with_data = lambda name, shape: {**fake_const(name, shape), **shaped_data(name + '_d', shape)}
|
||||
const = lambda name, value, kwargs={}: {name: {'kind': 'op', 'type': 'Const', 'op': 'Const',
|
||||
'value': value, 'shape': int64_array(value.shape),
|
||||
'infer': Const.infer, 'type_infer': Const.type_infer, **kwargs}}
|
||||
|
||||
valued_const_with_data = lambda name, value: {**const(name, value), **valued_data(name + '_d', value)}
|
||||
fake_const = lambda name, shape, kwargs={}: {name: {'kind': 'op', 'op': 'Const', 'type': 'Const',
|
||||
'value': None, 'infer': Const.infer, **kwargs,
|
||||
'shape': int64_array(shape) if shape is not None else None}}
|
||||
|
||||
shaped_const_with_data = lambda name, shape, kwargs={}: {**fake_const(name, shape, kwargs),
|
||||
**shaped_data(name + '_d', shape)}
|
||||
|
||||
valued_const_with_data = lambda name, value, kwargs={}: {**const(name, value, kwargs),
|
||||
**valued_data(name + '_d', value)}
|
||||
|
||||
|
||||
def extract_port_from_string(node_name: str):
|
||||
|
||||
Reference in new issue
Block a user