@@ -24,6 +24,76 @@ def set_more_shapes(self):
2424 return None
2525
2626
27+ #
28+ # sparse_attention shape layout:
29+ # (batch, seq_len, kv_len, topk, heads, dim)
30+ #
31+ SPARSE_ATTENTION_SHAPES = [
32+ (16 , 1 , 136 , 136 , 8 , 512 ),
33+ (16 , 1 , 392 , 385 , 8 , 512 ),
34+ (16 , 1 , 392 , 386 , 8 , 512 ),
35+ (16 , 1 , 392 , 387 , 8 , 512 ),
36+ (32 , 1 , 392 , 388 , 8 , 512 ),
37+ (32 , 1 , 392 , 389 , 8 , 512 ),
38+ (32 , 1 , 392 , 390 , 8 , 512 ),
39+ (32 , 1 , 392 , 391 , 8 , 512 ),
40+ (64 , 1 , 136 , 136 , 8 , 512 ),
41+ (64 , 1 , 392 , 385 , 8 , 512 ),
42+ (64 , 1 , 392 , 388 , 8 , 512 ),
43+ (64 , 1 , 392 , 389 , 8 , 512 ),
44+ ]
45+
46+
47+ def torch_sparse_attention (q , kv , attn_sink , topk_idxs , softmax_scale ):
48+ batch , seq_len , heads , dim = q .shape
49+ topk = topk_idxs .shape [- 1 ]
50+
51+ kv_expanded = kv [:, None , :, :].expand (batch , seq_len , - 1 , dim )
52+ idx_expanded = topk_idxs [:, :, :, None ].expand (batch , seq_len , topk , dim ).long ()
53+ gathered_kv = torch .gather (kv_expanded , 2 , idx_expanded )
54+
55+ scores = (
56+ torch .einsum ("bmhd,bmtd->bmht" , q .float (), gathered_kv .float ()) * softmax_scale
57+ )
58+ sink = attn_sink [None , None , :, None ].expand (batch , seq_len , heads , 1 )
59+ attn = torch .softmax (torch .cat ([scores , sink ], dim = - 1 ), dim = - 1 )
60+
61+ out = torch .einsum ("bmht,bmtd->bmhd" , attn [:, :, :, :- 1 ], gathered_kv .float ())
62+ return out .to (q .dtype )
63+
64+
65+ class SparseAttentionBenchmark (Benchmark ):
66+ def set_shapes (self , shape_file_path = None ):
67+ self .shapes = SPARSE_ATTENTION_SHAPES [:]
68+ self .shape_desc = "B, M, KV_LEN, TOPK, H, D"
69+
70+ def set_more_shapes (self ):
71+ return None
72+
73+ def get_input_iter (self , cur_dtype ):
74+ for seed , (batch , seq_len , kv_len , topk , heads , dim ) in enumerate (self .shapes ):
75+ torch .manual_seed (2026 + seed )
76+ q = torch .randn (
77+ (batch , seq_len , heads , dim ),
78+ dtype = cur_dtype ,
79+ device = self .device ,
80+ )
81+ kv = torch .randn (
82+ (batch , kv_len , dim ),
83+ dtype = cur_dtype ,
84+ device = self .device ,
85+ )
86+ attn_sink = torch .zeros ((heads ,), dtype = torch .float32 , device = self .device )
87+ topk_idxs = torch .randint (
88+ 0 ,
89+ kv_len ,
90+ (batch , seq_len , topk ),
91+ dtype = torch .int32 ,
92+ device = self .device ,
93+ )
94+ yield q , kv , attn_sink , topk_idxs , 1.0 / math .sqrt (dim )
95+
96+
2797def torch_flash_attention_forward (
2898 q , k , v , scale , is_causal , dropout_p = 0.0 , return_debug_mask = False , ** extra_kwargs
2999):
@@ -315,6 +385,18 @@ def sdpa_flash(
315385 del os .environ ["TRITON_HIP_USE_NEW_STREAM_PIPELINE" ]
316386
317387
388+ @pytest .mark .skipif (flag_gems .device == "cpu" , reason = "Unsupported in CPU mode" )
389+ @pytest .mark .sparse_attention
390+ def test_perf_sparse_attention ():
391+ bench = SparseAttentionBenchmark (
392+ op_name = "sparse_attention" ,
393+ torch_op = torch_sparse_attention ,
394+ dtypes = [torch .bfloat16 ],
395+ )
396+ bench .set_gems (flag_gems .sparse_attn_triton )
397+ bench .run ()
398+
399+
318400class FlashMLABenchmark (GenericBenchmark ):
319401 """
320402 benchmark for flash_mla
0 commit comments