main
py 741 lines 20.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import torch
5 from einops import rearrange
6
7 from vllm.platforms import current_platform
8 from vllm.triton_utils import tl, triton
9 from vllm.v1.attention.backends.utils import PAD_SLOT_ID
10
11
12 @triton.jit
13 def _fwd_diag_kernel(
14 Q,
15 K,
16 V,
17 Out,
18 S,
19 b: tl.constexpr,
20 h: tl.constexpr,
21 n,
22 d: tl.constexpr,
23 e: tl.constexpr,
24 BLOCK: tl.constexpr,
25 NUM_BLOCK,
26 CBLOCK: tl.constexpr,
27 ):
28 # This kernel computes the diagonal blocks of the attention matrix
29 # Each diagonal block represents attention
30 # where queries attend to keys in the same block
31 off = tl.program_id(0)
32 off_bh = off // NUM_BLOCK # batch-head index
33 off_block = off % NUM_BLOCK # block index within the sequence
34 off_cblock = tl.program_id(1) # sub-block index within a block
35
36 off_h = off_bh % h # head index
37
38 # Calculate base offsets for the current batch and head
39 qk_offset = off_bh * n * d
40 v_offset = off_bh * n * e
41 o_offset = off_bh * n * e
42
43 # Calculate offsets for the current block
44 block_offset = off_block * BLOCK
45 qk_block_offset = block_offset * d
46 v_block_offset = block_offset * e
47 o_block_offset = block_offset * e
48
49 # Calculate offsets for the current sub-block
50 cblock_offset = off_cblock * CBLOCK
51 q_cblock_offset = cblock_offset * d
52 o_cblock_offset = cblock_offset * e
53
54 # Calculate pointers to the query, key, value, and output tensors
55 Q_block_ptr = (
56 Q
57 + qk_offset
58 + qk_block_offset
59 + q_cblock_offset
60 + tl.arange(0, CBLOCK)[:, None] * d
61 + tl.arange(0, d)[None, :]
62 )
63 K_trans_block_ptr = (
64 K
65 + qk_offset
66 + qk_block_offset
67 + tl.arange(0, CBLOCK)[None, :] * d
68 + tl.arange(0, d)[:, None]
69 )
70 V_block_ptr = (
71 V
72 + v_offset
73 + v_block_offset
74 + tl.arange(0, CBLOCK)[:, None] * e
75 + tl.arange(0, e)[None, :]
76 )
77 O_block_ptr = (
78 Out
79 + o_offset
80 + o_block_offset
81 + o_cblock_offset
82 + tl.arange(0, CBLOCK)[:, None] * e
83 + tl.arange(0, e)[None, :]
84 )
85
86 # Load the decay rate for the current head
87 S_block_ptr = S + off_h
88 s = tl.load(S_block_ptr)
89
90 i = off_cblock
91 q_index = tl.arange(0, CBLOCK) + i * CBLOCK
92
93 # Load query values
94 q = tl.load(Q_block_ptr, mask=block_offset + q_index[:, None] < n, other=0.0).to(
95 tl.float32
96 )
97
98 # Initialize output accumulator
99 qkv = tl.zeros([CBLOCK, e], dtype=tl.float32)
100
101 # Process all sub-blocks up to and
102 # including the current one (causal attention)
103 for j in range(i + 1):
104 kv_index = tl.arange(0, CBLOCK) + j * CBLOCK
105 diff = q_index[:, None] - kv_index[None, :]
106 s_index = s * diff
107 # Apply causal mask: only attend to positions before the current one
108 s_index = tl.where(diff >= 0, -s_index, float("-inf"))
109 decay = tl.exp(s_index)
110
111 # Load key and value
112 k_trans = tl.load(
113 K_trans_block_ptr,
114 mask=block_offset + kv_index[None, :] < n,
115 other=0.0,
116 ).to(tl.float32)
117 v = tl.load(
118 V_block_ptr,
119 mask=block_offset + kv_index[:, None] < n,
120 other=0.0,
121 ).to(tl.float32)
122
123 # Compute attention scores and apply decay
124 qk = tl.dot(q, k_trans) * decay
125
126 # Compute weighted values and accumulate
127 qkv += tl.dot(qk, v)
128
129 # Move to the next sub-block
130 K_trans_block_ptr += CBLOCK * d
131 V_block_ptr += CBLOCK * e
132
133 # Store the result
134 tl.store(
135 O_block_ptr,
136 qkv.to(O_block_ptr.dtype.element_ty),
137 mask=block_offset + q_index[:, None] < n,
138 )
139
140
141 @triton.jit
142 def _fwd_kv_parallel(
143 K,
144 V,
145 K_decay,
146 KV,
147 b: tl.constexpr,
148 h: tl.constexpr,
149 n,
150 d: tl.constexpr,
151 e: tl.constexpr,
152 BLOCK: tl.constexpr,
153 NUM_BLOCK,
154 D_FBLOCK: tl.constexpr,
155 E_FBLOCK: tl.constexpr,
156 NUM_FBLOCK: tl.constexpr,
157 CBLOCK: tl.constexpr,
158 NUM_CBLOCK: tl.constexpr,
159 ):
160 # This kernel computes the key-value outer
161 # products for each block in parallel
162 off_bh = tl.program_id(0) # batch-head index
163 off_block = tl.program_id(1) # block index
164
165 off_h = off_bh % h # head index
166
167 block_offset = off_block * BLOCK
168
169 # Calculate offsets for the current block
170 k_block_offset = block_offset * d
171 v_block_offset = block_offset * e
172 kv_block_offset = off_block * d * e
173
174 # Calculate base offsets for the current batch and head
175 k_offset = off_bh * n * d
176 v_offset = off_bh * n * e
177 kv_offset = off_bh * NUM_BLOCK * d * e
178
179 # Calculate pointers to the key, value, and key-value tensors
180 K_trans_block_ptr = (
181 K
182 + k_offset
183 + k_block_offset
184 + tl.arange(0, CBLOCK)[None, :] * d
185 + tl.arange(0, D_FBLOCK)[:, None]
186 )
187 V_block_ptr = (
188 V
189 + v_offset
190 + v_block_offset
191 + tl.arange(0, CBLOCK)[:, None] * e
192 + tl.arange(0, E_FBLOCK)[None, :]
193 )
194 KV_block_ptr = (
195 KV
196 + kv_offset
197 + kv_block_offset
198 + tl.arange(0, D_FBLOCK)[:, None] * e
199 + tl.arange(0, E_FBLOCK)[None, :]
200 )
201
202 # Load the decay factors for the current head and block
203 k_decay_ptr = K_decay + off_h * BLOCK + tl.arange(0, CBLOCK)
204
205 kv_index = tl.arange(0, CBLOCK)
206
207 # Initialize the key-value outer product accumulator
208 kv = tl.zeros([D_FBLOCK, E_FBLOCK], dtype=tl.float32)
209
210 # Handle the last block which might be smaller than BLOCK
211 split_n = n - (NUM_BLOCK - 1) * BLOCK if off_block == NUM_BLOCK - 1 else BLOCK
212 left_shift = tl.cdiv(split_n, CBLOCK) * CBLOCK - split_n
213 num_blocks = min(tl.cdiv(split_n, CBLOCK), NUM_CBLOCK)
214 k_decay_ptr += (NUM_CBLOCK - num_blocks) * CBLOCK
215
216 # Process all sub-blocks in the current block
217 for j in range(num_blocks):
218 left_bound = (1 - j) * left_shift
219 # Load key and value, handling boundary conditions
220 k_trans = tl.load(
221 K_trans_block_ptr - left_shift * d,
222 mask=kv_index[None, :] >= left_bound,
223 other=0.0,
224 )
225 v = tl.load(
226 V_block_ptr - left_shift * e,
227 mask=kv_index[:, None] >= left_bound,
228 other=0.0,
229 )
230
231 # Load decay factor and compute weighted key-value outer product
232 k_decay = tl.load(k_decay_ptr)
233
234 # NOTE: Need to add the extra dim here due to AMD MLIR lowering error.
235 # Please don't move it back until issue is resolved.
236 # Issue: https://github.com/ROCm/triton/issues/907
237 k_decay = k_decay[None, :]
238
239 kv += tl.dot(k_trans * k_decay, v)
240
241 # Move to the next sub-block
242 K_trans_block_ptr += CBLOCK * d
243 V_block_ptr += CBLOCK * e
244 k_decay_ptr += CBLOCK
245
246 # Store the result
247 tl.store(KV_block_ptr, kv.to(KV_block_ptr.dtype.element_ty))
248
249
250 @triton.jit
251 def _fwd_kv_reduce(
252 S,
253 KV,
254 KV_HISTORY,
255 b: tl.constexpr,
256 h: tl.constexpr,
257 n,
258 d: tl.constexpr,
259 e: tl.constexpr,
260 BLOCK: tl.constexpr,
261 NUM_BLOCK,
262 D_FBLOCK: tl.constexpr,
263 E_FBLOCK: tl.constexpr,
264 ):
265 # This kernel reduces the key-value outer products
266 # across blocks and updates the KV history
267 off_bh = tl.program_id(0) # batch-head index
268 off_h = off_bh % h # head index
269
270 kv_offset = off_bh * NUM_BLOCK * d * e
271
272 # Calculate pointer to the key-value tensor
273 KV_block_ptr = (
274 KV
275 + kv_offset
276 + tl.arange(0, D_FBLOCK)[:, None] * e
277 + tl.arange(0, E_FBLOCK)[None, :]
278 )
279
280 # Load the decay rate for the current head
281 s_ptrs = S + off_h
282 s = tl.load(s_ptrs)
283
284 # Calculate pointer to the key-value history tensor
285 kv_history_offset = off_bh * d * e
286 KV_HISTORY_block_ptr = (
287 KV_HISTORY
288 + kv_history_offset
289 + tl.arange(0, D_FBLOCK)[:, None] * e
290 + tl.arange(0, E_FBLOCK)[None, :]
291 )
292
293 # Load the previous key-value history
294 kv_pre = tl.load(KV_HISTORY_block_ptr).to(tl.float32)
295
296 # Process all blocks in reverse order to compute the prefix sum
297 for i in range(NUM_BLOCK):
298 block_size = min(n - i * BLOCK, BLOCK)
299 # Compute decay factor for the current block
300 block_decay = tl.exp(-s.to(tl.float32) * block_size)
301
302 # Load the current key-value outer product
303 kv_cur = tl.load(KV_block_ptr).to(tl.float32)
304 # Store the previous key-value history to the current block
305 tl.store(KV_block_ptr, kv_pre.to(KV_block_ptr.dtype.element_ty))
306
307 # Update the key-value history with the current block
308 kv_pre = block_decay * kv_pre + kv_cur
309 KV_block_ptr += d * e
310
311 # Store the updated key-value history
312 tl.store(KV_HISTORY_block_ptr, kv_pre)
313
314
315 @triton.jit
316 def _fwd_none_diag_kernel(
317 Q,
318 Out,
319 S,
320 KV,
321 b: tl.constexpr,
322 h: tl.constexpr,
323 n,
324 d: tl.constexpr,
325 e: tl.constexpr,
326 BLOCK: tl.constexpr,
327 NUM_BLOCK,
328 E_FBLOCK: tl.constexpr,
329 CBLOCK: tl.constexpr,
330 NUM_CBLOCK: tl.constexpr,
331 ):
332 # This kernel computes the non-diagonal blocks of the attention matrix
333 # Each non-diagonal block represents attention
334 # where queries attend to keys in different blocks
335 off_bh = tl.program_id(0) # batch-head index
336 off_h = off_bh % h # head index
337
338 off_nc = tl.program_id(1)
339 off_n = off_nc // NUM_CBLOCK # block index
340 off_c = off_nc % NUM_CBLOCK # sub-block index
341 off_e = tl.program_id(2) # output feature block index
342
343 n_offset = off_n * BLOCK
344 c_offset = off_c * CBLOCK
345 e_offset = off_e * E_FBLOCK
346 block_offset = n_offset + c_offset
347
348 # Calculate offsets for the current batch, head, and block
349 q_offset = off_bh * n * d + (n_offset + c_offset) * d
350 o_offset = off_bh * n * e + (n_offset + c_offset) * e + e_offset
351 kv_offset = off_bh * NUM_BLOCK * d * e + off_n * d * e + e_offset
352
353 # Calculate pointers to the query, output, and key-value tensors
354 Q_block_ptr = (
355 Q + q_offset + tl.arange(0, CBLOCK)[:, None] * d + tl.arange(0, d)[None, :]
356 )
357 O_block_ptr = (
358 Out
359 + o_offset
360 + tl.arange(0, CBLOCK)[:, None] * e
361 + tl.arange(0, E_FBLOCK)[None, :]
362 )
363 KV_block_ptr = (
364 KV + kv_offset + tl.arange(0, d)[:, None] * e + tl.arange(0, E_FBLOCK)[None, :]
365 )
366
367 # Load the decay rate for the current head
368 S_block_ptr = S + off_h
369 s = tl.load(S_block_ptr)
370
371 c_array = tl.arange(0, CBLOCK)
372
373 # Load the key-value outer product for the current block
374 kv = tl.load(KV_block_ptr).to(tl.float32)
375 q_index = block_offset + tl.arange(0, CBLOCK)
376
377 # Load query values
378 q = tl.load(Q_block_ptr, mask=q_index[:, None] < n, other=0.0).to(tl.float32)
379
380 # Compute decay factors for the current sub-block
381 q_decay = tl.exp(-s.to(tl.float32) * (off_c * CBLOCK + c_array[:, None]))
382
383 # Compute non-diagonal attention output
384 qkv_none_diag = tl.dot(q, kv) * q_decay
385
386 # Load diagonal attention output (computed by _fwd_diag_kernel)
387 qkv_diag = tl.load(O_block_ptr, mask=q_index[:, None] < n, other=0.0).to(tl.float32)
388
389 # Combine diagonal and non-diagonal attention outputs
390 qkv = qkv_diag + qkv_none_diag
391
392 # Store the result
393 tl.store(
394 O_block_ptr, qkv.to(O_block_ptr.dtype.element_ty), mask=q_index[:, None] < n
395 )
396
397
398 class _attention(torch.autograd.Function):
399 @staticmethod
400 def forward(ctx, q, k, v, s, kv_history):
401 # Forward pass of the lightning attention algorithm
402 q = q.contiguous()
403 k = k.contiguous()
404 v = v.contiguous()
405 s = s.contiguous()
406
407 # Check CUDA compute capability (Ampere+ required for flash attention
408 # path). Other accelerators (ROCm, XPU) rely on their own Triton
409 # backend support and skip this check.
410 if current_platform.is_cuda():
411 capability = torch.cuda.get_device_capability()
412 if capability[0] < 8:
413 raise RuntimeError(
414 "Flash attention currently only supported",
415 "for compute capability >= 80",
416 )
417
418 # Get input dimensions
419 b, h, n, d = q.shape
420 e = v.shape[-1]
421
422 # Initialize output tensor
423 o = torch.empty((b, h, n, e), dtype=q.dtype, device=q.device)
424
425 # Set block sizes
426 BLOCK = 256
427 NUM_BLOCK = triton.cdiv(n, BLOCK)
428
429 CBLOCK = 32
430 NUM_CBLOCK = BLOCK // CBLOCK
431 assert BLOCK % CBLOCK == 0, "BLOCK must be a multiple of CBLOCK"
432
433 # Compute decay factors for keys
434 array = torch.arange(0, BLOCK, device=q.device) + 1
435 k_decay = torch.exp(-s * (BLOCK - array.reshape(1, -1)))
436
437 # Step 1: Compute diagonal blocks of attention
438 grid = (b * h * NUM_BLOCK, NUM_CBLOCK)
439 _fwd_diag_kernel[grid](
440 q,
441 k,
442 v,
443 o,
444 s,
445 b,
446 h,
447 n,
448 d,
449 e,
450 BLOCK=BLOCK,
451 NUM_BLOCK=NUM_BLOCK,
452 CBLOCK=CBLOCK,
453 )
454
455 # Set feature block sizes
456 NUM_FBLOCK = 1
457 D_FBLOCK = d // NUM_FBLOCK
458 assert d % NUM_FBLOCK == 0
459 E_FBLOCK = e // NUM_FBLOCK
460 assert e % NUM_FBLOCK == 0
461
462 CBLOCK = 64
463 NUM_CBLOCK = BLOCK // CBLOCK
464 assert BLOCK % CBLOCK == 0, "BLOCK must be a multiple of CBLOCK"
465
466 # Step 2: Compute key-value outer products for each block in parallel
467 kv = torch.empty((b, h, NUM_BLOCK, d, e), dtype=torch.float32, device=q.device)
468 grid = (b * h, NUM_BLOCK)
469 _fwd_kv_parallel[grid](
470 k,
471 v,
472 k_decay,
473 kv,
474 b,
475 h,
476 n,
477 d,
478 e,
479 BLOCK=BLOCK,
480 NUM_BLOCK=NUM_BLOCK,
481 D_FBLOCK=D_FBLOCK,
482 E_FBLOCK=E_FBLOCK,
483 NUM_FBLOCK=NUM_FBLOCK,
484 CBLOCK=CBLOCK,
485 NUM_CBLOCK=NUM_CBLOCK,
486 )
487
488 # Step 3: Reduce key-value outer products
489 # across blocks and update KV history
490 grid = (b * h, NUM_FBLOCK)
491 _fwd_kv_reduce[grid](
492 s,
493 kv,
494 kv_history,
495 b,
496 h,
497 n,
498 d,
499 e,
500 BLOCK=BLOCK,
501 NUM_BLOCK=NUM_BLOCK,
502 D_FBLOCK=D_FBLOCK,
503 E_FBLOCK=E_FBLOCK,
504 )
505
506 # Step 4: Compute non-diagonal blocks of attention
507 grid = (b * h, NUM_BLOCK * NUM_CBLOCK)
508 _fwd_none_diag_kernel[grid](
509 q,
510 o,
511 s,
512 kv,
513 b,
514 h,
515 n,
516 d,
517 e,
518 BLOCK=BLOCK,
519 NUM_BLOCK=NUM_BLOCK,
520 E_FBLOCK=E_FBLOCK,
521 CBLOCK=CBLOCK,
522 NUM_CBLOCK=NUM_CBLOCK,
523 )
524
525 # Save tensors for backward pass
526 ctx.save_for_backward(q, k, v, s, kv)
527 ctx.BLOCK = BLOCK
528
529 return o, torch.cat([kv, kv_history.unsqueeze(2)], dim=2)
530
531
532 # Apply the lightning attention function
533 lightning_attention_ = _attention.apply
534
535
536 def lightning_attention(
537 q: torch.Tensor,
538 k: torch.Tensor,
539 v: torch.Tensor,
540 ed: torch.Tensor,
541 block_size: int = 256,
542 kv_history: torch.Tensor | None = None,
543 ) -> tuple[torch.Tensor, torch.Tensor]:
544 """Apply lightning attention algorithm
545 to compute attention efficiently.
546
547 Args:
548 q: Query tensor of shape [batch, heads, seq_len, dim]
549 k: Key tensor of shape [batch, heads, seq_len, dim]
550 v: Value tensor of shape [batch, heads, seq_len, dim_v]
551 ed: Decay rate tensor of shape [heads]
552 block_size: Size of blocks for block-sparse attention
553 kv_history: Optional key-value history from previous computations
554
555 Returns:
556 output: Attention output
557 kv: Updated key-value history
558
559 """
560 d = q.shape[-1]
561 e = v.shape[-1]
562
563 if ed.dim() == 1:
564 ed = ed.view(1, -1, 1, 1)
565
566 # Split the computation into chunks for better parallelism
567 m = 128 if d >= 128 else 64
568 assert d % m == 0, f"Dimension d ({d}) must be divisible by m ({m})"
569 arr = [m * i for i in range(d // m + 1)]
570 if arr[-1] != d:
571 arr.append(d)
572 n = len(arr)
573 output = 0
574
575 # Initialize or clone key-value history
576 if kv_history is None:
577 kv_history = torch.zeros(
578 (q.shape[0], q.shape[1], d, e), dtype=torch.float32, device=q.device
579 )
580 else:
581 kv_history = kv_history.clone().contiguous()
582
583 # Process each chunk and accumulate results
584 for i in range(n - 1):
585 s = arr[i]
586 e = arr[i + 1]
587 q1 = q[..., s:e]
588 k1 = k[..., s:e]
589 o, kv = lightning_attention_(q1, k1, v, ed, kv_history)
590 output = output + o
591 return output, kv
592
593
594 @triton.jit
595 def _linear_attn_decode_kernel(
596 q_ptr,
597 k_ptr,
598 v_ptr,
599 kv_cache_ptr,
600 slope_rate,
601 slot_idx,
602 output_ptr,
603 D: tl.constexpr,
604 qkv_b_stride,
605 qkv_h_stride,
606 cache_b_stride,
607 cache_h_stride,
608 cache_d0_stride,
609 cache_d1_stride,
610 pad_slot_id: tl.constexpr,
611 BLOCK_SIZE: tl.constexpr,
612 ):
613 """Kernel for linear attention decoding with KV cache.
614
615 This kernel computes attention for a single token using the KV cache.
616 """
617 pid_b = tl.program_id(0) # batch index
618 pid_h = tl.program_id(1) # head index
619 pid_d = tl.program_id(2) # dimension block index
620
621 # Load slot index for the current batch
622 slot_id = tl.load(slot_idx + pid_b).to(tl.int64)
623
624 # Skip if slot_id is PAD_SLOT_ID (padding)
625 if slot_id == pad_slot_id:
626 return
627
628 batch_id = pid_b
629 head_id = pid_h
630
631 # Load decay rate for the current head
632 ratio = tl.load(slope_rate + pid_h)
633
634 # Calculate offsets for dimensions
635 qk_d_offsets = tl.arange(0, D)
636 v_d_offsets = tl.arange(0, BLOCK_SIZE) + pid_d * BLOCK_SIZE
637 cache_d_offsets = (
638 qk_d_offsets[:, None] * cache_d0_stride + v_d_offsets[None, :] * cache_d1_stride
639 )
640
641 # Calculate offsets for the current batch and head
642 q_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
643 k_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
644 v_offset = batch_id * qkv_b_stride + head_id * qkv_h_stride
645
646 cache_offset = slot_id * cache_b_stride + head_id * cache_h_stride
647
648 # Create masks for loading tensors
649 qk_mask = qk_d_offsets < D
650 v_mask = v_d_offsets < D
651
652 # Load query, key, and value tensors
653 q = tl.load(q_ptr + q_offset + qk_d_offsets, mask=qk_mask, other=0.0)
654 k = tl.load(k_ptr + k_offset + qk_d_offsets, mask=qk_mask, other=0.0)
655 v = tl.load(v_ptr + v_offset + v_d_offsets, mask=v_mask, other=0.0)
656
657 # Compute key-value outer product
658 kv_outer = k[:, None] * v[None, :]
659 kv_mask = qk_mask[:, None] & v_mask[None, :]
660
661 # Apply decay to previous KV cache
662 ratio = tl.exp(-ratio)
663 kv_ptr = kv_cache_ptr + cache_offset + cache_d_offsets
664 kv_cache_old = tl.load(kv_ptr, mask=kv_mask, other=0.0)
665 kv_outer = kv_outer + ratio * kv_cache_old
666
667 # Compute attention output
668 output = q[:, None].to(tl.float32) * kv_outer
669 output = tl.sum(output, axis=0)
670
671 # Update KV cache and store output
672 tl.store(kv_ptr, kv_outer, mask=kv_mask)
673 tl.store(output_ptr + q_offset + v_d_offsets, output, mask=v_mask)
674
675
676 def linear_decode_forward_triton(
677 q: torch.Tensor,
678 k: torch.Tensor,
679 v: torch.Tensor,
680 kv_caches: torch.Tensor,
681 slope_rate: torch.Tensor,
682 slot_idx: torch.Tensor,
683 BLOCK_SIZE: int = 32,
684 ) -> torch.Tensor:
685 """Perform linear attention decoding using Triton kernels.
686
687 Args:
688 q: Query tensor of shape [B, H, 1, D]
689 k: Key tensor of shape [B, H, 1, D]
690 v: Value tensor of shape [B, H, 1, D]
691 kv_caches: Key-value cache tensor
692 slope_rate: Decay rate tensor
693 slot_idx: Slot indices for batches
694 BLOCK_SIZE: Size of blocks for processing
695
696 Returns:
697 output: Attention output tensor
698
699 """
700 B, H, _, D = q.shape
701 assert k.shape == (B, H, 1, D)
702 assert v.shape == (B, H, 1, D)
703
704 # Initialize output tensor
705 output = torch.empty_like(q)
706
707 # Set grid dimensions for the kernel
708 grid = (B, H, D // BLOCK_SIZE)
709
710 # Calculate strides for tensors
711 qkv_b_stride = q.stride(0)
712 qkv_h_stride = q.stride(1)
713
714 cache_b_stride = kv_caches.stride(0)
715 cache_h_stride = kv_caches.stride(1)
716 cache_d0_stride = kv_caches.stride(2)
717 cache_d1_stride = kv_caches.stride(3)
718
719 # Launch the kernel
720 _linear_attn_decode_kernel[grid](
721 q,
722 k,
723 v,
724 kv_caches,
725 slope_rate,
726 slot_idx,
727 output,
728 D,
729 qkv_b_stride,
730 qkv_h_stride,
731 cache_b_stride,
732 cache_h_stride,
733 cache_d0_stride,
734 cache_d1_stride,
735 pad_slot_id=PAD_SLOT_ID,
736 BLOCK_SIZE=BLOCK_SIZE,
737 )
738
739 # Reshape output and return
740 output = rearrange(output, "b h n d -> b n (h d)")
741 return output.squeeze(1).contiguous()