[MO][TF] Fixed TFSliceToSlice conversion error on Win (#4367)

* [MO][TF] Fixed TFSliceToSlice conversion error on Win, replaced int32_max with ShapeOf, minor correction in comments

* review apply: added create_op_with_const_inputs

* review apply: renamed variables, updated typehints for int64_array, float_array, float32_array

* renamed to correct_ends

* returned back check_op_attrs=True
This commit is contained in:
Pavel Esir authored and GitHub committed 2021-02-24 16:34:43 +03:00
1 parent 7ba9723242
commit 162bdde251
5 files changed
+55 -67

No files matched your search

@@ -1,5 +1,5 @@
"""
Copyright (C) 2018-2020 Intel Corporation
Copyright (C) 2018-2021 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,63 +19,50 @@ import numpy as np
from extensions.ops.Cast import Cast
from extensions.ops.elementwise import Add, Equal
from extensions.ops.select import Select
from mo.front.common.partial_infer.utils import int64_array
from mo.front.common.replacement import FrontReplacementOp
from mo.front.tf.graph_utils import create_op_with_const_inputs
from mo.graph.graph import Graph, rename_nodes
from mo.ops.const import Const
from mo.ops.shape import Shape
from mo.ops.slice import Slice
class TFSliceToSliceReplacer(FrontReplacementOp):
"""
This transformation converts TFSlice to internal Slice operation.
In TFSlice size[i] == -1 means take all elements on axis i up to the end including(!) the last
In internal MO Slice (which is borrowed from ONNX) -1 means take all excluding(!) the last (shape[i] - 1).
Also TFSlice has 'sizes' on the second input while Slice has 'ends'.
This transformation was added to avoid multiple if statements in future transformations.
TFSlice has 'size' on the second input while Slice has 'ends', therefore we insert Add(begin, size).
size[i] == -1 is a magic number that means take the whole range along axis i up to the end.
To process the case when size[i] == -1 we insert subgraph with ShapeOf.
"""
op = 'TFSlice'
enabled = True
def replace_sub_graph(self, graph: Graph, match: dict):
node = match['op']
slice_name = node.soft_get('name', node.id)
tf_slice_node = match['op']
slice_name = tf_slice_node.soft_get('name', tf_slice_node.id)
slice_node = Slice(graph).create_node()
rename_nodes([(node, slice_name + '/to_be_removed'), (slice_node, slice_name)])
rename_nodes([(tf_slice_node, slice_name + '/to_be_removed'), (slice_node, slice_name)])
ends_node = Add(graph, {'name': slice_name + '/ends'}).create_node()
eq_node = Equal(graph, {'name': slice_name + '/equal'}).create_node()
minus_one_node = Const(graph, {'name': slice_name + '/minus_one', 'value': np.array(-1)}).create_node()
int32_max_node = Const(graph, {'name': slice_name + '/int32_max', 'value': np.iinfo(np.int32).max}).create_node()
select_node = Select(graph, {'name': slice_name + '/select'}).create_node()
# reconnect input, begin, and size from TFSlice to the subgraph with Slice
tf_slice_node.in_port(0).get_connection().set_destination(slice_node.in_port(0))
tf_slice_node.in_port(1).get_connection().set_destination(slice_node.in_port(1))
tf_slice_node.in_port(2).get_connection().set_destination(ends_node.in_port(0))
slice_node.in_port(1).get_connection().add_destination(ends_node.in_port(1))
# node to convert sizes to ends
sum_node = Add(graph, {'name': slice_name + '/end_const'}).create_node()
max_ends = Shape(graph, {'name': slice_name + '/ShapeOf'}).create_node()
slice_node.in_port(0).get_connection().add_destination(max_ends.in_port(0))
# reconnect input from tfslice to slice
node.in_port(0).get_source().connect(slice_node.in_port(0))
node.in_port(0).disconnect()
# reconnect begin of tfslice to start of slice
node.in_port(1).get_source().connect(slice_node.in_port(1))
node.in_port(1).disconnect()
# check if size[i] == -1, will be applied elementwisely: len(size) = len(begin) = input_rank
where_max_ends_is_needed = create_op_with_const_inputs(graph, Equal, {0: int64_array(-1)},
{'name': slice_name + '/where_max_ends_is_needed'})
ends_node.in_port(0).get_connection().add_destination(where_max_ends_is_needed.in_port(1))
# select requires equal dtypes, need to convert ends to I64
ends_casted_to_i64 = Cast(graph, {'name': slice_name + '/CastToI64',
'dst_type': np.int64}).create_node([ends_node])
# if size[i] == 1 then take max_ends values
correct_ends = Select(graph, {'name': slice_name + '/chosen_ends'}).create_node([where_max_ends_is_needed,
max_ends, ends_casted_to_i64])
correct_ends.out_port(0).connect(slice_node.in_port(2))
# (size -> ends) reconnect begins and sizes to sum to evaluate ends for Slice
# connects begins to slice
slice_node.in_port(1).get_source().connect(sum_node.in_port(0))
node.in_port(2).get_source().connect(sum_node.in_port(1))
node.in_port(2).disconnect()
# if size[i] == -1 when take int32_max as end[i]
sum_node.in_port(1).get_source().connect(eq_node.in_port(0))
minus_one_node.out_port(0).connect(eq_node.in_port(1))
# from equal to 0 port of select
eq_node.out_port(0).connect(select_node.in_port(0))
# from int32_max to 1 of select
int32_max_node.out_port(0).connect(select_node.in_port(1))
# from sum to 2nd of select
sum_node.out_port(0).connect(select_node.in_port(2))
# out of select to end (2nd of slice)
select_node.out_port(0).connect(slice_node.in_port(2))
cast = Cast(graph, dict(name=sum_node.name + '/CastToI64', dst_type=np.int64)).create_node()
select_node.in_port(2).get_connection().insert_node(cast)
node.out_port(0).get_connection().set_source(slice_node.out_port(0))
tf_slice_node.out_port(0).get_connection().set_source(slice_node.out_port(0))
@@ -1,5 +1,5 @@
"""
Copyright (C) 2018-2020 Intel Corporation
Copyright (C) 2018-2021 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ nodes = {
# nodes after replacement
**const('minus_one', np.array(-1)),
**const('int32_max', np.array(np.iinfo(np.int32).max)),
**regular_op_with_empty_data('shapeof', {'op': 'ShapeOf', 'type': 'ShapeOf'}),
**regular_op_with_empty_data('end_const', {'op': 'Add', 'type': 'Add'}),
**regular_op_with_empty_data('equal', {'op': 'Equal', 'type': 'Equal'}),
**regular_op_with_empty_data('select', {'op': 'Select', 'type': 'Select'}),
@@ -57,6 +57,7 @@ class SliceReplacerTest(unittest.TestCase):
graph_ref = build_graph(nodes_attrs=nodes, edges=[
*connect_front('input:0', 'slice'),
*connect_front('input:0', 'shapeof'),
*connect_front('begin:0', 'slice:1'),
*connect_front('begin:0', 'john_doe:1'),
@@ -64,7 +65,7 @@ class SliceReplacerTest(unittest.TestCase):
*connect_front('size:0', 'end_const:1'),
*connect_front('size:0', 'equal:0'),
*connect_front('int32_max:0', 'select:1'),
*connect_front('shapeof:0', 'select:1'),
*connect_front('minus_one:0', 'equal:1'),
*connect_front('equal:0', 'select:0'),
@@ -81,7 +82,7 @@ class SliceReplacerTest(unittest.TestCase):
def test_slice_replacer(self):
graph = build_graph(nodes_attrs=nodes, edges=[
*connect_front('input:0', 'tfslice'),
*connect_front('input:0', '0:tfslice'),
*connect_front('begin:0', '1:tfslice'),
*connect_front('size:0', '2:tfslice'),
*connect_front('tfslice:0', 'output'),
@@ -92,11 +93,12 @@ class SliceReplacerTest(unittest.TestCase):
graph_ref = build_graph(nodes_attrs=nodes, edges=[
*connect_front('input:0', 'slice'),
*connect_front('input:0', 'shapeof'),
*connect_front('begin:0', '1:slice'),
*connect_front('begin:0', '0:end_const'),
*connect_front('size:0', '1:end_const'),
*connect_front('size:0', '0:equal'),
*connect_front('int32_max:0', '1:select'),
*connect_front('shapeof:0', '1:select'),
*connect_front('minus_one:0', '1:equal'),
*connect_front('equal:0', '0:select'),
*connect_front('end_const:0', '0:cast'),
@@ -20,16 +20,16 @@ from typing import Iterable, List, Union
import numpy as np
def int64_array(l: Iterable):
return np.array(l, dtype=np.int64)
def int64_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray:
return np.array(value, dtype=np.int64)
def float_array(l: list):
return np.array(l, dtype=np.float64)
def float_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray:
return np.array(value, dtype=np.float64)
def float32_array(l: list):
return np.array(l, dtype=np.float32)
def float32_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray:
return np.array(value, dtype=np.float32)
def mark_input_bins(node, names=('weights', 'biases'), start_port: int = 1):
+9 -10
View File
@@ -34,7 +34,7 @@ A number of transformations take place on the front phase to convert framework s
class AttributedSlice(Op):
"""
AttributedSlice is used in old versions of ONNX models (opset version < 10).
The operation is replaced with the OpenVINO Slice operation on the front phase.
Is replaced with internal Slice on the front phase.
"""
op = 'AttributedSlice'
enabled = False
@@ -53,7 +53,7 @@ class CaffeSlice(Op):
"""
Slice in Caffe is equivalent to Split operation in OpenVINO.
https://caffe.berkeleyvision.org/tutorial/layers/slice.html
The operation is replaced with the OpenVINO Split operation on the front phase.
Is replaced with Split from opset on the front phase.
"""
op = 'CaffeSlice'
enabled = False
@@ -71,11 +71,10 @@ class CaffeSlice(Op):
class TFSlice(Op):
"""
Slice operation in Tensorflow is different from Slice in ONNX, Caffe and MXNet. It has begin and size inputs while
ONNX Slice and internal MO Slice has start, end, step and axis parameters specified as inputs.
TFSlice differs from Slice in ONNX, Caffe and MXNet.
TFSlice has 'begin' and 'size' inputs while Slice has 'start', 'end', 'step', and 'axis' inputs.
https://www.tensorflow.org/api_docs/python/tf/slice
The operation is replaced with the internal Slice on the front phase.
If size[i] == -1 is replaced to int32_max value for the end.
Is replaced with internal Slice op on the front phase.
"""
op = 'TFSlice'
enabled = False
@@ -94,7 +93,7 @@ class MXSlice(Op):
"""
Slice operation in MXNet is different from ONNX, Caffe, Tensorflow. It has begin, end & step attributes
https://mxnet.apache.org/versions/1.6/api/python/docs/api/symbol/op/index.html#mxnet.symbol.op.slice
The operation is replaced with the OpenVINO StridedSlice operation on the front phase.
Is replaced with the StridedSlice from opset on the front phase.
"""
op = 'MXSlice'
enabled = False
@@ -112,9 +111,9 @@ class MXSlice(Op):
class Slice(Op):
"""
Semantic of MO internal Slice operation is identical to Slice in ONNX opset >= 10.
It has starts, ends, steps and axes inputs.
The operation is internal (not present in the OpenVINO opset) and is replaced to StridedSlice.
Semantic of Slice is identical to Slice in ONNX opset >= 10.
It has 'starts', 'ends', 'steps', and 'axes' inputs.
SliceConverter replaces it with StridedSlice from opset.
"""
op = 'Slice'
enabled = False