Skip to content

Commit 7f474aa

Browse files
committed
feat: added support for abs using new Neutron flow
1 parent a58fcf1 commit 7f474aa

3 files changed

Lines changed: 139 additions & 29 deletions

File tree

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
# Copyright 2025 NXP
1+
# Copyright 2025-2026 NXP
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.
55

66

7+
import torch
8+
79
from executorch.backends.nxp.backend.ir.converter.node_converter import (
810
CustomDelegationOptions,
11+
NeutronTargetSpec,
912
NodeConverter,
1013
)
1114
from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import (
@@ -25,6 +28,25 @@ def _is_supported_in_IR(
2528
) -> bool:
2629
return True
2730

31+
@staticmethod
32+
def _is_supported_on_target(
33+
node: Node,
34+
neutron_target_spec: NeutronTargetSpec,
35+
parameters_mapping: dict[str, Parameter],
36+
custom_delegation_options: CustomDelegationOptions,
37+
) -> bool:
38+
39+
if custom_delegation_options.use_new_flow_neutron_c:
40+
# Requirements specified by the new Neutron flow documentation.
41+
42+
supported_types = [torch.int8, torch.uint8]
43+
if not NodeConverter.uses_quantization_type_for_io(
44+
node, supported_types, [0], None
45+
):
46+
return False
47+
48+
return True
49+
2850
def convert(self, node: Node):
2951
"""Convert 'aten::abs' operator to TFLite 'Abs'."""
3052
self.assert_convertible(node)

backends/nxp/tests/dataset_creator.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ def generate_samples(
4545
class RandomDatasetCreator(DatasetCreator):
4646
"""Dataset creator that generates random input samples."""
4747

48-
def __init__(self, num_samples=2):
48+
def __init__(self, num_samples=2, low=0.0, high=1.0):
4949
self._num_samples = num_samples
50+
self.low = low
51+
self.high = high
5052

5153
def generate_samples(
5254
self, dataset_dir: str, input_spec: list[ModelInputSpec]
@@ -103,9 +105,11 @@ def _gen_samples(
103105
case _:
104106
raise ValueError(f"Unsupported dim_order: {spec.dim_order}")
105107

106-
sample_vector = rng.random(
107-
np.prod(shape), torch_type_to_numpy_type(spec.dtype)
108-
).reshape(shape)
108+
sample_vector = (
109+
rng.uniform(self.low, self.high, size=np.prod(shape))
110+
.astype(torch_type_to_numpy_type(spec.dtype))
111+
.reshape(shape)
112+
)
109113
file_name = (
110114
f"{str(spec_idx).zfill(2)}.bin"
111115
if len(input_spec) > 1

backends/nxp/tests/ir/converter/node_converter/test_abs_converter.py

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
# Copyright 2025 NXP
1+
# Copyright 2025-2026 NXP
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.
55

66
import numpy as np
77
import pytest
88
import torch
9-
109
from executorch.backends.nxp.backend.edge_program_converter import (
1110
EdgeProgramToIRConverter,
1211
)
@@ -17,6 +16,15 @@
1716
ToChannelFirstPreprocess,
1817
ToChannelLastPreprocess,
1918
)
19+
from executorch.backends.nxp.tests.graph_verifier import (
20+
BaseGraphVerifier,
21+
NonDelegatedNode,
22+
)
23+
24+
from executorch.backends.nxp.tests.nsys_testing import (
25+
lower_run_compare,
26+
RandomDatasetCreator,
27+
)
2028

2129
from executorch.exir.dialects._ops import ops as exir_ops
2230
from torch.export import ExportedProgram
@@ -29,7 +37,7 @@ def reseed_model_per_test_run():
2937
np.random.seed(23)
3038

3139

32-
class ConvBlocksWithAbs(torch.nn.Module):
40+
class ConvBlocksWithAbsModule(torch.nn.Module):
3341
def __init__(self, conv_in_channels: int = 3):
3442
super().__init__()
3543
self.block1 = torch.nn.Sequential(
@@ -56,36 +64,112 @@ def forward(self, x):
5664
return self.block2(x)
5765

5866

59-
class Abs(torch.nn.Module):
67+
class AbsModule(torch.nn.Module):
6068
def __init__(self):
6169
super().__init__()
6270

6371
def forward(self, x):
6472
return x.abs()
6573

6674

67-
def test_conv_abs(mocker, use_qat, input_shape: tuple[int] = (1, 3, 112, 112)):
68-
model = ConvBlocksWithAbs(conv_in_channels=input_shape[1])
75+
class TestAbsLegacyNeutronFlow:
76+
def test_conv_abs(
77+
self, mocker, use_qat, input_shape: tuple[int, ...] = (1, 3, 112, 112)
78+
):
79+
model = ConvBlocksWithAbsModule(conv_in_channels=input_shape[1])
6980

70-
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
81+
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
7182

72-
quantized_program = to_quantized_edge_program(
73-
model, input_shape, use_qat=use_qat, use_neutron_for_format_conversion=False
74-
).exported_program()
83+
quantized_program = to_quantized_edge_program(
84+
model,
85+
input_shape,
86+
use_qat=use_qat,
87+
use_neutron_for_format_conversion=False,
88+
use_new_flow_neutron_c=False,
89+
).exported_program()
7590

76-
tflite_flatbuffers_model, io_formats = converter_spy.spy_return
77-
exported_program: ExportedProgram = converter_spy.call_args.args[1]
91+
tflite_flatbuffers_model, io_formats = converter_spy.spy_return
92+
exported_program: ExportedProgram = converter_spy.call_args.args[1]
7893

79-
assert not graph_contains_any_of_ops(
80-
graph=quantized_program.graph, ops=[exir_ops.edge.aten.abs.default]
81-
)
94+
assert not graph_contains_any_of_ops(
95+
graph=quantized_program.graph, ops=[exir_ops.edge.aten.abs.default]
96+
)
8297

83-
input_data = (np.random.random(input_shape) * 50).astype(np.int8)
84-
convert_run_compare(
85-
exported_program,
86-
tfl_model=tflite_flatbuffers_model,
87-
tflite_input_preprocess=ToChannelLastPreprocess(),
88-
tflite_output_preprocess=ToChannelFirstPreprocess(),
89-
input_data=input_data,
90-
atol=1.0,
91-
)
98+
input_data = (np.random.random(input_shape) * 50).astype(np.int8)
99+
convert_run_compare(
100+
exported_program,
101+
tfl_model=tflite_flatbuffers_model,
102+
tflite_input_preprocess=ToChannelLastPreprocess(),
103+
tflite_output_preprocess=ToChannelFirstPreprocess(),
104+
input_data=input_data,
105+
atol=1.0,
106+
)
107+
108+
109+
class TestAbsNewNeutronFlow:
110+
@staticmethod
111+
def _get_dataset_creator():
112+
# to test `abs` reliably, we need to include negative values
113+
low = -255.0
114+
high = 255.0
115+
116+
dataset = RandomDatasetCreator(low=low, high=high)
117+
return dataset
118+
119+
def test__basic_nsys_inference(self):
120+
input_shape = (2, 3, 6, 7)
121+
model = AbsModule()
122+
graph_verifier = BaseGraphVerifier(
123+
exp_num_delegate_call_nodes=1, # Delegated Abs.
124+
exp_non_delegated_nodes=[],
125+
)
126+
127+
dataset_creator = self._get_dataset_creator()
128+
lower_run_compare(
129+
model,
130+
input_shape,
131+
graph_verifier,
132+
dataset_creator,
133+
use_new_flow_neutron_c=True,
134+
)
135+
136+
def test__basic_nsys_inference__big(self):
137+
# some operators have delegation requirement that size must be < 4096
138+
input_shape = (4097, 1)
139+
model = AbsModule()
140+
graph_verifier = BaseGraphVerifier(
141+
exp_num_delegate_call_nodes=1, # Delegated Abs.
142+
exp_non_delegated_nodes=[],
143+
)
144+
145+
dataset_creator = self._get_dataset_creator()
146+
lower_run_compare(
147+
model,
148+
input_shape,
149+
graph_verifier,
150+
dataset_creator,
151+
use_new_flow_neutron_c=True,
152+
)
153+
154+
def test_basic_nsys_inference__with_conv(self):
155+
input_shape = (2, 3, 6, 7)
156+
in_channels = input_shape[1]
157+
model = ConvBlocksWithAbsModule(conv_in_channels=in_channels)
158+
graph_verifier = BaseGraphVerifier(
159+
exp_num_delegate_call_nodes=1, # Delegated `Abs` + `Relu` in one partition
160+
exp_non_delegated_nodes=[
161+
NonDelegatedNode("aten_convolution_default", 2),
162+
NonDelegatedNode(
163+
"aten_relu_default", 1 # One `Relu` ends up in partition with `Abs`
164+
),
165+
],
166+
)
167+
168+
dataset_creator = self._get_dataset_creator()
169+
lower_run_compare(
170+
model,
171+
input_shape,
172+
graph_verifier,
173+
dataset_creator,
174+
use_new_flow_neutron_c=True,
175+
)

0 commit comments

Comments
 (0)