Skip to content

Commit 6c599cf

Browse files
authored
[KMCompiler] Add flash_mla_sparse_fwd based on sparse mla. (flagos-ai#2255)
1 parent fad021d commit 6c599cf

4 files changed

Lines changed: 920 additions & 1 deletion

File tree

benchmark/test_vllm_perf.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import dataclasses
12
import random
23
from itertools import product
34
from math import ceil
5+
from typing import List
46

57
import pytest
68
import torch
@@ -1058,3 +1060,188 @@ def test_get_paged_mqa_logits_metadata_benchmark():
10581060
)
10591061
bench.set_gems(flag_gems.get_paged_mqa_logits_metadata)
10601062
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()

src/flag_gems/fused/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
fused_recurrent_gated_delta_rule_fwd,
99
)
1010
from flag_gems.fused.flash_mla import flash_mla
11+
from flag_gems.fused.flashmla_sparse import flash_mla_sparse_fwd
1112
from flag_gems.fused.fused_add_rms_norm import fused_add_rms_norm
1213
from flag_gems.fused.fused_moe import (
1314
dispatch_fused_moe_kernel,
@@ -51,6 +52,7 @@
5152
"dreglu",
5253
"dswiglu",
5354
"flash_mla",
55+
"flash_mla_sparse_fwd",
5456
"fused_add_rms_norm",
5557
"fused_experts_impl",
5658
"fused_recurrent_gated_delta_rule_fwd",

0 commit comments

Comments
 (0)