|
12 | 12 | from typing import List, Optional, Tuple, Union |
13 | 13 |
|
14 | 14 | import torch |
| 15 | +from executorch.backends.cadence.aot.compiler_utils import get_shape |
15 | 16 | from executorch.backends.cadence.aot.pass_utils import get_arg |
16 | 17 | from executorch.backends.cadence.aot.quantizer.pattern_utils import ( |
17 | 18 | find_quant_user, |
|
25 | 26 | check_out_zero_point_is_min_range, |
26 | 27 | copy_node_metadata, |
27 | 28 | get_bias_qparams, |
| 29 | + quantize_tensor_multiplier, |
28 | 30 | ) |
29 | 31 | from torch import fx |
30 | 32 | from torch._ops import OpOverload |
@@ -304,9 +306,8 @@ def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
304 | 306 | quant_node = find_quant_user(relu_node) |
305 | 307 | if quant_node is None: |
306 | 308 | return None |
307 | | - check_out_zero_point_is_min_range( |
308 | | - quant_node.args[2], quant_node.args[5] # pyre-ignore[6] |
309 | | - ) |
| 309 | + # pyre-ignore[6]: Argument -> int/dtype narrowing |
| 310 | + check_out_zero_point_is_min_range(quant_node.args[2], quant_node.args[5]) |
310 | 311 | args = ( |
311 | 312 | dq0.args[0], |
312 | 313 | dq0.args[1], |
@@ -803,6 +804,30 @@ def get_anchors( |
803 | 804 | def replacement_op(self) -> OpOverload: |
804 | 805 | return torch.ops.cadence.quantized_max_pool2d_nchw.default |
805 | 806 |
|
| 807 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 808 | + dq_input = get_dequant(anchor_node.args[0]) |
| 809 | + if dq_input is None: |
| 810 | + return None |
| 811 | + quant_node = find_quant_user(anchor_node) |
| 812 | + if quant_node is None: |
| 813 | + return None |
| 814 | + kernel_size = get_arg(anchor_node, "kernel_size", Optional[list[int]]) or [1, 1] |
| 815 | + stride = get_arg(anchor_node, "stride", Optional[list[int]]) or kernel_size |
| 816 | + padding = get_arg(anchor_node, "padding", Optional[list[int]]) or [0, 0] |
| 817 | + dilation = get_arg(anchor_node, "dilation", Optional[list[int]]) or [1, 1] |
| 818 | + ceil_mode = get_arg(anchor_node, "ceil_mode", Optional[bool]) or False |
| 819 | + args = (dq_input.args[0],) |
| 820 | + kwargs = { |
| 821 | + "kernel_size": kernel_size, |
| 822 | + "stride": stride, |
| 823 | + "padding": padding, |
| 824 | + "dilation": dilation, |
| 825 | + "ceil_mode": ceil_mode, |
| 826 | + } |
| 827 | + return insert_fused_op( |
| 828 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 829 | + ) |
| 830 | + |
806 | 831 |
|
807 | 832 | class MaxPool2dWithoutIndicesPattern(QuantizationPattern): |
808 | 833 | """ |
@@ -842,6 +867,30 @@ def get_anchors( |
842 | 867 | def replacement_op(self) -> OpOverload: |
843 | 868 | return torch.ops.cadence.quantized_max_pool2d_nchw.default |
844 | 869 |
|
| 870 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 871 | + dq_input = get_dequant(anchor_node.args[0]) |
| 872 | + if dq_input is None: |
| 873 | + return None |
| 874 | + quant_node = find_quant_user(anchor_node) |
| 875 | + if quant_node is None: |
| 876 | + return None |
| 877 | + kernel_size = get_arg(anchor_node, "kernel_size", Optional[list[int]]) or [1, 1] |
| 878 | + stride = get_arg(anchor_node, "stride", Optional[list[int]]) or kernel_size |
| 879 | + padding = get_arg(anchor_node, "padding", Optional[list[int]]) or [0, 0] |
| 880 | + dilation = get_arg(anchor_node, "dilation", Optional[list[int]]) or [1, 1] |
| 881 | + ceil_mode = get_arg(anchor_node, "ceil_mode", Optional[bool]) or False |
| 882 | + args = (dq_input.args[0],) |
| 883 | + kwargs = { |
| 884 | + "kernel_size": kernel_size, |
| 885 | + "stride": stride, |
| 886 | + "padding": padding, |
| 887 | + "dilation": dilation, |
| 888 | + "ceil_mode": ceil_mode, |
| 889 | + } |
| 890 | + return insert_fused_op( |
| 891 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 892 | + ) |
| 893 | + |
845 | 894 |
|
846 | 895 | # This is a base class for ReLU |
847 | 896 |
|
@@ -871,6 +920,29 @@ def get_anchors( |
871 | 920 | def replacement_op(self) -> OpOverload: |
872 | 921 | return torch.ops.cadence.quantized_relu.per_tensor |
873 | 922 |
|
| 923 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 924 | + dq_input = get_dequant(anchor_node.args[0]) |
| 925 | + if dq_input is None: |
| 926 | + return None |
| 927 | + quant_node = find_quant_user(anchor_node) |
| 928 | + if quant_node is None: |
| 929 | + return None |
| 930 | + input_scale = dq_input.args[1] |
| 931 | + # pyre-fixme[58] |
| 932 | + requantize_scale = input_scale / quant_node.args[1] |
| 933 | + requantize_scale_t = torch.tensor([requantize_scale]) |
| 934 | + out_multiplier, out_shift = quantize_tensor_multiplier(requantize_scale_t) |
| 935 | + args = (dq_input.args[0],) |
| 936 | + kwargs = { |
| 937 | + "X_zero_point": dq_input.args[2], |
| 938 | + "out_zero_point": quant_node.args[2], |
| 939 | + "out_multiplier": out_multiplier[0].item(), |
| 940 | + "out_shift": out_shift[0].item(), |
| 941 | + } |
| 942 | + return insert_fused_op( |
| 943 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 944 | + ) |
| 945 | + |
874 | 946 |
|
875 | 947 | # Regular relu op |
876 | 948 | class ReluPattern0(ReluBasePattern): |
@@ -930,6 +1002,27 @@ def get_anchors( |
930 | 1002 | def replacement_op(self) -> OpOverload: |
931 | 1003 | return torch.ops.cadence.quantized_conv2d_nchw.per_tensor |
932 | 1004 |
|
| 1005 | + def anchor_ops(self) -> tuple[OpOverload, ...]: |
| 1006 | + return (self.partition_types()[0],) |
| 1007 | + |
| 1008 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1009 | + conv_users = list(anchor_node.users) |
| 1010 | + if len(conv_users) != 1: |
| 1011 | + return None |
| 1012 | + relu_node = conv_users[0] |
| 1013 | + if relu_node.target != self.partition_types()[1]: |
| 1014 | + return None |
| 1015 | + dq_input = get_dequant(anchor_node.args[0]) |
| 1016 | + dq_weight = get_dequant(anchor_node.args[1]) |
| 1017 | + if dq_input is None or dq_weight is None: |
| 1018 | + return None |
| 1019 | + quant_node = find_quant_user(relu_node) |
| 1020 | + if quant_node is None: |
| 1021 | + return None |
| 1022 | + # pyre-ignore[6]: Argument -> int/dtype narrowing |
| 1023 | + check_out_zero_point_is_min_range(quant_node.args[2], quant_node.args[5]) |
| 1024 | + return fuse_conv(self, gm, anchor_node, dq_input, dq_weight, quant_node) |
| 1025 | + |
933 | 1026 |
|
934 | 1027 | # Conv1d + regular relu op fusion |
935 | 1028 | class Conv1dReluPattern0(ConvReluBasePattern): |
@@ -984,6 +1077,52 @@ def get_anchors( |
984 | 1077 | def replacement_op(self) -> OpOverload: |
985 | 1078 | return torch.ops.cadence.quantized_softmax.per_tensor |
986 | 1079 |
|
| 1080 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1081 | + dq_input = get_dequant(anchor_node.args[0]) |
| 1082 | + if dq_input is None: |
| 1083 | + return None |
| 1084 | + quant_node = find_quant_user(anchor_node) |
| 1085 | + if quant_node is None: |
| 1086 | + return None |
| 1087 | + input_q = dq_input.args[0] |
| 1088 | + assert isinstance(input_q, fx.Node) |
| 1089 | + quant_input = quant_node.args[0] |
| 1090 | + assert isinstance(quant_input, fx.Node) |
| 1091 | + mask_shape = get_shape(gm, quant_input) |
| 1092 | + mask_shape = list(mask_shape) if mask_shape else [] |
| 1093 | + mask_shape[-1] = mask_shape[-1] // 16 |
| 1094 | + with gm.graph.inserting_before(anchor_node): |
| 1095 | + mask_tensor = gm.graph.call_function( |
| 1096 | + torch.ops.aten.full.default, (mask_shape, 0.0), {"dtype": torch.int32} |
| 1097 | + ) |
| 1098 | + assert "val" in input_q.meta |
| 1099 | + fake_mode = input_q.meta["val"].fake_mode |
| 1100 | + assert fake_mode is not None |
| 1101 | + with fake_mode: |
| 1102 | + mask_tensor.meta["val"] = torch.full(mask_shape, 0.0, dtype=torch.int32) |
| 1103 | + copy_node_metadata(mask_tensor, input_q) |
| 1104 | + with gm.graph.inserting_before(anchor_node): |
| 1105 | + pos_tensor = gm.graph.call_function( |
| 1106 | + torch.ops.aten.full.default, ([1], 0), {"dtype": torch.int64} |
| 1107 | + ) |
| 1108 | + with fake_mode: |
| 1109 | + pos_tensor.meta["val"] = torch.full([1], 0, dtype=torch.int64) |
| 1110 | + copy_node_metadata(pos_tensor, input_q) |
| 1111 | + args = ( |
| 1112 | + input_q, |
| 1113 | + mask_tensor, |
| 1114 | + get_arg(anchor_node, "dim", int), |
| 1115 | + 0, |
| 1116 | + pos_tensor, |
| 1117 | + dq_input.args[1], |
| 1118 | + dq_input.args[2], |
| 1119 | + quant_node.args[1], |
| 1120 | + quant_node.args[2], |
| 1121 | + ) |
| 1122 | + return insert_fused_op( |
| 1123 | + gm, anchor_node, self.replacement_op(), args, {}, quant_node |
| 1124 | + ) |
| 1125 | + |
987 | 1126 |
|
988 | 1127 | class MixedW8A32LinearPattern(QuantizationPattern): |
989 | 1128 | def partition_types(self) -> List[OpOverload]: |
@@ -1038,6 +1177,28 @@ def get_anchors( |
1038 | 1177 | def replacement_op(self) -> OpOverload: |
1039 | 1178 | return torch.ops.cadence.quantized_w8a32_linear.default |
1040 | 1179 |
|
| 1180 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1181 | + if len(anchor_node.args) != 3 or len(anchor_node.kwargs) > 0: |
| 1182 | + return None |
| 1183 | + dq_weight = get_dequant(anchor_node.args[1]) |
| 1184 | + dq_bias = get_dequant(anchor_node.args[2]) |
| 1185 | + if dq_weight is None or dq_bias is None: |
| 1186 | + return None |
| 1187 | + input_node = anchor_node.args[0] |
| 1188 | + assert isinstance(input_node, fx.Node) |
| 1189 | + args = ( |
| 1190 | + input_node, |
| 1191 | + dq_weight.args[0], |
| 1192 | + dq_weight.args[1], |
| 1193 | + dq_bias.args[0], |
| 1194 | + dq_bias.args[1], |
| 1195 | + ) |
| 1196 | + with gm.graph.inserting_after(anchor_node): |
| 1197 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1198 | + fused.meta = anchor_node.meta |
| 1199 | + anchor_node.replace_all_uses_with(fused) |
| 1200 | + return fused |
| 1201 | + |
1041 | 1202 |
|
1042 | 1203 | class MixedW8A32ConvPattern(QuantizationPattern): |
1043 | 1204 | def partition_types(self) -> List[OpOverload]: |
@@ -1112,6 +1273,68 @@ def get_anchors( |
1112 | 1273 | def replacement_op(self) -> OpOverload: |
1113 | 1274 | return torch.ops.cadence.quantized_w8a32_conv.default |
1114 | 1275 |
|
| 1276 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1277 | + if len(anchor_node.args) != 3 or len(anchor_node.kwargs) > 0: |
| 1278 | + return None |
| 1279 | + dq_weight = get_dequant(anchor_node.args[1]) |
| 1280 | + dq_bias = get_dequant(anchor_node.args[2]) |
| 1281 | + if dq_weight is None or dq_bias is None: |
| 1282 | + return None |
| 1283 | + input_node = anchor_node.args[0] |
| 1284 | + assert isinstance(input_node, fx.Node) |
| 1285 | + assert get_arg(anchor_node, "stride", list[int]) == [1] |
| 1286 | + assert get_arg(anchor_node, "padding", list[int]) == [0] |
| 1287 | + assert get_arg(anchor_node, "dilation", list[int]) == [1] |
| 1288 | + assert get_arg(anchor_node, "groups", int) == 1 |
| 1289 | + weight_q = dq_weight.args[0] |
| 1290 | + assert isinstance(weight_q, fx.Node) |
| 1291 | + with gm.graph.inserting_before(anchor_node): |
| 1292 | + transposed_inputs = gm.graph.call_function( |
| 1293 | + torch.ops.aten.permute.default, (input_node, [0, 2, 1]) |
| 1294 | + ) |
| 1295 | + if "val" in input_node.meta: |
| 1296 | + original_val = input_node.meta["val"] |
| 1297 | + fake_mode = original_val.fake_mode |
| 1298 | + if fake_mode is not None: |
| 1299 | + with fake_mode: |
| 1300 | + transposed_inputs.meta["val"] = torch.ops.aten.permute.default( |
| 1301 | + original_val, [0, 2, 1] |
| 1302 | + ) |
| 1303 | + else: |
| 1304 | + transposed_inputs.meta["val"] = torch.ops.aten.permute.default( |
| 1305 | + original_val, [0, 2, 1] |
| 1306 | + ) |
| 1307 | + copy_node_metadata(transposed_inputs, input_node) |
| 1308 | + with gm.graph.inserting_before(anchor_node): |
| 1309 | + transposed_weights = gm.graph.call_function( |
| 1310 | + torch.ops.aten.permute.default, (weight_q, [2, 0, 1]) |
| 1311 | + ) |
| 1312 | + if "val" in weight_q.meta: |
| 1313 | + original_val = weight_q.meta["val"] |
| 1314 | + fake_mode = original_val.fake_mode |
| 1315 | + if fake_mode is not None: |
| 1316 | + with fake_mode: |
| 1317 | + transposed_weights.meta["val"] = torch.ops.aten.permute.default( |
| 1318 | + original_val, [2, 0, 1] |
| 1319 | + ) |
| 1320 | + else: |
| 1321 | + transposed_weights.meta["val"] = torch.ops.aten.permute.default( |
| 1322 | + original_val, [2, 0, 1] |
| 1323 | + ) |
| 1324 | + copy_node_metadata(transposed_weights, weight_q) |
| 1325 | + args = ( |
| 1326 | + transposed_inputs, |
| 1327 | + transposed_weights, |
| 1328 | + dq_weight.args[1], |
| 1329 | + dq_bias.args[0], |
| 1330 | + dq_bias.args[1], |
| 1331 | + ) |
| 1332 | + with gm.graph.inserting_after(anchor_node): |
| 1333 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1334 | + fused.meta = anchor_node.meta |
| 1335 | + anchor_node.replace_all_uses_with(fused) |
| 1336 | + return fused |
| 1337 | + |
1115 | 1338 |
|
1116 | 1339 | class MixedW8A32GruPattern(QuantizationPattern): |
1117 | 1340 | def partition_types(self) -> List[OpOverload]: |
@@ -1184,6 +1407,37 @@ def __init__(self, args, meta): |
1184 | 1407 | def replacement_op(self) -> OpOverload: |
1185 | 1408 | return torch.ops.cadence.quantized_w8a32_gru.default |
1186 | 1409 |
|
| 1410 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1411 | + if len(anchor_node.kwargs) > 0: |
| 1412 | + return None |
| 1413 | + params = anchor_node.args[2] |
| 1414 | + if not isinstance(params, (list, tuple)) or len(params) < 4: |
| 1415 | + return None |
| 1416 | + dq_w_ih = get_dequant(params[0]) |
| 1417 | + dq_w_hh = get_dequant(params[1]) |
| 1418 | + dq_b_ih = get_dequant(params[2]) |
| 1419 | + dq_b_hh = get_dequant(params[3]) |
| 1420 | + if dq_w_ih is None or dq_w_hh is None or dq_b_ih is None or dq_b_hh is None: |
| 1421 | + return None |
| 1422 | + input_node = anchor_node.args[0] |
| 1423 | + hidden_node = anchor_node.args[1] |
| 1424 | + args = ( |
| 1425 | + input_node, |
| 1426 | + hidden_node, |
| 1427 | + dq_w_ih.args[0], |
| 1428 | + dq_w_ih.args[1], |
| 1429 | + dq_w_hh.args[0], |
| 1430 | + dq_w_hh.args[1], |
| 1431 | + dq_b_ih.args[0], |
| 1432 | + dq_b_ih.args[1], |
| 1433 | + dq_b_hh.args[0], |
| 1434 | + ) |
| 1435 | + with gm.graph.inserting_after(anchor_node): |
| 1436 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1437 | + fused.meta = anchor_node.meta |
| 1438 | + anchor_node.replace_all_uses_with(fused) |
| 1439 | + return fused |
| 1440 | + |
1187 | 1441 |
|
1188 | 1442 | class RmsNormPattern(QuantizationPattern): |
1189 | 1443 | """Pattern that preserves rms_norm from decomposition without matching anything.""" |
|
0 commit comments