Skip to content

Commit 3ddb14b

Browse files
committed
NXP backend: Add test cases for new Neutron C flow
1 parent 3af4a97 commit 3ddb14b

2 files changed

Lines changed: 131 additions & 7 deletions

File tree

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

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

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)