Prhub

#50858 [BugFix] Disable TP for Qwen3-Omni audio encoder when heads % TP != 0

原始 PR 作者 CalvinXKY 合并时间 2026-08-29 11:18 文件变更 1 提交数 2 评论 13 代码增减 +8 / -1

执行摘要

修复 Qwen3-Omni 音频编码器在 TP 不可整除时无法初始化的问题

Qwen3-Omni 的音频编码器固定使用 20 个注意力头,而常见的 TP 大小(如 8)不能整除 20,导致在创建 QKVParallelLinear / RowParallelLinear 时抛出头部数量与 TP 不可整除的错误,使得模型在 TP=8 下无法正常初始化。PR 描述中也明确提到‘Under some tensor-parallel sizes such as TP=8, 20 % tp_size != 0, so constructing QKVParallelLinear / RowParallelLinear fails during rollout’。

值得精读。该 PR 展示了在多模态模型中对 Tensor Parallelism 粒度进行条件控制的典型模式:通过判断头数是否可整除 TP 来动态决定是否分片。实现简洁,但有性能权衡,建议了解其背后的并行策略。

讨论亮点

评论中,Isotr0py 指出 TP=4 是可整除且常用于 30B 模型的 TP 大小,暗示应保留 TP=4 的分片行为。CalvinXKY 回复‘Yes. Updated’,并将实现改为仅在不可整除时禁用 TP。另外,Isotr0py 建议移除多余测试,CalvinXKY 已移除。最终 Isotr0py 批准了该 PR。

实现拆解

变更集中在 Qwen3OmniMoeAudioAttention.init 方法中:

  1. 计算 tp_size 后,新增 disable_tp = self.num_heads % tp_size != 0 判断。
  2. 根据 disable_tp 动态设置 num_local_heads:不可整除时等于 num_heads(即不进行分片),可整除时保持原有的 num_heads // tp_size。
  3. 将 disable_tp 传给 QKVParallelLinear 和 RowParallelLinear,以便在这两个线性层中跳过 TP 分片逻辑。
  4. 后续 MMEncoderAttention 仍使用 num_local_heads 作为本地头数,forward 逻辑不变。
    该改动保持了可整除场景下的原有行为,同时为不可整除的 TP 场景提供了降级路径。没有新增测试文件,仅通过手动验证覆盖。
文件 模块 状态 重要度
vllm/model_executor/models/qwen3_omni_moe_thinker.py 模型实现 modified 6.09

关键符号

Qwen3OmniMoeAudioAttention.__init__

关键源码片段

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

核心文件,修改了音频注意力层的 TP 处理逻辑,是 PR 的实质变更。

# vllm/model_executor/models/qwen3_omni_moe_thinker.py
class Qwen3OmniMoeAudioAttention(nn.Module):
    def __init__(self, config: Qwen3OmniMoeAudioEncoderConfig, prefix: str = ""):
        super().__init__()
        self.embed_dim = config.d_model
        self.num_heads = config.encoder_attention_heads
        self.head_dim = self.embed_dim // self.num_heads
        # 音频编码器使用 20 个头,当 TP 大小不能整除头数时(如 TP=8),
        # 若仍进行 TP 分片会导致初始化失败,故此时禁用 TP 分片。
        tp_size = get_tensor_model_parallel_world_size()
        self.disable_tp = self.num_heads % tp_size != 0
        self.num_local_heads = (
            self.num_heads if self.disable_tp else self.num_heads // tp_size
        )
        # 当 disable_tp 为 True 时,QKV 和输出投影不做切分,
        # 从而避免头数不可整除带来的错误。
        self.qkv_proj = QKVParallelLinear(
            hidden_size=self.embed_dim,
            head_size=self.head_dim,
            total_num_heads=self.num_heads,
            total_num_kv_heads=self.num_heads,
            bias=True,
            prefix=f"{prefix}.qkv_proj",
            disable_tp=self.disable_tp,
        )
        self.out_proj = RowParallelLinear(
            input_size=self.embed_dim,
            output_size=self.embed_dim,
            bias=True,
            prefix=f"{prefix}.out_proj",
            disable_tp=self.disable_tp,
        )
        # 注意力的本地头数会随着 disable_tp 动态变化,forward 内无需改动。
        self.attn = MMEncoderAttention(
            num_heads=self.num_local_heads,
            head_size=self.head_dim,
            scale=self.scaling,
            prefix=f"{prefix}.attn",
        )

评论区精华

TP=4 的可分性讨论 设计

Isotr0py 指出 TP=4 仍可整除且是 30B 模型常用的 TP 大小,强调需要保留该情况下的分片行为。

结论:CalvinXKY 更新实现,仅在不可整除时禁用 TP。 · 已解决

移除冗余测试 测试

Isotr0py 要求移除冗余测试。

结论:CalvinXKY 已移除。 · 已解决

风险与影响

该改动可能改变模型在多卡 TP 场景下的并行方式,影响推理性能和显存分布:

  • 在 TP=8 下禁用 TP 会导致音频编码器在单卡上执行,可能增加单卡显存占用和计算负载,对于大规模部署可能产生显存瓶颈。
  • 由于行为变化不会在常规测试中覆盖,缺乏自动化回归测试,未来改动可能再次引入此问题。
  • 实现仅在音频注意力层处理,若其他编码器层也存在类似 TP 约束,可能遗漏类似错误。

影响范围:

  • 用户:修复了 Qwen3-Omni 在 TP=8 下无法启动的问题,扩大了部署灵活性。
  • 系统:音频编码器在 TP 不可整除时采用无分片回退,可能导致性能下降。
  • 团队:无新增 API,维护成本低。影响程度中等,属于针对性修复。
缺少测试覆盖 性能回退风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论