[Core] Remove deprecated mamba_cache_mode "all" (#58997)

Signed-off-by: zjy0516 <riverclouds.zhu@qq.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Thomas Parnell <tpa@zurich.ibm.com>

Jiangyun Zhu committed Oct 1, 2026 at 17:59 UTC cb6458c5ce61632524c0447d49790725c1e1d9a2
45 files changed +201 -1476
tests/kernels/mamba/test_cpu_short_conv.py
-10
@@ -88,11 +88,6 @@ def test_short_conv_forward_native_prefill(vllm_config):
88 state_indices_tensor_d=torch.empty((0, 1), dtype=torch.int32),
89 num_accepted_tokens=None,
90 query_start_loc_d=None,
91 - block_idx_last_scheduled_token=None,
92 - block_idx_first_scheduled_token_p=None,
93 - block_idx_last_computed_token=None,
94 - block_idx_last_scheduled_token_prev_step=None,
95 - num_computed_tokens_p=None,
91 seq_lens=torch.tensor([5]),
92 )
93
@@ -145,11 +140,6 @@ def test_short_conv_forward_native_decode(vllm_config):
140 state_indices_tensor_d=state_indices_tensor_d,
141 num_accepted_tokens=None,
142 query_start_loc_d=torch.tensor([0, 1, 2], dtype=torch.int32),
148 - block_idx_last_scheduled_token=None,
149 - block_idx_first_scheduled_token_p=None,
150 - block_idx_last_computed_token=None,
151 - block_idx_last_scheduled_token_prev_step=None,
152 - num_computed_tokens_p=None,
143 seq_lens=torch.tensor([1, 1]),
144 )
145
tests/kernels/mamba/test_ssu_dispatch.py
-5
@@ -568,15 +568,10 @@ def test_replayssm_mixer_selects_mtp_layout(
568 num_reqs=2,
569 has_initial_states_p=None,
570 query_start_loc_p=None,
571 - num_computed_tokens_p=None,
571 state_indices_tensor_p=None,
572 state_indices_tensor_d=torch.tensor([[1], [2]], dtype=torch.int32),
573 query_start_loc_d=query_start_loc_d,
574 num_accepted_tokens=torch.tensor([4, 2], dtype=torch.int32),
576 - block_idx_last_scheduled_token=None,
577 - block_idx_first_scheduled_token_p=None,
578 - block_idx_last_computed_token=None,
579 - block_idx_last_scheduled_token_prev_step=None,
575 seq_lens=torch.tensor([104, 102], dtype=torch.int32),
576 replayssm_scratch=(torch.empty(0), torch.empty(0), torch.empty(0)),
577 replayssm_state_indices_d=torch.tensor([1, 2], dtype=torch.int32),
tests/test_config.py
+6 -3
@@ -270,9 +270,6 @@ def test_kda_recoverssm_derivation_is_revalidated():
270 with pytest.raises(ValueError, match="VLLM_USE_V2_MODEL_RUNNER=1"):
271 VllmConfig.validate_mamba_cached_kernel(config)
272 config.use_v2_model_runner = True
273 - config.cache_config.mamba_cache_mode = "all"
274 - with pytest.raises(ValueError, match="only none and align"):
275 - VllmConfig.validate_mamba_cached_kernel(config)
273 config.cache_config.mamba_cache_mode = "none"
274
275 config.model_config.architecture = "NemotronHForCausalLM"
@@ -286,6 +283,12 @@ def test_kda_recoverssm_derivation_is_revalidated():
283 VllmConfig.validate_mamba_cached_kernel(config)
284
285
286 +def test_mamba_cache_mode_all_is_rejected():
287 + """The removed 'all' mode must fail validation instead of being ignored."""
288 + with pytest.raises(ValidationError, match="mamba_cache_mode"):
289 + CacheConfig(mamba_cache_mode="all")
290 +
291 +
292 def test_per_request_spec_decode_metrics_requires_spec_decode():
293 # The flag only makes sense with speculative decoding configured; enabling
294 # it without --speculative-config should fail fast rather than silently
tests/v1/attention/test_mamba_metadata_builder.py new
+58
@@ -0,0 +1,58 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Tests for BaseMambaAttentionMetadataBuilder batch classification."""
4 +
5 +from types import SimpleNamespace
6 +
7 +from tests.v1.attention.utils import MockMambaBuilder
8 +from vllm.config.compilation import CUDAGraphMode
9 +
10 +
11 +def _make_vllm_config(
12 + max_model_len: int,
13 + max_num_seqs: int,
14 + num_speculative_tokens: int = 0,
15 + block_size: int | None = None,
16 +):
17 + """Create a minimal mock VllmConfig with only the fields the builder
18 + accesses, avoiding any model download / HF config inspection."""
19 + speculative_config = (
20 + SimpleNamespace(
21 + num_speculative_tokens=num_speculative_tokens,
22 + parallel_drafting=False,
23 + )
24 + if num_speculative_tokens > 0
25 + else None
26 + )
27 + return SimpleNamespace(
28 + cache_config=SimpleNamespace(
29 + block_size=block_size,
30 + mamba_cache_mode="none",
31 + use_replayssm=False,
32 + replayssm_buffer_len=16,
33 + ),
34 + compilation_config=SimpleNamespace(
35 + cudagraph_mode=CUDAGraphMode.FULL,
36 + max_cudagraph_capture_size=None,
37 + ),
38 + speculative_config=speculative_config,
39 + num_speculative_tokens=num_speculative_tokens,
40 + parallel_config=SimpleNamespace(decode_context_parallel_size=1),
41 + scheduler_config=SimpleNamespace(max_num_seqs=max_num_seqs),
42 + model_config=SimpleNamespace(max_model_len=max_model_len),
43 + )
44 +
45 +
46 +def test_mamba_single_token_prompt_runs_as_prefill():
47 + seq_lens = [8, 9, 1]
48 + config = _make_vllm_config(256, len(seq_lens), block_size=16)
49 + metadata = MockMambaBuilder.build_mamba_metadata(
50 + config,
51 + seq_lens=seq_lens,
52 + query_lens=[1] * len(seq_lens),
53 + is_prefilling=[False, False, True],
54 + )
55 +
56 + assert metadata.num_decodes == 2
57 + assert metadata.num_prefills == 1
58 + assert metadata.has_initial_states_p.tolist() == [False]
tests/v1/attention/test_mamba_update_block_table.py deleted
-466
@@ -1,466 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""Regression test for https://github.com/vllm-project/vllm/issues/34865.
4 -
5 -When multiple KV cache groups share the same MambaSpec (as in Nemotron
6 -hybrid models), the metadata caching optimization reuses metadata from
7 -an earlier group via update_block_table(). In 'all' mode with CUDA graphs,
8 -update_block_table() must copy block_idx_last_scheduled_token and
9 -block_idx_last_computed_token to the *current* builder's persistent
10 -buffers, otherwise CUDA graph replay reads stale values from uninitialized
11 -buffers.
12 -"""
13 -
14 -from types import SimpleNamespace
15 -
16 -import torch
17 -
18 -from tests.v1.attention.utils import MockMambaBuilder
19 -from vllm.config.compilation import CUDAGraphMode
20 -from vllm.v1.attention.backends.mamba_attn import BaseMambaAttentionMetadata
21 -from vllm.v1.kv_cache_interface import MambaSpec
22 -
23 -
24 -def _make_vllm_config(
25 - max_model_len: int,
26 - max_num_seqs: int,
27 - num_speculative_tokens: int = 0,
28 - block_size: int | None = None,
29 -):
30 - """Create a minimal mock VllmConfig with only the fields the builder
31 - accesses, avoiding any model download / HF config inspection."""
32 - speculative_config = (
33 - SimpleNamespace(
34 - num_speculative_tokens=num_speculative_tokens,
35 - parallel_drafting=False,
36 - )
37 - if num_speculative_tokens > 0
38 - else None
39 - )
40 - return SimpleNamespace(
41 - cache_config=SimpleNamespace(
42 - block_size=block_size,
43 - mamba_cache_mode="all",
44 - use_replayssm=False,
45 - replayssm_buffer_len=16,
46 - ),
47 - compilation_config=SimpleNamespace(
48 - cudagraph_mode=CUDAGraphMode.FULL,
49 - max_cudagraph_capture_size=None,
50 - ),
51 - speculative_config=speculative_config,
52 - num_speculative_tokens=num_speculative_tokens,
53 - parallel_config=SimpleNamespace(decode_context_parallel_size=1),
54 - scheduler_config=SimpleNamespace(max_num_seqs=max_num_seqs),
55 - model_config=SimpleNamespace(max_model_len=max_model_len),
56 - )
57 -
58 -
59 -def test_mamba_single_token_prompt_runs_as_prefill():
60 - seq_lens = [8, 9, 1]
61 - config = _make_vllm_config(256, len(seq_lens), block_size=16)
62 - metadata = MockMambaBuilder.build_mamba_metadata(
63 - config,
64 - seq_lens=seq_lens,
65 - query_lens=[1] * len(seq_lens),
66 - is_prefilling=[False, False, True],
67 - )
68 -
69 - assert metadata.num_decodes == 2
70 - assert metadata.num_prefills == 1
71 - assert metadata.has_initial_states_p.tolist() == [False]
72 -
73 -
74 -def test_update_block_table_copies_block_idx_to_persistent_buffers():
75 - """update_block_table() must write block_idx tensors to the current
76 - builder's persistent buffers, not leave them pointing to a different
77 - builder's buffers."""
78 - block_size = 16
79 - max_model_len = 256
80 - num_reqs = 4
81 - device = torch.device("cpu")
82 -
83 - vllm_config = _make_vllm_config(max_model_len, num_reqs)
84 -
85 - spec = MambaSpec(
86 - block_size=block_size,
87 - shapes=((1,), (1,)),
88 - dtypes=(torch.float32,),
89 - mamba_cache_mode="all",
90 - )
91 -
92 - # Two builders simulating two KV cache groups with the same MambaSpec.
93 - builder_a = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
94 - builder_b = MockMambaBuilder(spec, ["layer1"], vllm_config, device)
95 -
96 - # Sanity: each builder has its own persistent buffer.
97 - assert (
98 - builder_a.block_idx_last_scheduled_token.data_ptr()
99 - != builder_b.block_idx_last_scheduled_token.data_ptr()
100 - )
101 -
102 - # Construct decode-only metadata as if builder_a.build() produced it.
103 - max_blocks = max_model_len // block_size
104 - seq_lens = torch.full((num_reqs,), 64, dtype=torch.int32, device=device)
105 - block_idx_vals = (seq_lens - 1) // block_size # [3, 3, 3, 3]
106 -
107 - builder_a.block_idx_last_scheduled_token[:num_reqs].copy_(block_idx_vals)
108 - builder_a.block_idx_last_computed_token[:num_reqs].copy_(block_idx_vals)
109 -
110 - metadata_a = BaseMambaAttentionMetadata(
111 - num_prefills=0,
112 - num_prefill_tokens=0,
113 - num_decodes=num_reqs,
114 - num_decode_tokens=num_reqs,
115 - num_reqs=num_reqs,
116 - has_initial_states_p=None,
117 - query_start_loc_p=None,
118 - num_computed_tokens_p=None,
119 - state_indices_tensor_p=None,
120 - query_start_loc_d=None,
121 - num_accepted_tokens=None,
122 - state_indices_tensor_d=builder_a.state_indices_tensor_d[:num_reqs],
123 - block_idx_last_scheduled_token=(
124 - builder_a.block_idx_last_scheduled_token[:num_reqs]
125 - ),
126 - block_idx_first_scheduled_token_p=None,
127 - block_idx_last_computed_token=(
128 - builder_a.block_idx_last_computed_token[:num_reqs]
129 - ),
130 - block_idx_last_scheduled_token_prev_step=None,
131 - seq_lens=seq_lens,
132 - )
133 -
134 - # Call update_block_table on builder_b (simulates the metadata caching
135 - # optimization reusing metadata from builder_a's group).
136 - blk_table = torch.randint(
137 - 0, 100, (num_reqs, max_blocks), dtype=torch.int32, device=device
138 - )
139 - slot_mapping = torch.zeros(num_reqs, dtype=torch.int64, device=device)
140 -
141 - metadata_b = builder_b.update_block_table(metadata_a, blk_table, slot_mapping)
142 -
143 - # block_idx tensors must live in builder_b's persistent buffers.
144 - def shares_storage(tensor, buffer):
145 - return (
146 - tensor.untyped_storage().data_ptr() == buffer.untyped_storage().data_ptr()
147 - )
148 -
149 - assert shares_storage(
150 - metadata_b.block_idx_last_scheduled_token,
151 - builder_b.block_idx_last_scheduled_token,
152 - ), "block_idx_last_scheduled_token not in builder_b's persistent buffer"
153 -
154 - assert shares_storage(
155 - metadata_b.block_idx_last_computed_token,
156 - builder_b.block_idx_last_computed_token,
157 - ), "block_idx_last_computed_token not in builder_b's persistent buffer"
158 -
159 - # Must NOT point to builder_a's buffers.
160 - assert not shares_storage(
161 - metadata_b.block_idx_last_scheduled_token,
162 - builder_a.block_idx_last_scheduled_token,
163 - ), "block_idx_last_scheduled_token still points to builder_a's buffer"
164 -
165 - # Values must be correct (copied from metadata_a).
166 - torch.testing.assert_close(
167 - metadata_b.block_idx_last_scheduled_token,
168 - block_idx_vals,
169 - )
170 - torch.testing.assert_close(
171 - metadata_b.block_idx_last_computed_token,
172 - block_idx_vals,
173 - )
174 -
175 -
176 -def test_state_indices_tensor_d_includes_num_speculative_blocks():
177 - """Regression test for https://github.com/vllm-project/vllm/issues/39809
178 - bug 1: with mamba_cache_mode='all' and speculative decoding enabled,
179 - the cudagraph buffer for state_indices_tensor_d must allocate the same
180 - per-request column count as the runtime block table, which includes
181 - num_speculative_blocks trailing scratch columns."""
182 - block_size = 16
183 - max_model_len = 256
184 - max_num_seqs = 4
185 - num_speculative_tokens = 1
186 - num_speculative_blocks = 2
187 - device = torch.device("cpu")
188 -
189 - vllm_config = _make_vllm_config(
190 - max_model_len,
191 - max_num_seqs,
192 - num_speculative_tokens=num_speculative_tokens,
193 - )
194 -
195 - spec = MambaSpec(
196 - block_size=block_size,
197 - shapes=((1,), (1,)),
198 - dtypes=(torch.float32,),
199 - mamba_cache_mode="all",
200 - num_speculative_blocks=num_speculative_blocks,
201 - )
202 -
203 - builder = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
204 -
205 - expected_cols = (max_model_len // block_size) + num_speculative_blocks
206 - assert builder.state_indices_tensor_d.shape == (max_num_seqs, expected_cols)
207 -
208 -
209 -def test_block_idx_cudagraph_capture_padded_by_num_reqs():
210 - """Regression test for https://github.com/vllm-project/vllm/issues/39809
211 - bug 2: with mamba_cache_mode='all' and spec decode, _update_metadata_for
212 - _cudagraph_capture must slice block_idx_last_{scheduled,computed}_token
213 - by the request count (padded_bs == num_reqs), not by num_decode_tokens.
214 - Past num_decodes, the slice must be zero-filled."""
215 - block_size = 16
216 - max_model_len = 256
217 - max_num_seqs = 8
218 - num_speculative_tokens = 1
219 - device = torch.device("cpu")
220 -
221 - vllm_config = _make_vllm_config(
222 - max_model_len,
223 - max_num_seqs,
224 - num_speculative_tokens=num_speculative_tokens,
225 - )
226 -
227 - spec = MambaSpec(
228 - block_size=block_size,
229 - shapes=((1,), (1,)),
230 - dtypes=(torch.float32,),
231 - mamba_cache_mode="all",
232 - num_speculative_blocks=2,
233 - )
234 -
235 - builder = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
236 -
237 - builder.block_idx_last_scheduled_token.fill_(-1)
238 - builder.block_idx_last_computed_token.fill_(-1)
239 -
240 - num_decodes = 2
241 - num_reqs = 3
242 - num_decode_tokens = num_decodes * (1 + num_speculative_tokens)
243 - seq_lens = torch.full((num_reqs,), 64, dtype=torch.int32, device=device)
244 - block_idx_vals = torch.tensor([3, 5], dtype=torch.int32, device=device)
245 - state_indices_d = torch.zeros(
246 - (num_decodes, builder.state_indices_tensor_d.shape[1]),
247 - dtype=torch.int32,
248 - device=device,
249 - )
250 - query_start_loc_d = torch.arange(
251 - num_decodes + 1, dtype=torch.int32, device=device
252 - ) * (1 + num_speculative_tokens)
253 - num_accepted_tokens = torch.ones(num_decodes, dtype=torch.int32, device=device)
254 -
255 - metadata = BaseMambaAttentionMetadata(
256 - num_prefills=0,
257 - num_prefill_tokens=0,
258 - num_decodes=num_decodes,
259 - num_decode_tokens=num_decode_tokens,
260 - num_reqs=num_reqs,
261 - has_initial_states_p=None,
262 - query_start_loc_p=None,
263 - num_computed_tokens_p=None,
264 - state_indices_tensor_p=None,
265 - state_indices_tensor_d=state_indices_d,
266 - query_start_loc_d=query_start_loc_d,
267 - num_accepted_tokens=num_accepted_tokens,
268 - block_idx_last_scheduled_token=block_idx_vals,
269 - block_idx_first_scheduled_token_p=None,
270 - block_idx_last_computed_token=block_idx_vals,
271 - block_idx_last_scheduled_token_prev_step=None,
272 - seq_lens=seq_lens,
273 - )
274 -
275 - out = builder._update_metadata_for_cudagraph_capture(metadata)
276 -
277 - assert out.block_idx_last_scheduled_token.shape == (num_reqs,)
278 - assert out.block_idx_last_computed_token.shape == (num_reqs,)
279 - torch.testing.assert_close(
280 - out.block_idx_last_scheduled_token[:num_decodes], block_idx_vals
281 - )
282 - torch.testing.assert_close(
283 - out.block_idx_last_computed_token[:num_decodes], block_idx_vals
284 - )
285 - assert torch.all(out.block_idx_last_scheduled_token[num_decodes:] == 0)
286 - assert torch.all(out.block_idx_last_computed_token[num_decodes:] == 0)
287 -
288 -
289 -def test_block_idx_prev_step_persistent_buffer_allocated():
290 - """With mamba_cache_mode='all' + spec decode, the builder must allocate
291 - block_idx_last_scheduled_token_prev_step as a persistent buffer with the
292 - same shape as the existing block_idx_last_{scheduled,computed}_token
293 - buffers, so cudagraph capture records a stable pointer for the prev-step
294 - input anchor consumed by mamba_mixer2's input gather."""
295 - block_size = 16
296 - max_model_len = 256
297 - max_num_seqs = 8
298 - num_speculative_tokens = 1
299 - device = torch.device("cpu")
300 -
301 - vllm_config = _make_vllm_config(
302 - max_model_len,
303 - max_num_seqs,
304 - num_speculative_tokens=num_speculative_tokens,
305 - )
306 - spec = MambaSpec(
307 - block_size=block_size,
308 - shapes=((1,), (1,)),
309 - dtypes=(torch.float32,),
310 - mamba_cache_mode="all",
311 - num_speculative_blocks=2,
312 - )
313 - builder = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
314 -
315 - assert hasattr(builder, "block_idx_last_scheduled_token_prev_step")
316 - assert builder.block_idx_last_scheduled_token_prev_step.shape == (max_num_seqs,)
317 - assert builder.block_idx_last_scheduled_token_prev_step.dtype == torch.int32
318 -
319 -
320 -def test_block_idx_prev_step_persistent_buffer_skipped_without_spec_decode():
321 - """Without spec decode, the prev-step buffer is unused and must not be
322 - allocated — the input anchor reduces to last_computed_token."""
323 - block_size = 16
324 - max_model_len = 256
325 - max_num_seqs = 8
326 - device = torch.device("cpu")
327 -
328 - vllm_config = _make_vllm_config(
329 - max_model_len, max_num_seqs, num_speculative_tokens=0
330 - )
331 - spec = MambaSpec(
332 - block_size=block_size,
333 - shapes=((1,), (1,)),
334 - dtypes=(torch.float32,),
335 - mamba_cache_mode="all",
336 - )
337 - builder = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
338 -
339 - assert not hasattr(builder, "block_idx_last_scheduled_token_prev_step")
340 -
341 -
342 -def test_block_idx_prev_step_cudagraph_capture_uses_persistent_buffer():
343 - """_update_metadata_for_cudagraph_capture must copy the prev-step anchor
344 - into the builder's persistent buffer (so cudagraph replay reads from the
345 - same underlying memory), pad past num_decodes with zero, and return a
346 - slice of the persistent buffer in the metadata."""
347 - block_size = 16
348 - max_model_len = 256
349 - max_num_seqs = 8
350 - num_speculative_tokens = 1
351 - device = torch.device("cpu")
352 -
353 - vllm_config = _make_vllm_config(
354 - max_model_len,
355 - max_num_seqs,
356 - num_speculative_tokens=num_speculative_tokens,
357 - )
358 - spec = MambaSpec(
359 - block_size=block_size,
360 - shapes=((1,), (1,)),
361 - dtypes=(torch.float32,),
362 - mamba_cache_mode="all",
363 - num_speculative_blocks=2,
364 - )
365 - builder = MockMambaBuilder(spec, ["layer0"], vllm_config, device)
366 - builder.block_idx_last_scheduled_token.fill_(-1)
367 - builder.block_idx_last_computed_token.fill_(-1)
368 - builder.block_idx_last_scheduled_token_prev_step.fill_(-1)
369 -
370 - num_decodes = 2
371 - num_reqs = 3
372 - num_decode_tokens = num_decodes * (1 + num_speculative_tokens)
373 - seq_lens = torch.full((num_reqs,), 64, dtype=torch.int32, device=device)
374 - block_idx_vals = torch.tensor([3, 5], dtype=torch.int32, device=device)
375 - prev_step_vals = torch.tensor([2, 4], dtype=torch.int32, device=device)
376 - state_indices_d = torch.zeros(
377 - (num_decodes, builder.state_indices_tensor_d.shape[1]),
378 - dtype=torch.int32,
379 - device=device,
380 - )
381 - query_start_loc_d = torch.arange(
382 - num_decodes + 1, dtype=torch.int32, device=device
383 - ) * (1 + num_speculative_tokens)
384 - num_accepted_tokens = torch.ones(num_decodes, dtype=torch.int32, device=device)
385 -
386 - metadata = BaseMambaAttentionMetadata(
387 - num_prefills=0,
388 - num_prefill_tokens=0,
389 - num_decodes=num_decodes,
390 - num_decode_tokens=num_decode_tokens,
391 - num_reqs=num_reqs,
392 - has_initial_states_p=None,
393 - query_start_loc_p=None,
394 - num_computed_tokens_p=None,
395 - state_indices_tensor_p=None,
396 - state_indices_tensor_d=state_indices_d,
397 - query_start_loc_d=query_start_loc_d,
398 - num_accepted_tokens=num_accepted_tokens,
399 - block_idx_last_scheduled_token=block_idx_vals,
400 - block_idx_first_scheduled_token_p=None,
401 - block_idx_last_computed_token=block_idx_vals,
402 - block_idx_last_scheduled_token_prev_step=prev_step_vals,
403 - seq_lens=seq_lens,
404 - )
405 -
406 - out = builder._update_metadata_for_cudagraph_capture(metadata)
407 -
408 - # Output field exists and is identity-shared with the persistent buffer.
409 - assert out.block_idx_last_scheduled_token_prev_step is not None
410 - assert (
411 - out.block_idx_last_scheduled_token_prev_step.untyped_storage().data_ptr()
412 - == builder.block_idx_last_scheduled_token_prev_step.untyped_storage().data_ptr()
413 - ), (
414 - "prev-step buffer must live in the builder's persistent buffer, not "
415 - "in the caller-provided tensor"
416 - )
417 -
418 - # Padded by num_reqs (not num_decode_tokens) — same fix as bug 2 for the
419 - # other block_idx_* fields.
420 - assert out.block_idx_last_scheduled_token_prev_step.shape == (num_reqs,)
421 -
422 - # First num_decodes values: input values copied through.
423 - torch.testing.assert_close(
424 - out.block_idx_last_scheduled_token_prev_step[:num_decodes],
425 - prev_step_vals,
426 - )
427 -
428 - # Tail values past num_decodes: zero-filled padding for cudagraph capture.
429 - assert torch.all(out.block_idx_last_scheduled_token_prev_step[num_decodes:] == 0)
430 -
431 -
432 -def test_num_state_writes_p_matches_block_idx_tensors():
433 - """The prefill state save sizes its batched copy from
434 - num_state_writes_p instead of reading the block index tensors back from
435 - the GPU, so the host count must equal what those tensors say."""
436 - # (block_size, seq_lens, query_lens, is_prefilling)
437 - cases = [
438 - # block-aligned and unaligned computed tokens, and a chunked prefill
439 - (16, [64, 33, 17], [48, 17, 1], [True, True, True]),
440 - (32, [200, 100, 64], [200, 36, 64], [True, True, True]),
441 - # decode rows ahead of the prefills
442 - (16, [40, 41, 100, 200], [1, 1, 60, 200], [False, False, True, True]),
443 - # prefills that stay inside their first block: nothing to write
444 - (16, [65, 66, 67], [1, 2, 3], [True, True, True]),
445 - ]
446 -
447 - for block_size, seq_lens, query_lens, is_prefilling in cases:
448 - metadata = MockMambaBuilder.build_mamba_metadata(
449 - _make_vllm_config(4096, len(seq_lens), block_size=block_size),
450 - seq_lens=seq_lens,
451 - query_lens=query_lens,
452 - is_prefilling=is_prefilling,
453 - )
454 - num_reqs, num_prefills = metadata.num_reqs, metadata.num_prefills
455 - block_idx_last_p = metadata.block_idx_last_scheduled_token[
456 - num_reqs - num_prefills : num_reqs
457 - ]
458 - expected = int(
459 - (block_idx_last_p - metadata.block_idx_first_scheduled_token_p)
460 - .clamp(min=0)
461 - .sum()
462 - )
463 -
464 - assert metadata.num_state_writes_p == expected, (
465 - f"seq_lens={seq_lens} query_lens={query_lens} block_size={block_size}"
466 - )
tests/v1/core/test_scheduler.py
+38 -38
@@ -39,6 +39,7 @@ from vllm.sampling_params import SamplingParams, StructuredOutputsParams
39 from vllm.utils.hashing import sha256
40 from vllm.v1.core.encoder_cache_manager import EncoderCacheManager
41 from vllm.v1.core.kv_cache_coordinator import HybridKVCacheCoordinator
42 +from vllm.v1.core.kv_cache_manager import KVCacheManager
43 from vllm.v1.core.kv_cache_utils import get_request_block_hasher, init_none_hash
44 from vllm.v1.core.sched.diffusion_scheduler import (
45 DiffusionAsyncScheduler,
@@ -6751,7 +6752,7 @@ def _create_hybrid_mamba_connector_scheduler(
6752 num_blocks: int = 100,
6753 supports_divergent_hits: bool = True,
6754 ) -> Scheduler:
6754 - """FA + Mamba ("all" cache mode) scheduler with a MockKVConnector."""
6755 + """FA + Mamba ("align" cache mode) scheduler with a MockKVConnector."""
6756 model_config = ModelConfig(
6757 model="facebook/opt-125m",
6758 trust_remote_code=True,
@@ -6772,7 +6773,7 @@ def _create_hybrid_mamba_connector_scheduler(
6773 cache_config=CacheConfig(
6774 block_size=block_size,
6775 enable_prefix_caching=True,
6775 - mamba_cache_mode="all",
6776 + mamba_cache_mode="align",
6777 ),
6778 kv_transfer_config=KVTransferConfig(
6779 kv_connector="MockKVConnector",
@@ -6804,7 +6805,7 @@ def _create_hybrid_mamba_connector_scheduler(
6805 block_size=block_size,
6806 shapes=((1, 1),),
6807 dtypes=(torch.float32,),
6807 - mamba_cache_mode="all",
6808 + mamba_cache_mode="align",
6809 ),
6810 ),
6811 ],
@@ -6820,6 +6821,33 @@ def _create_hybrid_mamba_connector_scheduler(
6821 )
6822
6823
6824 +def _seed_hybrid_prefix(
6825 + manager: KVCacheManager, num_blocks: int, block_size: int
6826 +) -> tuple[list[int], list[int]]:
6827 + """Prefill a prefix one block per step so that align-mode Mamba caches
6828 + the state at every block boundary. Returns the FA and Mamba block ids."""
6829 + [fill] = create_requests(
6830 + num_requests=1,
6831 + num_tokens=num_blocks * block_size,
6832 + max_tokens=1,
6833 + same_prompt=True,
6834 + block_size=block_size,
6835 + req_ids=["fill"],
6836 + )
6837 + computed_blocks, num_computed, _ = manager.get_computed_blocks(fill)
6838 + assert num_computed == 0
6839 + mamba_ids = []
6840 + for i in range(num_blocks):
6841 + manager.allocate_slots(
6842 + fill, block_size, new_computed_blocks=computed_blocks if i == 0 else None
6843 + )
6844 + fill.num_computed_tokens += block_size
6845 + fa_ids, group_mamba_ids = manager.get_block_ids(fill.request_id)
6846 + mamba_ids.append(group_mamba_ids[i])
6847 + manager.free(fill)
6848 + return fa_ids, mamba_ids
6849 +
6850 +
6851 @pytest.mark.parametrize(
6852 "matched_tokens,expected_num_computed",
6853 [
@@ -6845,23 +6873,8 @@ def test_hybrid_per_group_hit_divergence_with_connector(
6873 manager = scheduler.kv_cache_manager
6874 assert isinstance(manager.coordinator, HybridKVCacheCoordinator)
6875
6848 - # Seed a 4-block prefix so both groups cache all four boundaries
6849 - # (mamba cache mode "all" caches every block's state densely).
6850 - [fill] = create_requests(
6851 - num_requests=1,
6852 - num_tokens=4 * block_size,
6853 - max_tokens=1,
6854 - same_prompt=True,
6855 - block_size=block_size,
6856 - req_ids=["fill"],
6857 - )
6858 - computed_blocks, num_computed, _ = manager.get_computed_blocks(fill)
6859 - blocks = manager.allocate_slots(
6860 - fill, fill.num_tokens, num_computed, computed_blocks
6861 - )
6862 - fa_ids = [b.block_id for b in blocks.blocks[0]]
6863 - mamba_ids = [b.block_id for b in blocks.blocks[1]]
6864 - manager.free(fill)
6876 + # Seed a 4-block prefix so both groups cache all four boundaries.
6877 + fa_ids, mamba_ids = _seed_hybrid_prefix(manager, 4, block_size)
6878
6879 # Evict the FA tail and the middle mamba states; block 0 (both groups)
6880 # and the deep mamba state at block 3 survive.
@@ -6885,8 +6898,8 @@ def test_hybrid_per_group_hit_divergence_with_connector(
6898
6899 scheduler.add_request(replay)
6900 output = scheduler.schedule()
6888 - num_scheduled = output.num_scheduled_tokens[replay.request_id]
6889 - assert replay.num_tokens - num_scheduled == expected_num_computed
6901 + [new_req] = output.scheduled_new_reqs
6902 + assert new_req.num_computed_tokens == expected_num_computed
6903
6904
6905 @pytest.mark.parametrize(
@@ -6921,20 +6934,7 @@ def test_hybrid_fa_deeper_hit_respects_connector_lookup_policy(
6934 assert isinstance(manager.coordinator, HybridKVCacheCoordinator)
6935
6936 # Seed a 4-block prefix in both groups.
6924 - [fill] = create_requests(
6925 - num_requests=1,
6926 - num_tokens=4 * block_size,
6927 - max_tokens=1,
6928 - same_prompt=True,
6929 - block_size=block_size,
6930 - req_ids=["fill"],
6931 - )
6932 - computed_blocks, num_computed, _ = manager.get_computed_blocks(fill)
6933 - blocks = manager.allocate_slots(
6934 - fill, fill.num_tokens, num_computed, computed_blocks
6935 - )
6936 - mamba_ids = [b.block_id for b in blocks.blocks[1]]
6937 - manager.free(fill)
6937 + _, mamba_ids = _seed_hybrid_prefix(manager, 4, block_size)
6938
6939 # Keep all FA blocks; evict every mamba state but block 0. FA reaches 4
6940 # blocks, the mamba hit only reaches 1 -> diverged (FA > Mamba).
@@ -6955,8 +6955,8 @@ def test_hybrid_fa_deeper_hit_respects_connector_lookup_policy(
6955
6956 scheduler.add_request(replay)
6957 output = scheduler.schedule()
6958 - num_scheduled = output.num_scheduled_tokens[replay.request_id]
6959 - assert replay.num_tokens - num_scheduled == expected_num_computed
6958 + [new_req] = output.scheduled_new_reqs
6959 + assert new_req.num_computed_tokens == expected_num_computed
6960
6961
6962 def _make_encoder_instance_request(scheduler, text_prefix=8, image_tokens=16):
tests/v1/kv_connector/nixl_integration/run_mamba_prefix_cache_test.sh
+2 -2
@@ -60,7 +60,7 @@ vllm serve "$MODEL" \
60 --block-size 128 \
61 --trust-remote-code \
62 --enable-prefix-caching \
63 - --mamba-cache-mode all \
63 + --mamba-cache-mode align \
64 --kv-transfer-config "$KV_CONFIG" \
65 "${EXTRA_ARGS[@]}" &
66
@@ -78,7 +78,7 @@ vllm serve "$MODEL" \
78 --block-size 128 \
79 --trust-remote-code \
80 --enable-prefix-caching \
81 - --mamba-cache-mode all \
81 + --mamba-cache-mode align \
82 --kv-transfer-config "$KV_CONFIG" \
83 "${EXTRA_ARGS[@]}" &
84
tests/v1/kv_connector/unit/test_moriio_hma_scheduler.py
+13 -48
@@ -67,7 +67,6 @@ class _FakeScheduler(moriio_connector.MoRIIOConnectorScheduler): # type: ignore
67 self._mamba_group_ids: list[int] = []
68 self._attn_group_ids: list[int] = [0]
69 self._num_ssm_scratch_blocks = 0
70 - self._ssm_state_slots_are_positional = False
70 self._is_hma_required = False
71 self.kv_cache_config = SimpleNamespace(
72 kv_cache_groups=[None, None],
@@ -130,7 +129,7 @@ def _mamba_spec(num_states: int = 2, *, num_speculative_blocks: int = 0):
129 block_size=16,
130 shapes=shapes,
131 dtypes=(torch.float32,) * num_states,
133 - mamba_cache_mode="all",
132 + mamba_cache_mode="align",
133 num_speculative_blocks=num_speculative_blocks,
134 )
135
@@ -146,7 +145,7 @@ def _gate_vllm_config(
145 block_size: int = 16,
146 num_lookahead_tokens: int = 0,
147 num_speculative_tokens: int = 0,
149 - mamba_cache_mode: str = "all",
148 + mamba_cache_mode: str = "align",
149 dcp_size: int = 1,
150 ):
151 return SimpleNamespace(
@@ -193,7 +192,7 @@ def test_split_block_groups_ignores_transfer_disabled_group():
192 block_size=16,
193 shapes=((1, 1),),
194 dtypes=(torch.float32,),
196 - mamba_cache_mode="all",
195 + mamba_cache_mode="align",
196 )
197 config = SimpleNamespace(
198 kv_cache_groups=[
@@ -361,7 +360,6 @@ def test_scheduler_accepts_dspark_with_wrapped_full_attention(
360 block_size=4,
361 num_lookahead_tokens=7,
362 num_speculative_tokens=7,
364 - mamba_cache_mode="align",
363 dcp_size=dcp_size,
364 )
365 scheduler = moriio_connector.MoRIIOConnectorScheduler(vllm_config, "engine", config)
@@ -398,21 +396,6 @@ def test_scheduler_accepts_dspark_with_wrapped_full_attention(
396 )
397
398
401 -def test_scheduler_rejects_dspark_with_positional_mamba_cache():
402 - """Positional mode counts lookahead in the Mamba allocation, so the
403 - one-block recurrent-state tail bound no longer holds."""
404 - with pytest.raises(moriio_common.MoRIIOError, match="mamba_cache_mode"):
405 - moriio_connector.MoRIIOConnectorScheduler(
406 - _gate_vllm_config(
407 - speculative_config=_spec_config("dspark"),
408 - block_size=4,
409 - mamba_cache_mode="all",
410 - ),
411 - "engine",
412 - _dspark_wrapped_attention_config(),
413 - )
414 -
415 -
399 def test_scheduler_rejects_hybrid_write():
400 config = SimpleNamespace(
401 kv_cache_groups=[
@@ -441,20 +424,7 @@ def test_split_block_groups_accepts_empty_abort_payload():
424 assert sched.split_block_groups(()) == ([], [])
425
426
444 -def test_split_block_groups_keeps_positional_slots_in_all_mode():
445 - # mamba_cache_mode="all" keeps a state per block position.
446 - sched = _FakeScheduler(
447 - _has_mamba=True,
448 - _attn_group_ids=[0],
449 - _mamba_group_ids=[1],
450 - _ssm_state_slots_are_positional=True,
451 - )
452 - attn, mamba = sched.split_block_groups(([1], [40, 41, 42]))
453 - assert attn == [1]
454 - assert mamba == [[40, 41, 42]]
455 -
456 -
457 -def test_split_block_groups_keeps_only_running_state_outside_all_mode():
427 +def test_split_block_groups_keeps_only_running_state():
428 sched = _FakeScheduler(
429 _has_mamba=True,
430 _attn_group_ids=[0],
@@ -464,24 +434,21 @@ def test_split_block_groups_keeps_only_running_state_outside_all_mode():
434
435
436 @pytest.mark.parametrize(
467 - "positional,blocks,expected",
437 + "blocks,expected",
438 [
469 - # Trailing scratch slots go in either cache mode.
470 - (True, [40, 41, 42], [40]),
471 - # Outside "all" the running state is picked only after they are gone.
472 - (False, [40, 41, 42, 43], [41]),
439 + # The running state is picked only after the scratch slots are gone.
440 + ([40, 41, 42, 43], [41]),
441 # Never empty: a list shorter than the scratch count keeps one state.
474 - (True, [40], [40]),
475 - (False, [], []),
442 + ([40], [40]),
443 + ([], []),
444 ],
445 )
478 -def test_split_block_groups_strips_dspark_scratch_slots(positional, blocks, expected):
446 +def test_split_block_groups_strips_dspark_scratch_slots(blocks, expected):
447 sched = _FakeScheduler(
448 _has_mamba=True,
449 _attn_group_ids=[0],
450 _mamba_group_ids=[1],
451 _num_ssm_scratch_blocks=2,
484 - _ssm_state_slots_are_positional=positional,
452 )
453
454 assert sched.split_block_groups(([1], blocks)) == ([1], [expected])
@@ -555,7 +522,6 @@ def _make_read_scheduler():
522 _has_mamba=True,
523 _attn_group_ids=[0],
524 _mamba_group_ids=[1],
558 - _ssm_state_slots_are_positional=True,
525 request_id_to_transfer_id={},
526 transfer_id_to_request_id={},
527 _reqs_need_recv={},
@@ -578,17 +544,17 @@ def _make_read_request(remote_block_ids):
544
545 def test_update_state_drops_decode_recompute_tail_block():
546 sched = _make_read_scheduler()
581 - request = _make_read_request([[10, 11], [90, 91]])
547 + request = _make_read_request([[10, 11], [91]])
548 blocks = _FakeBlocks(
549 all_groups=([100, 101, 102], [200, 201, 202]),
550 )
551
552 sched.update_state_after_alloc(request, blocks, num_external_tokens=256)
553
588 - assert sched._reqs_need_recv["req"][1] == [[100, 101], [200, 201]]
554 + assert sched._reqs_need_recv["req"][1] == [[100, 101], [202]]
555 assert sched._req_kv_params["req"]["remote_block_ids"] == [
556 [10, 11],
591 - [90, 91],
557 + [91],
558 ]
559
560
@@ -609,7 +575,6 @@ def test_update_state_pairs_trimmed_mamba_state_with_remote_state():
575 """The DSpark shape end to end: scratch slots are stripped, the running
576 state is then selected, and the attention list loses its lookahead tail."""
577 sched = _make_read_scheduler()
612 - sched._ssm_state_slots_are_positional = False
578 sched._num_ssm_scratch_blocks = 2
579 sched._max_decode_tail_blocks = 2
580 request = _make_read_request([[10, 11], [90]])
tests/v1/kv_connector/unit/test_nixl_connector_hma.py
+3 -39
@@ -856,28 +856,6 @@ def test_apply_prefix_caching_mamba_hybrid(
856 [[6, 7, 8, 9], [99]],
857 id="fa_prefix_hit_and_ssm_trim",
858 ),
859 - # Multi-slot SSM ("all" mode): a local prefix hit leaves fewer local
860 - # slots; the earlier remote slots are covered locally → remote tail.
861 - pytest.param(
862 - 10,
863 - 10,
864 - [list(range(10)), [5, 6]],
865 - [list(range(10)), [1, 2, 3]],
866 - [list(range(10)), [5, 6]],
867 - [list(range(10)), [2, 3]],
868 - id="ssm_multi_block_local_hit_tail",
869 - ),
870 - # Multi-slot SSM ("all" mode): the one trailing local position holds
871 - # the token D recomputes itself → local head-clip.
872 - pytest.param(
873 - 10,
874 - 10,
875 - [list(range(10)), [4, 5, 6]],
876 - [list(range(10)), [8, 9]],
877 - [list(range(10)), [4, 5]],
878 - [list(range(10)), [8, 9]],
879 - id="ssm_multi_block_local_extra_head_clip",
880 - ),
859 ],
860 )
861 def test_apply_prefix_caching_ssm_prefix_cache_hit(
@@ -2034,13 +2012,12 @@ def test_logical_to_kernel_block_ids_with_remote_ratio(
2012
2013 @pytest.mark.cpu_test
2014 def test_exchange_clipped_blocks_ssm_single_state():
2037 - """In single-state cache modes, SSM lists are reduced to the running
2038 - state slot: speculative scratch slots, null placeholders and the previous
2039 - step's state carry nothing. Attention groups pass through untouched."""
2015 + """SSM lists are reduced to the running state slot: speculative scratch
2016 + slots, null placeholders and the previous step's state carry nothing.
2017 + Attention groups pass through untouched."""
2018 sched = make_nixl_scheduler(has_mamba=True, is_hma_required=True)
2019 sched.blocks_per_sw = [0, 0]
2020 sched._ssm_spec_blocks = [None, 2]
2043 - sched._ssm_state_slots_are_positional = False
2021
2022 # Align-mode list: null placeholders, state block, 2 speculative slots.
2023 clipped = sched.get_exchange_clipped_blocks(([1, 2, 3], [0, 0, 7, 8, 9]))
@@ -2069,19 +2046,6 @@ def test_exchange_clipped_blocks_ssm_single_state():
2046 assert fa_sched.get_exchange_clipped_blocks(([1, 2],)) == ([1, 2],)
2047
2048
2072 -@pytest.mark.cpu_test
2073 -def test_exchange_clipped_blocks_ssm_positional_states():
2074 - """In "all" mode every position holds a state, so only the speculative
2075 - slots go; placeholders stay to keep the list position-indexed."""
2076 - sched = make_nixl_scheduler(has_mamba=True, is_hma_required=True)
2077 - sched.blocks_per_sw = [0, 0]
2078 - sched._ssm_spec_blocks = [None, 2]
2079 - sched._ssm_state_slots_are_positional = True
2080 -
2081 - clipped = sched.get_exchange_clipped_blocks(([1, 2, 3], [0, 5, 6, 7, 8, 9]))
2082 - assert clipped == ([1, 2, 3], [0, 5, 6, 7])
2083 -
2084 -
2049 # ── Hybrid MLA+SSM (KimiLinear-shaped KDA+MLA) tests ─────────────────────
2050
2051
tests/v1/kv_connector/unit/test_offloading_connector.py
+2 -5
@@ -566,10 +566,7 @@ def test_fs_tiering_offloading(tmp_path) -> None:
566 ("state-spaces/mamba-1.4b-hf", 16, 1),
567 ],
568 )
569 -@pytest.mark.parametrize("mamba_cache_mode", ["align", "all"])
570 -def test_mamba_cpu_offload_boundary(
571 - model: str, block_size: int, tp_size: int, mamba_cache_mode: str
572 -):
569 +def test_mamba_cpu_offload_boundary(model: str, block_size: int, tp_size: int):
570 kv_transfer_config = KVTransferConfig(
571 kv_connector="OffloadingConnector",
572 kv_role="kv_both",
@@ -586,7 +583,7 @@ def test_mamba_cpu_offload_boundary(
583 kv_transfer_config=kv_transfer_config,
584 language_model_only=True,
585 enable_prefix_caching=True,
589 - mamba_cache_mode=mamba_cache_mode,
586 + mamba_cache_mode="align",
587 # Use lossless state storage so this exact-equality regression isolates
588 # offload boundary selection from low-precision Mamba checkpointing.
589 mamba_cache_dtype="float32",
tests/v1/kv_connector/unit/utils.py
+1 -1
@@ -474,7 +474,7 @@ def make_kv_cache_config(
474 mamba_enabled: bool = False,
475 sw_size: int = 128,
476 num_blocks: int = 100,
477 - mamba_cache_mode: Literal["all", "align", "none"] = "none",
477 + mamba_cache_mode: Literal["align", "none"] = "none",
478 ) -> KVCacheConfig:
479 kv_cache_groups = [
480 KVCacheGroupSpec(
tests/v1/simple_kv_offload/test_scheduler.py
+1 -50
@@ -2335,7 +2335,7 @@ def test_hybrid_store_uses_resolved_group_block_sizes() -> None:
2335 # of the hash block, so it cannot exceed the smallest of them.
2336 hash_block_size=BLOCK_SIZE,
2337 dcp_world_size=dcp_world_size,
2338 - mamba_cache_mode="all",
2338 + mamba_cache_mode="none",
2339 )
2340 sched = fix.scheduler
2341 gpu_pool = fix.gpu_block_pool
@@ -2747,55 +2747,6 @@ def test_eager_store_does_not_scan_mamba_blocks_positionally() -> None:
2747 assert sched._reqs_to_store[req.request_id].num_stored_blocks == [2, 0]
2748
2749
2750 -def test_eager_store_scans_mamba_all_blocks_positionally() -> None:
2751 - """Mamba all-mode retains positional identity and needs no handoff."""
2752 - block_size = 4 * BLOCK_SIZE
2753 - fix = _make_hybrid_attention_mamba_scheduler(
2754 - num_cpu_blocks=16,
2755 - num_gpu_blocks=24,
2756 - block_size=block_size,
2757 - mamba_cache_mode="all",
2758 - )
2759 - sched = fix.scheduler
2760 - gpu_pool = fix.gpu_block_pool
2761 - attention_manager, mamba_manager = sched.cpu_coordinator.single_type_managers
2762 - assert attention_manager.has_positionally_stable_blocks
2763 - assert mamba_manager.has_positionally_stable_blocks
2764 -
2765 - req = _make_cp_request(num_blocks=2, virtual_block_size=block_size)
2766 - attention_blocks = _allocate_cp_gpu_blocks(
2767 - gpu_pool, req, 2, virtual_block_size=block_size, group_id=0
2768 - )
2769 - mamba_blocks = _allocate_cp_gpu_blocks(
2770 - gpu_pool, req, 2, virtual_block_size=block_size, group_id=1
2771 - )
2772 - kv_blocks = KVCacheBlocks(blocks=(attention_blocks, mamba_blocks))
2773 - sched.update_state_after_alloc(req, kv_blocks, num_external_tokens=0)
2774 -
2775 - # build_connector_meta runs before the scheduler commits this step's
2776 - # tokens, so the positional scan picks the blocks up on the next step.
2777 - first = sched.build_connector_meta(
2778 - make_scheduler_output(
2779 - {req.request_id: 2 * block_size},
2780 - new_reqs={req.request_id: kv_blocks.get_block_ids()},
2781 - )
2782 - )
2783 - assert first.store_gpu_blocks == []
2784 - req.num_computed_tokens = 2 * block_size
2785 - meta = sched.build_connector_meta(
2786 - make_scheduler_output(
2787 - {req.request_id: 1},
2788 - cached_req_new_blocks={req.request_id: None},
2789 - )
2790 - )
2791 -
2792 - assert set(meta.store_gpu_blocks) == {
2793 - *(block.block_id for block in attention_blocks),
2794 - *(block.block_id for block in mamba_blocks),
2795 - }
2796 - assert sched._reqs_to_store[req.request_id].num_stored_blocks == [2, 2]
2797 -
2798 -
2750 @pytest.mark.parametrize("gone_via", ["preempted", "finished", "unregistered"])
2751 def test_boundary_handoff_dropped_for_departing_request(gone_via: str) -> None:
2752 """Handoffs for a request leaving this step must not be read.
tests/v1/worker/test_gpu_warmup_blocks.py
+2 -10
@@ -189,7 +189,7 @@ def test_mixed_warmup_reserves_lookahead_blocks():
189 _assert_covers_lookahead(recorder.steps, num_lookahead_tokens)
190
191
192 -@pytest.mark.parametrize("mamba_cache_mode", ["none", "all", "align"])
192 +@pytest.mark.parametrize("mamba_cache_mode", ["none", "align"])
193 def test_warmup_reserves_mamba_speculative_blocks(mamba_cache_mode):
194 """Mamba groups hold the running-state block plus the speculative tail.
195
@@ -227,14 +227,7 @@ def test_warmup_reserves_mamba_speculative_blocks(mamba_cache_mode):
227
228
229 def _hybrid_kv_cache_config(num_blocks: int) -> KVCacheConfig:
230 - """Attention, circular, and Mamba groups exercise every reservation branch.
231 -
232 - "none" and "all" reach the same branch of both `_reserved_block_count` and
233 - `MambaManager`, which tests only for "align". They are still both listed:
234 - the mode is a spec-level input, and having the real manager confirm the
235 - prediction for each is what keeps a future divergence between them from
236 - landing unnoticed.
237 - """
230 + """Attention, circular, and Mamba groups exercise every reservation branch."""
231 return KVCacheConfig(
232 num_blocks=num_blocks,
233 kv_cache_tensors=[],
@@ -242,7 +235,6 @@ def _hybrid_kv_cache_config(num_blocks: int) -> KVCacheConfig:
235 _attention_group(),
236 _circular_group(),
237 _mamba_group("none"),
245 - _mamba_group("all"),
238 _mamba_group("align"),
239 ],
240 )
tests/v1/worker/test_mamba_utils.py
+1 -1
@@ -398,7 +398,7 @@ def _make_kv_cache_config(cfg: _TestConfig, layer_names: list[str]) -> KVCacheCo
398 (cfg.temporal_state_dim,),
399 ),
400 dtypes=(cfg.dtype, cfg.dtype),
401 - mamba_cache_mode="all",
401 + mamba_cache_mode="align",
402 )
403 group = KVCacheGroupSpec(
404 layer_names=layer_names,
vllm/config/cache.py
+1 -18
@@ -59,7 +59,7 @@ CacheDType = Literal[
59
60
61 MambaDType = Literal["auto", "float32", "float16", "bfloat16"]
62 -MambaCacheMode = Literal["all", "align", "none"]
62 +MambaCacheMode = Literal["align", "none"]
63 PrefixCachingHashAlgo = Literal["sha256", "sha256_cbor", "xxhash", "xxhash_cbor"]
64 KVOffloadingBackend = Literal["native", "lmcache"]
65
@@ -75,8 +75,6 @@ class CacheConfig:
75 Accepts None (meaning "use default"). After construction, always int."""
76 user_specified_block_size: bool = field(default=False, init=False)
77 """Whether block_size was explicitly provided. Derived automatically."""
78 - user_specified_mamba_block_size: bool = field(default=False, init=False)
79 - """Whether mamba_block_size was explicitly provided. Derived automatically."""
78 kv_cache_layout: str | None = field(default=None, init=False)
79 """Resolved physical KV cache layout name (a ``KVCacheLayout`` member).
80
@@ -191,7 +189,6 @@ class CacheConfig:
189 """The cache strategy for Mamba layers:
190
191 - "none": set when prefix caching is disabled.
194 - - "all": cache the mamba state of all tokens at position i * block_size.
192 - "align": only cache the mamba state of the last token of each scheduler step and
193 when the token is at position i * block_size. This is the default when prefix
194 caching is enabled.
@@ -293,7 +290,6 @@ class CacheConfig:
290 "mamba_page_size_padded",
291 "skip_page_size_padded",
292 "user_specified_block_size",
296 - "user_specified_mamba_block_size",
293 "_block_size_resolved",
294 # Post-init/derived counters
295 "num_gpu_blocks",
@@ -337,21 +333,8 @@ class CacheConfig:
333 self.block_size = self.DEFAULT_BLOCK_SIZE
334 else:
335 self.user_specified_block_size = True
340 - if self.mamba_block_size is not None:
341 - self.user_specified_mamba_block_size = True
336 return self
337
344 - @field_validator("mamba_cache_mode", mode="after")
345 - @classmethod
346 - def _validate_mamba_cache_mode(cls, mode: MambaCacheMode) -> MambaCacheMode:
347 - if mode == "all":
348 - logger.warning_once(
349 - "Mamba cache mode 'all' is deprecated and will be removed in an "
350 - "upcoming release. If this is a problem, please open an issue "
351 - "at https://github.com/vllm-project/vllm/issues."
352 - )
353 - return mode
354 -
338 @field_validator("cache_dtype", mode="after")
339 @classmethod
340 def _validate_cache_dtype(cls, cache_dtype: CacheDType) -> CacheDType:
vllm/config/vllm.py
-12
@@ -3091,9 +3091,6 @@ class VllmConfig:
3091 if self.parallel_config.use_ubatching:
3092 unsupported.extend(self._get_dbo_unsupported_features())
3093
3094 - if self.cache_config.mamba_cache_mode == "all":
3095 - unsupported.append("mamba cache mode 'all'")
3096 -
3094 return unsupported
3095
3096 def _get_v1_model_runner_unsupported_features(self) -> list[str]:
@@ -3499,10 +3496,6 @@ class VllmConfig:
3496 "SSM state caches, not --enable-mamba-cache-stochastic-"
3497 "rounding, which requires an explicit float16 cache"
3498 )
3502 - if self.cache_config.mamba_cache_mode not in ("none", "align"):
3503 - raise ValueError(
3504 - "RecoverSSM supports only none and align Mamba cache modes"
3505 - )
3499 if (
3500 self.cache_config.mamba_cache_mode == "align"
3501 and not self.use_v2_model_runner
@@ -3535,11 +3528,6 @@ class VllmConfig:
3528 "Mamba2 ReplaySSM speculative decoding requires "
3529 "--mamba-backend flashinfer"
3530 )
3538 - elif self.cache_config.mamba_cache_mode == "all":
3539 - raise ValueError(
3540 - "--use-replayssm supports prefix caching only in align mode; "
3541 - "pass --mamba-cache-mode align"
3542 - )
3531 elif self.mamba_config.backend == MambaBackendEnum.FLASHINFER:
3532 if self.cache_config.mamba_cache_mode == "align":
3533 raise ValueError(
vllm/distributed/kv_transfer/kv_connector/utils.py
+3 -5
@@ -33,16 +33,14 @@ EngineId = str
33 BlockIds = tuple[list[int], ...] | list[list[int]]
34
35
36 -def clip_ssm_state_blocks(
37 - blocks: list[int], num_spec_blocks: int, positional: bool
38 -) -> list[int]:
39 - """Drop speculative scratch slots, retaining position-indexed or running state."""
36 +def clip_ssm_state_blocks(blocks: list[int], num_spec_blocks: int) -> list[int]:
37 + """Drop speculative scratch slots and keep only the running-state slot."""
38 if not blocks:
39 return blocks
40 # Keep at least the running-state slot, even before scratch allocation.
41 if num_scratch := min(num_spec_blocks, len(blocks) - 1):
42 blocks = blocks[:-num_scratch]
45 - return blocks if positional else blocks[-1:]
43 + return blocks[-1:]
44
45
46 def get_kv_connector_cache_layout(vllm_config: VllmConfig | None = None):
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py
+2 -15
@@ -450,13 +450,6 @@ def _validate_hybrid_speculation(vllm_config: VllmConfig) -> None:
450 "MoRIIO hybrid READ supports DSpark speculative decoding only, got "
451 f"method={speculative_config.method!r}"
452 )
453 - if vllm_config.cache_config.mamba_cache_mode == "all":
454 - # Positional allocation counts lookahead, so its local tail can exceed
455 - # the single running-state block supported by hybrid READ.
456 - raise MoRIIOError(
457 - "MoRIIO hybrid READ does not support DSpark with "
458 - "mamba_cache_mode='all'; use 'align'"
459 - )
453
454
455 def _validate_mamba_specs(specs: Collection[MambaSpec]) -> MambaSpec | None:
@@ -521,11 +514,6 @@ class MoRIIOConnectorScheduler:
514 "MoRIIO hybrid (mamba/KDA) transfer is implemented for READ "
515 "mode only; set kv_connector_extra_config.read_mode=true"
516 )
524 - # Only "all" mode keeps a state per block position; the other modes keep
525 - # a single running state in the last slot.
526 - self._ssm_state_slots_are_positional = (
527 - vllm_config.cache_config.mamba_cache_mode == "all"
528 - )
517 self._num_ssm_scratch_blocks = (
518 mamba_spec.num_speculative_blocks if mamba_spec is not None else 0
519 )
@@ -1288,8 +1276,8 @@ class MoRIIOConnectorScheduler:
1276 """Select transferable attention and Mamba block groups.
1277
1278 The wire payload stores the attention group first, followed by every
1291 - Mamba group in transfer-group order. Outside ``mamba_cache_mode="all"``,
1292 - only each group's running-state slot is transferred.
1279 + Mamba group in transfer-group order. Only each group's running-state
1280 + slot is transferred.
1281 """
1282 if not block_ids:
1283 return [], []
@@ -1301,7 +1289,6 @@ class MoRIIOConnectorScheduler:
1289 clip_ssm_state_blocks(
1290 list(transfer_block_ids[group_id]),
1291 self._num_ssm_scratch_blocks,
1304 - self._ssm_state_slots_are_positional,
1292 )
1293 for group_id in self._mamba_group_ids
1294 ]
vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_scheduler.py
+4 -13
@@ -154,11 +154,6 @@ class NixlBaseConnectorScheduler:
154 else None
155 for g in kv_cache_config.transfer_groups
156 ]
157 - # Only "all" mode keeps a state per block position; the other modes
158 - # keep a single running state in the last non-speculative slot.
159 - self._ssm_state_slots_are_positional = (
160 - vllm_config.cache_config.mamba_cache_mode == "all"
161 - )
157
158 # Threshold to decide whether to compute kv cache locally
159 # or pull from a remote node: minimum number of remote
@@ -258,11 +253,9 @@ class NixlBaseConnectorScheduler:
253 out-of-window blocks only prior to the `request_finished_all_groups`
254 hook.
255
261 - SSM groups keep only their state-bearing slots: the trailing
262 - speculative scratch slots always go, and in single-state cache modes
263 - so does everything before the running state (null placeholders and
264 - the previous step's superseded state). "all" mode keeps its remaining
265 - slots, which the worker pairs position-wise.
256 + SSM groups keep only their state-bearing slot: the trailing
257 + speculative scratch slots and everything before the running state
258 + (null placeholders and the previous step's superseded state) go.
259
260 Use this at every block-id exchange point. Pass ``clip_ssm=False``
261 for per-step partial lists (host-buffer save), where the SSM strip
@@ -289,9 +282,7 @@ class NixlBaseConnectorScheduler:
282 and blocks
283 and (n_spec_blocks := self._ssm_spec_blocks[i]) is not None
284 ):
292 - blocks = clip_ssm_state_blocks(
293 - blocks, n_spec_blocks, self._ssm_state_slots_are_positional
294 - )
285 + blocks = clip_ssm_state_blocks(blocks, n_spec_blocks)
286 clipped.append(blocks)
287 return tuple(clipped)
288
vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_worker.py
+6 -7
@@ -3510,13 +3510,12 @@ class NixlBaseConnectorWorker:
3510 if _is_ssm_spec(self._group_spec_types[i]):
3511 if num_decode_blocks == num_prefill_blocks:
3512 continue
3513 - # Only state-bearing slots reach here, single-state modes
3514 - # just one (see get_exchange_clipped_blocks), so differing
3515 - # counts mean position-indexed "all"-mode lists. A longer
3516 - # prefill list carries earlier positions the decode side
3517 - # already has (prefix hit) -> take its tail; a longer decode
3518 - # list holds the position D recomputes itself, which gets
3519 - # no prefill state.
3513 + # Only state-bearing slots reach here, normally just one
3514 + # (see get_exchange_clipped_blocks). A longer prefill list
3515 + # carries earlier positions the decode side already has
3516 + # (prefix hit) -> take its tail; a longer decode list holds
3517 + # the position D recomputes itself, which gets no prefill
3518 + # state.
3519 assert num_decode_blocks - num_prefill_blocks <= 1, (
3520 f"Group {i}: unpairable SSM state slots, "
3521 f"decode={num_decode_blocks} prefill={num_prefill_blocks}"
vllm/distributed/kv_transfer/kv_connector/v1/offloading/scheduler.py
+2 -5
@@ -156,7 +156,7 @@ def resolve_mamba_align_size(
156 """Scan all KV cache groups in *spec* and return the single mamba alignment
157 size, or None if no group requires mamba alignment.
158
159 - For MambaSpec groups in "align" or "all" cache mode the hit window must be
159 + For MambaSpec groups in "align" cache mode the hit window must be
160 rounded down to a multiple of the offloaded chunk size. Asserts that all
161 such groups agree on the same value.
162 """
@@ -164,10 +164,7 @@ def resolve_mamba_align_size(
164 for group in spec.config.groups:
165 tokens_per_block = group.tokens_per_block
166 kv_spec = kv_cache_config.kv_cache_groups[group.group_id].kv_cache_spec
167 - if isinstance(kv_spec, MambaSpec) and kv_spec.mamba_cache_mode in (
168 - "align",
169 - "all",
170 - ):
167 + if isinstance(kv_spec, MambaSpec) and kv_spec.mamba_cache_mode == "align":
168 tokens_per_chunk = tokens_per_block * spec.blocks_per_chunk
169 assert mamba_align_size is None or mamba_align_size == tokens_per_chunk
170 mamba_align_size = tokens_per_chunk
vllm/model_executor/layers/mamba/mamba_mixer.py
+2 -55
@@ -260,8 +260,6 @@ class MambaMixer(MambaBase, PluggableLayer):
260 attn_metadata_raw = forward_context.attn_metadata
261
262 assert self.cache_config is not None
263 - mamba_block_size = self.cache_config.mamba_block_size
264 - is_mamba_cache_all = self.cache_config.mamba_cache_mode == "all"
263
264 attn_metadata: AttentionMetadata | None = None
265 if attn_metadata_raw is not None:
@@ -297,8 +295,6 @@ class MambaMixer(MambaBase, PluggableLayer):
295
296 num_prefill_tokens = attn_metadata.num_prefill_tokens # token count
297 num_decode_tokens = attn_metadata.num_decode_tokens
300 - num_prefills = attn_metadata.num_prefills # request count
301 - num_decodes = attn_metadata.num_decode_tokens # token count (=request)
298 has_prefill = num_prefill_tokens > 0
299 has_decode = num_decode_tokens > 0
300 num_actual_tokens = num_prefill_tokens + num_decode_tokens
@@ -314,34 +310,6 @@ class MambaMixer(MambaBase, PluggableLayer):
310 gate_p = prefill_decode_split.gate_p
311 gate_d = prefill_decode_split.gate_d
312
317 - if is_mamba_cache_all:
318 - block_idx_last_computed_token_d, block_idx_last_computed_token_p = (
319 - torch.split(
320 - attn_metadata.block_idx_last_computed_token,
321 - [num_decodes, num_prefills],
322 - dim=0,
323 - )
324 - )
325 - block_idx_last_scheduled_token_d, block_idx_last_scheduled_token_p = (
326 - torch.split(
327 - attn_metadata.block_idx_last_scheduled_token,
328 - [num_decodes, num_prefills],
329 - dim=0,
330 - )
331 - )
332 -
333 - block_idx_first_scheduled_token_p = (
334 - attn_metadata.block_idx_first_scheduled_token_p
335 - )
336 - num_computed_tokens_p = attn_metadata.num_computed_tokens_p
337 - else:
338 - block_idx_last_computed_token_d = None
339 - block_idx_last_computed_token_p = None
340 - block_idx_last_scheduled_token_d = None
341 - block_idx_last_scheduled_token_p = None
342 - block_idx_first_scheduled_token_p = None
343 - num_computed_tokens_p = None
344 -
313 ssm_outputs = []
314
315 if has_prefill:
@@ -355,11 +323,6 @@ class MambaMixer(MambaBase, PluggableLayer):
323 has_initial_state=has_initial_states_p,
324 cache_indices=state_indices_tensor_p,
325 query_start_loc=query_start_loc_p,
358 - block_idx_first_scheduled_token=block_idx_first_scheduled_token_p,
359 - block_idx_last_scheduled_token=block_idx_last_scheduled_token_p,
360 - initial_state_idx=block_idx_last_computed_token_p,
361 - num_computed_tokens=num_computed_tokens_p,
362 - block_size_to_align=mamba_block_size,
326 metadata=attn_metadata,
327 )
328 # 3. State Space Model sequence transformations.
@@ -383,10 +346,6 @@ class MambaMixer(MambaBase, PluggableLayer):
346 cache_indices=state_indices_tensor_p,
347 has_initial_state=has_initial_states_p,
348 query_start_loc=query_start_loc_p,
386 - block_size=mamba_block_size,
387 - block_idx_first_scheduled_token=block_idx_first_scheduled_token_p,
388 - block_idx_last_scheduled_token=block_idx_last_scheduled_token_p,
389 - initial_state_idx=block_idx_last_computed_token_p,
349 cu_chunk_seqlen=cu_chunk_seqlen_p,
350 last_chunk_indices=last_chunk_indices_p,
351 )
@@ -396,16 +355,6 @@ class MambaMixer(MambaBase, PluggableLayer):
355 # state_indices_tensor_d is assigned when attn_metadata is not None,
356 # and has_decode is only True when attn_metadata is not None
357 assert state_indices_tensor_d is not None
399 - if is_mamba_cache_all:
400 - state_indices_tensor_d_input = state_indices_tensor_d.gather(
401 - 1, block_idx_last_computed_token_d.unsqueeze(1)
402 - ).squeeze(1)
403 - state_indices_tensor_d_output = state_indices_tensor_d.gather(
404 - 1, block_idx_last_scheduled_token_d.unsqueeze(1)
405 - ).squeeze(1)
406 - else:
407 - state_indices_tensor_d_input = state_indices_tensor_d
408 - state_indices_tensor_d_output = state_indices_tensor_d
358 # 2. Convolution sequence transformation
359 conv_out_d = causal_conv1d_update(
360 hidden_states_BC_d.transpose(0, 1),
@@ -414,8 +363,6 @@ class MambaMixer(MambaBase, PluggableLayer):
363 self.conv1d.bias,
364 self.activation,
365 conv_state_indices=state_indices_tensor_d,
417 - block_idx_last_scheduled_token=block_idx_last_scheduled_token_d,
418 - initial_state_idx=block_idx_last_computed_token_d,
366 ).transpose(0, 1)
367
368 # 3. State Space Model sequence transformation.
@@ -437,8 +384,8 @@ class MambaMixer(MambaBase, PluggableLayer):
384 time_proj_bias,
385 z=gate_d.transpose(0, 1),
386 dt_softplus=True,
440 - state_batch_indices=state_indices_tensor_d_input,
441 - dst_state_batch_indices=state_indices_tensor_d_output,
387 + state_batch_indices=state_indices_tensor_d,
388 + dst_state_batch_indices=state_indices_tensor_d,
389 out=scan_outputs_d,
390 )
391 scan_outputs_d = scan_outputs_d.transpose(0, 1)
vllm/model_executor/layers/mamba/mamba_mixer2.py
+17 -182
@@ -549,12 +549,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
549 self._updates_replayssm_trackers = True
550
551 self.num_spec = vllm_config.num_speculative_tokens
552 - if self.num_spec > 0:
553 - self.register_buffer(
554 - "_decode_state_offsets",
555 - torch.arange(1 + self.num_spec, dtype=torch.int32).unsqueeze(0),
556 - persistent=False,
557 - )
552
553 # Pre-compute sizes for forward pass
554 self.tped_intermediate_size = self.intermediate_size // self.tp_size
@@ -732,8 +726,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
726 attn_metadata_raw = forward_context.attn_metadata
727
728 assert self.cache_config is not None
735 - mamba_block_size = self.cache_config.mamba_block_size
736 - is_mamba_cache_all = self.cache_config.mamba_cache_mode == "all"
729 use_spec_decode = self.num_spec > 0
730 ring_start = prev_num_accepted = prev_query_len = None
731
@@ -803,45 +795,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
795 dim=0,
796 )
797
806 - if is_mamba_cache_all:
807 - # If prefix caching is enabled, retrieve the relevant variables
808 - # for prefill and decode
809 - block_idx_last_computed_token_d, block_idx_last_computed_token_p = (
810 - torch.split(
811 - attn_metadata.block_idx_last_computed_token,
812 - [num_decodes, num_prefills],
813 - dim=0,
814 - )
815 - )
816 - block_idx_last_scheduled_token_d, block_idx_last_scheduled_token_p = (
817 - torch.split(
818 - attn_metadata.block_idx_last_scheduled_token,
819 - [num_decodes, num_prefills],
820 - dim=0,
821 - )
822 - )
823 - if attn_metadata.block_idx_last_scheduled_token_prev_step is not None:
824 - block_idx_last_scheduled_token_prev_step_d, _ = torch.split(
825 - attn_metadata.block_idx_last_scheduled_token_prev_step,
826 - [num_decodes, num_prefills],
827 - dim=0,
828 - )
829 - else:
830 - block_idx_last_scheduled_token_prev_step_d = None
831 - # Prefill-only variables:
832 - block_idx_first_scheduled_token_p = (
833 - attn_metadata.block_idx_first_scheduled_token_p
834 - )
835 - num_computed_tokens_p = attn_metadata.num_computed_tokens_p
836 - else:
837 - block_idx_last_computed_token_p = None
838 - block_idx_last_scheduled_token_p = None
839 - block_idx_first_scheduled_token_p = None
840 - block_idx_last_scheduled_token_d = None
841 - block_idx_last_computed_token_d = None
842 - block_idx_last_scheduled_token_prev_step_d = None
843 - num_computed_tokens_p = None
844 -
798 preallocated_ssm_out_d, preallocated_ssm_out_p = torch.split(
799 output[:num_actual_tokens],
800 [num_decode_tokens, num_prefill_tokens],
@@ -858,11 +811,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
811 # to by "state_indices_tensor_p".
812 # In particular, it will always write the state at the
813 # sequence end.
861 - # In addition, "block_idx_first_scheduled_token_p" and
862 - # "block_idx_last_scheduled_token_p"
863 - # are provided (which are pointers into
864 - # "state_indices_tensor_p"), it will write additional cache
865 - # states aligned at "block_size_to_align".
814 x = hidden_states_B_C_p.transpose(
815 0, 1
816 ) # this is the form that causal-conv see
@@ -874,11 +822,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
822 conv_states=conv_state,
823 has_initial_state=has_initial_states_p,
824 cache_indices=state_indices_tensor_p,
877 - block_idx_first_scheduled_token=block_idx_first_scheduled_token_p,
878 - block_idx_last_scheduled_token=block_idx_last_scheduled_token_p,
879 - initial_state_idx=block_idx_last_computed_token_p,
880 - num_computed_tokens=num_computed_tokens_p,
881 - block_size_to_align=mamba_block_size,
825 metadata=attn_metadata,
826 query_start_loc=query_start_loc_p,
827 ).transpose(0, 1)[:num_prefill_tokens]
@@ -891,14 +834,9 @@ class MambaMixer2(MambaBase, PluggableLayer):
834 initial_states = None
835 if has_initial_states_p is not None and prep_initial_states:
836 assert state_indices_tensor_p is not None
894 - kernel_ssm_indices = state_indices_tensor_p
895 - if is_mamba_cache_all:
896 - kernel_ssm_indices = state_indices_tensor_p.gather(
897 - 1, block_idx_last_computed_token_p.unsqueeze(1)
898 - ).squeeze(1)
837 initial_states = torch.where(
838 has_initial_states_p[:, None, None, None],
901 - ssm_state[kernel_ssm_indices],
839 + ssm_state[state_indices_tensor_p],
840 0,
841 )
842
@@ -921,130 +859,29 @@ class MambaMixer2(MambaBase, PluggableLayer):
859 cu_chunk_seqlens=cu_chunk_seqlen_p,
860 last_chunk_indices=last_chunk_indices_p,
861 initial_states=initial_states,
924 - return_intermediate_states=is_mamba_cache_all,
862 dt_softplus=True,
863 dt_limit=(0.0, float("inf")),
864 out=preallocated_ssm_out_p.view(num_prefill_tokens, -1, self.head_dim),
865 state_dtype=ssm_state.dtype,
866 )
867
931 - if is_mamba_cache_all:
932 - assert mamba_block_size is not None
933 - assert state_indices_tensor_p is not None
934 - assert block_idx_first_scheduled_token_p is not None
935 - assert block_idx_last_scheduled_token_p is not None
936 - assert last_chunk_indices_p is not None
937 - assert num_computed_tokens_p is not None
938 -
939 - # The chunk_stride is the number of chunks per mamba block
940 - # e.g., if mamba_block_size = 512 and chunk_size = 256,
941 - # then chunk_stride = 2
942 - chunk_stride = mamba_block_size // chunk_size
943 -
944 - # Save state for sequences with more than just final state,
945 - # batched over sequences so that the number of kernel launches
946 - # does not scale with the number of prefills.
947 - n_blocks_to_fill = (
948 - block_idx_last_scheduled_token_p - block_idx_first_scheduled_token_p
949 - ).clamp(min=0)
950 - total_fill = attn_metadata.num_state_writes_p
951 - if total_fill > 0:
952 - first_chunk = torch.zeros_like(last_chunk_indices_p)
953 - first_chunk[1:] = last_chunk_indices_p[:-1] + 1
954 -
955 - # The chunk that completes this sequence's first mamba block.
956 - # A block spans chunk_stride chunks. When the sequence resumed
957 - # mid-block, the chunks before this step are not in varlen_states,
958 - # so subtract them: the boundary sits earlier by their count.
959 - num_unaligned_computed_tokens = (
960 - num_computed_tokens_p % mamba_block_size
961 - )
962 - unaligned_chunk_shift = num_unaligned_computed_tokens // chunk_size
963 - first_aligned_chunk = (
964 - first_chunk + (chunk_stride - 1) - unaligned_chunk_shift
965 - )
966 -
967 - # Flatten all writes into one list: fill_counts=[2,1,2]
968 - # -> seq_ids=[0,0,1,2,2], write_offsets=[0,1,0,0,1]
969 - fill_counts = n_blocks_to_fill.long()
970 - # output_size, or repeat_interleave reads the size off the device
971 - seq_ids = torch.repeat_interleave(
972 - torch.arange(num_prefills, device=ssm_state.device),
973 - fill_counts,
974 - output_size=total_fill,
975 - )
976 - write_starts = torch.cumsum(fill_counts, dim=0) - fill_counts
977 - write_offsets = torch.arange(
978 - total_fill, device=ssm_state.device
979 - ) - torch.repeat_interleave(
980 - write_starts, fill_counts, output_size=total_fill
981 - )
982 -
983 - cache_blocks_to_fill = state_indices_tensor_p[
984 - seq_ids,
985 - block_idx_first_scheduled_token_p[seq_ids] + write_offsets,
986 - ]
987 - # Write the states. Each row goes to its own cache block,
988 - # so the indexed copy has no duplicate destinations.
989 - ssm_state[cache_blocks_to_fill] = varlen_states[
990 - first_aligned_chunk[seq_ids] + write_offsets * chunk_stride
991 - ]
992 -
993 - # For all seqs, store the last state (note: might be partial):
994 - assert state_indices_tensor_p is not None
995 - ssm_state[
996 - state_indices_tensor_p.gather(
997 - 1, block_idx_last_scheduled_token_p.unsqueeze(1)
998 - ).squeeze(1)
999 - ] = varlen_states[last_chunk_indices_p]
1000 -
1001 - else:
1002 - # update ssm states
1003 - # - varlen state is a (num_prefills, nheads, headdim, dstate)
1004 - # tensor
1005 - assert state_indices_tensor_p is not None
1006 - ssm_state[state_indices_tensor_p] = varlen_states
1007 - if ring_start is not None and self._updates_replayssm_trackers:
1008 - assert prev_num_accepted is not None
1009 - reset_replayssm_ring_trackers(
1010 - ring_start,
1011 - prev_num_accepted,
1012 - prev_query_len,
1013 - state_indices_tensor_p,
1014 - )
868 + # update ssm states
869 + # - varlen state is a (num_prefills, nheads, headdim, dstate)
870 + # tensor
871 + assert state_indices_tensor_p is not None
872 + ssm_state[state_indices_tensor_p] = varlen_states
873 + if ring_start is not None and self._updates_replayssm_trackers:
874 + assert prev_num_accepted is not None
875 + reset_replayssm_ring_trackers(
876 + ring_start,
877 + prev_num_accepted,
878 + prev_query_len,
879 + state_indices_tensor_p,
880 + )
881
882 # Process decode requests
883 if has_decode:
884 assert state_indices_tensor_d is not None
1019 - if is_mamba_cache_all:
1020 - if use_spec_decode:
1021 - assert block_idx_last_scheduled_token_prev_step_d is not None
1022 - input_indices = (
1023 - block_idx_last_scheduled_token_prev_step_d.unsqueeze(1)
1024 - + self._decode_state_offsets
1025 - )
1026 - output_indices = (
1027 - block_idx_last_scheduled_token_d.unsqueeze(1)
1028 - + self._decode_state_offsets
1029 - )
1030 - state_indices_tensor_d_input = state_indices_tensor_d.gather(
1031 - 1, input_indices
1032 - )
1033 - state_indices_tensor_d_output = state_indices_tensor_d.gather(
1034 - 1, output_indices
1035 - )
1036 - else:
1037 - state_indices_tensor_d_input = state_indices_tensor_d.gather(
1038 - 1, block_idx_last_computed_token_d.unsqueeze(1)
1039 - ).squeeze(1)
1040 - state_indices_tensor_d_output = state_indices_tensor_d.gather(
1041 - 1, block_idx_last_scheduled_token_d.unsqueeze(1)
1042 - ).squeeze(1)
1043 - else:
1044 - # Without caching, read and write in-place to the same blocks:
1045 - state_indices_tensor_d_input = state_indices_tensor_d
1046 - state_indices_tensor_d_output = state_indices_tensor_d
1047 -
885 if (
886 self.use_replayssm
887 and self.mamba_config.backend == MambaBackendEnum.FLASHINFER
@@ -1081,8 +918,6 @@ class MambaMixer2(MambaBase, PluggableLayer):
918 self.conv1d.bias,
919 self.activation,
920 conv_state_indices=state_indices_tensor_d,
1084 - block_idx_last_scheduled_token=block_idx_last_scheduled_token_d,
1085 - initial_state_idx=block_idx_last_computed_token_d,
921 num_accepted_tokens=num_accepted_tokens,
922 query_start_loc=query_start_loc_d,
923 # ReplaySSM keeps one physical state block while a speculative
@@ -1207,7 +1042,7 @@ class MambaMixer2(MambaBase, PluggableLayer):
1042 write_pos=attn_metadata.write_pos_d,
1043 is_flush=attn_metadata.is_flush_d,
1044 max_cache_len=self.replayssm_buffer_len,
1210 - state_batch_indices=state_indices_tensor_d_input,
1045 + state_batch_indices=state_indices_tensor_d,
1046 out=preallocated_ssm_out_d,
1047 enable_stochastic_rounding=(
1048 self.mamba_config.enable_stochastic_rounding
@@ -1227,8 +1062,8 @@ class MambaMixer2(MambaBase, PluggableLayer):
1062 D_d,
1063 dt_bias,
1064 dt_softplus=True,
1230 - state_batch_indices=state_indices_tensor_d_input,
1231 - dst_state_batch_indices=state_indices_tensor_d_output,
1065 + state_batch_indices=state_indices_tensor_d,
1066 + dst_state_batch_indices=state_indices_tensor_d,
1067 out=preallocated_ssm_out_d,
1068 num_accepted_tokens=num_accepted_tokens,
1069 cu_seqlens=query_start_loc_d,
vllm/model_executor/models/config.py
+3 -14
@@ -646,20 +646,9 @@ class MambaModelConfig(VerifyAndUpdateConfig):
646 cache_config.mamba_cache_mode,
647 model_config.architecture,
648 )
649 - if (
650 - cache_config.mamba_cache_mode == "all"
651 - and not model_config.supports_mamba_prefix_caching
652 - ):
653 - cache_config.mamba_cache_mode = "align"
654 - logger.warning(
655 - "Hybrid or mamba-based model detected without support "
656 - "for prefix caching with Mamba cache 'all' mode: "
657 - "falling back to 'align' mode."
658 - )
659 - if cache_config.mamba_cache_mode == "align":
660 - assert vllm_config.scheduler_config.enable_chunked_prefill, (
661 - "Chunked prefill is required for mamba cache mode 'align'."
662 - )
649 + assert vllm_config.scheduler_config.enable_chunked_prefill, (
650 + "Chunked prefill is required for mamba cache mode 'align'."
651 + )
652 # By default, mamba block size will be set to max_model_len (see
653 # below). When enabling prefix caching, we align mamba block size
654 # to the block size as the basic granularity for prefix caching.
vllm/model_executor/models/interns2_mobius.py
-6
@@ -418,12 +418,6 @@ class InternS2MobiusForCausalLM(Qwen3_5ForCausalLMBase):
418 self.quant_config = vllm_config.quant_config
419 self.scheduler_config = vllm_config.scheduler_config
420
421 - if vllm_config.cache_config.mamba_cache_mode == "all":
422 - raise NotImplementedError(
423 - "Intern-S2-Mobius does not support 'all' prefix caching; "
424 - "use '--mamba-cache-mode=align' instead."
425 - )
426 -
421 nn.Module.__init__(self)
422 self.config = config
423 self.model = InternS2MobiusModel(
vllm/model_executor/models/lfm2.py
-7
@@ -458,13 +458,6 @@ class Lfm2ForCausalLM(
458 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
459 config = vllm_config.model_config.hf_config
460 quant_config = vllm_config.quant_config
461 - cache_config = vllm_config.cache_config
462 - if cache_config.mamba_cache_mode == "all":
463 - raise NotImplementedError(
464 - "Lfm2 currently does not support 'all' prefix caching, "
465 - "please use '--mamba-cache-mode=align' instead"
466 - )
467 -
461 super().__init__()
462 self.config = config
463 self.model = Lfm2Model(
vllm/model_executor/models/lfm2_moe.py
-7
@@ -573,13 +573,6 @@ class Lfm2MoeForCausalLM(
573 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
574 config = vllm_config.model_config.hf_config
575 quant_config = vllm_config.quant_config
576 - cache_config = vllm_config.cache_config
577 -
578 - if cache_config.mamba_cache_mode == "all":
579 - raise NotImplementedError(
580 - "Lfm2Moe currently does not support 'all' prefix caching, "
581 - "please use '--mamba-cache-mode=align' instead"
582 - )
576
577 super().__init__()
578 self.config = config
vllm/model_executor/models/qwen3_5.py
-6
@@ -329,14 +329,8 @@ class Qwen3_5ForCausalLMBase(
329 config = vllm_config.model_config.hf_text_config
330 self.vllm_config = vllm_config
331 self.model_config = vllm_config.model_config
332 - cache_config = vllm_config.cache_config
332
333 scheduler_config = vllm_config.scheduler_config
335 - if cache_config.mamba_cache_mode == "all":
336 - raise NotImplementedError(
337 - "Qwen3.5 currently does not support 'all' prefix caching, "
338 - "please use '--mamba-cache-mode=align' instead"
339 - )
334 self.quant_config = vllm_config.quant_config
335
336 super().__init__()
vllm/model_executor/models/qwen3_5_mtp.py
-7
@@ -227,13 +227,6 @@ class Qwen3_5MTP(LocalArgmaxMixin, nn.Module, SupportsMultiModal, SupportsPP):
227 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
228 config = vllm_config.model_config.hf_text_config
229 self.vllm_config = vllm_config
230 - cache_config = vllm_config.cache_config
231 - if cache_config.mamba_cache_mode == "all":
232 - raise NotImplementedError(
233 - "Qwen3_5MTP currently does not support 'all' prefix caching, "
234 - "please use '--mamba-cache-mode=align' instead"
235 - )
236 -
230 self.quant_config = vllm_config.quant_config
231
232 super().__init__()
vllm/model_executor/models/qwen3_next.py
-6
@@ -828,14 +828,8 @@ class Qwen3NextForCausalLM(
828 config = vllm_config.model_config.hf_text_config
829 self.vllm_config = vllm_config
830 self.model_config = vllm_config.model_config
831 - cache_config = vllm_config.cache_config
831
832 scheduler_config = vllm_config.scheduler_config
834 - if cache_config.mamba_cache_mode == "all":
835 - raise NotImplementedError(
836 - "Qwen3Next currently does not support 'all' prefix caching, "
837 - "please use '--mamba-cache-mode=align' instead"
838 - )
833 self.quant_config = vllm_config.quant_config
834
835 super().__init__()
vllm/model_executor/models/qwen3_next_mtp.py
-7
@@ -187,13 +187,6 @@ class Qwen3NextMTP(nn.Module, QwenNextMixtureOfExperts):
187 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
188 config = vllm_config.model_config.hf_config
189 self.vllm_config = vllm_config
190 - cache_config = vllm_config.cache_config
191 - if cache_config.mamba_cache_mode == "all":
192 - raise NotImplementedError(
193 - "Qwen3NextMTP currently does not support 'all' prefix caching, "
194 - "please use '--mamba-cache-mode=align' instead"
195 - )
196 -
190 self.quant_config = vllm_config.quant_config
191
192 super().__init__()
vllm/models/kimi_k3/nvidia/kda_metadata.py
+1 -1
@@ -104,7 +104,7 @@ def _mamba_get_block_table_tensor(
104 kv_cache_spec: MambaSpec,
105 mamba_cache_mode: str,
106 ) -> torch.Tensor:
107 - if mamba_cache_mode in ("all", "none"):
107 + if mamba_cache_mode == "none":
108 return block_table
109
110 assert block_table.is_cuda and seq_lens.is_cuda
vllm/models/qwen4_exp/amd/model.py
-5
@@ -638,11 +638,6 @@ class Qwen4ExpForCausalLM(
638 self.quant_config = vllm_config.quant_config
639 self.config = config
640 self.scheduler_config = vllm_config.scheduler_config
641 - if vllm_config.cache_config.mamba_cache_mode == "all":
642 - raise NotImplementedError(
643 - "Qwen4Exp currently does not support 'all' prefix caching, "
644 - "please use '--mamba-cache-mode=align' instead"
645 - )
641 self.model = Qwen4ExpModel(
642 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
643 )
vllm/models/qwen4_exp/amd/mtp.py
-7
@@ -395,13 +395,6 @@ class Qwen4ExpMTP(nn.Module, SupportsPP, Qwen4ExpMixtureOfExperts):
395 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
396 config: Qwen4ExpTextConfig = vllm_config.model_config.hf_text_config
397 self.vllm_config = vllm_config
398 - cache_config = vllm_config.cache_config
399 - if cache_config.mamba_cache_mode == "all":
400 - raise NotImplementedError(
401 - "Qwen4ExpMTP currently does not support 'all' prefix caching, "
402 - "please use '--mamba-cache-mode=align' instead"
403 - )
404 -
398 self.quant_config = vllm_config.quant_config
399
400 super().__init__()
vllm/models/qwen4_exp/nvidia/model.py
-5
@@ -669,11 +669,6 @@ class Qwen4ExpForCausalLM(
669 self.quant_config = vllm_config.quant_config
670 self.config = config
671 self.scheduler_config = vllm_config.scheduler_config
672 - if vllm_config.cache_config.mamba_cache_mode == "all":
673 - raise NotImplementedError(
674 - "Qwen4Exp currently does not support 'all' prefix caching, "
675 - "please use '--mamba-cache-mode=align' instead"
676 - )
672 self.model = Qwen4ExpModel(
673 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
674 )
vllm/models/qwen4_exp/nvidia/mtp.py
-7
@@ -375,13 +375,6 @@ class Qwen4ExpMTP(nn.Module, SupportsPP, Qwen4ExpMixtureOfExperts):
375 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
376 config: Qwen4ExpTextConfig = vllm_config.model_config.hf_text_config
377 self.vllm_config = vllm_config
378 - cache_config = vllm_config.cache_config
379 - if cache_config.mamba_cache_mode == "all":
380 - raise NotImplementedError(
381 - "Qwen4ExpMTP currently does not support 'all' prefix caching, "
382 - "please use '--mamba-cache-mode=align' instead"
383 - )
384 -
378 self.quant_config = vllm_config.quant_config
379
380 super().__init__()
vllm/platforms/interface.py
+9 -28
@@ -939,13 +939,6 @@ class Platform:
939 if mamba_page_size == 0:
940 return
941
942 - # mamba_block_size here should either be user specified value or None
943 - mamba_block_size = (
944 - cache_config.mamba_block_size
945 - if cache_config.user_specified_mamba_block_size
946 - else None
947 - )
948 -
942 # Get kernel block alignment from the backend's supported sizes
943 with set_current_vllm_config(vllm_config):
944 kernel_block_alignment_size = max(
@@ -962,27 +955,15 @@ class Platform:
955 # multiple of 128 so split kernel blocks keep that invariant.
956 kernel_block_alignment_size = max(kernel_block_alignment_size, 128)
957
965 - if cache_config.mamba_cache_mode == "all":
966 - # With prefix caching, align to mamba chunk size for kernel perf
967 - # TODO(tdoublep): this constraint can be relaxed fairly
968 - # easily by changing the way we layout chunks in the
969 - # mamba2 kernels.
970 - base_chunk_size = mamba_block_size or model_config.get_mamba_chunk_size()
971 - assert base_chunk_size is not None
972 - attn_tokens_per_mamba_state = cdiv(mamba_page_size, attn_page_size_1_token)
973 - chunk_size = lcm(base_chunk_size, kernel_block_alignment_size)
974 - attn_block_size = chunk_size * cdiv(attn_tokens_per_mamba_state, chunk_size)
975 - cache_config.mamba_block_size = attn_block_size
976 - else:
977 - # Without prefix caching, use minimum block size that satisfies
978 - # both backend alignment and mamba page size compatibility
979 - attn_block_size = kernel_block_alignment_size * cdiv(
980 - mamba_page_size,
981 - kernel_block_alignment_size * attn_page_size_1_token,
982 - )
983 - indexer_align = cls._get_indexer_block_alignment(vllm_config)
984 - if indexer_align:
985 - attn_block_size = indexer_align * cdiv(attn_block_size, indexer_align)
958 + # Use minimum block size that satisfies both backend alignment and
959 + # mamba page size compatibility
960 + attn_block_size = kernel_block_alignment_size * cdiv(
961 + mamba_page_size,
962 + kernel_block_alignment_size * attn_page_size_1_token,
963 + )
964 + indexer_align = cls._get_indexer_block_alignment(vllm_config)
965 + if indexer_align:
966 + attn_block_size = indexer_align * cdiv(attn_block_size, indexer_align)
967
968 if cache_config.block_size < attn_block_size:
969 cache_config.block_size = attn_block_size
vllm/v1/attention/backends/mamba1_attn.py
+2 -21
@@ -1,7 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 -from dataclasses import dataclass, replace
4 +from dataclasses import dataclass
5 from typing import Any
6
7 from vllm.v1.attention.backend import AttentionBackend, CommonAttentionMetadata
@@ -42,23 +42,4 @@ class Mamba1AttentionMetadataBuilder(
42 fast_build: bool = False,
43 **kwargs: Any,
44 ) -> Mamba1AttentionMetadata:
45 - common = self._compute_common_metadata(common_attn_metadata)
46 -
47 - if (
48 - common.num_prefills > 0
49 - and self.vllm_config.cache_config.mamba_cache_mode == "all"
50 - ):
51 - cu_chunk_seqlen_p, _, last_chunk_indices_p = (
52 - self._build_chunk_metadata_tensors(
53 - self.kv_cache_spec.block_size,
54 - common,
55 - common_attn_metadata,
56 - )
57 - )
58 - return replace(
59 - common,
60 - cu_chunk_seqlen_p=cu_chunk_seqlen_p,
61 - last_chunk_indices_p=last_chunk_indices_p,
62 - )
63 -
64 - return common
45 + return self._compute_common_metadata(common_attn_metadata)
vllm/v1/attention/backends/mamba2_attn.py
-1
@@ -139,7 +139,6 @@ class Mamba2AttentionMetadataBuilder(
139 common = self._compute_common_metadata(
140 common_attn_metadata,
141 num_accepted_tokens=kwargs.get("num_accepted_tokens"),
142 - prev_last_scheduled_idx=kwargs.get("prev_last_scheduled_idx"),
142 num_decode_draft_tokens_cpu=kwargs.get("num_decode_draft_tokens_cpu"),
143 )
144
vllm/v1/attention/backends/mamba_attn.py
+17 -250
@@ -39,7 +39,6 @@ class BaseMambaAttentionMetadata:
39 # the batch has no prefill requests.
40 has_initial_states_p: torch.Tensor | None
41 query_start_loc_p: torch.Tensor | None
42 - num_computed_tokens_p: torch.Tensor | None
42 state_indices_tensor_p: torch.Tensor | None
43
44 # The following tensors are used for decode requests and
@@ -52,13 +51,6 @@ class BaseMambaAttentionMetadata:
51 # Includes the bonus token (so minimum is 1)
52 num_accepted_tokens: torch.Tensor | None # shape: [batch,]
53
55 - # The following tensors are only used for prefix caching in all mode and
56 - # are None if disabled
57 - block_idx_last_scheduled_token: torch.Tensor | None
58 - block_idx_first_scheduled_token_p: torch.Tensor | None
59 - block_idx_last_computed_token: torch.Tensor | None
60 - block_idx_last_scheduled_token_prev_step: torch.Tensor | None
61 -
54 # The following tensor is only used for prefix caching in align mode
55 seq_lens: torch.Tensor
56
@@ -70,9 +62,6 @@ class BaseMambaAttentionMetadata:
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
73 - # Number of block-aligned SSM states the prefill path writes, i.e.
74 - # sum(block_idx_last_scheduled_token_p - block_idx_first_scheduled_token_p).
75 - num_state_writes_p: int = 0
65
66 # The following attributes are for triton implementation of causal_conv1d
67 nums_dict: dict | None = None
@@ -130,47 +119,11 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
119 self.compilation_config.max_cudagraph_capture_size,
120 )
121
133 - if self.vllm_config.cache_config.mamba_cache_mode == "all":
134 - max_num_blocks = (
135 - cdiv(
136 - self.vllm_config.model_config.max_model_len,
137 - kv_cache_spec.block_size,
138 - )
139 - + kv_cache_spec.num_speculative_blocks
140 - )
141 - # TODO: reduce this size as needed for decode-only cudagraph capture
142 - self.state_indices_tensor_d: torch.Tensor = torch.empty(
143 - (
144 - self.decode_cudagraph_max_bs,
145 - max_num_blocks,
146 - ),
147 - dtype=torch.int32,
148 - device=device,
149 - )
150 - self.block_idx_last_scheduled_token: torch.Tensor = torch.empty(
151 - (self.decode_cudagraph_max_bs,),
152 - dtype=torch.int32,
153 - device=device,
154 - )
155 - self.block_idx_last_computed_token: torch.Tensor = torch.empty(
156 - (self.decode_cudagraph_max_bs,),
157 - dtype=torch.int32,
158 - device=device,
159 - )
160 - if self.use_spec_decode:
161 - self.block_idx_last_scheduled_token_prev_step: torch.Tensor = (
162 - torch.empty(
163 - (self.decode_cudagraph_max_bs,),
164 - dtype=torch.int32,
165 - device=device,
166 - )
167 - )
168 - else:
169 - self.state_indices_tensor_d = torch.empty(
170 - (self.decode_cudagraph_max_bs, 1 + self.num_spec_tokens),
171 - dtype=torch.int32,
172 - device=device,
173 - )
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
@@ -259,23 +212,7 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
212 if self.num_spec_tokens > 0:
213 num_accepted_tokens = torch.diff(m.query_start_loc)
214
262 - prev_last_scheduled_idx = None
263 - if (
264 - self.use_spec_decode
265 - and self.vllm_config.cache_config.mamba_cache_mode == "all"
266 - ):
267 - prev_last_scheduled_idx = torch.zeros(
268 - (m.num_reqs,),
269 - dtype=torch.int32,
270 - device=m.query_start_loc.device,
271 - )
272 -
273 - return self.build(
274 - 0,
275 - m,
276 - num_accepted_tokens=num_accepted_tokens,
277 - prev_last_scheduled_idx=prev_last_scheduled_idx,
278 - )
215 + return self.build(0, m, num_accepted_tokens=num_accepted_tokens)
216
217 def build(
218 self,
@@ -284,7 +221,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
221 fast_build: bool = False,
222 *,
223 num_accepted_tokens: torch.Tensor | None = None,
287 - prev_last_scheduled_idx: torch.Tensor | None = None,
224 num_decode_draft_tokens_cpu: torch.Tensor | None = None,
225 **kwargs: Any,
226 ) -> M:
@@ -294,7 +230,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
230 return self._compute_common_metadata(
231 common_attn_metadata,
232 num_accepted_tokens=num_accepted_tokens,
297 - prev_last_scheduled_idx=prev_last_scheduled_idx,
233 num_decode_draft_tokens_cpu=num_decode_draft_tokens_cpu,
234 )
235
@@ -423,56 +358,11 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
358 )
359 return cu_chunk_seqlen_p, seq_idx_p, last_chunk_indices_p
360
426 - def _compute_prefix_caching_block_indices(
427 - self,
428 - common_attn_metadata: CommonAttentionMetadata,
429 - mamba_block_size: int,
430 - ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
431 - num_computed_tokens = common_attn_metadata.compute_num_computed_tokens()
432 - # Block index of the last computed token
433 - block_idx_last_computed_token = (
434 - torch.div(
435 - num_computed_tokens + mamba_block_size - 1,
436 - mamba_block_size,
437 - rounding_mode="floor",
438 - )
439 - - 1
440 - )
441 - # which is <= block index for the first scheduled token
442 - block_idx_first_scheduled_token = (
443 - torch.div(
444 - num_computed_tokens + mamba_block_size,
445 - mamba_block_size,
446 - rounding_mode="floor",
447 - )
448 - - 1
449 - )
450 - # which is <= block index of the last scheduled token
451 - block_idx_last_scheduled_token = (
452 - torch.div(
453 - common_attn_metadata.seq_lens + mamba_block_size - 1,
454 - mamba_block_size,
455 - rounding_mode="floor",
456 - )
457 - - 1
458 - )
459 - # -1 in case it's non-computed and causes later issues with indexing
460 - block_idx_last_computed_token.clamp_(min=0)
461 - # -1 in the case we have a padded request (0 seq-len)
462 - block_idx_last_scheduled_token.clamp_(min=0)
463 -
464 - return (
465 - block_idx_last_computed_token,
466 - block_idx_first_scheduled_token,
467 - block_idx_last_scheduled_token,
468 - )
469 -
361 def _compute_common_metadata(
362 self,
363 common_attn_metadata: CommonAttentionMetadata,
364 *,
365 num_accepted_tokens: torch.Tensor | None = None,
475 - prev_last_scheduled_idx: 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."""
@@ -540,16 +430,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
430 has_initial_states_p = None
431 query_start_loc_p = None
432 query_start_loc_d = None
543 - num_computed_tokens = None
544 - num_computed_tokens_p = None
545 -
546 - # for prefix caching
547 - block_idx_first_scheduled_token = None
548 - block_idx_first_scheduled_token_p = None
549 - block_idx_last_computed_token = None
550 - block_idx_last_scheduled_token = None
551 - block_idx_last_scheduled_token_prev_step = None
552 - num_state_writes_p = 0
433
434 # for causal_conv1d
435 nums_dict, batch_ptr, token_chunk_offset_ptr = None, None, None
@@ -557,35 +437,12 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
437 is_flush_d = None
438 replayssm_scratch = None
439
560 - if self.vllm_config.cache_config.mamba_cache_mode == "all":
561 - num_computed_tokens = common_attn_metadata.compute_num_computed_tokens()
562 -
563 - # Return a tensor of shape (#requests, #max blocks)
564 - state_indices_tensor = common_attn_metadata.block_table_tensor
565 - # Additional cache-related variables:
566 - mamba_block_size = self.kv_cache_spec.block_size
567 - (
568 - block_idx_last_computed_token,
569 - block_idx_first_scheduled_token,
570 - block_idx_last_scheduled_token,
571 - ) = self._compute_prefix_caching_block_indices(
572 - common_attn_metadata, mamba_block_size
573 - )
574 - if self.use_spec_decode and prev_last_scheduled_idx is not None:
575 - fallback = (num_computed_tokens - 1) // mamba_block_size
576 - fallback.clamp_(min=0)
577 - block_idx_last_scheduled_token_prev_step = torch.where(
578 - prev_last_scheduled_idx >= 0,
579 - prev_last_scheduled_idx,
580 - fallback,
581 - )
582 - else:
583 - state_indices_tensor = mamba_get_block_table_tensor(
584 - common_attn_metadata.block_table_tensor,
585 - common_attn_metadata.seq_lens,
586 - self.kv_cache_spec,
587 - self.vllm_config.cache_config.mamba_cache_mode,
588 - )
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)
@@ -595,11 +452,8 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
452 [num_decodes, num_prefills],
453 dim=0,
454 )
598 - if self.vllm_config.cache_config.mamba_cache_mode != "all":
599 - state_indices_tensor_d = state_indices_tensor_d[
600 - :, : 1 + self.num_spec_tokens
601 - ]
602 - state_indices_tensor_p = state_indices_tensor_p[:, 0]
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]
@@ -613,8 +467,7 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
467 num_accepted_tokens = num_accepted_tokens[:num_decodes]
468
469 if num_prefills > 0:
616 - if num_computed_tokens is None:
617 - num_computed_tokens = common_attn_metadata.compute_num_computed_tokens()
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 :]
@@ -636,33 +489,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
489 )
490 )
491
639 - if self.vllm_config.cache_config.mamba_cache_mode == "all":
640 - assert num_computed_tokens is not None
641 - num_computed_tokens_p = num_computed_tokens[
642 - num_reqs - num_prefills : num_reqs
643 - ]
644 - assert block_idx_first_scheduled_token is not None
645 - block_idx_first_scheduled_token_p = block_idx_first_scheduled_token[
646 - num_reqs - num_prefills : num_reqs
647 - ]
648 -
649 - # How many block-aligned states prefill will write: the same
650 - # (block_idx_last - block_idx_first) as above, from CPU data so
651 - # the mixer needs no D2H read. The -1 in both indices cancels.
652 - block_size = self.kv_cache_spec.block_size
653 - seq_lens_p_cpu = seq_lens_cpu[num_reqs - num_prefills : num_reqs]
654 - num_computed_tokens_p_cpu, _ = self._prefill_cpu_metadata(
655 - common_attn_metadata, num_reqs, num_prefills, num_decode_tokens
656 - )
657 - num_state_writes_p = int(
658 - (
659 - (seq_lens_p_cpu + block_size - 1) // block_size
660 - - (num_computed_tokens_p_cpu + block_size) // block_size
661 - )
662 - .clamp(min=0)
663 - .sum()
664 - )
665 -
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
@@ -764,14 +590,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
590 replayssm_scratch=replayssm_scratch,
591 num_accepted_tokens=num_accepted_tokens,
592 query_start_loc_d=query_start_loc_d,
767 - block_idx_last_scheduled_token=block_idx_last_scheduled_token,
768 - block_idx_first_scheduled_token_p=block_idx_first_scheduled_token_p,
769 - num_state_writes_p=num_state_writes_p,
770 - block_idx_last_computed_token=block_idx_last_computed_token,
771 - block_idx_last_scheduled_token_prev_step=(
772 - block_idx_last_scheduled_token_prev_step
773 - ),
774 - num_computed_tokens_p=num_computed_tokens_p,
593 num_reqs=num_reqs,
594 seq_lens=common_attn_metadata.seq_lens,
595 nums_dict=nums_dict,
@@ -791,11 +609,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
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
794 - block_idx_last_scheduled_token = metadata.block_idx_last_scheduled_token
795 - block_idx_last_computed_token = metadata.block_idx_last_computed_token
796 - block_idx_last_scheduled_token_prev_step = (
797 - metadata.block_idx_last_scheduled_token_prev_step
798 - )
612 write_pos_d = metadata.write_pos_d
613 is_flush_d = metadata.is_flush_d
614 bc_pre_scratch = metadata.bc_pre_scratch
@@ -824,44 +637,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
637 1 # pad with 1st slot index
638 )
639
827 - if self.vllm_config.cache_config.mamba_cache_mode == "all":
828 - assert block_idx_last_scheduled_token is not None
829 - assert block_idx_last_computed_token is not None
830 - self.block_idx_last_scheduled_token[: metadata.num_decodes].copy_(
831 - block_idx_last_scheduled_token[: metadata.num_decodes],
832 - non_blocking=True,
833 - )
834 - block_idx_last_scheduled_token = self.block_idx_last_scheduled_token[
835 - :padded_bs
836 - ]
837 - block_idx_last_scheduled_token[metadata.num_decodes :] = 0
838 -
839 - self.block_idx_last_computed_token[: metadata.num_decodes].copy_(
840 - block_idx_last_computed_token[: metadata.num_decodes],
841 - non_blocking=True,
842 - )
843 - block_idx_last_computed_token = self.block_idx_last_computed_token[
844 - :padded_bs
845 - ]
846 - block_idx_last_computed_token[metadata.num_decodes :] = 0
847 -
848 - if (
849 - self.use_spec_decode
850 - and block_idx_last_scheduled_token_prev_step is not None
851 - ):
852 - self.block_idx_last_scheduled_token_prev_step[
853 - : metadata.num_decodes
854 - ].copy_(
855 - block_idx_last_scheduled_token_prev_step[
856 - : metadata.num_decodes
857 - ],
858 - non_blocking=True,
859 - )
860 - block_idx_last_scheduled_token_prev_step = (
861 - self.block_idx_last_scheduled_token_prev_step[:padded_bs]
862 - )
863 - block_idx_last_scheduled_token_prev_step[metadata.num_decodes :] = 0
864 -
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
@@ -914,11 +689,6 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
689 bc_pre_scratch=bc_pre_scratch,
690 replayssm_scratch=replayssm_scratch,
691 replayssm_state_indices_d=replayssm_state_indices_d,
917 - block_idx_last_scheduled_token=block_idx_last_scheduled_token,
918 - block_idx_last_computed_token=block_idx_last_computed_token,
919 - block_idx_last_scheduled_token_prev_step=(
920 - block_idx_last_scheduled_token_prev_step
921 - ),
692 )
693
694 def update_block_table(
@@ -950,11 +720,8 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
720 [metadata.num_decodes, metadata.num_prefills],
721 dim=0,
722 )
953 - if self.vllm_config.cache_config.mamba_cache_mode != "all":
954 - state_indices_tensor_d = state_indices_tensor_d[
955 - :, : 1 + self.num_spec_tokens
956 - ]
957 - state_indices_tensor_p = state_indices_tensor_p[:, 0]
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,
vllm/v1/attention/backends/short_conv_attn.py
+3 -10
@@ -283,7 +283,6 @@ class PleShortConvAttentionMetadataBuilder(ShortConvAttentionMetadataBuilder):
283
284 has_initial_states_p = None
285 has_initial_states_d = None
286 - num_computed_tokens_p = None
286
287 # Per-request classification by mask, NOT by position. With
288 # spec-decode, the front decode group can contain both spec-decode
@@ -402,10 +401,9 @@ class PleShortConvAttentionMetadataBuilder(ShortConvAttentionMetadataBuilder):
401 assert non_spec_query_start_loc is not None
402 query_start_loc_d = non_spec_query_start_loc[: num_decodes + 1]
403 if num_prefills > 0:
405 - num_computed_tokens_p = num_computed_tokens[
406 - num_decodes : num_decodes + num_prefills
407 - ]
408 - has_initial_states_p = num_computed_tokens_p > 0
404 + has_initial_states_p = (
405 + num_computed_tokens[num_decodes : num_decodes + num_prefills] > 0
406 + )
407 assert non_spec_query_start_loc is not None
408 query_start_loc_p = (
409 non_spec_query_start_loc[num_decodes:] - num_decode_tokens
@@ -478,11 +476,6 @@ class PleShortConvAttentionMetadataBuilder(ShortConvAttentionMetadataBuilder):
476 query_start_loc_d=query_start_loc_d,
477 state_indices_tensor_p=state_indices_tensor_p,
478 state_indices_tensor_d=state_indices_tensor_d,
481 - num_computed_tokens_p=num_computed_tokens_p,
482 - block_idx_last_scheduled_token=None,
483 - block_idx_first_scheduled_token_p=None,
484 - block_idx_last_computed_token=None,
485 - block_idx_last_scheduled_token_prev_step=None,
479 seq_lens=m.seq_lens,
480 )
481
vllm/v1/attention/backends/utils.py
+1 -6
@@ -1162,11 +1162,6 @@ def mamba_get_block_table_tensor(
1162 """Get the block table tensor for mamba kernels from the input
1163 common_attn_metadata.block_table_tensor given different mamba cache modes.
1164
1165 - - "all": input (#requests, cdiv(max_model_len, block_size)
1166 - + num_speculative_blocks);
1167 - output (#requests, cdiv(max_model_len, block_size)
1168 - + num_speculative_blocks).
1169 -
1165 - "none": input (#requests, 1 + num_speculative_blocks);
1166 output (#requests, 1 + num_speculative_blocks).
1167
@@ -1174,7 +1169,7 @@ def mamba_get_block_table_tensor(
1169 output (#requests, 1 + num_speculative_blocks), which are the last
1170 1 + num_speculative_blocks of each request.
1171 """
1177 - if mamba_cache_mode in ("all", "none"):
1172 + if mamba_cache_mode == "none":
1173 return block_table
1174 else:
1175 assert isinstance(kv_cache_spec, MambaSpec)
vllm/v1/kv_cache_interface.py
+1 -6
@@ -1079,12 +1079,7 @@ class MambaSpec(KVCacheSpec):
1079 return None
1080
1081 def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
1082 - if vllm_config.cache_config.mamba_cache_mode == "all":
1083 - max_model_len = vllm_config.model_config.max_model_len
1084 - return (
1085 - cdiv(max_model_len, self.block_size) + self.num_speculative_blocks
1086 - ) * self.page_size_bytes
1087 - elif vllm_config.cache_config.mamba_cache_mode == "align":
1082 + if vllm_config.cache_config.mamba_cache_mode == "align":
1083 return self.page_size_bytes * (
1084 2 + self.num_speculative_blocks + self.num_prefill_checkpoint_blocks
1085 )
vllm/v1/worker/gpu_model_runner.py
-32
@@ -967,11 +967,6 @@ class GPUModelRunner(
967 self.mamba_state_idx: dict[str, int] = {}
968 self._mamba_bufs: mamba_utils.MambaBuffers | None = None
969 self._mamba_state_copy_funcs: MambaStateCopyFuncsByType | None = None
970 - self.mamba_prev_last_scheduled_idx: CpuGpuBuffer | None = None
971 - if self.cache_config.mamba_cache_mode == "all" and self.num_spec_tokens > 0:
972 - self.mamba_prev_last_scheduled_idx = self._make_buffer(
973 - self.max_num_reqs, dtype=torch.int32
974 - )
970 self.layerwise_nvtx_hooks_registered = False
971
972 def update_max_model_len(self, max_model_len: int) -> None:
@@ -1608,17 +1603,6 @@ class GPUModelRunner(
1603 assert self.num_accepted_tokens_event is not None
1604 self.num_accepted_tokens_event.record()
1605
1611 - if self.cache_config.mamba_cache_mode == "all":
1612 - mamba_utils.postprocess_mamba_all(
1613 - scheduler_output,
1614 - self.kv_cache_config,
1615 - self.input_batch,
1616 - self.requests,
1617 - self.mamba_state_idx,
1618 - self.num_spec_tokens,
1619 - num_reqs,
1620 - )
1621 -
1606 def _update_streaming_request(
1607 self, req_id: str, new_req_data: NewRequestData
1608 ) -> CachedRequestState:
@@ -2127,15 +2111,6 @@ class GPUModelRunner(
2111 self.num_accepted_tokens.np.fill(1)
2112 self.num_accepted_tokens.gpu.fill_(1)
2113
2130 - if self.mamba_prev_last_scheduled_idx is not None:
2131 - mamba_utils.preprocess_mamba_all_specdec(
2132 - scheduler_output,
2133 - self.input_batch,
2134 - self.mamba_state_idx,
2135 - num_reqs,
2136 - self.mamba_prev_last_scheduled_idx,
2137 - )
2138 -
2114 # Update num_computed_tokens on GPU. In async spec decode,
2115 # CPU values are optimistic (all drafts accepted). The kernel
2116 # corrects on GPU using the previous step's
@@ -2511,13 +2486,6 @@ class GPUModelRunner(
2486 :num_reqs_padded
2487 ],
2488 )
2514 - if (
2515 - isinstance(builder, Mamba2AttentionMetadataBuilder)
2516 - and self.mamba_prev_last_scheduled_idx is not None
2517 - ):
2518 - extra_attn_metadata_args["prev_last_scheduled_idx"] = (
2519 - self.mamba_prev_last_scheduled_idx.gpu[:num_reqs_padded]
2520 - )
2489
2490 if for_cudagraph_capture:
2491 attn_metadata_i = builder.build_for_cudagraph_capture(
vllm/v1/worker/mamba_utils.py
-47
@@ -1535,53 +1535,6 @@ def preprocess_mamba(
1535 do_mamba_copy_block(copy_bufs)
1536
1537
1538 -def postprocess_mamba_all(
1539 - scheduler_output: SchedulerOutput,
1540 - kv_cache_config: KVCacheConfig,
1541 - input_batch: InputBatch,
1542 - requests: dict[str, CachedRequestState],
1543 - mamba_state_idx: dict[str, int],
1544 - num_spec_tokens: int,
1545 - num_reqs: int,
1546 -):
1547 - """All-mode postprocess (only meaningful with num_spec_tokens > 0):
1548 - record per-request the block index of the last token scheduled this
1549 - step, so the next step can anchor its in-place writes when accepted
1550 - drafts leave the sequence at a non-block-aligned position.
1551 - """
1552 - if num_spec_tokens <= 0:
1553 - return
1554 - mamba_groups = get_mamba_groups(kv_cache_config)
1555 - block_sizes = {mamba_spec.block_size for mamba_spec in mamba_groups}
1556 - assert len(block_sizes) == 1, "all mamba groups must share block_size"
1557 - block_size = next(iter(block_sizes))
1558 - full_decode_len = 1 + num_spec_tokens
1559 - scheduled = scheduler_output.num_scheduled_tokens
1560 - for req_id in input_batch.req_ids[:num_reqs]:
1561 - num_query = scheduled.get(req_id, 0)
1562 - if num_query == full_decode_len:
1563 - req = requests[req_id]
1564 - seq_len = req.num_computed_tokens + num_query
1565 - mamba_state_idx[req_id] = max(0, (seq_len - 1) // block_size)
1566 - else:
1567 - mamba_state_idx.pop(req_id, None)
1568 -
1569 -
1570 -def preprocess_mamba_all_specdec(
1571 - scheduler_output: SchedulerOutput,
1572 - input_batch: InputBatch,
1573 - mamba_state_idx: dict[str, int],
1574 - num_reqs: int,
1575 - prev_last_scheduled_idx_buf: CpuGpuBuffer,
1576 -) -> None:
1577 - cleanup_mamba_state_idx(scheduler_output, mamba_state_idx)
1578 - np_view = prev_last_scheduled_idx_buf.np
1579 - for i, req_id in enumerate(input_batch.req_ids[:num_reqs]):
1580 - np_view[i] = mamba_state_idx.get(req_id, -1)
1581 - np_view[num_reqs:].fill(-1)
1582 - prev_last_scheduled_idx_buf.copy_to_gpu()
1583 -
1584 -
1538 def postprocess_mamba_align_gpu(
1539 *,
1540 bufs: "MambaBuffers",