perf: precompute kv_indptr once per decode step

- bind_tasks builds kv_indptr (prefix sum of seq_lens) a single time
- fwd_decode/fwd_prefill reuse it instead of rebuilding per layer
- Removes 24 cumsum launches per decode step (was ~1ms/step at B=4)
- Decode B=4: 9.60 -> 7.82 ms/step (-18.5%), +22.8% tok/s
This commit is contained in:
2026-08-01 16:09:26 +08:00
parent a27c8a819d
commit 4b25664c79
2 changed files with 9 additions and 5 deletions
+2 -5
View File
@@ -341,8 +341,7 @@ class CudaBackend(AttentionBackend):
b = q.size(0) b = q.size(0)
q_3d = q.squeeze(1) q_3d = q.squeeze(1)
kv_indptr = torch.zeros(b + 1, dtype=torch.int32, device=q.device) kv_indptr = kv_cache.kv_indptr
kv_indptr[1:] = kv_cache.seq_lens.cumsum(0).to(torch.int32)
out = attn_paged_decode( out = attn_paged_decode(
q_3d, q_3d,
@@ -376,9 +375,7 @@ class CudaBackend(AttentionBackend):
b = q.size(0) b = q.size(0)
q_len = q.size(1) q_len = q.size(1)
kv_indptr = torch.zeros(b + 1, dtype=torch.int32, device=q.device) kv_indptr = kv_cache.kv_indptr
kv_indptr[1:] = kv_cache.seq_lens.cumsum(0).to(torch.int32)
qo_indptr = torch.arange(b + 1, dtype=torch.int32, device=q.device) * q_len qo_indptr = torch.arange(b + 1, dtype=torch.int32, device=q.device) * q_len
q_flat = q.reshape(b * q_len, q.size(2), q.size(3)) q_flat = q.reshape(b * q_len, q.size(2), q.size(3))
+7
View File
@@ -203,6 +203,8 @@ class KVCache:
seq_lens: [batch_size] — per-request total sequence lengths seq_lens: [batch_size] — per-request total sequence lengths
out_cache_loc: [batch, new_seq_len] or [batch, 1] — write indices out_cache_loc: [batch, new_seq_len] or [batch, 1] — write indices
max_len: max(seq_lens) as Python int — avoids GPU sync in decode max_len: max(seq_lens) as Python int — avoids GPU sync in decode
kv_indptr: [batch+1] int32 — prefix sum of seq_lens, precomputed once
per step so the attention backend avoids rebuilding it per layer.
""" """
k_buffer: Tensor k_buffer: Tensor
@@ -212,6 +214,7 @@ class KVCache:
seq_lens: Tensor seq_lens: Tensor
out_cache_loc: Tensor out_cache_loc: Tensor
max_len: int = 0 max_len: int = 0
kv_indptr: Optional[Tensor] = None
class PagePool: class PagePool:
@@ -434,6 +437,9 @@ class PagePool:
req_pool_indices, write_pos req_pool_indices, write_pos
].unsqueeze(-1) ].unsqueeze(-1)
kv_indptr = torch.zeros(len(seq_lens) + 1, dtype=torch.int32, device=device)
kv_indptr[1:] = seq_lens_t.cumsum(0).to(torch.int32)
return KVCache( return KVCache(
k_buffer=self._storage.k_buffer, k_buffer=self._storage.k_buffer,
v_buffer=self._storage.v_buffer, v_buffer=self._storage.v_buffer,
@@ -442,6 +448,7 @@ class PagePool:
seq_lens=seq_lens_t, seq_lens=seq_lens_t,
out_cache_loc=out_cache_loc, out_cache_loc=out_cache_loc,
max_len=max(seq_lens), max_len=max(seq_lens),
kv_indptr=kv_indptr,
) )
# ---- internals ---- # ---- internals ----