Files
openvino/model-optimizer/extensions/middle/InsertLayoutPropagationTransposes.py
T
Vladimir Gavrilov 0c288d506c MO support for operations DFT and IDFT (#5197)
* Written MO classes for DFT and IDFT operations.

* Added class to read TF (I)FFT operations.

* Written extractors for TF operations FFT, FFT2D, FFT3D, IFFT, IFFT2D, IFFT3D.

* Written MO Roll operation and TF Roll operation extractor.

* Started to write needed transformations.

* Written transformation StridedSlices + Complex + Roll + (i)FFTxD + Roll + (Imag, Real) + Pack -> Roll + (I)DFT + Roll.

* Written transformation for Complex + ComplexAbs.

* Written correction of axes of Roll.

* Small fix.

* Small fix.

* Some fixes.

* Some changes.

* Now TF Roll is read as TFRoll. Written inserting Transposes before and after (I)DFT.

* Small fix.

* Written tests for the transformation TFRollToRoll.

* Added comments to some transformations.

* Deleted redundant import.

* Written tests for the transformation TransposeDFT.

* Fixes in MO IR Reader to read/write (I)DFT.

* Fixes in the list of supported TF layers.

* Started to write tests for SSliceComplexRolledFFTPackBlockReplacement transformation.

* Written tests for the MO transformation SSliceComplexRolledFFTPackBlockReplacement.

* Written tests for the MO transformation ComplexAbs.

* Tests for transformations were moved into unit_tests directory.

* All extractors for (I)FFTxD are in one file now.

* Deleted redundant transformations.

* Fixed extractor for TF Roll: now this operation is read as MO Roll.

* Added comments to TFFFT operation.

* The method insert_transpose of classes TransposeDFT and LayoutChangeForGatherND was moved into the separate function in the file model-optimizer/extensions/middle/InsertLayoutPropagationTransposes.py.

* Fixed comment for the transformation TransposeDFT.

* Small fix.

* Some fixes.

* Deleted shape infer function for the operation TFFFT. Sorted imports in complex_abs.py.

* Small fixes.

* Deleted redundant import.

* Fixes in some asserts.

* Small fix.

* Added names for created nodes in the transformation ComplexAbs.

* Added comments to the method canonicalize_axes.

* The transformation SSliceComplexRolledFFTPackBlockReplacement was split into the sequence of transformations SSliceComplexRollReplacement -> RollRealImagPackReplacement -> TFFFTToDFT.

* Written tests for the transformation SSliceComplexRollReplacement.

* Written tests for the transformation RollRealImagPackReplacement.

* Written tests for the transformation TFFFTToDFT.

* Deleted commented code.

* Fixed types of constants in the transformation ComplexAbs.

* Written tests for canonicalization of signal_size value.

* Deleted 'Replacement' from names of files and classes.

* Used comarison of ids, not names.

* replace_sub_graph was replaced with find_and_replace_pattern.

* Now the transformation RollRealImagPack is executed before running transformation model-optimizer/extensions/front/Pack.py.

* The body of the function create_dft_from_tffft is a part of the transformation TFFFTToDFT body now.

* Now method correct_roll_axes of classes RollRealImagPack and SSliceComplexRoll is moved to the function in mo/front/tf/graph_utils.py.

* Small changes.

* Added comment before mark_input_as_in_correct_layout(roll, 2).

* Now the functions correct_roll_axes generates sub-graph in the input port 2 of Roll.

* Corrected tests for the transformation SSliceComplexRoll.

* Corrected tests for the transformation RollRealImagPack.

* Deleted commented code.

* Some renaming.

* Added decomposition of the separate operation ComplexAbs (without Complex before it).

* Added comment to the transformation ComplexAbsAfterComplex.

* Optimized imports for the transformation TFFFTToDFT.

* The transformation SSliceComplexRoll was split into the sequence SSliceComplex -> CorrectRollAxes and disabled.

* Written tests for the transformation ComplexAbs.

* Written tests for the transformation SSliceComplex.

* Written tests for the transformation CorrectRollAxes.

* Deleted the transformation SSliceComplexRoll.

* Deleted renaming nodes.

* Fixed comment.

* Small fixes.

* Small fix.

* The attribute need_correction was renamed as input_rank_changed.

* Small fixes.

* Deleted commented code.

* Now we iterate over all complex_node.out_port(0).get_connection().get_destinations() input ports and mark the corresponding nodes with the marker attribute.

* Added the attribute 'in_ports_count' into the class FFTBase.

* Tests for the transformation TransposeDFT were rewritten using helper functions.

* Now the transformation RollRealImagPack uses existing Roll node instead of creating new one.

* Small fixes.

* Fix in the documentation.

* Written class to read MxNet (I)FFT operations. Written corresponding extractors.

* Corrected shape infer function for MXFFT operation. Written transformation to convert MXFFT to (I)DFT.

* Fixed shape infer function.

* Fixed the conversion MXFFT to (I)DFT.

* Written tests for the transformation MXFFTToDFT.

* The function correct_roll_axes was replaced with more generic function add_constant_to_negative_values.

* Fixes in classes TFFFT, FFTBase, DFT, IDFT, MXFFT.

* Added asserts in constructors of operations TFFFT and MXFFT.

* Refactored transformation MXFFTToDFT: conversion of DFT and IDFT were moved into separated functions.

* Moved some commented code.

* Fixed BOM file.

* Written function convert_ifft_to_dft.

* Started to rewrite tests for MXFFTToDFT transformations, in the case is_inverse=False.

* Small fixes.

* Fixes in the transformation RollRealImagPack.

* Renaming tests class for the transformation SSliceComplex.

* Fixes in the function compare_graphs. Now we get all output nodes of op node, and these output nodes are sorted by names.

* Fixed tests for the transformation MXFFTToDFT.

* Fix in the transformation ThresholdedReluDecomposition: added disconnect for trelu input port.

* Fixes in test for the transformation TFSliceToSlice.

* Small fix in the transformation ObjectDetectionAPIPreprocessor2Replacement.

* Small fix in comment.

* Optimized imports.

* Used remove_node in the transformation ThresholdedReluDecomposition and remove_nodes_from in the transformation RollRealImagPack, instead of ports disconnection.

* Deleted commented code.

* Deleted test case test_slice_replacer_begin_with_2_inputs.
2021-05-07 09:44:24 +03:00

189 lines
9.7 KiB
Python

# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import numpy as np
from extensions.middle.pass_separator import PostMiddleStart
from extensions.ops.transpose import Transpose
from mo.front.common.partial_infer.utils import int64_array
from mo.graph.graph import Graph, Node, Port
from mo.middle.replacement import MiddleReplacementPattern
from mo.ops.op import PermuteAttrs
class InsertLayoutPropagationTranspose(MiddleReplacementPattern):
"""
The transformation inserts Transpose layers before/after operations that change the interpretation of data, for
example, Reshape from 3D to 4D or from 4D to 3D. These Transpose layers basically convert layout from N(D)HWC to
NC(D)HW and in the reverse order.
"""
enabled = True
force_clean_up = True # need to run clean up after the transformation to update shapes
graph_condition = [lambda graph: graph.graph['layout'] == 'NHWC']
def run_after(self):
return [PostMiddleStart]
def run_before(self):
return []
@staticmethod
def is_nchw_to_nhwc_transpose_needed(node: Node):
"""
The function checks that it is necessary to insert Transpose from NCHW to NHWC before the node.
The transpose is needed when all the following conditions are met:
1. The node is marked as 'reinterp_shape' attribute
2. The node is *not* marked as getting input in correct layout (implicitly imply that the input is on port 0)
3. The input shape rank is not less than 4
4. Node is not a part of shape sub-graph (layout permutation is handled separately for such a sub-graph)
:param node: node to check
:return: result of the check
"""
return node.has_and_set('reinterp_shape') and \
not is_input_data_in_correct_layout(node, 0) and \
len(node.in_port(0).data.get_shape()) >= 4 and \
all([port.data.get_value() is None for port in node.out_ports().values() if not port.disconnected()])
@staticmethod
def is_nhwc_to_nchw_transpose_needed(node: Node):
"""
The function checks that it is necessary to insert Transpose from NHWC to NCHW after the node.
The transpose is needed when all the following conditions are met:
1. The node is marked as 'reinterp_shape' attribute
2. The node is *not* marked as generating output in correct layout (implicitly imply that the output port is 0)
3. The output shape rank is not less than 4
4. Node is not a part of shape sub-graph (layout permutation is handled separately for such a sub-graph)
:param node: node to check
:return: result of the check
"""
return node.has_and_set('reinterp_shape') and \
not is_output_data_in_correct_layout(node, 0) and \
len(node.out_port(0).data.get_shape()) >= 4 and \
all([port.data.get_value() is None for port in node.out_ports().values() if not port.disconnected()])
def find_and_replace_pattern(self, graph: Graph):
# we need to import these functions here to avoid circular dependent imports
from mo.front.tf.graph_utils import create_op_node_with_second_input
if graph.graph['layout'] != 'NHWC':
# we check it here because this transformation is called explicitly from the pipeline
return
# reshape from 4D-5D -> ND. Insert Transpose(NC(D)HW->N(D)HWC) before Reshape
for reinterp_shape_node_id in graph.get_nodes_with_attributes(reinterp_shape=True):
reinterp_shape_node = Node(graph, reinterp_shape_node_id)
assert 0 in reinterp_shape_node.in_nodes(), 'Node {} does not have 0 input. \n{}'.format(
reinterp_shape_node_id, graph.dump_graph_for_graphviz())
input_shape = reinterp_shape_node.in_node(0).shape
if self.is_nchw_to_nhwc_transpose_needed(reinterp_shape_node):
permute_node = create_op_node_with_second_input(
graph, Transpose, PermuteAttrs().get_nchw_to_nhwc_permutation(len(input_shape)).perm,
{'name': reinterp_shape_node.in_port(0).get_source().node.name + '/Transpose'}
)
reinterp_shape_node.in_port(0).get_connection().insert_node(permute_node)
order_const = permute_node.in_port(1).get_source().node
order_const.infer(order_const)
# do not infer the Transpose node because it should have input data node in NCHW layout (but currently
# it is NHWC because data node attributes has not been permuted yet) and produce output in NHWC layout
# (which is true at this moment)
permute_node['need_shape_inference'] = False
# mark the Transpose output data node having correct layout so it's shape will not be permuted
mark_output_as_in_correct_layout(permute_node, 0)
# keep the reinterp_shape_node in NHWC layout
mark_input_as_in_correct_layout(reinterp_shape_node, 0)
mark_input_as_in_correct_layout(reinterp_shape_node, 1)
# reshape from ND -> 4D-5D. Insert Transpose(N(D)HWC->NC(D)HW) after Reshape
for reinterp_shape_node_id in graph.get_nodes_with_attributes(reinterp_shape=True):
reinterp_shape_node = Node(graph, reinterp_shape_node_id)
assert 0 in reinterp_shape_node.out_nodes(), 'Node {} does not have 0 output. \n{}'.format(
reinterp_shape_node_id, graph.dump_graph_for_graphviz())
output_shape = reinterp_shape_node.out_node(0).shape
if self.is_nhwc_to_nchw_transpose_needed(reinterp_shape_node):
permute_node = create_op_node_with_second_input(
graph, Transpose, PermuteAttrs().get_nhwc_to_nchw_permutation(len(output_shape)).perm,
{'name': reinterp_shape_node.id + '/Transpose'})
reinterp_shape_node.out_port(0).get_connection().insert_node(permute_node)
# the Reshape and Transpose operations should work in original (NHWC layout) so the Transpose
# will convert it to the NCHW
mark_input_as_in_correct_layout(permute_node, 0)
mark_input_as_in_correct_layout(permute_node, 1)
# do not set Transpose output data node 'correct_data_layout' attribute so the data node shape will be
# permuted
# keep the reinterp_shape_node in NHWC layout
mark_output_as_in_correct_layout(reinterp_shape_node, 0)
mark_input_as_in_correct_layout(reinterp_shape_node, 1)
# do not re-infer the Transpose node because it output data node should be in NHWC layout to make the
# rest of the graph consistent
permute_node['need_shape_inference'] = False
def is_input_data_in_correct_layout(node: Node, port_ind: int):
assert node.soft_get('kind') == 'op', 'The function work with operation nodes only'
return 'correct_in_data_layout' in node.attrs() and port_ind in node.attrs()['correct_in_data_layout']
def mark_input_as_in_correct_layout(node: Node, port_ind: int):
assert node.soft_get('kind') == 'op', 'The function work with operation nodes only'
graph = node.graph
graph.node[node.id].setdefault('correct_in_data_layout', set())
graph.node[node.id]['correct_in_data_layout'].add(port_ind)
def is_output_data_in_correct_layout(node: Node, port_ind: int):
assert node.soft_get('kind') == 'op', 'The function work with operation nodes only'
return 'correct_out_data_layout' in node.attrs() and port_ind in node.attrs()['correct_out_data_layout']
def mark_output_as_in_correct_layout(node: Node, port_ind: int):
assert node.soft_get('kind') == 'op', 'The function work with operation nodes only'
graph = node.graph
graph.node[node.id].setdefault('correct_out_data_layout', set())
graph.node[node.id]['correct_out_data_layout'].add(port_ind)
def mark_as_correct_data_layout(node: Node):
"""
The analogue of the attribute 'correct_data_layout' for the operation node
:param node: node to mark it with attribute 'correct_data_layout'
:return: None
"""
assert node.soft_get('kind') == 'op', 'The function work with operation nodes only'
for ind, port in node.in_ports().items():
mark_input_as_in_correct_layout(node, ind)
for ind, port in node.out_ports().items():
mark_output_as_in_correct_layout(node, ind)
def insert_transpose(graph: Graph, input_port: Port, before_input=True):
from mo.front.tf.graph_utils import create_op_with_const_inputs
input_rank = len(input_port.data.get_shape())
if input_rank > 3:
if before_input:
axis_order = np.concatenate((int64_array([0]),
int64_array(list(range(2, input_rank))),
int64_array([1])))
source_node = input_port.get_source().node
transpose_name = source_node.soft_get('name', source_node.id) + '/TransposeToNHWC'
else:
axis_order = np.concatenate(
(int64_array([0]),
int64_array([input_rank - 1]),
int64_array(list(range(1, input_rank - 1)))))
transpose_name = input_port.node.soft_get('name', input_port.node.id) + '/TransposeToNCHW'
input_port.node['need_shape_inference'] = True
input_port.node['override_output_shape'] = True
transpose = create_op_with_const_inputs(graph, Transpose, {1: axis_order}, {'name': transpose_name})
input_port.get_connection().insert_node(transpose)
transpose['need_shape_inference'] = True
transpose['override_output_shape'] = True