99
1010import numpy as np
1111import torch
12+ from executorch .backends .qualcomm .utils .check_qnn_version import (
13+ is_qnn_sdk_version_less_than ,
14+ )
1215from executorch .backends .qualcomm .utils .constants import (
1316 QCOM_DATA ,
1417 QCOM_DTYPE ,
@@ -35,15 +38,19 @@ class Embedding(NodeVisitor):
3538 def __init__ (self , * args ) -> None :
3639 super ().__init__ (* args )
3740
38- def define_node (
41+ def _is_pcq_embedding (self , weight_node : torch .fx .Node ) -> bool :
42+ return (
43+ QCOM_QUANT_ATTRS in weight_node .meta
44+ and weight_node .meta [QCOM_QUANT_ATTRS ][QCOM_ENCODING ]
45+ in PER_CHANNEL_ENCODING
46+ )
47+
48+ def _define_weight_and_indices (
3949 self ,
4050 node : torch .fx .Node ,
4151 nodes_to_wrappers : Dict [torch .fx .Node , PyQnnManager .TensorWrapper ],
42- ) -> PyQnnManager . PyQnnOpWrapper :
52+ ):
4353 weight_node = self .get_node (node .args [0 ])
44- is_pcq_embedding = QCOM_QUANT_ATTRS in weight_node .meta and weight_node .meta [
45- QCOM_QUANT_ATTRS
46- ][QCOM_ENCODING ] in (PER_CHANNEL_ENCODING )
4754 weight_tensor = get_parameter (weight_node , self .edge_program )
4855 weight_tensor_wrapper = self .define_tensor (
4956 weight_node ,
@@ -62,31 +69,131 @@ def define_node(
6269 PyQnnManager .Qnn_TensorType_t .QNN_TENSOR_TYPE_NATIVE ,
6370 nodes_to_wrappers ,
6471 )
72+ return weight_node , weight_tensor , weight_tensor_wrapper , indices_tensor_wrapper
73+
74+ def _act_dtype (self , node : torch .fx .Node ):
75+ act_quant_encoding , act_quant_configs = self .get_quant_encoding_conf (node , node )
76+ act_dtype = (
77+ torch .uint16
78+ if act_quant_configs [QCOM_DTYPE ] == torch .int32
79+ else act_quant_configs [QCOM_DTYPE ]
80+ )
81+ return act_quant_encoding , act_quant_configs , act_dtype
82+
83+ def _build_gather_op (
84+ self , node_name , gather_input_tensors , gather_output_tensors
85+ ) -> PyQnnManager .PyQnnOpWrapper :
86+ gather_op = PyQnnManager .PyQnnOpWrapper (
87+ node_name ,
88+ QNN_OP_PACKAGE_NAME_QTI_AISW ,
89+ OpGather .op_name ,
90+ )
91+ gather_op .AddInputTensors (gather_input_tensors )
92+ gather_op .AddOutputTensors (gather_output_tensors )
93+ # For now, default axis is zero.
94+ gather_op .AddScalarParam (
95+ OpGather .param_axis ,
96+ PyQnnManager .Qnn_DataType_t .QNN_DATATYPE_INT_32 ,
97+ {QCOM_DATA : np .int32 (0 )},
98+ )
99+ return gather_op
100+
101+ # Note that this pattern is only supported with QNN 2.48+.
102+ # The weight is converted to the activation quantization before gather so
103+ # the backend can fuse the convert into gather for better performance.
104+ def define_node_optimize (
105+ self ,
106+ node : torch .fx .Node ,
107+ nodes_to_wrappers : Dict [torch .fx .Node , PyQnnManager .TensorWrapper ],
108+ ) -> PyQnnManager .PyQnnOpWrapper :
109+ op_wrapper_list = []
110+ (
111+ weight_node ,
112+ weight_tensor ,
113+ weight_tensor_wrapper ,
114+ indices_tensor_wrapper ,
115+ ) = self ._define_weight_and_indices (node , nodes_to_wrappers )
116+
117+ gather_input_tensors = []
118+ if self ._is_pcq_embedding (weight_node ):
119+ act_quant_encoding , act_quant_configs , act_dtype = self ._act_dtype (node )
120+ convert_tensor_wrapper = self .define_custom_tensor_wrapper (
121+ node_name = node .name + "_convert" ,
122+ tensor_type = PyQnnManager .Qnn_TensorType_t .QNN_TENSOR_TYPE_NATIVE ,
123+ dtype = QNN_QUANT_TYPE_MAP [act_dtype ],
124+ quant_encoding = act_quant_encoding ,
125+ quant_configs = act_quant_configs ,
126+ dims = weight_tensor .size (),
127+ tensor = weight_tensor ,
128+ is_fake_tensor = True ,
129+ nodes_to_wrappers = nodes_to_wrappers ,
130+ )
131+ convert_op = PyQnnManager .PyQnnOpWrapper (
132+ node .name + "_convert" ,
133+ QNN_OP_PACKAGE_NAME_QTI_AISW ,
134+ OpConvert .op_name ,
135+ )
136+ convert_op .AddInputTensors ([weight_tensor_wrapper ])
137+ convert_op .AddOutputTensors ([convert_tensor_wrapper ])
138+ op_wrapper_list .append (convert_op )
139+ gather_input_tensors .append (convert_tensor_wrapper )
140+ else :
141+ gather_input_tensors .append (weight_tensor_wrapper )
142+ gather_input_tensors .append (indices_tensor_wrapper )
143+
144+ output_tensor = self .get_tensor (node , node )
145+ output_tensor_wrapper = self .define_tensor (
146+ node ,
147+ node ,
148+ output_tensor ,
149+ PyQnnManager .Qnn_TensorType_t .QNN_TENSOR_TYPE_NATIVE ,
150+ nodes_to_wrappers ,
151+ node_name = node .name ,
152+ )
153+
154+ op_wrapper_list .append (
155+ self ._build_gather_op (
156+ node .name , gather_input_tensors , [output_tensor_wrapper ]
157+ )
158+ )
159+ return op_wrapper_list
160+
161+ def define_node_legacy (
162+ self ,
163+ node : torch .fx .Node ,
164+ nodes_to_wrappers : Dict [torch .fx .Node , PyQnnManager .TensorWrapper ],
165+ ) -> PyQnnManager .PyQnnOpWrapper :
166+ (
167+ weight_node ,
168+ _ ,
169+ weight_tensor_wrapper ,
170+ indices_tensor_wrapper ,
171+ ) = self ._define_weight_and_indices (node , nodes_to_wrappers )
172+ is_pcq_embedding = self ._is_pcq_embedding (weight_node )
65173
66174 gather_input_tensors = [weight_tensor_wrapper , indices_tensor_wrapper ]
67175
68176 output_tensor = self .get_tensor (node , node )
69177 node_name = node .name
70178 if is_pcq_embedding :
71179 node_quant_attrs = node .meta [QCOM_QUANT_ATTRS ].copy ()
72- intermediate_quant_attrs = node .meta [QCOM_QUANT_ATTRS ]. copy ()
180+ weight_quant_attrs = weight_node .meta [QCOM_QUANT_ATTRS ]
73181 # Based on QNN HTP quantization constraints,
74182 # we should set the scale to max of scales and per-tensor quantization for embedding op
183+ intermediate_quant_attrs = node_quant_attrs .copy ()
75184 intermediate_quant_attrs [QCOM_SCALE ] = (
76- weight_node . meta [ QCOM_QUANT_ATTRS ] [QCOM_SCALES ].max ().item ()
185+ weight_quant_attrs [QCOM_SCALES ].max ().item ()
77186 )
78187 intermediate_quant_attrs [QCOM_ZERO_POINT ] = (
79- weight_node . meta [ QCOM_QUANT_ATTRS ] [QCOM_ZERO_POINTS ].max ().item ()
188+ weight_quant_attrs [QCOM_ZERO_POINTS ].max ().item ()
80189 )
81- intermediate_quant_attrs [QCOM_DTYPE ] = weight_node .meta [QCOM_QUANT_ATTRS ][
82- QCOM_DTYPE
190+ intermediate_quant_attrs [QCOM_DTYPE ] = weight_quant_attrs [QCOM_DTYPE ]
191+ intermediate_quant_attrs [QCOM_QUANT_MAX ] = weight_quant_attrs [
192+ QCOM_QUANT_MAX
193+ ]
194+ intermediate_quant_attrs [QCOM_QUANT_MIN ] = weight_quant_attrs [
195+ QCOM_QUANT_MIN
83196 ]
84- intermediate_quant_attrs [QCOM_QUANT_MAX ] = weight_node .meta [
85- QCOM_QUANT_ATTRS
86- ][QCOM_QUANT_MAX ]
87- intermediate_quant_attrs [QCOM_QUANT_MIN ] = weight_node .meta [
88- QCOM_QUANT_ATTRS
89- ][QCOM_QUANT_MIN ]
90197 node .meta [QCOM_QUANT_ATTRS ] = intermediate_quant_attrs
91198 node_name += "_intermediate"
92199 output_tensor_wrapper = self .define_tensor (
@@ -99,33 +206,15 @@ def define_node(
99206 )
100207 gather_output_tensors = [output_tensor_wrapper ]
101208
102- gather_op = PyQnnManager .PyQnnOpWrapper (
103- node_name ,
104- QNN_OP_PACKAGE_NAME_QTI_AISW ,
105- OpGather .op_name ,
106- )
107- gather_op .AddInputTensors (gather_input_tensors )
108- gather_op .AddOutputTensors (gather_output_tensors )
109-
110- # For now, default axis is zero.
111- gather_op .AddScalarParam (
112- OpGather .param_axis ,
113- PyQnnManager .Qnn_DataType_t .QNN_DATATYPE_INT_32 ,
114- {QCOM_DATA : np .int32 (0 )},
115- )
116-
117- op_wrapper_list = [gather_op ]
209+ op_wrapper_list = [
210+ self ._build_gather_op (
211+ node_name , gather_input_tensors , gather_output_tensors
212+ )
213+ ]
118214
119215 if is_pcq_embedding :
120216 node .meta [QCOM_QUANT_ATTRS ] = node_quant_attrs
121- act_quant_encoding , act_quant_configs = self .get_quant_encoding_conf (
122- node , node
123- )
124- act_dtype = (
125- torch .uint16
126- if act_quant_configs [QCOM_DTYPE ] == torch .int32
127- else act_quant_configs [QCOM_DTYPE ]
128- )
217+ act_quant_encoding , act_quant_configs , act_dtype = self ._act_dtype (node )
129218 convert_tensor_wrapper = self .define_custom_tensor_wrapper (
130219 node_name = node .name ,
131220 tensor_type = PyQnnManager .Qnn_TensorType_t .QNN_TENSOR_TYPE_NATIVE ,
@@ -147,3 +236,12 @@ def define_node(
147236 op_wrapper_list .append (convert_op )
148237
149238 return op_wrapper_list
239+
240+ def define_node (
241+ self ,
242+ node : torch .fx .Node ,
243+ nodes_to_wrappers : Dict [torch .fx .Node , PyQnnManager .TensorWrapper ],
244+ ) -> PyQnnManager .PyQnnOpWrapper :
245+ if is_qnn_sdk_version_less_than ("2.48" ):
246+ return self .define_node_legacy (node , nodes_to_wrappers )
247+ return self .define_node_optimize (node , nodes_to_wrappers )
0 commit comments