Prhub

#47058 Remove more unnecessary `load_weights` methods

原始 PR 作者 hmellor 合并时间 2026-06-30 22:22 文件变更 63 提交数 16 评论 3 代码增减 +1028 / -3778

执行摘要

重构 MoE 权重加载,删除 63 个文件中的重复 load_weights 方法

此前每个 MoE 模型都需编写独立的 get_expert_mapping 和 load_weights 方法,导致大量重复代码,且容易因权重命名不一致而引入错误。核心动机是将专家权重映射逻辑提升至 FusedMoE/RoutedExperts 层,通过 ckpt_names 参数统一描述检查点权重名称,使模型子类只需声明映射规则,无需重复权重加载流程。

值得精读。本 PR 展示了如何通过基础设施层抽象消除跨模型重复,设计模式(ckpt_names + WeightsMapper + AutoWeightsLoader)可作为 vLLM 内部模型权重加载的标准范式推广。阅读时应重点理解 RoutedExperts.get_expert_mapping 的动态构造逻辑以及 WeightsMapper 的 prefix/substr/stacked 映射策略。

讨论亮点

关键错误报告:lzkgogogo 在 routed_experts.py 第 383 行发现运行时错误(expert_data.copy_ 形状不匹配)。hmellor 确认问题并已在 #50137 中修复,该修复属于同一个重构系列。其他 review 均为 approve 无争议。

实现拆解

  1. 改造 FusedMoE 初始化:FusedMoE 现在接受 ckpt_names 而不是 expert_mapping,将命名规则存储到 RoutedExperts 中(对应文件 vllm/model_executor/layers/fused_moe/__init__.pyrouted_experts.py)。
  2. 新增 RoutedExperts.get_expert_mapping:该方法利用 ckpt_names 动态构建专家映射,不再需要模型子类定义 get_expert_mapping。同时支持融合权重自动转置(如 Qwen3 VL MoE)和提前跳出优化(routed_experts.py)。
  3. 引入 WeightsMapper 和 AutoWeightsLoader:模型子类只需在类上定义 hf_to_vllm_mapper(WeightsMapper 实例),声明权重名称从 HF 到 vLLM 的映射关系,然后通过 AutoWeightsLoader.load_weights 自动加载。例如 Qwen2MoeForCausalLM 定义 orig_to_new_stacked 后整个 load_weights 被删除(qwen2_moe.py)。
  4. 逐步迁移各模型:使用 16 个 commit 逐个转换 Qwen、MiniMax、DeepSeek、MiMo、Exaone、SigLIP 等 63 个文件。每个模型子类只需删除 get_expert_mappingload_weights 方法,替换为 hf_to_vllm_mapperAutoWeightsLoader 调用。
  5. LoRA 适配修正FusedMoEWithLoRA 现在显式传递 lora_base_layer_prefix,使 RoutedExperts 无需扫描整个模型即可判断 LoRA 前缀,改进了检测可靠性。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/routed_experts.py MoE 核心 modified 8.33
vllm/model_executor/models/qwen2_moe.py Qwen MoE modified 8.67
vllm/model_executor/models/minimax_m2.py MiniMax M2 modified 8.67
vllm/model_executor/models/mimo_mtp.py MiMo MTP modified 8.64
vllm/model_executor/models/qwen3_moe.py Qwen3 MoE modified 8.49
vllm/model_executor/models/qwen3_5.py Qwen3.5 modified 8.6
vllm/model_executor/models/qwen3_vl_moe.py Qwen3 VL modified 8.51
vllm/model_executor/models/exaone_moe.py Exaone MoE modified 8.43

关键符号

RoutedExperts.get_expert_mapping RoutedExperts.build_expert_params_mapping RoutedExperts.load_weights FusedMoE.__init__ FusedMoEWithLoRA.__init__ AutoWeightsLoader.load_weights WeightsMapper.__init__ Qwen2MoeForCausalLM.hf_to_vllm_mapper MiniMaxM2Model.load_weights MiniMaxM2MoE.__init__ MiMoMTP.hf_to_vllm_mapper Qwen3MoeModel.hf_to_vllm_mapper Qwen3_5Model.hf_to_vllm_mapper

关键源码片段

vllm/model_executor/layers/fused_moe/routed_experts.py core-logic

核心变更文件,新增 `get_expert_mapping` 方法并重构 `load_weights`,支持 ckpt_names 动态映射和融合权重转置

def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
    # 使用初始化时传入的 ckpt_names 动态构造专家参数映射
    # 返回 (param_name, weight_name, expert_id, shard_id) 元组列表
    return build_expert_params_mapping(
        self,
        ckpt_gate_proj_name=self.ckpt_names[0], # 例如 "w1"
        ckpt_down_proj_name=self.ckpt_names[1], # 例如 "w2"
        ckpt_up_proj_name=self.ckpt_names[2], # 例如 "w3"
        num_experts=self.num_experts,
    )def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
    # 重构后统一入口:先处理 stacked 参数,再调用 get_expert_mapping 处理专家参数
    # 若已使用融合权重(如 w13)则提前 break,避免重复加载
    loaded_params: set[str] = set()
    expert_mapping = self.get_expert_mapping()
    for name, loaded_weight in weights:
        if "rotary_emb.inv_freq" in name:
            continue
        # ... 其余加载逻辑,同旧版但更简洁
    return loaded_params
vllm/model_executor/models/minimax_m2.py data-contract

示例展示 FusedMoE 使用 ckpt_names 参数以及 load_weights 被替换为更简洁的 AutoWeightsLoader

class MiniMaxM2MoE(nn.Module):
    def __init__(self, ...):
        super().__init__()
        # ...
        self.experts = FusedMoE(
            # ...
            prefix=f"{prefix}.experts",
            router_logits_dtype=torch.float32,
            ckpt_names=("w1", "w2", "w3"), # 新参数替代旧 expert_mapping
        )class MiniMaxM2Model(nn.Module, EagleModelMixin):
    hf_to_vllm_mapper = WeightsMapper(
        orig_to_new_stacked={
            ".q_proj": (".qkv_proj", "q"),
            ".k_proj": (".qkv_proj", "k"),
            ".v_proj": (".qkv_proj", "v"),
        }
    )
    def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
        # 跳过 MTP 层,其余负载由 AutoWeightsLoader 完成
        skip_prefixes = None
        num_mtp = getattr(self.config, "num_mtp_modules", 0)
        if num_mtp:
            base = self.config.num_hidden_layers
            skip_prefixes = [f"layers.{base + i}." for i in range(num_mtp)]
        loader = AutoWeightsLoader(self, skip_prefixes=skip_prefixes)
        return loader.load_weights(weights)

评论区精华

routed_experts.py 运行时错误 正确性

lzkgogogo 报告在 _load_per_channel_weight_scale 中调用 _load_w13 时出现 expert_data.copy_ 错误,栈轨迹显示形状不匹配。

结论:hmellor 确认问题,并在 #50137 中修复,属于同一重构系列。 · 已解决

风险与影响

回归风险高:删除了 3778 行代码,涉及 63 个文件,大量自定义 load_weights 被替换为通用机制。虽然核心逻辑覆盖全面,但特定模型(如 Qwen3 VL MoE)的融合权重转置、LoRA 前缀检测等边界条件仍可能出现形状或键不匹配。后续 #50137 已修复一个运行时错误,建议关注生产环境的模型加载日志。性能退化风险:通用加载器可能略慢于手写版本,但加载流程并非热点路径,影响可忽略。

对用户:模型权重加载行为保持不变,兼容性无预期破坏,但若存在自定义模型子类未使用本重构,可能需要适配新的 hf_to_vllm_mapper 模式。对系统:大幅减少模板代码,统一专家映射入口,降低了未来添加新 MoE 模型的成本。对团队:需要 review 者理解 WeightsMapperAutoWeightsLoader 的约定,但整体降低了维护负债。影响范围:所有使用 FusedMoE 的模型。

核心路径变更 大规模代码删除 已发现运行时错误

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论