Prhub

#42242 [LoRA] Support 2D and 3D MoE LoRA adapter at the same time

原始 PR 作者 jeejeelee 合并时间 2026-05-18 15:22 文件变更 16 提交数 10 评论 5 代码增减 +391 / -9

执行摘要

支持 2D 与 3D MoE LoRA 适配器混布

来自 Issue #40005 的用户反馈指出,基于 Megatron-Swift 微调的 Qwen3.5-MoE LoRA 权重(采用 3D 格式:gate_up_proj / down_proj 两个融合张量)无法被 vLLM 加载,而 Qwen3-MoE 与 Qwen3-Dense 均可正常工作。该 PR 旨在填补这一兼容性缺口,在不破坏既有 2D LoRA 支持的前提下,允许两种格式在相同 engine 中共存。

该 PR 值得所有 LoRA MoE 相关开发者精读,尤其关注:

1) 设计方案_enable_mixed_moe_lora_format_model_is_3d_moe 的双重状态设计,以及 _convert_3d_to_2d_moe_lora 的具体张量重排实现;
2) 测试策略:混合 batch 中通过独立输出比对验证适配器路由正确性;
3) 未解决的 review 意见:assert 与 architectures 访问的改进建议尚未实施,后续需跟进以避免潜在 crash。

讨论亮点

在 Review 中,gemini-code-assist[bot] 提出两点改进建议:

1) 将 _convert_3d_to_2d_moe_lora 中的 assert intermediate_x2 % 2 == 0 改为 raise ValueError(...),以避免在生产环境中因断言失败导致引擎崩溃;
2) 直接访问 self.model.config.architectures[0] 可能因模型配置异常引发 IndexError,建议通过 getattr(config, 'architectures', [None])[0] 安全访问。
维护者 ywang96 询问了 _is_3d_moe_model 属性的用途,作者 jeejeelee 解释该属性用于 GPT-Oss 等始终为 3D MoE 的模型初始化对应的 LoRA 层。这些讨论已记录并合入,但前两处改进尚未在最终版本中应用(需关注后续修复)。

实现拆解

  1. 配置扩展:在 LoRAConfigEngineArgs 中新增 enable_mixed_moe_lora_format 布尔字段,用户可通过 CLI --enable-mixed-moe-lora-format 启用混合模式。
  2. 数据契约扩展:在 LoRARequestLoRAModel 中新增 is_3d_lora_weight 标志,将从 REST API 透传至模型管理器,以识别待加载适配器的格式。
  3. 初始化分支ModelManager.__init__ 根据模型是否为 MoE 以及配置决定 _enable_mixed_moe_lora_format_is_3d_moe_model。当混合模式启用时,即使模型原生支持 3D,也强制使用 FusedMoEWithLoRA 2D 包装器,并通过 force_2d_moe=True 调用 process_packed_modules_mapping 获取 2D 模块映射。
  4. 权重转换:新增 _convert_3d_to_2d_moe_lora 方法,解析 3D 格式的 gate_up_projdown_proj 的张量切片,重排为 2D 格式所需的 w1/w2/w3 分离张量。该方法在 _create_merged_loras_inplace 中对于 FusedMoEWithLoRAlora_model.is_3d_lora_weight=True 时被调用。
  5. 入口透传:在 OpenAIServing 的静态 LoRA 初始化与动态加载方法中,传递 is_3d_lora_weight 到后端。
  6. 测试覆盖:新增 test_qwen36_moe_lora.py,使用 Qwen3.6-35B-A3B 模型做 2D/3D 混合 batch 测试,验证单独推理与混合推理输出一致,并交换顺序验证路由正确。在 conftest.py 中注册 fixture 提供权重路径。现有 test_qwen3moe_tp.py 也更新以使用新配置。
文件 模块 状态 重要度
vllm/lora/model_manager.py LoRA 管理器 modified 8.12
tests/lora/test_qwen36_moe_lora.py 集成测试 added 7.6
vllm/lora/utils.py 工具函数 modified 6.34
vllm/lora/lora_model.py LoRA 模型 modified 5.44
vllm/lora/request.py 请求层 modified 5.23
vllm/config/lora.py 配置层 modified 5.23
vllm/engine/arg_utils.py 引擎参数 modified 5.4
vllm/entrypoints/openai/models/serving.py 服务层 modified 5.34
tests/lora/conftest.py 测试配置 modified 4.73
tests/lora/test_qwen3moe_tp.py TP 测试 modified 5.26

关键符号

_convert_3d_to_2d_moe_lora process_packed_modules_mapping

关键源码片段

vllm/lora/model_manager.py core-logic

核心变更:添加 _convert_3d_to_2d_moe_lora 转换方法,修改初始化逻辑以支持混合模式,是重量级改动。

def __init__(self, ...):
    # ... 省略前置代码 ...
    is_moe = is_moe_model(self.model)
    # 记录模型原生的 3D 权重标志
    self._model_is_3d_moe = is_moe and self.model.is_3d_moe_weight
    # 决定是否启用混合模式:强制使用 2D 包装器
    self._enable_mixed_moe_lora_format = (
        is_moe and lora_config.enable_mixed_moe_lora_format
    )
    # 最终决定是否将 model 视为 3D ( 当混合模式关闭时才视为 3D)
    self._is_3d_moe_model = (
        self._model_is_3d_moe and not self._enable_mixed_moe_lora_format
    )
    # 在混合模式下强制生成 2D 映射
    self.packed_modules_mapping = process_packed_modules_mapping(
        self.model, force_2d_moe=self._enable_mixed_moe_lora_format
    )
    # ... 后续代码 ...def _convert_3d_to_2d_moe_lora(self, lora_model, module, module_name):
    """将 3D 格式的 MoE LoRA 适配器转换为 2D 布局。
    3D 格式存储两个融合张量对 (gate_up_proj / down_proj),
    需要拆解并重排为 2D 所需的 w1/w2/w3 分离张量。
    """
    # 从 lora_model.loras 中提取 gate_up_proj 与 down_proj
    # ... 具体张量操作 ...
    # 注意:中间维度检查使用了 assert,建议改为 ValueError(见 Review)
    assert intermediate_x2 % 2 == 0, "gate_up_proj LoRA-B 维度应为 2*intermediate"
    # 安全访问模型架构名(存在 IndexError 风险,见 Review)
    base_arch = self.model.config.architectures[0]
    # ... 重塑与拆分逻辑 ...
tests/lora/test_qwen36_moe_lora.py test-coverage

新增的测试文件,覆盖 2D/3D 混合 batch 的核心场景,验证路由与正确性。

def _run_mixed_2d_3d_lora_test(
    lora_2d_files: str,
    lora_3d_files: str,
    tensor_parallel_size: int,
    fully_sharded_loras: bool,
) -> None:
    llm = vllm.LLM(
        model=MODEL_PATH,
        max_model_len=4096,
        enable_lora=True,
        enable_mixed_moe_lora_format=True, # 启用混合模式
        max_loras=2,
        max_lora_rank=8,
        max_num_seqs=4,
        enforce_eager=True,
        tensor_parallel_size=tensor_parallel_size,
        enable_expert_parallel=not fully_sharded_loras,
        fully_sharded_loras=fully_sharded_loras,
        trust_remote_code=True,
        enable_tower_connector_lora=True,
        mm_processor_cache_gb=0,
        limit_mm_per_prompt={"image": 1},
    )
​
    lora_2d = LoRARequest("lora_2d", 1, lora_2d_files, is_3d_lora_weight=False)
    lora_3d = LoRARequest("lora_3d", 2, lora_3d_files, is_3d_lora_weight=True)
​
    # 基线:单独使用单个适配器对两个 prompt 生成
    outputs_2d_alone = _generate(llm, lora_2d)
    outputs_3d_alone = _generate(llm, lora_3d)
​
    # 混合 batch:prompt 0 -> lora_2d, prompt 1 -> lora_3d
    mixed_outputs = _generate(llm, [lora_2d, lora_3d])
    assert mixed_outputs[0] == outputs_2d_alone[0]
    assert mixed_outputs[1] == outputs_3d_alone[1]
​
    # 顺序交换验证路由无误
    swapped_outputs = _generate(llm, [lora_3d, lora_2d])
    assert swapped_outputs[0] == outputs_3d_alone[0]
    assert swapped_outputs[1] == outputs_2d_alone[1]

评论区精华

assert 应替换为 ValueError 正确性

gemini-code-assist[bot] 指出使用 assert 进行数据验证会导致引擎在 Python -O 模式下静默失败,建议改为 ValueError。

结论:已被记录,但尚未应用修改。 · 待处理

architectures[0] 可能 IndexError 正确性

gemini-code-assist[bot] 建议使用 getattr(config, 'architectures', [None])[0] 安全访问 models 配置中的 architectures 列表。

结论:已被记录,但尚未应用修改。 · 待处理

_is_3d_moe_model 属性用途 question

ywang96 询问新属性 _is_3d_moe_model 的使用场景。

结论:作者 jeejeelee 解释用于 GPT-Oss 等始终为 3D MoE 的模型初始化 LoRA 层。 · 已解决

风险与影响

1) 断言风险_convert_3d_to_2d_moe_lora 中使用 assert 检查中间维度,在 python -O 模式下会被跳过,导致静默错误;
2) 兼容性风险:当 enable_mixed_moe_lora_format=True 时,所有 3D 模型(包括原本不需要转换的模型,如 GPT-Oss)也将强制走 2D 包装路径,需要保证所有 3D 原生模型的权重加载路径无误;
3) 性能影响:3D→2D 转换发生在适配器加载时,对推理延迟无影响,但会增加加载时间与额外内存拷贝。

用户端:Qwen3.5-MoE 用户可直接加载 3D LoRA 权重,无需手动转换;系统端:新增配置与数据标志,扩展 LoRA 接口契约;测试侧:新增多 GPU 混合 batch 测试,覆盖 TP=2/4 场景;维护侧model_manager.py 的初始化逻辑与 _create_merged_loras_inplace 分支复杂度增加,需维护状态一致性。

核心路径变更 断言取代 配置新增 兼容性影响

关联 Issue

#40005 [Feature]: Qwen3.5-Moe LoRA Support (experts)

完整报告

参与讨论