Prhub

#46820 Fix Transformers backend FP8 MoE and remove some boilerplate

原始 PR 作者 hmellor 合并时间 2026-06-27 07:16 文件变更 29 提交数 11 评论 4 代码增减 +80 / -156

执行摘要

修复 Transformers 后端 FP8 MoE 的内存泄漏并清理样板代码

PR body 指出:Transformers backend 在量化前急切存储 expert_weights,导致 FP8 模型保留未量化权重的陈旧引用,内存使用异常偏高。作者希望通过移除 self.expert_weights 的初始化与 set_eplb_state 方法来解决此问题,并清理冗余样板代码。

此 PR 值得仔细阅读,展示了如何通过集中化 EPLB 状态管理和清理样板代码来减少跨模型冗余。关注 MoE 架构的开发者应重点审查 transformers/moe.py 和 interfaces.py 的接口变化,确认与自定义模型的兼容性。

讨论亮点
  • EPLB 审查请求:作者 @hmellor 在 PR body 中请求 @abmfy 从 EPLB(Expert Parallelism Load Balancing)角度审查,但未收到进一步回复,PR 最终由 Isotr0py 批准。
  • Gemma4 兼容性回归:用户 @ehfd 在 issue #46948 报告此 PR 导致 Gemma4 Unified 模型无法加载(ImportError / 属性缺失)。作者立即确认并在 #46956 中 forward fix,问题已解决。

实现拆解

  1. 核心 transformers/moe.py 重构:将 TransformersFusedMoE 重命名为 TransformersMoERunner,使其与基类命名一致;将 forward 和 fake forward 函数私有化(添加下划线前缀),移除不必要的 dispatch_key 参数,简化 custom op 注册。
  2. EPLB 逻辑集中:从 MoEMixin 中删除 set_eplb_state 方法,该逻辑已移至基类 MixtureOfExperts;调整 update_physical_experts_metadata 中的层迭代方式(从 mlp_moe_layers 改为 mlp_layers),确保与基类兼容。
  3. 清除所有模型的 expert_weights 初始化:在 deepseek_v2.py、glm4_moe.py、ernie45_moe.py、AXK1.py 等 20+ 个文件中删除 self.expert_weights = [] 行,避免每个模型实例分配空列表。
  4. 接口层清理(interfaces.py):将部分运行时依赖(如 ModelConfig、MambaStateCopyFunc)移至 TYPE_CHECKING 块内,统一使用前向引用字符串,减少运行时导入开销。
  5. 模型适配:afmoe.py 和 hy_v3.py 添加 MixtureOfExperts 混入,step3p5.py 调整 moe_layers 类型为 list[MoERunner] 并直接存储 layer.moe.experts;后续通过 PR #46956 修复了 Gemma4 模型的兼容性问题。
文件 模块 状态 重要度
vllm/model_executor/models/transformers/moe.py MoE 层 modified 8.48
vllm/model_executor/models/interfaces.py 模型接口 modified 8.26
vllm/model_executor/models/afmoe.py AFMoE 模型 modified 7.97
vllm/model_executor/models/step3p5.py Step3.5 模型 modified 6.95
vllm/model_executor/models/hy_v3.py HYV3 模型 modified 6.31

关键符号

TransformersMoERunner.forward _transformers_moe_forward MoEMixin.update_physical_experts_metadata AfmoeForCausalLM.update_physical_experts_metadata

关键源码片段

vllm/model_executor/models/transformers/moe.py core-logic

核心修改文件:修复 FP8 MoE 内存问题的主要逻辑变更,包括类重命名、函数私有化、删除 set_eplb_state 并调整 update_physical_experts_metadata。

@PluggableLayer.register("transformers_fused_moe")
class TransformersMoERunner(MoERunner):
    """Custom MoE runner for the Transformers modeling backend.    重命名自 TransformersFusedMoE,与 MoERunner 基类命名对齐。
    """
​
    def __init__(self, *args, moe_state: TransformersMoEState, **kwargs):
        super().__init__(*args, **kwargs)
        self._moe_state = moe_state
        # 如果配置使用序列并行,则设置状态
        self._moe_state.is_sequence_parallel = self.moe_config.is_sequence_parallel
​
    def forward(
        self,
        hidden_states: torch.Tensor,
        topk_ids: torch.Tensor,
        topk_weights: torch.Tensor,
        **kwargs: Any,
    ) -> torch.Tensor:
        """通过自定义 op 转发,避免 topk_ids 干扰 CUDA Graph 捕获。"""
        return torch.ops.vllm.transformers_moe_forward(
            hidden_states,
            topk_ids.to(torch.int32),
            topk_weights.to(torch.float32),
            self.layer_name,
        )
​
    def _forward_super(
        self,
        hidden_states: torch.Tensor,
        topk_weights: torch.Tensor,
    ) -> torch.Tensor:
        return super().forward(hidden_states, topk_weights)
​
    def load_weights(
        self, weights: Iterable[tuple[str, torch.Tensor]]
    ) -> Iterable[str]:
        return self.routed_experts.load_weights(weights)
# Custom op 注册简化,移除 dispatch_key
direct_register_custom_op(
    op_name="transformers_moe_forward",
    op_func=_transformers_moe_forward, # 私有化命名
    mutates_args=["hidden_states"],
    fake_impl=_transformers_moe_forward_fake,
    tags=(torch.Tag.needs_fixed_stride_order,),
)
class MoEMixin(MixtureOfExperts):
    def __init__(self, *, vllm_config: "VllmConfig", prefix: str = ""):
        self.check_version("5.0.0", "MoE models support")
        # 跳过 MixtureOfExperts.__init__,直接调用 MRO 中下一个类的 __init__
        super(MixtureOfExperts, self).__init__(vllm_config=vllm_config, prefix=prefix)
​
    def update_physical_experts_metadata(
        self,
        num_physical_experts: int,
        num_local_physical_experts: int,
    ):
        assert self.num_local_physical_experts == num_local_physical_experts
        self.num_physical_experts = num_physical_experts
        self.num_local_physical_experts = num_local_physical_experts
        self.num_redundant_experts = num_physical_experts - self.num_logical_experts
        # 注意:原代码使用 self.mlp_moe_layers,现改为 self.mlp_layers
        for mlp in self.mlp_layers:
            mlp.n_local_physical_experts = num_local_physical_experts
            mlp.n_physical_experts = num_physical_experts
            mlp.n_redundant_experts = self.num_redundant_experts
            mlp.experts.update_expert_map()
vllm/model_executor/models/interfaces.py type-hint

接口层调整:将所有运行时依赖移至 TYPE_CHECKING 块内,减少循环导入风险;更新类型注解为前向引用,提升模块加载速度。

# head 版本将大多数导入移至 TYPE_CHECKING 块内
if TYPE_CHECKING:
    from vllm.config import (
        ModelConfig,
        SpeechToTextConfig,
        SpeechToTextParams,
        VllmConfig,
    )
    from vllm.inputs import PromptType, TokensPrompt
    from vllm.model_executor.layers.fused_moe import MoERunner
    from vllm.model_executor.layers.mamba.mamba_utils import MambaStateCopyFunc
    from vllm.model_executor.models.interfaces_base import VllmModel
    from vllm.tasks import ScoreType
    # 其余类似 ...# 类型注解统一使用前向引用字符串(如 "VllmModel" 而非 VllmModel)
def get_language_model(self) -> "VllmModel":
    ...
vllm/model_executor/models/afmoe.py data-contract

AFMoE 模型适配:添加 MixtureOfExperts 混入,删除 expert_weights 初始化,用 update_physical_experts_metadata 替换 set_eplb_state,增强与基类一致性。

class AfmoeForCausalLM(
    nn.Module, SupportsPP, SupportsEagle3, SupportsLoRA, MixtureOfExperts
):
    # ... 其他类属性
​
    def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
        super().__init__()
        config = vllm_config.model_config.hf_config
        quant_config = vllm_config.quant_config
        self.config = config
        self.quant_config = quant_config
        self.model = AfmoeModel(
            vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
        )
        if get_pp_group().is_last_rank:
            self.lm_head = ParallelLMHead(...) # 省略参数
            self.logits_processor = LogitsProcessor(config.vocab_size)
        else:
            self.lm_head = PPMissingLayer()
        self.make_empty_intermediate_tensors = (
            self.model.make_empty_intermediate_tensors
        )
        # 删除 : self.expert_weights = []
        # 设置 MoE 超参数
        self.num_moe_layers = config.num_hidden_layers - config.num_dense_layers
        self.num_expert_groups = config.n_group
        self.moe_layers: list[MoERunner] = []
        # ... 循环收集 moe_layers
​
    def update_physical_experts_metadata(
        self,
        num_physical_experts: int,
        num_local_physical_experts: int,
    ) -> None:
        # 替代原 set_eplb_state,直接更新所有 MoE 层
        assert self.num_local_physical_experts == num_local_physical_experts
        self.num_physical_experts = num_physical_experts
        self.num_local_physical_experts = num_local_physical_experts
        self.num_redundant_experts = num_physical_experts - self.num_logical_experts
        for layer in self.model.layers:
            if isinstance(layer, PPMissingLayer):
                continue
            if layer.moe_enabled:
                moe = layer.mlp
                moe.n_local_physical_experts = num_local_physical_experts
                moe.n_physical_experts = num_physical_experts
                moe.n_redundant_experts = self.num_redundant_experts
                moe.experts.update_expert_map()

评论区精华

EPLB 审查请求 设计

作者 @hmellor 请求 @abmfy 从 EPLB(Expert Parallelism Load Balancing)角度审查变更,但未收到进一步评论。

结论:未收到额外意见,PR 最终由 Isotr0py 批准。 · closed

Gemma4 兼容性问题 正确性

用户 @ehfd 在 issue #46948 报告此 PR 导致 Gemma4 Unified 模型无法加载(ImportError / 属性缺失)。

结论:作者确认问题并立即在 #46956 中 forward fix,问题已修复。 · 已解决

风险与影响

  1. 跨模型兼容风险:删除 set_eplb_state 方法并依赖基类实现,可能影响未及时适配的自定义 MoE 模型。Gemma4 的加载失败即为例证,已通过后续 PR 修复。
  2. EPLB 逻辑路径变更:afmoe.py 和 step3p5.py 中使用 update_physical_experts_metadata 替代原 set_eplb_state,且迭代方式从 moe_layers 列表改为直接遍历模型层,若存在非标准层结构可能出错。
  3. 序列并行影响:transformers/moe.py 中将 mlp_moe_layers 改为 mlp_layers,可能改变序列并行模式下 MoE 层的注册行为,需关注相关测试。
  4. 缺少测试覆盖:本次修改涉及 29 个文件但无对应测试变更,回归风险较高。后续 #46956 的快速修复表明需要更全面的 MoE 加载测试。
  • 用户影响:FP8 MoE 模型的内存使用显著降低(不再保留未量化权重),加载稳定性改善(所有 MoE 模型统一初始化逻辑)。
  • 系统影响:减少不必要的空列表分配,降低内存碎片;统一的状态接口使新 MoE 模型集成更简单。
  • 团队影响:代码架构更清晰,基类 MixtureOfExperts 承担更多职责,后续维护成本降低。
跨模型兼容风险 EPLB 逻辑路径变更 缺少测试覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论