Prhub

#51149 Interns2mobius support

原始 PR 作者 lvhan028 合并时间 2026-08-06 18:39 文件变更 8 提交数 5 评论 5 代码增减 +664 / -3

执行摘要

新增 Intern-S2-Mobius 模型支持,含共享 MoE 与 MTP 适配

PR body 只给出了一句话:"Add support for model https://huggingface.co/internlm/Intern-S2-Mobius",核心诉求是让 vLLM 能够直接加载并推理该 Hugging Face 模型。从实现细节看,Intern-S2-Mobius 的文本子模型是 MoE 结构且带 MTP 头,因此本 PR 需要同时打通多模态文本子模型、共享专家 MoE 与 MTP 投机解码三条链路,而不只是简单加一个模型类。

值得精读,重点看两点:一是 InternS2MobiusMetaMoeBlock + layer_idx % num_blocks 的共享 MoE bank 设计,以及与 sequence-parallel all-gather/reduce-scatter 的交互;二是 is_supported_config 这个扩展点在 fused_moe 模块化 kernel 机制里的用法。建议读者配合后续的精度/性能测试 PR 一起阅读,因为本 PR 自身没有附带数值验证结果。

讨论亮点

整个 PR 没有出现实质性的技术 review 交锋。claude[bot] 评论指出这是 fork 来的 PR,自动 review 被禁用,维护者可手动触发;Isotr0py 最终直接 APPROVED,并通过 /ci run 触发了两轮 Buildkite CI(#82483、#82658),均未见失败报告。因此本 PR 的决策主要依赖作者提交历史(MTP 适配、禁用 DeepStack 路径、限制 TRTLLM 专家数)和 CI 结果,而非代码评审讨论。

实现拆解

实现按以下 5 步拆解:

  1. 新增模型主实现 vllm/model_executor/models/interns2_mobius.py。新增 InternS2MobiusModel(继承 EagleModelMixin,支持 torch.compile),核心是 InternS2MobiusMetaMoeBlock:一个被多个 decoder 层共享的路由 MoE bank,内部用 FusedMoEFactory 构建专家、用 ReplicatedLinear 做 gate,并支持 is_internal_router 与 sequence-parallel 输入分片。InternS2MobiusSharedExpertBlock 则复用 Qwen2MoeMLP 作为每层共享专家。InternS2MobiusDecoderLayer 根据 layer_type 在 QwenGatedDeltaNetAttention(linear_attention)与 Qwen3NextAttention(full_attention)之间选择,前向时通过 layer_idx % num_blocks 从一个 nn.ModuleList 形态的 meta_mlp 银行中取块,与共享专家输出相加,形成 Mobius 式的共享 MoE 拓扑;同时实现了 SP 场景下 all-gather/reduce-scatter 的往返切分逻辑。
  2. MTP 投机解码适配 vllm/config/speculative.py。在 hf_config_override 中把 interns2_mobius 与 intern_s2_preview 合并处理:对 Mobius 将 text_config.model_type 改写为 qwen3_5_moe_text,并把 mtp_num_experts、mtp_num_experts_per_tok 迁移到 num_experts、num_experts_per_tok,使 FusedMoE 能正确构建;随后统一映射到 qwen3_5_mtp 架构,指定 InternS2MobiusMTP 作为投机模型架构。
  3. MoE 专家数上限校验。分别在 triton_moe.py、trtllm_bf16_moe.py、trtllm_fp8_moe.py 中为 TritonExperts、TrtLlmBf16ExpertsBase、TrtLlmFp8ExpertsBase 新增静态方法 is_supported_config:Triton 后端要求 32 对齐后的 padded 专家数小于 1024(moe_align_block_size 限制),TRTLLM 后端要求 num_experts <= 2048(FlashInfer TRTLLM routing 限制)。不满足时返回明确的不可用原因,而不是让内核在后端崩溃。
  4. 模型注册 vllm/model_executor/models/registry.py。在多模态模型注册表中新增 InternS2MobiusForConditionalGeneration -> interns2_mobius 条目,在 MTP/投机解码注册表中新增 InternS2MobiusMTP -> interns2_mobius.InternS2MobiusMTP 条目。
  5. 测试与文档配套。tests/models/registry.py 为两个架构新增 _HfExamplesInfo(对应 internlm/Intern-S2-Mobius,is_available_online=False,即不触发在线下载测试);docs/models/supported_models.md 增加一行支持表格。
文件 模块 状态 重要度
vllm/model_executor/models/interns2_mobius.py 模型实现 added 9.28
vllm/config/speculative.py 配置映射 modified 6.12
vllm/model_executor/layers/fused_moe/experts/triton_moe.py MoE 内核 modified 6.42
vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py MoE 内核 modified 6.37
vllm/model_executor/layers/fused_moe/experts/trtllm_fp8_moe.py MoE 内核 modified 6.37
vllm/model_executor/models/registry.py 模型注册 modified 5.34
tests/models/registry.py 测试配置 modified 4.11
docs/models/supported_models.md 模型文档 modified 1.39

关键符号

InternS2MobiusMetaMoeBlock.__init__ InternS2MobiusMetaMoeBlock.forward InternS2MobiusSharedExpertBlock.__init__ InternS2MobiusSharedExpertBlock.forward InternS2MobiusDecoderLayer.__init__ InternS2MobiusDecoderLayer.forward InternS2MobiusModel.__init__ InternS2MobiusModel.forward get_layer hf_config_override TritonExperts.is_supported_config TrtLlmBf16ExpertsBase.is_supported_config TrtLlmFp8ExpertsBase.is_supported_config

关键源码片段

vllm/model_executor/models/interns2_mobius.py core-logic

新增 562 行模型主实现,包含共享 MetaMoE 块、共享专家、双类型 decoder 层与 MTP 支持,是本次变更的核心。

    def forward(
        self,
        hidden_states: torch.Tensor,
        residual: torch.Tensor | None,
        positions: torch.Tensor,
        meta_mlp: nn.ModuleList,
    ) -> tuple[torch.Tensor, torch.Tensor]:
        # 记录当前层的完整 token 数,后续 SP 切分 / 合并后需要按它裁剪
        full_num_tokens = positions.shape[-1]
        # 判断输入是否已是 sequence-parallel 分片:只有当启用了 SP MoE、
        # 存在残差流、且行数与完整 token 数不一致时才需要先 all-gather
        input_is_sequence_parallel = (
            self.use_attn_reduce_scatter_for_moe
            and residual is not None
            and hidden_states.shape[0] != full_num_tokens
        )
​
        if residual is None:
            # 首层没有残差,直接对输入做 RMSNorm
            residual = hidden_states
            hidden_states = self.input_layernorm(hidden_states)
        else:
            # 后续层走带残差融合的 norm,减少一次 kernel 启动
            hidden_states, residual = self.input_layernorm(hidden_states, residual)
​
        if input_is_sequence_parallel:
            # 从 SP 分片恢复完整序列后才能送入注意力
            hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
            hidden_states = hidden_states[:full_num_tokens]
​
        # 根据 HF 配置的 layer_type 选择线性注意力(Gated DeltaNet)
        # 或全注意力(Qwen3NextAttention)
        if self.layer_type == "linear_attention":
            hidden_states = self.linear_attn(hidden_states=hidden_states)
        else:
            hidden_states = self.self_attn(
                hidden_states=hidden_states,
                positions=positions,
            )
​
        if self.layer_scale:
            scale = self.attn_layer_scale.to(hidden_states.dtype)
            hidden_states = hidden_states * (scale[0] + 1)
​
        if self.use_attn_reduce_scatter_for_moe:
            # 为了 MoE 的 EP/SP 效率,attention 输出先 reduce-scatter 回分片,
            # 让每个 rank 只持有本地专家需要的 token 子集
            tp_world_size = get_tensor_model_parallel_world_size()
            sp_pad = (-hidden_states.shape[0]) % tp_world_size
            hidden_states = torch.nn.functional.pad(hidden_states, (0, 0, 0, sp_pad))
            hidden_states = tensor_model_parallel_reduce_scatter(hidden_states, 0)
            if not input_is_sequence_parallel:
                # 残差流也要切成同样的分片布局,保持对齐
                residual = sequence_parallel_chunk(residual)
​
        hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
        # Mobius 拓扑:所有 decoder 层共享 num_blocks 个 MetaMoE 块,
        # 按层号取模轮转使用,显著减少路由专家的参数量
        block_idx = self.layer_idx % self.num_blocks
        routed_output = meta_mlp[block_idx](
            hidden_states,
            already_sequence_parallel=self.use_attn_reduce_scatter_for_moe,
        )
        # 路由专家输出与每层共享专家(Qwen2MoeMLP)输出相加
        hidden_states = routed_output + self.mlp(hidden_states)
​
        if self.layer_scale:
            scale = self.ffn_layer_scale.to(hidden_states.dtype)
            hidden_states = hidden_states * (scale[0] + 1)
​
        return hidden_states, residual
vllm/config/speculative.py core-logic

hf_config_override 中新增 interns2_mobius 分支,将文本子配置归一化为 qwen3_5_moe_text 并迁移 MTP 专家数,决定 MTP 投机解码能否正确构建。

        if hf_config.model_type in ("intern_s2_preview", "interns2_mobius"):
            is_mobius = hf_config.model_type == "interns2_mobius"
            text_config = getattr(hf_config, "text_config", None)
            # Mobius 的文本子模型本身就是 MoE,无需像 Preview 一样靠 model_type 推断
            is_moe = is_mobius or (
                getattr(text_config, "model_type", None) == "qwen3_5_moe_text"
            )
            if is_mobius:
                # 将文本子配置归一化为 qwen3_5_moe_text,并把 MTP 专用专家数迁移
                # 到 num_experts / num_experts_per_tok,让 FusedMoE 按正文配置构建
                assert text_config is not None
                text_config.model_type = "qwen3_5_moe_text"
                text_config.num_experts = text_config.mtp_num_experts
                text_config.num_experts_per_tok = text_config.mtp_num_experts_per_tok
            # 统一走 Qwen3.5 的 MTP 架构,但 Mobius 使用专属的 InternS2MobiusMTP
            hf_config.model_type = "qwen3_5_mtp"
            n_predict = getattr(text_config, "mtp_num_hidden_layers", None)
            architecture = (
                "InternS2MobiusMTP"
                if is_mobius
                else ("Qwen3_5MoeMTP" if is_moe else "Qwen3_5MTP")
            )
            hf_config.update(
                {
                    "n_predict": n_predict,
                    "architectures": [architecture],
                }
            )
vllm/model_executor/layers/fused_moe/experts/triton_moe.py data-contract

为 TritonExperts 新增 is_supported_config,限制 padded 专家数小于 1024,避免 MoE align 内核在超大专家数下失败;这是对全局 MoE kernel 选择逻辑的扩展。

    @staticmethod
    def is_supported_config(
        cls: type[mk.FusedMoEExperts],
        moe_config: FusedMoEConfig,
        weight_key: QuantKey | None,
        activation_key: QuantKey | None,
        activation_format: mk.FusedMoEActivationFormat,
    ) -> tuple[bool, str | None]:
        # 先走基类的通用校验(量化、activation 格式、设备等)
        supported, reason = mk.FusedMoEExperts.is_supported_config(
            cls,
            moe_config,
            weight_key,
            activation_key,
            activation_format,
        )
        # 非 CUDA 平台不做额外限制,直接交回基类结论
        if not supported or not current_platform.is_cuda_alike():
            return supported, reason
​
        # triton_moe 内核的 moe_align_block_size 会把专家数对齐到 32,
        # 当对齐后数量达到 1024 时内核不可用,这里提前返回明确错误
        padded_num_experts = (moe_config.num_experts + 31) // 32 * 32
        if padded_num_experts >= 1024:
            return False, (
                "kernel's moe_align_block_size requires fewer than 1024 padded experts"
            )
        return True, None

评论区精华

Fork PR 的自动 review 被禁用 other

claude[bot] 评论:"This pull request is from a fork — automated review is disabled. A repository maintainer can comment `@claude review` to run a one-time review."

结论:未触发一次性 review,Isotr0py 随后手动 APPROVED。 · 已解决

CI 触发与结果 other

Isotr0py 两次执行 `/ci run`,github-actions[bot] 分别触发 Buildkite CI #82483 与 #82658。

结论:两轮 CI 均成功触发,未报告失败。 · 已解决

风险与影响

  1. 新增模型代码缺乏数值验证:PR body 的 Test Plan/Test Result 为空,tests 只登记了 registry 条目且 is_available_online=False,不会真正运行精度测试;562 行新模型代码的正确性依赖作者自测。
  2. 复用大量实验性组件:QwenGatedDeltaNetAttention、Qwen3NextAttention、Qwen3_5 系列仍在快速演进,组件行为变化可能给该模型带来隐性回归。
  3. MoE 专家数上限影响面较广:三个 is_supported_config 属于全局 kernel 选择逻辑,虽然现有模型专家数远低于阈值,但未来任何超过 1024/2048 专家的模型都会在 Triton/TRTLLM 路径上被明确拒绝,需评估是否有模型依赖这两条路径。
  4. MTP 配置重写依赖字段存在性:speculative.py 中 assert text_config is not None,并强依赖 mtp_num_experts、mtp_num_experts_per_tok 等 HF 自定义字段,若上游配置格式变化会直接断言崩溃。
  5. 非 CUDA 路径未验证:is_supported_config 对非 CUDA 平台放行,但新增模型使用的注意力与 MoE 组件在 ROCm/XPU/CPU 上是否可用并未验证。

对用户而言,可以首次在 vLLM 中加载 internlm/Intern-S2-Mobius,并可直接使用其 MTP 投机解码能力。对系统而言,模型注册表新增两个入口,hf_config_override 新增一个 model_type 分支,fused_moe 的 kernel 选择路径新增专家数上限校验——后者是全局性改动,但对既有配置是向后兼容的(原模型专家数远小于阈值)。对团队而言,该实现为后续 Intern-S2 系列变体提供了可复用的骨架,但也需要尽快补充精度/性能测试与更完整的文档。影响范围集中在新增路径,风险可控。

新增模型代码缺乏测试结果 依赖多个实验性组件 MoE 专家数上限影响其他模型 MTP 配置重写依赖字段存在性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论