[MO] Add reverse_infer (input rank deducing) for set of operations (#8928)

* grouped convolution resolver fix

* add reverse_infer for Transpose, Elementwise, Identity and some fixes to successfully OOB convert MXNet models

* add reverse_infer for BatchNorms, dynamic groups fix, multi_box_prior.py fix

* add reverse infer for trivial cases

* input rank setting for image detection ops

* reverse_infer for Gather, AttributedGather add todo comment for Parameter

* reverse infer for LSTM, swapaxis; fixes and unit-tests for Elementwise, Gather, Transpose; also generalized Parameter reverse_infer; fixed DetectionOutput and partial reverse_infer

* clarified comment for LSTM reverse_infer

* detalized Elementwise reverse_infer, added several more unit-tests, Split reverse_infer fix, IteratorGetNext_ext.py fix

* generalized group convolution resolver

* specified in_ports explicitly

* clarified Split, Concat, Parameter, other minor typos

* relaxed compatibility check for elementwise; simplified scalar extracting for convolution.py and gather.py, fixed replacer order for ConvolutionNormalizer.py

* added routine set_input_shapes for image detection ops

* fixed typos to pass locally E2E, slightly changed LSTM reverse_infer

* VariadicSplit reverse_infer bugfix

* IteratorGetNextCut.py added check if port is connected

* explicitly specified in_port, added asserts, improved Split reverse infer

* sorted imports

* reverse_infer for ONNX and MXNet LSTM, GRU, RNN operations

* replaced Error raising with red warning

* added missing return for defined input shapes, fixed getting num groups for tf conv

* added mandatory attributes; fixed failing unit-tests, fixed typo with scalar group in ConvolutionNormalizer

* removed priorbox reverse_infer; Parameter reverse_infer corrected to use the only one existing out_port

* fixed clarify_partial_shape_test
This commit is contained in:
Pavel Esir authored and GitHub committed 2022-01-25 10:14:41 +03:00
1 parent d27bbb4bdd
commit 41d4fcdf25
60 files changed
+1084 -94

No files matched your search

@@ -6,45 +6,97 @@ import numpy as np
from openvino.tools.mo.back.ReshapeMutation import ReshapeMutation
from openvino.tools.mo.back.ReverseInputChannels import ApplyReverseChannels
from openvino.tools.mo.back.replacement import BackReplacementPattern
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.front.common.partial_infer.utils import shape_array, is_fully_defined, int64_array
from openvino.tools.mo.front.common.partial_infer.utils import shape_array, is_fully_defined, int64_array, mo_array
from openvino.tools.mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.ops.concat import Concat
from openvino.tools.mo.ops.const import Const
from openvino.tools.mo.ops.elementwise import Div
from openvino.tools.mo.ops.gather import Gather
from openvino.tools.mo.ops.reshape import Reshape
from openvino.tools.mo.ops.shape import Shape
from openvino.tools.mo.ops.strided_slice import StridedSlice
from openvino.tools.mo.utils.error import Error
def resolve_convolution_with_group(node: Node, group: int, ir_version: str):
node_name = node.soft_get('name', node.id)
input_shape = node.in_port(0).data.get_shape()
assert len(input_shape) in [3, 4, 5]
weights_shape = node.in_port(1).data.get_shape()
assert weights_shape is not None
assert len(weights_shape) in [3, 4, 5]
group = int64_array(group).item()
assert weights_shape[0] % group == 0
if ir_version == 'V7':
if weights_shape[0] == node.output:
# weights are already is in [G*O I X Y] format
return
new_shape = shape_array([node.output, -1, *weights_shape[2:]])
num_spatial_dims = len(weights_shape[2:])
# Reshape has special_zero=True, if zeros are set then original shapes are copied
zeros_to_copy_spatial_dims = np.zeros(num_spatial_dims)
new_shape = shape_array([node.output, -1, *zeros_to_copy_spatial_dims])
reshape = create_op_node_with_second_input(node.graph, Reshape, new_shape,
{'override_output_shape': True})
elif ir_version == 'V10':
# TODO rewrite this transformation to generate a shape-computing sub-graph. Ticket 62076
I = input_shape[1]
new_shape = shape_array([group, node.output // group, I // group, *weights_shape[2:]])
assert is_fully_defined(weights_shape[2:]) and is_fully_defined(I) and \
np.prod(weights_shape) == np.prod(new_shape), 'Initial weights shape {}, grouped weights shape {}' \
''.format(weights_shape, new_shape)
if is_fully_defined(weights_shape[2:]) and is_fully_defined(I):
new_shape = shape_array([group, node.output // group, I // group, *weights_shape[2:]])
assert np.prod(weights_shape) == np.prod(new_shape), 'Initial weights shape {}, grouped weights shape {}' \
''.format(weights_shape, new_shape)
reshape = create_op_node_with_second_input(node.graph, Reshape, new_shape,
{'override_output_shape': True, 'special_zero': True})
else:
# if weights/or input channel dimension is dynamic need to to compute new_shape in a new subgraph
weights_node = node.in_port(1).get_source().node
input_node = node.in_port(0).get_source().node
weights_shape = Shape(node.graph, {'name': node_name + '/ShapeOfWeights'}).create_node()
weights_shape.in_port(0).connect(weights_node.out_port(0))
weights_spatial_shape = create_op_with_const_inputs(node.graph, StridedSlice,
port_value_dict={1: int64_array([2]),
2: int64_array([-1])},
op_attrs={'begin_mask': [1],
'end_mask': [0],
'new_axis_mask': [0],
'shrink_axis_mask': [0],
'ellipsis_mask': [0]},
input_node=weights_shape)
const_part_of_shape = Const(node.graph, attrs=dict(name=node_name + '/GroupsAndOutputChannelsSize',
value=int64_array(
[group, node.output // group]))).create_node()
input_shape_node = Shape(node.graph, {'name': node_name + '/ShapeOfInput'}).create_node()
input_shape_node.in_port(0).connect(input_node.out_port(0))
input_num_channels = create_op_with_const_inputs(node.graph, Gather,
port_value_dict={1: int64_array([1]), 2: int64_array(0)},
op_attrs={'name': node_name + '/GatherInputNumChannels'},
input_node=input_shape_node)
# input channels num divided by number of groups to alight weights shape into [GROUPS C_OUT C_IN X Y]
C_IN = create_op_with_const_inputs(node.graph, Div,
port_value_dict={1: int64_array(group)},
op_attrs={'name': node_name + '/Div'},
input_node=input_num_channels)
new_shape_node = Concat(node.graph, {'axis': 0, 'in_ports_count': 3}).create_node()
new_shape_node.in_port(0).connect(const_part_of_shape.out_port(0))
new_shape_node.in_port(1).connect(C_IN.out_port(0))
new_shape_node.in_port(2).connect(weights_spatial_shape.out_port(0))
reshape = Reshape(node.graph, {'override_output_shape': True, 'special_zero': True}).create_node()
reshape.in_port(1).connect(new_shape_node.out_port(0))
del node['group']
node['type'] = 'GroupConvolution'
else:
raise Error("Unknown IR version: {}".format(ir_version))
reshape = create_op_node_with_second_input(node.graph, Reshape, int64_array(new_shape),
{'override_output_shape': True})
node.in_port(1).get_connection().insert_node(reshape)
@@ -104,7 +156,8 @@ class ConvolutionWithGroupsResolver(BackReplacementPattern):
force_clean_up = True
def run_before(self):
return [ReshapeMutation]
from openvino.tools.mo.back.StridedSliceMasksNormalizer import StridedSliceMasksNormalizer
return [ReshapeMutation, StridedSliceMasksNormalizer]
def run_after(self):
return [ApplyReverseChannels]
@@ -3,11 +3,13 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension, dynamic_dimension_value
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension, dynamic_dimension_value, \
undefined_shape_of_rank, compatible_shapes, compatible_dims
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.utils.error import Error
def eltwise_infer(node, op=None, **kwargs):
def eltwise_infer(node: Node, op=None, **kwargs):
def broadcast_dims(dim1, dim2):
if dim1 is not dynamic_dimension and dim2 is not dynamic_dimension:
mind = min(dim1, dim2)
@@ -80,8 +82,86 @@ def eltwise_infer(node, op=None, **kwargs):
node.out_port(0).data.set_value(op(node.out_node().value, values[i + 1]))
def eltwise_reverse_infer(node: Node):
input_1_shape = node.in_port(0).data.get_shape()
input_2_shape = node.in_port(1).data.get_shape()
if input_1_shape is not None and input_2_shape is not None:
return
output_shape = node.out_port(0).data.get_shape()
node_name = node.soft_get('name', node.id)
if node['auto_broadcast'] is 'none':
# input_1, input_2 and output shapes must match
# therefore undefined partial shapes can be exactly defined from output shape
if output_shape is not None:
most_defined_shape = output_shape
# if out_shape = [4, dyn] and input_1_shape = [dyn, 13]
# then missing shape must be [4, 13]
if input_1_shape is not None and not compatible_shapes(output_shape, input_1_shape):
raise Error("shapes are not compatible for node '{}'".format(node_name))
elif input_1_shape is not None:
most_defined_shape = find_common_partial_shape(output_shape, input_1_shape)
if input_2_shape is not None and not compatible_shapes(output_shape, input_2_shape):
raise Error("shapes are not compatible for node '{}'".format(node_name))
elif input_2_shape is not None:
most_defined_shape = find_common_partial_shape(most_defined_shape, input_2_shape)
if input_1_shape is None:
node.in_port(0).data.set_shape(most_defined_shape)
if input_2_shape is None:
node.in_port(1).data.set_shape(most_defined_shape)
elif node['auto_broadcast'] == 'numpy':
if output_shape is not None:
out_rank = len(output_shape)
deduced_in_shape = undefined_shape_of_rank(out_rank)
if input_1_shape is not None and input_2_shape is None and out_rank > len(input_1_shape):
in_port_to_update = 1
defined_in_shape = input_1_shape
elif input_2_shape is not None and input_1_shape is None and out_rank > len(input_2_shape):
in_port_to_update = 0
defined_in_shape = input_2_shape
else:
return
defined_in_rank = len(defined_in_shape)
for i in range(-1, -defined_in_rank - 1, -1):
assert defined_in_shape[i] == 1 or np.ma.is_masked(defined_in_shape[i]) \
or compatible_dims(defined_in_shape[i], output_shape[i]), \
"Shapes of Elementwise node '{}' are not compatible for reverse_infer.".format(node_name)
# if defined_input_shape = [1] and output_shape = [N, 400, 400, 3]
# partial shape information about sizes should not be lost
if defined_in_shape[i] == 1 or output_shape[i] == 1:
deduced_in_shape[i] = output_shape[i]
deduced_in_shape[:-defined_in_rank] = output_shape[:-defined_in_rank]
node.in_port(in_port_to_update).data.set_shape(deduced_in_shape)
def bias_add_infer(node, op):
if node.in_port(0).data.get_value() is not None and node.in_port(1).data.get_value() is not None and op is not None:
node.out_port(0).data.set_value(op(node.in_port(0).data.get_value(), node.in_port(1).data.get_value()))
else:
node.out_port(0).data.set_shape(node.in_port(0).data.get_shape())
def find_common_partial_shape(shape1, shape2):
"""
Extracts maximum information from partially defined shapes.
For example, if shape_1 = [4, dyn] and shape_2 = [dyn, 13]
then resulting shape will be [4, 13]
:param shape1: partially defined shape
:param shape2: partially defined shape
:return:
"""
assert compatible_shapes(shape1, shape2), 'shapes must be compatible'
res = shape1.copy()
for i, (d1, d2) in enumerate(zip(shape1, shape2)):
if np.ma.is_masked(res[i]):
res[i] = d2
return res
@@ -1,7 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.partial_infer.utils import shape_array
from openvino.tools.mo.graph.graph import Node
@@ -12,6 +12,6 @@ def multi_box_prior_infer_mxnet(node: Node):
num_ratios = len(node.aspect_ratio)
num_priors = len(node.min_size) + num_ratios - 1
if v10:
node.out_node(0).shape = int64_array([2, data_H * data_W * num_priors * 4])
node.out_node(0).shape = shape_array([2, data_H * data_W * num_priors * 4])
else:
node.out_node(0).shape = int64_array([1, 2, data_H * data_W * num_priors * 4])
node.out_node(0).shape = shape_array([1, 2, data_H * data_W * num_priors * 4])
@@ -314,3 +314,49 @@ def is_dynamic_slice(s: [slice, int, None]):
return isinstance(s, slice) and (s.start is dynamic_dimension or
s.stop is dynamic_dimension or
s.step is dynamic_dimension)
def reverse_bypass_infer(node, in_ports: List[int]):
"""
Copies shapes from the out_port 0 into ports specified in the in_ports
:param node:
:param in_ports: input ports for which shape will be updated
:return:
"""
assert node.is_out_port_connected(0)
output_shape = node.out_port(0).data.get_shape()
if output_shape is not None:
for port in in_ports:
assert node.is_in_port_connected(port)
if node.in_port(port).data.get_shape() is None:
node.in_port(port).data.set_shape(output_shape)
def clarify_partial_shape(shapes: List):
"""
returns more precise partial shape from a set of partial shapes,
e.g. pshape_1 = [dyn, 2, dyn], pshape_2 = [10, dyn, dyn] => out_shape = [10, 2, dyn]
:param shapes:
:return:
"""
assert len(shapes) > 0
out_shape = shapes[0]
for shape in shapes:
assert compatible_shapes(shape, out_shape), "shapes {} and {} are not compatible".format(
unmask_shape(shape), unmask_shape(out_shape))
shape_unmasked = shape.data.copy()
for i, dim in enumerate(shape_unmasked):
if dim != dynamic_dimension_value:
out_shape[i] = dim
return out_shape
def set_input_shapes(node, *shapes: List):
assert len(shapes) <= len(node.in_ports())
for i, shape in enumerate(shapes):
if node.is_in_port_connected(i) and node.in_port(i).data.get_shape() is None:
node.in_port(i).data.set_shape(shape)
@@ -3,13 +3,14 @@
from openvino.tools.mo.front.common.extractors.utils import layout_attrs
from openvino.tools.mo.front.common.partial_infer.batch_norm import batch_norm_4_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
def batch_norm_ext(attrs):
node_attrs = {
'type': 'BatchNormalization',
'eps': attrs.float('eps', 0.001),
'infer': batch_norm_4_infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'fix_gamma': attrs.bool('fix_gamma', False)
}
node_attrs.update(layout_attrs())
@@ -2,7 +2,6 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.caffe.extractors.utils import get_canonical_axis_index
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.utils.error import Error
@@ -26,7 +25,7 @@ def mxnet_slice_axis_infer(node):
node.axis = get_canonical_axis_index(in_shape, node.axis)
slice_axis = node.axis
new_shape = int64_array(in_shape)
new_shape = in_shape.copy()
new_shape[slice_axis] = new_shape[slice_axis] / len(node.out_nodes())
axis_size = in_shape[slice_axis]
@@ -31,15 +31,18 @@ class IteratorGetNextCut(FrontReplacementPattern):
iter_get_next_shapes = defaultdict(list)
for iter_get_next in graph.get_op_nodes(op='IteratorGetNext'):
iter_get_next_name = iter_get_next.soft_get('name', iter_get_next.id)
for port in iter_get_next.out_ports():
if not np_data_type_to_precision(iter_get_next.types[port]) in SUPPORTED_DATA_TYPES:
for port_idx, port in iter_get_next.out_ports().items():
if port.disconnected():
continue
if not np_data_type_to_precision(iter_get_next.types[port_idx]) in SUPPORTED_DATA_TYPES:
raise Error("In IteratorGetNext node '{}' data type '{}' is not supported".format(
iter_get_next_name, iter_get_next.types[port]))
iter_get_next_name, iter_get_next.types[port_idx]))
iter_get_next_shapes[iter_get_next_name].append(dict(
shape=iter_get_next.shapes[port],
out=port,
data_type=iter_get_next.types[port]
shape=iter_get_next.shapes[port_idx],
out=port_idx,
data_type=iter_get_next.types[port_idx]
))
add_input_ops(graph, iter_get_next_shapes, True)
@@ -20,5 +20,5 @@ class IteratorGetNextExtractor(FrontExtractorOp):
result_shapes = []
for shape_pb in shapes:
result_shapes.append(tf_tensor_shape(shape_pb))
Op.update_node_stat(node, {'shapes': result_shapes, 'types': extracted_types, 'out_ports_count': 1})
Op.update_node_stat(node, {'shapes': result_shapes, 'types': extracted_types, 'out_ports_count': len(result_shapes)})
return cls.enabled
@@ -1,7 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import convert_tf_padding_to_str, int64_array
from openvino.tools.mo.front.common.partial_infer.utils import convert_tf_padding_to_str, int64_array, dynamic_dimension
from openvino.tools.mo.front.extractor import FrontExtractorOp
from openvino.tools.mo.front.tf.extractors.utils import tf_data_format_channel, tf_data_format_batch, \
tf_int_list
@@ -16,9 +16,21 @@ class Conv2DFrontExtractor(FrontExtractorOp):
@classmethod
def extract(cls, node):
attrs = tf_create_attrs(node, 2, 3)
def get_num_groups(node):
if 'group' in node:
return node.group
elif node.in_node(0).shape is not None and node.kernel_shape is not None \
and node.in_node(0).shape[node.channel_dims[0]] is not dynamic_dimension \
and node.kernel_shape[node.input_feature_channel] is not dynamic_dimension:
# if group attribute is not defined, number of groups is calculated
# from number of input channels and filter channel size
return node.in_node(0).shape[node.channel_dims] // node.kernel_shape[node.input_feature_channel]
else:
return 1
attrs.update({'op': __class__.op,
'get_group': lambda node: node.group if 'group' in node and node.group is not None else
node.in_node(0).shape[node.channel_dims] // node.kernel_shape[node.input_feature_channel],
'get_group': get_num_groups,
'get_output_feature_dim': lambda node: node.kernel_shape[node.output_feature_channel],
'get_weights_permute': PermuteAttrs.Permutation(perm=int64_array([3, 2, 0, 1]),
inv=int64_array([2, 3, 1, 0]))
@@ -3,8 +3,7 @@
import logging as log
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.front.common.partial_infer.utils import shape_array
from openvino.tools.mo.front.common.partial_infer.utils import mo_array, reverse_bypass_infer, shape_array
from openvino.tools.mo.front.tf.extractors.utils import tf_dtype_extractor
@@ -24,5 +23,6 @@ def tf_fused_bn_extractor(pb):
'data_type': tf_dtype_extractor(pb.attr["T"].type),
'eps': pb.attr['epsilon'].f,
'infer': tf_fused_bn_infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'is_training': is_training
}
@@ -3,13 +3,14 @@
import numpy as np
from openvino.tools.mo.ops.transpose import Transpose
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_insert, mo_array
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_insert, mo_array, shape_array, \
unmask_shape
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.middle.replacement import MiddleReplacementPattern
from openvino.tools.mo.ops.const import Const
from openvino.tools.mo.ops.op import Op
from openvino.tools.mo.ops.reshape import Reshape
from openvino.tools.mo.ops.transpose import Transpose
class MXNetRNNSequenceNormalize(MiddleReplacementPattern):
@@ -184,9 +185,9 @@ class MXNetRNNSequenceNormalize(MiddleReplacementPattern):
mxnet_shape = lstm.out_node(0).shape.copy()
if lstm.batch_dim == 0:
mo_shape = int64_array([input.shape[lstm.batch_dim], input.shape[lstm.sequence_dim], lstm.hidden_size])
mo_shape = shape_array([input.shape[lstm.batch_dim], input.shape[lstm.sequence_dim], lstm.hidden_size])
else:
mo_shape = int64_array([input.shape[lstm.sequence_dim], input.shape[lstm.batch_dim], lstm.hidden_size])
mo_shape = shape_array([input.shape[lstm.sequence_dim], input.shape[lstm.batch_dim], lstm.hidden_size])
if lstm.has_num_directions:
mo_shape = shape_insert(mo_shape, 1, np.int64(num_directions))
@@ -206,7 +207,7 @@ class MXNetRNNSequenceNormalize(MiddleReplacementPattern):
# Add Reshape
reshape = Reshape(graph, {'name': lstm_name + '/Reshape_mxnet/'})
reshape_dim_data = Const(graph, {'name': lstm_name + '/Reshape_mxnet_dim',
'value': mxnet_shape}).create_node_with_data()
'value': int64_array(unmask_shape(mxnet_shape))}).create_node_with_data()
reshape.create_node_with_data([permute_data, reshape_dim_data], dict(), data_nodes=[old_data_node])
@@ -3,11 +3,10 @@
import logging as log
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, shape_array, \
dynamic_dimension_value
dynamic_dimension_value, unmask_shape
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.middle.passes.infer import partial_infer
from openvino.tools.mo.middle.replacement import MiddleReplacementPattern
from openvino.tools.mo.ops.parameter import Parameter
class PartialInfer(MiddleReplacementPattern):
@@ -27,7 +26,7 @@ class PartialInfer(MiddleReplacementPattern):
param_shape = parameter.soft_get('shape', shape_array(dynamic_dimension_value))
if not is_fully_defined(param_shape):
parameter_name = parameter.soft_get('name', parameter.id)
dynamic_inputs[parameter_name] = parameter
dynamic_inputs[parameter_name] = param_shape
if dynamic_inputs:
log.error('The model contains input(s) with partially defined shapes: {}. '
'Starting from the 2022.1 release the Model Optimizer can generate an IR with partially defined '
@@ -36,7 +35,7 @@ class PartialInfer(MiddleReplacementPattern):
'call "reshape" method in the Inference Engine and specify static input shapes. For optimal '
'performance, it is still recommended to update input shapes with fixed ones using "--input" or '
'"--input_shape" command-line parameters.'
.format(','.join('name="{}" shape="{}"'.format(name, Parameter.shape_serialize(parameter))
for name, parameter in dynamic_inputs.items())),
.format(','.join(f'name="{name}" shape="{unmask_shape(param_shape)}"'
for name, param_shape in dynamic_inputs.items())),
extra={'is_warning': True})
partial_infer(graph)
@@ -117,7 +117,7 @@ def partial_infer(graph: Graph, start_node: str = None):
not_fully_inferred = graph.get_nodes_with_attributes(is_not_fully_inferred=True)
for n in not_fully_inferred:
node = Node(graph, n)
if node.has('infer') and not node.infer is None:
if node.has_and_set('infer'):
node.infer(node)
return graph
@@ -145,6 +145,7 @@ def infer_nodes(graph: Graph, nodes: List[Node], constant_subgraph_only: bool =
in_values = [port.data.get_value() for port in node.in_ports().values()]
if node.soft_get('op') == 'Parameter' or any(value is None for value in in_values) or \
(node.soft_get('op') == 'ShapeOf' and node.in_port(0).data.get_shape() is None):
# if here will be any new ShapeOf type operation, we should update condition above
continue
if debug_logger:
@@ -335,8 +336,20 @@ def copy_type_infer(node):
def reverse_infer(graph: Graph, nodes: list):
nodes = reversed(nodes)
debug_logger = log.getLogger().isEnabledFor(log.DEBUG)
for n in nodes:
node = Node(graph, n)
if node.has_valid('reverse_infer'):
if node.has_and_set('reverse_infer'):
log.debug("Executed reverse infer for node '{}'".format(node.soft_get('name', node.id)))
node.reverse_infer(node)
if debug_logger:
log.debug('-' * 20)
log.debug('Reverse infer for {}'.format(node.soft_get('name')))
log.debug('Op: {}'.format(node.soft_get('op')))
log.debug('Outputs:')
log_debug_dict(node.out_nodes(), 'outputs')
log.debug('Inputs:')
log_debug_dict(node.in_nodes(), 'inputs')
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -19,8 +20,10 @@ class BatchNormInference(Op):
'op': self.op,
'in_ports_count': 5,
'out_ports_count': 1,
'infer': self.infer
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}, attrs)
@staticmethod
def infer(node):
node.out_port(0).data.set_shape(node.in_port(0).data.get_shape())
+2
View File
@@ -5,6 +5,7 @@ import logging as log
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.middle.passes.convert_data_type import np_data_type_to_precision, convert_blob, \
np_data_type_to_destination_type, packed_I4, packed_U4
@@ -22,6 +23,7 @@ class Cast(Op):
'type': 'Convert',
'version': 'opset1',
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'type_infer': self.type_infer,
'dst_type': None,
'in_ports_count': 1,
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.elemental import copy_shape_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -17,6 +18,7 @@ class ConvertLike(Op):
'version': 'opset1',
'infer': copy_shape_infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'type_infer': self.type_infer,
'in_ports_count': 2,
@@ -3,10 +3,10 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, compatible_dims
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, compatible_dims, \
undefined_shape_of_rank, set_input_shapes
from openvino.tools.mo.front.extractor import bool_to_str
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.ops.op import Op
from openvino.tools.mo.utils.error import Error
@@ -23,6 +23,7 @@ class DetectionOutput(Op):
'in_ports_count': 3,
'out_ports_count': 1,
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'input_width': 1,
'input_height': 1,
'normalized': True,
@@ -104,3 +105,20 @@ class DetectionOutput(Op):
# the line below is needed for the TF framework so the MO will not change the layout
node.graph.node[node.out_node(0).id]['nchw_layout'] = True
@staticmethod
def reverse_infer(node):
num_in_ports = len(node.in_ports())
assert num_in_ports in [3, 6], 'incorrect number of input ports for DetectionOutput node {}'.format(node.soft_get('name', node.id))
if num_in_ports == 3:
set_input_shapes(node,
undefined_shape_of_rank(2),
undefined_shape_of_rank(2),
undefined_shape_of_rank(3))
elif num_in_ports == 6:
set_input_shapes(node,
undefined_shape_of_rank(2),
undefined_shape_of_rank(2),
undefined_shape_of_rank(3),
undefined_shape_of_rank(2),
undefined_shape_of_rank(2))
+2 -1
View File
@@ -1,9 +1,9 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.ops.RNN import rnn_infer
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.RNN import rnn_infer, RNN
from openvino.tools.mo.ops.op import Op
@@ -18,6 +18,7 @@ class GRU(Op):
'has_num_directions': False,
'direction': 'forward',
'infer': __class__.infer,
'reverse_infer': RNN.reverse_infer,
'multiplier': 3,
'multilayers': False,
'gate_order': mo_array([0, 1, 2]), # TODO: change it later
+2 -1
View File
@@ -1,8 +1,8 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.ops.RNN import rnn_infer
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.RNN import rnn_infer, RNN
from openvino.tools.mo.ops.op import Op
@@ -17,6 +17,7 @@ class LSTM(Op):
'has_num_directions': False, # if True, output shape has 4 dimensions; 3D otherwise
'direction': 'forward',
'infer': self.infer,
'reverse_infer': RNN.reverse_infer,
'multiplier': 4,
'gate_order': None,
'normalized': False,
+117 -2
View File
@@ -1,11 +1,14 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import logging as log
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import mark_input_bins, shape_array, shape_insert
from openvino.tools.mo.front.common.partial_infer.utils import mark_input_bins, shape_insert, dynamic_dimension, \
shape_array
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.graph.graph import Node, Graph, add_opoutput
from openvino.tools.mo.graph.graph import Node, Graph, add_opoutput, Error
from openvino.tools.mo.ops.op import Op
@@ -20,6 +23,7 @@ class RNN(Op):
'has_num_directions': False,
'direction': 'forward',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'multiplier': 1,
'gate_order': mo_array([0]), # Only one gate in this cell
'normalized': False,
@@ -68,6 +72,20 @@ class RNN(Op):
rnn_infer(node, [1])
@staticmethod
def reverse_infer(node: Node):
if node.in_port(0).data.get_shape() is not None:
return
input_size = get_rnn_input_size(node)
batch_size, seq_len = get_rnn_batch_size_and_seq_len(node)
# MXNet, ONNX has the same input layout
input_shape = shape_array([seq_len, batch_size, input_size])
if node.format == 'tf':
input_shape = shape_array([batch_size, seq_len, input_size])
node.in_port(0).data.set_shape(input_shape)
def rnn_infer(node: Node, out_ports=None):
"""
@@ -154,3 +172,100 @@ def rnn_infer(node: Node, out_ports=None):
else:
data_node = node.out_node(i)
data_node.shape = shape_array(state_size)
def get_rnn_batch_size_and_seq_len(node: Node):
"""
Gets batch_size and sequence_length from RNN constant inputs
and output shapes retrieved during reverse_infer
:param node:
:return:
"""
node_name = node.soft_get('name', node.id)
out_shape = node.out_port(0).data.get_shape()
batch_size = dynamic_dimension
seq_len = dynamic_dimension
in_port_with_initial_states = 3 # initial hidden size values is framework dependent
if out_shape is not None:
# note that op is not in opset state but in the state of the original framework
if node.batch_dim == 1:
seq_len = out_shape[0]
if node.format == 'mxnet':
assert len(out_shape) == 3, 'incorrect out_shape rank for node {}'.format(node_name)
# for MXNet out_shape = [seq_len, batch_size, hidden_size]
batch_size = out_shape[1]
in_port_with_initial_states = 2
elif node.format == 'onnx':
assert len(out_shape) == 4, 'incorrect out_shape rank for node {}'.format(node_name)
# even for ONNX in extractor 'batch_dim': 1 (front/onnx/lstm_ext.py:26) despite the fact that
# out_shape = [seq_len, num_directions, batch_size, hidden_size]
batch_size = out_shape[2]
in_port_with_initial_states = 5
elif node.format == 'tf':
log.error('reverse infer for TensorFlow RNN operation {} is not implemented yet'.format(node_name),
extra={'is_warning': True})
else:
raise Error('Incorrect framework name')
elif node.batch_dim == 0:
# out_shape = [batch_size, num_directions, seq_len, hidden_size]
batch_size = out_shape[0]
seq_len = out_shape[2]
in_port_with_initial_states = 3
else:
raise Error('incorrect batch_dim for node {}'.format(node_name))
if batch_size is dynamic_dimension:
if node.is_in_port_connected(in_port_with_initial_states):
initial_hidden_state_size = node.in_port(in_port_with_initial_states).data.get_shape()
if initial_hidden_state_size is not None:
batch_size = initial_hidden_state_size[1]
if seq_len is dynamic_dimension and node.format == 'onnx':
# ONNX can store seq_len in optional input
if node.is_in_port_connected(4):
seq_len_val = node.in_port(4).data.get_value()
if seq_len_val is not None:
seq_len = seq_len.item()
return [batch_size, seq_len]
def get_rnn_input_size(node: Node):
node_name = node.soft_get('name', node.id)
assert node.is_in_port_connected(1), 'weights input is not connected'
if node.format == 'onnx':
# ONNX weights on input 1 contain only W part, R, and B are connected separately
# weights_shape = `[num_directions, 4 * hidden_size, input_size]`
weights_size = node.in_port(1).data.get_shape()
assert len(weights_size) == 3, 'incorrect weights ranks for MXNet {} node {}'.format(node.op, node_name)
input_size = weights_size[2]
return input_size
elif node.format == 'mxnet':
multiplier = node.multiplier
hidden_size = node.hidden_size
num_layers = node.num_layers
direction = 2 if node.has_num_directions else 1
# for MXNet models we always get flattened weights which contains WRB
weights_size = node.in_port(1).data.get_shape()
assert len(weights_size) == 1, 'incorrect weights ranks for MXNet {} node {}'.format(node.op, node_name)
weights_size = weights_size[0]
size = hidden_size * direction * multiplier
other_layer_params_size = (hidden_size * direction + hidden_size + 2) * size
first_layer_params_size = weights_size - (num_layers - 1) * other_layer_params_size
# lhe lines above to find first_layer_params_size was taken from MXNetSplitMultiLayers.py:79
# input_size can be calculated from the first_layer_params_size
# if first_layer_params_size = (input_size + hidden_size + 2) * size
# then input_size = first_layer_params_size / size - 2 - hidden_size
input_size = first_layer_params_size / size - 2 - hidden_size
return input_size
elif node.format == 'tf':
log.error('reverse infer for TensorFlow RNN operation {} is not implemented yet'.format(node_name),
extra={'is_warning': True})
else:
raise Error('Incorrect framework name')
+2 -1
View File
@@ -3,7 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -19,6 +19,7 @@ class Reverse(Op):
'in_ports_count': 2,
'out_ports_count': 1,
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}
super().__init__(graph, mandatory_props, attrs)
@@ -4,6 +4,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.ops.clamp import AttributedClamp
from openvino.tools.mo.ops.op import Op
@@ -24,6 +25,7 @@ class Activation(Op):
'operation': self.operation,
'version': self.version,
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'in_ports_count': 1,
'out_ports_count': 1,
}, attrs)
+18
View File
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.concat import concat_infer
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.ops.op import Op
@@ -16,8 +18,24 @@ class Concat(Op):
'version': 'opset1',
'axis': 1,
'infer': concat_infer,
'reverse_infer': self.reverse_infer,
'out_ports_count': 1,
}, attrs)
def supported_attrs(self):
return ['axis']
@staticmethod
def reverse_infer(node: Node):
assert hasattr(node, 'axis')
out_shape = node.out_port(0).data.get_shape()
if out_shape is None:
return
out_shape[node.axis] = dynamic_dimension
for in_port in node.in_ports().values():
in_shape = in_port.data.get_shape()
if in_shape is None:
in_port.data.set_shape(out_shape)
@@ -7,7 +7,6 @@ import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, mark_input_bins, assign_dims_to_weights, \
tf_window_op_pad_infer, dynamic_dimension_value, shape_array, is_fully_defined, undefined_shape_of_rank
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.front.onnx.extractors.utils import get_backend_pad
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.graph.perm_inputs import PermuteInputs
@@ -115,8 +114,12 @@ class Convolution(Op):
'group') and node.has_valid('kernel_spatial')):
log.error('Cannot reshape kernel due to not all required attrs was set to {} node'.format(node.id))
return
# since item() unmasks values, result should be masked back
num_in_channels = shape_array(input_shape[node.channel_dims].item())
# layout for Convolution weights is OIHW
kernel_shape = shape_array([node.output, input_shape[node.channel_dims].item() / node.group,
kernel_shape = shape_array([node.output, num_in_channels / node.group,
*[node.kernel_spatial[i] for i in range(len(node.kernel_spatial))]])
if node.type == 'Deconvolution': # layout for Deconvolution weights is IOHW
kernel_shape[[0, 1]] = kernel_shape[[1, 0]]
@@ -1,7 +1,8 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.front.common.partial_infer.utils import undefined_shape_of_rank
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.ops.convolution import Convolution
from openvino.tools.mo.ops.op import Op
@@ -31,3 +32,12 @@ class DeformableConvolution(Op):
if self.get_opset() == 'opset8':
attrs.append('bilinear_interpolation_pad')
return attrs
@staticmethod
def reverse_infer(node: Node):
input_shape_1 = node.in_port(0).data.get_shape()
input_shape_2 = node.in_port(1).data.get_shape()
if input_shape_1 is None:
node.in_port(0).data.set_shape(undefined_shape_of_rank(4))
if input_shape_2 is None:
node.in_port(1).data.set_shape(undefined_shape_of_rank(4))
@@ -3,6 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -16,6 +17,7 @@ class ExperimentalDetectronDetectionOutput(Op):
op=self.op,
version='opset6',
infer=self.infer,
reverse_infer=self.reverse_infer,
type_infer=self.type_infer,
in_ports_count=4,
out_ports_count=3,
@@ -54,3 +56,11 @@ class ExperimentalDetectronDetectionOutput(Op):
node.out_port(2).set_data_type(in_data_type)
if node.is_out_port_connected(3):
node.out_port(3).set_data_type(np.int32) # the fourth output contains batch indices
@staticmethod
def reverse_infer(node):
set_input_shapes(node,
shape_array([dynamic_dimension_value, 4]),
shape_array([dynamic_dimension_value, node['num_classes'] * 4]),
shape_array([dynamic_dimension_value, node['num_classes']]),
shape_array([1, 3]))
@@ -3,7 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer, bias_add_infer
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer, bias_add_infer, eltwise_reverse_infer
from openvino.tools.mo.front.common.partial_infer.utils import float32_array
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.middle.passes.infer import copy_type_infer
@@ -24,6 +24,7 @@ class Elementwise(Op):
'type': self.op_type,
'version': self.version,
'infer': lambda node: eltwise_infer(node, self.operation),
'reverse_infer': eltwise_reverse_infer,
'type_infer': self.type_infer,
'can_be_bias': True,
'can_be_fused': True,
+41 -2
View File
@@ -4,8 +4,8 @@
import numpy as np
from openvino.tools.mo.front.caffe.extractors.utils import get_canonical_axis_index
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, is_fully_defined
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
from openvino.tools.mo.front.common.partial_infer.utils import mo_array, int64_array, is_fully_defined, shape_array, \
dynamic_dimension_value
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op, PermuteAttrs
from openvino.tools.mo.utils.error import Error
@@ -23,6 +23,7 @@ class Gather(Op):
'batch_dims': 0,
'reinterp_shape': True,
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'force_precision_in_ports': {1: 'int32', 2: 'int64'},
'in_ports_count': 3,
'out_ports_count': 1,
@@ -100,6 +101,43 @@ class Gather(Op):
else:
node.out_port(0).data.set_shape(out_shape)
@staticmethod
def reverse_infer(node: Node):
out_shape = node.out_port(0).data.get_shape()
data_shape = node.in_port(0).data.get_shape()
indices_shape = node.in_port(1).data.get_shape()
batch_dims = node.batch_dims
batch_dims = batch_dims + len(indices_shape) if batch_dims < 0 else batch_dims
axis = node.in_port(2).data.get_value()
# axis of Gather could be accepted as both scalar and 1D tensor
if isinstance(axis, np.ndarray):
axis = axis.item()
assert axis is not None, 'axis input is undefined'
# we can deduce data or indices partial shapes from output shape calculation formula
# out_shape = Concat(data_shape[:axis], indices_shape[batch_dims:batch_dims + indices_rank], data_shape[axis + 1:])
# data partial shape is unknown
if out_shape is not None and data_shape is None and indices_shape is not None:
out_rank = len(out_shape)
indices_rank = len(indices_shape)
deduced_data_shape = out_shape.tolist(dynamic_dimension_value)
for i in range(indices_rank):
deduced_data_shape.pop(axis)
deduced_data_shape.insert(axis, dynamic_dimension_value)
node.in_port(0).data.set_shape(shape_array(deduced_data_shape))
# indices partial shape is unknown
if out_shape is not None and indices_shape is None and data_shape is not None:
out_rank = len(out_shape)
data_rank = len(data_shape)
indices_rank = out_rank + 1 - data_rank + batch_dims
indices_shape = out_shape[axis:axis + indices_rank]
node.in_port(1).data.set_shape(indices_shape)
class AttributedGather(Op):
op = 'AttributedGather'
@@ -113,6 +151,7 @@ class AttributedGather(Op):
'axis': 0,
'reinterp_shape': True,
'infer': self.infer,
# reverse_infer is not needed since is replaced by Gather on the front (AttributedGatherNormalizer)
'force_precision_in_ports': {1: 'int32'},
+3 -1
View File
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.elemental import copy_shape_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -16,7 +17,8 @@ class GRNOp(Op):
'version': 'opset1',
'in_ports_count': 1,
'out_ports_count': 1,
'infer': copy_shape_infer
'infer': copy_shape_infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}
super().__init__(graph, mandatory_props, attrs)
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -16,6 +17,7 @@ class Identity(Op):
'identity': True,
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'in_ports_count': 1,
'out_ports_count': 1,
+2 -1
View File
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -24,7 +25,7 @@ class LRN(Op):
'version': 'opset1',
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'in_ports_count': 2,
'out_ports_count': 1,
}, attrs)
+3 -1
View File
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.elemental import copy_shape_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.front.extractor import bool_to_str
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.graph.perm_inputs import PermuteInputs
@@ -24,7 +25,8 @@ class MVN(Op):
'eps_mode': None,
'in_ports_count': 2,
'out_ports_count': 1,
'infer': self.infer
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}, attrs)
def supported_attrs(self):
@@ -5,7 +5,8 @@ import logging as log
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension, shape_array, dynamic_dimension_value
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension, shape_array, dynamic_dimension_value, \
set_input_shapes, undefined_shape_of_rank
from openvino.tools.mo.front.extractor import bool_to_str
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.middle.passes.convert_data_type import np_data_type_to_destination_type
@@ -22,6 +23,7 @@ class NonMaxSuppression(Op):
'op': self.op,
'version': 'opset5',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'output_type': np.int64,
'box_encoding': 'corner',
'in_ports_count': 5,
@@ -116,3 +118,7 @@ class NonMaxSuppression(Op):
node.out_port(0).set_data_type(node.output_type)
else:
node.out_port(0).set_data_type(np.int64)
@staticmethod
def reverse_infer(node):
set_input_shapes(node, undefined_shape_of_rank(3), undefined_shape_of_rank(3))
@@ -3,6 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.graph.perm_inputs import PermuteInputs
from openvino.tools.mo.ops.op import Op
@@ -22,7 +23,8 @@ class NormalizeL2Op(Op):
'eps_mode': None,
'in_ports_count': 2,
'out_ports_count': 1,
'infer': self.infer
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}, attrs)
def supported_attrs(self):
@@ -77,4 +77,3 @@ class Parameter(Op):
shape = node.soft_get('shape', None)
if shape is None and node.out_port(0).data.get_shape() is not None:
node['shape'] = node.out_port(0).data.get_shape()
@@ -4,7 +4,7 @@
import numpy as np
from openvino.tools.mo.front.common.layout import get_width_dim, get_height_dim
from openvino.tools.mo.front.extractor import attr_getter, bool_to_str
from openvino.tools.mo.front.extractor import attr_getter
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import shape_array, dynamic_dimension_value, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -13,6 +14,7 @@ class ExperimentalDetectronPriorGridGenerator(Op):
op=self.op,
version='opset6',
infer=self.infer,
reverse_infer=self.reverse_infer,
)
super().__init__(graph, mandatory_props, attrs)
@@ -36,3 +38,11 @@ class ExperimentalDetectronPriorGridGenerator(Op):
else:
out_shape = [grid_h, grid_w, priors_num, 4]
node.out_port(0).data.set_shape(out_shape)
@staticmethod
def reverse_infer(node):
priors_shape = shape_array([dynamic_dimension_value, 4])
feature_map_shape = shape_array([1, dynamic_dimension_value, dynamic_dimension_value, dynamic_dimension_value])
image_shape = shape_array([1, dynamic_dimension_value, dynamic_dimension_value, dynamic_dimension_value])
set_input_shapes(node, priors_shape, feature_map_shape, image_shape)
+6 -1
View File
@@ -1,7 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.partial_infer.utils import undefined_shape_of_rank, set_input_shapes
from openvino.tools.mo.front.extractor import attr_getter, bool_to_str
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -17,6 +17,7 @@ class ProposalOp(Op):
'version': 'opset4',
'post_nms_topn': 300, # default in caffe-shared
'infer': ProposalOp.proposal_infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 3,
'out_ports_count': 1 if attrs.get('version') == 'opset1' else 2,
'normalize': False,
@@ -66,3 +67,7 @@ class ProposalOp(Op):
# the second optional output contains box probabilities
if len(node.out_ports()) == 2 and not node.out_port(1).disconnected():
node.out_port(1).data.set_shape([input_shape[0] * node.post_nms_topn])
@staticmethod
def reverse_infer(node):
set_input_shapes(node, undefined_shape_of_rank(4), undefined_shape_of_rank(4))
@@ -1,6 +1,8 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, \
undefined_shape_of_rank, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -12,6 +14,7 @@ class ExperimentalDetectronGenerateProposalsSingleImage(Op):
type=self.op,
op=self.op,
version='experimental',
reverse_infer=self.reverse_infer,
infer=self.infer
)
@@ -29,3 +32,11 @@ class ExperimentalDetectronGenerateProposalsSingleImage(Op):
def infer(node):
node.out_port(0).data.set_shape([node.post_nms_count, 4])
node.out_port(1).data.set_shape([node.post_nms_count])
@staticmethod
def reverse_infer(node):
set_input_shapes(node,
undefined_shape_of_rank(1),
shape_array([dynamic_dimension_value, 4]),
undefined_shape_of_rank(3),
undefined_shape_of_rank(3))
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.layout import get_batch_dim, shape_for_layout
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, \
undefined_shape_of_rank, set_input_shapes
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -20,6 +22,7 @@ class PSROIPoolingOp(Op):
'out_ports_count': 1,
'trans_std': 0,
'no_trans': True,
'reverse_infer': self.reverse_infer,
'infer': PSROIPoolingOp.psroipooling_infer
}
super().__init__(graph, mandatory_props, attrs)
@@ -57,6 +60,10 @@ class PSROIPoolingOp(Op):
height=node.group_size,
width=node.group_size)
@staticmethod
def reverse_infer(node):
set_input_shapes(node, undefined_shape_of_rank(4), shape_array([dynamic_dimension_value, 5]))
class DeformablePSROIPoolingOp(PSROIPoolingOp):
op = 'DeformablePSROIPooling'
@@ -76,3 +83,13 @@ class DeformablePSROIPoolingOp(PSROIPoolingOp):
def supported_attrs(self):
return super().supported_attrs() + ['trans_std', 'part_size']
@staticmethod
def reverse_infer(node):
transformation_values_shape = shape_array(
[dynamic_dimension_value, dynamic_dimension_value, int(node.group_size), int(node.group_size)])
set_input_shapes(node,
undefined_shape_of_rank(4),
shape_array([dynamic_dimension_value, 5]),
transformation_values_shape)
+8 -1
View File
@@ -5,7 +5,8 @@ import numpy as np
from openvino.tools.mo.front.caffe.extractors.utils import get_canonical_axis_index
from openvino.tools.mo.front.common.layout import get_batch_dim, get_height_dim, get_width_dim, shape_for_layout
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, dynamic_dimension_value
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, dynamic_dimension_value, \
undefined_shape_of_rank
from openvino.tools.mo.front.extractor import attr_getter, bool_to_str
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -22,6 +23,7 @@ class RegionYoloOp(Op):
'version': 'opset1',
'in_ports_count': 1,
'out_ports_count': 1,
'reverse_infer': self.reverse_infer,
'infer': RegionYoloOp.regionyolo_infer
}
super().__init__(graph, mandatory_props, attrs)
@@ -73,3 +75,8 @@ class RegionYoloOp(Op):
features=(node.classes + node.coords + 1) * len(node.mask),
height=input_shape[get_height_dim(layout, 4)],
width=input_shape[get_width_dim(layout, 4)]))
@staticmethod
def reverse_infer(node):
if node.in_port(0).data.get_shape() is None:
node.in_port(0).data.set_shape(undefined_shape_of_rank(4))
@@ -3,6 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import undefined_shape_of_rank
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op, PermuteAttrs
from openvino.tools.mo.utils.error import Error
@@ -16,6 +17,7 @@ class ReorgYoloOp(Op):
'type': self.op,
'op': self.op,
'version': 'opset2',
'reverse_infer': self.reverse_infer,
'infer': ReorgYoloOp.reorgyolo_infer
}
super().__init__(graph, mandatory_props, attrs)
@@ -41,3 +43,8 @@ class ReorgYoloOp(Op):
node.out_port(0).data.set_shape(output_shape)
PermuteAttrs.create_permute_attrs(node, attrs=[('channel_dims', 'input:0'), ('spatial_dims', 'input:0')])
@staticmethod
def reverse_infer(node):
if node.in_port(0).data.get_shape() is None:
node.in_port(0).data.set_shape(undefined_shape_of_rank(4))
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op, PermuteAttrs
@@ -18,6 +19,7 @@ class ReverseSequence(Op):
'in_ports_count': 2,
'out_ports_count': 1,
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
}
super().__init__(graph, mandatory_props, attrs)
+10 -1
View File
@@ -2,7 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.layout import get_features_dim, shape_for_layout
from openvino.tools.mo.front.common.partial_infer.utils import compatible_dims
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, \
undefined_shape_of_rank, set_input_shapes, compatible_dims
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -24,6 +25,7 @@ class ROIAlign(Op):
'version': 'opset3',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 3,
'out_ports_count': 1,
@@ -69,3 +71,10 @@ class ROIAlign(Op):
height=node.pooled_h,
width=node.pooled_w)
)
@staticmethod
def reverse_infer(node):
set_input_shapes(node,
undefined_shape_of_rank(4),
shape_array([dynamic_dimension_value, 4]),
undefined_shape_of_rank(1))
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -13,6 +14,7 @@ class ExperimentalDetectronROIFeatureExtractor(Op):
op=self.op,
version='opset6',
infer=self.infer,
reverse_infer=self.reverse_infer,
out_ports_count=2,
)
@@ -34,3 +36,9 @@ class ExperimentalDetectronROIFeatureExtractor(Op):
node.out_port(0).data.set_shape([rois_num, channels_num, node.output_size, node.output_size])
if not node.out_port(1).disconnected():
node.out_port(1).data.set_shape([rois_num, 4])
@staticmethod
def reverse_infer(node):
set_input_shapes(node,
shape_array([dynamic_dimension_value, 4]),
shape_array([1, dynamic_dimension_value, dynamic_dimension_value, dynamic_dimension_value]))
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.roipooling import roipooling_infer
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, \
undefined_shape_of_rank, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -19,9 +21,14 @@ class ROIPooling(Op):
'spatial_scale': 0.0625,
'method': 'max',
'infer': roipooling_infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 2,
'out_ports_count': 1,
}, attrs)
def supported_attrs(self):
return ['pooled_h', 'pooled_w', 'spatial_scale', 'method']
@staticmethod
def reverse_infer(node):
set_input_shapes(node, undefined_shape_of_rank(4), shape_array([dynamic_dimension_value, 5]))
+2
View File
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.elemental import copy_shape_infer
from openvino.tools.mo.front.common.partial_infer.utils import reverse_bypass_infer
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.graph.perm_inputs import PermuteInputs
from openvino.tools.mo.ops.op import Op
@@ -20,6 +21,7 @@ class Roll(Op):
'op': self.op,
'version': 'opset7',
'infer': roll_infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'in_ports_count': 3,
'out_ports_count': 1
}, attrs)
+2 -1
View File
@@ -3,7 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import compatible_shapes
from openvino.tools.mo.front.common.partial_infer.utils import compatible_shapes, reverse_bypass_infer
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import Op
@@ -25,6 +25,7 @@ class Scatter(Op):
'is_scatter': True, # is used for gathering all types of scatters in common transformations
'infer': self.infer,
'reverse_infer': lambda node: reverse_bypass_infer(node, in_ports=[0]),
'in_ports_count': 4,
'out_ports_count': 1,
+4 -4
View File
@@ -4,7 +4,7 @@
import numpy as np
from openvino.tools.mo.front.caffe.extractors.utils import get_canonical_axis_index
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, is_fully_defined
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.op import Op
@@ -29,7 +29,7 @@ class SliceLike(Op):
input_value = node.in_port(0).data.get_value()
shape_like = node.in_port(1).data.get_shape()
new_shape = np.copy(input_shape)
new_shape = shape_array(input_shape.copy())
if node.axes is not None:
node.axes = sorted([get_canonical_axis_index(input_shape, i) for i in node.axes])
for i in node.axes:
@@ -38,10 +38,10 @@ class SliceLike(Op):
assert input_shape.size == shape_like.size,\
'Input shape ranks are inconsistent: {} and {}'.format(input_shape.size, shape_like.size)
node.axes = int64_array(range(shape_like.size))
new_shape = np.copy(shape_like)
new_shape = shape_like.copy()
node.out_port(0).data.set_shape(new_shape)
if input_value is not None:
if input_value is not None and is_fully_defined(new_shape):
out_value = np.copy(input_value)
slice_indexes = []
+57 -1
View File
@@ -5,7 +5,8 @@ import logging as log
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, is_fully_defined, dynamic_dimension, shape_delete
from openvino.tools.mo.front.common.partial_infer.utils import is_fully_defined, dynamic_dimension, shape_delete, \
clarify_partial_shape, shape_array
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.graph.perm_inputs import PermuteInputs
from openvino.tools.mo.ops.op import Op, PermuteAttrs
@@ -107,6 +108,18 @@ class VariadicSplitBase(Op):
elif op == 'AttributedVariadicSplit':
PermuteAttrs.create_permute_attrs(node, attrs=[('axis', 'input:0')])
@staticmethod
def reverse_infer(node: Node):
if node.in_port(0).data.get_shape() is not None:
return
axis = node.in_port(1).data.get_value() if node.op == 'VariadicSplit' else node.soft_get('axis', None)
assert axis is not None, '{} `axis` is unknown for node {}'.format(node.op, node.soft_get('name', node.id))
split_lengths = node.in_port(2).data.get_value() if node.op == 'VariadicSplit' else node.soft_get('split_lengths', None)
assert split_lengths is not None, '{} `split_lengths` is unknown for node {}'.format(node.op, node.soft_get('name', node.id))
split_reverse_infer(node, len(split_lengths), axis)
class VariadicSplit(VariadicSplitBase):
op = 'VariadicSplit'
@@ -129,6 +142,7 @@ class VariadicSplit(VariadicSplitBase):
'type': self.op,
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 3,
}, attrs)
@@ -154,6 +168,7 @@ class AttributedVariadicSplit(VariadicSplitBase):
'version': 'opset1',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 1,
}, attrs)
@@ -212,6 +227,16 @@ class SplitBase(Op):
elif op == 'AttributedSplit':
PermuteAttrs.create_permute_attrs(node, attrs=[('axis', 'input:0')])
@staticmethod
def reverse_infer(node: Node):
if node.in_port(0).data.get_shape() is not None:
return
assert hasattr(node, 'num_splits')
axis = node.in_port(1).data.get_value() if node.op == 'Split' else node.soft_get('axis', None)
assert axis is not None, '{} `axis` is unknown for node {}'.format(node.op, node.soft_get('name', node.id))
split_reverse_infer(node, node['num_splits'], axis)
class Split(SplitBase):
op = 'Split'
@@ -227,6 +252,7 @@ class Split(SplitBase):
'version': 'opset1',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 2,
}, attrs)
@@ -255,6 +281,7 @@ class AttributedSplit(SplitBase):
'axis': 1,
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 1,
}, attrs)
@@ -263,3 +290,32 @@ class AttributedSplit(SplitBase):
def supported_attrs(self):
return ['num_splits', 'axis']
def split_reverse_infer(node: Node, num_splits: int, axis: int):
aggregated_size_along_axis = 0
shapes = []
for i in range(num_splits):
shape = node.out_port(i).data.get_shape()
if shape is not None:
# if out_shape_1 = [dyn, 4, 3], out_shape_2 = [7, dyn, 3], axis = 2
# to get the original source shape [7, 4, 6]
# dimensions along axis must be summed while
# for other dimensions clarify_partial_shape should be called
aggregated_size_along_axis += shape[axis]
# in order to be able to call clarify_partial_shape axis dimension is masked into dynamic
shape[axis] = dynamic_dimension
shapes.append(shape_array(shape))
else:
# if at least one output shape is None/undefined
# set value of shape along axis into dynamic
# dynamic_dimension + static_value = dynamic_dimension
aggregated_size_along_axis = dynamic_dimension
continue
if len(shapes) == 0:
return
res_partial_shape = clarify_partial_shape(shapes)
res_partial_shape[axis] = aggregated_size_along_axis
node.in_port(0).data.set_shape(res_partial_shape)
@@ -3,6 +3,7 @@
import numpy as np
from openvino.tools.mo.front.common.partial_infer.utils import shape_array
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.op import PermuteAttrs, Op
@@ -15,6 +16,7 @@ class SwapAxis(Op):
super().__init__(graph, {
'op': self.op,
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'in_ports_count': 1,
'out_ports_count': 1,
}, attrs)
@@ -30,3 +32,11 @@ class SwapAxis(Op):
node.out_port(0).data.set_value(np.transpose(node.in_port(0).data.get_value(), axes=node.order))
PermuteAttrs.create_permute_attrs(node, attrs=[('order', 'input:0')])
@staticmethod
def reverse_infer(node: Node):
output_shape = node.out_port(0).data.get_shape()
if node.in_port(0).data.get_shape() is None and output_shape is not None:
input_shape = output_shape.data.copy()
input_shape[node.dim2], input_shape[node.dim1] = input_shape[node.dim1], input_shape[node.dim2]
node.in_port(0).data.set_shape(shape_array(input_shape))
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.tools.mo.front.common.partial_infer.utils import dynamic_dimension_value, shape_array, set_input_shapes
from openvino.tools.mo.ops.op import Op
@@ -12,6 +13,7 @@ class ExperimentalDetectronTopKROIs(Op):
type=self.op,
op=self.op,
version='experimental',
reverse_infer=self.reverse_infer,
infer=self.infer
)
super().__init__(graph, mandatory_props, attrs)
@@ -22,3 +24,7 @@ class ExperimentalDetectronTopKROIs(Op):
@staticmethod
def infer(node):
node.out_port(0).data.set_shape([node.max_rois, 4])
@staticmethod
def reverse_infer(node):
set_input_shapes(node, shape_array([dynamic_dimension_value, 4]), shape_array([dynamic_dimension_value]))
+16 -2
View File
@@ -3,7 +3,8 @@
import numpy as np
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.front.common.partial_infer.utils import shape_array
from openvino.tools.mo.graph.graph import Graph, Node
from openvino.tools.mo.ops.op import Op
@@ -17,13 +18,14 @@ class Transpose(Op):
'op': self.op,
'version': 'opset1',
'infer': self.infer,
'reverse_infer': self.reverse_infer,
'force_precision_in_ports': {1: 'int64'},
'in_ports_count': 2,
'out_ports_count': 1,
}, attrs)
@staticmethod
def infer(node):
def infer(node: Node):
# order parameter calculation and checks
in_ports = node.in_ports()
connected_ports = [port for port in in_ports.values() if not port.disconnected()]
@@ -49,4 +51,16 @@ class Transpose(Op):
else:
node.out_port(0).data.set_shape(input_shape[order])
@staticmethod
def reverse_infer(node: Node):
input_shape = node.in_port(0).data.get_shape()
output_shape = node.out_port(0).data.get_shape()
order = node.in_port(1).data.get_value()
if input_shape is None and output_shape is not None and order is not None:
output_shape_unmasked = output_shape.data.copy()
input_shape_unmasked = output_shape.data.copy()
for curr_out_size, order_axis in zip(output_shape_unmasked, order):
input_shape_unmasked[order_axis] = curr_out_size
input_shape = shape_array(input_shape_unmasked)
node.in_port(0).data.set_shape(input_shape)
@@ -7,8 +7,10 @@ import numpy as np
from openvino.tools.mo.back.ConvolutionNormalizer import PullReshapeThroughFQ, V7ConvolutionWithGroupsResolver, \
V10ConvolutionWithGroupsResolver
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, dynamic_dimension_value
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.ops.const import Const
from openvino.tools.mo.ops.fakequantize import FakeQuantize
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.ops.reshape import Reshape
from openvino.tools.mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, result, regular_op_with_shaped_data, regular_op_with_empty_data, \
@@ -106,7 +108,7 @@ class TestV7ConvolutionWithGroupsResolver(unittest.TestCase):
**valued_const_with_data('weights', np.ones([3, 8, 7, 7])),
**valued_const_with_data('dim', int64_array([24, -1, 7, 7])),
**valued_const_with_data('dim', int64_array([24, -1, 0, 0])),
**regular_op_with_empty_data('reshape', {'type': 'Reshape'}),
**regular_op_with_shaped_data('convolution', None, {'type': 'Convolution', 'group': 3, 'output': 24}),
@@ -155,7 +157,7 @@ class TestV7ConvolutionWithGroupsResolver(unittest.TestCase):
**valued_const_with_data('weights', np.ones([1, 8, 7, 7])),
**valued_const_with_data('dim', int64_array([8, -1, 7, 7])),
**valued_const_with_data('dim', int64_array([8, -1, 0, 0])),
**regular_op_with_empty_data('reshape', {'type': 'Reshape'}),
**regular_op_with_shaped_data('convolution', None, {'type': 'Convolution', 'group': 1, 'output': 8,
@@ -253,3 +255,78 @@ class TestV10ConvolutionWithGroupsResolver(unittest.TestCase):
(flag, resp) = compare_graphs(graph, graph_ref, last_node='output', check_op_attrs=True)
self.assertTrue(flag, resp)
class TestConvolutionWithGroupsResolverForDynamicWeightsChannels(unittest.TestCase):
def test_v10_group_convolution_resolver_for_dynamic_weights(self):
num_groups = 2
C_OUT = 8
nodes = {
**regular_op_with_shaped_data('input', shape_array([1, dynamic_dimension_value, 224, 224]), {'type': 'Parameter'}),
**valued_const_with_data('weights', np.ones([num_groups, C_OUT, 7, 7])),
**regular_op_with_empty_data('reshape', {'type': 'Reshape'}),
**regular_op_with_empty_data('ss', {'type': 'StridedSlice',
'begin_mask': [1], 'end_mask': [0],
'new_axis_mask': [0], 'shrink_axis_mask': [0], 'ellipsis_mask': [0]}),
**regular_op_with_empty_data('weights_shape', {'type': 'ShapeOf'}),
**regular_op_with_empty_data('input_shape', {'type': 'ShapeOf'}),
**regular_op_with_empty_data('gather', {'type': 'Gather'}),
**regular_op_with_empty_data('concat', {'type': 'Concat'}),
**regular_op_with_empty_data('div', {'type': 'Divide'}),
**valued_const_with_data('channels_const', int64_array([num_groups, C_OUT / num_groups])),
**valued_const_with_data('num_groups', int64_array(num_groups)),
**valued_const_with_data('begin', int64_array([2])),
**valued_const_with_data('end', int64_array([-1])),
**valued_const_with_data('channel_index', int64_array([1])),
**valued_const_with_data('axis', int64_array(0)),
**regular_op_with_shaped_data('convolution', None, {'type': 'Convolution', 'group': num_groups, 'output': C_OUT}),
**result(),
}
graph = build_graph(nodes, [
*connect('input', '0:convolution'),
*connect('weights', '1:convolution'),
*connect('convolution', 'output'),
], nodes_with_edges_only=True)
V10ConvolutionWithGroupsResolver().find_and_replace_pattern(graph)
nodes['convolution']['type'] = 'GroupConvolution'
del nodes['convolution']['group']
graph_ref = build_graph(nodes, [
*connect('input', '0:convolution'),
*connect('weights', '0:reshape'),
('input_d', 'input_shape', {'in': 0, 'out': 0}),
('weights_d', 'weights_shape', {'in': 0, 'out': 0}),
*connect('input_shape', '0:gather'),
*connect('channel_index', '1:gather'),
*connect('axis', '2:gather'),
*connect('weights_shape', '0:ss'),
*connect('begin', '1:ss'),
*connect('end', '2:ss'),
*connect('gather', '0:div'),
*connect('num_groups', '1:div'),
*connect('channels_const', '0:concat'),
*connect('div', '1:concat'),
*connect('ss', '2:concat'),
*connect('concat', '1:reshape'),
*connect('reshape', '1:convolution'),
*connect('convolution', 'output'),
], nodes_with_edges_only=True)
Const.infer(Node(graph, 'convolution/GroupsAndOutputChannelsSize'))
Const.infer(Node(graph, 'convolution/Div_input_port_1/value'))
(flag, resp) = compare_graphs(graph, graph_ref, last_node='output')
self.assertTrue(flag, resp)
@@ -6,11 +6,15 @@ import unittest
import numpy as np
from generator import generator, generate
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer
from openvino.tools.mo.front.common.partial_infer.utils import shape_array, dynamic_dimension_value, strict_compare_tensors
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer, eltwise_reverse_infer
from openvino.tools.mo.front.common.partial_infer.utils import shape_array, strict_compare_tensors, \
dynamic_dimension_value
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.middle.passes.infer import partial_infer
from openvino.tools.mo.ops.parameter import Parameter
from openvino.tools.mo.utils.error import Error
from unit_tests.utils.graph import build_graph
from unit_tests.utils.graph import result, regular_op_with_empty_data, connect, \
build_graph, shaped_parameter
nodes_attributes = {'node_1': {'value': 2, 'kind': 'data'},
'node_2': {'value': 3, 'kind': 'data'},
@@ -105,3 +109,99 @@ class TestEltwiseInfer(unittest.TestCase):
with self.assertRaisesRegex(Error, 'Input shapes mismatch*'):
eltwise_infer(eltwise_node)
dyn = dynamic_dimension_value
class TestElementwiseReverseInfer(unittest.TestCase):
@staticmethod
def build_and_test_reverse_inference(inp_shape_1, inp_shape_2, out_shape, ref_shape, auto_broadcast='numpy'):
in_port_with_defined_shape = 0 if inp_shape_1 is not None else 1
defined_shape = shape_array(inp_shape_1 if inp_shape_1 is not None else inp_shape_2)
nodes = {
**shaped_parameter('undefined_shape_data', None, {'reverse_infer': Parameter.reverse_infer}),
**shaped_parameter('data', shape_array(defined_shape), {'reverse_infer': Parameter.reverse_infer}),
**regular_op_with_empty_data('elementwise', {'op': 'Add', 'type': 'Add',
'infer': eltwise_infer,
'reverse_infer': eltwise_reverse_infer,
'auto_broadcast': auto_broadcast}),
**result('res'),
}
edges = [
*connect('undefined_shape_data', '{}:elementwise'.format(int(not in_port_with_defined_shape))),
*connect('data', '{}:elementwise'.format(in_port_with_defined_shape)),
*connect('elementwise', 'res')
]
graph = build_graph(nodes, edges)
graph.stage = 'middle'
Node(graph, 'elementwise').out_port(0).data.set_shape(shape_array(out_shape))
Node(graph, 'elementwise').in_port(in_port_with_defined_shape).data.set_shape(defined_shape)
partial_infer(graph)
actual_shape = Node(graph, 'undefined_shape_data').out_port(0).data.get_shape()
if ref_shape is None:
assert actual_shape == ref_shape
else:
assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
def test_reverse_infer_1(self):
self.build_and_test_reverse_inference(inp_shape_1=[dyn, dyn],
inp_shape_2=None,
out_shape=[dyn, dyn, dyn, dyn],
ref_shape=[dyn, dyn, dyn, dyn])
def test_reverse_infer_2(self):
self.build_and_test_reverse_inference(inp_shape_1=None,
inp_shape_2=[dyn, dyn],
out_shape=[dyn, dyn, dyn, dyn],
ref_shape=[dyn, dyn, dyn, dyn])
def test_reverse_infer_3(self):
self.build_and_test_reverse_inference(inp_shape_1=[1],
inp_shape_2=None,
out_shape=[dyn, 400, 400, 3],
ref_shape=[dyn, 400, 400, 3])
def test_reverse_infer_4(self):
self.build_and_test_reverse_inference(inp_shape_1=[4, 1],
inp_shape_2=None,
out_shape=[dyn, dyn, 4, 3],
ref_shape=[dyn, dyn, dyn, 3])
def test_reverse_infer_5(self):
self.build_and_test_reverse_inference(inp_shape_1=[4, 1],
inp_shape_2=None,
out_shape=[dyn, dyn, 4, 1],
ref_shape=[dyn, dyn, dyn, 1])
def test_reverse_infer_6(self):
# both output and input has the same rank, cannot deduce other inputs rank
with self.assertRaisesRegex(Error, 'Stopped shape/value propagation'):
self.build_and_test_reverse_inference(inp_shape_1=[dyn, dyn, dyn, dyn],
inp_shape_2=None,
out_shape=[dyn, dyn, 4, 1],
ref_shape=None)
def test_reverse_infer_7(self):
self.build_and_test_reverse_inference(inp_shape_1=[4, dyn],
inp_shape_2=None,
out_shape=[1, dyn, dyn, 1],
ref_shape=[1, dyn, dyn, 1])
def test_reverse_infer_8(self):
with self.assertRaisesRegex(AssertionError, "Shapes of Elementwise node '.*' are not compatible"):
self.build_and_test_reverse_inference(inp_shape_1=[4, dyn],
inp_shape_2=None,
out_shape=[1, dyn, 7, 1],
ref_shape=None)
def test_reverse_infer_no_broadcast(self):
self.build_and_test_reverse_inference(inp_shape_1=[1, 4, dyn, dyn],
inp_shape_2=None,
out_shape=[1, dyn, dyn, 1],
ref_shape=[1, 4, dyn, 1],
auto_broadcast='none')
@@ -6,8 +6,9 @@ import unittest
import numpy as np
from generator import generator, generate
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, mo_array, is_fully_defined, dynamic_dimension_value, \
dynamic_dimension, shape_array, compatible_shapes, shape_delete, shape_insert, strict_compare_tensors
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, mo_array, is_fully_defined, \
dynamic_dimension_value, dynamic_dimension, shape_array, compatible_shapes, shape_delete, shape_insert, \
strict_compare_tensors, clarify_partial_shape
from openvino.tools.mo.utils.error import Error
@@ -143,3 +144,12 @@ class mo_array_test(unittest.TestCase):
])
def test_mo_array_negative(self, data, result):
self.assertNotEqual(data.dtype, result.dtype)
class clarify_partial_shape_test(unittest.TestCase):
def test_clarify_1(self):
actual_result = clarify_partial_shape([shape_array([dynamic_dimension, 10, dynamic_dimension]),
shape_array([4, dynamic_dimension, dynamic_dimension])])
ref_result = shape_array([4, 10, dynamic_dimension])
assert strict_compare_tensors(actual_result, ref_result)
@@ -83,12 +83,14 @@ class IteratorGetNextAnalysisTest(unittest.TestCase):
def test_unsupported_data_type(self):
graph = build_graph_with_edge_attrs(
{
'iter_get_next': {'kind': 'op', 'op': 'IteratorGetNext', 'shapes': shape_array([[2, 2]]),
'iter_get_next': {'kind': 'op', 'op': 'IteratorGetNext', 'shapes': [shape_array([2, 2])],
'types': [None]},
'sub': {'kind': 'op', 'op': 'Sub'},
'result': {'kind': 'op', 'op': 'Result'}
},
[
('iter_get_next', 'sub', {'out': 0, 'in': 0}),
('sub', 'result', {'out': 0, 'in': 0}),
]
)
+75 -2
View File
@@ -3,10 +3,12 @@
import numpy.testing as npt
from openvino.tools.mo.ops.gather import Gather
from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, strict_compare_tensors, \
dynamic_dimension_value
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.middle.passes.infer import partial_infer
from openvino.tools.mo.ops.gather import Gather
from openvino.tools.mo.ops.parameter import Parameter
from openvino.tools.mo.utils.error import Error
from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry
from unit_tests.utils.graph import valued_const_with_data, result, regular_op_with_empty_data, connect, \
@@ -266,3 +268,74 @@ class TestGatherPartialInfer(UnitTestWithMockedTelemetry):
data_shape=[3, 4, 7],
indices_shape=[3, 4, 2],
ref_shape=[3, 4, 2])
dyn = dynamic_dimension_value
class TestElementwiseReverseInfer(UnitTestWithMockedTelemetry):
@staticmethod
def build_and_test_reverse_inference(data_shape, indices_shape, axis, batch_dims, out_shape, ref_shape):
in_port_with_defined_shape = 0 if data_shape is not None else 1
defined_shape = shape_array(data_shape if data_shape is not None else indices_shape)
nodes = {
**shaped_parameter('data', data_shape, {'reverse_infer': Parameter.reverse_infer}),
**shaped_parameter('indices', indices_shape, {'reverse_infer': Parameter.reverse_infer}),
**valued_const_with_data('axis', int64_array(axis)),
**regular_op_with_empty_data('gather', {'op': 'Gather', 'batch_dims': batch_dims,
'infer': Gather.infer,
'reverse_infer': Gather.reverse_infer}),
**result('res'),
}
edges = [
*connect('data', '0:gather'),
*connect('indices', '1:gather'),
*connect('axis', '2:gather'),
*connect('gather', 'res')
]
graph = build_graph(nodes, edges)
graph.stage = 'middle'
Node(graph, 'gather').out_port(0).data.set_shape(shape_array(out_shape))
Node(graph, 'gather').in_port(in_port_with_defined_shape).data.set_shape(defined_shape)
partial_infer(graph)
actual_shape = Node(graph, 'gather').in_port(int(not in_port_with_defined_shape)).data.get_shape()
assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
# undefined indices pshape
def test_reverse_infer_1(self):
self.build_and_test_reverse_inference(data_shape=[dyn, dyn],
indices_shape=None,
axis=0,
batch_dims=0,
out_shape=[dyn, dyn, dyn, dyn],
ref_shape=[dyn, dyn, dyn])
def test_reverse_infer_2(self):
self.build_and_test_reverse_inference(data_shape=[3, 10],
indices_shape=None,
axis=1,
batch_dims=0,
out_shape=[3, 40, 50, 60],
ref_shape=[40, 50, 60])
# undefined data pshape
def test_reverse_infer_3(self):
self.build_and_test_reverse_inference(data_shape=None,
indices_shape=[4, 5],
axis=0,
batch_dims=0,
out_shape=[4, 5, 10],
ref_shape=[dyn, 10])
def test_reverse_infer_4(self):
self.build_and_test_reverse_inference(data_shape=None,
indices_shape=[4, 67],
axis=1,
batch_dims=0,
out_shape=[3, 4, 67, 100],
ref_shape=[3, dyn, 100])
+63 -4
View File
@@ -4,15 +4,15 @@
import unittest
import numpy as np
from generator import generator, generate
from openvino.tools.mo.ops.split import AttributedSplit, AttributedVariadicSplit, VariadicSplit
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, dynamic_dimension_value, strict_compare_tensors
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, \
dynamic_dimension_value, dynamic_dimension, strict_compare_tensors
from openvino.tools.mo.graph.graph import Node
from openvino.tools.mo.ops.split import AttributedSplit, AttributedVariadicSplit, VariadicSplit
from openvino.tools.mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph
from generator import generator, generate
class TestSplitOp(unittest.TestCase):
nodes = {
@@ -324,3 +324,62 @@ class TestVariadicSplitOp(unittest.TestCase):
except AssertionError as e:
self.assertTrue(e.args[0] == 'VariadicSplit `axis` should be scalar or tensor with shape [1], '
'but it`s not for node split_op')
class TestSplitReverseInfer(unittest.TestCase):
def test_split_reverse_infer(self):
ref_input_shape = [7, 4, 6]
axis = 2
num_splits = 2
output_shape_1 = [dynamic_dimension, 4, 3]
output_shape_2 = [7, dynamic_dimension, 3]
graph = build_graph(TestSplitOp.nodes, TestSplitOp.edges,
{
'split_input_data': {'shape': None,
'value': None},
'split_op': {'axis': np.array(axis), 'num_splits': np.array(num_splits)},
'split_output_0_data': {'shape': shape_array(output_shape_1),
'value': None},
'split_output_1_data': {'shape': shape_array(output_shape_2),
'value': None},
})
split_node = Node(graph, 'split_op')
AttributedSplit.reverse_infer(split_node)
actual_input_shape = split_node.in_port(0).data.get_shape()
self.assertTrue(strict_compare_tensors(ref_input_shape, actual_input_shape))
class TestAttributedVariadicSplitReverseInfer(unittest.TestCase):
def test_splitv_dynamic_input(self):
ref_input_shape = [7, 4, 11]
axis = 2
num_splits = 2
output_shape_1 = [dynamic_dimension, 4, 3]
output_shape_2 = [7, dynamic_dimension, 3]
output_shape_3 = [7, dynamic_dimension, 5]
graph = build_graph(TestAttributedVariadicSplitOp.nodes, TestAttributedVariadicSplitOp.edges,
{
'split_input_data': {'shape': None},
'split_op': {'axis': np.array(2), 'split_lengths': np.array([3, 3, 5]),
'out_ports_count': 2},
'split_output_0_data': {'shape': shape_array(output_shape_1),
'value': None},
'split_output_1_data': {'shape': shape_array(output_shape_2),
'value': None},
'split_output_2_data': {'shape': shape_array(output_shape_3),
'value': None},
}
)
node = Node(graph, 'split_op')
for p in range(len(node.out_edges()), node.out_ports_count):
node.add_output_port(p)
AttributedVariadicSplit.reverse_infer(node)
actual_input_shape = node.in_port(0).data.get_shape()
self.assertTrue(strict_compare_tensors(ref_input_shape, actual_input_shape))
+52 -2
View File
@@ -7,9 +7,14 @@ import unittest
import numpy as np
from generator import generator, generate
from openvino.tools.mo.ops.transpose import Transpose
from openvino.tools.mo.front.common.partial_infer.utils import int64_array, shape_array, strict_compare_tensors, \
dynamic_dimension_value
from openvino.tools.mo.graph.graph import Node
from unit_tests.utils.graph import build_graph
from openvino.tools.mo.middle.passes.infer import partial_infer
from openvino.tools.mo.ops.parameter import Parameter
from openvino.tools.mo.ops.transpose import Transpose
from unit_tests.utils.graph import valued_const_with_data, result, regular_op_with_empty_data, connect, \
build_graph, shaped_parameter
input_shape = np.array([1, 3, 224, 224])
@@ -100,3 +105,48 @@ class TestTransposeOp(unittest.TestCase):
transpose_node = Node(graph, 'transpose')
transpose_node['reverse_order'] = False
self.assertRaises(AssertionError, Transpose.infer, transpose_node)
dyn = dynamic_dimension_value
class TestTransposeReverseInfer(unittest.TestCase):
@staticmethod
def build_and_test_reverse_inference(order, out_shape, ref_shape):
nodes = {
**shaped_parameter('data', None, {'reverse_infer': Parameter.reverse_infer}),
**valued_const_with_data('order', int64_array(order)),
**regular_op_with_empty_data('transpose', {'op': 'Transpose',
'infer': Transpose.infer,
'reverse_infer': Transpose.reverse_infer}),
**result('res'),
}
edges = [
*connect('data', '0:transpose'),
*connect('order', '1:transpose'),
*connect('transpose', 'res')
]
graph = build_graph(nodes, edges)
graph.stage = 'middle'
Node(graph, 'transpose').out_port(0).data.set_shape(shape_array(out_shape))
partial_infer(graph)
actual_shape = Node(graph, 'data').out_port(0).data.get_shape()
assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
def test_reverse_infer_1(self):
self.build_and_test_reverse_inference(order=[0, 3, 1, 2],
out_shape=[dyn, dyn, dyn, dyn],
ref_shape=[dyn, dyn, dyn, dyn])
def test_reverse_infer_2(self):
self.build_and_test_reverse_inference(order=[0, 3, 1, 2],
out_shape=[44, 32, 77, 1],
ref_shape=[44, 77, 1, 32])
def test_reverse_infer_3(self):
self.build_and_test_reverse_inference(order=[0, 2, 3, 1],
out_shape=[44, 32, 77, 1],
ref_shape=[44, 1, 32, 77])