main
py 732 lines 30.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import abc
5 from dataclasses import dataclass, replace
6 from typing import Any, ClassVar, TypeVar
7
8 import torch
9
10 from vllm.config import VllmConfig
11 from vllm.config.mamba import MambaBackendEnum
12 from vllm.utils.math_utils import cdiv
13 from vllm.utils.torch_utils import async_tensor_h2d
14 from vllm.v1.attention.backend import (
15 AttentionCGSupport,
16 AttentionMetadataBuilder,
17 CommonAttentionMetadata,
18 )
19 from vllm.v1.attention.backends.utils import (
20 NULL_BLOCK_ID,
21 compute_causal_conv1d_metadata,
22 mamba_get_block_table_tensor,
23 split_decodes_and_prefills,
24 )
25 from vllm.v1.kv_cache_interface import MambaSpec
26
27 M = TypeVar("M", bound="BaseMambaAttentionMetadata")
28
29
30 @dataclass
31 class BaseMambaAttentionMetadata:
32 num_prefills: int
33 num_prefill_tokens: int
34 num_decodes: int
35 num_decode_tokens: int
36 num_reqs: int
37
38 # The following tensors only contain prefill requests and will be None if
39 # the batch has no prefill requests.
40 has_initial_states_p: torch.Tensor | None
41 query_start_loc_p: torch.Tensor | None
42 state_indices_tensor_p: torch.Tensor | None
43
44 # The following tensors are used for decode requests and
45 # speculative decoding compatibility, and will be None if the batch
46 # has no decode requests.
47 state_indices_tensor_d: torch.Tensor | None
48 query_start_loc_d: torch.Tensor | None # shape: [num_decodes + 1,]
49
50 # Number of accepted tokens for each spec sequence (for loading correct checkpoint)
51 # Includes the bonus token (so minimum is 1)
52 num_accepted_tokens: torch.Tensor | None # shape: [batch,]
53
54 # The following tensor is only used for prefix caching in align mode
55 seq_lens: torch.Tensor
56
57 # cu_chunk_seqlen_p is a tensor of shape (nchunks+1,) that contains, for
58 # each chunk, its offsets into the varlen sequence dimension. It is defined
59 # such that the i-th chunk contains tokens from cu_chunk_seqlen_p[i] to
60 # cu_chunk_seqlen_p[i+1].
61 cu_chunk_seqlen_p: torch.Tensor | None = None
62 # last_chunk_indices_p is a tensor of shape (batch,) that contains the
63 # index of the last chunk for every sequence in the (prefill) batch.
64 last_chunk_indices_p: torch.Tensor | None = None
65
66 # The following attributes are for triton implementation of causal_conv1d
67 nums_dict: dict | None = None
68 batch_ptr: torch.Tensor | None = None
69 token_chunk_offset_ptr: torch.Tensor | None = None
70 # ReplaySSM standard decode — Triton: per-row ring cursor and flush flag,
71 # plus (decode_rows, ngroups, replayssm_buffer_len) fp32 scratch for
72 # precomputed B·C products. All None when use_replayssm is disabled or
73 # the FlashInfer ReplaySSM backend is selected.
74 write_pos_d: torch.Tensor | None = None
75 is_flush_d: torch.Tensor | None = None
76 bc_pre_scratch: torch.Tensor | None = None
77 # ReplaySSM — FlashInfer checkpointing_ssu two-kernel scratch.
78 replayssm_scratch: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None
79 # Contiguous cache-slot indices shared by all FlashInfer ReplaySSM layers.
80 replayssm_state_indices_d: torch.Tensor | None = None
81
82
83 class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
84 kv_cache_spec: MambaSpec
85 metadata_cls: type[M]
86 reorder_batch_threshold: int = 1
87 _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH
88
89 # Will be disabled if speculative decoding is used
90 supports_update_block_table: bool = True
91 needs_causal_conv1d_metadata: bool = True
92
93 def __init__(
94 self,
95 kv_cache_spec: MambaSpec,
96 layer_names: list[str],
97 vllm_config: VllmConfig,
98 device: torch.device,
99 ):
100 super().__init__(kv_cache_spec, layer_names, vllm_config, device)
101
102 # Enable speculative decoding support
103 self.speculative_config = vllm_config.speculative_config
104 self.compilation_config = vllm_config.compilation_config
105 self.num_spec_tokens: int = vllm_config.num_speculative_tokens
106 self.use_spec_decode = self.num_spec_tokens > 0
107 self.use_replayssm = vllm_config.cache_config.use_replayssm
108 self.replayssm_buffer_len = vllm_config.cache_config.replayssm_buffer_len
109 self.use_flashinfer_replayssm = (
110 self.use_replayssm
111 and vllm_config.mamba_config.backend == MambaBackendEnum.FLASHINFER
112 )
113
114 scheduler_config = vllm_config.scheduler_config
115 self.decode_cudagraph_max_bs: int = scheduler_config.max_num_seqs
116 if self.compilation_config.max_cudagraph_capture_size is not None:
117 self.decode_cudagraph_max_bs = min(
118 self.decode_cudagraph_max_bs,
119 self.compilation_config.max_cudagraph_capture_size,
120 )
121
122 self.state_indices_tensor_d = torch.empty(
123 (self.decode_cudagraph_max_bs, 1 + self.num_spec_tokens),
124 dtype=torch.int32,
125 device=device,
126 )
127
128 # For speculative decoding, we need to store the following buffers
129 # for CUDA graph capture during decode
130 if self.num_spec_tokens > 0:
131 self.decode_num_accepted_tokens: torch.Tensor = torch.empty(
132 (self.decode_cudagraph_max_bs,),
133 dtype=torch.int32,
134 device=device,
135 )
136 self.decode_bc_pre_scratch: torch.Tensor | None = None
137 self.decode_replayssm_scratch: (
138 tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None
139 ) = None
140 self.decode_replayssm_state_indices_d: torch.Tensor | None = None
141 # ReplaySSM CUDA-graph buffers for the selected backend.
142 if self.use_replayssm and not self.use_flashinfer_replayssm:
143 self.decode_write_pos_d: torch.Tensor = torch.empty(
144 (self.decode_cudagraph_max_bs,),
145 dtype=torch.int32,
146 device=device,
147 )
148 self.decode_is_flush_d: torch.Tensor = torch.empty(
149 (self.decode_cudagraph_max_bs,),
150 dtype=torch.int8,
151 device=device,
152 )
153 # B_cache shape = (ngroups, replayssm_buffer_len, dstate); the page
154 # layout is (conv_state, ssm_state, x_cache, dt_cache, B_cache).
155 bc_ngroups = kv_cache_spec.shapes[4][0]
156 bc_scratch_bs = max(
157 self.decode_cudagraph_max_bs, scheduler_config.max_num_seqs
158 )
159 self.decode_bc_pre_scratch = torch.empty(
160 (
161 bc_scratch_bs,
162 bc_ngroups,
163 self.replayssm_buffer_len,
164 ),
165 dtype=torch.float32,
166 device=device,
167 )
168 elif self.use_flashinfer_replayssm:
169 from flashinfer.mamba.checkpointing_ssu import (
170 allocate_checkpointing_ssu_scratch,
171 )
172
173 nheads = kv_cache_spec.shapes[2][0]
174 self.decode_replayssm_scratch = allocate_checkpointing_ssu_scratch(
175 batch_size=scheduler_config.max_num_seqs,
176 num_heads=nheads,
177 num_predicted_tokens=1 + self.num_spec_tokens,
178 max_window=self.replayssm_buffer_len,
179 dtype=vllm_config.model_config.dtype,
180 device=device,
181 )
182 # Full CUDA graphs retain capture-time tensor addresses. Keep the
183 # contiguous first-column view used by FlashInfer in a persistent
184 # buffer and refresh its contents before each replay.
185 self.decode_replayssm_state_indices_d = torch.empty(
186 (self.decode_cudagraph_max_bs,), dtype=torch.int32, device=device
187 )
188
189 self._init_reorder_batch_threshold(1, self.use_spec_decode)
190 if self.use_spec_decode:
191 self.supports_update_block_table = False
192
193 def build_for_cudagraph_capture(
194 self, common_attn_metadata: CommonAttentionMetadata
195 ) -> M:
196 """This method builds the metadata for full cudagraph capture.
197 Currently, only decode is supported for full cudagraphs with Mamba.
198 """
199 m = common_attn_metadata
200
201 assert (
202 m.max_query_len <= 1 + self.num_spec_tokens
203 and m.num_reqs <= self.decode_cudagraph_max_bs
204 ), (
205 "Mamba only supports decode-only full CUDAGraph capture. "
206 "Make sure all cudagraph capture sizes <= max_num_seq."
207 )
208
209 assert m.max_query_len == 1 + self.num_spec_tokens # decode-only
210
211 num_accepted_tokens = None
212 if self.num_spec_tokens > 0:
213 num_accepted_tokens = torch.diff(m.query_start_loc)
214
215 return self.build(0, m, num_accepted_tokens=num_accepted_tokens)
216
217 def build(
218 self,
219 common_prefix_len: int,
220 common_attn_metadata: CommonAttentionMetadata,
221 fast_build: bool = False,
222 *,
223 num_accepted_tokens: torch.Tensor | None = None,
224 num_decode_draft_tokens_cpu: torch.Tensor | None = None,
225 **kwargs: Any,
226 ) -> M:
227 """Default build implementation for Mamba-like attention backends.
228 Subclasses (e.g., Mamba2) can override to add additional metadata.
229 """
230 return self._compute_common_metadata(
231 common_attn_metadata,
232 num_accepted_tokens=num_accepted_tokens,
233 num_decode_draft_tokens_cpu=num_decode_draft_tokens_cpu,
234 )
235
236 def _compute_chunk_metadata(
237 self,
238 chunk_size: int,
239 num_prefills: int,
240 num_computed_tokens_p_cpu: torch.Tensor,
241 query_start_loc_p_cpu: torch.Tensor,
242 ) -> tuple[list[int], list[int], list[int]]:
243 """Compute chunk-specific metadata for Mamba models.
244
245 The code below carefully constructs the chunks such that:
246 1. Chunks contain tokens from a *single* sequence only.
247 2. For every sequence, we are guaranteed that we can
248 retrieve the mamba state *every* chunk_size tokens.
249 Constraint (1) dramatically simplifies the mamba kernels.
250 Constraint (2) dramatically simplifies the implementation
251 of prefix caching for mamba (wip). We need to take care
252 of the interaction with chunked prefill in order to
253 satisfy constraint (2).
254 """
255 # TODO (tdoublep): This code could probably be optimized.
256 cu_chunk_seqlen = []
257 seq_idx = []
258 last_chunk_indices = []
259 seqlen_pos = 0
260
261 for req_idx in range(num_prefills):
262 this_num_computed = num_computed_tokens_p_cpu[req_idx].item()
263 this_new_tokens = (
264 query_start_loc_p_cpu[req_idx + 1].item()
265 - query_start_loc_p_cpu[req_idx].item()
266 )
267
268 # if computed tokens are not chunk-aligned, use the first
269 # chunk to finish it off
270 if this_num_computed % chunk_size != 0:
271 seq_idx.append(req_idx)
272 cu_chunk_seqlen.append(seqlen_pos)
273 # how many tokens to finish the chunk?
274 chunk_len = (
275 cdiv(this_num_computed, chunk_size) * chunk_size - this_num_computed
276 )
277 # we can only use at most this_new_tokens
278 chunk_len = min(chunk_len, this_new_tokens)
279 seqlen_pos += chunk_len
280 this_new_tokens -= chunk_len
281
282 n_chunks = cdiv(this_new_tokens, chunk_size)
283 for chunk in range(n_chunks):
284 seq_idx.append(req_idx)
285 cu_chunk_seqlen.append(seqlen_pos)
286 chunk_len = min(chunk_size, this_new_tokens)
287 seqlen_pos += chunk_len
288 this_new_tokens -= chunk_len
289
290 assert this_new_tokens == 0
291 last_chunk_indices.append(len(cu_chunk_seqlen) - 1)
292
293 cu_chunk_seqlen.append(seqlen_pos)
294
295 return cu_chunk_seqlen, seq_idx, last_chunk_indices
296
297 def _prefill_cpu_metadata(
298 self,
299 common_attn_metadata: CommonAttentionMetadata,
300 num_reqs: int,
301 num_prefills: int,
302 num_decode_tokens: int,
303 ) -> tuple[torch.Tensor, torch.Tensor]:
304 """Prefill context lengths and query offsets, from CPU data only.
305
306 `seq_lens_cpu_upper_bound` is precise for prefill rows in all modes
307 (including async spec decode), so this avoids the D2H sync that
308 `compute_num_computed_tokens().cpu()` would force.
309
310 Returns (num_computed_tokens_p_cpu, query_start_loc_p_cpu).
311 """
312 seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
313 assert seq_lens_cpu is not None
314 query_start_loc_p_cpu = (
315 common_attn_metadata.query_start_loc_cpu[-num_prefills - 1 :]
316 - num_decode_tokens
317 )
318 prefill_query_lens_cpu = query_start_loc_p_cpu[1:] - query_start_loc_p_cpu[:-1]
319 num_computed_tokens_p_cpu = (
320 seq_lens_cpu[num_reqs - num_prefills : num_reqs] - prefill_query_lens_cpu
321 )
322 return num_computed_tokens_p_cpu, query_start_loc_p_cpu
323
324 def _build_chunk_metadata_tensors(
325 self,
326 chunk_size: int,
327 common: M,
328 common_attn_metadata: CommonAttentionMetadata,
329 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
330 """Compute chunk metadata and return as device tensors.
331 Returns (cu_chunk_seqlen_p, seq_idx_p, last_chunk_indices_p).
332 """
333 num_prefills = common.num_prefills
334
335 num_computed_tokens_p_cpu, query_start_loc_p_cpu = self._prefill_cpu_metadata(
336 common_attn_metadata,
337 common.num_reqs,
338 num_prefills,
339 common.num_decode_tokens,
340 )
341
342 cu_chunk_seqlen, seq_idx, last_chunk_indices = self._compute_chunk_metadata(
343 chunk_size,
344 num_prefills,
345 num_computed_tokens_p_cpu,
346 query_start_loc_p_cpu,
347 )
348
349 device = common_attn_metadata.query_start_loc.device
350 # Build on pinned CPU and upload non-blocking to avoid the synchronous
351 # H2D copy that `torch.as_tensor(list, device=cuda)` would force.
352 cu_chunk_seqlen_p = async_tensor_h2d(
353 cu_chunk_seqlen, dtype=torch.int32, device=device
354 )
355 seq_idx_p = async_tensor_h2d(seq_idx, dtype=torch.int32, device=device)
356 last_chunk_indices_p = async_tensor_h2d(
357 last_chunk_indices, dtype=torch.int32, device=device
358 )
359 return cu_chunk_seqlen_p, seq_idx_p, last_chunk_indices_p
360
361 def _compute_common_metadata(
362 self,
363 common_attn_metadata: CommonAttentionMetadata,
364 *,
365 num_accepted_tokens: torch.Tensor | None = None,
366 num_decode_draft_tokens_cpu: torch.Tensor | None = None,
367 ) -> M:
368 """Compute metadata common to both Mamba1 and Mamba2."""
369 num_reqs = common_attn_metadata.num_reqs
370
371 # Treat multi-token queries as decode requests when
372 # speculative decoding is enabled. Otherwise, use the
373 # default decode threshold to prevent misclassification
374 # of prefill queries as decode requests.
375 decode_threshold = (
376 self.reorder_batch_threshold if num_accepted_tokens is not None else 1
377 )
378
379 # FULL-CG dispatch is shape-based, so one-token prefills with
380 # prior Mamba state can replay a decode graph while `is_prefilling`
381 # is still true. Treat them as decode/update rows. This is required
382 # for NIXL disagg's h(N-1)->N recompute path and for sporadic
383 # final single-token prefill chunks that land in a `uniform` FULL-CG
384 # batch. Relies on `reorder` putting short extends before pure prefills.
385 is_prefilling = common_attn_metadata.is_prefilling
386 assert is_prefilling is not None
387 seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
388 assert seq_lens_cpu is not None
389 query_lens_cpu = torch.diff(common_attn_metadata.query_start_loc_cpu)
390
391 # First prompt chunks have no prior Mamba state and must stay prefills.
392 has_prior_state = seq_lens_cpu > query_lens_cpu
393 stateful_prefill_rows = is_prefilling & has_prior_state
394
395 # One-token prefills with prior state can use the decode/update path.
396 prefill_to_decode = stateful_prefill_rows & (query_lens_cpu == 1)
397
398 # The scheduler may pad a one-token remote prompt tail with placeholder
399 # drafts to retain the uniform K+1 decode graph. This is a speculative
400 # decode transaction even though the real token is still in the prompt:
401 # the decode kernels keep h(N) in the running slot and h(N+i) in scratch
402 # slots, so normal acceptance rollback remains valid. The prefill kernels
403 # only return h(N+K) and cannot roll the placeholders back.
404 if num_decode_draft_tokens_cpu is not None:
405 padded_prompt_tail_rows = (
406 stateful_prefill_rows
407 & (num_decode_draft_tokens_cpu >= 0)
408 & (query_lens_cpu == num_decode_draft_tokens_cpu + 1)
409 )
410 prefill_to_decode |= padded_prompt_tail_rows
411
412 if torch.any(prefill_to_decode).item():
413 # ReplaySSM handles these rows as single-token flushes (see the
414 # write-position derivation below), same as the baseline decode path.
415 is_prefilling = is_prefilling.clone()
416 is_prefilling[prefill_to_decode] = False
417 common_attn_metadata = common_attn_metadata.replace(
418 is_prefilling=is_prefilling
419 )
420
421 num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
422 split_decodes_and_prefills(
423 common_attn_metadata,
424 decode_threshold=decode_threshold,
425 treat_short_extends_as_decodes=False,
426 )
427 )
428
429 # Need flags to indicate if there are initial states
430 has_initial_states_p = None
431 query_start_loc_p = None
432 query_start_loc_d = None
433
434 # for causal_conv1d
435 nums_dict, batch_ptr, token_chunk_offset_ptr = None, None, None
436 write_pos_d = None
437 is_flush_d = None
438 replayssm_scratch = None
439
440 state_indices_tensor = mamba_get_block_table_tensor(
441 common_attn_metadata.block_table_tensor,
442 common_attn_metadata.seq_lens,
443 self.kv_cache_spec,
444 self.vllm_config.cache_config.mamba_cache_mode,
445 )
446
447 if state_indices_tensor.dim() == 1:
448 state_indices_tensor = state_indices_tensor.unsqueeze(-1)
449
450 state_indices_tensor_d, state_indices_tensor_p = torch.split(
451 state_indices_tensor,
452 [num_decodes, num_prefills],
453 dim=0,
454 )
455 state_indices_tensor_d = state_indices_tensor_d[:, : 1 + self.num_spec_tokens]
456 state_indices_tensor_p = state_indices_tensor_p[:, 0]
457
458 if num_decodes > 0 and self.use_spec_decode:
459 query_start_loc_d = common_attn_metadata.query_start_loc[: num_decodes + 1]
460 if num_accepted_tokens is None:
461 # Single-token prefill chunks can be reclassified as decodes before
462 # speculative decoding has produced acceptance counts. Treat each
463 # token as accepted so recurrent state and ReplaySSM trackers follow
464 # the normal speculative-decode path.
465 num_accepted_tokens = torch.diff(query_start_loc_d)
466 else:
467 num_accepted_tokens = num_accepted_tokens[:num_decodes]
468
469 if num_prefills > 0:
470 num_computed_tokens = common_attn_metadata.compute_num_computed_tokens()
471
472 query_start_loc_p = (
473 common_attn_metadata.query_start_loc[-num_prefills - 1 :]
474 - num_decode_tokens
475 )
476 has_initial_states_p = (
477 num_computed_tokens[num_reqs - num_prefills : num_reqs] > 0
478 )
479
480 if self.needs_causal_conv1d_metadata:
481 query_start_loc_p_cpu = (
482 common_attn_metadata.query_start_loc_cpu[-num_prefills - 1 :]
483 - num_decode_tokens
484 )
485 nums_dict, batch_ptr, token_chunk_offset_ptr = (
486 compute_causal_conv1d_metadata(
487 query_start_loc_p_cpu,
488 device=common_attn_metadata.query_start_loc.device,
489 )
490 )
491
492 if self.use_replayssm and not self.use_flashinfer_replayssm and num_decodes > 0:
493 decode_base_cpu = common_attn_metadata.replayssm_decode_base_cpu
494 seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
495 async_spec_decode = (
496 self.vllm_config.scheduler_config.async_scheduling
497 and self.vllm_config.speculative_config is not None
498 )
499 if decode_base_cpu is None or seq_lens_cpu is None or async_spec_decode:
500 raise ValueError(
501 "--use-replayssm requires exact CPU sequence lengths and "
502 "decode-base counts to derive decode write positions"
503 )
504 query_lens_cpu = (
505 common_attn_metadata.query_start_loc_cpu[1 : num_decodes + 1]
506 - common_attn_metadata.query_start_loc_cpu[:num_decodes]
507 )
508 num_computed_d = seq_lens_cpu[:num_decodes] - query_lens_cpu
509 decode_base_d = decode_base_cpu[:num_decodes]
510 align_mode = self.vllm_config.cache_config.mamba_cache_mode == "align"
511 block_size = self.kv_cache_spec.block_size
512 if align_mode:
513 # After a boundary the align copy leaves an exact checkpoint at
514 # the block start and the new block's ring restarts empty, so
515 # re-anchor there; max() keeps the prompt-end anchor for the
516 # first (partial) block.
517 effective_base = torch.maximum(
518 decode_base_d, (num_computed_d // block_size) * block_size
519 )
520 else:
521 effective_base = decode_base_d
522 # write_pos counts decode steps since the ring's last full-state
523 # write (the anchor), so a resumed request re-anchors correctly.
524 decode_steps_cpu = num_computed_d - effective_base
525 valid_decode_rows = query_lens_cpu > 0
526 # A single-token prefill row replayed as decode (query_len==1 with
527 # prior state) has decode_steps < 0; force it to a one-token flush
528 # (write_pos=0, is_flush=1). The flush branch reads an empty history
529 # window, so it applies exactly one recurrence step off the checkpoint
530 # -- identical to the baseline decode kernel for that row. The split
531 # (treat_short_extends_as_decodes=False) admits only such rows here.
532 leftover_prompt = valid_decode_rows & (decode_steps_cpu < 0)
533 decode_steps_cpu = torch.where(
534 valid_decode_rows & ~leftover_prompt,
535 decode_steps_cpu,
536 torch.zeros_like(decode_steps_cpu),
537 )
538 write_pos_cpu = torch.remainder(decode_steps_cpu, self.replayssm_buffer_len)
539 is_flush_cpu = (
540 write_pos_cpu == self.replayssm_buffer_len - 1
541 ) | leftover_prompt
542 if align_mode:
543 # Force a flush on the step completing a mamba block so the exact
544 # boundary state is materialized for prefix caching.
545 is_flush_cpu = is_flush_cpu | (
546 valid_decode_rows
547 & ((num_computed_d + query_lens_cpu) % block_size == 0)
548 )
549 is_flush_cpu = is_flush_cpu.to(torch.int8)
550 write_pos_d = async_tensor_h2d(
551 write_pos_cpu.to(torch.int32).tolist(),
552 dtype=torch.int32,
553 device=common_attn_metadata.query_start_loc.device,
554 )
555 is_flush_d = async_tensor_h2d(
556 is_flush_cpu.tolist(),
557 dtype=torch.int8,
558 device=common_attn_metadata.query_start_loc.device,
559 )
560
561 if self.use_flashinfer_replayssm and num_decodes > 0:
562 assert self.decode_replayssm_scratch is not None
563 cb_scaled, cumAdt_vec, cb_old = self.decode_replayssm_scratch
564 replayssm_scratch = (
565 cb_scaled[:num_decodes],
566 cumAdt_vec[:num_decodes],
567 cb_old[:num_decodes],
568 )
569
570 bc_pre_scratch = None
571 if (
572 self.use_replayssm
573 and self.decode_bc_pre_scratch is not None
574 and num_decodes > 0
575 ):
576 bc_pre_scratch = self.decode_bc_pre_scratch[:num_decodes]
577
578 metadata = self.metadata_cls(
579 num_prefills=num_prefills,
580 num_prefill_tokens=num_prefill_tokens,
581 num_decodes=num_decodes,
582 num_decode_tokens=num_decode_tokens,
583 query_start_loc_p=query_start_loc_p,
584 has_initial_states_p=has_initial_states_p,
585 state_indices_tensor_p=state_indices_tensor_p,
586 state_indices_tensor_d=state_indices_tensor_d,
587 write_pos_d=write_pos_d,
588 is_flush_d=is_flush_d,
589 bc_pre_scratch=bc_pre_scratch,
590 replayssm_scratch=replayssm_scratch,
591 num_accepted_tokens=num_accepted_tokens,
592 query_start_loc_d=query_start_loc_d,
593 num_reqs=num_reqs,
594 seq_lens=common_attn_metadata.seq_lens,
595 nums_dict=nums_dict,
596 batch_ptr=batch_ptr,
597 token_chunk_offset_ptr=token_chunk_offset_ptr,
598 )
599
600 return self._update_metadata_for_cudagraph_capture(metadata)
601
602 def _update_metadata_for_cudagraph_capture(
603 self,
604 metadata: M,
605 ) -> M:
606 """Update the metadata for cudagraph capture.
607 Currently, only decode is supported for full cudagraphs with Mamba.
608 """
609 state_indices_tensor_d = metadata.state_indices_tensor_d
610 query_start_loc_d = metadata.query_start_loc_d
611 num_accepted_tokens = metadata.num_accepted_tokens
612 write_pos_d = metadata.write_pos_d
613 is_flush_d = metadata.is_flush_d
614 bc_pre_scratch = metadata.bc_pre_scratch
615 replayssm_scratch = metadata.replayssm_scratch
616 replayssm_state_indices_d = None
617 if (
618 metadata.num_prefills == 0
619 and metadata.num_decodes <= self.decode_cudagraph_max_bs
620 and self.compilation_config.cudagraph_mode.has_full_cudagraphs()
621 ):
622 padded_bs = metadata.num_reqs
623 self.state_indices_tensor_d[: metadata.num_decodes].copy_(
624 state_indices_tensor_d, non_blocking=True
625 )
626 state_indices_tensor_d = self.state_indices_tensor_d[:padded_bs]
627 state_indices_tensor_d[metadata.num_decodes :] = NULL_BLOCK_ID
628
629 if self.use_spec_decode and num_accepted_tokens is not None:
630 assert query_start_loc_d is not None
631 query_start_loc_d = query_start_loc_d[: padded_bs + 1]
632 self.decode_num_accepted_tokens[: metadata.num_decodes].copy_(
633 num_accepted_tokens, non_blocking=True
634 )
635 num_accepted_tokens = self.decode_num_accepted_tokens[:padded_bs]
636 num_accepted_tokens[metadata.num_decodes :] = (
637 1 # pad with 1st slot index
638 )
639
640 if self.use_replayssm and not self.use_flashinfer_replayssm:
641 assert write_pos_d is not None
642 assert is_flush_d is not None
643 self.decode_write_pos_d[: metadata.num_decodes].copy_(
644 write_pos_d[: metadata.num_decodes],
645 non_blocking=True,
646 )
647 write_pos_d = self.decode_write_pos_d[:padded_bs]
648 write_pos_d[metadata.num_decodes :] = 0
649
650 self.decode_is_flush_d[: metadata.num_decodes].copy_(
651 is_flush_d[: metadata.num_decodes],
652 non_blocking=True,
653 )
654 is_flush_d = self.decode_is_flush_d[:padded_bs]
655 is_flush_d[metadata.num_decodes :] = 0
656
657 if self.decode_bc_pre_scratch is not None:
658 bc_pre_scratch = self.decode_bc_pre_scratch[:padded_bs]
659 elif self.use_flashinfer_replayssm:
660 assert self.decode_replayssm_scratch is not None
661 cb_scaled, cumAdt_vec, cb_old = self.decode_replayssm_scratch
662 replayssm_scratch = (
663 cb_scaled[:padded_bs],
664 cumAdt_vec[:padded_bs],
665 cb_old[:padded_bs],
666 )
667 assert self.decode_replayssm_state_indices_d is not None
668 self.decode_replayssm_state_indices_d[:padded_bs].copy_(
669 state_indices_tensor_d[:, 0], non_blocking=True
670 )
671 replayssm_state_indices_d = self.decode_replayssm_state_indices_d[
672 :padded_bs
673 ]
674
675 if (
676 self.use_flashinfer_replayssm
677 and state_indices_tensor_d is not None
678 and replayssm_state_indices_d is None
679 ):
680 replayssm_state_indices_d = state_indices_tensor_d[:, 0].contiguous()
681
682 return replace(
683 metadata,
684 state_indices_tensor_d=state_indices_tensor_d,
685 query_start_loc_d=query_start_loc_d,
686 num_accepted_tokens=num_accepted_tokens,
687 write_pos_d=write_pos_d,
688 is_flush_d=is_flush_d,
689 bc_pre_scratch=bc_pre_scratch,
690 replayssm_scratch=replayssm_scratch,
691 replayssm_state_indices_d=replayssm_state_indices_d,
692 )
693
694 def update_block_table(
695 self,
696 metadata: M,
697 blk_table: torch.Tensor,
698 slot_mapping: torch.Tensor,
699 ) -> M:
700 state_indices_tensor = mamba_get_block_table_tensor(
701 blk_table,
702 metadata.seq_lens,
703 self.kv_cache_spec,
704 self.vllm_config.cache_config.mamba_cache_mode,
705 )
706 if state_indices_tensor.dim() == 1:
707 state_indices_tensor = state_indices_tensor.unsqueeze(-1)
708
709 assert (
710 metadata.num_prefills + metadata.num_decodes
711 == state_indices_tensor.shape[0]
712 ), (
713 "Mismatch in number of requests when updating block table."
714 f" Expected {metadata.num_prefills + metadata.num_decodes}, "
715 f"got {state_indices_tensor.shape[0]}."
716 )
717
718 state_indices_tensor_d, state_indices_tensor_p = torch.split(
719 state_indices_tensor,
720 [metadata.num_decodes, metadata.num_prefills],
721 dim=0,
722 )
723 state_indices_tensor_d = state_indices_tensor_d[:, : 1 + self.num_spec_tokens]
724 state_indices_tensor_p = state_indices_tensor_p[:, 0]
725
726 new_metadata = replace(
727 metadata,
728 state_indices_tensor_d=state_indices_tensor_d,
729 state_indices_tensor_p=state_indices_tensor_p,
730 )
731
732 return self._update_metadata_for_cudagraph_capture(new_metadata)