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.
5+
56import numpy as np
67import pytest
78import torch
89
910from executorch .backends .nxp .backend .edge_program_converter import (
1011 EdgeProgramToIRConverter ,
1112)
12- from executorch .backends .nxp .tests .executorch_pipeline import to_quantized_edge_program
13+ from executorch .backends .nxp .tests .executorch_pipeline import (
14+ ModelInputSpec ,
15+ to_quantized_edge_program ,
16+ )
1317from executorch .backends .nxp .tests .executors import (
1418 convert_run_compare ,
19+ graph_contains_any_of_ops ,
1520 ToChannelFirstPreprocess ,
1621 ToChannelLastPreprocess ,
1722)
23+ from executorch .backends .nxp .tests .graph_verifier import DetailedGraphVerifier
24+ from executorch .backends .nxp .tests .model_output_comparator import (
25+ NumericalStatsOutputComparator ,
26+ )
1827from executorch .backends .nxp .tests .models import (
1928 AddTensorConvModule ,
2029 AddTensorModule ,
2130 AddTensorOneInputModule ,
2231)
32+ from executorch .backends .nxp .tests .nsys_testing import lower_run_compare
33+ from executorch .backends .nxp .tests .ops_aliases import (
34+ AddTensor ,
35+ Convolution ,
36+ ExecutorchDelegateCall ,
37+ )
2338from torch .export import ExportedProgram
2439from executorch .backends .nxp .tests .use_qat import * # noqa F403
2540
@@ -64,7 +79,6 @@ def test_add_tensor_quant_conversion(mocker, input_shape, use_qat):
6479@pytest .mark .parametrize (
6580 "input_shape" ,
6681 [
67- pytest .param ((4 ,), id = "1D." ),
6882 pytest .param ((6 , 6 ), id = "2D." ),
6983 pytest .param ((1 , 4 , 8 ), id = "3D." ),
7084 pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
@@ -92,20 +106,26 @@ def test_add_tensor_one_input_quant_conversion(mocker, input_shape, use_qat):
92106
93107
94108@pytest .mark .parametrize (
95- "input_shape " ,
109+ "x_input_shape " ,
96110 [
97111 pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
98112 pytest .param ((1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8." ),
99113 ],
100114)
101- def test_add_tensor_w_conv_quant_conversion (mocker , input_shape , use_qat ):
115+ def test_add_tensor_w_conv_quant_conversion (mocker , x_input_shape , use_qat ):
102116 model = AddTensorConvModule ()
103117
104118 converter_spy = mocker .spy (EdgeProgramToIRConverter , "convert_program" )
105119
120+ n , c , h , w = x_input_shape
121+ y_input_shape = (n , 8 , h , w )
122+
106123 # Run conversion
107124 _ = to_quantized_edge_program (
108- model , input_shape , use_qat = use_qat , use_neutron_for_format_conversion = False
125+ model ,
126+ [x_input_shape , y_input_shape ],
127+ use_qat = use_qat ,
128+ use_neutron_for_format_conversion = False ,
109129 )
110130
111131 # Capture generated model
@@ -114,7 +134,13 @@ def test_add_tensor_w_conv_quant_conversion(mocker, input_shape, use_qat):
114134 # Capture converted program
115135 exported_program : ExportedProgram = converter_spy .call_args .args [1 ]
116136
117- input_data = (np .random .random (input_shape ).astype (np .float32 ) * 50 ).astype (np .int8 )
137+ input_data_1 = (np .random .random (x_input_shape ).astype (np .float32 ) * 50 ).astype (
138+ np .int8
139+ )
140+ input_data_2 = (np .random .random (y_input_shape ).astype (np .float32 ) * 50 ).astype (
141+ np .int8
142+ )
143+ input_data = {0 : input_data_1 , 1 : input_data_2 }
118144
119145 convert_run_compare (
120146 exported_program ,
@@ -149,7 +175,7 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
149175 nodes = list (edge_program .graph .nodes )
150176
151177 # Broadcast is not supported, node is not converted
152- assert nodes [6 ].target . __name__ == "aten.add.Tensor" # Add Tensor is not delegated.
178+ assert nodes [6 ].target == AddTensor # Add Tensor is not delegated.
153179
154180 # Capture converted program
155181 # exported_program: ExportedProgram = converter_spy.call_args.args[1]
@@ -159,3 +185,164 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
159185 # input_data = {0: x_input_data, 1: y_input_data}
160186 #
161187 # convert_run_compare(exported_program, tfl_model=tflite_flatbuffers_model, input_data=input_data)
188+
189+
190+ class TestAddTensorNewNeutronFlow :
191+ @pytest .mark .skip ("AIR-14602: incorrect results" )
192+ @pytest .mark .parametrize (
193+ "x_input_shape" ,
194+ [
195+ pytest .param ((1 ,), id = "1D." ),
196+ pytest .param ((6 , 8 ), id = "2D." ),
197+ pytest .param ((1 , 4 , 8 ), id = "3D." ),
198+ pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
199+ ],
200+ )
201+ def test__basic_nsys_inference (self , x_input_shape , mocker ):
202+ x_input_spec = ModelInputSpec (x_input_shape )
203+ model = AddTensorModule ()
204+ graph_verifier = DetailedGraphVerifier (
205+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
206+ )
207+
208+ lower_run_compare (
209+ model ,
210+ [x_input_spec , x_input_spec ],
211+ graph_verifier ,
212+ use_new_flow_neutron_c = True ,
213+ )
214+
215+ @pytest .mark .skip ("AIR-14602: incorrect results" )
216+ @pytest .mark .parametrize (
217+ "x_input_shape" ,
218+ [
219+ pytest .param ((6 , 8 ), id = "2D." ),
220+ pytest .param ((1 , 4 , 8 ), id = "3D." ),
221+ pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
222+ ],
223+ )
224+ def test__basic_nsys_inference_qat (self , x_input_shape , mocker ):
225+ x_input_spec = ModelInputSpec (x_input_shape )
226+ model = AddTensorModule ()
227+ comparator = NumericalStatsOutputComparator ()
228+ graph_verifier = DetailedGraphVerifier (
229+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
230+ )
231+
232+ lower_run_compare (
233+ model ,
234+ [x_input_spec , x_input_spec ],
235+ graph_verifier ,
236+ output_comparator = comparator ,
237+ use_new_flow_neutron_c = True ,
238+ use_qat = True ,
239+ )
240+
241+ @pytest .mark .skip ("AIR-14602: incorrect results" )
242+ @pytest .mark .parametrize (
243+ "input_spec" ,
244+ [
245+ pytest .param (
246+ [ModelInputSpec ((4 , 6 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
247+ ),
248+ pytest .param (
249+ [ModelInputSpec ((5 , 3 , 4 )), ModelInputSpec ((1 , 3 , 1 ))],
250+ id = "2 inputs 3D." ,
251+ ),
252+ pytest .param (
253+ [ModelInputSpec ((4 ,)), ModelInputSpec ((4 , 4 ))], id = "2 inputs 2D+3D."
254+ ),
255+ ],
256+ )
257+ def test__correct_broadcast (self , input_spec , mocker ):
258+ model = AddTensorModule ()
259+ graph_verifier = DetailedGraphVerifier (
260+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
261+ )
262+
263+ lower_run_compare (
264+ model , input_spec , graph_verifier , use_new_flow_neutron_c = True
265+ )
266+
267+ @pytest .mark .parametrize (
268+ "input_spec" ,
269+ [
270+ pytest .param (
271+ [ModelInputSpec ((4 , 1 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
272+ ),
273+ pytest .param (
274+ [ModelInputSpec ((1 , 3 , 4 )), ModelInputSpec ((5 , 3 , 1 ))],
275+ id = "2 inputs 3D." ,
276+ ),
277+ pytest .param (
278+ [ModelInputSpec ((6 , 4 )), ModelInputSpec ((6 , 6 , 1 ))],
279+ id = "2 inputs 2D+3D." ,
280+ ),
281+ ],
282+ )
283+ def test__incorrect_broadcast (self , input_spec ):
284+ # Broadcast where at least one of the inputs is not equal to output is not supported
285+ model = AddTensorModule ()
286+
287+ delegated_ep = to_quantized_edge_program (
288+ model , input_spec , use_new_flow_neutron_c = True
289+ ).exported_program ()
290+
291+ # Make sure the `add.Tensor` was NOT delegated.
292+ assert not graph_contains_any_of_ops (
293+ delegated_ep .graph , [ExecutorchDelegateCall ]
294+ )
295+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
296+
297+ @pytest .mark .skip ("AIR-14602: incorrect results" )
298+ @pytest .mark .parametrize (
299+ "x_input_shape" ,
300+ [
301+ pytest .param (
302+ (1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8."
303+ ),
304+ ],
305+ )
306+ def test__w_conv (self , x_input_shape , mocker ):
307+ model = AddTensorConvModule ()
308+
309+ n , c , h , w = x_input_shape
310+ y_input_spec = ModelInputSpec ((n , 8 , h , w ))
311+ x_input_spec = ModelInputSpec (x_input_shape )
312+
313+ graph_verifier = DetailedGraphVerifier (
314+ mocker ,
315+ expected_delegated_ops = {AddTensor : 1 , Convolution : 1 },
316+ expected_non_delegated_ops = {},
317+ )
318+
319+ lower_run_compare (
320+ model ,
321+ [x_input_spec , y_input_spec ],
322+ graph_verifier ,
323+ use_new_flow_neutron_c = True ,
324+ )
325+
326+ @pytest .mark .parametrize (
327+ "input_spec" ,
328+ [
329+ pytest .param (
330+ [ModelInputSpec ((1 , 4 , 5 , 5 )), ModelInputSpec ((1 , 5 ))],
331+ id = "2 inputs 4D + 2D." ,
332+ ),
333+ pytest .param (
334+ [ModelInputSpec ((1 , 4 , 4 , 10 )), ModelInputSpec ((1 , 4 , 1 ))],
335+ id = "2 inputs last + 3D." ,
336+ ),
337+ ],
338+ )
339+ def test__w_conv_unsupported (self , input_spec ):
340+ model = AddTensorConvModule ()
341+
342+ delegated_ep = to_quantized_edge_program (
343+ model , input_spec , use_new_flow_neutron_c = True
344+ ).exported_program ()
345+
346+ # Make sure the `add.Tensor` was NOT delegated.
347+ assert graph_contains_any_of_ops (delegated_ep .graph , [ExecutorchDelegateCall ])
348+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
0 commit comments