Prhub

#43356 Add Cosmos3 Reasoner model

原始 PR 作者 MaciejBalaNV 合并时间 2026-05-29 00:43 文件变更 10 提交数 15 评论 19 代码增减 +170 / -1

执行摘要

新增 Cosmos3 Reasoner 模型支持

根据 PR 描述,Cosmos3 是一个尚未发布的混合 transformer 模型,包含 Reasoner 和 Generator 双塔。Reasoner 塔架构与 Qwen-VL 相同,此 PR 实现从统一的 diffusers 格式检查点到 vLLM 的权重映射,以加载 Reasoner 部分。

此 PR 是模型集成的良好范例,展示了如何通过 WeightsMapper 和 secondary_weights 机制快速适配非标准 checkpoint 格式。其中的权重映射模式设计值得学习和参考。推荐在引入其他类似架构(如混合双塔模型)时参考此实现。

讨论亮点
  • 权重映射路径的正确性:gemini-code-assist[bot] 质疑 regex 替换目标路径不正确,但作者澄清 WeightsMapper 会依次应用 regex、substr 和 prefix 映射,最终路径正确,测试通过。
  • 避免全局配置副作用:早期版本修改 vllm_config.load_config.load_format 导致副作用,作者根据反馈改为硬编码 allow_patterns_overrides
  • 子目录加载支持:Isotr0py 建议使用 subfolder 参数简化 secondary_weights 加载,但作者说明主权重同样需要子目录,因此保留对 default_loader.py 的修改。
  • 测试可用性:Isotr0py 建议在 registry 测试中设置 is_available_online=False 以避免 CI 失败,作者采纳。

实现拆解

  1. 配置定义:新增 vllm/transformers_utils/configs/cosmos3.py,定义 Cosmos3Config 继承 Qwen3VLConfig,设置 model_type = "cosmos3_omni",使 vLLM 能识别该架构。
  2. 模型实现:新增 vllm/model_executor/models/cosmos3.py,实现 Cosmos3ForConditionalGeneration 继承 Qwen3VLForConditionalGeneration。关键工作是定义 hf_to_vllm_mapperWeightsMapper 实例),通过三个子映射完成键名转换:
    • orig_to_new_regex:将 layers.*embed_tokens.*norm.* 前缀重写为 language_model.model.*,将 blocks.*merger.* 等视觉前缀重写为 visual.*,并丢弃 audio_modality_embedaction_modality_embed
    • orig_to_new_substr:替换子字符串,例如 _moe_genadd_*_proj 相关键被删除(对应 Generator 塔权重),同时重命名 to_qq_projto_outo_proj 等。
    • orig_to_new_prefix:删除 proj_in.time_embedder. 等 Generator 专用前缀,并将 lm_head. 映射到 language_model.lm_head.
    • 此外设置 allow_patterns_overrides = ["transformer/*.safetensors"] 以指定主权重位于 transformer/ 子目录。
  3. 双权重加载:在 __init__ 中通过 self.secondary_weights 指定从同一模型仓库的 vision_encoder/ 子目录加载视觉编码器权重,使用 DefaultModelLoader.Source 精确控制路径。
  4. 加载器增强:修改 vllm/model_executor/model_loader/default_loader.py 中的 _prepare_weights 函数,将 pattern == "*.safetensors" 改为 pattern.endswith(".safetensors"),以允许子目录内的 glob 模式匹配安全张量文件。
  5. 注册与文档:在 vllm/model_executor/models/registry.py 中注册 Cosmos3ForConditionalGeneration 的映射;更新 vllm/transformers_utils/configs/__init__.pyvllm/transformers_utils/config.py 以导入新配置;在 docs/models/supported_models.md 中添加一行。
  6. 测试覆盖
    • tests/models/multimodal/test_mapping.py:新增 test_cosmos3_new_checkpoint_weights_mapper 函数,验证 WeightsMapper 正确转换代表性键名,并确认所有 Generator 塔键被丢弃(返回空列表)。
    • tests/models/multimodal/generation/test_common.py:在 VLMTestInfo 中注册 "cosmos3" 条目,使用 nvidia/Cosmos3-Nano 模型,设置支持 image、multi-image 和 video 输入类型,以及相应的 prompt 格式和预处理后处理钩子。
    • tests/models/registry.py:添加模型条目并标记 is_available_online=False,避免 CI 因模型未公开而失败。
文件 模块 状态 重要度
vllm/model_executor/models/cosmos3.py 模型定义 added 8.37
vllm/transformers_utils/configs/cosmos3.py 配置 added 6.65
tests/models/multimodal/test_mapping.py 测试 modified 6.38
vllm/model_executor/model_loader/default_loader.py 加载器 modified 5.17
tests/models/multimodal/generation/test_common.py 测试 modified 5.18
vllm/model_executor/models/registry.py 注册中心 modified 4.93

关键符号

Cosmos3ForConditionalGeneration.__init__ test_cosmos3_new_checkpoint_weights_mapper

关键源码片段

vllm/model_executor/models/cosmos3.py data-contract

核心模型实现,定义了权重映射逻辑和 secondary_weights,是 PR 主体。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM projectimport regexfrom vllm.config import VllmConfig
from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
from vllm.model_executor.models.qwen3_vl import Qwen3VLForConditionalGeneration
from vllm.model_executor.models.utils import WeightsMapper
​
​
class Cosmos3ForConditionalGeneration(Qwen3VLForConditionalGeneration):
    # Cosmos3 统一检查点以一种扁平键布局存储 Qwen3-VL 理解塔和生成塔的权重。
    # 此映射器丢弃生成塔权重,并将理解塔键重写为 Qwen3VLForConditionalGeneration
    # 期望的嵌套形式。
    hf_to_vllm_mapper = WeightsMapper(
        orig_to_new_regex={
            # 语言模型部分:layers.* → language_model.model.layers.*
            regex.compile(
                r"^(layers\.|embed_tokens\.|norm\.)(.+)$"
            ): r"language_model.model.\1\2",
            # 视觉部分:blocks.* → visual.blocks.*
            regex.compile(
                r"^(blocks\.|merger\.|patch_embed\.|pos_embed\.|deepstack_merger_list\.)"
            ): r"visual.\1",
            # 丢弃多模态嵌入:音频和动作
            regex.compile(r"^audio_modality_embed(?:\..*)?$"): None,
            regex.compile(r"^action_modality_embed(?:\..*)?$"): None,
        },
        orig_to_new_substr={
            # 删除生成塔专有子字符串
            "_moe_gen": None,
            ".add_q_proj.": None,
            ".add_k_proj.": None,
            ".add_v_proj.": None,
            ".to_add_out.": None,
            ".norm_added_q.": None,
            ".norm_added_k.": None,
            # 重命名注意力投影:to_q → q_proj, 等
            ".to_q.": ".q_proj.",
            ".to_k.": ".k_proj.",
            ".to_v.": ".v_proj.",
            ".to_out.": ".o_proj.",
            ".norm_q.": ".q_norm.",
            ".norm_k.": ".k_norm.",
        },
        orig_to_new_prefix={
            # 丢弃生成塔专用前缀
            "proj_in.": None,
            "proj_out.": None,
            "time_embedder.": None,
            "audio_proj_in.": None,
            "audio_proj_out.": None,
            "action_proj_in.": None,
            "action_proj_out.": None,
            # lm_head 重定向到语言模型内
            "lm_head.": "language_model.lm_head.",
        },
    )
​
    # 指定主权重位于 checkpoint 的 transformer/ 子目录
    allow_patterns_overrides = ["transformer/*.safetensors"]    """
    Cosmos3 checkpoint 将 transformer 权重和 vision_encoder 权重分离到不同目录,
    采用了 diffusers 格式。这里使用 secondary_weights 加载 Reasoner 部分所需的全部权重。
    """
​
    def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
        super().__init__(vllm_config=vllm_config, prefix=prefix)
        # 从 vision_encoder 子目录加载视觉编码器权重作为次要权重
        self.secondary_weights = [
            DefaultModelLoader.Source(
                model_or_path=vllm_config.model_config.model,
                revision=vllm_config.model_config.revision,
                prefix="",
                allow_patterns_overrides=["vision_encoder/*.safetensors"],
            ),
        ]
vllm/transformers_utils/configs/cosmos3.py core-logic

定义 Cosmos3Config,使 vLLM 能识别该架构。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project# 继承 Qwen3VL 配置以实现快速集成
from transformers.models.qwen3_vl.configuration_qwen3_vl import Qwen3VLConfig
​
​
class Cosmos3Config(Qwen3VLConfig):
    model_type = "cosmos3_omni" # 用于模型注册的标识符
tests/models/multimodal/test_mapping.py test-coverage

包含权重映射单元测试,验证关键映射正确性。

def test_cosmos3_new_checkpoint_weights_mapper():
    # 导入 Cosmos3 模型类以获取映射器
    from vllm.model_executor.models.cosmos3 import Cosmos3ForConditionalGeneration
​
    mapper = Cosmos3ForConditionalGeneration.hf_to_vllm_mapper
​
    # 验证语言模型与视觉键的转换
    assert mapper.apply_list(
        [
            "layers.0.self_attn.to_q.weight",
            "layers.0.self_attn.to_k.weight",
            "layers.0.self_attn.to_v.weight",
            "layers.0.self_attn.to_out.weight",
            "layers.0.self_attn.norm_q.weight",
            "layers.0.self_attn.norm_k.weight",
            "embed_tokens.weight",
            "norm.weight",
            "lm_head.weight",
            "blocks.0.attn.qkv.weight",
        ]
    ) == [
        "language_model.model.layers.0.self_attn.q_proj.weight",
        "language_model.model.layers.0.self_attn.k_proj.weight",
        "language_model.model.layers.0.self_attn.v_proj.weight",
        "language_model.model.layers.0.self_attn.o_proj.weight",
        "language_model.model.layers.0.self_attn.q_norm.weight",
        "language_model.model.layers.0.self_attn.k_norm.weight",
        "language_model.model.embed_tokens.weight",
        "language_model.model.norm.weight",
        "language_model.lm_head.weight",
        "visual.blocks.0.attn.qkv.weight",
    ]
​
    # 验证生成塔相关键被丢弃(返回空列表)
    assert (
        mapper.apply_list(
            [
                "layers.0.self_attn.add_q_proj.weight",
                "layers.0.self_attn.add_k_proj.weight",
                "layers.0.self_attn.add_v_proj.weight",
                "layers.0.self_attn.to_add_out.weight",
                "layers.0.self_attn.norm_added_q.weight",
                "layers.0.self_attn.norm_added_k.weight",
                "layers.0.self_attn.q_proj_moe_gen.weight",
                "layers.0.mlp_moe_gen.gate_up_proj.weight",
                "norm_moe_gen.weight",
                "proj_in.weight",
                "proj_out.weight",
                "time_embedder.linear_1.weight",
                "audio_proj_in.weight",
                "audio_proj_out.weight",
                "action_proj_in.weight",
                "action_proj_out.weight",
                "audio_modality_embed",
                "action_modality_embed",
            ]
        )
        == []
    )

评论区精华

权重映射 regex 路径是否正确 正确性

gemini-code-assist[bot] 指出 regex 替换目标不正确,应直接映射到最终属性路径。作者回应 WeightsMapper 会依次应用多个映射,最终路径正确,经测试通过。

结论:作者解释并坚持原实现,审核人不再质疑,状态 resolved。 · 已解决

避免全局配置副作用 设计

gemini-code-assist[bot] 指出修改 vllm_config.load_config.load_format 是副作用,建议在加载器中改进检测。作者改为硬编码 allow_patterns_overrides。

结论:作者接受建议并修改,删除了全局副作用。 · 已解决

allow_patterns 安全风险 安全

depthfirst-app[bot] 指出从模型 config.json 读取 allow_patterns_overrides 可能导致加载不安全权重。作者回复已硬编码模式。

结论:作者硬编码了 allow_patterns_overrides,避免了风险。 · 已解决

测试可用性处理 测试

Isotr0py 建议在 registry 测试中设置 is_available_online=False 以避免 CI 失败。作者采纳。

结论:接受建议并修改。 · 已解决

secondary_weights 使用 subfolder 设计

Isotr0py 建议使用 subfolder 参数而非 allow_patterns_overrides 来加载 vision_encoder 权重。作者解释主权重也需要子目录支持,保留修改。

结论:双方达成一致,维持当前实现,追加子目录支持。 · 已解决

风险与影响

  • 权重映射正确性:映射规则遗漏或错误可能导致加载失败或静默精度下降。当前单元测试覆盖了主要场景,降低了风险。
  • 加载器模式变更:将 _prepare_weights 从精确匹配改为后缀匹配,可能影响其他使用自定义 allow_patterns 的模型,但影响范围有限(仅在自定义 override 路径时触发)。
  • 模型未公开:测试依赖 nvidia/Cosmos3-Nano 仓库,在模型公开前 CI 无法运行端到端测试,仅依赖单元测试。
  • 用户影响:用户可加载 Cosmos3 模型进行推理,但模型尚未公开发布;新增模型不影响现有模型使用。
  • 系统影响:无性能或安全回归风险;新代码集中在特定模型类,与核心调度、缓存等模块解耦。
  • 团队影响:维护成本低,因 Cosmos3 基于 Qwen3VL,未来底座更新时可自动受益。
模型未公开 加载器模式变更可能影响其他模型

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论