66
77import copy
88
9- from typing import cast , Optional , Set , Type
9+ from typing import cast , ClassVar , Optional , Set , Type
1010
1111import torch
1212from executorch .backends .arm ._passes import ArmPass
@@ -127,12 +127,36 @@ class FoldAndAnnotateQParamsPass(ArmPass):
127127 InsertTableOpsPass ,
128128 RemoveNoopPass ,
129129 }
130+ _default_partial_binary_qdq_targets : ClassVar [tuple [object , ...]] = (
131+ exir_ops .edge .aten .add .Tensor ,
132+ exir_ops .edge .aten .sub .Tensor ,
133+ )
134+ _mixed_profile_partial_binary_qdq_targets : ClassVar [tuple [object , ...]] = (
135+ * _default_partial_binary_qdq_targets ,
136+ exir_ops .edge .aten .mul .Tensor ,
137+ exir_ops .edge .aten .div .Tensor ,
138+ exir_ops .edge .aten .minimum .default ,
139+ exir_ops .edge .aten .maximum .default ,
140+ exir_ops .edge .aten .mm .default ,
141+ exir_ops .edge .aten .bmm .default ,
142+ exir_ops .edge .aten .eq .Tensor ,
143+ exir_ops .edge .aten .ge .Tensor ,
144+ exir_ops .edge .aten .gt .Tensor ,
145+ exir_ops .edge .aten .le .Tensor ,
146+ exir_ops .edge .aten .lt .Tensor ,
147+ exir_ops .edge .aten .grid_sampler_2d .default ,
148+ )
130149
131150 def __init__ (
132- self , exported_program : Optional [ExportedProgram ] = None , * args , ** kwargs
151+ self ,
152+ exported_program : Optional [ExportedProgram ] = None ,
153+ * args ,
154+ preserve_partial_binary_tensor_qdq : bool = False ,
155+ ** kwargs ,
133156 ) -> None :
134157 super ().__init__ (* args , ** kwargs )
135158 self .exported_program = exported_program
159+ self .preserve_partial_binary_tensor_qdq = preserve_partial_binary_tensor_qdq
136160
137161 def _extract_input_params (
138162 self , arg_list : list [Node ]
@@ -177,9 +201,13 @@ def _annotate_input_params(
177201 index : int ,
178202 input_qparams : QuantArgs ,
179203 nodes_to_remove : set [Node ],
204+ remove_qdq : bool = True ,
180205 ) -> None :
181206 node .meta ["input_qparams" ][index ] = input_qparams
182207
208+ if not remove_qdq :
209+ return
210+
183211 for dq in nodes_to_remove :
184212 if dq .target not in DQ_OPS :
185213 raise RuntimeError (f"Expected one of { DQ_OPS } dq_op, got { dq .target } " )
@@ -201,6 +229,35 @@ def fold_and_annotate_arg(
201229 graph_module , node , i , input_qparams , nodes_to_remove
202230 )
203231
232+ def _extract_arg_input_params (
233+ self , arg : object
234+ ) -> tuple [Optional [QuantArgs ], set [Node ]]:
235+ if isinstance (arg , (list , tuple )):
236+ return self ._extract_input_params (list (arg ))
237+ if isinstance (arg , Node ):
238+ return self ._extract_input_params ([arg ])
239+ return None , set ()
240+
241+ def _has_partial_binary_tensor_qdq_inputs (
242+ self , node : Node , input_qparams : dict [int , QuantArgs ]
243+ ) -> bool :
244+ targets = (
245+ self ._mixed_profile_partial_binary_qdq_targets
246+ if self .preserve_partial_binary_tensor_qdq
247+ else self ._default_partial_binary_qdq_targets
248+ )
249+ if node .target not in targets :
250+ return False
251+
252+ lhs_idx , rhs_idx = 0 , 1
253+ if lhs_idx >= len (node .args ) or rhs_idx >= len (node .args ):
254+ return False
255+ if not isinstance (node .args [lhs_idx ], Node ) or not isinstance (
256+ node .args [rhs_idx ], Node
257+ ):
258+ return False
259+ return (lhs_idx in input_qparams ) != (rhs_idx in input_qparams )
260+
204261 def _handle_control_flow_node (self , node : Node , graph_module : GraphModule ):
205262 """Fold outmost quant nodes inside submodule.
206263
@@ -327,12 +384,12 @@ def _correct_output_dtype(node: torch.fx.Node):
327384 def call (self , graph_module : GraphModule ) -> PassResult : # noqa: C901
328385
329386 # Loop over the graph nodes and find any node in the 'targeted_ops' list.
330- modified = False
387+ graph_modified = False
388+ metadata_modified = False
331389 for n in graph_module .graph .nodes :
332390 n = cast (Node , n )
333391 if not FoldAndAnnotateQParamsPass .is_foldable (n ):
334392 continue
335- modified = True
336393
337394 # Make sure we haven't already set qparams meta information on the node
338395 if "input_qparams" in n .meta :
@@ -346,17 +403,32 @@ def call(self, graph_module: GraphModule) -> PassResult: # noqa: C901
346403 "output_qparams should not have been set at this point"
347404 )
348405
406+ input_qparams : dict [int , QuantArgs ] = {}
407+ input_nodes_to_remove : dict [int , set [Node ]] = {}
408+ for i , arg in enumerate (n .args ):
409+ qparams , nodes_to_remove = self ._extract_arg_input_params (arg )
410+ if qparams is not None :
411+ input_qparams [i ] = qparams
412+ input_nodes_to_remove [i ] = nodes_to_remove
413+
414+ preserve_qdq = self ._has_partial_binary_tensor_qdq_inputs (n , input_qparams )
415+ graph_modified = graph_modified or not preserve_qdq
416+ metadata_modified = True
417+
349418 # for the inputs and outputs search the graph for quantization info and
350419 # store the information in a dict with order of the _tensor_ inputs as key,
351420 # ignoring any other arguments to the target node.
352421 n .meta ["input_qparams" ] = {}
353422 n .meta ["output_qparams" ] = {}
354- for i , arg in enumerate (n .args ):
355- if isinstance (arg , (list , tuple )):
356- self .fold_and_annotate_arg (graph_module , n , arg , i ) # type: ignore
357-
358- elif isinstance (arg , Node ):
359- self .fold_and_annotate_arg (graph_module , n , [arg ], i )
423+ for i , qparams in input_qparams .items ():
424+ self ._annotate_input_params (
425+ graph_module ,
426+ n ,
427+ i ,
428+ qparams ,
429+ input_nodes_to_remove [i ],
430+ remove_qdq = not preserve_qdq ,
431+ )
360432
361433 # Copy the users, since we are modifying it.
362434 users_copy = copy .copy (n .users )
@@ -369,8 +441,9 @@ def call(self, graph_module: GraphModule) -> PassResult: # noqa: C901
369441 user .target , user .args
370442 )
371443
372- user .replace_all_uses_with (n )
373- graph_module .graph .erase_node (user )
444+ if not preserve_qdq :
445+ user .replace_all_uses_with (n )
446+ graph_module .graph .erase_node (user )
374447
375448 # Some op(s) contain a "dtype" key in their node kwargs. Set this
376449 # to the type of output qparams.
@@ -383,10 +456,10 @@ def call(self, graph_module: GraphModule) -> PassResult: # noqa: C901
383456 self ._handle_control_flow_node (n , graph_module )
384457
385458 # retrace the graph to update the fake tensor types
386- if modified :
459+ if graph_modified :
387460 graph_module = super ().call (graph_module ).graph_module
388461
389- return PassResult (graph_module , modified )
462+ return PassResult (graph_module , metadata_modified or graph_modified )
390463
391464
392465class QuantizeClampArgumentsPass (ArmPass ):
0 commit comments