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 .dataset_creator import RandomDatasetCreator
14+ from executorch .backends .nxp .tests .executorch_pipeline import (
15+ ModelInputSpec ,
16+ to_quantized_edge_program ,
17+ )
1318from executorch .backends .nxp .tests .executors import (
1419 convert_run_compare ,
20+ graph_contains_any_of_ops ,
1521 ToChannelFirstPreprocess ,
1622 ToChannelLastPreprocess ,
1723)
24+ from executorch .backends .nxp .tests .graph_verifier import DetailedGraphVerifier
1825from executorch .backends .nxp .tests .models import (
1926 AddTensorConvModule ,
2027 AddTensorModule ,
2128 AddTensorOneInputModule ,
2229)
30+ from executorch .backends .nxp .tests .nsys_testing import lower_run_compare
31+ from executorch .backends .nxp .tests .ops_aliases import (
32+ AddTensor ,
33+ Convolution ,
34+ ExecutorchDelegateCall ,
35+ )
2336from torch .export import ExportedProgram
2437from executorch .backends .nxp .tests .use_qat import * # noqa F403
2538
@@ -92,20 +105,26 @@ def test_add_tensor_one_input_quant_conversion(mocker, input_shape, use_qat):
92105
93106
94107@pytest .mark .parametrize (
95- "input_shape " ,
108+ "x_input_shape " ,
96109 [
97110 pytest .param ((1 , 4 , 8 , 8 ), id = "4D." ),
98111 pytest .param ((1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8." ),
99112 ],
100113)
101- def test_add_tensor_w_conv_quant_conversion (mocker , input_shape , use_qat ):
114+ def test_add_tensor_w_conv_quant_conversion (mocker , x_input_shape , use_qat ):
102115 model = AddTensorConvModule ()
103116
104117 converter_spy = mocker .spy (EdgeProgramToIRConverter , "convert_program" )
105118
119+ n , c , h , w = x_input_shape
120+ y_input_shape = (n , 8 , h , w )
121+
106122 # Run conversion
107123 _ = to_quantized_edge_program (
108- model , input_shape , use_qat = use_qat , use_neutron_for_format_conversion = False
124+ model ,
125+ [x_input_shape , y_input_shape ],
126+ use_qat = use_qat ,
127+ use_neutron_for_format_conversion = False ,
109128 )
110129
111130 # Capture generated model
@@ -114,7 +133,13 @@ def test_add_tensor_w_conv_quant_conversion(mocker, input_shape, use_qat):
114133 # Capture converted program
115134 exported_program : ExportedProgram = converter_spy .call_args .args [1 ]
116135
117- input_data = (np .random .random (input_shape ).astype (np .float32 ) * 50 ).astype (np .int8 )
136+ input_data_1 = (np .random .random (x_input_shape ).astype (np .float32 ) * 50 ).astype (
137+ np .int8
138+ )
139+ input_data_2 = (np .random .random (y_input_shape ).astype (np .float32 ) * 50 ).astype (
140+ np .int8
141+ )
142+ input_data = {0 : input_data_1 , 1 : input_data_2 }
118143
119144 convert_run_compare (
120145 exported_program ,
@@ -149,7 +174,7 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
149174 nodes = list (edge_program .graph .nodes )
150175
151176 # Broadcast is not supported, node is not converted
152- assert nodes [6 ].target . __name__ == "aten.add.Tensor" # Add Tensor is not delegated.
177+ assert nodes [6 ].target == AddTensor # Add Tensor is not delegated.
153178
154179 # Capture converted program
155180 # exported_program: ExportedProgram = converter_spy.call_args.args[1]
@@ -159,3 +184,227 @@ def test_add_tensor_broadcasting_unsupported_quant_conversion(
159184 # input_data = {0: x_input_data, 1: y_input_data}
160185 #
161186 # convert_run_compare(exported_program, tfl_model=tflite_flatbuffers_model, input_data=input_data)
187+
188+
189+ class TestAddTensorNewNeutronFlow :
190+ @pytest .mark .parametrize (
191+ "x_input_shape" ,
192+ [
193+ pytest .param ((1 ,), id = "1D." ),
194+ pytest .param ((6 , 5 ), id = "2D." ),
195+ pytest .param ((1 , 4 , 7 ), id = "3D." ),
196+ pytest .param ((2 , 4 , 3 , 15 ), id = "4D." ),
197+ pytest .param (
198+ (6 , 82 ),
199+ id = "2D incorrect." ,
200+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
201+ ),
202+ pytest .param (
203+ (1 , 68 , 7 ),
204+ id = "3D incorrect." ,
205+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
206+ ),
207+ pytest .param (
208+ (1 , 4 , 9 , 11 , 4 ),
209+ id = "5D incorrect." ,
210+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
211+ ),
212+ ],
213+ )
214+ def test__basic_nsys_inference (self , x_input_shape , mocker ):
215+ x_input_spec = ModelInputSpec (x_input_shape )
216+ model = AddTensorModule ()
217+ graph_verifier = DetailedGraphVerifier (
218+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
219+ )
220+ dataset_creator = RandomDatasetCreator (low = - 1.0 , high = 1.0 )
221+
222+ lower_run_compare (
223+ model ,
224+ [x_input_spec , x_input_spec ],
225+ graph_verifier ,
226+ dataset_creator ,
227+ use_new_flow_neutron_c = True ,
228+ )
229+
230+ @pytest .mark .parametrize (
231+ "x_input_shape" ,
232+ [
233+ pytest .param ((1 ,), id = "1D." ),
234+ pytest .param ((6 , 5 ), id = "2D." ),
235+ pytest .param ((1 , 4 , 7 ), id = "3D." ),
236+ pytest .param ((2 , 4 , 3 , 15 ), id = "4D." ),
237+ pytest .param (
238+ (1 , 4 , 9 , 11 , 4 ),
239+ id = "5D." ,
240+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
241+ ),
242+ ],
243+ )
244+ def test__basic_nsys_inference_qat (self , x_input_shape , mocker ):
245+ x_input_spec = ModelInputSpec (x_input_shape )
246+ model = AddTensorModule ()
247+ graph_verifier = DetailedGraphVerifier (
248+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
249+ )
250+ dataset_creator = RandomDatasetCreator (low = - 1.0 , high = 1.0 )
251+
252+ lower_run_compare (
253+ model ,
254+ [x_input_spec , x_input_spec ],
255+ graph_verifier ,
256+ dataset_creator ,
257+ use_new_flow_neutron_c = True ,
258+ use_qat = True ,
259+ )
260+
261+ @pytest .mark .parametrize (
262+ "input_spec" ,
263+ [
264+ pytest .param (
265+ [ModelInputSpec ((4 , 6 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
266+ ),
267+ pytest .param (
268+ [ModelInputSpec ((5 , 3 , 4 )), ModelInputSpec ((1 , 3 , 1 ))],
269+ id = "2 inputs 3D." ,
270+ ),
271+ pytest .param (
272+ [ModelInputSpec ((4 ,)), ModelInputSpec ((4 , 4 ))], id = "2 inputs 1D + 2D."
273+ ),
274+ pytest .param (
275+ [ModelInputSpec ((69 , 73 )), ModelInputSpec ((1 , 73 ))],
276+ id = "2 inputs 2D incorrect." ,
277+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
278+ ),
279+ ],
280+ )
281+ def test__broadcast (self , input_spec , mocker ):
282+ model = AddTensorModule ()
283+ graph_verifier = DetailedGraphVerifier (
284+ mocker , expected_delegated_ops = {AddTensor : 1 }, expected_non_delegated_ops = {}
285+ )
286+ dataset_creator = RandomDatasetCreator (low = - 1.0 , high = 1.0 )
287+
288+ lower_run_compare (
289+ model ,
290+ input_spec ,
291+ graph_verifier ,
292+ dataset_creator ,
293+ use_new_flow_neutron_c = True ,
294+ )
295+
296+ @pytest .mark .parametrize (
297+ "input_spec" ,
298+ [
299+ pytest .param (
300+ [ModelInputSpec ((4 , 1 )), ModelInputSpec ((1 , 6 ))], id = "2 inputs 2D."
301+ ),
302+ pytest .param (
303+ [ModelInputSpec ((1 , 3 , 4 )), ModelInputSpec ((5 , 3 , 1 ))],
304+ id = "2 inputs 3D." ,
305+ ),
306+ pytest .param (
307+ [ModelInputSpec ((6 , 4 )), ModelInputSpec ((6 , 6 , 1 ))],
308+ id = "2 inputs 2D + 3D." ,
309+ ),
310+ ],
311+ )
312+ def test__broadcast_unsupported (self , input_spec ):
313+ # Broadcast where at least one of the inputs is not equal to output is not supported
314+ model = AddTensorModule ()
315+
316+ delegated_ep = to_quantized_edge_program (
317+ model , input_spec , use_new_flow_neutron_c = True
318+ ).exported_program ()
319+
320+ # Make sure the `add.Tensor` was NOT delegated.
321+ assert not graph_contains_any_of_ops (
322+ delegated_ep .graph , [ExecutorchDelegateCall ]
323+ )
324+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
325+
326+ @pytest .mark .parametrize (
327+ "x_input_shape" ,
328+ [
329+ pytest .param (
330+ (1 , 4 , 5 , 5 ), id = "4D, product of dims is not a multiple of 8."
331+ ),
332+ ],
333+ )
334+ def test__w_conv (self , x_input_shape , mocker ):
335+ model = AddTensorConvModule ()
336+
337+ n , c , h , w = x_input_shape
338+ y_input_spec = ModelInputSpec ((n , 8 , h , w ))
339+ x_input_spec = ModelInputSpec (x_input_shape )
340+
341+ graph_verifier = DetailedGraphVerifier (
342+ mocker ,
343+ expected_delegated_ops = {AddTensor : 1 , Convolution : 1 },
344+ expected_non_delegated_ops = {},
345+ )
346+ dataset_creator = RandomDatasetCreator (low = - 1.0 , high = 1.0 )
347+
348+ lower_run_compare (
349+ model ,
350+ [x_input_spec , y_input_spec ],
351+ graph_verifier ,
352+ dataset_creator ,
353+ use_new_flow_neutron_c = True ,
354+ )
355+
356+ @pytest .mark .parametrize (
357+ "input_spec" ,
358+ [
359+ pytest .param (
360+ [ModelInputSpec ((1 , 4 , 5 , 5 )), ModelInputSpec ((1 , 8 , 5 , 1 ))],
361+ id = "2 inputs 4D + 4D." ,
362+ ),
363+ pytest .param (
364+ [ModelInputSpec ((1 , 4 , 5 , 67 )), ModelInputSpec ((1 , 8 , 5 , 1 ))],
365+ id = "2 inputs 4D + 4D incorrect." ,
366+ marks = pytest .mark .xfail (reason = "AIR-14602: incorrect results" ),
367+ ),
368+ ],
369+ )
370+ def test__w_conv_broadcast (self , input_spec , mocker ):
371+ model = AddTensorConvModule ()
372+
373+ graph_verifier = DetailedGraphVerifier (
374+ mocker ,
375+ expected_delegated_ops = {AddTensor : 1 , Convolution : 1 },
376+ expected_non_delegated_ops = {},
377+ )
378+ dataset_creator = RandomDatasetCreator (low = - 1.0 , high = 1.0 )
379+
380+ lower_run_compare (
381+ model ,
382+ input_spec ,
383+ graph_verifier ,
384+ dataset_creator ,
385+ use_new_flow_neutron_c = True ,
386+ )
387+
388+ @pytest .mark .parametrize (
389+ "input_spec" ,
390+ [
391+ pytest .param (
392+ [ModelInputSpec ((1 , 4 , 5 , 5 )), ModelInputSpec ((1 , 5 ))],
393+ id = "2 inputs 4D + 2D." ,
394+ ),
395+ pytest .param (
396+ [ModelInputSpec ((1 , 4 , 4 , 10 )), ModelInputSpec ((1 , 4 , 1 ))],
397+ id = "2 inputs 4D + 3D." ,
398+ ),
399+ ],
400+ )
401+ def test__w_conv_unsupported (self , input_spec ):
402+ model = AddTensorConvModule ()
403+
404+ delegated_ep = to_quantized_edge_program (
405+ model , input_spec , use_new_flow_neutron_c = True
406+ ).exported_program ()
407+
408+ # Make sure the `add.Tensor` was NOT delegated.
409+ assert graph_contains_any_of_ops (delegated_ep .graph , [ExecutorchDelegateCall ])
410+ assert graph_contains_any_of_ops (delegated_ep .graph , [AddTensor ])
0 commit comments