Skip to content

Commit 7b06dec

Browse files
committed
NXP backend: Add test cases for new Neutron C flow
1 parent 0a4a466 commit 7b06dec

2 files changed

Lines changed: 133 additions & 7 deletions

File tree

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

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,35 @@
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
)
13-
from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program
12+
from executorch.backends.nxp.backend.ir.converter.builder.aten_model_builder_director import (
13+
AtenModelBuilderDirector,
14+
)
15+
from executorch.backends.nxp.backend.ir.lib.tflite.BuiltinOperator import (
16+
BuiltinOperator as Ops,
17+
)
18+
from executorch.backends.nxp.tests.executorch_pipeline import (
19+
ModelInputSpec,
20+
to_quantized_edge_program,
21+
)
1422
from executorch.backends.nxp.tests.executors import (
1523
convert_run_compare,
1624
graph_contains_any_of_ops,
1725
)
26+
from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier
27+
from executorch.backends.nxp.tests.model_output_comparator import (
28+
NumericalStatsOutputComparator,
29+
)
30+
from executorch.backends.nxp.tests.nsys_testing import lower_run_compare
31+
from executorch.backends.nxp.tests.ops_aliases import (
32+
AddTensor,
33+
Clamp,
34+
ExecutorchDelegateCall,
35+
)
1836
from executorch.exir.dialects._ops import ops as exir_ops
37+
from executorch.backends.nxp.tests.use_qat import * # noqa: F403
1938

2039

2140
@pytest.fixture(autouse=True)
@@ -24,11 +43,6 @@ def reseed_model_per_test_run():
2443
np.random.seed(23)
2544

2645

27-
# noinspection PyProtectedMember
28-
ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate
29-
Clamp = exir_ops.edge.aten.clamp.default
30-
31-
3246
class ClampModule(torch.nn.Module):
3347

3448
# noinspection PyShadowingBuiltins
@@ -180,3 +194,113 @@ def test_convert_clamp__no_delegation__unsupported_bounds(min, max):
180194

181195
# Make sure the `clamp` was NOT delegated.
182196
assert graph_contains_any_of_ops(delegated_ep.graph, [Clamp])
197+
198+
199+
class TestClampNewNeutronFlow:
200+
@pytest.mark.parametrize(
201+
"min, max",
202+
[
203+
pytest.param(-1, 2, id="min = -1, max = 2 (Max/Min)"),
204+
pytest.param(None, 1, id="min = None, max = 1 (Max/Min)"),
205+
pytest.param(1, None, id="min = 1, max = None (Max/Min)"),
206+
pytest.param(0, 2, id="min = 0, max = 2 (Max/Min)"),
207+
pytest.param(0, 1, id="min = 0, max = 1 (Relu0To1)"),
208+
pytest.param(-1, 1, id="min = -1, max = 1 (ReluN1To1)"),
209+
pytest.param(0, None, id="min = 0, max = None (Relu)"),
210+
# # Float bounds
211+
pytest.param(-1.0, 2.0, id="min = -1.0, max = 2.0 (Max/Min)"),
212+
pytest.param(None, 1.0, id="min = None, max = 1.0 (Max/Min)"),
213+
pytest.param(1.0, None, id="min = 1.0, max = None (Max/Min)"),
214+
pytest.param(1.0, float("inf"), id="min = 1.0, max = infinity (Max/Min)"),
215+
pytest.param(-float("inf"), 1.0, id="min = infinity, max = 1.0 (Max/Min)"),
216+
pytest.param(0.1, 0.5, id="min = 0.1, max = 0.5 (Max/Min)"),
217+
pytest.param(0.0, 1.0, id="min = 0.0, max = 1.0 (Relu0To1)"),
218+
pytest.param(-1.0, 1.0, id="min = -1.0, max = 1.0 (ReluN1To1)"),
219+
pytest.param(0.0, None, id="min = 0, max = None (Relu)"),
220+
],
221+
)
222+
def test_convert_clamp__full_pipeline(self, mocker, min, max, use_qat):
223+
input_shape = (2, 7, 2) # Indivisible by num_macs
224+
model = AddClampModule(min, max)
225+
226+
x_input_spec = ModelInputSpec(input_shape)
227+
comparator = NumericalStatsOutputComparator()
228+
graph_verifier = DetailedGraphVerifier(
229+
mocker,
230+
expected_delegated_ops={
231+
AddTensor: 1,
232+
Clamp: 1,
233+
},
234+
expected_non_delegated_ops={},
235+
)
236+
237+
lower_run_compare(
238+
model=model,
239+
input_spec=[x_input_spec],
240+
dlg_model_verifier=graph_verifier,
241+
output_comparator=comparator,
242+
use_new_flow_neutron_c=True,
243+
use_qat=use_qat,
244+
)
245+
246+
# noinspection PyShadowingBuiltins
247+
@pytest.mark.parametrize(
248+
"min, max, expected_tflite_ops",
249+
[
250+
pytest.param(
251+
0.1,
252+
0.5,
253+
[Ops.ADD, Ops.MAXIMUM, Ops.MINIMUM],
254+
id="min = 0.1, max = 0.5 (Max/Min)",
255+
),
256+
pytest.param(
257+
0.0, 1.0, [Ops.ADD, Ops.RELU_0_TO_1], id="min = 0, max = 1 (Relu0To1)"
258+
),
259+
pytest.param(
260+
-1.0,
261+
1.0,
262+
[Ops.ADD, Ops.RELU_N1_TO_1],
263+
id="min = -1, max = 1 (ReluN1To1)",
264+
),
265+
pytest.param(
266+
0.0, None, [Ops.ADD, Ops.RELU], id="min = 0, max = None (Relu)"
267+
),
268+
],
269+
)
270+
def test_convert_clamp__relu_vs_maxmin(self, mocker, min, max, expected_tflite_ops):
271+
input_shape = (23,)
272+
model = AddClampModule(min, max)
273+
274+
converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program")
275+
tflite_spy = mocker.spy(AtenModelBuilderDirector, "finish")
276+
277+
delegated_ep = to_quantized_edge_program(
278+
model,
279+
input_shape,
280+
use_new_flow_neutron_c=True,
281+
).exported_program()
282+
283+
# Make sure the `clamp` was delegated.
284+
assert graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall])
285+
assert not graph_contains_any_of_ops(delegated_ep.graph, [Clamp])
286+
287+
intermediate_ep = converter_spy.call_args.args[1]
288+
quant_node = list(intermediate_ep.graph.nodes)[-2]
289+
dequant_node = list(intermediate_ep.graph.nodes)[-4]
290+
tflite_internal_ops = list(
291+
op.builtin_code for op in tflite_spy.spy_return.operator_codes.vector
292+
)
293+
294+
assert graph_contains_any_of_ops(intermediate_ep.graph, [Clamp])
295+
assert len(tflite_internal_ops) == len(expected_tflite_ops) + 1 # Transpose
296+
assert all(op in tflite_internal_ops for op in expected_tflite_ops)
297+
298+
if len(expected_tflite_ops) == 3:
299+
# Min/Max variant should have same input and output quantization
300+
assert all(
301+
q == dq for q, dq in zip(quant_node.args[1:], dequant_node.args[1:])
302+
)
303+
else:
304+
assert not all(
305+
q == dq for q, dq in zip(quant_node.args[1:], dequant_node.args[1:])
306+
)

backends/nxp/tests/ops_aliases.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
from executorch.exir.dialects._ops import ops as exir_ops
1313

1414
Abs = exir_ops.edge.aten.abs.default
15+
AddTensor = exir_ops.edge.aten.add.Tensor
1516
AvgPool2D = exir_ops.edge.aten.avg_pool2d.default
1617
Bmm = exir_ops.edge.aten.bmm.default
18+
Clamp = exir_ops.edge.aten.clamp.default
1719
Convolution = exir_ops.edge.aten.convolution.default
1820
DequantizePerChannel = exir_ops.edge.quantized_decomposed.dequantize_per_channel.default
1921
DequantizePerTensor = exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default

0 commit comments

Comments
 (0)