Files
openvino/model-optimizer/extensions/ops/MatMul.py
Evgeny Lazarev 3775dad345 MO dynamic shapes support (#5918)
* Allow MO to generate IR with -1 in dimensions

* Some fixes to support -1 for StridedSlice operation

* Updated TensorArrayGatherV3 shape infer to support dynamic output shape

* Several fixes to support undefined dimensions in the Broadcast,Reshape,Slice and Tile

* Fixed bug in the normalization transformation of TF NMS to opset NMS

* Updated shape infer functions related to StridedSlice and NMS

* Updated Select shape inference function to use common shape broadcasting function supporting dynamism

* Fixed operation TFResize shape infer function to work correctly for case when model is converted with --disable_nhwc_to_nchw

* Dynamic Range and update asserts in NMS

* Changed the way how dynamic dimensions are specified. Refactored shape inference functions and common places to use new approach

* More fixes to support dynamic shapes

* More fixes for support of dynamic shapes

* Fixed generation of IR with dynamic dimensions

* Allow reading IRs with undefined dimensions

* More changes in the IE to support dynamic dimensions

* Fixes for Switch, Merge, Concat shape and value infer related to dynamism

* Fixed TensorArray related ops to properly handle dynamic dimensions. Fixed StridedSlice infer for case with new_axis

* Fixed shape_for_layout function to generate masked array

* Fixed shape inference for Convolution and Poolings to support dynamic spatial dimensions

* Updated shape infer functions for CTCGreedyDecotder, CTCLoss and Enter

* Fixed shape inference with dynamic dimensions for MatMul, Split, Upsample, SpaceToBatch, some fixes for the TI

* Fixes for undefined dimensions support for Proposal and DetectionOutput

* Fixed ExtractImagePatches, DepthToSpace and RegionYolo shape infer functions to work with partially dynamic dimensions

* Changes in tf_window_op_pad_infer to better work with dynamic dimensions

* Fixed output shape calculation for StridedSlice operation

* More StridedSlice fixes

* Fixed resolve_convolution_with_group

* Fixed unit tests

* Fixed unit tests

* Fixed Switch op unit tests

* Fixed shape inference for Upsample operation

* Updated unit tests for the Concat operation

* Fixed eltwise shape infer unit tests

* Fixed shape infer tests for Convolution and DetectionOutput ops

* Fixed Crop shape infer function tests

* Fixed Slice op unit test and minor fix in the shape inference. Fixed emitter

* Updated unit test for telemetry and match_shape function for dynamism

* Fixed unit test for the DetectionOutput

* Added support for the TF ClipByValue operation

* Fixed GatherND shape inference for dynamic shapes support

* Dynamic shapes support for the MO IR Reader

* Fixed BlockLSTM operation to not work as an extractor

* Allow to serialize IRs with partially defined shapes

* Updated SelectBroadcast transformation to not check shape values

* Fixed MO IR comparator

* Fixed SS value propagation when slices are dynamic

* Do not re-run graph clean-up for ProposalMutation

* Fixed InterpolateSequenceToInterpolate transformation to support dynamic dimensions

* Fixed Loop iteration count calculation and reading IteratorGetNext shapes

* Fixed unit test for serialization

* Fixed serialization test

* Fixed RandomUniform shape infer

* Fixed several transformations related to RNN to respect dynamic output shapes

* Fixed Deconvolutin shape calculation for dynamic batch. Eltwise shape infer improvements

* Fixed shape infer functions for ExperimentalDetectron ops, reverted changes for NonZero and removed debug prints

* Fixed check for dynamism of a list, fixed value propagation for Concat op and remove redundant shape infer for reshape

* Update Eltwise value propagation to use np.ma

* Fixed ExpandDims shape infer function

* Shape infer functions fixes and improvements

* Remove Accum op from the MO

* Updated activation functions shape infer

* Removed unsupported operation Correlation

* Fixed shape infers for several functions

* Removed unsupported DataAugmentation operation

* Fixed shape infer functions for several ops in extensions directory

* Removed not-support operation PowerFile

* Removed unsupported SpatialTransformer,SimplerNMS and PredictionHeatmap operations

* More shape infer functions updates

* Merge shape infer fix

* Fixed typo

* Fixed TensorArraySize shape infer function

* Fixed VariadicSplit and Squeeze shape infer

* Fixed ONNX models Parameter extractor

* Updated Select value propagation for the dynamic case

* Fixed ReorgYolo shape infer and test

* Removed unnecessary tests

* Fixed Tile shape infer

* Fixed SparseFillEmptryRows unit tests

* Fixed package bom

* Added extractor for the TF operation Mod

* Fixed value propagation for MatMul operation

* Updated Parameter extender to generate shape_array when shape is partially defined only

* Fixed BOM file

* Fixed issue with the TF OD API models and DetectionOutput op. Now the shape infer function for the DO do not re-infer "num_classes" attribute value if it is already known

* Fixed unit test for the DO infer

* Fixed num classes calculation for the DO generation for Faster/Mask-RCNN models

* Changed NMS op to produce static output shape

* Restore dynamic output shape calculation for the NMS for NMS-5

* Fixed CellNormalizer transformation. It should work for static shapes only

* RNNCell Op class fixes

* Revert some changes

* Updated documentation with a list of supported operations

* Revert changes

* Fixes for the ConstantFill op

* Removed redundant SequenceLengthToMask transformation

* TensorArray* ops shape infer code style and refactoring

* Reverse some unnecessary changes in the ConvolutionNormalizer

* Fixes and unit tests for shape_array, compare_shapes, is_fully_defined functions

* Implemented shape_insert, shape_delete functions and tests for them

* Modified code to use shape_delete function

* Added usage of shape_insert function where necessary

* Use shape_insert function in many places

* Some fixes in shape inference for various ops

* Updated shape_delete function to support negative indices

* Changes and unit tests for the MatMul infer function

* Removed strange code from the TF Merge infer function

* Merge op shape infer fixes

* Fixed value propagation in the transformation EltwiseInputReshape.py for the dynamic dimension case

* Code cleanup

* Updated GatherND to support dynamic dimensions

* Minor fixes

* Fixed shape_insert and shape_delete to support np.int64 and np.int32 types

* Updated Upsample operation unit tests with dynamic input shapes

* Minor change in the extensions/back/ConvolutionNormalizer.py to make sure that input dimensions are static

* Fixed ConvertGroupedStridedSlice transformation and added unit tests

* Revert debug changes

* Fixed value propagation for Unsqueeze to work with partially defined input values

* Typo fix

* Added unit tests for the Unsqueeze op shape infer

* broadcasting functions changes and unit tests

* Fixed Tile value inference for partially defined input tensor

* Unit tests for Split and VariadicSplit ops

* Fixes for the Concat infer + unit tests

* Removed redundant tf_pack shape infer

* Fixed Concat value infer and added unit tests

* Fixed StridedSlice shape inference for case with dynamic slices

* Fixes related to StridedSlice shape infer, changes in tests

* Unit tests for the eltwise shape and value infer

* Fixed Pad op value propagation to allow dynamic input values to be propagated

* Unit test for Pooling dynamic input shape infer

* Squeeze op unit tests for dynamic input shape

* Added assert to the Squeeze op shape infer for case when squeeze dimension is dynamic value

* Added message to the MO when input shapes are dynamic

* Convolution dynamic unit test

* Removed redundant transformation GroupedConvWeightsNormalize

* Removed non-ascii character from the message

* Fixed typo in the BOM file

* Code style and comment fixes

* Fixed copy-paste issue in the DO shape infer function

* Fixed setting dynamic shape in the MO command line

* Added function to compare tensor with dynamic values. Fixes in the unit tests and shape infer functions

* Improved Reshape shape infer + added unit tests

* Fixed value propagation for Select op

* Renamed several internal functions, minor code fixes.

* Code style fixes

* Modified condition in the _set_shape method of the Port class to not check shape if the "override_output_shape" attribute is specified

* Fixed constant value propagation for ReduceOps when inputs have dynamic values. Added unit test

* Fixed shape infer for the Loop for dynamic dimensions case

* Fix in the NMS shape infer to avoid ragged numpy array generation. Fixed Scatter shape infer validation

* Improved shapes infer for eltwise ops with respect to dynamic dimensions

* Changed code comments

* Renamed tensor names in the ClipByValueTFTransformation

* Changed np.ma.allequal to strict_compare_tensors in the Merge op infer

* Chanded np.ma.allequal with strict_compare_tensor.

* Fixed Merge op value infer

* Fixed debug code

* Removed commented line

* Updated condition to check for dynamic shapes in the Partial infer to not fail for MxNet models

* Improvements to the get_shape_from_slice and is_dynamic_slice functions

* Reverted change in the `normalize_slices_attr` for ellipsis mask case

* Updated shape conditions in the ScatterNDBase op to support dynamic dimensions

* Crop op file refactoring

* Set "type" attribute to None for SparseFillEmptyRows op which is not from any opset

* Removed unnecessary extractor test

* Restored Crop operation type

* Removed "type" attribute from the Crop operation and updated the MO code to find Crop by "op" attribute

* Fixed If shape infer function to produce dynamic dimensions

* Updated If shape and value infer to properly work when condition is static

* Fixed fusing transformation check to work with dynamic dimensions. Change comparison in the shape_inference function to not use strict shapes comparison

* Optimize imports in the LayerNorm

* ConvertGroupedStridedSlice minor fixes related to dynamism support

* Fixed ConvertGroupedStridedSlice to properly check if the dimension is sliced
2021-09-01 14:35:06 +03:00

241 lines
9.7 KiB
Python

# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import logging as log
import numpy as np
from mo.front.common.partial_infer.utils import assign_dims_to_weights, int64_array, compatible_dims, compatible_shapes, \
shape_array, is_fully_defined, shape_delete, shape_insert
from mo.front.extractor import bool_to_str
from mo.graph.graph import Node, Graph
from mo.ops.op import Op
class MatMul(Op):
"""
Operation is specified at docs/ops/matrix/MatMul_1.md
"""
op = 'MatMul'
def __init__(self, graph: Graph, attrs: dict):
mandatory_props = {
'type': self.op,
'op': self.op,
'version': 'opset1',
'transpose_a': False,
'transpose_b': False,
'infer': self.infer,
'in_ports_count': 2,
'out_ports_count': 1,
}
super().__init__(graph, mandatory_props, attrs)
def supported_attrs(self):
return [
('transpose_a', lambda node: bool_to_str(node, 'transpose_a')),
('transpose_b', lambda node: bool_to_str(node, 'transpose_b')),
]
@staticmethod
def shape_alignment(node: Node):
"""
Specification of MatMul operation allows inputs to be aligned together before matrix multiplication.
Current method raises an error if input shapes are not valid at any step of alignment process
:return: aligned copies of both input shapes
"""
node_name = node.soft_get('name', str(node.id))
input_shapes = [node.in_port(i).data.get_shape() for i in range(2)]
transpose_a = node.has_and_set('transpose_a')
transpose_b = node.has_and_set('transpose_b')
transformed_shapes = []
for i, shape in enumerate(input_shapes):
input_shape = shape.copy()
# prerequisites check
assert input_shape is not None, "MatMul has shape=`None` for {} input of `{}` node".format(i, node_name)
assert input_shape.ndim == 1, "MatMul doesn't support scalar inputs. {} input of `{}` node has shape {}" \
"".format(i, node_name, input_shape)
assert input_shape.size >= 1, "MatMul doesn't support inputs with rank lower than 1. {} input of `{}` " \
"node has shape {}".format(i, node_name, input_shape)
rank = input_shape.size
# shape alignment
if rank != 1 and ((i == 0 and transpose_a) or (i == 1 and transpose_b)):
input_shape[-2], input_shape[-1] = input_shape[-1], input_shape[-2]
if rank == 1:
input_shape = shape_insert(input_shape, int(i == 1), 1)
max_shape_length = max(input_shapes[0].size, input_shapes[1].size)
input_shape = shape_insert(input_shape, 0, [1] * (max_shape_length - input_shape.size))
transformed_shapes.append(input_shape)
A_shape = shape_array(transformed_shapes[0])
B_shape = shape_array(transformed_shapes[1])
assert A_shape.size == B_shape.size, \
"Shapes were not aligned by length for MatMul `{}`. Shapes: `{}`".format(node_name, transformed_shapes)
# batch broadcasting
batch_len = A_shape.size - 2
for i in range(batch_len):
if A_shape[i] != B_shape[i]:
if A_shape[i] == 1:
A_shape[i] = B_shape[i]
if B_shape[i] == 1:
B_shape[i] = A_shape[i]
assert compatible_shapes(A_shape[:-2], B_shape[:-2]), \
"MatMul input shapes are incorrect. BATCH_DIMs are not equal. Node: {}. Aligned shapes: {}" \
"".format(node_name, transformed_shapes)
return A_shape, B_shape
@staticmethod
def value_propagation(node: Node):
"""
This function performs a value propagation for MatMul layer.
:param node: MatMul layer
:return: None
"""
a_value = node.in_port(0).get_source().data.get_value()
b_value = node.in_port(1).get_source().data.get_value()
if is_fully_defined(a_value) and is_fully_defined(b_value):
if node.transpose_a:
a_value = transpose(a_value)
if node.transpose_b:
b_value = transpose(b_value)
# np.matmul does not work correctly with masked arrays, so need explicitly convert inputs to regular arrays
if isinstance(a_value, np.ma.masked_array):
a_value = a_value.filled()
if isinstance(b_value, np.ma.masked_array):
b_value = b_value.filled()
node.out_port(0).data.set_value(np.matmul(a_value, b_value))
@staticmethod
def infer(node: Node):
"""
Performs shape inference of MatMul node as operation doc-string says
Raises on any shape inconsistency
"""
name = node.soft_get('name', str(node.id))
connected_in_ports = {idx: port for idx, port in node.in_ports().items() if not port.disconnected()}
assert len(connected_in_ports) == 2 and 0 in connected_in_ports and 1 in connected_in_ports, \
"MatMul should have 2 connected input ports, but it doesn't for node: `{}`. Ports: {}" \
"".format(name, connected_in_ports)
log.debug('MatMul `{}` input shapes: {}'.format(name, [node.in_port(i).data.get_shape() for i in range(2)]))
A_shape, B_shape = MatMul.shape_alignment(node)
log.debug('MatMul `{}` aligned input shapes: {}'.format(name, [A_shape, B_shape]))
assert compatible_dims(A_shape[-1], B_shape[-2]), \
"MatMul input shapes are incorrect. COL_INDEX_DIMs are not equal. Node: {}. Shapes: {}" \
"".format(name, [A_shape, B_shape])
output_shape = np.ma.concatenate((A_shape[:-1], B_shape[-1:]))
if node.in_port(0).data.get_shape().size == 1:
assert compatible_dims(output_shape[-2], 1)
output_shape = shape_delete(output_shape, -2)
if node.in_port(1).data.get_shape().size == 1:
assert compatible_dims(output_shape[-1], 1)
output_shape = shape_delete(output_shape, -1)
node.out_port(0).data.set_shape(output_shape)
in_ch = 0 if not node.transpose_b else 1
out_ch = 1 if not node.transpose_b else 0
assign_dims_to_weights(node.in_node(1), None, in_ch, out_ch, node.in_port(1).data.get_shape().size)
MatMul.value_propagation(node)
def transpose(value):
num_of_dims = value.ndim
if num_of_dims == 1:
return value
else:
return np.transpose(value, [*range(0, num_of_dims - 2), num_of_dims - 1, num_of_dims - 2])
# MatMul-like operation from frameworks
class GemmONNX(Op):
"""
Represents Gemm operation from ONNX
Missing `type` and `infer` attributes on purpose - node should be decomposed on front phase
and should never be inferred or translated to IR as is
"""
op = 'Gemm'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
mandatory_props = {
'op': self.op,
'transpose_a': False,
'transpose_b': False,
'alpha': 1,
'beta': 1,
'broadcast_c': True,
'in_ports_count': 3,
'out_ports_count': 1,
}
super().__init__(graph, mandatory_props, attrs)
class FullyConnected(Op):
# TODO: remove `infer`, `type` and supported_attrs after op removal from IR Spec
op = 'FullyConnected'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'op': self.op,
'type': self.op,
'infer': self.infer,
'in_ports_count': 3,
'out_ports_count': 1,
}, attrs)
def supported_attrs(self):
return [
'out-size',
]
@staticmethod
def infer(node: Node):
name = node.soft_get('name', node.id)
connected_in_ports = {idx: port for idx, port in node.in_ports().items() if not port.disconnected()}
assert len(connected_in_ports) >= 2 and 0 in connected_in_ports and 1 in connected_in_ports, \
'FullyConnected should have 2 connected input ports, but it doesn\'t for node: `{}`. Ports: {}' \
''.format(name, connected_in_ports)
assert node.has_valid('out-size')
input_shape = node.in_port(0).data.get_shape()
weights_shape = node.in_port(1).data.get_shape()
assert input_shape is not None and weights_shape is not None, \
'Incorrect FullyConnected input shapes. Node: {}. Shapes: {}'.format(name, [input_shape, weights_shape])
assert weights_shape.size == 2
out_size = node.soft_get('out-size')
assert compatible_dims(weights_shape[0], out_size), \
'weights_shape={}, out-size={}'.format(weights_shape, out_size)
if 2 in connected_in_ports:
bias_value = node.in_port(2).data.get_value()
bias_shape = node.in_port(2).data.get_shape()
assert bias_shape is not None, 'Shape was not inferred for biases of FullyConnected {}'.format(name)
assert bias_value is not None, 'Value was not inferred for biases of FullyConnected {}'.format(name)
assert compatible_shapes(bias_shape, [out_size]) or compatible_shapes(bias_shape, [1, out_size]), \
'Incorrect FullyConnected bias shape `{}` for node {}. `out-size`={}'.format(bias_shape, node, out_size)
node.out_port(0).data.set_shape([*input_shape[:-1], out_size])
# MatMul-like operations for IR V6
class Gemm(MatMul):
"""
Represents GEMM operation that is acceptable to appear in v6 IRs
Inherits MatMul semantic to be re-inferred in back phase and to be successfully translated to IR (v6)
"""
op = 'GEMM'
enabled = False