1+ # Copyright 2025 NXP
2+ #
3+ # This source code is licensed under the BSD-style license found in the
4+ # LICENSE file in the root directory of this source tree.
5+
6+ import copy
7+ import unittest
8+
19import numpy as np
10+ import torch
11+
12+ from executorch .backends .nxp .aten_passes .neutron_aten_pass_manager import (
13+ NeutronAtenPassManager ,
14+ )
15+ from executorch .backends .nxp .backend .custom_delegation_options import (
16+ CustomDelegationOptions ,
17+ )
218from executorch .backends .nxp .backend .ir .converter .node_converters .ops_converters import (
319 ViewCopyConverter ,
420)
5- from executorch .backends .nxp .tests .executorch_pipeline import to_quantized_edge_program
21+ from executorch .backends .nxp .edge_passes .neutron_edge_pass_manager import (
22+ NeutronEdgePassManager ,
23+ )
24+ from executorch .backends .nxp .edge_passes .remove_additional_quantize_dequantize_nodes_pass import (
25+ RemoveAdditionalQDQClustersPass ,
26+ )
27+ from executorch .backends .nxp .neutron_partitioner import NeutronPartitioner
28+ from executorch .backends .nxp .nxp_backend import generate_neutron_compile_spec
29+ from executorch .backends .nxp .quantizer .neutron_quantizer import NeutronQuantizer
30+ from executorch .backends .nxp .tests .executorch_pipeline import (
31+ _quantize_model ,
32+ get_random_calibration_inputs ,
33+ to_model_input_spec ,
34+ to_quantized_edge_program ,
35+ )
636from executorch .backends .nxp .tests .executors import (
37+ compare_output_arrays ,
738 EdgeProgramExecutor ,
839 OverrideTargetSupportCheck ,
940)
41+ from executorch .backends .nxp .tests .ir .converter .node_converter .test_permute_copy_converter import (
42+ Conv2dPermuteModule ,
43+ )
1044from executorch .backends .nxp .tests .models import ConvFCFCSoftmaxModuleWithoutReshape
1145from executorch .exir .dialects ._ops import ops as exir_ops
46+ from executorch .extension .export_util .utils import export_to_edge
1247from torch .fx import Graph , Node
1348
1449
@@ -57,18 +92,26 @@ def _assert_nodes_form_a_view_copy_qdq_cluster(graph: Graph, node_indices: list[
5792 assert quantize .args [0 ] == view_copy
5893
5994
60- def test_moving_view_copy_into_separate_qdq_clusters ():
61- model = ConvFCFCSoftmaxModuleWithoutReshape ()
62- input_shape = (1 , 4 , 3 , 33 )
95+ class TestEdgePasses (unittest .TestCase ):
96+ @classmethod
97+ def setUpClass (cls ):
98+ torch .manual_seed (23 )
99+ np .random .seed (42 )
63100
64- # Prohibit `view_copy` conversion for the testing purposes.
65- def unsupported_target (* _ ):
66- return False
101+ def test_moving_view_copy_into_separate_qdq_clusters (self ):
102+ model = ConvFCFCSoftmaxModuleWithoutReshape ()
103+ input_shape = (1 , 4 , 3 , 33 )
104+
105+ # Prohibit `view_copy` conversion for the testing purposes.
106+ def unsupported_target (* _ ):
107+ return False
108+
109+ # Prohibit `view_copy` conversion for the testing purposes.
110+ with OverrideTargetSupportCheck (
111+ ViewCopyConverter , new_target_support_check = unsupported_target
112+ ):
113+ epm = to_quantized_edge_program (model , input_shape , target = "imxrt700" )
67114
68- with OverrideTargetSupportCheck (
69- ViewCopyConverter , new_target_support_check = unsupported_target
70- ):
71- epm = to_quantized_edge_program (model , input_shape , target = "imxrt700" )
72115 exported_program = epm .exported_program ()
73116
74117 nodes = list (exported_program .graph_module .graph .nodes )
@@ -86,3 +129,90 @@ def unsupported_target(*_):
86129 input_data = np .random .random (input_shape ).astype ("float32" )
87130 program_executor = EdgeProgramExecutor (exported_program )
88131 program_executor .inference (input_data )
132+
133+ def test_remove_additional_quantize_dequantize_nodes_pass (self ):
134+ input_shape = (1 , 3 , 8 , 16 )
135+ new_dims = (3 , 2 , 1 , 0 )
136+ model = Conv2dPermuteModule (input_shape [1 ], new_dims )
137+ target = "imxrt700"
138+ custom_delegation_options = CustomDelegationOptions ()
139+
140+ calibration_inputs = get_random_calibration_inputs (
141+ to_model_input_spec (input_shape )
142+ )
143+
144+ example_input = calibration_inputs [0 ]
145+ exir_program_aten = torch .export .export (model , example_input ).module ()
146+
147+ # Run pre-processing passes of the float32 aten dialect program.
148+ exir_program_aten = NeutronAtenPassManager ()(exir_program_aten ).graph_module
149+
150+ exir_program_aten_quant = _quantize_model (
151+ exir_program_aten , NeutronQuantizer (), calibration_inputs
152+ )
153+ edge_program_manager = export_to_edge (
154+ exir_program_aten_quant ,
155+ example_input ,
156+ )
157+
158+ edge_program_manager = NeutronEdgePassManager ()(edge_program_manager )
159+
160+ compile_spec = generate_neutron_compile_spec (target , "SDK_25_09" )
161+ partitioner = NeutronPartitioner (compile_spec , custom_delegation_options )
162+
163+ edge_program_manager = edge_program_manager .to_backend (partitioner )
164+
165+ # Make sure QDQ cluster for permute_copy is present.
166+ edge_program_with_qdq_cluster = copy .deepcopy (
167+ edge_program_manager .exported_program ()
168+ )
169+ nodes = list (edge_program_with_qdq_cluster .graph .nodes )
170+ assert len (nodes ) == 10
171+ assert (
172+ nodes [5 ].target
173+ == exir_ops .edge .quantized_decomposed .dequantize_per_tensor .default
174+ )
175+ assert nodes [6 ].target == exir_ops .edge .aten .permute_copy .default
176+ assert "cluster" in nodes [6 ].meta
177+ assert (
178+ nodes [7 ].target
179+ == exir_ops .edge .quantized_decomposed .quantize_per_tensor .default
180+ )
181+
182+ # Run pass for removal of additional QDQ nodes and compute in non-float types where possible
183+ edge_program_manager = NeutronEdgePassManager (
184+ [RemoveAdditionalQDQClustersPass ()]
185+ )(edge_program_manager )
186+
187+ # Make sure QDQ cluster for permute_copy is removed.
188+ edge_program_without_qdq_cluster = edge_program_manager .exported_program ()
189+ nodes = list (edge_program_without_qdq_cluster .graph .nodes )
190+ assert len (nodes ) == 8
191+ assert nodes [4 ].name == "getitem"
192+ assert nodes [5 ].target == exir_ops .edge .aten .permute_copy .default
193+ assert "cluster" not in nodes [5 ].meta
194+ assert (
195+ nodes [6 ].target
196+ == exir_ops .edge .quantized_decomposed .dequantize_per_tensor .default
197+ )
198+
199+ edge_program_executor_without_qdq_cluster = EdgeProgramExecutor (
200+ edge_program_without_qdq_cluster
201+ )
202+ edge_program_executor_with_qdq_cluster = EdgeProgramExecutor (
203+ edge_program_with_qdq_cluster
204+ )
205+
206+ input_data = np .random .random (input_shape ).astype (np .float32 )
207+ edge_program_output_without_qdq_cluster = (
208+ edge_program_executor_without_qdq_cluster .inference (input_data )
209+ )
210+ edge_program_output_with_qdq_cluster = (
211+ edge_program_executor_with_qdq_cluster .inference (input_data )
212+ )
213+
214+ compare_output_arrays (
215+ edge_program_output_without_qdq_cluster ,
216+ edge_program_output_with_qdq_cluster ,
217+ "main output" ,
218+ )
0 commit comments