66import numpy as np
77import pytest
88import torch
9-
109from 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+ )
1422from 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+ )
1836from 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-
3246class 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+ )
0 commit comments