|
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 | DQ_PER_TENSOR, |
|
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 |
@@ -321,9 +323,8 @@ def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
321 | 323 | quant_node = find_quant_user(relu_node) |
322 | 324 | if quant_node is None: |
323 | 325 | return None |
324 | | - check_out_zero_point_is_min_range( |
325 | | - quant_node.args[2], quant_node.args[5] # pyre-ignore[6] |
326 | | - ) |
| 326 | + # pyre-ignore[6]: Argument -> int/dtype narrowing |
| 327 | + check_out_zero_point_is_min_range(quant_node.args[2], quant_node.args[5]) |
327 | 328 | args = ( |
328 | 329 | dq0.args[0], |
329 | 330 | dq0.args[1], |
@@ -724,13 +725,11 @@ def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
724 | 725 | quant_node = find_quant_user(anchor_node) |
725 | 726 | if quant_node is None: |
726 | 727 | return None |
727 | | - dq_bias = ( |
728 | | - anchor_node.args[2] |
729 | | - if len(anchor_node.args) > 2 |
730 | | - and isinstance(anchor_node.args[2], fx.Node) |
731 | | - and anchor_node.args[2].target == DQ_PER_TENSOR |
732 | | - else None |
733 | | - ) |
| 728 | + dq_bias: Optional[fx.Node] = None |
| 729 | + if len(anchor_node.args) > 2: |
| 730 | + bias_arg = anchor_node.args[2] |
| 731 | + if isinstance(bias_arg, fx.Node) and bias_arg.target == DQ_PER_TENSOR: |
| 732 | + dq_bias = bias_arg |
734 | 733 | return fuse_linear( |
735 | 734 | gm, |
736 | 735 | dq_input, |
@@ -836,6 +835,30 @@ def get_anchors( |
836 | 835 | def replacement_op(self) -> OpOverload: |
837 | 836 | return torch.ops.cadence.quantized_max_pool2d_nchw.default |
838 | 837 |
|
| 838 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 839 | + dq_input = anchor_node.args[0] |
| 840 | + if not isinstance(dq_input, fx.Node) or dq_input.target != DQ_PER_TENSOR: |
| 841 | + return None |
| 842 | + quant_node = find_quant_user(anchor_node) |
| 843 | + if quant_node is None: |
| 844 | + return None |
| 845 | + kernel_size = get_arg(anchor_node, "kernel_size", Optional[list[int]]) or [1, 1] |
| 846 | + stride = get_arg(anchor_node, "stride", Optional[list[int]]) or kernel_size |
| 847 | + padding = get_arg(anchor_node, "padding", Optional[list[int]]) or [0, 0] |
| 848 | + dilation = get_arg(anchor_node, "dilation", Optional[list[int]]) or [1, 1] |
| 849 | + ceil_mode = get_arg(anchor_node, "ceil_mode", Optional[bool]) or False |
| 850 | + args = (dq_input.args[0],) |
| 851 | + kwargs = { |
| 852 | + "kernel_size": kernel_size, |
| 853 | + "stride": stride, |
| 854 | + "padding": padding, |
| 855 | + "dilation": dilation, |
| 856 | + "ceil_mode": ceil_mode, |
| 857 | + } |
| 858 | + return replace_with_op( |
| 859 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 860 | + ) |
| 861 | + |
839 | 862 |
|
840 | 863 | class MaxPool2dWithoutIndicesPattern(QuantizationPattern): |
841 | 864 | """ |
@@ -875,6 +898,30 @@ def get_anchors( |
875 | 898 | def replacement_op(self) -> OpOverload: |
876 | 899 | return torch.ops.cadence.quantized_max_pool2d_nchw.default |
877 | 900 |
|
| 901 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 902 | + dq_input = anchor_node.args[0] |
| 903 | + if not isinstance(dq_input, fx.Node) or dq_input.target != DQ_PER_TENSOR: |
| 904 | + return None |
| 905 | + quant_node = find_quant_user(anchor_node) |
| 906 | + if quant_node is None: |
| 907 | + return None |
| 908 | + kernel_size = get_arg(anchor_node, "kernel_size", Optional[list[int]]) or [1, 1] |
| 909 | + stride = get_arg(anchor_node, "stride", Optional[list[int]]) or kernel_size |
| 910 | + padding = get_arg(anchor_node, "padding", Optional[list[int]]) or [0, 0] |
| 911 | + dilation = get_arg(anchor_node, "dilation", Optional[list[int]]) or [1, 1] |
| 912 | + ceil_mode = get_arg(anchor_node, "ceil_mode", Optional[bool]) or False |
| 913 | + args = (dq_input.args[0],) |
| 914 | + kwargs = { |
| 915 | + "kernel_size": kernel_size, |
| 916 | + "stride": stride, |
| 917 | + "padding": padding, |
| 918 | + "dilation": dilation, |
| 919 | + "ceil_mode": ceil_mode, |
| 920 | + } |
| 921 | + return replace_with_op( |
| 922 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 923 | + ) |
| 924 | + |
878 | 925 |
|
879 | 926 | # This is a base class for ReLU |
880 | 927 |
|
@@ -904,6 +951,29 @@ def get_anchors( |
904 | 951 | def replacement_op(self) -> OpOverload: |
905 | 952 | return torch.ops.cadence.quantized_relu.per_tensor |
906 | 953 |
|
| 954 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 955 | + dq_input = anchor_node.args[0] |
| 956 | + if not isinstance(dq_input, fx.Node) or dq_input.target != DQ_PER_TENSOR: |
| 957 | + return None |
| 958 | + quant_node = find_quant_user(anchor_node) |
| 959 | + if quant_node is None: |
| 960 | + return None |
| 961 | + input_scale = dq_input.args[1] |
| 962 | + # pyre-fixme[58] |
| 963 | + requantize_scale = input_scale / quant_node.args[1] |
| 964 | + requantize_scale_t = torch.tensor([requantize_scale]) |
| 965 | + out_multiplier, out_shift = quantize_tensor_multiplier(requantize_scale_t) |
| 966 | + args = (dq_input.args[0],) |
| 967 | + kwargs = { |
| 968 | + "X_zero_point": dq_input.args[2], |
| 969 | + "out_zero_point": quant_node.args[2], |
| 970 | + "out_multiplier": out_multiplier[0].item(), |
| 971 | + "out_shift": out_shift[0].item(), |
| 972 | + } |
| 973 | + return replace_with_op( |
| 974 | + gm, anchor_node, self.replacement_op(), args, kwargs, quant_node |
| 975 | + ) |
| 976 | + |
907 | 977 |
|
908 | 978 | # Regular relu op |
909 | 979 | class ReluPattern0(ReluBasePattern): |
@@ -963,6 +1033,37 @@ def get_anchors( |
963 | 1033 | def replacement_op(self) -> OpOverload: |
964 | 1034 | return torch.ops.cadence.quantized_conv2d_nchw.per_tensor |
965 | 1035 |
|
| 1036 | + def anchor_ops(self) -> tuple[OpOverload, ...]: |
| 1037 | + return (self.partition_types()[0],) |
| 1038 | + |
| 1039 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1040 | + conv_users = list(anchor_node.users) |
| 1041 | + if len(conv_users) != 1: |
| 1042 | + return None |
| 1043 | + relu_node = conv_users[0] |
| 1044 | + if relu_node.target != self.partition_types()[1]: |
| 1045 | + return None |
| 1046 | + _arg0 = anchor_node.args[0] |
| 1047 | + dq_input = ( |
| 1048 | + _arg0 |
| 1049 | + if isinstance(_arg0, fx.Node) and _arg0.target == DQ_PER_TENSOR |
| 1050 | + else None |
| 1051 | + ) |
| 1052 | + _arg1 = anchor_node.args[1] |
| 1053 | + dq_weight = ( |
| 1054 | + _arg1 |
| 1055 | + if isinstance(_arg1, fx.Node) and _arg1.target == DQ_PER_TENSOR |
| 1056 | + else None |
| 1057 | + ) |
| 1058 | + if dq_input is None or dq_weight is None: |
| 1059 | + return None |
| 1060 | + quant_node = find_quant_user(relu_node) |
| 1061 | + if quant_node is None: |
| 1062 | + return None |
| 1063 | + # pyre-ignore[6]: Argument -> int/dtype narrowing |
| 1064 | + check_out_zero_point_is_min_range(quant_node.args[2], quant_node.args[5]) |
| 1065 | + return fuse_conv(self, gm, anchor_node, dq_input, dq_weight, quant_node) |
| 1066 | + |
966 | 1067 |
|
967 | 1068 | # Conv1d + regular relu op fusion |
968 | 1069 | class Conv1dReluPattern0(ConvReluBasePattern): |
@@ -1017,6 +1118,52 @@ def get_anchors( |
1017 | 1118 | def replacement_op(self) -> OpOverload: |
1018 | 1119 | return torch.ops.cadence.quantized_softmax.per_tensor |
1019 | 1120 |
|
| 1121 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1122 | + dq_input = anchor_node.args[0] |
| 1123 | + if not isinstance(dq_input, fx.Node) or dq_input.target != DQ_PER_TENSOR: |
| 1124 | + return None |
| 1125 | + quant_node = find_quant_user(anchor_node) |
| 1126 | + if quant_node is None: |
| 1127 | + return None |
| 1128 | + input_q = dq_input.args[0] |
| 1129 | + assert isinstance(input_q, fx.Node) |
| 1130 | + quant_input = quant_node.args[0] |
| 1131 | + assert isinstance(quant_input, fx.Node) |
| 1132 | + mask_shape = get_shape(gm, quant_input) |
| 1133 | + mask_shape = list(mask_shape) if mask_shape else [] |
| 1134 | + mask_shape[-1] = mask_shape[-1] // 16 |
| 1135 | + with gm.graph.inserting_before(anchor_node): |
| 1136 | + mask_tensor = gm.graph.call_function( |
| 1137 | + torch.ops.aten.full.default, (mask_shape, 0.0), {"dtype": torch.int32} |
| 1138 | + ) |
| 1139 | + assert "val" in input_q.meta |
| 1140 | + fake_mode = input_q.meta["val"].fake_mode |
| 1141 | + assert fake_mode is not None |
| 1142 | + with fake_mode: |
| 1143 | + mask_tensor.meta["val"] = torch.full(mask_shape, 0.0, dtype=torch.int32) |
| 1144 | + copy_node_metadata(mask_tensor, input_q) |
| 1145 | + with gm.graph.inserting_before(anchor_node): |
| 1146 | + pos_tensor = gm.graph.call_function( |
| 1147 | + torch.ops.aten.full.default, ([1], 0), {"dtype": torch.int64} |
| 1148 | + ) |
| 1149 | + with fake_mode: |
| 1150 | + pos_tensor.meta["val"] = torch.full([1], 0, dtype=torch.int64) |
| 1151 | + copy_node_metadata(pos_tensor, input_q) |
| 1152 | + args = ( |
| 1153 | + input_q, |
| 1154 | + mask_tensor, |
| 1155 | + get_arg(anchor_node, "dim", int), |
| 1156 | + 0, |
| 1157 | + pos_tensor, |
| 1158 | + dq_input.args[1], |
| 1159 | + dq_input.args[2], |
| 1160 | + quant_node.args[1], |
| 1161 | + quant_node.args[2], |
| 1162 | + ) |
| 1163 | + return replace_with_op( |
| 1164 | + gm, anchor_node, self.replacement_op(), args, {}, quant_node |
| 1165 | + ) |
| 1166 | + |
1020 | 1167 |
|
1021 | 1168 | class MixedW8A32LinearPattern(QuantizationPattern): |
1022 | 1169 | def partition_types(self) -> List[OpOverload]: |
@@ -1071,6 +1218,38 @@ def get_anchors( |
1071 | 1218 | def replacement_op(self) -> OpOverload: |
1072 | 1219 | return torch.ops.cadence.quantized_w8a32_linear.default |
1073 | 1220 |
|
| 1221 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1222 | + if len(anchor_node.args) != 3 or len(anchor_node.kwargs) > 0: |
| 1223 | + return None |
| 1224 | + _arg1 = anchor_node.args[1] |
| 1225 | + dq_weight = ( |
| 1226 | + _arg1 |
| 1227 | + if isinstance(_arg1, fx.Node) and _arg1.target == DQ_PER_TENSOR |
| 1228 | + else None |
| 1229 | + ) |
| 1230 | + _arg2 = anchor_node.args[2] |
| 1231 | + dq_bias = ( |
| 1232 | + _arg2 |
| 1233 | + if isinstance(_arg2, fx.Node) and _arg2.target == DQ_PER_TENSOR |
| 1234 | + else None |
| 1235 | + ) |
| 1236 | + if dq_weight is None or dq_bias is None: |
| 1237 | + return None |
| 1238 | + input_node = anchor_node.args[0] |
| 1239 | + assert isinstance(input_node, fx.Node) |
| 1240 | + args = ( |
| 1241 | + input_node, |
| 1242 | + dq_weight.args[0], |
| 1243 | + dq_weight.args[1], |
| 1244 | + dq_bias.args[0], |
| 1245 | + dq_bias.args[1], |
| 1246 | + ) |
| 1247 | + with gm.graph.inserting_after(anchor_node): |
| 1248 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1249 | + fused.meta = anchor_node.meta |
| 1250 | + anchor_node.replace_all_uses_with(fused) |
| 1251 | + return fused |
| 1252 | + |
1074 | 1253 |
|
1075 | 1254 | class MixedW8A32ConvPattern(QuantizationPattern): |
1076 | 1255 | def partition_types(self) -> List[OpOverload]: |
@@ -1145,6 +1324,78 @@ def get_anchors( |
1145 | 1324 | def replacement_op(self) -> OpOverload: |
1146 | 1325 | return torch.ops.cadence.quantized_w8a32_conv.default |
1147 | 1326 |
|
| 1327 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1328 | + if len(anchor_node.args) != 3 or len(anchor_node.kwargs) > 0: |
| 1329 | + return None |
| 1330 | + _arg1 = anchor_node.args[1] |
| 1331 | + dq_weight = ( |
| 1332 | + _arg1 |
| 1333 | + if isinstance(_arg1, fx.Node) and _arg1.target == DQ_PER_TENSOR |
| 1334 | + else None |
| 1335 | + ) |
| 1336 | + _arg2 = anchor_node.args[2] |
| 1337 | + dq_bias = ( |
| 1338 | + _arg2 |
| 1339 | + if isinstance(_arg2, fx.Node) and _arg2.target == DQ_PER_TENSOR |
| 1340 | + else None |
| 1341 | + ) |
| 1342 | + if dq_weight is None or dq_bias is None: |
| 1343 | + return None |
| 1344 | + input_node = anchor_node.args[0] |
| 1345 | + assert isinstance(input_node, fx.Node) |
| 1346 | + assert get_arg(anchor_node, "stride", list[int]) == [1] |
| 1347 | + assert get_arg(anchor_node, "padding", list[int]) == [0] |
| 1348 | + assert get_arg(anchor_node, "dilation", list[int]) == [1] |
| 1349 | + assert get_arg(anchor_node, "groups", int) == 1 |
| 1350 | + weight_q = dq_weight.args[0] |
| 1351 | + assert isinstance(weight_q, fx.Node) |
| 1352 | + with gm.graph.inserting_before(anchor_node): |
| 1353 | + transposed_inputs = gm.graph.call_function( |
| 1354 | + torch.ops.aten.permute.default, (input_node, [0, 2, 1]) |
| 1355 | + ) |
| 1356 | + if "val" in input_node.meta: |
| 1357 | + original_val = input_node.meta["val"] |
| 1358 | + fake_mode = original_val.fake_mode |
| 1359 | + if fake_mode is not None: |
| 1360 | + with fake_mode: |
| 1361 | + transposed_inputs.meta["val"] = torch.ops.aten.permute.default( |
| 1362 | + original_val, [0, 2, 1] |
| 1363 | + ) |
| 1364 | + else: |
| 1365 | + transposed_inputs.meta["val"] = torch.ops.aten.permute.default( |
| 1366 | + original_val, [0, 2, 1] |
| 1367 | + ) |
| 1368 | + copy_node_metadata(transposed_inputs, input_node) |
| 1369 | + with gm.graph.inserting_before(anchor_node): |
| 1370 | + transposed_weights = gm.graph.call_function( |
| 1371 | + torch.ops.aten.permute.default, (weight_q, [2, 0, 1]) |
| 1372 | + ) |
| 1373 | + if "val" in weight_q.meta: |
| 1374 | + original_val = weight_q.meta["val"] |
| 1375 | + fake_mode = original_val.fake_mode |
| 1376 | + if fake_mode is not None: |
| 1377 | + with fake_mode: |
| 1378 | + transposed_weights.meta["val"] = torch.ops.aten.permute.default( |
| 1379 | + original_val, [2, 0, 1] |
| 1380 | + ) |
| 1381 | + else: |
| 1382 | + transposed_weights.meta["val"] = torch.ops.aten.permute.default( |
| 1383 | + original_val, [2, 0, 1] |
| 1384 | + ) |
| 1385 | + copy_node_metadata(transposed_weights, weight_q) |
| 1386 | + args = ( |
| 1387 | + transposed_inputs, |
| 1388 | + transposed_weights, |
| 1389 | + dq_weight.args[1], |
| 1390 | + dq_bias.args[0], |
| 1391 | + dq_bias.args[1], |
| 1392 | + ) |
| 1393 | + with gm.graph.inserting_after(anchor_node): |
| 1394 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1395 | + fused.meta = anchor_node.meta |
| 1396 | + anchor_node.replace_all_uses_with(fused) |
| 1397 | + return fused |
| 1398 | + |
1148 | 1399 |
|
1149 | 1400 | class MixedW8A32GruPattern(QuantizationPattern): |
1150 | 1401 | def partition_types(self) -> List[OpOverload]: |
@@ -1217,6 +1468,43 @@ def __init__(self, args, meta): |
1217 | 1468 | def replacement_op(self) -> OpOverload: |
1218 | 1469 | return torch.ops.cadence.quantized_w8a32_gru.default |
1219 | 1470 |
|
| 1471 | + def fuse(self, gm: fx.GraphModule, anchor_node: fx.Node) -> Optional[fx.Node]: |
| 1472 | + if len(anchor_node.kwargs) > 0: |
| 1473 | + return None |
| 1474 | + params = anchor_node.args[2] |
| 1475 | + if not isinstance(params, (list, tuple)) or len(params) < 4: |
| 1476 | + return None |
| 1477 | + dq_w_ih = params[0] |
| 1478 | + if not isinstance(dq_w_ih, fx.Node) or dq_w_ih.target != DQ_PER_TENSOR: |
| 1479 | + return None |
| 1480 | + dq_w_hh = params[1] |
| 1481 | + if not isinstance(dq_w_hh, fx.Node) or dq_w_hh.target != DQ_PER_TENSOR: |
| 1482 | + return None |
| 1483 | + dq_b_ih = params[2] |
| 1484 | + if not isinstance(dq_b_ih, fx.Node) or dq_b_ih.target != DQ_PER_TENSOR: |
| 1485 | + return None |
| 1486 | + dq_b_hh = params[3] |
| 1487 | + if not isinstance(dq_b_hh, fx.Node) or dq_b_hh.target != DQ_PER_TENSOR: |
| 1488 | + return None |
| 1489 | + input_node = anchor_node.args[0] |
| 1490 | + hidden_node = anchor_node.args[1] |
| 1491 | + args = ( |
| 1492 | + input_node, |
| 1493 | + hidden_node, |
| 1494 | + dq_w_ih.args[0], |
| 1495 | + dq_w_ih.args[1], |
| 1496 | + dq_w_hh.args[0], |
| 1497 | + dq_w_hh.args[1], |
| 1498 | + dq_b_ih.args[0], |
| 1499 | + dq_b_ih.args[1], |
| 1500 | + dq_b_hh.args[0], |
| 1501 | + ) |
| 1502 | + with gm.graph.inserting_after(anchor_node): |
| 1503 | + fused = gm.graph.call_function(self.replacement_op(), args, {}) |
| 1504 | + fused.meta = anchor_node.meta |
| 1505 | + anchor_node.replace_all_uses_with(fused) |
| 1506 | + return fused |
| 1507 | + |
1220 | 1508 |
|
1221 | 1509 | class RmsNormPattern(QuantizationPattern): |
1222 | 1510 | """Pattern that preserves rms_norm from decomposition without matching anything.""" |
|
0 commit comments