@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 10d765482d
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

`FusedMoE` support for the Transformers backend (#22650)

Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Harry Mellor committed Oct 3, 2025 at 07:12 UTC 10d765482d19abfab6c66b5f815720a66aa9de42
10 files changed +485 -91
docs/models/supported_models.md
+3 -2
@@ -17,12 +17,12 @@ These models are what we list in [supported-text-models][supported-text-models]
17
18 ### Transformers
19
20 -vLLM also supports model implementations that are available in Transformers. You should expect the performance of a Transformers model implementation used in vLLM to be within <1% of the performance of a dedicated vLLM model implementation. We call this feature the "Transformers backend".
20 +vLLM also supports model implementations that are available in Transformers. You should expect the performance of a Transformers model implementation used in vLLM to be within <5% of the performance of a dedicated vLLM model implementation. We call this feature the "Transformers backend".
21
22 Currently, the Transformers backend works for the following:
23
24 - Modalities: embedding models, language models and vision-language models*
25 -- Architectures: encoder-only, decoder-only
25 +- Architectures: encoder-only, decoder-only, mixture-of-experts
26 - Attention types: full attention and/or sliding attention
27
28 _*Vision-language models currently accept only image inputs. Support for video inputs will be added in a future release._
@@ -31,6 +31,7 @@ If the Transformers model implementation follows all the steps in [writing a cus
31
32 - All the features listed in the [compatibility matrix](../features/README.md#feature-x-feature)
33 - Any combination of the following vLLM parallelisation schemes:
34 + - Data parallel
35 - Pipeline parallel
36 - Tensor parallel
37
tests/models/registry.py
+4
@@ -661,6 +661,10 @@ _TRANSFORMERS_BACKEND_MODELS = {
661 "TransformersForSequenceClassification": _HfExamplesInfo("papluca/xlm-roberta-base-language-detection", min_transformers_version="4.57.0.dev0"), # noqa: E501
662 "TransformersForCausalLM": _HfExamplesInfo("hmellor/Ilama-3.2-1B", trust_remote_code=True), # noqa: E501
663 "TransformersForMultimodalLM": _HfExamplesInfo("BAAI/Emu3-Chat-hf"),
664 + "TransformersMoEForCausalLM": _HfExamplesInfo("allenai/OLMoE-1B-7B-0924", min_transformers_version="4.57.0.dev0"), # noqa: E501
665 + "TransformersMoEForMultimodalLM": _HfExamplesInfo("Qwen/Qwen3-VL-30B-A3B-Instruct", min_transformers_version="4.57.0.dev0"), # noqa: E501
666 + "TransformersMoEEmbeddingModel": _HfExamplesInfo("Qwen/Qwen3-30B-A3B", min_transformers_version="4.57.0.dev0"), # noqa: E501
667 + "TransformersMoEForSequenceClassification": _HfExamplesInfo("Qwen/Qwen3-30B-A3B", min_transformers_version="4.57.0.dev0"), # noqa: E501
668 }
669
670 _EXAMPLE_MODELS = {
tests/models/test_transformers.py
+9
@@ -66,6 +66,7 @@ def check_implementation(
66 [
67 ("meta-llama/Llama-3.2-1B-Instruct", "transformers"),
68 ("hmellor/Ilama-3.2-1B", "auto"), # CUSTOM CODE
69 + ("allenai/OLMoE-1B-7B-0924", "transformers"), # MoE
70 ]) # trust_remote_code=True by default
71 def test_models(
72 hf_runner: type[HfRunner],
@@ -74,6 +75,14 @@ def test_models(
75 model: str,
76 model_impl: str,
77 ) -> None:
78 + import transformers
79 + from packaging.version import Version
80 + installed = Version(transformers.__version__)
81 + required = Version("4.57.0.dev0")
82 + if model == "allenai/OLMoE-1B-7B-0924" and installed < required:
83 + pytest.skip("MoE models with the Transformers backend require "
84 + f"transformers>={required}, but got {installed}")
85 +
86 check_implementation(hf_runner,
87 vllm_runner,
88 example_prompts,
tests/models/utils.py
+16 -7
@@ -430,17 +430,26 @@ def dummy_hf_overrides(
430
431 update_dict = {
432 "num_layers": num_layers,
433 - "num_experts": num_experts,
434 - "num_experts_per_tok": 2,
435 - "num_local_experts": num_experts,
436 - # Otherwise there will not be any expert layers
437 - "first_k_dense_replace": 0,
438 - # To avoid OOM on DeepSeek-V3
439 - "n_routed_experts": num_experts,
433 # For Gemma-3n
434 "num_kv_shared_layers": 1,
435 }
436
437 + class DummyConfig:
438 + hf_text_config = text_config
439 +
440 + # Only set MoE related config when the model has MoE layers.
441 + # Otherwise all models detected as MoE by _get_transformers_backend_cls.
442 + if ModelConfig.get_num_experts(DummyConfig) > 0:
443 + update_dict.update({
444 + "num_experts": num_experts,
445 + "num_experts_per_tok": 2,
446 + "num_local_experts": num_experts,
447 + # Otherwise there will not be any expert layers
448 + "first_k_dense_replace": 0,
449 + # To avoid OOM on DeepSeek-V3
450 + "n_routed_experts": num_experts,
451 + })
452 +
453 # Update num_hidden_layers for non-Longcat architectures
454 if model_arch != "LongcatFlashForCausalLM" \
455 and model_arch != "LongCatFlashMTPModel":
vllm/config/model.py
+23 -16
@@ -20,7 +20,7 @@ from vllm.config.multimodal import (MMCacheType, MMEncoderTPMode,
20 MultiModalConfig)
21 from vllm.config.pooler import PoolerConfig
22 from vllm.config.scheduler import RunnerType
23 -from vllm.config.utils import assert_hashable, config
23 +from vllm.config.utils import assert_hashable, config, getattr_iter
24 from vllm.logger import init_logger
25 from vllm.platforms import current_platform
26 from vllm.transformers_utils.config import (
@@ -667,6 +667,8 @@ class ModelConfig:
667 def _get_transformers_backend_cls(self) -> str:
668 """Determine which Transformers backend class will be used if
669 `model_impl` is set to `transformers` or `auto`."""
670 + prefix = "Transformers"
671 + prefix += "MoE" if self.get_num_experts() > 1 else ""
672 # Check if the architecture we're wrapping has defaults
673 runner = None
674 convert = None
@@ -685,15 +687,15 @@ class ModelConfig:
687 # Resolve Transformers backend pooling classes
688 if runner == "pooling":
689 if convert == "embed":
688 - return "TransformersEmbeddingModel"
690 + return prefix + "EmbeddingModel"
691 if convert == "classify":
690 - return "TransformersForSequenceClassification"
692 + return prefix + "ForSequenceClassification"
693 # Resolve Transformers backend generate classes
694 if self.hf_config != self.hf_text_config:
695 # If 'hf_text_config' is the same as 'hf_config'. If not, it is
696 # probably a composite config, i.e. multimodal
695 - return "TransformersForMultimodalLM"
696 - return "TransformersForCausalLM"
697 + return prefix + "ForMultimodalLM"
698 + return prefix + "ForCausalLM"
699
700 def using_transformers_backend(self) -> bool:
701 """Check if the model is using the Transformers backend class."""
@@ -1025,17 +1027,7 @@ class ModelConfig:
1027 self.enforce_eager = True
1028
1029 def _verify_with_expert_parallelism(self) -> None:
1028 - num_expert_names = [
1029 - "moe_num_experts", # Dbrx
1030 - "num_experts", # Jamba
1031 - "n_routed_experts", # DeepSeek
1032 - "num_local_experts", # Mixtral
1033 - ]
1034 - num_experts = 0
1035 - for name in num_expert_names:
1036 - num_experts = getattr(self.hf_text_config, name, 0)
1037 - if num_experts > 0:
1038 - break
1030 + num_experts = self.get_num_experts()
1031 if num_experts < 1:
1032 raise ValueError(
1033 "Number of experts in the model must be greater than 0 "
@@ -1220,6 +1212,21 @@ class ModelConfig:
1212 num_heads = getattr(self.hf_text_config, "num_attention_heads", 0)
1213 return num_heads // parallel_config.tensor_parallel_size
1214
1215 + def get_num_experts(self) -> int:
1216 + """Returns the number of experts in the model."""
1217 + num_expert_names = [
1218 + "num_experts", # Jamba
1219 + "moe_num_experts", # Dbrx
1220 + "n_routed_experts", # DeepSeek
1221 + "num_local_experts", # Mixtral
1222 + ]
1223 + num_experts = getattr_iter(self.hf_text_config, num_expert_names, 0)
1224 + if isinstance(num_experts, list):
1225 + # Ernie VL's remote code uses list[int]...
1226 + # The values are always the same so we just take the first one.
1227 + return num_experts[0]
1228 + return num_experts
1229 +
1230 def get_layers_start_end_indices(
1231 self, parallel_config: ParallelConfig) -> tuple[int, int]:
1232 from vllm.distributed.utils import get_pp_indices
vllm/model_executor/layers/fused_moe/layer.py
+31
@@ -960,6 +960,7 @@ class FusedMoE(CustomOp):
960 is_sequence_parallel=False,
961 zero_expert_num: Optional[int] = 0,
962 zero_expert_type: Optional[str] = None,
963 + expert_mapping: Optional[list[tuple[str, str, int, str]]] = None,
964 ):
965 super().__init__()
966 if params_dtype is None:
@@ -996,6 +997,9 @@ class FusedMoE(CustomOp):
997 self.zero_expert_num = zero_expert_num
998 self.zero_expert_type = zero_expert_type
999
1000 + # Expert mapping used in self.load_weights
1001 + self.expert_mapping = expert_mapping
1002 +
1003 # Round up hidden size if needed.
1004 hidden_size = maybe_roundup_hidden_size(hidden_size, moe_in_dtype,
1005 quant_config,
@@ -1617,6 +1621,33 @@ class FusedMoE(CustomOp):
1621
1622 return False if return_success else None
1623
1624 + def load_weights(
1625 + self, weights: Iterable[tuple[str,
1626 + torch.Tensor]]) -> Iterable[str]:
1627 + if (expert_mapping := self.expert_mapping) is None:
1628 + raise ValueError("`self.expert_mapping` must be provided to "
1629 + "load weights using `self.load_weights`.")
1630 + for expert_name, loaded_weight in weights:
1631 + qual_name = f"{self.layer_name}.{expert_name}"
1632 + for param_name, weight_name, expert_id, shard_id in expert_mapping:
1633 + if weight_name not in qual_name:
1634 + continue
1635 + weight_name = qual_name.replace(weight_name, param_name)
1636 + param_name = weight_name.removeprefix(f"{self.layer_name}.")
1637 + param = getattr(self, param_name)
1638 + success = self.weight_loader(
1639 + param=param,
1640 + loaded_weight=loaded_weight,
1641 + weight_name=weight_name,
1642 + shard_id=shard_id,
1643 + expert_id=expert_id,
1644 + return_success=True,
1645 + )
1646 + if success:
1647 + logger.debug("Loaded %s for expert %d into %s", param_name,
1648 + expert_id, self.layer_name)
1649 + yield param_name
1650 +
1651 def get_expert_weights(self) -> Iterable[torch.Tensor]:
1652 weights = list(self.named_parameters())
1653 assert all(weight.is_contiguous() for _, weight in weights)
vllm/model_executor/models/registry.py
+6 -2
@@ -307,10 +307,14 @@ _TRANSFORMERS_SUPPORTED_MODELS = {
307 }
308
309 _TRANSFORMERS_BACKEND_MODELS = {
310 - "TransformersEmbeddingModel": ("transformers_pooling", "TransformersEmbeddingModel"), # noqa: E501
311 - "TransformersForSequenceClassification": ("transformers_pooling", "TransformersForSequenceClassification"), # noqa: E501
310 "TransformersForCausalLM": ("transformers", "TransformersForCausalLM"),
311 "TransformersForMultimodalLM": ("transformers", "TransformersForMultimodalLM"), # noqa: E501
312 + "TransformersMoEForCausalLM": ("transformers_moe", "TransformersMoEForCausalLM"), # noqa: E501
313 + "TransformersMoEForMultimodalLM": ("transformers_moe", "TransformersMoEForMultimodalLM"), # noqa: E501
314 + "TransformersEmbeddingModel": ("transformers_pooling", "TransformersEmbeddingModel"), # noqa: E501
315 + "TransformersForSequenceClassification": ("transformers_pooling", "TransformersForSequenceClassification"), # noqa: E501
316 + "TransformersMoEForSequenceClassification": ("transformers_pooling", "TransformersMoEForSequenceClassification"), # noqa: E501
317 + "TransformersMoEEmbeddingModel": ("transformers_pooling", "TransformersMoEEmbeddingModel"), # noqa: E501
318 }
319 # yapf: enable
320
vllm/model_executor/models/transformers.py
+107 -54
@@ -22,6 +22,8 @@ from typing import Literal, Optional, Union
22
23 import regex as re
24 import torch
25 +import transformers
26 +from packaging.version import Version
27 from torch import nn
28 from transformers import (AutoModel, BatchFeature, PretrainedConfig,
29 PreTrainedModel)
@@ -35,6 +37,7 @@ from vllm.config.utils import getattr_iter
37 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
38 from vllm.distributed.utils import get_pp_indices
39 from vllm.logger import init_logger
40 +from vllm.model_executor.layers.layernorm import RMSNorm
41 from vllm.model_executor.layers.linear import (ColumnParallelLinear,
42 ReplicatedLinear,
43 RowParallelLinear)
@@ -121,10 +124,14 @@ def can_enable_torch_compile(vllm_config: VllmConfig) -> bool:
124 return enable
125
126
127 +Style = Literal["colwise", "colwise_rep", "rowwise", "rowwise_rep",
128 + "replicate"]
129 +
130 +
131 def replace_linear_class(
132 linear: nn.Linear,
126 - style: Literal["colwise", "rowwise"],
127 - quant_config: QuantizationConfig,
133 + style: Style = "replicate",
134 + quant_config: Optional[QuantizationConfig] = None,
135 *,
136 prefix: str = "",
137 ) -> Union[ColumnParallelLinear, RowParallelLinear, ReplicatedLinear]:
@@ -132,11 +139,11 @@ def replace_linear_class(
139 Replace nn.Linear with one of vLLM's tensor parallel linear classes.
140
141 Args:
135 - linear (nn.Linear): `nn.Linear` to be replaced.
136 - style (str): Tensor parallel style of the new linear, e.g. "colwise".
137 - quant_config (QuantConfig): Quantization config for the new linear.
142 + linear: `nn.Linear` to be replaced.
143 + style: Tensor parallel style of the new linear, e.g. "colwise".
144 + quant_config: Quantization config for the new linear.
145 Returns:
139 - Union[ColumnParallelLinear, RowParallelLinear]: The new linear.
146 + The new linear.
147 """
148
149 if not isinstance(style, str):
@@ -166,6 +173,31 @@ def replace_linear_class(
173 )
174
175
176 +def replace_rms_norm_class(rms_norm: nn.Module, hidden_size: int) -> RMSNorm:
177 + """Replace a Transformers RMSNorm with vLLM's RMSNorm.
178 +
179 + This method assumes:
180 + - Weight is stored as `weight`.
181 + - Epsilon is stored as `eps` or `variance_epsilon`.
182 + - `with_scale` indicates whether the layer has a weight (Gemma3n only).
183 + - `var_hidden_size` is only ever used for Intern vision encoder in vLLM
184 + and Transformers doesn't appear to have the same concept.
185 + """
186 + kwargs = {
187 + "hidden_size": hidden_size,
188 + "eps": getattr_iter(rms_norm, ("eps", "variance_epsilon"), 1e-6),
189 + "has_weight": getattr(rms_norm, "with_scale", True)
190 + }
191 + if (weight := getattr(rms_norm, "weight", None)) is not None:
192 + # If weight is a Parameter, get its data tensor
193 + weight = getattr(weight, "data", weight)
194 + kwargs["dtype"] = weight.dtype
195 + else:
196 + # No weight, fall back to weightless RMSNorm
197 + kwargs["has_weight"] = False
198 + return RMSNorm(**kwargs)
199 +
200 +
201 # Copied from `accelerate`
202 @contextmanager
203 def init_on_device_without_buffers(device: torch.device):
@@ -463,9 +495,15 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
495 self.ignore_unexpected_suffixes: list[str] = []
496 """Ignore unexpected weights whose qualname ends with these suffixes."""
497
466 - # Skip loading extra bias for GPTQ models.
467 - if self.quant_config and "gptq" in self.quant_config.get_name():
468 - self.ignore_unexpected_suffixes.append(".bias")
498 + if self.quant_config:
499 + quant_method_name = self.quant_config.get_name()
500 + # Check for unsupported quantization methods.
501 + if quant_method_name == "mxfp4":
502 + raise NotImplementedError("Transformers backend does not "
503 + "support MXFP4 quantization yet.")
504 + # Skip loading extra bias for GPTQ models.
505 + if "gptq" in quant_method_name:
506 + self.ignore_unexpected_suffixes.append(".bias")
507
508 # Set correct attn and init on "meta" to delay allocating GPU tensors
509 # TODO: @raushan, use the public `model.set_attn_implementation()`
@@ -478,8 +516,12 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
516 trust_remote_code=self.model_config.trust_remote_code,
517 )
518
519 + # Remove layers not on this pipeline parallel rank
520 self.pipeline_parallel()
482 - self.tensor_parallel()
521 + # Substitute remaining layers with vLLM's layers as needed
522 + self.recursive_replace()
523 + # Create attention instances for KV cache allocation
524 + self.attention_instances = self.create_attention_instances()
525
526 # Input embeddings
527 if not isinstance(self.model.get_input_embeddings(), PPMissingLayer):
@@ -494,12 +536,10 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
536 quant_config=self.quant_config,
537 ))
538
497 - # Attention layers
498 - self.attention_instances = self.create_attention_instances()
499 -
539 # Initialize any parameters that have not had their modules replaced
540 self.init_parameters(self.model)
541
542 + # Pipeline parallel intermediate tensors
543 self.make_empty_intermediate_tensors = (
544 make_empty_intermediate_tensors_factory(
545 ["hidden_states"], self.text_config.hidden_size))
@@ -558,56 +598,53 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
598 if not self.pp_group.is_last_rank:
599 setattr(self.model, name, PPMissingLayer())
600
561 - def tensor_parallel(self):
562 - """
563 - Apply the model's tensor parallelization plan.
564 - Currently only supports linear layers.
601 + def recursive_replace(self):
602 + """Recursively replace modules in the model as needed.
603 +
604 + Currently, this replaces:
605 +
606 + - `nn.Linear` with vLLM's tensor parallel linear classes
607 + - `*RMSNorm` with vLLM's `RMSNorm`
608 """
566 - # Look for tp plans in all of the PreTrainedModels found in self.model
567 - is_pretrained_model = lambda m: isinstance(m, PreTrainedModel)
568 - supports_tp_plan = lambda m: m.config.base_model_tp_plan is not None
569 - pretrained_models = filter(is_pretrained_model, self.model.modules())
570 - models_with_tp_plan = filter(supports_tp_plan, pretrained_models)
609 + tp_plan = self.model.tp_plan
610
572 - if not any(models_with_tp_plan) and self.tp_size > 1:
611 + if not tp_plan and self.tp_size > 1:
612 tip = get_feature_request_tip(self.model_config.model,
613 self.model_config.trust_remote_code)
614 raise ValueError(
615 f"{type(self.model)} does not support tensor parallel. {tip}")
616
578 - def _tensor_parallel(module: nn.Module, prefix: str, tp_plan=None):
579 - tp_plan = tp_plan or {}
580 -
581 - # If the current module is a PreTrainedModel, set the tp_plan for
582 - # all of its children
583 - if isinstance(module, PreTrainedModel):
584 - tp_plan = module.config.base_model_tp_plan or {}
585 - tp_plan = {
586 - maybe_prefix(prefix, k): v
587 - for k, v in tp_plan.items()
588 - }
589 -
590 - # Some weight loaders expect linear layers to inherit from vLLM's
591 - # LinearBase class, so we set a default style which causes any
592 - # unspecified linear layers to be replaced with ReplicatedLinear
617 + # Prefix the patterns because we always start from `self.model`
618 + tp_plan = {maybe_prefix("model", k): v for k, v in tp_plan.items()}
619 +
620 + def _recursive_replace(module: nn.Module, prefix: str):
621 for child_name, child_module in module.named_children():
622 + new_module = child_module
623 qual_name = maybe_prefix(prefix, child_name)
624 if isinstance(child_module, nn.Linear):
625 generator = (p for p in tp_plan if re.match(p, qual_name))
626 pattern = next(generator, None)
627 + # Some weight loaders expect all linear layers to inherit
628 + # LinearBase, so we set a default style which causes any
629 + # unspecified layers to be replaced with ReplicatedLinear
630 style = tp_plan.get(pattern, "replicate")
631 new_module = replace_linear_class(child_module,
632 style,
633 self.quant_config,
634 prefix=qual_name)
635 + # TODO(hmellor): Enable RMSNorm replacement once we have a way
636 + # to choose RMSNorm vs GemmaRMSNorm
637 + # elif child_module.__class__.__name__.endswith("RMSNorm"):
638 + # new_module = replace_rms_norm_class(
639 + # child_module, self.config.hidden_size)
640 + else:
641 + _recursive_replace(child_module, prefix=qual_name)
642 +
643 + if new_module is not child_module:
644 setattr(module, child_name, new_module)
645 log_replacement(qual_name, child_module, new_module)
605 - else:
606 - _tensor_parallel(child_module,
607 - prefix=qual_name,
608 - tp_plan=tp_plan)
646
610 - _tensor_parallel(self.model, prefix="model")
647 + _recursive_replace(self.model, prefix="model")
648
649 def create_attention_instances(
650 self,
@@ -657,15 +694,21 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
694 self.model: PreTrainedModel = AutoModel.from_config(...)
695 ```
696 """
660 - for name, param in module.named_parameters(recurse=False):
661 - if param.device == torch.device("meta"):
662 - new_param = nn.Parameter(
663 - torch.empty_like(param.data,
664 - dtype=dtype or self.model_config.dtype,
665 - device=self.device_config.device))
666 - setattr(module, name, new_param)
667 - for child in module.children():
668 - self.init_parameters(child, dtype)
697 +
698 + def _init_parameters(module: nn.Module, dtype: Optional[torch.dtype]):
699 + for name, param in module.named_parameters(recurse=False):
700 + if param.device == torch.device("meta"):
701 + new_param = nn.Parameter(
702 + torch.empty_like(
703 + param.data,
704 + dtype=dtype or self.model_config.dtype,
705 + device=self.device_config.device,
706 + ))
707 + setattr(module, name, new_param)
708 + for child in module.children():
709 + _init_parameters(child, dtype)
710 +
711 + _init_parameters(module, dtype)
712
713 def forward(
714 self,
@@ -702,8 +745,10 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
745
746 return hidden_states
747
705 - def load_weights(self, weights: Iterable[tuple[str,
706 - torch.Tensor]]) -> set[str]:
748 + def load_weights(
749 + self,
750 + weights: Iterable[tuple[str, torch.Tensor]],
751 + ) -> set[str]:
752 loader = AutoWeightsLoader(
753 self,
754 skip_prefixes=self.skip_prefixes,
@@ -713,6 +758,14 @@ class TransformersBase(nn.Module, SupportsQuant, SupportsLoRA, SupportsPP):
758 )
759 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
760
761 + def check_version(self, min_version: str, feature: str):
762 + installed = Version(transformers.__version__)
763 + required = Version(min_version)
764 + if installed < required:
765 + raise ImportError(
766 + f"Transformers backend requires transformers>={required} "
767 + f"for {feature}, but got {installed}")
768 +
769
770 @support_torch_compile(enable_if=can_enable_torch_compile)
771 class TransformersForCausalLM(TransformersBase):
vllm/model_executor/models/transformers_moe.py new
+268
@@ -0,0 +1,268 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +# Copyright 2024 The vLLM team.
5 +#
6 +# Licensed under the Apache License, Version 2.0 (the "License");
7 +# you may not use this file except in compliance with the License.
8 +# You may obtain a copy of the License at
9 +#
10 +# http://www.apache.org/licenses/LICENSE-2.0
11 +#
12 +# Unless required by applicable law or agreed to in writing, software
13 +# distributed under the License is distributed on an "AS IS" BASIS,
14 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 +# See the License for the specific language governing permissions and
16 +# limitations under the License.
17 +"""Wrapper around `transformers` MoE models."""
18 +from typing import Any
19 +
20 +import torch
21 +import torch.nn as nn
22 +
23 +from vllm.compilation.decorators import support_torch_compile
24 +from vllm.config.utils import getattr_iter
25 +from vllm.forward_context import ForwardContext, get_forward_context
26 +from vllm.model_executor.custom_op import CustomOp
27 +from vllm.model_executor.layers.fused_moe import FusedMoE
28 +from vllm.platforms import current_platform
29 +from vllm.utils import direct_register_custom_op
30 +
31 +from .transformers import (TransformersBase, TransformersForCausalLM,
32 + TransformersForMultimodalLM,
33 + can_enable_torch_compile, log_replacement)
34 +from .utils import maybe_prefix
35 +
36 +
37 +@CustomOp.register("transformers_fused_moe")
38 +class TransformersFusedMoE(FusedMoE):
39 + """Custom FusedMoE for the Transformers backend."""
40 +
41 + def __init__(self, *args, **kwargs):
42 + super().__init__(*args, **kwargs)
43 + self._top_k_index: torch.Tensor = None
44 +
45 + def custom_routing_function(hidden_states, gating_output, topk,
46 + renormalize):
47 + """Return `top_k_weights` from `gating_output` and the
48 + `top_k_index` we stored in the layer earlier."""
49 + return gating_output, self._top_k_index
50 +
51 + self.custom_routing_function = custom_routing_function
52 +
53 + def forward(self, hidden_states: torch.Tensor, top_k_index: torch.Tensor,
54 + top_k_weights: torch.Tensor, **kwargs: Any) -> torch.Tensor:
55 + """In Transformers `experts.forward` will have this signature.
56 +
57 + We discard any extra kwargs because we cannot use them here."""
58 + return torch.ops.vllm.transformers_moe_forward(hidden_states,
59 + top_k_index,
60 + top_k_weights,
61 + self.layer_name)
62 +
63 +
64 +def transformers_moe_forward(hidden_states: torch.Tensor,
65 + top_k_index: torch.Tensor,
66 + top_k_weights: torch.Tensor,
67 + layer_name: str) -> torch.Tensor:
68 + """Store the `top_k_index` in the layer and call the actual forward."""
69 + forward_context: ForwardContext = get_forward_context()
70 + self = forward_context.no_compile_layers[layer_name]
71 + self._top_k_index = top_k_index
72 + # Clone hidden_states because it will be mutated in-place in FusedMoE
73 + return self.forward_impl(hidden_states.clone(), top_k_weights)
74 +
75 +
76 +def transformers_moe_forward_fake(hidden_states: torch.Tensor,
77 + top_k_index: torch.Tensor,
78 + top_k_weights: torch.Tensor,
79 + layer_name: str) -> torch.Tensor:
80 + return torch.empty_like(hidden_states)
81 +
82 +
83 +direct_register_custom_op(
84 + op_name="transformers_moe_forward",
85 + op_func=transformers_moe_forward,
86 + mutates_args=["hidden_states"],
87 + fake_impl=transformers_moe_forward_fake,
88 + dispatch_key=current_platform.dispatch_key,
89 + tags=(torch.Tag.needs_fixed_stride_order, ),
90 +)
91 +
92 +
93 +class TransformersMoEBase(TransformersBase):
94 +
95 + def __init__(self, *, vllm_config, prefix=""):
96 + self.check_version("4.57.0.dev0", "MoE models support")
97 + super().__init__(vllm_config=vllm_config, prefix=prefix)
98 +
99 + if self.parallel_config.enable_expert_parallel:
100 + raise NotImplementedError(
101 + "Transformers backend does not support expert parallel yet.")
102 + if self.parallel_config.enable_eplb:
103 + raise NotImplementedError(
104 + "Transformers backend does not support expert parallel load "
105 + "balancing yet.")
106 +
107 + def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
108 + """
109 + Params for weights, fp8 weight scales, fp8 activation scales
110 + (param_name, weight_name, expert_id, shard_id)
111 + """
112 + ckpt_names = [
113 + # (ckpt_gate_proj_name, ckpt_down_proj_name, ckpt_up_proj_name)
114 + ("gate_proj", "down_proj", "up_proj"), # Most common MoE style
115 + ("w1", "w2", "w3"), # Granite, Mixtral, Phi MoE style
116 + ("linear", "linear_1", "linear_v"), # Grok1 style
117 + ]
118 + expert_mapping = []
119 + for gate_proj, down_proj, up_proj in ckpt_names:
120 + expert_mapping.extend(
121 + FusedMoE.make_expert_params_mapping(
122 + ckpt_gate_proj_name=gate_proj,
123 + ckpt_down_proj_name=down_proj,
124 + ckpt_up_proj_name=up_proj,
125 + num_experts=self.model_config.get_num_experts(),
126 + num_redundant_experts=0, # TODO: enable EPLB
127 + ))
128 + return expert_mapping
129 +
130 + def recursive_replace(self):
131 + """Initialize the MoE layers."""
132 + text_config = self.text_config
133 +
134 + # Positional arguments
135 + num_experts = self.model_config.get_num_experts()
136 + top_k = getattr_iter(text_config, ["num_experts_per_tok", "top_k"],
137 + None)
138 + assert top_k is not None
139 + hidden_size = text_config.hidden_size
140 + intermediate_size = getattr_iter(
141 + text_config, ["moe_intermediate_size", "intermediate_size"], None)
142 + assert intermediate_size is not None
143 +
144 + # If there are shared experts, the results are
145 + # reduced after mlp.forward() not inside FusedMoE
146 + num_experts_shared = getattr_iter(text_config, [
147 + "num_experts_shared", "n_shared_experts", "moe_num_shared_experts"
148 + ], 0)
149 + reduce_results = num_experts_shared == 0
150 +
151 + def add_all_reduce(mlp: nn.Module):
152 + """Adds an all-reduce to the output of `mlp.forward()`."""
153 +
154 + class MLPWithAllReduce(mlp.__class__):
155 +
156 + def forward(self, *args, **kwargs):
157 + output = super().forward(*args, **kwargs)
158 + return self.experts.maybe_all_reduce_tensor_model_parallel(
159 + output)
160 +
161 + mlp.__class__ = MLPWithAllReduce
162 +
163 + # Unused kwargs since we use custom_routing_function:
164 + # - `scoring_func` and `e_score_correction_bias` only used for grouped
165 + # topk routing inside vLLM and are non-trivial to infer
166 + # and hard code `use_grouped_topk=False`
167 + # - `renormalize` passed anyway because it's easy to infer
168 + # - `num_expert_group` and `topk_group` used for inferring expert
169 + # placement strategy in FusedMoE
170 + # - `apply_router_weight_on_input` is already applied in Transformers
171 + renormalize = getattr(text_config, "norm_topk_prob", top_k > 1)
172 + num_expert_group = getattr(text_config, "n_group", None)
173 + topk_group = getattr(text_config, "topk_group", None)
174 +
175 + # MoE activation function
176 + activation = "silu"
177 + wrapped_arch = self.config.architectures[0].lower()
178 + if "gptoss" in wrapped_arch:
179 + activation = "swigluoai"
180 + elif "grok1" in wrapped_arch:
181 + activation = "gelu"
182 +
183 + # Expert mapping for `AutoWeightsLoader`
184 + expert_mapping = self.get_expert_mapping()
185 +
186 + # Configs
187 + parallel_config = self.parallel_config
188 + eplb_config = parallel_config.eplb_config
189 +
190 + # Expert parallel load balancing kwargs
191 + enable_eplb = parallel_config.enable_eplb
192 + num_redundant_experts = eplb_config.num_redundant_experts
193 +
194 + # Recursively fuse MoE layers
195 + def _recursive_replace(module: nn.Module, prefix: str):
196 + for child_name, child_module in module.named_children():
197 + qual_name = maybe_prefix(prefix, child_name)
198 + if (child_name == "experts"
199 + and isinstance(child_module, nn.ModuleList)):
200 + # Alias for readability
201 + mlp = module
202 + experts = child_module
203 + # Do the experts have biases
204 + has_bias = False
205 + for experts_param_name, _ in experts.named_parameters():
206 + if "bias" in experts_param_name:
207 + has_bias = True
208 + break
209 + # Double check there are no shared experts
210 + nonlocal reduce_results
211 + if reduce_results:
212 + for mlp_param_name, _ in mlp.named_parameters():
213 + if "shared_expert" in mlp_param_name:
214 + reduce_results = False
215 + break
216 + # Replace experts module with FusedMoE
217 + fused_experts = TransformersFusedMoE(
218 + num_experts=num_experts,
219 + top_k=top_k,
220 + hidden_size=hidden_size,
221 + intermediate_size=intermediate_size,
222 + reduce_results=reduce_results,
223 + renormalize=renormalize,
224 + # Hard coded because topk happens in Transformers
225 + use_grouped_topk=False,
226 + num_expert_group=num_expert_group,
227 + topk_group=topk_group,
228 + quant_config=self.quant_config,
229 + prefix=qual_name,
230 + activation=activation,
231 + enable_eplb=enable_eplb,
232 + num_redundant_experts=num_redundant_experts,
233 + has_bias=has_bias,
234 + expert_mapping=expert_mapping,
235 + )
236 + mlp.experts = fused_experts
237 + log_replacement(qual_name, experts, fused_experts)
238 + # If results are not all-reduced in FusedMoE, ensure they
239 + # are all-reduced at the end of mlp.forward() if tensor
240 + # parallel or expert parallel is enabled
241 + if not reduce_results and (fused_experts.tp_size > 1
242 + or fused_experts.ep_size > 1):
243 + add_all_reduce(mlp)
244 + else:
245 + _recursive_replace(child_module, prefix=qual_name)
246 +
247 + _recursive_replace(self.model, prefix="model")
248 + # Continue with the replacement of layers in TransformersBase
249 + super().recursive_replace()
250 +
251 +
252 +@support_torch_compile(enable_if=can_enable_torch_compile)
253 +class TransformersMoEForCausalLM(TransformersMoEBase, TransformersForCausalLM):
254 + pass
255 +
256 +
257 +@support_torch_compile(
258 + # set `positions` to last dim to support Qwen-mrope
259 + dynamic_arg_dims={
260 + "input_ids": 0,
261 + "positions": -1,
262 + "intermediate_tensors": 0,
263 + "inputs_embeds": 0,
264 + },
265 + enable_if=can_enable_torch_compile)
266 +class TransformersMoEForMultimodalLM(TransformersMoEForCausalLM,
267 + TransformersForMultimodalLM):
268 + pass
vllm/model_executor/models/transformers_pooling.py
+18 -10
@@ -20,7 +20,7 @@ from typing import Optional, Union
20 import torch
21 from transformers import AutoModelForSequenceClassification
22
23 -from vllm.attention import AttentionType
23 +from vllm.attention import Attention, AttentionType
24 from vllm.compilation.decorators import support_torch_compile
25 from vllm.config import VllmConfig
26 from vllm.model_executor.layers.pooler import (ClassifierPooler, CLSPool,
@@ -29,6 +29,7 @@ from vllm.sequence import IntermediateTensors
29
30 from .interfaces_base import VllmModelForPooling
31 from .transformers import TransformersBase, can_enable_torch_compile
32 +from .transformers_moe import TransformersMoEBase
33 from .utils import WeightsMapper
34
35
@@ -79,7 +80,9 @@ class TransformersPoolingBase(TransformersBase, VllmModelForPooling):
80 self.padding_idx = self.text_config.pad_token_id
81
82 def create_attention_instances(
82 - self, attn_type: AttentionType = AttentionType.DECODER):
83 + self,
84 + attn_type: AttentionType = AttentionType.DECODER
85 + ) -> dict[int, Attention]:
86 # TODO(hmellor): Better way to detect encoder models
87 # In encoder models, the attention layers will have `is_causal=False`
88 is_encoder = lambda m: not getattr(m, "is_causal", True)
@@ -90,14 +93,7 @@ class TransformersPoolingBase(TransformersBase, VllmModelForPooling):
93
94 # Check minimum transformers version for encoder models support
95 if attn_type == AttentionType.ENCODER_ONLY:
93 - import transformers
94 - from packaging.version import Version
95 - installed = Version(transformers.__version__)
96 - required = Version("4.57.0.dev0")
97 - if installed < required:
98 - raise ValueError(
99 - "Encoder models with the Transformers backend require "
100 - f"transformers>={required}, but got {installed}")
96 + self.check_version("4.57.0.dev0", "encoder models support")
97
98 return super().create_attention_instances(attn_type)
99
@@ -198,3 +194,15 @@ class TransformersForSequenceClassification(TransformersPoolingBase):
194 vllm_config.model_config),
195 ),
196 })
197 +
198 +
199 +@support_torch_compile(enable_if=can_enable_torch_compile)
200 +class TransformersMoEEmbeddingModel(TransformersMoEBase,
201 + TransformersEmbeddingModel):
202 + pass
203 +
204 +
205 +@support_torch_compile(enable_if=can_enable_torch_compile)
206 +class TransformersMoEForSequenceClassification(
207 + TransformersMoEBase, TransformersForSequenceClassification):
208 + pass