Files
openvino/model-optimizer/mo/ops/convolution.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

265 lines
15 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 int64_array, mark_input_bins, assign_dims_to_weights, \
tf_window_op_pad_infer, dynamic_dimension_value, shape_array
from mo.front.onnx.extractors.utils import get_backend_pad
from mo.graph.graph import Node, Graph
from mo.graph.perm_inputs import PermuteInputs
from mo.ops.op import Op, PermuteAttrs
from mo.utils.error import Error
class Convolution(Op):
op = 'Convolution'
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'type': self.op,
'op': self.op,
'version': 'opset1',
'infer': self.infer,
'multiplication_transparent': True,
'multiplication_transparent_ports': [(0, 0), (1, 0)],
'in_ports_count': 3,
'out_ports_count': 1,
}, attrs)
def backend_attrs(self):
def pad_attribute_helper(node: Node, pad_type: str='begin'):
assert pad_type in ['begin', 'end']
if not node.has_valid('pad'):
return None
pad = get_backend_pad(node.pad, node.spatial_dims, 0 if pad_type == 'begin' else 1)
if node.has_valid('auto_pad') and node.auto_pad != 'explicit':
pad = [0 for _ in pad]
return ','.join(map(str, pad))
return [
('auto_pad', lambda node: node.auto_pad if node.has_valid('auto_pad') else 'explicit'),
('strides', lambda node: ','.join(map(str, node['stride'][node.spatial_dims]))),
('dilations', lambda node: ','.join(map(str, node['dilation'][node.spatial_dims]))),
('pads_begin', lambda node: pad_attribute_helper(node, 'begin')),
('pads_end', lambda node: pad_attribute_helper(node, 'end')),
# for Backpropdata operations only - according to spec
('output_padding', lambda node: ','.join(map(str, node.output_padding[node.spatial_dims])) \
if node.has_valid('output_padding') and node.type in
('GroupConvolutionBackpropData', 'ConvolutionBackpropData') else None),
# for BinaryConvolution only
'pad_value',
'mode',
]
@staticmethod
def calc_convolution(input_spatial_shape, stride_spatial_shape, pad_spatial_shape, kernel_extent):
"""
Calculates output shape for Convolution.
Verified to be applicable for both Caffe and ONNX.
"""
spatial_val_wo_stride = input_spatial_shape + pad_spatial_shape - kernel_extent
if np.any(spatial_val_wo_stride < 0):
raise Error("Data after padding has dimension less than window size. " +
"Possible reason of error is incorrectly specified model input shape(s).")
return spatial_val_wo_stride / stride_spatial_shape + 1
@staticmethod
def calc_deconvolution(node, input_spatial_shape, pad_spatial_shape, kernel_extent):
"""
Calculates output shape for Deconvolution.
Verified to be applicable for both Caffe and ONNX with explicitly defined pads.
If pads are not specified for ONNX operator, this function is not applicable.
"""
return node.stride[node.spatial_dims] * (input_spatial_shape - 1) + kernel_extent - pad_spatial_shape
@staticmethod
def infer(node: Node):
"""
Infers shape of convolution node as it is done in ONNX.
It is very similar to one that Caffe does, but slightly different.
We made a complete fork of this function because they are supposed to be
supported differently by different people.
Args:
node: graph convolution node
"""
input_shape = node.in_port(0).data.get_shape()
if input_shape is None:
raise Error('Input data shape is None for node {}'.format(node.soft_get('name', node.id)))
# bias_term cannot be deduced earlier for frameworks that represent
# convolution weights/biases as regular inputs; so the number of inputs
# is being checked here and restore correct value for bias_term to
# have the rest of the code unchanged. It will be used after we merge
# several infer functions for convolution in different FWs to a single one.
if not node.has_valid('bias_term'):
node['bias_term'] = len(node.in_nodes()) == 3
weights_index = node.weights_index if node.has_valid('weights_index') else 1
# Reshape weights kernel to original shape
# In case of caffe or MXNet framework, values for weights have no structured shape like OIHW
# so we have to reshape weights to normal shape
# For this case, Convolution node should have attribute reshape_kernel = True
if node.has_valid('reshape_kernel') and node.reshape_kernel:
if not (node.has_valid('output') and node.has_valid('channel_dims') and node.has_valid(
'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
# layout for Convolution weights is OIHW
kernel_shape = int64_array([node.output, input_shape[node.channel_dims].item() / 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]]
if np.prod(kernel_shape) != np.prod(node.in_node(weights_index).value.shape):
log.error("Size of weights {} does not match kernel shape: {}\n"
"".format(np.prod(node.in_node(weights_index).value.shape), kernel_shape) +
" Possible reason is wrong channel number in input shape\n")
raise Error("Cannot reshape weights to kernel shape")
node.in_node(weights_index).shape = np.array(kernel_shape)
node.in_node(weights_index).value = np.reshape(node.in_node(weights_index).value, kernel_shape)
node.reshape_kernel = False
# Pass weights shape to node attribute kernel_shape
kernel_shape = node.in_node(weights_index).shape
node['kernel_shape'] = kernel_shape
# Calculate kernel_spatial_idx and spatial_dims if it is not specified
# It is necessary for ONNX dut to convolution can be 1D/2D/3D
if not node.has_valid('kernel_spatial_idx'):
node['kernel_spatial_idx'] = np.delete([x for x in range(len(kernel_shape))],
(node.input_feature_channel, node.output_feature_channel))
if not node.has_valid('spatial_dims'):
node['spatial_dims'] = np.delete([x for x in range(len(input_shape))],
(node.channel_dims[0], node.batch_dims[0]))
node['kernel_spatial'] = kernel_shape[node.kernel_spatial_idx]
if not node.has_valid('output'):
# restore the number of output feature maps from the second argument that is weights
if node.type in ['Convolution', 'Deconvolution', 'DeformableConvolution', 'BinaryConvolution']:
node['output'] = kernel_shape[node.output_feature_channel]
else:
raise Error(
'Convolution infer function was called for a node {} with unsupported type {}',
node.soft_get('name'),
node.type
)
# Set default values for dilation, strides and pads if not set
if not node.has_valid('dilation'):
node['dilation'] = np.full([len(input_shape)], 1, dtype=np.int64)
if not node.has_valid('stride'):
node['stride'] = np.full([len(input_shape)], 1, dtype=np.int64)
if not node.has_valid('pad'):
node['pad'] = int64_array([[0, 0]] * len(input_shape))
node['pad_spatial_shape'] = node.pad[node.spatial_dims]
if not node.has_valid('output_padding'):
node['output_padding'] = np.full([len(input_shape)], 0, dtype=np.int64)
if node.has_valid('output_padding') and len(input_shape) > len(node['output_padding']):
output_padding = np.zeros(len(input_shape), dtype=np.int64)
for i in range(len(node['output_padding'])):
output_padding[i] = node['output_padding'][i]
node['output_padding'] = output_padding
input_spatial_shape = input_shape[node.spatial_dims]
stride_spatial_shape = node.stride[node.spatial_dims]
kernel_extent = node.dilation[node.spatial_dims] * (node.kernel_spatial - 1) + 1
# TensorFlow always has auto_pad attribute that can be either valid or same_upper
# In ONNX auto_pad attribute is deprecated but appears in some models (could be valid, same_upper or same_lower)
# Caffe do not use auto_pad attribute
if node.has_valid('auto_pad') and node.auto_pad != 'explicit' and not node.has_valid('output_spatial_shape'):
node['pad_spatial_shape'], node['output_spatial_shape'] = tf_window_op_pad_infer(input_spatial_shape,
kernel_extent,
stride_spatial_shape,
node.auto_pad,
node.type == 'Deconvolution')
pad = np.zeros((len(input_shape), 2), dtype=np.int64)
pad[node.spatial_dims] = node.pad_spatial_shape
node.pad = pad
else:
pad_spatial_shape = np.add.reduce(node.pad_spatial_shape, axis=1)
if node.type in ('Convolution', 'BinaryConvolution'):
float_spatial = Convolution.calc_convolution(input_spatial_shape, stride_spatial_shape,
pad_spatial_shape,
kernel_extent)
node['output_spatial_shape'] = shape_array(float_spatial)
elif node.type == 'Deconvolution':
# In case of given output_spatial_shape we calculate pads spatial
if node.has_valid('output_spatial_shape'):
if node.has_valid('get_pad'):
node['pad'] = node.get_pad(node, input_shape, kernel_shape)
else:
log.debug('Can\'t calculate paddings due to missing lambda get_pad in {} node'.format(node.id))
return
else:
output_padding = node.output_padding[node.spatial_dims] if node.has_valid('output_padding') else None
if output_padding is not None and any(output_padding):
pad_spatial_shape -= output_padding
for dim in range(len(pad_spatial_shape)):
node.pad_spatial_shape[dim][1] -= pad_spatial_shape[dim]
float_spatial = Convolution.calc_deconvolution(node, input_spatial_shape, pad_spatial_shape,
kernel_extent)
node['output_spatial_shape'] = shape_array(float_spatial)
elif node.type == 'DeformableConvolution':
# get the output spatial shape from the second input with offsets
node['output_spatial_shape'] = int64_array([node.in_node(1).shape[2:4]])
else:
assert 'Unsupported layer type "{}"'.format(node.type)
# For cases when group attribute wasn't set in extractor we should specify get_group attribute
# this attribute should store lambda node: ... (check tf convolution extractor)
if node.has_valid('get_group'):
node['group'] = node.get_group(node)
output_shape = shape_array([dynamic_dimension_value for _ in range(len(input_shape))])
output_shape[node.batch_dims] = input_shape[node.batch_dims] # pylint: disable=unsupported-assignment-operation
output_shape[node.spatial_dims] = node.output_spatial_shape # pylint: disable=unsupported-assignment-operation
# For cases when output attribute wasn't set in extractor we should specify get_output_feature_dim attribute
# this attribute should store lambda node: ... (check tf convolution extractor)
if node.has_valid('get_output_feature_dim'):
node['output'] = node.get_output_feature_dim(node)
output_shape[node.channel_dims] = node.output # pylint: disable=unsupported-assignment-operation
node['output_shape'] = output_shape
node.out_port(0).data.set_shape(output_shape)
# bin attribute is used for pre-processing, but it will be deleted in BlobNormalizer transformation
# and the blobs (weights, biases) will be represented as inputs to the node
mark_input_bins(node, start_port=1 if node.type != 'DeformableConvolution' else 2)
assign_dims_to_weights(node.in_node(weights_index), node.kernel_spatial_idx, node.input_feature_channel,
node.output_feature_channel, len(kernel_shape))
PermuteAttrs.create_permute_attrs(node, attrs=[('pad', 'input:0'),
('stride', 'input:0'),
('dilation', 'input:0'),
('output_shape', 'input:0'),
('batch_dims', 'input:0'),
('channel_dims', 'input:0'),
('spatial_dims', 'input:0'),
('kernel_shape', 'input:{}'.format(weights_index)),
('kernel_spatial_idx', 'input:{}'.format(weights_index)),
('input_feature_channel', 'input:{}'.format(weights_index)),
('output_feature_channel', 'input:{}'.format(weights_index)),
])
# is needed to permute Conv weights from the original TF [H, W, C_IN, C_OUT] into IE [C_OUT, C_IN, H, W]
# but for other nodes in weights subgraph permutations must turned off
# by marking with MarkSubGraphsWithCorrectLayout even if graph layout is NCHW.
PermuteAttrs.set_permutation(node.in_node(weights_index), node, node.soft_get('get_weights_permute', None))
PermuteInputs().set_input_permutation(
node.in_node(weights_index), node, 'input:{}'.format(weights_index), 'transpose')