|
2 | 2 | import unittest |
3 | 3 |
|
4 | 4 | import torch |
| 5 | +import torch.nn.functional as F |
5 | 6 |
|
6 | 7 | from sglang.srt.layers.attention.triton_ops.decode_attention import ( |
7 | 8 | decode_attention_fwd, |
|
18 | 19 | from sglang.test.test_utils import CustomTestCase |
19 | 20 |
|
20 | 21 |
|
| 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 | + |
21 | 96 | class TestTritonAttention(CustomTestCase): |
22 | 97 |
|
23 | 98 | def _set_all_seeds(self, seed): |
@@ -180,6 +255,115 @@ def test_extend_attention(self): |
180 | 255 | for value in attention_values: |
181 | 256 | self._test_extend_attention_once(19, 12331, 12, 4, value) |
182 | 257 |
|
| 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 | + |
183 | 367 | def _test_context_attention_once(self, head_dim, is_causal): |
184 | 368 | # Set up a simple test case |
185 | 369 | num_heads = 4 |
|
0 commit comments