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 BaseGraphVerifier
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 AddTensor , ExecutorchDelegateCall
2334from torch .export import ExportedProgram
2435from executorch .backends .nxp .tests .use_qat import * # noqa F403
2536
@@ -64,7 +75,6 @@ def test_add_tensor_quant_conversion(mocker, input_shape, use_qat):
6475@pytest .mark .parametrize (
6576 "input_shape" ,
6677 [
67- pytest .param ((4 ,), id = "1D." ),
6878 pytest .param ((6 , 6 ), id = "2D." ),
6979 pytest .param ((1 , 4 , 8 ), id = "3D." ),
7080 pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
@@ -92,20 +102,26 @@ def test_add_tensor_one_input_quant_conversion(mocker, input_shape, use_qat):
92102
93103
94104@pytest .mark .parametrize (
95- "input_shape " ,
105+ "x_input_shape " ,
96106 [
97107 pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
98108 pytest .param ((1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8." ),
99109 ],
100110)
101- def test_add_tensor_w_conv_quant_conversion (mocker , input_shape , use_qat ):
111+ def test_add_tensor_w_conv_quant_conversion (mocker , x_input_shape , use_qat ):
102112 model = AddTensorConvModule ()
103113
104114 converter_spy = mocker .spy (EdgeProgramToIRConverter , "convert_program" )
105115
116+ n , c , h , w = x_input_shape
117+ y_input_shape = (n , 8 , h , w )
118+
106119 # Run conversion
107120 _ = to_quantized_edge_program (
108- model , input_shape , use_qat = use_qat , use_neutron_for_format_conversion = False
121+ model ,
122+ [x_input_shape , y_input_shape ],
123+ use_qat = use_qat ,
124+ use_neutron_for_format_conversion = False ,
109125 )
110126
111127 # Capture generated model
@@ -114,7 +130,13 @@ def test_add_tensor_w_conv_quant_conversion(mocker, input_shape, use_qat):
114130 # Capture converted program
115131 exported_program : ExportedProgram = converter_spy .call_args .args [1 ]
116132
117- input_data = (np .random .random (input_shape ).astype (np .float32 ) * 50 ).astype (np .int8 )
133+ input_data_1 = (np .random .random (x_input_shape ).astype (np .float32 ) * 50 ).astype (
134+ np .int8
135+ )
136+ input_data_2 = (np .random .random (y_input_shape ).astype (np .float32 ) * 50 ).astype (
137+ np .int8
138+ )
139+ input_data = {0 : input_data_1 , 1 : input_data_2 }
118140
119141 convert_run_compare (
120142 exported_program ,
@@ -149,7 +171,7 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
149171 nodes = list (edge_program .graph .nodes )
150172
151173 # Broadcast is not supported, node is not converted
152- assert nodes [6 ].target . __name__ == "aten.add.Tensor" # Add Tensor is not delegated.
174+ assert nodes [6 ].target == AddTensor # Add Tensor is not delegated.
153175
154176 # Capture converted program
155177 # exported_program: ExportedProgram = converter_spy.call_args.args[1]
@@ -159,3 +181,166 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
159181 # input_data = {0: x_input_data, 1: y_input_data}
160182 #
161183 # convert_run_compare(exported_program, tfl_model=tflite_flatbuffers_model, input_data=input_data)
184+
185+
186+ class TestAddTensorNewNeutronFlow :
187+ @pytest .mark .skip ("AIR-14602: incorrect results" )
188+ @pytest .mark .parametrize (
189+ "x_input_shape" ,
190+ [
191+ pytest .param ((1 ,), id = "1D." ),
192+ pytest .param ((6 , 8 ), id = "2D." ),
193+ pytest .param ((1 , 4 , 8 ), id = "3D." ),
194+ pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
195+ ],
196+ )
197+ def test__basic_nsys_inference (self , x_input_shape ):
198+ x_input_spec = ModelInputSpec (x_input_shape )
199+ model = AddTensorModule ()
200+ graph_verifier = BaseGraphVerifier (
201+ exp_num_delegate_call_nodes = 1 ,
202+ exp_non_delegated_nodes = [],
203+ )
204+
205+ lower_run_compare (
206+ model ,
207+ [x_input_spec , x_input_spec ],
208+ graph_verifier ,
209+ use_new_flow_neutron_c = True ,
210+ )
211+
212+ @pytest .mark .skip ("AIR-14602: incorrect results" )
213+ @pytest .mark .parametrize (
214+ "x_input_shape" ,
215+ [
216+ pytest .param ((6 , 8 ), id = "2D." ),
217+ pytest .param ((1 , 4 , 8 ), id = "3D." ),
218+ pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
219+ ],
220+ )
221+ def test__basic_nsys_inference_qat (self , x_input_shape ):
222+ x_input_spec = ModelInputSpec (x_input_shape )
223+ model = AddTensorModule ()
224+ comparator = NumericalStatsOutputComparator ()
225+ graph_verifier = BaseGraphVerifier (
226+ exp_num_delegate_call_nodes = 1 ,
227+ exp_non_delegated_nodes = [],
228+ )
229+
230+ lower_run_compare (
231+ model ,
232+ [x_input_spec , x_input_spec ],
233+ graph_verifier ,
234+ output_comparator = comparator ,
235+ use_new_flow_neutron_c = True ,
236+ use_qat = True ,
237+ )
238+
239+ @pytest .mark .skip ("AIR-14602: incorrect results" )
240+ @pytest .mark .parametrize (
241+ "input_spec" ,
242+ [
243+ pytest .param (
244+ [ModelInputSpec ((4 , 6 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
245+ ),
246+ pytest .param (
247+ [ModelInputSpec ((5 , 3 , 4 )), ModelInputSpec ((1 , 3 , 1 ))],
248+ id = "2 inputs 3D." ,
249+ ),
250+ pytest .param (
251+ [ModelInputSpec ((4 ,)), ModelInputSpec ((4 , 4 ))], id = "2 inputs 2D+3D."
252+ ),
253+ ],
254+ )
255+ def test__correct_broadcast (self , input_spec ):
256+ model = AddTensorModule ()
257+ graph_verifier = BaseGraphVerifier (
258+ exp_num_delegate_call_nodes = 1 ,
259+ exp_non_delegated_nodes = [],
260+ )
261+
262+ lower_run_compare (
263+ model , input_spec , graph_verifier , use_new_flow_neutron_c = True
264+ )
265+
266+ @pytest .mark .parametrize (
267+ "input_spec" ,
268+ [
269+ pytest .param (
270+ [ModelInputSpec ((4 , 1 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
271+ ),
272+ pytest .param (
273+ [ModelInputSpec ((1 , 3 , 4 )), ModelInputSpec ((5 , 3 , 1 ))],
274+ id = "2 inputs 3D." ,
275+ ),
276+ pytest .param (
277+ [ModelInputSpec ((6 , 4 )), ModelInputSpec ((6 , 6 , 1 ))],
278+ id = "2 inputs 2D+3D." ,
279+ ),
280+ ],
281+ )
282+ def test__incorrect_broadcast (self , input_spec ):
283+ # Broadcast where at least one of the inputs is not equal to output is not supported
284+ model = AddTensorModule ()
285+
286+ delegated_ep = to_quantized_edge_program (
287+ model , input_spec , use_new_flow_neutron_c = True
288+ ).exported_program ()
289+
290+ # Make sure the `add.Tensor` was NOT delegated.
291+ assert not graph_contains_any_of_ops (
292+ delegated_ep .graph , [ExecutorchDelegateCall ]
293+ )
294+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
295+
296+ @pytest .mark .skip ("AIR-14602: incorrect results" )
297+ @pytest .mark .parametrize (
298+ "x_input_shape" ,
299+ [
300+ pytest .param (
301+ (1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8."
302+ ),
303+ ],
304+ )
305+ def test__w_conv (self , x_input_shape ):
306+ model = AddTensorConvModule ()
307+
308+ n , c , h , w = x_input_shape
309+ y_input_spec = ModelInputSpec ((n , 8 , h , w ))
310+ x_input_spec = ModelInputSpec (x_input_shape )
311+
312+ graph_verifier = BaseGraphVerifier (
313+ exp_num_delegate_call_nodes = 1 ,
314+ exp_non_delegated_nodes = [],
315+ )
316+
317+ lower_run_compare (
318+ model ,
319+ [x_input_spec , y_input_spec ],
320+ graph_verifier ,
321+ use_new_flow_neutron_c = True ,
322+ )
323+
324+ @pytest .mark .parametrize (
325+ "input_spec" ,
326+ [
327+ pytest .param (
328+ [ModelInputSpec ((1 , 4 , 5 , 5 )), ModelInputSpec ((1 , 5 ))],
329+ id = "2 inputs 4D + 2D." ,
330+ ),
331+ pytest .param (
332+ [ModelInputSpec ((1 , 4 , 4 , 10 )), ModelInputSpec ((1 , 4 , 1 ))],
333+ id = "2 inputs last + 3D." ,
334+ ),
335+ ],
336+ )
337+ def test__w_conv_unsupported (self , input_spec ):
338+ model = AddTensorConvModule ()
339+
340+ delegated_ep = to_quantized_edge_program (
341+ model , input_spec , use_new_flow_neutron_c = True
342+ ).exported_program ()
343+
344+ # Make sure the `add.Tensor` was NOT delegated.
345+ assert graph_contains_any_of_ops (delegated_ep .graph , [ExecutorchDelegateCall ])
346+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
0 commit comments