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 7, 07:45 UTC
[Bugfix] Cap FULL decode cudagraph sizes for Mamba/hybrid models (#34094) (#34571)
Signed-off-by: haosdent <haosdent@gmail.com> Co-authored-by: zjy0516 <riverclouds.zhu@qq.com>
haosdent committed
Mar 4, 2026 at 18:56 UTC
d6e04f4c43612eb9fbbafc3723da8144d54dfde9
4 files changed
+230
tests/compile/test_config.py
+42
@@ -570,3 +570,45 @@ def test_compile_sizes_padding_validation():
570
assert sorted(config.compile_sizes) == [3, 5, 7]
571
dispatcher = CudagraphDispatcher(_create_vllm_config_for_validation(config))
572
dispatcher.initialize_cudagraph_keys(CUDAGraphMode.NONE) # Should not raise
573
+
574
+
575
+@pytest.mark.parametrize(
576
+ "capture_sizes, max_size, num_blocks, expected_sizes, expected_max",
577
+ [
578
+ # Normal capping: sizes filtered to <= num_blocks
579
+ (
580
+ [1, 2, 4, 8, 16, 32, 64, 128, 256, 512],
581
+ 512,
582
+ 200,
583
+ [1, 2, 4, 8, 16, 32, 64, 128],
584
+ 128,
585
+ ),
586
+ # No capping needed: num_blocks >= max
587
+ ([1, 2, 4, 8, 16], 16, 1000, [1, 2, 4, 8, 16], 16),
588
+ # Exact boundary: num_blocks == max (no capping)
589
+ ([1, 2, 4, 8, 16, 32], 32, 32, [1, 2, 4, 8, 16, 32], 32),
590
+ # All sizes capped: num_blocks < smallest size
591
+ ([8, 16, 32], 32, 4, [], 0),
592
+ # num_blocks <= 0: early return, no change
593
+ ([1, 2, 4], 4, 0, [1, 2, 4], 4),
594
+ ],
595
+)
596
+def test_adjust_cudagraph_sizes_for_mamba_cache(
597
+ capture_sizes, max_size, num_blocks, expected_sizes, expected_max
598
+):
599
+ """Test that cudagraph capture sizes are correctly capped to fit
600
+ available Mamba cache blocks.
601
+
602
+ See: https://github.com/vllm-project/vllm/issues/34094
603
+ """
604
+ config = CompilationConfig(
605
+ cudagraph_capture_sizes=capture_sizes,
606
+ max_cudagraph_capture_size=max_size,
607
+ cudagraph_mode=CUDAGraphMode.NONE,
608
+ )
609
+ config.adjust_cudagraph_sizes_for_mamba_cache(num_blocks)
610
+ assert config.cudagraph_capture_sizes == expected_sizes
611
+ assert config.max_cudagraph_capture_size == expected_max
612
+ # Invariant: last element == max_cudagraph_capture_size
613
+ if expected_sizes:
614
+ assert config.cudagraph_capture_sizes[-1] == config.max_cudagraph_capture_size
tests/v1/worker/test_gpu_model_runner.py
+120
@@ -1199,3 +1199,123 @@ def test_is_uniform_decode() -> None:
1199
num_reqs=15,
1200
force_uniform_decode=False,
1201
)
1202
+
1203
+
1204
+@pytest.mark.skipif(
1205
+ current_platform.is_rocm(),
1206
+ reason="Attention backend FLASHINFER is not supported on ROCm.",
1207
+)
1208
+def test_cudagraph_sizes_capped_for_mamba_cache():
1209
+ """Test that cudagraph capture sizes are capped to num_blocks for
1210
+ hybrid models with Mamba layers.
1211
+
1212
+ See: https://github.com/vllm-project/vllm/issues/34094
1213
+ """
1214
+ set_random_seed(42)
1215
+
1216
+ update_environment_variables(
1217
+ {
1218
+ "RANK": "0",
1219
+ "LOCAL_RANK": "0",
1220
+ "WORLD_SIZE": "1",
1221
+ "MASTER_ADDR": "localhost",
1222
+ "MASTER_PORT": "12345",
1223
+ }
1224
+ )
1225
+ from tests.utils import ensure_current_vllm_config
1226
+
1227
+ with ensure_current_vllm_config():
1228
+ init_distributed_environment()
1229
+ initialize_model_parallel(tensor_model_parallel_size=1)
1230
+ torch.set_default_dtype(torch.float16)
1231
+
1232
+ model_config = ModelConfig(
1233
+ model="ibm-granite/granite-4.0-tiny-preview",
1234
+ dtype="float16",
1235
+ )
1236
+ scheduler_config = SchedulerConfig(
1237
+ max_num_seqs=10,
1238
+ max_num_batched_tokens=512,
1239
+ max_model_len=512,
1240
+ is_encoder_decoder=model_config.is_encoder_decoder,
1241
+ )
1242
+ cache_config = CacheConfig(
1243
+ block_size=BLOCK_SIZE,
1244
+ gpu_memory_utilization=0.9,
1245
+ swap_space=0,
1246
+ cache_dtype="auto",
1247
+ )
1248
+ parallel_config = ParallelConfig()
1249
+ attention_config = AttentionConfig(backend=AttentionBackendEnum.FLASHINFER)
1250
+ vllm_config = VllmConfig(
1251
+ model_config=model_config,
1252
+ cache_config=cache_config,
1253
+ scheduler_config=scheduler_config,
1254
+ parallel_config=parallel_config,
1255
+ attention_config=attention_config,
1256
+ )
1257
+
1258
+ with set_current_vllm_config(vllm_config):
1259
+ hf_config = vllm_config.model_config.hf_config
1260
+ fwd_context = {}
1261
+ for key in ["model.layers.0.self_attn.attn", "model.layers.1.self_attn.attn"]:
1262
+ fwd_context[key] = Attention(
1263
+ num_heads=model_config.get_num_attention_heads(parallel_config),
1264
+ num_kv_heads=model_config.get_num_kv_heads(parallel_config),
1265
+ head_size=model_config.get_head_size(),
1266
+ scale=1.0,
1267
+ prefix=key,
1268
+ )
1269
+ for key in [
1270
+ "model.layers.2.mixer",
1271
+ "model.layers.3.mixer",
1272
+ "model.layers.4.mixer",
1273
+ "model.layers.5.mixer",
1274
+ ]:
1275
+ fwd_context[key] = MambaMixer2(
1276
+ hidden_size=hf_config.hidden_size,
1277
+ ssm_state_size=hf_config.mamba_d_state,
1278
+ conv_kernel_size=hf_config.mamba_d_conv,
1279
+ intermediate_size=hf_config.mamba_expand * hf_config.hidden_size,
1280
+ use_conv_bias=hf_config.mamba_conv_bias,
1281
+ use_bias=hf_config.mamba_proj_bias,
1282
+ n_groups=hf_config.mamba_n_groups,
1283
+ num_heads=hf_config.mamba_n_heads,
1284
+ head_dim=hf_config.mamba_d_head,
1285
+ rms_norm_eps=hf_config.rms_norm_eps,
1286
+ activation=hf_config.hidden_act,
1287
+ cache_config=cache_config,
1288
+ model_config=model_config,
1289
+ prefix=key,
1290
+ )
1291
+ assert fwd_context is not None
1292
+
1293
+ runner = GPUModelRunner(vllm_config, DEVICE)
1294
+ kv_cache_spec = runner.get_kv_cache_spec()
1295
+
1296
+ available_memory = 5 * GiB_bytes
1297
+ kv_cache_config = get_kv_cache_configs(
1298
+ vllm_config, [kv_cache_spec], [available_memory]
1299
+ )[0]
1300
+ num_blocks = kv_cache_config.num_blocks
1301
+
1302
+ # Set max_cudagraph_capture_size to a value larger than num_blocks
1303
+ # to trigger the Mamba capping logic.
1304
+ large_max = num_blocks + 100
1305
+ compilation_config = vllm_config.compilation_config
1306
+ compilation_config.max_cudagraph_capture_size = large_max
1307
+ compilation_config.cudagraph_capture_sizes = [
1308
+ s for s in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] if s <= large_max
1309
+ ]
1310
+
1311
+ runner.initialize_kv_cache(kv_cache_config)
1312
+
1313
+ # After initialization, cudagraph sizes should be capped
1314
+ assert compilation_config.max_cudagraph_capture_size <= num_blocks
1315
+ assert all(s <= num_blocks for s in compilation_config.cudagraph_capture_sizes)
1316
+ # Invariant: last element == max
1317
+ if compilation_config.cudagraph_capture_sizes:
1318
+ assert (
1319
+ compilation_config.cudagraph_capture_sizes[-1]
1320
+ == compilation_config.max_cudagraph_capture_size
1321
+ )
vllm/config/compilation.py
+52
@@ -1190,6 +1190,58 @@ class CompilationConfig:
1190
self.max_cudagraph_capture_size = rounded_sizes[-1]
1191
self.cudagraph_capture_sizes = rounded_sizes
1192
1193
+ def adjust_cudagraph_sizes_for_mamba_cache(
1194
+ self, num_mamba_cache_blocks: int
1195
+ ) -> None:
1196
+ """Cap cudagraph capture sizes to available Mamba cache blocks.
1197
+
1198
+ For hybrid Mamba/attention models, the Mamba conv_state and
1199
+ ssm_state tensors have their first dimension equal to num_blocks
1200
+ (from KVCacheConfig). During CUDA graph capture the decode batch
1201
+ size equals num_tokens, so capture sizes exceeding num_blocks
1202
+ would cause out-of-bounds access in Mamba kernels.
1203
+
1204
+ See: https://github.com/vllm-project/vllm/issues/34094
1205
+ """
1206
+ if not self.cudagraph_capture_sizes or num_mamba_cache_blocks <= 0:
1207
+ return
1208
+
1209
+ assert self.max_cudagraph_capture_size is not None
1210
+
1211
+ if num_mamba_cache_blocks >= self.max_cudagraph_capture_size:
1212
+ return
1213
+
1214
+ capped_sizes = [
1215
+ s for s in self.cudagraph_capture_sizes if s <= num_mamba_cache_blocks
1216
+ ]
1217
+
1218
+ if len(capped_sizes) == 0:
1219
+ logger.warning(
1220
+ "No valid cudagraph capture sizes remain after capping "
1221
+ "to Mamba cache blocks (%d). The smallest capture size "
1222
+ "was %d. Disabling cudagraph capture. Consider reducing "
1223
+ "max_num_seqs or increasing available GPU memory.",
1224
+ num_mamba_cache_blocks,
1225
+ self.cudagraph_capture_sizes[0],
1226
+ )
1227
+ self.cudagraph_capture_sizes = []
1228
+ self.max_cudagraph_capture_size = 0
1229
+ return
1230
+
1231
+ logger.warning(
1232
+ "Capping cudagraph capture sizes from max %d to %d to fit "
1233
+ "Mamba cache blocks (%d blocks available). This limits the "
1234
+ "maximum batch size that can use CUDA graphs. To increase "
1235
+ "this limit, reduce max_num_seqs or increase available GPU "
1236
+ "memory.",
1237
+ self.max_cudagraph_capture_size,
1238
+ capped_sizes[-1],
1239
+ num_mamba_cache_blocks,
1240
+ )
1241
+
1242
+ self.max_cudagraph_capture_size = capped_sizes[-1]
1243
+ self.cudagraph_capture_sizes = capped_sizes
1244
+
1245
def get_compile_ranges(self) -> list[Range]:
1246
"""Get the compile ranges for the compilation config."""
1247
if self.compile_ranges_split_points is None:
vllm/v1/worker/gpu_model_runner.py
+16
@@ -5700,6 +5700,22 @@ class GPUModelRunner(
5700
self.uniform_decode_query_len, self.parallel_config.tensor_parallel_size
5701
)
5702
5703
+ # If the model has Mamba layers and cudagraph mode includes FULL
5704
+ # decode, cap cudagraph capture sizes to the number of available
5705
+ # Mamba cache blocks. Each decode request needs one conv_state
5706
+ # cache line, so capture batch sizes cannot exceed num_blocks.
5707
+ # Only FULL decode graphs are affected because PIECEWISE captures
5708
+ # run GDN/Mamba ops eagerly (prefill path, no causal_conv1d_update).
5709
+ # See: https://github.com/vllm-project/vllm/issues/34094
5710
+ if cudagraph_mode.has_full_cudagraphs():
5711
+ has_mamba = any(
5712
+ isinstance(g.kv_cache_spec, MambaSpec) for g in kv_cache_groups
5713
+ )
5714
+ if has_mamba and self.kv_cache_config is not None:
5715
+ self.compilation_config.adjust_cudagraph_sizes_for_mamba_cache(
5716
+ self.kv_cache_config.num_blocks
5717
+ )
5718
+
5719
# Trigger cudagraph dispatching keys initialization after
5720
# resolved cudagraph mode.
5721
self.compilation_config.cudagraph_mode = cudagraph_mode