[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:
Pavel Esir authored and GitHub committed 2021-06-04 18:49:49 +03:00
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)
+12 -14
View File
@@ -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)
+17 -11
View File
@@ -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):