|
| 1 | +import dataclasses |
1 | 2 | import random |
2 | 3 | from itertools import product |
3 | 4 | from math import ceil |
| 5 | +from typing import List |
4 | 6 |
|
5 | 7 | import pytest |
6 | 8 | import torch |
@@ -1058,3 +1060,188 @@ def test_get_paged_mqa_logits_metadata_benchmark(): |
1058 | 1060 | ) |
1059 | 1061 | bench.set_gems(flag_gems.get_paged_mqa_logits_metadata) |
1060 | 1062 | bench.run() |
| 1063 | + |
| 1064 | + |
| 1065 | +# ---------------------- flashmla_sparse op test ---------------------- |
| 1066 | +try: |
| 1067 | + from vllm.v1.attention.ops.flashmla import ( |
| 1068 | + flash_mla_sparse_fwd as vllm_flash_mla_sparse_fwd, |
| 1069 | + ) |
| 1070 | + |
| 1071 | + HAS_VLLM_FLASHMLA_SPARSE = True |
| 1072 | +except ImportError: |
| 1073 | + HAS_VLLM_FLASHMLA_SPARSE = False |
| 1074 | + |
| 1075 | + |
| 1076 | +@dataclasses.dataclass |
| 1077 | +class Flashmla_Sparse_Test_Param: |
| 1078 | + s_q: int |
| 1079 | + s_kv: int |
| 1080 | + topk: int |
| 1081 | + h_q: int = 128 |
| 1082 | + h_kv: int = 1 |
| 1083 | + d_qk: int = 512 |
| 1084 | + d_v: int = 512 |
| 1085 | + is_all_indices_invalid: bool = False |
| 1086 | + num_warmup: int = 5 |
| 1087 | + num_runs: int = 10 |
| 1088 | + have_attn_sink: bool = False |
| 1089 | + have_topk_length: bool = False |
| 1090 | + dtype: torch.dtype = torch.bfloat16 |
| 1091 | + device: torch.device = flag_gems.device |
| 1092 | + |
| 1093 | + |
| 1094 | +# used by make_input_flashmla |
| 1095 | +_flashmla_sparse_counter = 0 |
| 1096 | + |
| 1097 | + |
| 1098 | +class FlashmlaSparseBenchmark(Benchmark): |
| 1099 | + def __init__(self): |
| 1100 | + super().__init__( |
| 1101 | + "flash_mla_sparse_fwd", vllm_flash_mla_sparse_fwd, [torch.bfloat16] |
| 1102 | + ) |
| 1103 | + self.set_gems(flag_gems.flash_mla_sparse_fwd) |
| 1104 | + |
| 1105 | + def set_shapes(self, shape_file_path=None): |
| 1106 | + self.shapes = [] |
| 1107 | + |
| 1108 | + def get_input_iter(self, cur_dtype): |
| 1109 | + for param in FlashmlaSparseBenchmark.get_performance_test_params_flashmla(): |
| 1110 | + yield from FlashmlaSparseBenchmark.make_input_flashmla(param) |
| 1111 | + |
| 1112 | + @staticmethod |
| 1113 | + def _init_seed(seed): |
| 1114 | + random.seed(seed) |
| 1115 | + torch.manual_seed(seed) |
| 1116 | + |
| 1117 | + @staticmethod |
| 1118 | + def get_performance_test_params_flashmla(): |
| 1119 | + cases = ( |
| 1120 | + [ |
| 1121 | + Flashmla_Sparse_Test_Param( |
| 1122 | + 4096, s_kv, 2048, h_q=128, d_qk=576, have_attn_sink=True |
| 1123 | + ) |
| 1124 | + for s_kv in [8192, 32768, 65536, 98304, 131072] |
| 1125 | + ] |
| 1126 | + + [ |
| 1127 | + Flashmla_Sparse_Test_Param( |
| 1128 | + 4096, s_kv, 512, h_q=64, d_qk=512, have_attn_sink=True |
| 1129 | + ) |
| 1130 | + for s_kv in [8192, 32768, 49152, 65536] |
| 1131 | + ] |
| 1132 | + + [ |
| 1133 | + Flashmla_Sparse_Test_Param( |
| 1134 | + 4096, s_kv, 1024, h_q=128, d_qk=512, have_attn_sink=True |
| 1135 | + ) |
| 1136 | + for s_kv in [8192, 32768, 49152, 65536] |
| 1137 | + ] |
| 1138 | + ) |
| 1139 | + return cases |
| 1140 | + |
| 1141 | + @staticmethod |
| 1142 | + def _randperm_batch( |
| 1143 | + batch_size: int, perm_range: torch.Tensor, perm_size: int, paddings: List[int] |
| 1144 | + ) -> torch.Tensor: |
| 1145 | + """ |
| 1146 | + Generate random permutations in batch |
| 1147 | + The return tensor, denoted as `res`, has a shape of [batch_size, perm_size]. `0 <= res[i, :] < perm_range[i]` |
| 1148 | + holds. |
| 1149 | + Values within each row are unique. |
| 1150 | + If, for some `i`, `perm_range[i] < perm_size` holds, then `res[i, :]` contains values in `[0, perm_range[i])` |
| 1151 | + as many as possible, and the rest are filled with `padding`. |
| 1152 | + """ |
| 1153 | + assert not torch.are_deterministic_algorithms_enabled() |
| 1154 | + torch.use_deterministic_algorithms(True) |
| 1155 | + perm_range_max = max(int(torch.max(perm_range).item()), perm_size) |
| 1156 | + rand = torch.rand(batch_size, perm_range_max, dtype=torch.float32) |
| 1157 | + rand[ |
| 1158 | + torch.arange(0, perm_range_max).broadcast_to(batch_size, perm_range_max) |
| 1159 | + >= perm_range.view(batch_size, 1) |
| 1160 | + ] = float("-inf") |
| 1161 | + res = rand.topk(perm_size, dim=-1, sorted=True).indices.to(torch.int32) |
| 1162 | + if len(paddings) == 1: |
| 1163 | + res[res >= perm_range.view(batch_size, 1)] = paddings[0] |
| 1164 | + else: |
| 1165 | + fillers = torch.tensor(paddings, dtype=torch.int32).index_select( |
| 1166 | + 0, torch.randint(0, len(paddings), (res.numel(),), dtype=torch.int32) |
| 1167 | + ) |
| 1168 | + res.masked_scatter_(res >= perm_range.view(batch_size, 1), fillers) |
| 1169 | + torch.use_deterministic_algorithms(False) |
| 1170 | + return res |
| 1171 | + |
| 1172 | + @staticmethod |
| 1173 | + def make_input_flashmla(param: Flashmla_Sparse_Test_Param): |
| 1174 | + """Create input data for sparse MLA operator by referring to the FlashMLA examples""" |
| 1175 | + s_q = param.s_q |
| 1176 | + s_kv = param.s_kv |
| 1177 | + h_q = param.h_q |
| 1178 | + h_kv = param.h_kv |
| 1179 | + d_qk = param.d_qk |
| 1180 | + topk = param.topk |
| 1181 | + have_attn_sink = param.have_attn_sink |
| 1182 | + have_topk_length = param.have_topk_length |
| 1183 | + is_all_indices_invalid = param.is_all_indices_invalid |
| 1184 | + dtype = param.dtype |
| 1185 | + device = param.device |
| 1186 | + |
| 1187 | + global _flashmla_sparse_counter |
| 1188 | + FlashmlaSparseBenchmark._init_seed(_flashmla_sparse_counter) |
| 1189 | + _flashmla_sparse_counter = _flashmla_sparse_counter + 1 |
| 1190 | + |
| 1191 | + q = ( |
| 1192 | + torch.randn((s_q, h_q, d_qk), dtype=dtype, device=device) / 10 |
| 1193 | + + (random.random() - 0.5) / 10 |
| 1194 | + ) |
| 1195 | + kv = ( |
| 1196 | + torch.randn((s_kv, h_kv, d_qk), dtype=dtype, device=device) / 10 |
| 1197 | + + (random.random() - 0.5) / 10 |
| 1198 | + ) |
| 1199 | + q = q.clamp_(-10, 10) |
| 1200 | + kv = kv.clamp_(-10, 10) |
| 1201 | + invalid_indices_candidate = [ |
| 1202 | + -2147483648, |
| 1203 | + -123456, |
| 1204 | + -1, |
| 1205 | + s_kv, |
| 1206 | + 114514, |
| 1207 | + 1919810, |
| 1208 | + 2147480000, |
| 1209 | + 2147483647, |
| 1210 | + ] |
| 1211 | + indices = FlashmlaSparseBenchmark._randperm_batch( |
| 1212 | + s_q, |
| 1213 | + torch.full((s_q,), s_kv, dtype=torch.int32), |
| 1214 | + topk, |
| 1215 | + invalid_indices_candidate, |
| 1216 | + ).view(s_q, h_kv, topk) |
| 1217 | + if is_all_indices_invalid: |
| 1218 | + all_indices_invalid_mask = torch.randn(s_q, device="cpu") < -2 |
| 1219 | + indices[ |
| 1220 | + all_indices_invalid_mask[:, None, None].broadcast_to(indices.shape) |
| 1221 | + ] = random.choice(invalid_indices_candidate) |
| 1222 | + indices = indices.to(device) |
| 1223 | + |
| 1224 | + attn_sink = None |
| 1225 | + if have_attn_sink: |
| 1226 | + attn_sink = torch.randn((h_q,), dtype=torch.float32, device=device) |
| 1227 | + mask = torch.randn((h_q,), dtype=torch.float32, device=device) |
| 1228 | + attn_sink[mask < -0.5] = float("-inf") |
| 1229 | + attn_sink[mask > +0.5] = float("+inf") |
| 1230 | + |
| 1231 | + topk_length = None |
| 1232 | + if have_topk_length: |
| 1233 | + topk_length = torch.randint( |
| 1234 | + 0, max(topk + 1, 64), (s_q,), dtype=torch.int32, device=device |
| 1235 | + ).clamp_max(topk) |
| 1236 | + yield (q, kv, indices, 0.5, param.d_v, attn_sink, topk_length) |
| 1237 | + |
| 1238 | + |
| 1239 | +@pytest.mark.flashmla_sparse |
| 1240 | +@pytest.mark.performance |
| 1241 | +@pytest.mark.skipif(not HAS_VLLM_FLASHMLA_SPARSE, reason="vllm not installed") |
| 1242 | +def test_perf_flashmla_sparse_gems_vs_vllm(): |
| 1243 | + """ |
| 1244 | + Benchmark FlagGems flash_mla_sparse_fwd vs vLLM flash_mla_sparse_fwd. |
| 1245 | + """ |
| 1246 | + bench = FlashmlaSparseBenchmark() |
| 1247 | + bench.run() |
0 commit comments