Skip to content

Commit 7e29253

Browse files
NXP backend: Enable permute_copy with new Neutron MLIR flow. (#19974)
### Summary This PR updates the support for the `permute_copy` operator in the NXP backend to reflect the requirements of the new Neutron MLIR flow. In short, Neutron now supports all possible permutations without any restrictions. ### Test plan Unit tests provided. cc @robert-kalmar @JakeStevens @digantdesai @rascani
1 parent 586a79c commit 7e29253

21 files changed

Lines changed: 366 additions & 799 deletions

backends/nxp/backend/edge_helper.py

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,45 @@
88

99
import torch
1010

11-
from executorch.exir.dialects._ops import ops as exir_ops
11+
from executorch.backends.nxp.tests.ops_aliases import (
12+
AddTensor,
13+
Cat,
14+
Clone,
15+
CloneDimOrder,
16+
DequantizePerChannel,
17+
DequantizePerTensor,
18+
MulTensor,
19+
PermuteCopy,
20+
QuantizePerChannel,
21+
QuantizePerTensor,
22+
SubTensor,
23+
ViewCopy,
24+
)
1225
from torch.fx import GraphModule, Node
1326
from torch.fx.node import Argument
1427
from torch.nn import Parameter
1528

1629
QUANTIZE_OPERATORS = [
17-
exir_ops.edge.quantized_decomposed.quantize_per_channel.default,
18-
exir_ops.edge.quantized_decomposed.quantize_per_tensor.default,
30+
QuantizePerChannel,
31+
QuantizePerTensor,
32+
torch.ops.quantized_decomposed.quantize_per_tensor.default,
33+
torch.ops.quantized_decomposed.quantize_per_channel.default,
1934
]
2035

2136
DEQUANTIZE_OPERATORS = [
22-
exir_ops.edge.quantized_decomposed.dequantize_per_channel.default,
23-
exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default,
37+
DequantizePerChannel,
38+
DequantizePerTensor,
39+
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
40+
torch.ops.quantized_decomposed.dequantize_per_channel.default,
2441
]
2542

2643
# A set of operators which could possibly be no-ops in certain conditions. The operators in this set will be proclaimed
2744
# as no-ops (and potentially not delegated), if their input and output tensors are equal (when run on random data).
2845
no_op_candidates = {
29-
exir_ops.edge.aten.add.Tensor,
30-
exir_ops.edge.aten.mul.Tensor,
31-
exir_ops.edge.aten.sub.Tensor,
46+
AddTensor,
47+
MulTensor,
48+
PermuteCopy,
49+
SubTensor,
3250
}
3351

3452

@@ -108,21 +126,11 @@ def try_get_tensor_constant_from_node(
108126

109127

110128
def _is_dequantize(node_: Node) -> bool:
111-
return node_.op == "call_function" and node_.target in [
112-
exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default,
113-
exir_ops.edge.quantized_decomposed.dequantize_per_channel.default,
114-
torch.ops.quantized_decomposed.dequantize_per_tensor.default,
115-
torch.ops.quantized_decomposed.dequantize_per_channel.default,
116-
]
129+
return node_.op == "call_function" and node_.target in DEQUANTIZE_OPERATORS
117130

118131

119132
def _is_quantize(node_: Node) -> bool:
120-
return node_.op == "call_function" and node_.target in [
121-
exir_ops.edge.quantized_decomposed.quantize_per_tensor.default,
122-
exir_ops.edge.quantized_decomposed.quantize_per_channel.default,
123-
torch.ops.quantized_decomposed.quantize_per_tensor.default,
124-
torch.ops.quantized_decomposed.quantize_per_channel.default,
125-
]
133+
return node_.op == "call_function" and node_.target in QUANTIZE_OPERATORS
126134

127135

128136
def previous_non_qdq_node(node: Node, input_index: int = 0) -> Node | None:
@@ -172,21 +180,11 @@ def get_non_qdq_users(node: Node) -> list[Node]:
172180
"""
173181

174182
quant_nodes = list(node.users)
175-
if len(quant_nodes) != 1 or quant_nodes[0].target not in [
176-
exir_ops.edge.quantized_decomposed.quantize_per_tensor.default,
177-
exir_ops.edge.quantized_decomposed.quantize_per_channel.default,
178-
]:
183+
if len(quant_nodes) != 1 or not _is_quantize(quant_nodes[0]):
179184
return []
180185

181186
dequant_nodes = list(quant_nodes[0].users)
182-
if any(
183-
dequant_node.target
184-
not in [
185-
exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default,
186-
exir_ops.edge.quantized_decomposed.dequantize_per_channel.default,
187-
]
188-
for dequant_node in dequant_nodes
189-
):
187+
if any(not _is_dequantize(dequant_node) for dequant_node in dequant_nodes):
190188
return []
191189

192190
res = []
@@ -277,14 +275,14 @@ def is_no_op_on_neutron(node: Node, parameters_mapping: dict[str, Parameter]) ->
277275
)
278276

279277
if node.target in [
280-
exir_ops.edge.aten.view_copy.default,
281-
exir_ops.edge.dim_order_ops._clone_dim_order.default,
282-
exir_ops.edge.aten.clone.default,
278+
Clone,
279+
ViewCopy,
280+
CloneDimOrder,
283281
]:
284282
# Known operators which are always no-ops on Neutron.
285283
return True
286284

287-
if node.target == exir_ops.edge.aten.cat.default and len(node.args[0]) == 1:
285+
if node.target == Cat and len(node.args[0]) == 1:
288286
# Concatenation with 1 input is a no-op.
289287
return True
290288

backends/nxp/backend/edge_program_converter.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818
from torch._subclasses import FakeTensor
1919
from torch.export import ExportedProgram
20-
from torch.export.graph_signature import InputKind
20+
from torch.export.graph_signature import ExportGraphSignature, InputKind
2121
from torch.fx import Node
2222
from torch.nn.parameter import Parameter
2323
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters import * # noqa F403
@@ -78,7 +78,7 @@ def convert_program(
7878
conversion_config: ConversionConfig = _default_conversion_config,
7979
neutron_target_spec: NeutronTargetSpec = _default_target_spec,
8080
custom_delegation_options: CustomDelegationOptions = _default_delegation_options,
81-
) -> tuple[bytes, dict[str, DataFormat]]:
81+
) -> tuple[bytes, dict[str, dict[str, DataFormat]]]:
8282
"""
8383
Convert ExportedProgram in Edge dialect to IR (TFLite flatbuffers) as bytes.
8484
@@ -95,6 +95,7 @@ def convert_program(
9595
parameters_mapping,
9696
dim_order_map,
9797
neutron_target_spec,
98+
edge_program.graph_signature,
9899
conversion_config,
99100
custom_delegation_options,
100101
)
@@ -247,8 +248,9 @@ def map_nodes_to_dim_order(edge_program: ExportedProgram) -> dict[str, Parameter
247248
@staticmethod
248249
def build_conversion_context(
249250
parameters_mapping: dict,
250-
dim_order_map: dict[str, ...],
251+
dim_order_map: dict[str, Parameter],
251252
neutron_target_spec: NeutronTargetSpec,
253+
edge_program_signature: ExportGraphSignature,
252254
conversion_config: ConversionConfig = _default_conversion_config,
253255
custom_delegation_options: CustomDelegationOptions = _default_delegation_options,
254256
) -> ConversionContext:
@@ -268,6 +270,7 @@ def build_conversion_context(
268270
conversion_config,
269271
parameters_mapping,
270272
custom_delegation_options,
273+
edge_program_signature,
271274
)
272275

273276
return context

backends/nxp/backend/ir/conversion_context.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
#
33
# This source code is licensed under the BSD-style license found in the
44
# LICENSE file in the root directory of this source tree.
5-
65
from executorch.backends.nxp.backend.custom_delegation_options import (
76
CustomDelegationOptions,
87
)
98
from executorch.backends.nxp.backend.ir.conversion_config import ConversionConfig
109
from executorch.backends.nxp.backend.ir.converter.builder.aten_model_builder_director import (
1110
AtenModelBuilderDirector,
1211
)
12+
from torch.export import ExportGraphSignature
1313
from torch.nn import Parameter
1414

1515

@@ -23,16 +23,21 @@ def __init__(
2323
self,
2424
tflite_builder: AtenModelBuilderDirector,
2525
conversion_config: ConversionConfig,
26-
parameters_mapping: dict,
26+
parameters_mapping: dict[str, Parameter],
2727
custom_delegation_options: CustomDelegationOptions,
28+
edge_program_signature: ExportGraphSignature,
2829
):
2930
"""
3031
Context with data related to current conversion.
3132
3233
:param tflite_builder: TFLite model builder.
3334
:param conversion_config: Conversion configuration flags and metadata.
35+
:param parameters_mapping: Dictionary mapping node names to their data.
36+
:param custom_delegation_options: Options that affect which nodes will be delegated.
37+
:param edge_program_signature: Description of the inputs of the edge graph.
3438
"""
3539
self.tflite_builder = tflite_builder
3640
self.conversion_config = conversion_config
3741
self.parameters_mapping = parameters_mapping
3842
self.custom_delegation_options = custom_delegation_options
43+
self.edge_program_signature = edge_program_signature

backends/nxp/backend/ir/converter/node_converters/ops_converters/getitem_converter.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,16 @@ def convert(self, node: Node):
4141
input_.type = output.type
4242
propagate_quantization(from_tensor=output, to_tensor=input_)
4343

44-
self.builder.turn_operator_to_identity(t_op)
45-
self.builder.append_operators([t_op])
44+
consumes_model_input = (
45+
node.args[0].name in self.context.edge_program_signature.user_inputs
46+
)
47+
if consumes_model_input:
48+
# Convert as identity op (Transpose that will be removed) because the input tensor is also an input of the
49+
# model. If we did redirection here, we would change the name of a model input, which is prohibited.
50+
self.builder.turn_operator_to_identity(t_op)
51+
self.builder.append_operators([t_op])
52+
else:
53+
# The operator will be converted to nothing. That means its output will not be in the model. We need to
54+
# redirect the output to the input, so that any operators that consume the `output` will use the `input_`
55+
# instead.
56+
self.builder.redirect_tensor(output, input_)

0 commit comments

Comments
 (0)