Skip to content

Commit 550cf89

Browse files
committed
NXP backend: Move format related transpositions of partition inputs/outputs to Neutron when possible.
Due to the different tensor formats used by Executorch and Neutron, the inputs/outputs often have to be transposed. This used to be done exclusively by the runtime. Now, the transpositions are done by Neutron when possible.
1 parent c19c8bb commit 550cf89

File tree

5 files changed

+366
-23
lines changed

5 files changed

+366
-23
lines changed

backends/nxp/backend/edge_program_converter.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,16 @@ def convert_program(
8787
self._convert_qdq_cluster_q_dq_nodes(edge_program.graph.nodes, cc)
8888
self._process_nodes(edge_program.graph.nodes, cc)
8989

90-
# Assign output
91-
io_formats = cc.tflite_builder.assign_model_io_to_subgraph_and_get_io_formats(
92-
edge_program.graph_signature
93-
)
90+
# Assign the model its inputs and outputs.
91+
cc.tflite_builder.assign_model_io_to_subgraph(edge_program.graph_signature)
9492

95-
# TFLite model generation
93+
# Apply optimizations and finalize the model.
9694
internal_tflite_model = cc.tflite_builder.finish()
95+
96+
# Extract the formats of the model's inputs and outputs.
97+
io_formats = cc.tflite_builder.get_io_formats(edge_program.graph_signature)
98+
99+
# TFLite model generation
97100
flatbuffers_builder = flatbuffers.Builder()
98101
internal_tflite_model.gen_tflite(flatbuffers_builder)
99102

backends/nxp/backend/ir/converter/builder/aten_model_builder_director.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,40 @@ def append_operators(self, ops_to_add: list[tflite_model.Operator]):
8888

8989
self.check_and_append_operator(op)
9090

91-
def assign_model_io_to_subgraph_and_get_io_formats(
92-
self, graph_signature
93-
) -> dict[str, dict]:
94-
"""
95-
Assign model's inputs/outputs to SubGraph.
91+
def get_io_formats(self, graph_signature) -> dict[str, dict[str, TensorFormat]]:
92+
"""Get a mapping from tensor names to their formats.
9693
97-
:param graph_signature: Instance of GraphSignature.
94+
:param graph_signature: Instance of GraphSignature.
9895
:returns: Mapping between IO tensors' names and their formats.
9996
"""
10097
io_formats = {
10198
"inputs": {},
10299
"outputs": {},
103100
}
101+
for input_name in graph_signature.user_inputs:
102+
tensor = self.tensor_for_name(input_name)
103+
assert input_name == tensor.name, (
104+
"Program's input name doesn't match with tensor name in TFLite. "
105+
"Input was probably redirected."
106+
)
107+
io_formats["inputs"][tensor.name] = tensor.tensor_format
108+
109+
for output_name in graph_signature.user_outputs:
110+
tensor = self.tensor_for_name(output_name)
111+
assert output_name == tensor.name, (
112+
"Program's output name doesn't match with tensor name in TFLite. "
113+
"Output was probably redirected."
114+
)
115+
io_formats["outputs"][tensor.name] = tensor.tensor_format
116+
117+
return io_formats
118+
119+
def assign_model_io_to_subgraph(self, graph_signature):
120+
"""
121+
Assign model's inputs/outputs to SubGraph.
122+
123+
:param graph_signature: Instance of GraphSignature.
124+
"""
104125

105126
self.get_sub_graph().inputs = tflite_model.SubGraphInputs()
106127
for input_name in graph_signature.user_inputs:
@@ -110,7 +131,6 @@ def assign_model_io_to_subgraph_and_get_io_formats(
110131
"Input was probably redirected."
111132
)
112133
self.get_sub_graph().inputs.tmp_inputs.append(tensor)
113-
io_formats["inputs"][tensor.name] = tensor.tensor_format
114134

115135
self.get_sub_graph().outputs = tflite_model.SubGraphOutputs()
116136
for output_name in graph_signature.user_outputs:
@@ -120,7 +140,3 @@ def assign_model_io_to_subgraph_and_get_io_formats(
120140
"Output was probably redirected."
121141
)
122142
self.get_sub_graph().outputs.tmp_outputs.append(tensor)
123-
124-
io_formats["outputs"][tensor.name] = tensor.tensor_format
125-
126-
return io_formats

backends/nxp/backend/ir/converter/builder/model_builder.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
FlexTranspose,
4949
)
5050
from executorch.backends.nxp.backend.ir.tflite_optimizer import optimizer
51+
from executorch.backends.nxp.backend.neutron_operator_support import (
52+
transposition_is_supported_on_neutron,
53+
)
5154
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
5255

5356

@@ -355,6 +358,19 @@ def _make_inputs_channels_first(self):
355358
if input_tensor.tensor_format.is_channels_last():
356359
# Create a Transpose operator and replace the graph input
357360

361+
new_input_shape = translator.channels_last_shape_to_channels_first(
362+
input_tensor.shape
363+
)
364+
perm = translator.create_channels_first_to_channels_last_permutation(
365+
input_tensor.rank
366+
)
367+
368+
if not transposition_is_supported_on_neutron(
369+
new_input_shape.vector, list(perm), self.neutron_target_spec
370+
):
371+
new_inputs.append(input_tensor)
372+
continue
373+
358374
if input_tensor.rank > 6:
359375
msg = (
360376
f"Couldn't preserve the shape of input tensor '{input_tensor.name}', because it has "
@@ -365,14 +381,9 @@ def _make_inputs_channels_first(self):
365381
new_input = self.duplicate_tensor(
366382
input_tensor, input_tensor.name + "_channels_first"
367383
)
368-
new_input.shape = translator.channels_last_shape_to_channels_first(
369-
input_tensor.shape
370-
)
384+
new_input.shape = new_input_shape
371385
new_input.tensor_format = input_tensor.tensor_format.to_node_format()
372386

373-
perm = translator.create_channels_first_to_channels_last_permutation(
374-
input_tensor.rank
375-
)
376387
transpose = self._create_transpose_operator(
377388
new_input, input_tensor, perm
378389
)
@@ -397,6 +408,16 @@ def _make_outputs_channels_first(self):
397408
if output_tensor.tensor_format.is_channels_last():
398409
# Add a Transpose operator, to make the output channels first
399410

411+
shape = output_tensor.shape.vector
412+
perm = translator.create_channels_last_to_channels_first_permutation(
413+
len(shape), True
414+
)
415+
if not transposition_is_supported_on_neutron(
416+
shape, perm, self.neutron_target_spec
417+
):
418+
new_outputs.append(output_tensor)
419+
continue
420+
400421
if output_tensor.rank > 6:
401422
logger.e(
402423
logger.Code.IO_PRESERVATION_ERROR,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2025 NXP
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
7+
8+
9+
def is_tensor_invariant_permutation(
10+
input_shape: list[int], permutation: list[int]
11+
) -> bool:
12+
def input_dim_is_not_one(index):
13+
return input_shape[index] != 1
14+
15+
new_permutation = list(filter(input_dim_is_not_one, permutation))
16+
17+
return new_permutation == sorted(new_permutation)
18+
19+
20+
def transposition_is_supported_on_neutron(
21+
input_shape: list[int],
22+
permutation: list[int],
23+
neutron_target_spec: NeutronTargetSpec,
24+
) -> bool:
25+
"""This function determines if the current NeutronSoftware properly supports a `Transpose` operator with given
26+
`input_shape` and `permutation`.
27+
28+
:param input_shape: The shape of the main input tensor of the `Transpose` operator.
29+
:param permutation: The permutation the `Transpose` operator is computing.
30+
:param neutron_target_spec: Object holding some parameters of the target platform.
31+
"""
32+
num_macs = neutron_target_spec.get_num_macs()
33+
34+
if is_tensor_invariant_permutation(input_shape, permutation):
35+
# The `Transpose` will be turned into a `Reshape` by Neutron. The check includes the identity permutation.
36+
return True
37+
38+
if permutation == [0, 3, 1, 2]:
39+
# NHWC -> NCHW
40+
n, h, w, c = input_shape
41+
42+
if h * w * c % num_macs != 0: # Official Neutron requirement.
43+
return False
44+
45+
if not (
46+
c % num_macs == 0 and h * w % num_macs == 0
47+
): # Neutron would produce incorrect outputs.
48+
return False
49+
50+
if n != 1:
51+
# Neutron only supports `Transpose` operators where the dimensions can be combined into 2 consecutive
52+
# groups. These 2 groups are then transposed like a matrix, and the result is reshaped. Therefore, for the
53+
# [0, 3, 1, 2] permutation, when h * w != 1 and c != 1, batch size must be 1.
54+
return False
55+
56+
return True
57+
58+
elif permutation == [0, 2, 3, 1]:
59+
# NCHW -> NHWC
60+
61+
n, c, h, w = input_shape
62+
63+
if w % num_macs != 0: # Official Neutron requirement.
64+
return False
65+
66+
if not (
67+
c % num_macs == 0 and h * w % num_macs == 0
68+
): # Neutron would produce incorrect outputs.
69+
return False
70+
71+
if n != 1:
72+
# Neutron only supports `Transpose` operators where the dimensions can be combined into 2 consecutive
73+
# groups. These 2 groups are then transposed like a matrix, and the result is reshaped. Therefore, for the
74+
# [0, 2, 3, 1] permutation, when h * w != 1 and c != 1, batch size must be 1.
75+
return False
76+
77+
return True
78+
79+
return False

0 commit comments

Comments
 (0)