33#
44# This source code is licensed under the BSD-style license found in the
55# LICENSE file in the root directory of this source tree.
6- from typing import Dict
76
87import torch
8+ from executorch .backends .qualcomm ._passes .utils import (
9+ insert_dequant_node ,
10+ insert_quant_node ,
11+ )
912
10- from executorch .backends .qualcomm .builders .node_visitor import q_ops
13+ from executorch .backends .qualcomm .builders .node_visitor import q_dq_map , q_ops
1114
1215from executorch .backends .qualcomm .builders .utils import (
1316 is_mutable_buffer_input ,
1821 QCOM_QUANT_ATTRS ,
1922 QCOM_QUANTIZED_IO ,
2023)
21- from executorch .exir .dialects ._ops import ops as exir_ops
2224from executorch .exir .pass_base import ExportPass , PassResult
2325from executorch .exir .passes import dead_code_elimination_pass
2426
@@ -31,98 +33,10 @@ class InsertIOQDQ(ExportPass):
3133 right before outputs according to stored quantization encodings.
3234 """
3335
34- q_dq_map = {
35- # per tensor (quantize -> dequantize)
36- exir_ops .edge .quantized_decomposed .quantize_per_tensor .default : exir_ops .edge .quantized_decomposed .dequantize_per_tensor .tensor ,
37- exir_ops .edge .quantized_decomposed .quantize_per_tensor .tensor : exir_ops .edge .quantized_decomposed .dequantize_per_tensor .tensor ,
38- # per tensor (dequantize -> dequantize, for pre-quantized params)
39- exir_ops .edge .quantized_decomposed .dequantize_per_tensor .default : exir_ops .edge .quantized_decomposed .dequantize_per_tensor .tensor ,
40- exir_ops .edge .quantized_decomposed .dequantize_per_tensor .tensor : exir_ops .edge .quantized_decomposed .dequantize_per_tensor .tensor ,
41- # per channel (quantize -> dequantize)
42- exir_ops .edge .quantized_decomposed .quantize_per_channel .default : exir_ops .edge .quantized_decomposed .dequantize_per_channel .default ,
43- # per channel (dequantize -> dequantize, for pre-quantized params)
44- exir_ops .edge .quantized_decomposed .dequantize_per_channel .default : exir_ops .edge .quantized_decomposed .dequantize_per_channel .default ,
45- }
46-
4736 def __init__ (self , edge_program : torch .export .ExportedProgram ):
4837 super (InsertIOQDQ , self ).__init__ ()
4938 self .edge_program = edge_program
5039
51- def _ceate_args (self , target : torch .fx .node .Target , quant_attrs : Dict ):
52- ret = []
53-
54- arg_schemas = list (target ._schema .arguments )[1 :]
55- for arg_schema in arg_schemas :
56- name = arg_schema .name
57- # TODO: Due to the new parameter "out_dtype" in the dequantize node,
58- # it could not be found in the quant_attrs of other nodes,
59- # and it will cause a key error. For now, the output type
60- # of our dequantize node is only float. (by default in pytorch)
61- if name == "out_dtype" :
62- continue
63- value = quant_attrs [name ]
64- if isinstance (arg_schema .type , torch .Tensor ) and (
65- isinstance (value , int ) or isinstance (value , float )
66- ):
67- value = torch .tensor (value )
68- ret .append (value )
69- return ret
70-
71- def _create_node (
72- self ,
73- graph_module : torch .fx .GraphModule ,
74- node : torch .fx .node ,
75- target : torch .fx .node .Target ,
76- quant_attrs : Dict = None ,
77- ) -> torch .fx .node :
78- # check if there has a specified quant_attrs
79- # if not, use the existent info. from current node
80- if quant_attrs is None :
81- quant_attrs = node .meta .get (QCOM_QUANT_ATTRS )
82-
83- inserted_node = graph_module .graph .create_node (
84- "call_function" ,
85- target ,
86- (node , * self ._ceate_args (target , quant_attrs )),
87- )
88- meta_val = node .meta ["val" ]
89- if target in q_ops :
90- inserted_node .meta [QCOM_QUANT_ATTRS ] = node .meta .pop (QCOM_QUANT_ATTRS )
91- meta_val = meta_val .to (quant_attrs ["dtype" ])
92-
93- inserted_node .meta ["val" ] = meta_val
94- return inserted_node
95-
96- def _insert_quant_node (
97- self ,
98- graph_module : torch .fx .GraphModule ,
99- node : torch .fx .node ,
100- target : torch .fx .node .Target ,
101- quant_attrs : Dict = None ,
102- ) -> torch .fx .Node :
103- with graph_module .graph .inserting_after (node ):
104- users = list (node .users .keys ())
105- inserted_node = self ._create_node (graph_module , node , target , quant_attrs )
106- for user in users :
107- # If we found mix quantization pattern and reuse the existing q_node, we skip adding a new q node.
108- if user .target not in q_ops :
109- user .replace_input_with (node , inserted_node )
110-
111- return inserted_node
112-
113- def _insert_dequant_node (
114- self ,
115- graph_module : torch .fx .GraphModule ,
116- node : torch .fx .node ,
117- target : torch .fx .node .Target ,
118- ) -> None :
119- with graph_module .graph .inserting_after (node ):
120- users = list (node .users .keys ())
121- inserted_node = self ._create_node (graph_module , node , target )
122- for user in users :
123- if user .op == "output" :
124- user .replace_input_with (node , inserted_node )
125-
12640 def _insert (self , graph_module : torch .fx .GraphModule ) -> torch .fx .GraphModule :
12741 # Snapshot nodes: inserting Q/DQ nodes mutates the graph's linked list,
12842 # so iterating the live list can revisit newly inserted nodes.
@@ -140,20 +54,30 @@ def _insert(self, graph_module: torch.fx.GraphModule) -> torch.fx.GraphModule:
14054 or is_mutable_buffer_input (n , self .edge_program )
14155 )
14256 ):
143- self ._insert_quant_node (
144- graph_module , n , n .meta [QCOM_QUANT_ATTRS ][QCOM_ENCODING ]
145- )
57+ # Insert a single Q node and connect single Q node to all users
58+ users = list (n .users .keys ())
59+ if users :
60+ input_q_node = insert_quant_node (
61+ graph_module = graph_module ,
62+ input_node = n ,
63+ output_node = users [0 ],
64+ target = n .meta [QCOM_QUANT_ATTRS ][QCOM_ENCODING ],
65+ )
66+ for user in users [1 :]:
67+ if user .target not in q_ops :
68+ user .replace_input_with (n , input_q_node )
14669
14770 # insert dq before output or fold mix_quantization q if applicable
148- users = list (n .users .keys ())
149- if n .meta .get (QCOM_QUANT_ATTRS ) and any (
150- user .op == "output" for user in users
151- ):
152- self ._insert_dequant_node (
153- graph_module ,
154- n ,
155- self .q_dq_map [n .meta [QCOM_QUANT_ATTRS ][QCOM_ENCODING ]],
156- )
71+ users = [user for user in n .users if user .op == "output" ]
72+ if n .meta .get (QCOM_QUANT_ATTRS ) and len (users ) != 0 :
73+ # We should always only have 1 output node. Expect len(users) == 1
74+ for user in users :
75+ _ = insert_dequant_node (
76+ graph_module = graph_module ,
77+ input_node = n ,
78+ output_node = user ,
79+ target = q_dq_map [n .meta [QCOM_QUANT_ATTRS ][QCOM_ENCODING ]],
80+ )
15781
15882 def call (self , graph_module : torch .fx .GraphModule ):
15983 self ._insert (graph_module )
0 commit comments