@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 2f35a022e6
Fork 0

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 15, 03:30 UTC

Enable V1 for Hybrid SSM/Attention Models (#20016)

Signed-off-by: Thomas Parnell <tpa@zurich.ibm.com> Co-authored-by: Stanislaw Wozniak <stw@zurich.ibm.com> Co-authored-by: Tyler Michael Smith <tysmith@redhat.com> Co-authored-by: Chen Zhang <zhangch99@outlook.com>

Thomas Parnell committed Jul 4, 2025 at 19:46 UTC 2f35a022e648f83ea376ee7331deafaba36d7eac
14 files changed +401 -136
tests/models/language/generation/test_hybrid.py
+60 -10
@@ -3,6 +3,7 @@
3
4 import pytest
5
6 +from tests.models.registry import HF_EXAMPLE_MODELS
7 from tests.utils import multi_gpu_test
8 from vllm.engine.arg_utils import EngineArgs
9 from vllm.sampling_params import SamplingParams
@@ -19,31 +20,55 @@ pytestmark = pytest.mark.hybrid_model
20 SSM_MODELS = [
21 "state-spaces/mamba-130m-hf",
22 "tiiuae/falcon-mamba-tiny-dev",
22 - # TODO: Compare to a Mamba2 model. The HF transformers implementation of
23 - # Mamba2 is buggy for Codestral as it doesn't handle n_groups, so the test
24 - # doesn't compare vLLM output with HF output.
25 - # See https://github.com/huggingface/transformers/pull/35943
23 "mistralai/Mamba-Codestral-7B-v0.1",
24 ]
25
26 HYBRID_MODELS = [
27 "ai21labs/Jamba-tiny-dev",
31 - # NOTE: Currently the test failes due to HF transformers issue fixed in:
32 - # https://github.com/huggingface/transformers/pull/39033
33 - # We will enable vLLM test for Granite after next HF transformers release.
34 - # "ibm-granite/granite-4.0-tiny-preview",
28 # NOTE: Running Plamo2 in transformers implementation requires to install
29 # causal-conv1d package, which is not listed as a test dependency as it's
30 # not compatible with pip-compile.
31 "pfnet/plamo-2-1b",
32 "Zyphra/Zamba2-1.2B-instruct",
33 "hmellor/tiny-random-BambaForCausalLM",
34 + "ibm-ai-platform/Bamba-9B-v1",
35 + "nvidia/Nemotron-H-8B-Base-8K",
36 + "ibm-granite/granite-4.0-tiny-preview",
37 + "tiiuae/Falcon-H1-0.5B-Base",
38 +]
39 +
40 +HF_UNSUPPORTED_MODELS = [
41 + # The HF transformers implementation of
42 + # Mamba2 is buggy for Codestral as it doesn't handle n_groups, so the test
43 + # doesn't compare vLLM output with HF output.
44 + # See https://github.com/huggingface/transformers/pull/35943
45 + "mistralai/Mamba-Codestral-7B-v0.1",
46 + # Note: I'm not seeing the same output from vLLM V0 vs. HF transformers
47 + # for Nemotron-H-8B; currently only compare vLLM V0 vs. vLLM V1
48 + "nvidia/Nemotron-H-8B-Base-8K",
49 + # NOTE: Currently the test fails due to HF transformers issue fixed in:
50 + # https://github.com/huggingface/transformers/pull/39033
51 + # We will enable vLLM test for Granite after next HF transformers release.
52 + "ibm-granite/granite-4.0-tiny-preview",
53 ]
54
55 V1_SUPPORTED_MODELS = [
56 "mistralai/Mamba-Codestral-7B-v0.1",
57 + "ibm-ai-platform/Bamba-9B-v1",
58 + "Zyphra/Zamba2-1.2B-instruct",
59 + "nvidia/Nemotron-H-8B-Base-8K",
60 + "ibm-granite/granite-4.0-tiny-preview",
61 + "tiiuae/Falcon-H1-0.5B-Base",
62 ]
63
64 +ATTN_BLOCK_SIZES = {
65 + "ibm-ai-platform/Bamba-9B-v1": 528,
66 + "Zyphra/Zamba2-1.2B-instruct": 80,
67 + "nvidia/Nemotron-H-8B-Base-8K": 528,
68 + "ibm-granite/granite-4.0-tiny-preview": 400,
69 + "tiiuae/Falcon-H1-0.5B-Base": 800,
70 +}
71 +
72 # Avoid OOM
73 MAX_NUM_SEQS = 4
74
@@ -60,8 +85,16 @@ def test_models(
85 max_tokens: int,
86 num_logprobs: int,
87 ) -> None:
88 +
89 + try:
90 + model_info = HF_EXAMPLE_MODELS.find_hf_info(model)
91 + model_info.check_available_online(on_fail="skip")
92 + model_info.check_transformers_version(on_fail="skip")
93 + except ValueError:
94 + pass
95 +
96 with hf_runner(model) as hf_model:
64 - if model != "mistralai/Mamba-Codestral-7B-v0.1":
97 + if model not in HF_UNSUPPORTED_MODELS:
98 hf_outputs = hf_model.generate_greedy_logprobs_limit(
99 example_prompts, max_tokens, num_logprobs)
100 else:
@@ -72,12 +105,21 @@ def test_models(
105 example_prompts, max_tokens, num_logprobs)
106
107 if model in V1_SUPPORTED_MODELS:
108 + if model in HYBRID_MODELS and model in ATTN_BLOCK_SIZES:
109 + block_size = ATTN_BLOCK_SIZES[model]
110 + else:
111 + block_size = 16
112 +
113 with monkeypatch.context() as m:
114 m.setenv("VLLM_USE_V1", "1")
115 + if model in HYBRID_MODELS:
116 + # required due to reorder_batch behaviour
117 + m.setenv("VLLM_ATTENTION_BACKEND", "FLASHINFER")
118 with vllm_runner(model,
119 max_num_seqs=MAX_NUM_SEQS,
120 enforce_eager=True,
80 - enable_prefix_caching=False) as vllm_model:
121 + enable_prefix_caching=False,
122 + block_size=block_size) as vllm_model:
123 vllm_v1_outputs = vllm_model.generate_greedy_logprobs(
124 example_prompts, max_tokens, num_logprobs)
125 else:
@@ -111,6 +153,14 @@ def test_batching(
153 max_tokens: int,
154 num_logprobs: int,
155 ) -> None:
156 +
157 + try:
158 + model_info = HF_EXAMPLE_MODELS.find_hf_info(model)
159 + model_info.check_available_online(on_fail="skip")
160 + model_info.check_transformers_version(on_fail="skip")
161 + except ValueError:
162 + pass
163 +
164 for_loop_outputs = []
165 with vllm_runner(model, max_num_seqs=MAX_NUM_SEQS) as vllm_model:
166 for prompt in example_prompts:
tests/models/registry.py
+1 -1
@@ -169,7 +169,7 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
169 "ExaoneForCausalLM": _HfExamplesInfo("LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct"), # noqa: E501
170 "Fairseq2LlamaForCausalLM": _HfExamplesInfo("mgleize/fairseq2-dummy-Llama-3.2-1B"), # noqa: E501
171 "FalconForCausalLM": _HfExamplesInfo("tiiuae/falcon-7b"),
172 - "FalconH1ForCausalLM":_HfExamplesInfo("tiiuae/Falcon-H1-1.5B-Instruct",
172 + "FalconH1ForCausalLM":_HfExamplesInfo("tiiuae/Falcon-H1-0.5B-Base",
173 min_transformers_version="4.53"),
174 "GemmaForCausalLM": _HfExamplesInfo("google/gemma-1.1-2b-it"),
175 "Gemma2ForCausalLM": _HfExamplesInfo("google/gemma-2-9b"),
tests/v1/test_oracle.py
-1
@@ -13,7 +13,6 @@ UNSUPPORTED_MODELS_V1 = [
13 "openai/whisper-large-v3", # transcription
14 "facebook/bart-large-cnn", # encoder decoder
15 "state-spaces/mamba-130m-hf", # mamba1
16 - "hmellor/tiny-random-BambaForCausalLM", # hybrid
16 "BAAI/bge-m3", # embedding
17 ]
18
vllm/model_executor/layers/mamba/ops/mamba_ssm.py
+1 -1
@@ -108,7 +108,7 @@ def _selective_scan_update_kernel(
108 # is the same as the batch id.
109 if HAS_STATE_BATCH_INDICES:
110 state_batch_indices_ptr += pid_b
111 - state_batch_idx = tl.load(state_batch_indices_ptr)
111 + state_batch_idx = tl.load(state_batch_indices_ptr).to(tl.int64)
112 state_ptr += (state_batch_idx * stride_state_batch +
113 pid_h * stride_state_head)
114 else:
vllm/model_executor/models/bamba.py
+30 -15
@@ -9,6 +9,7 @@ import torch
9 from torch import nn
10 from transformers import BambaConfig
11
12 +from vllm import envs
13 from vllm.attention.layer import Attention
14 from vllm.config import CacheConfig, VllmConfig
15 from vllm.distributed import divide, get_tensor_model_parallel_world_size
@@ -36,7 +37,7 @@ from vllm.sequence import IntermediateTensors
37 from vllm.utils import LayerBlockType
38
39 from .interfaces import (HasInnerState, IsHybrid, SupportsLoRA, SupportsPP,
39 - SupportsQuant, SupportsV0Only)
40 + SupportsQuant)
41 from .utils import (AutoWeightsLoader, is_pp_missing_parameter,
42 make_empty_intermediate_tensors_factory, make_layers,
43 maybe_prefix)
@@ -97,7 +98,9 @@ class BambaMixerDecoderLayer(nn.Module):
98 head_dim=config.mamba_d_head,
99 rms_norm_eps=config.rms_norm_eps,
100 activation=config.hidden_act,
100 - quant_config=quant_config)
101 + quant_config=quant_config,
102 + prefix=f"{prefix}.mixer",
103 + chunk_size=config.mamba_chunk_size)
104
105 self.feed_forward = BambaMLP(config, quant_config=quant_config)
106 self.input_layernorm = RMSNorm(config.hidden_size,
@@ -313,10 +316,14 @@ class BambaModel(nn.Module):
316
317 attn_metadata = get_forward_context().attn_metadata
318
316 - mamba2_metadata = prepare_mamba2_metadata(
317 - chunk_size=self.config.mamba_chunk_size,
318 - attn_metadata=attn_metadata,
319 - )
319 + if not envs.VLLM_USE_V1:
320 + mamba2_metadata = prepare_mamba2_metadata(
321 + chunk_size=self.config.mamba_chunk_size,
322 + attn_metadata=attn_metadata,
323 + )
324 + else:
325 + # v1 get mamba2_metadata from forward_context
326 + mamba2_metadata = None
327
328 if get_pp_group().is_first_rank:
329 if inputs_embeds is not None:
@@ -337,7 +344,8 @@ class BambaModel(nn.Module):
344 num_attn += 1
345
346 layer_mamba_cache_params = None
340 - if isinstance(layer, BambaMixerDecoderLayer):
347 + if isinstance(layer,
348 + BambaMixerDecoderLayer) and mamba_cache_params:
349 layer_mamba_cache_params = mamba_cache_params.at_layer_idx(
350 i - num_attn)
351
@@ -411,7 +419,7 @@ class BambaModel(nn.Module):
419
420
421 class BambaForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
414 - IsHybrid, SupportsV0Only, SupportsQuant):
422 + IsHybrid, SupportsQuant):
423 packed_modules_mapping = {
424 "qkv_proj": [
425 "q_proj",
@@ -475,15 +483,22 @@ class BambaForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
483 intermediate_tensors: Optional[IntermediateTensors] = None,
484 inputs_embeds: Optional[torch.Tensor] = None,
485 **kwargs):
478 - if self.mamba_cache is None:
486
480 - num_mamba_layers = self.model_config.get_num_layers_by_block_type(
481 - self.vllm_config.parallel_config, LayerBlockType.mamba)
487 + mamba_cache_params = None
488 + if not envs.VLLM_USE_V1:
489 + if self.mamba_cache is None:
490 + num_mamba_layers = \
491 + self.model_config.get_num_layers_by_block_type(
492 + self.vllm_config.parallel_config,
493 + LayerBlockType.mamba
494 + )
495 +
496 + self.mamba_cache = MambaCacheManager(
497 + self.vllm_config, self.lm_head.weight.dtype,
498 + num_mamba_layers, *self._get_mamba_cache_shape())
499 +
500 + mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
501
483 - self.mamba_cache = MambaCacheManager(
484 - self.vllm_config, self.lm_head.weight.dtype, num_mamba_layers,
485 - *self._get_mamba_cache_shape())
486 - mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
502 hidden_states = self.model(input_ids, positions, mamba_cache_params,
503 intermediate_tensors, inputs_embeds)
504
vllm/model_executor/models/falcon_h1.py
+40 -17
@@ -8,6 +8,7 @@ import torch
8 from torch import nn
9 from transformers import FalconH1Config
10
11 +from vllm import envs
12 from vllm.attention.layer import Attention
13 from vllm.config import CacheConfig, VllmConfig
14 from vllm.distributed import divide, get_tensor_model_parallel_world_size
@@ -33,8 +34,7 @@ from vllm.model_executor.models.mamba_cache import (MambaCacheManager,
34 from vllm.model_executor.sampling_metadata import SamplingMetadata
35 from vllm.sequence import IntermediateTensors
36
36 -from .interfaces import (HasInnerState, IsHybrid, SupportsLoRA, SupportsPP,
37 - SupportsV0Only)
37 +from .interfaces import HasInnerState, IsHybrid, SupportsLoRA, SupportsPP
38 from .utils import (PPMissingLayer, is_pp_missing_parameter,
39 make_empty_intermediate_tensors_factory, make_layers,
40 maybe_prefix)
@@ -85,6 +85,7 @@ class FalconH1SSMDecoderLayer(nn.Module):
85 config: FalconH1Config,
86 cache_config: Optional[CacheConfig] = None,
87 quant_config: Optional[QuantizationConfig] = None,
88 + prefix: str = "",
89 ) -> None:
90 super().__init__()
91 self.config = config
@@ -107,6 +108,8 @@ class FalconH1SSMDecoderLayer(nn.Module):
108 activation=config.hidden_act,
109 quant_config=quant_config,
110 use_rms_norm=config.mamba_rms_norm,
111 + prefix=f"{prefix}.mixer",
112 + chunk_size=config.mamba_chunk_size,
113 )
114 # n_groups is overridden later by `MambaMixer2`
115 self.groups_time_state_size = self.mamba.n_groups * config.mamba_d_state
@@ -316,6 +319,7 @@ class FalconH1ParallelHybrid(nn.Module):
319 prefix: str = "",
320 ) -> None:
321 super().__init__()
322 +
323 # Instantiate the attention branch
324 self.self_attn = FalconH1AttentionDecoderLayer(
325 config=config,
@@ -323,11 +327,18 @@ class FalconH1ParallelHybrid(nn.Module):
327 quant_config=quant_config,
328 prefix=prefix,
329 )
330 +
331 + # In V1 all attention/ssm layers must have
332 + # different index in prefix
333 + ssm_layer_idx = config.num_hidden_layers + layer_idx
334 + ssm_prefix = prefix.split(".")[0] + f".{ssm_layer_idx}"
335 +
336 # Instantiate the SSM branch
337 self.mamba = FalconH1SSMDecoderLayer(
338 config=config,
339 cache_config=cache_config,
340 quant_config=quant_config,
341 + prefix=ssm_prefix,
342 )
343 self.ssm_out_multiplier = config.ssm_out_multiplier
344 self.ssm_in_multiplier = config.ssm_in_multiplier
@@ -452,10 +463,16 @@ class FalconH1Model(nn.Module):
463 # proper continuous batching computation including
464 # chunked prefill
465 attn_metadata = get_forward_context().attn_metadata
455 - mamba2_metadata = prepare_mamba2_metadata(
456 - chunk_size=self.config.mamba_chunk_size,
457 - attn_metadata=attn_metadata,
458 - )
466 +
467 + if not envs.VLLM_USE_V1:
468 + mamba2_metadata = prepare_mamba2_metadata(
469 + chunk_size=self.config.mamba_chunk_size,
470 + attn_metadata=attn_metadata,
471 + )
472 + else:
473 + # v1 get mamba2_metadata from forward_context
474 + mamba2_metadata = None
475 +
476 if get_pp_group().is_first_rank:
477 if inputs_embeds is not None:
478 hidden_states = inputs_embeds * self.embedding_multiplier
@@ -468,7 +485,9 @@ class FalconH1Model(nn.Module):
485
486 for i in range(self.start_layer, self.end_layer):
487 layer = self.layers[i]
471 - layer_mamba_cache_params = mamba_cache_params.at_layer_idx(i)
488 + layer_mamba_cache_params = None
489 + if mamba_cache_params:
490 + layer_mamba_cache_params = mamba_cache_params.at_layer_idx(i)
491 hidden_states = layer(
492 positions=positions,
493 hidden_states=hidden_states,
@@ -484,7 +503,7 @@ class FalconH1Model(nn.Module):
503
504
505 class FalconH1ForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
487 - IsHybrid, SupportsV0Only):
506 + IsHybrid):
507 packed_modules_mapping = {
508 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
509 "gate_up_proj": ["gate_proj", "up_proj"],
@@ -558,15 +577,19 @@ class FalconH1ForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
577 inputs_embeds: Optional[torch.Tensor] = None,
578 **kwargs,
579 ):
561 - if self.mamba_cache is None:
562 - self.mamba_cache = MambaCacheManager(
563 - self.vllm_config,
564 - self.lm_head.weight.dtype
565 - if hasattr(self.lm_head, 'weight') else torch.bfloat16,
566 - self.config.num_hidden_layers,
567 - *self._get_mamba_cache_shape(),
568 - )
569 - mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
580 +
581 + mamba_cache_params = None
582 + if not envs.VLLM_USE_V1:
583 + if self.mamba_cache is None:
584 + self.mamba_cache = MambaCacheManager(
585 + self.vllm_config,
586 + self.lm_head.weight.dtype if hasattr(
587 + self.lm_head, 'weight') else torch.bfloat16,
588 + self.config.num_hidden_layers,
589 + *self._get_mamba_cache_shape(),
590 + )
591 + mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
592 +
593 hidden_states = self.model(
594 input_ids,
595 positions,
vllm/model_executor/models/granitemoehybrid.py
+32 -17
@@ -9,6 +9,7 @@ import torch
9 from torch import nn
10 from transformers import GraniteMoeHybridConfig
11
12 +from vllm import envs
13 from vllm.attention.layer import Attention
14 from vllm.config import CacheConfig, VllmConfig
15 from vllm.distributed import divide, get_tensor_model_parallel_world_size
@@ -36,7 +37,7 @@ from vllm.utils import LayerBlockType
37 from .granitemoe import GraniteMoeMoE
38 from .granitemoeshared import GraniteMoeSharedMLP
39 from .interfaces import (HasInnerState, IsHybrid, SupportsLoRA, SupportsPP,
39 - SupportsQuant, SupportsV0Only)
40 + SupportsQuant)
41 from .utils import (AutoWeightsLoader, is_pp_missing_parameter,
42 make_empty_intermediate_tensors_factory, make_layers,
43 maybe_prefix)
@@ -67,7 +68,9 @@ class GraniteMoeHybridMambaDecoderLayer(nn.Module):
68 head_dim=config.mamba_d_head,
69 rms_norm_eps=config.rms_norm_eps,
70 activation=config.hidden_act,
70 - quant_config=quant_config)
71 + quant_config=quant_config,
72 + prefix=f"{prefix}.mixer",
73 + chunk_size=config.mamba_chunk_size)
74
75 self.block_sparse_moe = None
76 if getattr(config, "num_local_experts", 0) > 0:
@@ -361,10 +364,15 @@ class GraniteMoeHybridModel(nn.Module):
364 ) -> torch.Tensor:
365
366 attn_metadata = get_forward_context().attn_metadata
364 - mamba2_metadata = prepare_mamba2_metadata(
365 - chunk_size=self.config.mamba_chunk_size,
366 - attn_metadata=attn_metadata,
367 - )
367 +
368 + if not envs.VLLM_USE_V1:
369 + mamba2_metadata = prepare_mamba2_metadata(
370 + chunk_size=self.config.mamba_chunk_size,
371 + attn_metadata=attn_metadata,
372 + )
373 + else:
374 + # v1 get mamba2_metadata from forward_context
375 + mamba2_metadata = None
376
377 if get_pp_group().is_first_rank:
378 if inputs_embeds is not None:
@@ -386,7 +394,9 @@ class GraniteMoeHybridModel(nn.Module):
394 num_attn += 1
395
396 layer_mamba_cache_params = None
389 - if isinstance(layer, GraniteMoeHybridMambaDecoderLayer):
397 + if isinstance(
398 + layer,
399 + GraniteMoeHybridMambaDecoderLayer) and mamba_cache_params:
400 layer_mamba_cache_params = mamba_cache_params.at_layer_idx(
401 i - num_attn)
402
@@ -501,8 +511,7 @@ class GraniteMoeHybridModel(nn.Module):
511
512
513 class GraniteMoeHybridForCausalLM(nn.Module, HasInnerState, SupportsLoRA,
504 - SupportsPP, IsHybrid, SupportsV0Only,
505 - SupportsQuant):
514 + SupportsPP, IsHybrid, SupportsQuant):
515 packed_modules_mapping = {
516 "qkv_proj": [
517 "q_proj",
@@ -571,14 +580,20 @@ class GraniteMoeHybridForCausalLM(nn.Module, HasInnerState, SupportsLoRA,
580 intermediate_tensors: Optional[IntermediateTensors] = None,
581 inputs_embeds: Optional[torch.Tensor] = None,
582 **kwargs):
574 - if self.mamba_cache is None:
575 - num_mamba_layers = self.model_config.get_num_layers_by_block_type(
576 - self.vllm_config.parallel_config, LayerBlockType.mamba)
577 - self.mamba_cache = MambaCacheManager(
578 - self.vllm_config, self.model_config.dtype, num_mamba_layers,
579 - *self._get_mamba_cache_shape())
580 -
581 - mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
583 +
584 + mamba_cache_params = None
585 + if not envs.VLLM_USE_V1:
586 + if self.mamba_cache is None:
587 + num_mamba_layers = (
588 + self.model_config.get_num_layers_by_block_type(
589 + self.vllm_config.parallel_config,
590 + LayerBlockType.mamba))
591 + self.mamba_cache = MambaCacheManager(
592 + self.vllm_config, self.model_config.dtype,
593 + num_mamba_layers, *self._get_mamba_cache_shape())
594 +
595 + mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
596 +
597 hidden_states = self.model(input_ids, positions, mamba_cache_params,
598 intermediate_tensors, inputs_embeds)
599
vllm/model_executor/models/nemotron_h.py
+30 -15
@@ -23,6 +23,7 @@ from typing import Optional
23 import torch
24 from torch import nn
25
26 +from vllm import envs
27 from vllm.attention.layer import Attention
28 from vllm.config import CacheConfig, VllmConfig
29 from vllm.distributed import divide, get_tensor_model_parallel_world_size
@@ -44,8 +45,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
45 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
46 from vllm.model_executor.models.interfaces import (HasInnerState, IsHybrid,
47 SupportsLoRA, SupportsPP,
47 - SupportsQuant,
48 - SupportsV0Only)
48 + SupportsQuant)
49 from vllm.model_executor.models.mamba_cache import (MambaCacheManager,
50 MambaCacheParams)
51 from vllm.model_executor.models.utils import (
@@ -153,6 +153,8 @@ class NemotronHMambaDecoderLayer(nn.Module):
153 rms_norm_eps=config.rms_norm_eps,
154 activation=config.mamba_hidden_act,
155 quant_config=quant_config,
156 + prefix=f"{prefix}.mixer",
157 + chunk_size=config.chunk_size,
158 )
159
160 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
@@ -348,10 +350,14 @@ class NemotronHModel(nn.Module):
350
351 attn_metadata = get_forward_context().attn_metadata
352
351 - mamba2_metadata = prepare_mamba2_metadata(
352 - chunk_size=self.config.chunk_size,
353 - attn_metadata=attn_metadata,
354 - )
353 + if not envs.VLLM_USE_V1:
354 + mamba2_metadata = prepare_mamba2_metadata(
355 + chunk_size=self.config.chunk_size,
356 + attn_metadata=attn_metadata,
357 + )
358 + else:
359 + # v1 get mamba2_metadata from forward_context
360 + mamba2_metadata = None
361
362 if get_pp_group().is_first_rank:
363 if inputs_embeds is not None:
@@ -369,7 +375,8 @@ class NemotronHModel(nn.Module):
375 for i in range(len(self.layers)):
376 layer = self.layers[i]
377 layer_mamba_cache_params = None
372 - if isinstance(layer, NemotronHMambaDecoderLayer):
378 + if isinstance(layer,
379 + NemotronHMambaDecoderLayer) and mamba_cache_params:
380 layer_mamba_cache_params = mamba_cache_params.at_layer_idx(
381 i - num_non_mamba_layers)
382 else:
@@ -437,7 +444,7 @@ class NemotronHModel(nn.Module):
444
445
446 class NemotronHForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
440 - IsHybrid, SupportsV0Only, SupportsQuant):
447 + IsHybrid, SupportsQuant):
448 packed_modules_mapping = {
449 "qkv_proj": [
450 "q_proj",
@@ -499,15 +506,23 @@ class NemotronHForCausalLM(nn.Module, HasInnerState, SupportsLoRA, SupportsPP,
506 intermediate_tensors: Optional[IntermediateTensors] = None,
507 inputs_embeds: Optional[torch.Tensor] = None,
508 **kwargs):
502 - if self.mamba_cache is None:
509
504 - num_mamba_layers = self.model_config.get_num_layers_by_block_type(
505 - self.vllm_config.parallel_config, LayerBlockType.mamba)
510 + mamba_cache_params = None
511 + if not envs.VLLM_USE_V1:
512 + if self.mamba_cache is None:
513 +
514 + num_mamba_layers = \
515 + self.model_config.get_num_layers_by_block_type(
516 + self.vllm_config.parallel_config,
517 + LayerBlockType.mamba
518 + )
519 +
520 + self.mamba_cache = MambaCacheManager(
521 + self.vllm_config, self.lm_head.weight.dtype,
522 + num_mamba_layers, *self._get_mamba_cache_shape())
523 +
524 + mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
525
507 - self.mamba_cache = MambaCacheManager(
508 - self.vllm_config, self.lm_head.weight.dtype, num_mamba_layers,
509 - *self._get_mamba_cache_shape())
510 - mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
526 hidden_states = self.model(input_ids, positions, mamba_cache_params,
527 intermediate_tensors, inputs_embeds)
528
vllm/model_executor/models/zamba2.py
+64 -37
@@ -15,6 +15,7 @@ import torch
15 from torch import nn
16 from transformers import Zamba2Config
17
18 +from vllm import envs
19 from vllm.attention.layer import Attention
20 from vllm.config import CacheConfig, VllmConfig
21 from vllm.distributed import divide, get_tensor_model_parallel_world_size
@@ -41,7 +42,7 @@ from vllm.model_executor.models.mamba_cache import (MambaCacheManager,
42 from vllm.model_executor.sampling_metadata import SamplingMetadata
43 from vllm.sequence import IntermediateTensors
44
44 -from .interfaces import HasInnerState, IsHybrid, SupportsV0Only
45 +from .interfaces import HasInnerState, IsHybrid
46 from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
47
48
@@ -58,6 +59,7 @@ class Zamba2LoRA(nn.Module):
59 rank: int,
60 output_dim: Union[int, list[int]],
61 quant_config: Optional[QuantizationConfig] = None,
62 + prefix: str = "",
63 ):
64 """Initialize the attention layer.
65
@@ -283,6 +285,7 @@ class Zamba2MLP(nn.Module):
285 bare_block_idx: int,
286 num_hybrid_layers: dict[int, int],
287 quant_config: Optional[QuantizationConfig] = None,
288 + prefix: str = "",
289 ) -> None:
290 """Initialize the MLP layer.
291
@@ -471,11 +474,10 @@ class Zamba2MambaDecoderLayer(nn.Module):
474 computation depending on configuration.
475 """
476
474 - def __init__(
475 - self,
476 - config: Zamba2Config,
477 - quant_config: Optional[QuantizationConfig] = None,
478 - ) -> None:
477 + def __init__(self,
478 + config: Zamba2Config,
479 + quant_config: Optional[QuantizationConfig] = None,
480 + prefix: str = "") -> None:
481 """Initialize the Mamba decoder layer.
482
483 Args:
@@ -486,20 +488,21 @@ class Zamba2MambaDecoderLayer(nn.Module):
488
489 # Initialize Mamba mixer with expanded intermediate size
490 intermediate_size = config.mamba_expand * config.hidden_size
489 - self.mamba = MambaMixer2(
490 - hidden_size=config.hidden_size,
491 - ssm_state_size=config.mamba_d_state,
492 - conv_kernel_size=config.mamba_d_conv,
493 - intermediate_size=intermediate_size,
494 - use_conv_bias=config.use_conv_bias,
495 - use_bias=config.add_bias_linear,
496 - n_groups=config.mamba_ngroups,
497 - num_heads=config.n_mamba_heads,
498 - head_dim=intermediate_size // config.n_mamba_heads,
499 - rms_norm_eps=config.rms_norm_eps,
500 - activation="silu",
501 - quant_config=quant_config,
502 - )
491 + self.mamba = MambaMixer2(hidden_size=config.hidden_size,
492 + ssm_state_size=config.mamba_d_state,
493 + conv_kernel_size=config.mamba_d_conv,
494 + intermediate_size=intermediate_size,
495 + use_conv_bias=config.use_conv_bias,
496 + use_bias=config.add_bias_linear,
497 + n_groups=config.mamba_ngroups,
498 + num_heads=config.n_mamba_heads,
499 + head_dim=intermediate_size //
500 + config.n_mamba_heads,
501 + rms_norm_eps=config.rms_norm_eps,
502 + activation="silu",
503 + quant_config=quant_config,
504 + prefix=f"{prefix}.mixer",
505 + chunk_size=config.chunk_size)
506
507 # Input normalization
508 self.input_layernorm = RMSNorm(config.hidden_size,
@@ -573,6 +576,7 @@ class Zamba2HybridLayer(nn.Module):
576 config: Zamba2Config,
577 block_idx: int,
578 quant_config: Optional[QuantizationConfig] = None,
579 + prefix: str = "",
580 ) -> None:
581 """Initialize the hybrid layer.
582
@@ -589,7 +593,8 @@ class Zamba2HybridLayer(nn.Module):
593 bias=False,
594 quant_config=quant_config)
595 self.mamba_decoder = Zamba2MambaDecoderLayer(config,
592 - quant_config=quant_config)
596 + quant_config=quant_config,
597 + prefix=prefix)
598
599 def forward(
600 self,
@@ -699,14 +704,23 @@ class Zamba2Model(nn.Module):
704 # Initialize layers according to block type configuration
705 layers = []
706 for layer_idx, layer_type in enumerate(config.layers_block_type):
707 + # tdoublep: avoid layers getting same index
708 + # somewhat hacky but correct (I think)
709 + prefix = str(len(layer2block_map) + layer_idx)
710 if layer_type == "hybrid":
711 block = next(blocks)
712 block_idx = layer2block_map[layer_idx]
713 layers.append(
706 - Zamba2HybridLayer(block, config, block_idx, quant_config))
714 + Zamba2HybridLayer(block,
715 + config,
716 + block_idx,
717 + quant_config,
718 + prefix=prefix))
719 else:
720 layers.append(
709 - Zamba2MambaDecoderLayer(config, quant_config=quant_config))
721 + Zamba2MambaDecoderLayer(config,
722 + quant_config=quant_config,
723 + prefix=prefix))
724 self.layers = nn.ModuleList(layers)
725
726 # Final layer normalization
@@ -751,19 +765,30 @@ class Zamba2Model(nn.Module):
765
766 attn_metadata = get_forward_context().attn_metadata
767
754 - mamba2_metadata = prepare_mamba2_metadata(
755 - chunk_size=self.config.chunk_size,
756 - attn_metadata=attn_metadata,
757 - )
768 + if not envs.VLLM_USE_V1:
769 + mamba2_metadata = prepare_mamba2_metadata(
770 + chunk_size=self.config.chunk_size,
771 + attn_metadata=attn_metadata,
772 + )
773 + else:
774 + # v1 get mamba2_metadata from forward_context
775 + mamba2_metadata = None
776
777 # Process through layers
778 original_hidden_states = torch.clone(hidden_states)
779 for layer_idx, layer in enumerate(self.layers):
780 +
781 + layer_mamba_cache_params = None
782 + if (isinstance(layer, (Zamba2HybridLayer, Zamba2MambaDecoderLayer))
783 + and mamba_cache_params):
784 + layer_mamba_cache_params = mamba_cache_params.at_layer_idx(
785 + layer_idx)
786 +
787 layer_outputs = layer(
788 hidden_states,
789 original_hidden_states=original_hidden_states,
790 positions=positions,
766 - mamba_cache_params=mamba_cache_params.at_layer_idx(layer_idx),
791 + mamba_cache_params=layer_mamba_cache_params,
792 mamba2_metadata=mamba2_metadata,
793 )
794 hidden_states = layer_outputs
@@ -803,7 +828,7 @@ class Zamba2Model(nn.Module):
828 return loaded_params
829
830
806 -class Zamba2ForCausalLM(nn.Module, HasInnerState, IsHybrid, SupportsV0Only):
831 +class Zamba2ForCausalLM(nn.Module, HasInnerState, IsHybrid):
832 """Zamba2 model with causal language modeling head.
833
834 This class wraps the core Zamba2 model and adds:
@@ -897,14 +922,16 @@ class Zamba2ForCausalLM(nn.Module, HasInnerState, IsHybrid, SupportsV0Only):
922 Output hidden states
923 """
924 # Initialize Mamba cache if needed
900 - if self.mamba_cache is None:
901 - num_mamba_layers = self.config.num_hidden_layers
902 - self.mamba_cache = MambaCacheManager(
903 - self.vllm_config, self.lm_head.weight.dtype, num_mamba_layers,
904 - *self._get_mamba_cache_shape())
905 -
906 - # Get cache parameters for current run
907 - mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
925 + mamba_cache_params = None
926 + if not envs.VLLM_USE_V1:
927 + if self.mamba_cache is None:
928 + num_mamba_layers = self.config.num_hidden_layers
929 + self.mamba_cache = MambaCacheManager(
930 + self.vllm_config, self.lm_head.weight.dtype,
931 + num_mamba_layers, *self._get_mamba_cache_shape())
932 +
933 + # Get cache parameters for current run
934 + mamba_cache_params = self.mamba_cache.current_run_tensors(**kwargs)
935
936 # Forward pass through model
937 hidden_states = self.model(
vllm/v1/core/kv_cache_coordinator.py
+8 -3
@@ -27,6 +27,7 @@ class KVCacheCoordinator(ABC):
27 ):
28 self.kv_cache_config = kv_cache_config
29 self.max_model_len = max_model_len
30 + self.enable_caching = enable_caching
31
32 self.block_pool = BlockPool(kv_cache_config.num_blocks, enable_caching,
33 enable_kv_cache_events)
@@ -268,9 +269,13 @@ class HybridKVCacheCoordinator(KVCacheCoordinator):
269
270 self.full_attention_block_size = self.full_attention_spec.block_size
271 self.other_block_size = self.other_spec.block_size
271 - assert self.other_block_size % self.full_attention_block_size == 0, (
272 - "KVCacheCoordinator assumes the block_size of full attention "
273 - "layers is divisible by other layers now.")
272 +
273 + if self.enable_caching:
274 + # this requirement is only needed for the prefix caching logic
275 + divisible = self.other_block_size % self.full_attention_block_size
276 + assert divisible == 0, (
277 + "KVCacheCoordinator assumes the block_size of full "
278 + "attention layers is divisible by other layers now.")
279
280 if max(self.full_attention_group_ids) < min(self.other_group_ids):
281 self.full_attn_first = True
vllm/v1/core/kv_cache_manager.py
+10 -6
@@ -84,12 +84,15 @@ class KVCacheManager:
84 self.log_stats = log_stats
85 # FIXME: make prefix cache stats conditional on log_stats
86 self.prefix_cache_stats = PrefixCacheStats() if log_stats else None
87 - assert len(
88 - set(g.kv_cache_spec.block_size
89 - for g in kv_cache_config.kv_cache_groups)
90 - ) == 1, "Only one block size is supported for now"
91 - self.block_size = kv_cache_config.kv_cache_groups[
92 - 0].kv_cache_spec.block_size
87 +
88 + self.block_size: Optional[int] = None
89 + if self.enable_caching:
90 + assert len(
91 + set(g.kv_cache_spec.block_size
92 + for g in kv_cache_config.kv_cache_groups)
93 + ) == 1, "Only one block size is supported for now"
94 + self.block_size = kv_cache_config.kv_cache_groups[
95 + 0].kv_cache_spec.block_size
96
97 self.coordinator = get_kv_cache_coordinator(
98 kv_cache_config=kv_cache_config,
@@ -154,6 +157,7 @@ class KVCacheManager:
157 # if the scheduler has tried to schedule the request before.
158 block_hashes = self.req_to_block_hashes[request.request_id]
159 if not block_hashes:
160 + assert self.block_size is not None
161 block_hashes = hash_request_tokens(self.caching_hash_fn,
162 self.block_size, request)
163 self.req_to_block_hashes[request.request_id] = block_hashes
vllm/v1/core/kv_cache_utils.py
+4 -2
@@ -864,9 +864,11 @@ def _get_kv_cache_config_uniform_page_size(
864 kv_cache_groups=kv_cache_groups,
865 )
866
867 + min_block_size = min(
868 + [group.kv_cache_spec.block_size for group in kv_cache_groups])
869 +
870 # Print the KV cache size and maximum concurrency.
868 - num_tokens = num_blocks // len(
869 - grouped_layers) * vllm_config.cache_config.block_size
871 + num_tokens = num_blocks // len(grouped_layers) * min_block_size
872 num_tokens_str = f"{num_tokens:,}"
873 logger.info("GPU KV cache size: %s tokens", num_tokens_str)
874 max_model_len_str = f"{vllm_config.model_config.max_model_len:,}"
vllm/v1/kv_cache_interface.py
+6 -1
@@ -159,6 +159,7 @@ class SlidingWindowSpec(AttentionSpec):
159 class MambaSpec(KVCacheSpec):
160 shapes: tuple[tuple[int, ...], ...]
161 dtype: torch.dtype
162 + page_size_padded: Optional[int] = None
163
164 def __post_init__(self):
165 self.num_elements = sum(prod(shape) for shape in self.shapes)
@@ -169,7 +170,11 @@ class MambaSpec(KVCacheSpec):
170
171 @property
172 def page_size_bytes(self) -> int:
172 - return self.num_elements * get_dtype_size(self.dtype)
173 + page_size = self.num_elements * get_dtype_size(self.dtype)
174 + if self.page_size_padded is not None:
175 + assert self.page_size_padded >= page_size
176 + return self.page_size_padded
177 + return page_size
178
179 def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
180 # We allocate 1 block for each request now, so max_memory_usage_bytes is
vllm/v1/worker/gpu_model_runner.py
+115 -10
@@ -334,6 +334,9 @@ class GPUModelRunner(LoRAModelRunnerMixin):
334 # the same order of requests. We ensure this by only allowing the first
335 # group to reorder the batch and asserting that all other groups do not
336 # reorder the batch.
337 + # TODO(tdoublep): make this more flexible so that any group can
338 + # re-order the batch (not only the first).
339 + # TODO(tdoublep): verify this during engine init instead of at runtime
340 for i in range(1, len(self.kv_cache_config.kv_cache_groups)):
341 batch_reordered = self.attn_metadata_builders[i].reorder_batch(
342 self.input_batch, scheduler_output)
@@ -2449,6 +2452,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2452 corresponding memory buffer for KV cache.
2453 """
2454 kv_caches: dict[str, torch.Tensor] = {}
2455 + has_attn, has_mamba = False, False
2456 for i, kv_cache_group_spec in enumerate(
2457 kv_cache_config.kv_cache_groups):
2458 kv_cache_spec = kv_cache_group_spec.kv_cache_spec
@@ -2458,6 +2462,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2462 num_blocks = (raw_tensor.numel() //
2463 kv_cache_spec.page_size_bytes)
2464 if isinstance(kv_cache_spec, AttentionSpec):
2465 + has_attn = True
2466 kv_cache_shape = self.attn_backends[i].get_kv_cache_shape(
2467 num_blocks, kv_cache_spec.block_size,
2468 kv_cache_spec.num_kv_heads, kv_cache_spec.head_size)
@@ -2486,25 +2491,67 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2491 layer_name].view(dtype).view(kv_cache_shape).permute(
2492 *inv_order)
2493 elif isinstance(kv_cache_spec, MambaSpec):
2494 + has_mamba = True
2495 raw_tensor = kv_cache_raw_tensors[layer_name]
2496 dtype = kv_cache_spec.dtype
2497 + num_element_per_page = (kv_cache_spec.page_size_bytes //
2498 + get_dtype_size(dtype))
2499 state_tensors = []
2492 - start_pos = 0
2500 + storage_offset = 0
2501 for shape in kv_cache_spec.shapes:
2502 target_shape = (num_blocks, *shape)
2495 - size_in_bytes = np.prod(shape) * get_dtype_size(
2496 - dtype) * num_blocks
2497 - tensor = raw_tensor[start_pos:start_pos +
2498 - size_in_bytes]
2499 - tensor = tensor.view(dtype).view(target_shape)
2503 + stride = torch.empty(target_shape).stride()
2504 + target_stride = (num_element_per_page, *stride[1:])
2505 + tensor = torch.as_strided(
2506 + raw_tensor.view(dtype),
2507 + size=target_shape,
2508 + stride=target_stride,
2509 + storage_offset=storage_offset,
2510 + )
2511 state_tensors.append(tensor)
2501 - start_pos += size_in_bytes
2502 - assert start_pos == raw_tensor.numel()
2503 - kv_caches[layer_name] = tuple(state_tensors)
2512 + storage_offset += stride[0]
2513 +
2514 + kv_caches[layer_name] = state_tensors
2515 else:
2516 raise NotImplementedError
2517 +
2518 + if has_attn and has_mamba:
2519 + self._verify_hybrid_attention_mamba_layout(kv_cache_config,
2520 + kv_cache_raw_tensors)
2521 +
2522 return kv_caches
2523
2524 + def _verify_hybrid_attention_mamba_layout(
2525 + self, kv_cache_config: KVCacheConfig,
2526 + kv_cache_raw_tensors: dict[str, torch.Tensor]) -> None:
2527 + """
2528 + Verify that the KV cache memory layout is compatible for
2529 + models with both attention and mamba KV cache groups.
2530 +
2531 + Args:
2532 + kv_cache_config: The KV cache config
2533 + kv_cache_raw_tensors: The KV cache buffer of each layer.
2534 + """
2535 +
2536 + for i, kv_cache_group_spec in enumerate(
2537 + kv_cache_config.kv_cache_groups):
2538 + kv_cache_spec = kv_cache_group_spec.kv_cache_spec
2539 + for layer_name in kv_cache_group_spec.layer_names:
2540 + raw_tensor = kv_cache_raw_tensors[layer_name]
2541 + num_blocks = (raw_tensor.numel() //
2542 + kv_cache_spec.page_size_bytes)
2543 + if isinstance(kv_cache_spec, AttentionSpec):
2544 + kv_cache_shape = self.attn_backends[i].get_kv_cache_shape(
2545 + num_blocks, kv_cache_spec.block_size,
2546 + kv_cache_spec.num_kv_heads, kv_cache_spec.head_size)
2547 + if kv_cache_shape[0] != num_blocks or kv_cache_shape[
2548 + 1] != 2:
2549 + raise ValueError(
2550 + "Hybrid models in V1 require an attention "
2551 + "backend with kv_cache_shape="
2552 + "(num_blocks, 2, ...). Please try setting "
2553 + "VLLM_ATTENTION_BACKEND=FLASHINFER")
2554 +
2555 def initialize_kv_cache_tensors(
2556 self, kv_cache_config: KVCacheConfig) -> dict[str, torch.Tensor]:
2557 """
@@ -2623,11 +2670,69 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2670 raise NotImplementedError(
2671 "Prefix caching is not supported for Mamba yet.")
2672 max_model_len = self.vllm_config.model_config.max_model_len
2673 +
2674 + page_size_padded = self._maybe_pad_mamba_page_size(
2675 + attn_layers, mamba_layers, kv_cache_spec, max_model_len,
2676 + block_size)
2677 +
2678 # Set block_size to max_model_len, so that mamba model will always
2679 # have only one block in the KV cache.
2680 for layer_name, mamba_module in mamba_layers.items():
2681 kv_cache_spec[layer_name] = MambaSpec(
2682 shapes=mamba_module.get_state_shape(),
2683 dtype=self.kv_cache_dtype,
2632 - block_size=max_model_len)
2684 + block_size=max_model_len,
2685 + page_size_padded=page_size_padded)
2686 +
2687 return kv_cache_spec
2688 +
2689 + def _maybe_pad_mamba_page_size(
2690 + self,
2691 + attn_layers: dict[str, Attention],
2692 + mamba_layers: dict[str, MambaMixer2],
2693 + kv_cache_spec: dict[str, KVCacheSpec],
2694 + max_model_len: int,
2695 + block_size: int,
2696 + ) -> Optional[int]:
2697 + """
2698 + Ensure that page size of attention KV cache groups is greater than or
2699 + equal to the mamba KV cache groups. If not, we suggest to the user
2700 + how to set the attention block size to ensure that it is.
2701 +
2702 + If the attention page size is strictly greater than the mamba page size,
2703 + we pad the mamba page size to make them equal.
2704 +
2705 + Args:
2706 + attn_layers: Attention layers
2707 + mamba_layers: Mamba layers
2708 + kv_cache_spec: KV cache spec (populated with attention layers)
2709 +
2710 + Returns:
2711 + Optional[int]: Mamba page size with padding (None if no padding).
2712 + """
2713 +
2714 + if len(attn_layers) == 0:
2715 + return None
2716 +
2717 + attn_layer_name = next(iter(attn_layers))
2718 + attn_page_size = kv_cache_spec[attn_layer_name].page_size_bytes
2719 + mamba_layer_name = next(iter(mamba_layers))
2720 + mamba_page_size = MambaSpec(
2721 + shapes=mamba_layers[mamba_layer_name].get_state_shape(),
2722 + dtype=self.kv_cache_dtype,
2723 + block_size=max_model_len).page_size_bytes
2724 + if attn_page_size < mamba_page_size:
2725 + # attention page size (for 16 tokens)
2726 + attn_page_size_16 = 16 * attn_page_size // block_size
2727 + # some attention backends (e.g. FA) only support setting
2728 + # block size to multiple of 16, so let's suggest a value
2729 + # that would work (note: FA is currently not compatible
2730 + # with mamba layers, use FlashInfer instead).
2731 + suggest_attn_block_size = 16 * cdiv(mamba_page_size,
2732 + attn_page_size_16)
2733 + raise ValueError(
2734 + "Attention block size should be increased to at least "
2735 + f"{suggest_attn_block_size} in order to match "
2736 + "the mamba page size")
2737 +
2738 + return attn_page_size