A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 5, 21:00 UTC
[Mamba][Bugfix] Raise on insufficient cache blocks instead of silently capping cudagraph sizes (#38270)
Signed-off-by: NickLucche <nlucches@redhat.com>
Nicolò Lucchesi committed
Mar 30, 2026 at 11:41 UTC
cc06b4e86b2beb04fbee3e6d9167cc97f1491b1f
4 files changed
+41
-131
tests/compile/test_config.py
-42
@@ -577,48 +577,6 @@ def test_compile_sizes_padding_validation():
577
dispatcher.initialize_cudagraph_keys(CUDAGraphMode.NONE) # Should not raise
578
579
580
-@pytest.mark.parametrize(
581
- "capture_sizes, max_size, num_blocks, expected_sizes, expected_max",
582
- [
583
- # Normal capping: sizes filtered to <= num_blocks
584
- (
585
- [1, 2, 4, 8, 16, 32, 64, 128, 256, 512],
586
- 512,
587
- 200,
588
- [1, 2, 4, 8, 16, 32, 64, 128],
589
- 128,
590
- ),
591
- # No capping needed: num_blocks >= max
592
- ([1, 2, 4, 8, 16], 16, 1000, [1, 2, 4, 8, 16], 16),
593
- # Exact boundary: num_blocks == max (no capping)
594
- ([1, 2, 4, 8, 16, 32], 32, 32, [1, 2, 4, 8, 16, 32], 32),
595
- # All sizes capped: num_blocks < smallest size
596
- ([8, 16, 32], 32, 4, [], 0),
597
- # num_blocks <= 0: early return, no change
598
- ([1, 2, 4], 4, 0, [1, 2, 4], 4),
599
- ],
600
-)
601
-def test_adjust_cudagraph_sizes_for_mamba_cache(
602
- capture_sizes, max_size, num_blocks, expected_sizes, expected_max
603
-):
604
- """Test that cudagraph capture sizes are correctly capped to fit
605
- available Mamba cache blocks.
606
-
607
- See: https://github.com/vllm-project/vllm/issues/34094
608
- """
609
- config = CompilationConfig(
610
- cudagraph_capture_sizes=capture_sizes,
611
- max_cudagraph_capture_size=max_size,
612
- cudagraph_mode=CUDAGraphMode.NONE,
613
- )
614
- config.adjust_cudagraph_sizes_for_mamba_cache(num_blocks)
615
- assert config.cudagraph_capture_sizes == expected_sizes
616
- assert config.max_cudagraph_capture_size == expected_max
617
- # Invariant: last element == max_cudagraph_capture_size
618
- if expected_sizes:
619
- assert config.cudagraph_capture_sizes[-1] == config.max_cudagraph_capture_size
620
-
621
-
580
def test_inductor_asserts_default_disabled(monkeypatch):
581
"""Test that inductor runtime asserts are disabled by default
582
(INFO logging level) on torch < 2.12."""
tests/v1/worker/test_gpu_model_runner.py
+7
-22
@@ -1191,9 +1191,9 @@ def test_is_uniform_decode() -> None:
1191
current_platform.is_rocm(),
1192
reason="Attention backend FLASHINFER is not supported on ROCm.",
1193
)
1194
-def test_cudagraph_sizes_capped_for_mamba_cache():
1195
- """Test that cudagraph capture sizes are capped to num_blocks for
1196
- hybrid models with Mamba layers.
1194
+def test_mamba_cache_raises_when_max_num_seqs_exceeds_blocks():
1195
+ """Test that a ValueError is raised when max_num_seqs exceeds the
1196
+ available Mamba cache blocks for hybrid models with FULL cudagraphs.
1197
1198
See: https://github.com/vllm-project/vllm/issues/34094
1199
"""
@@ -1284,23 +1284,8 @@ def test_cudagraph_sizes_capped_for_mamba_cache():
1284
)[0]
1285
num_blocks = kv_cache_config.num_blocks
1286
1287
- # Set max_cudagraph_capture_size to a value larger than num_blocks
1288
- # to trigger the Mamba capping logic.
1289
- large_max = num_blocks + 100
1290
- compilation_config = vllm_config.compilation_config
1291
- compilation_config.max_cudagraph_capture_size = large_max
1292
- compilation_config.cudagraph_capture_sizes = [
1293
- s for s in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] if s <= large_max
1294
- ]
1287
+ # Force max_num_seqs to exceed num_blocks so the check triggers.
1288
+ runner.max_num_reqs = num_blocks + 100
1289
1296
- runner.initialize_kv_cache(kv_cache_config)
1297
-
1298
- # After initialization, cudagraph sizes should be capped
1299
- assert compilation_config.max_cudagraph_capture_size <= num_blocks
1300
- assert all(s <= num_blocks for s in compilation_config.cudagraph_capture_sizes)
1301
- # Invariant: last element == max
1302
- if compilation_config.cudagraph_capture_sizes:
1303
- assert (
1304
- compilation_config.cudagraph_capture_sizes[-1]
1305
- == compilation_config.max_cudagraph_capture_size
1306
- )
1290
+ with pytest.raises(ValueError, match="max_num_seqs"):
1291
+ runner.initialize_kv_cache(kv_cache_config)
vllm/config/compilation.py
-52
@@ -1279,58 +1279,6 @@ class CompilationConfig:
1279
self.max_cudagraph_capture_size = rounded_sizes[-1]
1280
self.cudagraph_capture_sizes = rounded_sizes
1281
1282
- def adjust_cudagraph_sizes_for_mamba_cache(
1283
- self, num_mamba_cache_blocks: int
1284
- ) -> None:
1285
- """Cap cudagraph capture sizes to available Mamba cache blocks.
1286
-
1287
- For hybrid Mamba/attention models, the Mamba conv_state and
1288
- ssm_state tensors have their first dimension equal to num_blocks
1289
- (from KVCacheConfig). During CUDA graph capture the decode batch
1290
- size equals num_tokens, so capture sizes exceeding num_blocks
1291
- would cause out-of-bounds access in Mamba kernels.
1292
-
1293
- See: https://github.com/vllm-project/vllm/issues/34094
1294
- """
1295
- if not self.cudagraph_capture_sizes or num_mamba_cache_blocks <= 0:
1296
- return
1297
-
1298
- assert self.max_cudagraph_capture_size is not None
1299
-
1300
- if num_mamba_cache_blocks >= self.max_cudagraph_capture_size:
1301
- return
1302
-
1303
- capped_sizes = [
1304
- s for s in self.cudagraph_capture_sizes if s <= num_mamba_cache_blocks
1305
- ]
1306
-
1307
- if len(capped_sizes) == 0:
1308
- logger.warning(
1309
- "No valid cudagraph capture sizes remain after capping "
1310
- "to Mamba cache blocks (%d). The smallest capture size "
1311
- "was %d. Disabling cudagraph capture. Consider reducing "
1312
- "max_num_seqs or increasing available GPU memory.",
1313
- num_mamba_cache_blocks,
1314
- self.cudagraph_capture_sizes[0],
1315
- )
1316
- self.cudagraph_capture_sizes = []
1317
- self.max_cudagraph_capture_size = 0
1318
- return
1319
-
1320
- logger.warning(
1321
- "Capping cudagraph capture sizes from max %d to %d to fit "
1322
- "Mamba cache blocks (%d blocks available). This limits the "
1323
- "maximum batch size that can use CUDA graphs. To increase "
1324
- "this limit, reduce max_num_seqs or increase available GPU "
1325
- "memory.",
1326
- self.max_cudagraph_capture_size,
1327
- capped_sizes[-1],
1328
- num_mamba_cache_blocks,
1329
- )
1330
-
1331
- self.max_cudagraph_capture_size = capped_sizes[-1]
1332
- self.cudagraph_capture_sizes = capped_sizes
1333
-
1282
def get_compile_ranges(self) -> list[Range]:
1283
"""Get the compile ranges for the compilation config."""
1284
if self.compile_ranges_endpoints is None:
vllm/v1/worker/gpu_model_runner.py
+34
-15
@@ -5800,7 +5800,7 @@ class GPUModelRunner(
5800
)
5801
self.cache_config.num_gpu_blocks_override = saved_override
5802
5803
- self.initialize_kv_cache(minimal_config)
5803
+ self.initialize_kv_cache(minimal_config, is_profiling=True)
5804
self.cache_config.num_gpu_blocks = minimal_config.num_blocks
5805
5806
logger.debug("Initialized minimal KV cache for CUDA graph profiling")
@@ -6121,7 +6121,11 @@ class GPUModelRunner(
6121
torch.accelerator.synchronize()
6122
self.maybe_remove_all_loras(self.lora_config)
6123
6124
- def initialize_attn_backend(self, kv_cache_config: KVCacheConfig) -> None:
6124
+ def initialize_attn_backend(
6125
+ self,
6126
+ kv_cache_config: KVCacheConfig,
6127
+ is_profiling: bool = False,
6128
+ ) -> None:
6129
"""
6130
Initialize the attention backends and attention metadata builders.
6131
"""
@@ -6193,7 +6197,9 @@ class GPUModelRunner(
6197
6198
# Resolve cudagraph_mode before actually initialize metadata_builders
6199
self._check_and_update_cudagraph_mode(
6196
- attention_backend_list, kv_cache_config.kv_cache_groups
6200
+ attention_backend_list,
6201
+ kv_cache_config.kv_cache_groups,
6202
+ is_profiling=is_profiling,
6203
)
6204
6205
# Check if attention backend supports PCP&DCP and related features.
@@ -6237,6 +6243,7 @@ class GPUModelRunner(
6243
self,
6244
attention_backends: list[set[type[AttentionBackend]]],
6245
kv_cache_groups: list[KVCacheGroupSpec],
6246
+ is_profiling: bool = False,
6247
) -> None:
6248
"""
6249
Resolve the cudagraph_mode when there are multiple attention
@@ -6377,21 +6384,29 @@ class GPUModelRunner(
6384
self.uniform_decode_query_len, self.parallel_config.tensor_parallel_size
6385
)
6386
6380
- # If the model has Mamba layers and cudagraph mode includes FULL
6381
- # decode, cap cudagraph capture sizes to the number of available
6382
- # Mamba cache blocks. Each decode request needs one conv_state
6383
- # cache line, so capture batch sizes cannot exceed num_blocks.
6384
- # Only FULL decode graphs are affected because PIECEWISE captures
6385
- # run GDN/Mamba ops eagerly (prefill path, no causal_conv1d_update).
6387
+ # For Mamba models with FULL decode cudagraphs, each decode
6388
+ # sequence needs one Mamba cache block. The decode cudagraph
6389
+ # dispatcher already caps batch sizes at max_num_seqs, so we just
6390
+ # need to verify that enough blocks exist. Raising here instead
6391
+ # of silently capping cudagraph_capture_sizes avoids unintended
6392
+ # restrictions on PIECEWISE (prefill) cudagraphs.
6393
# See: https://github.com/vllm-project/vllm/issues/34094
6387
- if cudagraph_mode.has_full_cudagraphs():
6394
+ if cudagraph_mode.has_full_cudagraphs() and not is_profiling:
6395
has_mamba = any(
6396
isinstance(g.kv_cache_spec, MambaSpec) for g in kv_cache_groups
6397
)
6398
if has_mamba and self.kv_cache_config is not None:
6392
- self.compilation_config.adjust_cudagraph_sizes_for_mamba_cache(
6393
- self.kv_cache_config.num_blocks
6394
- )
6399
+ num_blocks = self.kv_cache_config.num_blocks
6400
+ if self.max_num_reqs > num_blocks:
6401
+ raise ValueError(
6402
+ f"max_num_seqs ({self.max_num_reqs}) exceeds "
6403
+ f"available Mamba cache blocks ({num_blocks}). "
6404
+ f"Each decode sequence requires one Mamba cache "
6405
+ f"block, so CUDA graph capture cannot proceed. "
6406
+ f"Please lower max_num_seqs to at most "
6407
+ f"{num_blocks} or increase "
6408
+ f"gpu_memory_utilization."
6409
+ )
6410
6411
# Trigger cudagraph dispatching keys initialization after
6412
# resolved cudagraph mode.
@@ -6752,7 +6767,11 @@ class GPUModelRunner(
6767
else:
6768
break
6769
6755
- def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
6770
+ def initialize_kv_cache(
6771
+ self,
6772
+ kv_cache_config: KVCacheConfig,
6773
+ is_profiling: bool = False,
6774
+ ) -> None:
6775
"""
6776
Initialize KV cache based on `kv_cache_config`.
6777
Args:
@@ -6764,7 +6783,7 @@ class GPUModelRunner(
6783
self._mamba_copy_bufs = None
6784
self.may_add_encoder_only_layers_to_kv_cache_config()
6785
self.maybe_add_kv_sharing_layers_to_kv_cache_groups(kv_cache_config)
6767
- self.initialize_attn_backend(kv_cache_config)
6786
+ self.initialize_attn_backend(kv_cache_config, is_profiling=is_profiling)
6787
# The kernel block size for all KV cache groups. For example, if
6788
# kv_cache_manager uses block_size 256 for a given group, but the attention
6789
# backends for that group only supports block_size 64, we will return