Skip to content

Commit 9a2c39d

Browse files
Wong4jrosenrodt
authored andcommitted
fix mamba metadata prefill bubble in chunked prefill serving
Three optimizations to eliminate GPU idle bubbles during prefill in Mamba2Metadata.prepare() for hybrid GDN models (e.g. Qwen3.5): 1. Remove tl.constexpr from num_seqs and N in _cu_seqlens_triton_kernel. Triton JIT recompiles for each unique constexpr value (~120ms each). In serving, num_seqs varies every prefill step, causing repeated recompilation. With dynamic parameters, only one compilation occurs. 2. Accept total_seqlens from caller to skip first GPU->CPU sync. cu_seqlens[-1].item() blocked on all pending GPU work. The caller (Mamba2Metadata.prepare) already has num_ctx_tokens on CPU. 3. Compute extra_chunks with pure Python arithmetic on CPU seq_lens to eliminate the second GPU->CPU sync (cumsum + p[-1].item()). Before: _prepare_inputs ~120-460ms per prefill step (Triton recompile + GPU sync bubbles) After: _prepare_inputs ~1-2ms steady state Verified: 9200+ random equivalence tests + e2e serving assertion with 1000 requests (0 mismatches). GSM8K accuracy unchanged (90.07% on full 1319 samples). Signed-off-by: Shijie Wang <jaywan@nvidia.com>
1 parent 0b5c0e1 commit 9a2c39d

1 file changed

Lines changed: 35 additions & 10 deletions

File tree

tensorrt_llm/_torch/modules/mamba/mamba2_metadata.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ def _cu_seqlens_triton_kernel(
3333
cu_seqlens_ptr, # [num_seqs + 1]
3434
chunk_indices_ptr, # [N] output
3535
chunk_offsets_ptr, # [N] output
36-
num_seqs: tl.constexpr,
36+
num_seqs,
3737
chunk_size: tl.constexpr,
38-
N: tl.constexpr,
38+
N,
3939
BLOCK_SIZE: tl.constexpr,
4040
):
4141
"""Computes chunk_indices and chunk_offsets in a single kernel launch."""
@@ -67,8 +67,18 @@ def _cu_seqlens_triton_kernel(
6767

6868
def cu_seqlens_to_chunk_indices_offsets_triton(
6969
cu_seqlens: torch.Tensor,
70-
chunk_size: int) -> Tuple[torch.Tensor, torch.Tensor]:
71-
"""Optimized version of cu_seqlens_to_chunk_indices_offsets."""
70+
chunk_size: int,
71+
total_seqlens: int = -1,
72+
extra_chunks: int = -1) -> Tuple[torch.Tensor, torch.Tensor]:
73+
"""Optimized version of cu_seqlens_to_chunk_indices_offsets.
74+
75+
Args:
76+
total_seqlens: If provided (>= 0), avoids a GPU->CPU sync to read
77+
cu_seqlens[-1]. Callers that already know the total number of
78+
context tokens should pass it here.
79+
extra_chunks: If provided (>= 0), avoids a GPU->CPU sync to compute
80+
the number of extra chunks from misaligned sequence boundaries.
81+
"""
7282
device = cu_seqlens.device
7383
num_seqs = cu_seqlens.numel() - 1
7484

@@ -77,18 +87,20 @@ def cu_seqlens_to_chunk_indices_offsets_triton(
7787
torch.empty(0, dtype=torch.int, device=device))
7888

7989
cu = cu_seqlens.to(dtype=torch.int64)
80-
total_seqlens = cu[-1].item()
90+
if total_seqlens < 0:
91+
total_seqlens = cu[-1].item()
8192

8293
if num_seqs == 1:
8394
# Fast path for single sequence (no boundaries to process)
8495
N = (total_seqlens + chunk_size - 1) // chunk_size
8596
return (torch.arange(N, device=device, dtype=torch.int),
8697
torch.zeros(N, device=device, dtype=torch.int))
8798

88-
seq_starts = cu[1:-1]
89-
misaligned = ((seq_starts % chunk_size) > 0).to(torch.int64)
90-
p = torch.cumsum(misaligned, dim=0)
91-
extra_chunks = p[-1].item() if p.numel() > 0 else 0
99+
if extra_chunks < 0:
100+
seq_starts = cu[1:-1]
101+
misaligned = ((seq_starts % chunk_size) > 0).to(torch.int64)
102+
p = torch.cumsum(misaligned, dim=0)
103+
extra_chunks = p[-1].item() if p.numel() > 0 else 0
92104
N = (total_seqlens + chunk_size - 1) // chunk_size + extra_chunks
93105
chunk_indices = torch.empty(N, device=device, dtype=torch.int)
94106
chunk_offsets = torch.empty(N, device=device, dtype=torch.int)
@@ -282,8 +294,21 @@ def prepare(self, attn_metadata: AttentionMetadata):
282294
self.has_initial_states_cpu[:num_contexts].any())
283295

284296
if self.use_initial_states:
297+
# Compute extra_chunks using pure Python arithmetic on CPU
298+
# seq_lens to avoid any GPU->CPU sync point.
299+
_cs = self.chunk_size
300+
_cumsum = 0
301+
_extra = 0
302+
for i in range(num_contexts - 1):
303+
_cumsum += int(attn_metadata.seq_lens[i])
304+
if _cumsum % _cs != 0:
305+
_extra += 1
306+
285307
self.chunk_indices, self.chunk_offsets = cu_seqlens_to_chunk_indices_offsets_triton(
286-
self.cu_seqlens[:num_contexts + 1], self.chunk_size)
308+
self.cu_seqlens[:num_contexts + 1],
309+
self.chunk_size,
310+
total_seqlens=num_ctx_tokens,
311+
extra_chunks=_extra)
287312
else:
288313
self.chunk_indices = None
289314
self.chunk_offsets = None

0 commit comments

Comments
 (0)