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 3, 20:15 UTC
[Hybrid] Warmup Mamba2 SSD kernel (#39822)
Signed-off-by: Thomas Parnell <tpa@zurich.ibm.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Thomas Parnell committed
May 12, 2026 at 14:46 UTC
8f89381fc6b2d54591a7a560e20ee5211ce1ac33
3 files changed
+110
-4
vllm/config/model.py
+2
-2
@@ -1345,7 +1345,7 @@ class ModelConfig:
1345
)
1346
raise AssertionError(f"Unsupported block type: {block_type}")
1347
1348
- def get_mamba_chunk_size(self) -> int | None:
1348
+ def get_mamba_chunk_size(self) -> int:
1349
"""
1350
Returns the mamba chunk size if it exists
1351
"""
@@ -1356,7 +1356,7 @@ class ModelConfig:
1356
chunk_size = getattr(self.hf_text_config, "chunk_size", None)
1357
1358
# Since Mamba1 does not have a chunk notion
1359
- # we use a default chunk size of 1024.
1359
+ # we use a default chunk size of 2048.
1360
if chunk_size is None:
1361
chunk_size = 2048
1362
vllm/model_executor/layers/mamba/gdn_linear_attn.py
+2
-1
@@ -397,6 +397,7 @@ class GatedDeltaNetAttention(PluggableLayer, MambaBase):
397
)
398
399
self.chunk_gated_delta_rule = ChunkGatedDeltaRule()
400
+ self._prefill_kernels_warmed_up = False
401
self.enable_packed_recurrent_decode = (
402
envs.VLLM_ENABLE_FLA_PACKED_RECURRENT_DECODE
403
)
@@ -905,7 +906,7 @@ class GatedDeltaNetAttention(PluggableLayer, MambaBase):
906
which has fixed kernel parameters (no autotuning), so only the
907
prefill (chunked) path needs warming up.
908
"""
908
- if hasattr(self, "_prefill_kernels_warmed_up"):
909
+ if self._prefill_kernels_warmed_up:
910
return
911
self._prefill_kernels_warmed_up = True
912
vllm/model_executor/layers/mamba/mamba_mixer2.py
+106
-1
@@ -14,6 +14,7 @@ from vllm.distributed import (
14
tensor_model_parallel_all_reduce,
15
)
16
from vllm.forward_context import ForwardContext, get_forward_context
17
+from vllm.logger import init_logger
18
from vllm.model_executor.custom_op import CustomOp, PluggableLayer
19
from vllm.model_executor.layers.linear import (
20
ColumnParallelLinear,
@@ -54,6 +55,8 @@ from vllm.v1.attention.backend import AttentionMetadata
55
from vllm.v1.attention.backends.mamba2_attn import Mamba2AttentionMetadata
56
from vllm.v1.attention.backends.registry import MambaAttentionBackendEnum
57
58
+logger = init_logger(__name__)
59
+
60
# Added by the IBM Team, 2024
61
62
@@ -473,6 +476,8 @@ class MambaMixer2(MambaBase, PluggableLayer):
476
intermediate_size, n_groups, self.use_rms_norm, eps=rms_norm_eps
477
)
478
479
+ self._ssd_kernels_warmed_up = False
480
+
481
# - get hidden_states, B and C after depthwise convolution.
482
self.split_hidden_states_B_C_fn = lambda hidden_states_B_C: torch.split(
483
hidden_states_B_C,
@@ -557,6 +562,104 @@ class MambaMixer2(MambaBase, PluggableLayer):
562
563
return output
564
565
+ def _warmup_ssd_kernels(self, projected_states: torch.Tensor) -> None:
566
+ """Run a minimal SSD forward pass to trigger Triton autotuning
567
+ while GPU memory is still plentiful (before SSM cache allocation).
568
+ """
569
+ if self._ssd_kernels_warmed_up:
570
+ return
571
+ self._ssd_kernels_warmed_up = True
572
+ logger.info_once("Warming up Mamba2 SSD Triton kernels...")
573
+
574
+ device = projected_states.device
575
+ dtype = projected_states.dtype
576
+
577
+ nheads = self.num_heads // self.tp_size
578
+ ngroups = self.n_groups // self.tp_size
579
+ headdim = self.head_dim
580
+ dstate = self.ssm_state_size
581
+
582
+ if self.model_config is None:
583
+ return
584
+ chunk_size = self.model_config.get_mamba_chunk_size()
585
+
586
+ # Triton's autotuner includes tensor dtypes in its cache key,
587
+ # so state_dtype must match what real inference uses.
588
+ _, ssm_state_dtype = self.get_state_dtype()
589
+
590
+ # SSD kernel autotune keys depend on dtype and head dimensions,
591
+ # not on sequence length or batch size, so a single shape suffices.
592
+ seqlen = chunk_size
593
+ batch = 1
594
+ nchunks = seqlen // chunk_size # = 1
595
+
596
+ x = torch.randn(seqlen, nheads, headdim, device=device, dtype=dtype)
597
+ dt = torch.randn(seqlen, nheads, device=device, dtype=dtype)
598
+ B = torch.randn(seqlen, ngroups, dstate, device=device, dtype=dtype)
599
+ C = torch.randn(seqlen, ngroups, dstate, device=device, dtype=dtype)
600
+ cu_seqlens = torch.tensor([0, seqlen], device=device, dtype=torch.int32)
601
+ cu_chunk_seqlens = torch.tensor(
602
+ [i * chunk_size for i in range(nchunks + 1)],
603
+ device=device,
604
+ dtype=torch.int32,
605
+ )
606
+ last_chunk_indices = torch.tensor(
607
+ [nchunks - 1], device=device, dtype=torch.int32
608
+ )
609
+ seq_idx = torch.zeros(nchunks, device=device, dtype=torch.int32)
610
+ out = torch.empty(seqlen, nheads, headdim, device=device, dtype=dtype)
611
+
612
+ # Two kernels (_state_passing_fwd, _chunk_scan_fwd) use
613
+ # HAS_INITSTATES as a constexpr, producing separate compiled
614
+ # binaries. Warm up both code paths so neither triggers
615
+ # JIT compilation during inference.
616
+ for use_initial_states in (False, True):
617
+ initial_states = (
618
+ torch.randn(
619
+ batch,
620
+ nheads,
621
+ headdim,
622
+ dstate,
623
+ device=device,
624
+ dtype=ssm_state_dtype,
625
+ )
626
+ if use_initial_states
627
+ else None
628
+ )
629
+ try:
630
+ mamba_chunk_scan_combined_varlen(
631
+ x=x,
632
+ dt=dt,
633
+ A=self.A,
634
+ B=B,
635
+ C=C,
636
+ chunk_size=chunk_size,
637
+ cu_seqlens=cu_seqlens,
638
+ cu_chunk_seqlens=cu_chunk_seqlens,
639
+ last_chunk_indices=last_chunk_indices,
640
+ seq_idx=seq_idx,
641
+ out=out,
642
+ D=self.D,
643
+ z=None,
644
+ dt_bias=self.dt_bias,
645
+ initial_states=initial_states,
646
+ dt_softplus=True,
647
+ dt_limit=(0.0, float("inf")),
648
+ state_dtype=ssm_state_dtype,
649
+ )
650
+ except Exception:
651
+ logger.warning(
652
+ "Mamba2 SSD kernel warmup failed for layer %s "
653
+ "(initial_states=%s). First inference may experience "
654
+ "latency spike or OOM due to autotuner.",
655
+ self.prefix,
656
+ use_initial_states,
657
+ exc_info=True,
658
+ )
659
+
660
+ logger.debug("Mamba2 SSD kernel warmup completed for layer %s", self.prefix)
661
+ torch.accelerator.empty_cache()
662
+
663
def conv_ssm_forward(
664
self,
665
projected_states: torch.Tensor,
@@ -608,7 +711,9 @@ class MambaMixer2(MambaBase, PluggableLayer):
711
num_decode_tokens = attn_metadata.num_decode_tokens
712
713
if attn_metadata is None:
611
- # profile run
714
+ # V1 profile run -- warm up SSD kernels so that autotuning
715
+ # completes before SSM cache allocation.
716
+ self._warmup_ssd_kernels(projected_states)
717
hidden_states_B_C = (
718
hidden_states_B_C.transpose(0, 1).clone().transpose(0, 1)
719
).contiguous()