@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / dd58932280
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 14, 05:30 UTC

[V1] [Hybrid] Enable compile and piecewise CUDA graph for MiniMax-Text models (#22589)

Signed-off-by: Thomas Parnell <tpa@zurich.ibm.com>

Thomas Parnell committed Aug 27, 2025 at 19:05 UTC dd589322801e2eb8426aa2b95f2729699ff431c5
2 files changed +98 -137
vllm/config/compilation.py
+1
@@ -339,6 +339,7 @@ class CompilationConfig:
339 "vllm.mamba_mixer2",
340 "vllm.mamba_mixer",
341 "vllm.short_conv",
342 + "vllm.linear_attention",
343 ]
344
345 def compute_hash(self) -> str:
vllm/model_executor/models/minimax_text_01.py
+97 -137
@@ -1,7 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inference-only MiniMaxText01 model."""
4 -import copy
4 import math
5 from collections.abc import Iterable
6 from typing import TYPE_CHECKING, Optional, Union
@@ -19,13 +18,14 @@ from transformers import MiniMaxConfig
18
19 from vllm import envs
20 from vllm.attention import Attention, AttentionMetadata
21 +from vllm.compilation.decorators import support_torch_compile
22 from vllm.config import (CacheConfig, ModelConfig, VllmConfig,
23 get_current_vllm_config)
24 from vllm.distributed.communication_op import tensor_model_parallel_all_reduce
25 from vllm.distributed.parallel_state import (
26 get_pp_group, get_tensor_model_parallel_rank,
27 get_tensor_model_parallel_world_size)
28 -from vllm.forward_context import get_forward_context
28 +from vllm.forward_context import ForwardContext, get_forward_context
29 from vllm.model_executor.custom_op import CustomOp
30 from vllm.model_executor.layers.activation import SiluAndMul
31 from vllm.model_executor.layers.fused_moe import FusedMoE
@@ -43,12 +43,15 @@ from vllm.model_executor.layers.mamba.mamba_utils import (
43 MambaStateDtypeCalculator, MambaStateShapeCalculator)
44 from vllm.model_executor.layers.quantization.base_config import (
45 QuantizationConfig)
46 +from vllm.model_executor.layers.rotary_embedding import get_rope
47 from vllm.model_executor.layers.vocab_parallel_embedding import (
48 DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
49 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
50 from vllm.model_executor.models.utils import maybe_prefix
51 from vllm.model_executor.sampling_metadata import SamplingMetadata
52 +from vllm.platforms import current_platform
53 from vllm.sequence import IntermediateTensors
54 +from vllm.utils import direct_register_custom_op
55 from vllm.v1.attention.backends.linear_attn import LinearAttentionMetadata
56
57 from .interfaces import HasInnerState, IsHybrid
@@ -143,61 +146,6 @@ class MiniMaxText01RMSNormTP(CustomOp):
146 return self._forward(x)
147
148
146 -class MiniMaxText01RotaryEmbedding(CustomOp):
147 - name = "MiniMaxText01RotaryEmbedding"
148 -
149 - def __init__(
150 - self,
151 - head_size: int,
152 - rotary_dim: int,
153 - max_position: int,
154 - base: float,
155 - is_neox_style: bool,
156 - cache_dtype: torch.dtype,
157 - ) -> None:
158 - super().__init__()
159 - self.head_size = head_size
160 - self.rotary_dim = rotary_dim
161 - self.max_position_embeddings = max_position
162 - self.base = base
163 - self.is_neox_style = is_neox_style
164 - self.cache_dtype = cache_dtype
165 - cache = self._compute_cos_sin_cache().to(cache_dtype)
166 - self.register_buffer("cos_sin_cache", cache, persistent=False)
167 -
168 - def _compute_inv_freq(self, base: float) -> torch.Tensor:
169 - """Compute the inverse frequency."""
170 - inv_freq = 1.0 / (base**(torch.arange(
171 - 0, self.rotary_dim, 2, dtype=torch.float) / self.rotary_dim))
172 - return inv_freq
173 -
174 - def _compute_cos_sin_cache(self) -> torch.Tensor:
175 - """Compute the cos and sin cache."""
176 - inv_freq = self._compute_inv_freq(self.base)
177 - t = torch.arange(self.max_position_embeddings, dtype=torch.float)
178 - freqs = torch.einsum("i,j -> ij", t, inv_freq)
179 - cos = freqs.cos()
180 - sin = freqs.sin()
181 - cache = torch.cat((cos, sin), dim=-1)
182 - return cache
183 -
184 - def forward(
185 - self,
186 - positions: torch.Tensor,
187 - query: torch.Tensor,
188 - key: torch.Tensor,
189 - ) -> tuple[torch.Tensor, torch.Tensor]:
190 - from vllm import _custom_ops as ops
191 - self.cos_sin_cache = self.cos_sin_cache.to(positions.device)
192 - query_cast = query.to(self.cache_dtype)
193 - key_cast = key.to(self.cache_dtype)
194 - ops.rotary_embedding(positions, query_cast, key_cast, self.head_size,
195 - self.cos_sin_cache, self.is_neox_style)
196 - query = query_cast.to(query.dtype)
197 - key = key_cast.to(key.dtype)
198 - return query, key
199 -
200 -
149 class MiniMaxText01MLP(nn.Module):
150
151 def __init__(
@@ -526,20 +474,40 @@ class MiniMaxText01LinearAttention(nn.Module, MambaBase):
474 slot_id, 32)
475 return hidden
476
529 - def forward(self, hidden_states: torch.Tensor, positions: torch.Tensor,
530 - kv_caches: MinimaxCacheParams, **kwargs) -> torch.Tensor:
531 - qkv, _ = self.qkv_proj(hidden_states)
477 + def forward(self, hidden_states: torch.Tensor, output: torch.Tensor,
478 + positions: torch.Tensor,
479 + kv_caches: MinimaxCacheParams) -> None:
480 + if not envs.VLLM_USE_V1:
481 + self._forward(hidden_states, output, positions, kv_caches)
482 + else:
483 + torch.ops.vllm.linear_attention(
484 + hidden_states,
485 + output,
486 + positions,
487 + self.prefix,
488 + )
489 +
490 + def _forward(self, hidden_states: torch.Tensor, output: torch.Tensor,
491 + positions: torch.Tensor,
492 + kv_caches: Optional[MinimaxCacheParams]) -> None:
493 + forward_context = get_forward_context()
494 + attn_metadata: AttentionMetadata = forward_context.attn_metadata
495 + if envs.VLLM_USE_V1 and attn_metadata is not None:
496 + assert isinstance(attn_metadata, dict)
497 + attn_metadata = attn_metadata[self.prefix]
498 + assert isinstance(attn_metadata, LinearAttentionMetadata)
499 + num_actual_tokens = attn_metadata.num_prefill_tokens + \
500 + attn_metadata.num_decode_tokens
501 + else:
502 + num_actual_tokens = hidden_states.shape[0]
503 +
504 + qkv, _ = self.qkv_proj(hidden_states[:num_actual_tokens])
505 qkv32 = qkv.to(torch.float32)
506 qkvact = torch.nn.functional.silu(qkv32)
507 qkvact = qkvact.view((qkv.shape[0], self.tp_heads, -1))
508 q, k, v = torch.split(qkvact, [self.head_dim] * 3, dim=-1)
536 - forward_context = get_forward_context()
537 - attn_metadata = forward_context.attn_metadata
509 if envs.VLLM_USE_V1:
510 if attn_metadata is not None:
540 - assert isinstance(attn_metadata, dict)
541 - attn_metadata = attn_metadata[self.prefix]
542 - assert isinstance(attn_metadata, LinearAttentionMetadata)
511 kv_cache = self.kv_cache[forward_context.virtual_engine][0]
512 state_indices_tensor = attn_metadata.state_indices_tensor
513
@@ -578,13 +546,11 @@ class MiniMaxText01LinearAttention(nn.Module, MambaBase):
546 hidden = self._decode_infer(q, k, v, kv_cache,
547 state_indices_tensor,
548 attn_metadata)
581 -
549 hidden = self.norm._forward(hidden)
583 - gate, _ = self.output_gate(hidden_states)
550 + gate, _ = self.output_gate(hidden_states[:num_actual_tokens])
551 hidden = F.sigmoid(gate) * hidden
552 hidden = hidden.to(hidden_states.dtype)
586 - hidden, _ = self.out_proj(hidden)
587 - return hidden
553 + output[:num_actual_tokens], _ = self.out_proj(hidden)
554
555
556 class MiniMaxText01Attention(nn.Module):
@@ -652,23 +618,23 @@ class MiniMaxText01Attention(nn.Module):
618 quant_config=quant_config,
619 prefix=f"{prefix}.attn",
620 )
621 + self.rotary_emb = get_rope(
622 + head_size=self.head_dim,
623 + rotary_dim=rotary_dim,
624 + max_position=max_position,
625 + base=int(rope_theta),
626 + is_neox_style=True,
627 + dtype=torch.float32,
628 + )
629 return
630
657 - def forward(self, hidden_states: torch.Tensor, positions: torch.Tensor,
658 - **kwargs) -> torch.Tensor:
659 - forward_context = get_forward_context()
660 - attn_metadata = forward_context.attn_metadata
631 + def forward(self, hidden_states: torch.Tensor, output: torch.Tensor,
632 + positions: torch.Tensor, **kwargs) -> None:
633 qkv, _ = self.qkv_proj(hidden_states)
634 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
663 - if envs.VLLM_USE_V1:
664 - if attn_metadata is not None:
665 - q, k = attn_metadata[f"{self.prefix}.attn"].rotary_emb(
666 - positions, q, k)
667 - else:
668 - q, k = attn_metadata.rotary_emb(positions, q, k)
635 + q, k = self.rotary_emb(positions, q, k)
636 attn_output = self.attn(q, k, v)
670 - output, _ = self.o_proj(attn_output)
671 - return output
637 + output[:], _ = self.o_proj(attn_output)
638
639
640 class MiniMaxText01DecoderLayer(nn.Module):
@@ -816,16 +782,15 @@ class MiniMaxText01DecoderLayer(nn.Module):
782 is_warmup: bool = False,
783 **kwargs) -> tuple[torch.Tensor, torch.Tensor]:
784
819 - forward_context = get_forward_context()
820 - attn_metadata = forward_context.attn_metadata
785 layernorm_input = hidden_states
786 layernorm_output = self.input_layernorm(layernorm_input)
787 residual = layernorm_output if self.postnorm else layernorm_input
824 - self_attention_output = self.self_attn(
788 + self_attention_output = torch.empty_like(layernorm_output)
789 + self.self_attn(
790 hidden_states=layernorm_output,
791 + output=self_attention_output,
792 positions=positions,
793 kv_caches=kv_caches,
828 - attn_metadata=attn_metadata,
794 )
795
796 residual = residual * self.layernorm_attention_alpha
@@ -839,8 +804,8 @@ class MiniMaxText01DecoderLayer(nn.Module):
804 if self.expert_num == 1:
805 hidden_states = self.mlp(layernorm_output)
806 else:
842 - moe_hidden_states = self.block_sparse_moe(
843 - copy.deepcopy(layernorm_output))
807 + moe_layernorm_output = layernorm_output.clone()
808 + moe_hidden_states = self.block_sparse_moe(moe_layernorm_output)
809 if self.shared_moe:
810 before_moe_dtype = layernorm_output.dtype
811 moe_hidden_fp32 = moe_hidden_states.to(torch.float32)
@@ -878,18 +843,16 @@ class MiniMaxText01DecoderLayer(nn.Module):
843 return
844
845
846 +@support_torch_compile
847 class MiniMaxText01Model(nn.Module):
848
883 - def __init__(
884 - self,
885 - config: MiniMaxConfig,
886 - model_config: Optional[ModelConfig] = None,
887 - quant_config: Optional[QuantizationConfig] = None,
888 - cache_config: Optional[CacheConfig] = None,
889 - scheduler_config=None,
890 - prefix: str = "",
891 - ) -> None:
849 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
850 super().__init__()
851 + config: MiniMaxConfig = vllm_config.model_config.hf_config
852 + model_config = vllm_config.model_config
853 + quant_config = vllm_config.quant_config
854 + cache_config = vllm_config.cache_config
855 + scheduler_config = vllm_config.scheduler_config
856
857 self.padding_idx = config.pad_token_id
858 self.vocab_size = config.vocab_size
@@ -976,24 +939,6 @@ class MiniMaxText01Model(nn.Module):
939 self.minimax_cache = MinimaxCacheManager(
940 dtype=torch.float32, cache_shape=self.cache_shape)
941
979 - rope_theta = getattr(config, "rope_theta", 10000)
980 - head_dim = getattr(config, "head_dim", None)
981 - if head_dim is None:
982 - head_dim = config.hidden_size // config.num_attention_heads
983 - if hasattr(config, "max_model_len") and isinstance(
984 - config.max_model_len, int):
985 - max_position_embeddings = min(config.max_position_embeddings,
986 - config.max_model_len)
987 - self.rotary_emb = MiniMaxText01RotaryEmbedding(
988 - head_dim,
989 - rotary_dim=config.rotary_dim
990 - if hasattr(config, "rotary_dim") else head_dim,
991 - max_position=max_position_embeddings,
992 - base=int(rope_theta),
993 - is_neox_style=True,
994 - cache_dtype=torch.float32,
995 - )
996 -
942 norm_kwargs = {}
943 if hasattr(config, "rms_norm_eps"):
944 norm_kwargs["eps"] = config.rms_norm_eps
@@ -1043,12 +988,11 @@ class MiniMaxText01Model(nn.Module):
988 attn_metadata = forward_context.attn_metadata
989 if not envs.VLLM_USE_V1 and attn_metadata is None:
990 return None
1046 - if "request_ids_to_seq_ids" not in kwargs:
1047 - kwargs["request_ids_to_seq_ids"] = {}
1048 - if "finished_requests_ids" not in kwargs:
1049 - kwargs["finished_requests_ids"] = []
1050 -
991 if not envs.VLLM_USE_V1:
992 + if "request_ids_to_seq_ids" not in kwargs:
993 + kwargs["request_ids_to_seq_ids"] = {}
994 + if "finished_requests_ids" not in kwargs:
995 + kwargs["finished_requests_ids"] = []
996 (
997 minimax_cache_tensors,
998 state_indices_tensor,
@@ -1077,16 +1021,6 @@ class MiniMaxText01Model(nn.Module):
1021
1022 for i in range(self.start_layer, self.end_layer):
1023 layer = self.layers[i]
1080 - if attn_metadata is not None:
1081 - # TODO (tdoublep): this whole thing with the rotary_emb is
1082 - # weird. we shouldn't be passing it via attn_metadata imo.
1083 - if envs.VLLM_USE_V1:
1084 - if isinstance(layer.self_attn, MiniMaxText01Attention):
1085 - attn_metadata[layer.prefix +
1086 - ".attn"].rotary_emb = self.rotary_emb
1087 - else:
1088 - attn_metadata.rotary_emb = self.rotary_emb
1089 -
1024 _caches = None
1025 if not envs.VLLM_USE_V1 and isinstance(
1026 layer.self_attn, MiniMaxText01LinearAttention):
@@ -1120,7 +1054,6 @@ class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid):
1054
1055 super().__init__()
1056 config = vllm_config.model_config.hf_config
1123 - quant_config = vllm_config.quant_config
1057 lora_config = vllm_config.lora_config
1058 self.config = config
1059 self.lora_config = lora_config
@@ -1133,13 +1066,8 @@ class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid):
1066 self.unpadded_vocab_size = self.config.vocab_size
1067 if hasattr(vllm_config.model_config, "max_model_len"):
1068 self.config.max_model_len = vllm_config.model_config.max_model_len
1136 - self.model = MiniMaxText01Model(
1137 - self.config,
1138 - model_config=vllm_config.model_config,
1139 - cache_config=vllm_config.cache_config,
1140 - quant_config=quant_config,
1141 - scheduler_config=vllm_config.scheduler_config,
1142 - prefix=maybe_prefix(prefix, "model"))
1069 + self.model = MiniMaxText01Model(vllm_config=vllm_config,
1070 + prefix=maybe_prefix(prefix, "model"))
1071 if get_pp_group().is_last_rank:
1072 self.lm_head = ParallelLMHead(
1073 self.unpadded_vocab_size,
@@ -1469,3 +1397,35 @@ class MiniMaxText01ForCausalLM(nn.Module, HasInnerState, IsHybrid):
1397 tp_size=parallel_config.tensor_parallel_size,
1398 head_dim=hf_config.head_dim,
1399 )
1400 +
1401 +
1402 +def linear_attention(
1403 + hidden_states: torch.Tensor,
1404 + output: torch.Tensor,
1405 + positions: torch.Tensor,
1406 + layer_name: str,
1407 +) -> None:
1408 + forward_context: ForwardContext = get_forward_context()
1409 + self = forward_context.no_compile_layers[layer_name]
1410 + self._forward(hidden_states=hidden_states,
1411 + output=output,
1412 + positions=positions,
1413 + kv_caches=None)
1414 +
1415 +
1416 +def linear_attention_fake(
1417 + hidden_states: torch.Tensor,
1418 + output: torch.Tensor,
1419 + positions: torch.Tensor,
1420 + layer_name: str,
1421 +) -> None:
1422 + return
1423 +
1424 +
1425 +direct_register_custom_op(
1426 + op_name="linear_attention",
1427 + op_func=linear_attention,
1428 + mutates_args=["output"],
1429 + fake_impl=linear_attention_fake,
1430 + dispatch_key=current_platform.dispatch_key,
1431 +)