248 lines
12 KiB
Python
248 lines
12 KiB
Python
# Copyright (C) 2018-2021 Intel Corporation
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import logging as log
|
|
from collections import deque
|
|
from typing import Set
|
|
|
|
from extensions.middle.InsertLayoutPropagationTransposes import InsertLayoutPropagationTranspose, \
|
|
mark_as_correct_data_layout, mark_output_as_in_correct_layout, mark_input_as_in_correct_layout
|
|
from extensions.middle.LayoutChangeForConstantShapePaths import LayoutChangeForConstantShapePaths
|
|
from extensions.middle.pass_separator import PostMiddleStart
|
|
from mo.graph.graph import Graph, Node
|
|
from mo.graph.perm_inputs import PermuteInputs
|
|
from mo.graph.port import Port
|
|
from mo.middle.replacement import MiddleReplacementPattern
|
|
|
|
|
|
class MarkSubGraphsWithCorrectLayout(MiddleReplacementPattern):
|
|
"""
|
|
The transformation looks for the layout agnostic operations which does not have a layout (NCHW or NHWC) and makes
|
|
necessary changes to infer the part of the topology in the original layout:
|
|
1. Prevents from adding Transpose operations before and after "reinterp_shape" like operations which change rank of
|
|
the input and output tensors of this layout agnostic op.
|
|
2. Disable attributes permutation for all intermediate ops between these "reinterp_shape" nodes.
|
|
3. Marks nodes along the weight path of convolutions as in correct layout to not permute them from NHWC to NCHW
|
|
"""
|
|
enabled = True
|
|
graph_condition = [lambda graph: graph.graph['layout'] == 'NHWC']
|
|
op_conditions = [lambda n: n.soft_get('op') == 'MatMul' and
|
|
any([len(port.data.get_shape()) in (4, 5) for port in n.in_ports().values()]),
|
|
]
|
|
|
|
def run_after(self):
|
|
return [PostMiddleStart]
|
|
|
|
def run_before(self):
|
|
return [InsertLayoutPropagationTranspose]
|
|
|
|
@staticmethod
|
|
def get_input_nodes(node: Node):
|
|
return [src_port.get_source().node for src_port in node.in_ports().values() if not src_port.disconnected()]
|
|
|
|
@staticmethod
|
|
def get_output_nodes(node: Node):
|
|
result = []
|
|
for out_port in node.out_ports().values():
|
|
if not out_port.disconnected():
|
|
for dest_port in out_port.get_destinations():
|
|
result.append(dest_port.node)
|
|
return result
|
|
|
|
@staticmethod
|
|
def bfs(start_nodes: list, visited: set, condition: callable = None, forward: bool = True):
|
|
"""
|
|
The function performs BFS starting from selected nodes in forward or backward direction adding nodes by an
|
|
optional condition
|
|
:param start_nodes: Nodes to start search from
|
|
:param visited: set of already visited nodes where traversing should not happen
|
|
:param condition: function getting a Node as input and returning whether the node should be included into the
|
|
result or not. If the value is None then the node is added unconditionally.
|
|
:param forward: boolean flag specifying the traverse direction
|
|
:return: the list of Nodes visited
|
|
"""
|
|
assert visited is not None, 'The "visited" set must be defined'
|
|
assert start_nodes is not None, 'The list of start nodes must be specified'
|
|
|
|
result = list()
|
|
d = deque(start_nodes)
|
|
while len(d) != 0:
|
|
cur_node = d.popleft()
|
|
result.append(cur_node)
|
|
visited.add(cur_node)
|
|
if forward:
|
|
next_nodes = MarkSubGraphsWithCorrectLayout.get_output_nodes(cur_node)
|
|
else:
|
|
next_nodes = MarkSubGraphsWithCorrectLayout.get_input_nodes(cur_node)
|
|
for next_node in next_nodes:
|
|
if next_node not in visited and (condition is None or condition(next_node)):
|
|
d.append(next_node)
|
|
return result
|
|
|
|
def find_and_replace_pattern(self, graph: Graph):
|
|
visited = set()
|
|
marked_nodes = set()
|
|
condition_forward = lambda n: not InsertLayoutPropagationTranspose.is_nhwc_to_nchw_transpose_needed(n)
|
|
condition_backward = lambda n: not InsertLayoutPropagationTranspose.is_nchw_to_nhwc_transpose_needed(n)
|
|
for node_condition in self.op_conditions:
|
|
for node in graph.get_op_nodes():
|
|
if node_condition(node):
|
|
log.debug('Detected node "{}" as a node which should be executed in the original layout'
|
|
''.format(node.soft_get('name', node.id)))
|
|
forward_visited_nodes = self.bfs([node], visited, condition_forward, True)
|
|
backward_visited_nodes = self.bfs([node], visited, condition_backward, False)
|
|
|
|
# find "reinterp_shape" like ops which change rank of input to 4D or 5D from smaller dimensions
|
|
for back_node in backward_visited_nodes:
|
|
for input_node in self.get_input_nodes(back_node):
|
|
if input_node not in backward_visited_nodes and not condition_forward(input_node):
|
|
marked_nodes.add(input_node)
|
|
|
|
# find "reinterp_shape" like ops which change rank of input from 4D or 5D to smaller dimensions
|
|
for forward_node in forward_visited_nodes:
|
|
for output_node in self.get_output_nodes(forward_node):
|
|
if output_node not in forward_visited_nodes and not condition_backward(output_node):
|
|
marked_nodes.add(output_node)
|
|
|
|
marked_nodes.update(forward_visited_nodes + backward_visited_nodes)
|
|
|
|
if len(marked_nodes):
|
|
log.debug('The following nodes will be executed in the original layout: {}'
|
|
''.format([n.soft_get('name', n.id) for n in marked_nodes]))
|
|
|
|
# mark all matched nodes as in correct layout and disable attributes permutation for them
|
|
for visited_node in marked_nodes:
|
|
mark_as_correct_data_layout(visited_node)
|
|
visited_node['nchw_layout'] = True
|
|
|
|
_, nodes_weigths, nodes_in_weights = self.get_ports_and_nodes_on_weights(graph)
|
|
for node in nodes_weigths:
|
|
if node in nodes_in_weights:
|
|
for ind, port in node.in_ports().items():
|
|
if ind not in nodes_in_weights[node]:
|
|
mark_input_as_in_correct_layout(node, ind)
|
|
for ind, port in node.out_ports().items():
|
|
mark_output_as_in_correct_layout(node, ind)
|
|
else:
|
|
mark_as_correct_data_layout(node)
|
|
node['nchw_layout'] = True
|
|
|
|
for node in self.get_ports_and_nodes_on_shape_subgraphs(graph)[1]:
|
|
mark_as_correct_data_layout(node)
|
|
node['nchw_layout'] = True
|
|
|
|
@staticmethod
|
|
def get_weighted_layer_type_to_in_weights_port():
|
|
get_weights_port_index = lambda node: node.weights_index if node.has_valid('weights_index') else 1
|
|
weighted_layer_type_to_in_weights_port = {
|
|
'Convolution': get_weights_port_index,
|
|
'DeformableConvolution': get_weights_port_index,
|
|
'Deconvolution': get_weights_port_index,
|
|
'BinaryConvolution': get_weights_port_index,
|
|
}
|
|
return weighted_layer_type_to_in_weights_port
|
|
|
|
@staticmethod
|
|
def insert_permute_inputs_before_dynamic_weights_subgraph(dynamic_subgraphs: Set[Node] = None):
|
|
"""
|
|
The function inserts permutations on input nodes in the weights subgraph
|
|
:param dynamic_subgraphs: Set of Nodes belonging to weight path subgraphs
|
|
:return: the list of Nodes which are inputs to weight path subgraphs
|
|
"""
|
|
dynamic_in_nodes = dict()
|
|
for node in dynamic_subgraphs:
|
|
node_type = node.soft_get('type')
|
|
if node_type not in ['Const', 'Parameter', 'ShapeOf']:
|
|
idx_lst = list()
|
|
for idx in [idx for idx, port in node.in_ports().items() if
|
|
not port.disconnected() and port.get_source().node not in dynamic_subgraphs]:
|
|
PermuteInputs().set_input_permutation(node.in_node(idx), node, 'input:{}'.format(idx),
|
|
'transpose_nchw_to_nhwc')
|
|
idx_lst.append(idx)
|
|
if len(idx_lst):
|
|
dynamic_in_nodes[node] = idx_lst
|
|
return dynamic_in_nodes
|
|
|
|
@staticmethod
|
|
def walk_up_from_in_ports_to_out_ports(in_ports: Set[Port], out_ports: Set[Port], port_condition=None):
|
|
r""""
|
|
Returns all intermediate ports and nodes of such a sub-graph:
|
|
|
|
out_ports
|
|
| |
|
|
\/ \/
|
|
. . .
|
|
| |
|
|
\/ \/
|
|
in_ports
|
|
"""
|
|
visited_ports = set()
|
|
visited_nodes = set()
|
|
|
|
deque_of_in_ports = deque(in_ports)
|
|
while len(deque_of_in_ports):
|
|
in_port = deque_of_in_ports.popleft()
|
|
if in_port.get_source() is None:
|
|
continue
|
|
source_node = in_port.get_source().node
|
|
if in_port in visited_ports: # do not check visited_nodes as search is based on ports
|
|
continue
|
|
visited_ports.update({in_port, in_port.get_source()})
|
|
if in_port.get_source() in out_ports: # reached source marked to stop the search
|
|
if not len(in_port.get_source().node.in_ports()): # for Constants and Parameters to be visited
|
|
visited_nodes.add(in_port.get_source().node)
|
|
continue
|
|
for idx, port in source_node.in_ports().items():
|
|
if not port.disconnected() and (not port_condition or port_condition(source_node, idx)):
|
|
deque_of_in_ports.append(port)
|
|
visited_nodes.add(source_node)
|
|
return visited_ports, visited_nodes
|
|
|
|
@staticmethod
|
|
def is_not_weight_port(node: Node, idx: int):
|
|
w_types_to_in_port_dict = MarkSubGraphsWithCorrectLayout.get_weighted_layer_type_to_in_weights_port()
|
|
node_type = node.soft_get('type')
|
|
return node_type in w_types_to_in_port_dict.keys() and idx != w_types_to_in_port_dict[node_type](node)
|
|
|
|
@staticmethod
|
|
def get_ports_and_nodes_on_weights(graph):
|
|
nodes = graph.get_op_nodes()
|
|
|
|
# collect all input ports with weights
|
|
weight_ports = set()
|
|
result_ports = set()
|
|
start_ports = set()
|
|
w_types_to_in_port_dict = MarkSubGraphsWithCorrectLayout.get_weighted_layer_type_to_in_weights_port()
|
|
for node in nodes:
|
|
node_type = node.soft_get('type', 'unknown')
|
|
if node_type not in w_types_to_in_port_dict.keys():
|
|
if node_type in ['Const', 'Parameter', 'ShapeOf', 'ExtractImagePatches']:
|
|
start_ports.add(node.out_port(0))
|
|
continue
|
|
weight_port_idx = w_types_to_in_port_dict[node_type](node)
|
|
assert node.is_in_port_connected(weight_port_idx), \
|
|
'Unexpected port configuration of {} node with name=`{}`'.format(node_type,
|
|
node.soft_get('name', node.id))
|
|
weight_ports.add(node.in_port(weight_port_idx))
|
|
for result in graph.get_op_nodes(type='Result'):
|
|
result_ports.update(result.in_ports().values())
|
|
|
|
# collect all sub-graphs that start with Constant/Parameter/ShapeOf/ExtractImagePatches and end at in_port as
|
|
# weights
|
|
ports_w, nodes_w = MarkSubGraphsWithCorrectLayout.walk_up_from_in_ports_to_out_ports(weight_ports, start_ports)
|
|
# collect all sub-graphs that start with Constant/Parameter/ShapeOf/ExtractImagePatches, end at Result nodes and
|
|
# not contains branches that end as weights
|
|
ports_d, nodes_d = MarkSubGraphsWithCorrectLayout.walk_up_from_in_ports_to_out_ports(
|
|
result_ports, start_ports, MarkSubGraphsWithCorrectLayout.is_not_weight_port)
|
|
nodes_dif = nodes_w.difference(nodes_d)
|
|
nodes_in_w = MarkSubGraphsWithCorrectLayout.insert_permute_inputs_before_dynamic_weights_subgraph(nodes_dif)
|
|
return ports_w.difference(ports_d), nodes_dif, nodes_in_w
|
|
|
|
@staticmethod
|
|
def get_ports_and_nodes_on_shape_subgraphs(graph):
|
|
shape_sources = {shape_of.out_port(0) for shape_of in graph.get_op_nodes(type='ShapeOf')}
|
|
end_points = LayoutChangeForConstantShapePaths().find_shape_subgraph_endpoints(
|
|
[shape.out_port(0) for shape in graph.get_op_nodes(type='ShapeOf')])
|
|
ports, nodes = MarkSubGraphsWithCorrectLayout.walk_up_from_in_ports_to_out_ports(end_points, shape_sources)
|
|
return ports, nodes
|