Skip to content

Commit 06f05ca

Browse files
authored
Add exp support using new Neutron flow (#20300)
### Summary NXP backend: Enable aten.exp with new Neutron flow. ### Test plan Tests provided.
1 parent 6aa1fce commit 06f05ca

9 files changed

Lines changed: 150 additions & 0 deletions

File tree

backends/nxp/backend/edge_program_converter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
exir_ops.edge.dim_order_ops._clone_dim_order.default: CloneConverter, # noqa F405
4040
exir_ops.edge.aten.constant_pad_nd.default: ConstantPadNDConverter, # noqa F405
4141
exir_ops.edge.aten.convolution.default: ConvolutionConverter, # noqa F405
42+
exir_ops.edge.aten.exp.default: ExpConverter, # noqa F405
4243
exir_ops.edge.aten.hardtanh.default: HardTanhConverter, # noqa F405
4344
exir_ops.edge.aten.leaky_relu.default: LeakyReluConverter, # noqa F405
4445
exir_ops.edge.aten.log.default: LogConverter, # 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
@@ -31,6 +31,9 @@
3131
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.convolution_converter import (
3232
ConvolutionConverter,
3333
)
34+
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.exp_converter import (
35+
ExpConverter,
36+
)
3437
from executorch.backends.nxp.backend.ir.converter.node_converters.ops_converters.getitem_converter import (
3538
GetItemConverter,
3639
)
@@ -111,6 +114,7 @@
111114
"CloneConverter",
112115
"ConstantPadNDConverter",
113116
"ConvolutionConverter",
117+
"ExpConverter",
114118
"GetItemConverter",
115119
"HardTanhConverter",
116120
"LeakyReluConverter",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
import torch
6+
from executorch.backends.nxp.backend.ir.converter.node_converter import (
7+
CustomDelegationOptions,
8+
NeutronTargetSpec,
9+
NodeConverter,
10+
)
11+
from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options.exp_options import (
12+
Exp,
13+
)
14+
from torch.fx import Node
15+
from torch.nn import Parameter
16+
17+
18+
class ExpConverter(NodeConverter):
19+
20+
@staticmethod
21+
def _is_supported_in_IR(
22+
node: Node,
23+
parameters_mapping: dict[str, Parameter],
24+
custom_delegation_options: CustomDelegationOptions,
25+
) -> bool:
26+
return True
27+
28+
@staticmethod
29+
def _is_supported_on_target(
30+
node: Node,
31+
neutron_target_spec: NeutronTargetSpec,
32+
parameters_mapping: dict[str, Parameter],
33+
custom_delegation_options: CustomDelegationOptions,
34+
) -> bool:
35+
# Requirements specified by the new Neutron flow documentation.
36+
# Input and Output must be INT8/UINT8.
37+
if not NodeConverter.uses_quantization_type_for_io(
38+
node,
39+
supported_types=[torch.int8, torch.uint8],
40+
input_indices=[0],
41+
output_indices=[0],
42+
):
43+
return False
44+
return True
45+
46+
def convert(self, node: Node):
47+
"""Convert the `aten.exp.default` operator to Neutron IR `Exp`.
48+
The schema is:
49+
aten::exp(
50+
Tensor self
51+
) -> Tensor
52+
"""
53+
54+
self.assert_convertible(node)
55+
56+
t_op = self._create_tflite_op_with_io_tensors(node)
57+
t_op.builtin_options = Exp()
58+
59+
self.builder.append_operators([t_op])

backends/nxp/neutron_partitioner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def tag_qdq_clusters(self, nodes: list[torch.fx.Node]):
212212
exir_ops.edge.dim_order_ops._clone_dim_order.default: CloneConverter, # noqa F405
213213
exir_ops.edge.aten.constant_pad_nd.default: ConstantPadNDConverter, # noqa F405
214214
exir_ops.edge.aten.convolution.default: ConvolutionConverter, # noqa F405
215+
exir_ops.edge.aten.exp.default: ExpConverter, # noqa F405
215216
exir_ops.edge.aten.hardtanh.default: HardTanhConverter, # noqa F405
216217
exir_ops.edge.aten.leaky_relu.default: LeakyReluConverter, # noqa F405
217218
exir_ops.edge.aten.log.default: LogConverter, # noqa F405

backends/nxp/quantizer/neutron_quantizer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Conv2dPattern,
2626
ConvTranspose2dPattern,
2727
DropoutPattern,
28+
ExpPattern,
2829
FlattenPattern,
2930
HardTanhInPlacePattern,
3031
HardTanhPattern,
@@ -270,6 +271,7 @@ def __init__(self, neutron_target_spec: NeutronTargetSpec, is_qat: bool = False)
270271
ConvTranspose2dPattern(self, is_qat=is_qat), static_qconfig
271272
),
272273
OpQuantizer(DropoutPattern(is_qat=is_qat), static_qconfig),
274+
OpQuantizer(ExpPattern(is_qat=is_qat), static_qconfig),
273275
OpQuantizer(FlattenPattern(is_qat=is_qat), static_qconfig),
274276
OpQuantizer(HardTanhPattern(is_qat=is_qat), static_qconfig),
275277
OpQuantizer(HardTanhInPlacePattern(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
@@ -712,6 +712,15 @@ def partition_types(self):
712712
return [torch.ops.aten.dropout.default]
713713

714714

715+
class ExpPattern(SharedSpecPattern):
716+
"""
717+
Quantizer for Exp operator.
718+
"""
719+
720+
def partition_types(self):
721+
return [torch.ops.aten.exp.default]
722+
723+
715724
class FlattenPattern(SharedSpecPattern):
716725
"""
717726
Quantizer for Flatten operator.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 numpy as np
7+
8+
# noinspection PyUnusedImports
9+
import pytest
10+
import torch
11+
12+
from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier
13+
from executorch.backends.nxp.tests.nsys_testing import lower_run_compare
14+
from executorch.backends.nxp.tests.ops_aliases import Exp
15+
from executorch.backends.nxp.tests.use_qat import * # noqa F403
16+
from executorch.backends.nxp.tests.dataset_creator import (
17+
LinearRampDatasetCreator,
18+
RandomDatasetCreator,
19+
)
20+
21+
22+
@pytest.fixture(autouse=True)
23+
def reseed_model_per_test_run():
24+
torch.manual_seed(42)
25+
np.random.seed(23)
26+
27+
28+
class ExpModule(torch.nn.Module):
29+
30+
def __init__(self):
31+
super().__init__()
32+
33+
def forward(self, x):
34+
return torch.exp(x)
35+
36+
37+
class TestExpNewNeutronFlow:
38+
def test__basic_nsys_inference(self, mocker, request):
39+
input_shape = (256,)
40+
model = ExpModule()
41+
graph_verifier = DetailedGraphVerifier(
42+
mocker, expected_delegated_ops={Exp: 1}, expected_non_delegated_ops={}
43+
)
44+
lower_run_compare(
45+
model,
46+
input_shape,
47+
graph_verifier,
48+
request,
49+
dataset_creator=LinearRampDatasetCreator(),
50+
)
51+
52+
@pytest.mark.parametrize(
53+
"input_shape",
54+
[
55+
pytest.param((17, 2), id="2D"),
56+
pytest.param((1, 3, 10), id="3D"),
57+
pytest.param((1, 3, 16, 16), id="4D"),
58+
],
59+
)
60+
def test__basic_nsys_inference__qat(self, mocker, request, input_shape, use_qat):
61+
model = ExpModule()
62+
graph_verifier = DetailedGraphVerifier(
63+
mocker, expected_delegated_ops={Exp: 1}, expected_non_delegated_ops={}
64+
)
65+
lower_run_compare(
66+
model,
67+
input_shape,
68+
graph_verifier,
69+
request,
70+
dataset_creator=RandomDatasetCreator(low=-1.0, high=1.0),
71+
use_qat=use_qat,
72+
)

backends/nxp/tests/ops_aliases.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
DequantizePerChannel = exir_ops.edge.quantized_decomposed.dequantize_per_channel.default
2727
DequantizePerTensor = exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default
2828
ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate
29+
Exp = exir_ops.edge.aten.exp.default
2930
GetItem = operator.getitem
3031
HardTanh = exir_ops.edge.aten.hardtanh.default
3132
HardTanh_ = exir_ops.edge.aten.hardtanh_.default

docs/source/backends/nxp/op-support.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ aten.constant_pad_nd.default,int8,static int8,"H or W padding only"
1313
aten.convolution.default,int8,static int8,"1D or 2D convolution, constant weights, groups=1 or groups=channels_count (depthwise)"
1414
aten.dim_order_ops._clone_dim_order.default,,, "See aten.clone.default"
1515
aten.div.Tensor,int8,static int8,"divisor - static tensor or scalar value, one dimension must satisfy %8 = 0 or scalar division (all dims = 1)"
16+
aten.exp.default,int8,static int8,
1617
aten.hardtanh.default,int8,static int8,"supported ranges: <0,6>, <-1, 1>, <0,1>, <0,inf>"
1718
aten.leaky_relu.default,int8,static int8,
1819
aten.log.default,int8,static int8,

0 commit comments

Comments
 (0)