* 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.
189 lines
9.7 KiB
Python
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
|