Skip to content

Commit 3fa4f11

Browse files
NXP backend: Enable Amax with new Neutron flow
1 parent 368a3f2 commit 3fa4f11

10 files changed

Lines changed: 535 additions & 1 deletion

File tree

backends/nxp/backend/edge_program_converter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
exir_ops.edge.aten._adaptive_avg_pool2d.default: AdaptiveAvgPool2dConverter, # noqa F405
3232
exir_ops.edge.aten.addmm.default: AddMMConverter, # noqa F405
3333
exir_ops.edge.aten.add.Tensor: AddTensorConverter, # noqa F405
34+
exir_ops.edge.aten.amax.default: AmaxConverter, # noqa F405
3435
exir_ops.edge.aten.amin.default: AminConverter, # noqa F405
3536
exir_ops.edge.aten.avg_pool2d.default: AvgPool2dConverter, # noqa F405
3637
exir_ops.edge.aten.bmm.default: BMMConverter, # noqa F405

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.addmm_converter import (
1111
AddMMConverter,
1212
)
13+
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.amax_converter import (
14+
AmaxConverter,
15+
)
1316
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.amin_converter import (
1417
AminConverter,
1518
)
@@ -110,6 +113,7 @@
110113
"AdaptiveAvgPool2dConverter",
111114
"AddMMConverter",
112115
"AddTensorConverter",
116+
"AmaxConverter",
113117
"AminConverter",
114118
"AvgPool2dConverter",
115119
"BMMConverter",
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2026 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+
import torch
7+
8+
from executorch.backends.nxp.backend.ir.converter.conversion.common import OpsList
9+
from executorch.backends.nxp.backend.ir.converter.node_converter import (
10+
CustomDelegationOptions,
11+
is_not_qdq_node,
12+
NodeConverter,
13+
)
14+
from executorch.backends.nxp.backend.ir.converter.node_converters.shared.reduce_utils import (
15+
convert_axes_from_attribute,
16+
get_dim_and_handle_io_formats,
17+
get_reduce_node_attrs,
18+
)
19+
from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import (
20+
reduce_max_options,
21+
)
22+
from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec
23+
from torch.fx import Node
24+
from torch.fx.passes.infra.partitioner import Partition
25+
from torch.nn import Parameter
26+
27+
28+
class AmaxConverter(NodeConverter):
29+
30+
@classmethod
31+
def supports_partitioning_result(
32+
cls,
33+
node: Node,
34+
partition_list: list[Partition],
35+
custom_delegation_options: CustomDelegationOptions,
36+
neutron_target_spec: NeutronTargetSpec,
37+
parameters_mapping: dict[str, Parameter],
38+
) -> bool:
39+
dim, keepdim = get_reduce_node_attrs(node)
40+
input_shape = node.args[0].meta["val"].shape
41+
42+
is_alone_in_partition = cls.is_node_alone_in_partition(
43+
node, partition_list, filter_fn=is_not_qdq_node
44+
)
45+
46+
if is_alone_in_partition and keepdim and all(input_shape[d] == 1 for d in dim):
47+
# The operator is a no-op, so the Neutron Converter will skip it. If it's the only node in the
48+
# partition, the graph would end up empty.
49+
return False
50+
51+
return True
52+
53+
@staticmethod
54+
def _is_supported_on_target(
55+
node: Node,
56+
neutron_target_spec: NeutronTargetSpec,
57+
parameters_mapping: dict[str, Parameter],
58+
custom_delegation_options: CustomDelegationOptions,
59+
) -> bool:
60+
if not NodeConverter.uses_quantization_type_for_io(
61+
node,
62+
supported_types=[torch.int8, torch.uint8],
63+
input_indices=[0],
64+
output_indices=[0],
65+
):
66+
return False
67+
68+
return True
69+
70+
@staticmethod
71+
def _is_supported_in_IR(
72+
node: Node,
73+
parameters_mapping: dict[str, Parameter],
74+
custom_delegation_options: CustomDelegationOptions,
75+
) -> bool:
76+
if not NodeConverter._has_shared_q_params_if_quantized(node):
77+
return False
78+
79+
return True
80+
81+
def convert(self, node: Node):
82+
"""Convert the 'amax' operator to NeutronIR 'ReduceMax'.
83+
The ExecuTorch schema is:
84+
amax(
85+
Tensor self,
86+
int[1]? dim,
87+
bool keepdim=False,
88+
) -> Tensor
89+
"""
90+
self.assert_convertible(node)
91+
92+
dim, keepdim = get_reduce_node_attrs(node)
93+
94+
t_op = self._create_tflite_op_with_io_tensors(node)
95+
t_op.builtin_options = reduce_max_options.ReduceMax(keepdim)
96+
97+
ops = OpsList(middle_op=t_op)
98+
dim = get_dim_and_handle_io_formats(self.builder, ops, dim, keepdim)
99+
100+
convert_axes_from_attribute(t_op, self.builder, dim)
101+
self.builder.append_operators(ops.flatten())

backends/nxp/backend/node_format_inference.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from executorch.backends.nxp.backend.edge_program_converter import functions_converters
1717
from executorch.backends.nxp.tests.ops_aliases import (
1818
AdaptiveAvgPool2D,
19+
Amax,
1920
Amin,
2021
AvgPool2D,
2122
Convolution,
@@ -59,6 +60,7 @@ class NodeFormatInference:
5960
ViewCopy,
6061
PermuteCopy,
6162
MeanDim,
63+
Amax,
6264
Amin,
6365
}
6466

@@ -136,7 +138,7 @@ def _infer_format_of_nodes(self, node: Node):
136138
self._node_inputs[node][0], DataFormat.FORMATLESS
137139
)
138140

139-
elif op_type in [MeanDim, Amin]:
141+
elif op_type in [MeanDim, Amax, Amin]:
140142
# The operator schema is:
141143
# <reduce_op>(Tensor self, int[1]? dim, bool keepdim=False, *, ScalarType? dtype=None) -> Tensor
142144
keep_dim = try_get_arg(node, 2) or False

backends/nxp/neutron_partitioner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def tag_qdq_clusters(self, nodes: list[torch.fx.Node]):
204204
exir_ops.edge.aten._adaptive_avg_pool2d.default: AdaptiveAvgPool2dConverter, # noqa F405
205205
exir_ops.edge.aten.addmm.default: AddMMConverter, # noqa F405
206206
exir_ops.edge.aten.add.Tensor: AddTensorConverter, # noqa F405
207+
exir_ops.edge.aten.amax.default: AmaxConverter, # noqa F405
207208
exir_ops.edge.aten.amin.default: AminConverter, # noqa F405
208209
exir_ops.edge.aten.avg_pool2d.default: AvgPool2dConverter, # noqa F405
209210
exir_ops.edge.aten.bmm.default: BMMConverter, # noqa F405

backends/nxp/quantizer/neutron_quantizer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
AdaptiveAvgPoolPattern,
1717
AddmmPattern,
1818
AddTensorPattern,
19+
AmaxPattern,
1920
AminPattern,
2021
AvgPool1DPattern,
2122
AvgPool2DPattern,
@@ -261,6 +262,7 @@ def __init__(self, neutron_target_spec: NeutronTargetSpec, is_qat: bool = False)
261262
OpQuantizer(AdaptiveAvgPoolPattern(is_qat=is_qat), static_qconfig),
262263
OpQuantizer(AddTensorPattern(is_qat=is_qat), static_qconfig),
263264
OpQuantizer(AddmmPattern(self, is_qat=is_qat), static_fc_qconfig),
265+
OpQuantizer(AmaxPattern(is_qat=is_qat), static_qconfig),
264266
OpQuantizer(AminPattern(is_qat=is_qat), static_qconfig),
265267
OpQuantizer(AvgPool1DPattern(is_qat=is_qat), static_qconfig),
266268
OpQuantizer(AvgPool2DPattern(is_qat=is_qat), static_qconfig),

backends/nxp/quantizer/patterns.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,15 @@ def get_anchors(
319319
)
320320

321321

322+
class AmaxPattern(SharedSpecPattern):
323+
"""
324+
Quantizer for Amax operator.
325+
"""
326+
327+
def partition_types(self):
328+
return [torch.ops.aten.amax.default]
329+
330+
322331
class AminPattern(SharedSpecPattern):
323332
"""
324333
Quantizer for Amin operator.

0 commit comments

Comments
 (0)