执行摘要
- 一句话:修复 Transformers 后端 FP8 MoE 的内存泄漏并清理样板代码
- 推荐动作:此 PR 值得仔细阅读,展示了如何通过集中化 EPLB 状态管理和清理样板代码来减少跨模型冗余。关注 MoE 架构的开发者应重点审查 transformers/moe.py 和 interfaces.py 的接口变化,确认与自定义模型的兼容性。
功能与动机
PR body 指出:Transformers backend 在量化前急切存储 expert_weights,导致 FP8 模型保留未量化权重的陈旧引用,内存使用异常偏高。作者希望通过移除 self.expert_weights 的初始化与 set_eplb_state 方法来解决此问题,并清理冗余样板代码。
实现拆解
- 核心 transformers/moe.py 重构:将 TransformersFusedMoE 重命名为 TransformersMoERunner,使其与基类命名一致;将 forward 和 fake forward 函数私有化(添加下划线前缀),移除不必要的 dispatch_key 参数,简化 custom op 注册。
- EPLB 逻辑集中:从 MoEMixin 中删除 set_eplb_state 方法,该逻辑已移至基类 MixtureOfExperts;调整 update_physical_experts_metadata 中的层迭代方式(从 mlp_moe_layers 改为 mlp_layers),确保与基类兼容。
- 清除所有模型的 expert_weights 初始化:在 deepseek_v2.py、glm4_moe.py、ernie45_moe.py、AXK1.py 等 20+ 个文件中删除 self.expert_weights = [] 行,避免每个模型实例分配空列表。
- 接口层清理(interfaces.py):将部分运行时依赖(如 ModelConfig、MambaStateCopyFunc)移至 TYPE_CHECKING 块内,统一使用前向引用字符串,减少运行时导入开销。
- 模型适配: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 层;类别 source;类型 core-logic;符号 TransformersFusedMoE, TransformersMoERunner, transformers_moe_forward, _transformers_moe_forward): 核心修改文件:修复 FP8 MoE 内存问题的主要逻辑变更,包括类重命名、函数私有化、删除 set_eplb_state 并调整 update_physical_experts_metadata。
vllm/model_executor/models/interfaces.py(模块 模型接口;类别 source;类型 type-hint;符号 get_language_model, post_process_tokens, get_mamba_state_copy_func): 接口层调整:将所有运行时依赖移至 TYPE_CHECKING 块内,减少循环导入风险;更新类型注解为前向引用,提升模块加载速度。
vllm/model_executor/models/afmoe.py(模块 AFMoE 模型;类别 source;类型 data-contract;符号 AfmoeForCausalLM, set_eplb_state, update_physical_experts_metadata): AFMoE 模型适配:添加 MixtureOfExperts 混入,删除 expert_weights 初始化,用 update_physical_experts_metadata 替换 set_eplb_state,增强与基类一致性。
vllm/model_executor/models/step3p5.py(模块 Step3.5 模型;类别 source;类型 data-contract;符号 set_eplb_state): Step3p5 模型适配:更改 moe_layers 类型为 list[MoERunner] 并直接存储 experts,删除 set_eplb_state 方法,移除 expert_weights 初始化。
vllm/model_executor/models/hy_v3.py(模块 HYV3 模型;类别 source;类型 cleanup;符号 HYV3Model): HYV3 模型:添加 MixtureOfExperts 混入,移除 expert_weights = [],使用基类状态管理。
关键符号:TransformersMoERunner.forward, _transformers_moe_forward, MoEMixin.update_physical_experts_metadata, AfmoeForCausalLM.update_physical_experts_metadata
关键源码片段
vllm/model_executor/models/transformers/moe.py
核心修改文件:修复 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_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
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()
评论区精华
风险与影响
- 风险:
- 跨模型兼容风险:删除 set_eplb_state 方法并依赖基类实现,可能影响未及时适配的自定义 MoE 模型。Gemma4 的加载失败即为例证,已通过后续 PR 修复。
- EPLB 逻辑路径变更:afmoe.py 和 step3p5.py 中使用 update_physical_experts_metadata 替代原 set_eplb_state,且迭代方式从 moe_layers 列表改为直接遍历模型层,若存在非标准层结构可能出错。
- 序列并行影响:transformers/moe.py 中将 mlp_moe_layers 改为 mlp_layers,可能改变序列并行模式下 MoE 层的注册行为,需关注相关测试。
- 缺少测试覆盖:本次修改涉及 29 个文件但无对应测试变更,回归风险较高。后续 #46956 的快速修复表明需要更全面的 MoE 加载测试。
- 影响:
- 用户影响:FP8 MoE 模型的内存使用显著降低(不再保留未量化权重),加载稳定性改善(所有 MoE 模型统一初始化逻辑)。
- 系统影响:减少不必要的空列表分配,降低内存碎片;统一的状态接口使新 MoE 模型集成更简单。
- 团队影响:代码架构更清晰,基类 MixtureOfExperts 承担更多职责,后续维护成本降低。
- 风险标记:跨模型兼容风险, EPLB 逻辑路径变更, 缺少测试覆盖
关联脉络
- PR #46956 Fix Gemma4 Unified models loading after #46820: 直接修复此 PR 引入的 Gemma4 加载回归,是 #46820 的补丁。
参与讨论