Skip to content

Commit 4fc5f2f

Browse files
authored
Add unit test for triton swa kernel (sgl-project#8853)
1 parent 168033d commit 4fc5f2f

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

test/srt/test_triton_attention_kernels.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33

44
import torch
5+
import torch.nn.functional as F
56

67
from sglang.srt.layers.attention.triton_ops.decode_attention import (
78
decode_attention_fwd,
@@ -18,6 +19,80 @@
1819
from sglang.test.test_utils import CustomTestCase
1920

2021

22+
def extend_attention_fwd_torch(
23+
q: torch.Tensor, # [extend_tokens, H_Q, D]
24+
k: torch.Tensor, # [extend_tokens, H_KV, D]
25+
v: torch.Tensor, # [extend_tokens, H_KV, D]
26+
o: torch.Tensor, # [extend_tokens, H_Q, D]
27+
k_cache: torch.Tensor, # [total_tokens, H_KV, D]
28+
v_cache: torch.Tensor, # [total_tokens, H_KV, D]
29+
qo_indptr: torch.Tensor, # [B+1]
30+
kv_indptr: torch.Tensor, # [B+1]
31+
kv_indices: torch.Tensor, # [prefix_tokens]
32+
sliding_window_size: int,
33+
):
34+
B = qo_indptr.size(0) - 1
35+
_, H_Q, D = q.shape
36+
_, H_KV, _ = k.shape
37+
38+
group_size = H_Q // H_KV
39+
scale = 1.0 / D**0.5
40+
41+
for i in range(B):
42+
q_start = int(qo_indptr[i].item())
43+
q_end = int(qo_indptr[i + 1].item())
44+
kv_start = int(kv_indptr[i].item())
45+
kv_end = int(kv_indptr[i + 1].item())
46+
47+
prefix_indices = kv_indices[kv_start:kv_end]
48+
k_prefix = k_cache[prefix_indices] # [prefix_len, H_KV, D]
49+
v_prefix = v_cache[prefix_indices] # [prefix_len, H_KV, D]
50+
51+
k_extend = k[q_start:q_end] # [extend_len, H_KV, D]
52+
v_extend = v[q_start:q_end] # [extend_len, H_KV, D]
53+
q_extend = q[q_start:q_end] # [extend_len, H_Q, D]
54+
55+
k_full = torch.cat([k_prefix, k_extend], dim=0) # [total_len, H_KV, D]
56+
v_full = torch.cat([v_prefix, v_extend], dim=0) # [total_len, H_KV, D]
57+
58+
if group_size != 1:
59+
k_full_hq = k_full.repeat_interleave(
60+
group_size, dim=1
61+
) # [total_len, H_Q, D]
62+
v_full_hq = v_full.repeat_interleave(
63+
group_size, dim=1
64+
) # [total_len, H_Q, D]
65+
else:
66+
k_full_hq = k_full
67+
v_full_hq = v_full
68+
69+
prefix_len = k_prefix.size(0)
70+
extend_len = k_extend.size(0)
71+
total_len = prefix_len + extend_len
72+
73+
# causal
74+
pos_keys = torch.arange(total_len, device=q.device)
75+
t = prefix_len + torch.arange(extend_len, device=q.device) # [extend_len]
76+
causal_mask = pos_keys.unsqueeze(0) <= t.unsqueeze(1)
77+
78+
# sliding window
79+
if sliding_window_size is not None and sliding_window_size > 0:
80+
start = (t - (sliding_window_size)).clamp_min(0) # [extend_len]
81+
else:
82+
start = torch.zeros_like(t)
83+
window_mask = pos_keys.unsqueeze(0) >= start.unsqueeze(1)
84+
85+
final_mask = causal_mask & window_mask
86+
87+
attn_scores = (
88+
torch.einsum("qhd,khd->qhk", q_extend, k_full_hq) * scale
89+
) # [extend_len, H_Q, total_len]
90+
attn_scores = attn_scores.masked_fill(~final_mask.unsqueeze(1), float("-inf"))
91+
92+
attn_weights = F.softmax(attn_scores, dim=-1)
93+
o[q_start:q_end] = torch.einsum("qhk,khd->qhd", attn_weights, v_full_hq)
94+
95+
2196
class TestTritonAttention(CustomTestCase):
2297

2398
def _set_all_seeds(self, seed):
@@ -180,6 +255,115 @@ def test_extend_attention(self):
180255
for value in attention_values:
181256
self._test_extend_attention_once(19, 12331, 12, 4, value)
182257

258+
def _test_extend_attention_sliding_window_once(
259+
self, B, N_CTX, H_Q, H_KV, D, WINDOW_SIZE
260+
):
261+
dtype = torch.bfloat16
262+
263+
b_seq_len_prefix = torch.randint(
264+
1, N_CTX // 2, (B,), dtype=torch.int32, device="cuda"
265+
)
266+
b_seq_len_extend = torch.randint(
267+
1, N_CTX // 2, (B,), dtype=torch.int32, device="cuda"
268+
)
269+
b_seq_len = b_seq_len_prefix + b_seq_len_extend
270+
271+
b_start_loc = torch.zeros((B,), dtype=torch.int32, device="cuda")
272+
b_start_loc[1:] = torch.cumsum(b_seq_len[:-1], 0)
273+
b_start_loc_extend = torch.zeros((B,), dtype=torch.int32, device="cuda")
274+
b_start_loc_extend[1:] = torch.cumsum(b_seq_len_extend[:-1], 0)
275+
276+
kv_indptr = torch.zeros((B + 1,), dtype=torch.int32, device="cuda")
277+
kv_indptr[1 : B + 1] = torch.cumsum(b_seq_len_prefix[:B], dim=0)
278+
kv_indices = torch.zeros(
279+
(b_seq_len_prefix.sum().item(),), dtype=torch.int32, device="cuda"
280+
)
281+
282+
for i in range(B):
283+
kv_indices[kv_indptr[i] : kv_indptr[i + 1]] = torch.arange(
284+
b_start_loc[i], b_start_loc[i] + b_seq_len_prefix[i]
285+
)
286+
287+
total_token_num = torch.sum(b_seq_len).item()
288+
extend_token_num = torch.sum(b_seq_len_extend).item()
289+
k_buffer = torch.empty(
290+
(total_token_num, H_KV, D), dtype=dtype, device="cuda"
291+
).normal_(mean=0.1, std=0.2)
292+
v_buffer = torch.empty(
293+
(total_token_num, H_KV, D), dtype=dtype, device="cuda"
294+
).normal_(mean=0.1, std=0.2)
295+
296+
k_extend = torch.empty((extend_token_num, H_KV, D), dtype=dtype, device="cuda")
297+
v_extend = torch.empty((extend_token_num, H_KV, D), dtype=dtype, device="cuda")
298+
q_extend = torch.empty((extend_token_num, H_Q, D), dtype=dtype, device="cuda")
299+
for i in range(B):
300+
extend_start_in_buffer = b_start_loc[i] + b_seq_len_prefix[i]
301+
extend_end_in_buffer = b_start_loc[i] + b_seq_len[i]
302+
extend_start = b_start_loc_extend[i]
303+
extend_end = b_start_loc_extend[i] + b_seq_len_extend[i]
304+
k_extend[extend_start:extend_end] = k_buffer[
305+
extend_start_in_buffer:extend_end_in_buffer
306+
]
307+
v_extend[extend_start:extend_end] = v_buffer[
308+
extend_start_in_buffer:extend_end_in_buffer
309+
]
310+
q_extend[extend_start:extend_end] = torch.empty(
311+
(b_seq_len_extend[i], H_Q, D), dtype=dtype, device="cuda"
312+
).normal_(mean=0.1, std=0.2)
313+
314+
o_extend_triton = torch.empty(
315+
(extend_token_num, H_Q, D), dtype=dtype, device="cuda"
316+
)
317+
o_extend_torch = torch.empty(
318+
(extend_token_num, H_Q, D), dtype=dtype, device="cuda"
319+
)
320+
321+
b_seq_len_extend = b_seq_len - b_seq_len_prefix
322+
max_len_extend = torch.max(b_seq_len_extend, 0)[0].item()
323+
qo_indptr = torch.zeros((B + 1,), dtype=torch.int32, device="cuda")
324+
qo_indptr[1 : B + 1] = torch.cumsum(b_seq_len_extend[:B], dim=0)
325+
326+
extend_attention_fwd(
327+
q_extend,
328+
k_extend,
329+
v_extend,
330+
o_extend_triton,
331+
k_buffer,
332+
v_buffer,
333+
qo_indptr,
334+
kv_indptr,
335+
kv_indices,
336+
custom_mask=None,
337+
is_causal=True,
338+
mask_indptr=None,
339+
max_len_extend=max_len_extend,
340+
sliding_window_size=WINDOW_SIZE,
341+
)
342+
343+
extend_attention_fwd_torch(
344+
q_extend,
345+
k_extend,
346+
v_extend,
347+
o_extend_torch,
348+
k_buffer,
349+
v_buffer,
350+
qo_indptr,
351+
kv_indptr,
352+
kv_indices,
353+
WINDOW_SIZE,
354+
)
355+
356+
self.assertTrue(
357+
torch.allclose(o_extend_triton, o_extend_torch, rtol=1e-3, atol=1e-3)
358+
)
359+
360+
def test_extend_attention_sliding_window(self):
361+
window_sizes = [-1, 127]
362+
for window_size in window_sizes:
363+
self._test_extend_attention_sliding_window_once(
364+
19, 12331, 64, 8, 128, window_size
365+
)
366+
183367
def _test_context_attention_once(self, head_dim, is_causal):
184368
# Set up a simple test case
185369
num_heads = 4

0 commit comments

Comments
 (0)