Prhub

#42070 [Bugfix] Remove nested torch.compile in GDN rearrange_mixed_qkv causing CUDA graph capture failure

原始 PR 作者 tdoublep 合并时间 2026-05-09 23:30 文件变更 1 提交数 2 评论 14 代码增减 +0 / -1

执行摘要

移除 GDN 中嵌套 @torch.compile 修复 CUDA Graph 捕获失败

模型 Qwen/Qwen3.5-35B-A3B 使用 --speculative-config '{"method":"qwen3_next_mtp","num_speculative_tokens":2}' 启动时,会触发 torch.AcceleratorError: CUDA error: operation not permitted when stream is capturing。PR body 明确指出失败原因是嵌套的 @torch.compile(fullgraph=True) 导致 Triton autotuning 在 CUDA graph capture 期间插入了 torch.cuda.synchronize() 调用。

值得精读:该 PR 虽仅修改一行,但揭示了 torch.compile 中 CUDA Graph capture 与 Triton autotuning 之间的互斥约束,以及多层编译嵌套可能导致的隐蔽问题。对于参与 vLLM 编译优化的工程师有重要参考价值。设计决策关注点:当增加 @torch.compile 装饰器时需确认是否已被外层 AOT 编译覆盖;对模型启动崩溃问题,复现命令已提供,易于本地验证。

讨论亮点
  1. 设备适用性:reviewer @ZJY0516 最初怀疑此问题仅在 ROCm 平台上出现,但随后确认该问题在 NVIDIA GPU(如 GB200)上也存在,并且仅在开启推测解码(speculative decoding)时复现。
  2. 性能权衡:作者 @tpopp 指出,虽然删除 @torch.compile 可能会损失部分早期版本中观察到的额外 fusion,但这对于修复当前的问题来说是必要的。同时提示,更好的解决方案是修改 kernel 以接受非连续输入。
  3. 测试覆盖缺失:多位开发者(@tjtanaa, @tdoublep)承认 CI 中缺乏针对 Qwen3.5 模型 + MTP 推测解码的测试(仅有一个手动触发的 lm-eval 测试),导致该问题未能提前捕获。

实现拆解

  1. 定位问题:在 vllm/model_executor/layers/mamba/gdn_linear_attn.py 第 640 行的 rearrange_mixed_qkv 方法定义前,有一个多余的 @torch.compile(fullgraph=True) 装饰器。
  2. 删除多余装饰器:直接删除该装饰器行(+0/-1),使 rearrange_mixed_qkv 不再被 torch.compile 单独编译,而是依赖外层 AOT compilation 的统一编译流程。
  3. 影响范围:仅影响 GDN(Gated Differential Attention Network)线性注意力模块的 QKV 重排方法,其他方法不受影响。该行之前被引入以在较早版本中提供额外的 fusion 优化,但随着外围编译流程成熟,该优化已冗余且有害。
文件 模块 状态 重要度
vllm/model_executor/layers/mamba/gdn_linear_attn.py 注意力层 modified 5.19

关键符号

rearrange_mixed_qkv

关键源码片段

vllm/model_executor/layers/mamba/gdn_linear_attn.py core-logic

唯一修改的文件;删除 `rearrange_mixed_qkv` 方法前的 `@torch.compile(fullgraph=True)` 装饰器。该文件实现 GDN(Gated Differential Attention Network)线性注意力核心逻辑。

# vllm/model_executor/layers/mamba/gdn_linear_attn.pyclass GDNLinearAttention(nn.Module):
    # ... 其他方法 ...
​
    # 删除前:@torch.compile(fullgraph=True) ← 此行被移除
    # 删除后:无装饰器,直接定义方法
    def rearrange_mixed_qkv(self, mixed_qkv):
        """Split packed qkv into contiguous (1, seq, heads, dim) tensors.        The original code used ``rearrange(x, "l (h d) -> 1 l h d", d=...)``
        followed by ``.contiguous()`` on each tensor.  This version flattens
        all three splits into a single buffer via ``torch.cat`` so that
        torch.compile emits one Triton copy kernel instead of three separate
        contiguous() calls.        注意:此方法不需要独立的 @torch.compile 装饰器,
        因为它已经被外层的 AOT compilation pass 编译覆盖。
        嵌套的 @torch.compile 会在 CUDA Graph capture 期间
        触发 Triton autotuning(调用 torch.cuda.synchronize()),
        导致 CUDA error: operation not permitted when stream is capturing。
        """
        if mixed_qkv is None:
            return None, None, None
​
        seq_len = mixed_qkv.shape[0]
        q_dim = self.key_dim // self.tp_size
        k_dim = self.key_dim // self.tp_size
        v_dim = self.value_dim // self.tp_size
​
        query, key, value = torch.split(mixed_qkv, [q_dim, k_dim, v_dim], dim=-1)
​
        fused = torch.cat(
            [query.reshape(-1), key.reshape(-1), value.reshape(-1)], dim=0
        )
​
        q_size = seq_len * q_dim
        k_size = seq_len * k_dim
​
        q_contig = fused[0:q_size]
        k_contig = fused[q_size : q_size + k_size]
        v_contig = fused[q_size + k_size :]
​
        query = q_contig.view(1, seq_len, -1, self.head_k_dim)
        key = k_contig.view(1, seq_len, -1, self.head_k_dim)
        value = v_contig.view(1, seq_len, -1, self.head_v_dim)
​
        return query, key, value

评论区精华

问题复现与 CudaGraph 捕获冲突 正确性

多位 reviewer(@tjtanaa, @tdoublep, @ZJY0516)确认该问题仅在启用推测解码(MTP)时复现,且 GB200 上 main 分支已因此失败。@tjtanaa 指出 `rearrange_mixed_qkv` 被调用在自定义 op `gdn_attention_core` 的作用域内,该 op 对 torch.compile 是不透明的。

结论:确认问题为嵌套 @torch.compile 导致,删除即可修复。 · 已解决

性能权衡与替代方案 设计

@tpopp 表示精神上认可删除,但指出早期版本中该装饰器带来了额外的 fusion 优化,删除可能损失少量性能。@ZJY0516 认为更好的方案是修改 kernel 以接受非连续输入。

结论:当前以修复崩溃为首要目标,未来可考虑 kernel 优化。 · 已解决

CI 测试覆盖不足 测试

@tjtanaa 和 @tdoublep 讨论指出 CI 中缺乏 Qwen3.5 模型 + MTP 推测解码的测试,导致该 bug 未被提前发现。仅有手动触发的 lm-eval 测试。

结论:需要增加相关测试覆盖。 · unresolved

CI 任务重跑请求 other

@SoluMilken 请求维护者重跑两个失败的 Buildkite 任务:`fusion-e2e-tp2-b200`(基础设施 pod Pending)和 `amd-multi-modal-models-standard-2-qwen3-plus-gemm`(日志非断言失败)。

结论:重跑请求已发出,等待维护者操作。 · unresolved

对性能影响的后续评估 性能

@vadiklyutiy 建议 @tpopp 评估此更改对 PR #40711 性能提升的影响。@tpopp 回应说影响可能不大,因为 AITER 为基础的运行有快速路径,未受益于此,而 CUDA 上的每个 kernel 开销较小,收益也较小。

结论:删除后性能影响预计很小,但仍需量化确认。 · 已解决

风险与影响

回归风险低:变更仅删除一行装饰器,且该装饰器已被外层编译流程取代,不会引入新 bug。但根据 @tpopp 的观察,此操作可能会在早期版本或某些未受益于 AITER 快速路径的配置中丢失少量的 kernel fusion 优化,对性能有轻微负面影响(预计很小)。不涉及兼容性、安全性和稳定性风险

用户影响:修复了使用 GDN 模型(如 Qwen3.5-35B-A3B)且开启推测解码(MTP)时的启动崩溃问题,使这类模型能正常使用 CUDA Graph 加速。系统影响:仅修改一个文件的单个函数装饰器,影响面极小。团队影响:提示团队需要增加对 GDN 模型 + 推测解码组合的 CI 测试覆盖。

CUDA Graph capture 冲突 缺少测试覆盖 轻微性能回归可能

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论