* 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
265 lines
15 KiB
Python
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')
|