Prhub

#35178 [MoE] Make MoERunnerInterface a PluggableLayer for OOT support

原始 PR 作者 wxsIcey 合并时间 2026-04-30 18:31 文件变更 4 提交数 17 评论 31 代码增减 +35 / -23

执行摘要

MoERunnerInterface 继承 PluggableLayer 实现 OOT 替换

PR 描述指出:PluggableLayer 是比 CustomOp 更合适的基类,因为 MoERunnerInterface 是一个有状态的、组装模块的抽象(包含 router、gate、shared_experts、quant_method 等子模块)。OOT 替换机制允许第三方硬件平台自定义 MoE 执行流程。

值得精读,了解如何利用 PluggableLayer 设计模式支持 OOT 扩展。关注 _quant_method 命名规范和前缀变更。

讨论亮点
  • 设计选择:PluggableLayer vs CustomOp:reviewer @wangxiyuan 最初建议使用 CustomOp,但作者 wxsIcey 解释说 MoERunnerInterface 是有状态的模块组合,PluggableLayer 更合适。最终采用 PluggableLayer
  • DefaultMoERunner 是否重复继承:@bnellnm 质疑为什么 DefaultMoERunner 也要继承 PluggableLayer。作者最初添加了 @PluggableLayer.register,但后来同意只让 MoERunnerInterface 作为可插拔基类,移除了 DefaultMoERunner 上的装饰器。
  • quant_method 属性隐藏:@bnellnm 指出,若 Runner 暴露了 quant_methodprocess_weights_after_loading 会在所有量化方法中被错误触发。作者通过将属性改为 _quant_method 解决,确保仅 FusedMoE 层持有公开的 quant_method
  • 文档 mock 元类冲突:@wxsIcey 解释文档构建时 PluggableLayerMagicMock 替代,导致与 ABCMeta 冲突。通过添加 MockPluggableLayer 并注册到 mock 字典中解决。

实现拆解

  1. 修改 MoERunnerInterface 基类:在 moe_runner_interface.py 中导入 PluggableLayer 并将类定义改为 class MoERunnerInterface(PluggableLayer, ABC),使所有 MoE runner 自动继承插件注册能力。

  2. 保护 MoERunnerquant_method 属性:在 moe_runner.py 中将 self.quant_method 重命名为 self._quant_method,避免与 FusedMoE 层的 quant_method 属性冲突。遍历模块时 process_weights_after_loading 会跳过没有 quant_method 属性的层,从而避免错误调用。同步更新 _replace_quant_method_fused_output_is_reduced_maybe_pad_hidden_states_apply_quant_methoddo_naive_dispatch_combine 中的所有引用。

  3. 调整 FusedMoE.get_expert_weights 非专家权重前缀:在 layer.py 中将硬编码的前缀("_shared_experts.""_gate." 等)整合为元组 NON_EXPERT_PREFIXES,并加上 "runner." 前缀,因为 MoE 子模块现在位于 runner 属性下。确保 EPLB 权重展平时正确排除这些非专家参数。

  4. 补充文档构建 mock:在 docs/mkdocs/hooks/generate_argparse.py 中添加 MockPluggableLayer 类,并在 mock 字典中同时提供 PluggableLayer,避免无 Torch 环境下 Python 元类冲突导致文档构建失败。

文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/runner/moe_runner_interface.py MoE 接口 modified 6.57
vllm/model_executor/layers/fused_moe/runner/moe_runner.py MoE 执行器 modified 6.6
vllm/model_executor/layers/fused_moe/layer.py MoE 层 modified 6.39
docs/mkdocs/hooks/generate_argparse.py 文档构建 modified 6.13

关键符号

MoERunnerInterface _replace_quant_method get_expert_weights MockPluggableLayer

关键源码片段

vllm/model_executor/layers/fused_moe/runner/moe_runner_interface.py data-contract

核心变更:修改基类为 PluggableLayer,使 MoE runner 可插件化替换

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from abc import ABC, abstractmethodimport torch# ── 引入 PluggableLayer,使 MoERunnerInterface 获得插件注册能力 ──
from vllm.model_executor.custom_op import PluggableLayer
from vllm.model_executor.layers.fused_moe.fused_moe_method_base import (
    FusedMoEMethodBase,
)
from vllm.model_executor.layers.fused_moe.runner.shared_experts import (
    SharedExperts,
)class MoERunnerInterface(PluggableLayer, ABC):
    """
    MoE 执行器的抽象基类,同时继承 PluggableLayer 和 ABC。
    所有自定义 MoE runner 应继承此类并实现抽象方法。
    通过 PluggableLayer 的注册机制,可以实现 OOT 替换。
    """
​
    @abstractmethod
    def forward(
        self,
        hidden_states: torch.Tensor,
        router_logits: torch.Tensor,
        input_ids: torch.Tensor | None = None,
    ) -> torch.Tensor:
        raise NotImplementedError
​
    @abstractmethod
    def is_internal_router(self) -> bool:
        raise NotImplementedError
​
    @property
    @abstractmethod
    def shared_experts(self) -> SharedExperts | None:
        raise NotImplementedError
​
    # TODO(bnell): temporary hack, do not call this method.
    @abstractmethod
    def _replace_quant_method(self, quant_method: FusedMoEMethodBase):
        raise NotImplementedError
vllm/model_executor/layers/fused_moe/runner/moe_runner.py data-contract

修改 quant_method 为私有属性,避免与 FusedMoE 层冲突

class MoERunner(PluggableLayer, ABC):
    """MoE runner base class ..."""
    def __init__(
        self,
        layer_name: str,
        moe_config: FusedMoEConfig,
        router: FusedMoERouter,
        routed_input_transform: torch.nn.Module | None,
        gate: torch.nn.Module | None,
        shared_experts: torch.nn.Module | None,
        quant_method: FusedMoEMethodBase,
        enable_dbo: bool,
        routed_output_transform: torch.nn.Module | None = None,
        routed_scaling_factor: float = 1.0,
    ):
        super().__init__()
        self.moe_config = moe_config
        self.router = router
        self.routed_input_transform = routed_input_transform
        self.routed_output_transform = routed_output_transform
        self.routed_scaling_factor = routed_scaling_factor
        self.gate = gate
        # 使用 _quant_method 避免与 FusedMoE 层的 quant_method 属性冲突,
        # 防止遍历模块时被错误识别为量化层。
        self._quant_method = quant_method
        ... # 其余初始化
​
    @property
    def do_naive_dispatch_combine(self) -> bool:
        return (
            self.moe_config.dp_size > 1
            and not self._quant_method.supports_internal_mk
        )

评论区精华

DefaultMoERunner 是否需要继承 PluggableLayer 设计

bnellnm 提出疑问:如果 MoERunner 已是 PluggableLayer,DefaultMoERunner 为何还需要额外继承并注册?wxsIcey 最初添加了装饰器,但后来同意移除,仅保留基类上的 PluggableLayer。

结论:最终只让 MoERunnerInterface 继承 PluggableLayer,DefaultMoERunner 不再注册。 · 已解决

避免 process_weights_after_loading 在非 FusedMoE 层上执行 正确性

wxsIcey 指出模型加载时 named_modules 会遍历到 Runner,导致量化方法的 process_weights_after_loading 被错误调用。bnellnm 建议使 Runner 不暴露 quant_method 属性。

结论:将 Runner 中的 quant_method 属性重命名为 _quant_method,私有化后不再被识别为量化层。 · 已解决

文档构建中 PluggableLayer mock 元类冲突 bugfix

wxsIcey 解释文档构建 mock 时 PluggableLayer 缺失导致 metaclass conflict。通过添加 MockPluggableLayer 修复。

结论:添加 MockPluggableLayer 类并在 mock 字典中包含 PluggableLayer。 · 已解决

风险与影响

  • 内部属性更名风险quant_method 改为 _quant_method 可能影响外部自定义 runner 子类直接访问该属性(若之前通过 self.quant_method 引用)。需要迁移指南。
  • EPLB 权重前缀变更get_expert_weights 中前缀从 "_shared_experts." 变为 "runner._shared_experts.",若存在未适配的第三方量化方法或自定义 runner,可能导致权重展平错误或断言失败。
  • 依赖 PluggableLayer 注册机制:OOT 替换依赖 vLLM 的插件注册表,若注册逻辑有变动,可能影响 runner 查找。
  • 对用户:允许第三方硬件平台通过注册自定义 MoERunner 类来替换 MoE 执行逻辑,无需修改 vLLM 核心代码。
  • 对系统:内部功能无损,仅重构接口和属性命名。
  • 对团队:需确保所有自定义 MoE 实现遵循新的基类和属性约定。
内部属性更名 EPLB 前缀变更 依赖插件机制

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论