Prhub

#39064 [Bugfix] Fix GDN FLA kernel crashes with NULL_BLOCK_ID=0 CUDA graph padding

原始 PR 作者 vibhavagarwal5 合并时间 2026-04-11 16:35 文件变更 2 提交数 6 评论 23 代码增减 +14 / -13

执行摘要

修复 GDN FLA 内核因 CUDA 图块表填充从 -1 改为 0 导致的非法内存访问崩溃。

动机源于 Issue #39025,其中报告了 Qwen3.5-35B-A3B 模型在高并发和 TP=2 时,启用 CUDA 图后出现非法内存访问崩溃。PR body 指出:“Root cause: Commit bcc6f6744 changed CUDA graph block table padding from fill_(-1) (PAD_SLOT_ID) to fill_(NULL_BLOCK_ID=0). The FLA SSM kernels guard padded entries with state_idx < 0, which catches -1 but not 0.” 这导致填充条目错误读写真实序列的状态,从而引发崩溃。

该 PR 值得精读,因为它揭示了在系统级约定变更(如填充值从 -1 改为 0)时,如何确保内核守卫条件同步更新的重要性。关注设计决策:守卫条件的设计需与全局约定(NULL_BLOCK_ID)严格对齐,以避免隐蔽的内存错误。

讨论亮点

核心讨论围绕守卫条件修改的正确性展开。reviewer vadiklyutiy 询问:“May the real state_idx=0?”,质疑是否可能存在 state_idx=0 的真实序列。Alberto-Codes 在评论中澄清:“No — NULL_BLOCK_ID = 0 is a reserved sentinel (defined at vllm/v1/attention/backends/utils.py:45, introduced in #35431). Real sequences are never assigned slot 0, so any 0 here is padding by construction.” 这确认了修改的正确性,并引用了引入 NULL_BLOCK_ID 的 PR 作为依据。此外,MatthewBonanni 建议更新注释以仅引用 NULL_BLOCK_ID,最终被采纳。

实现拆解

  1. 修改 fused_recurrent.py 中的守卫条件:涉及 fused_recurrent_gated_delta_rule_fwd_kernel 函数,将 state_idx < 0 改为 state_idx <= 0 以跳过无效条目(包括 NULL_BLOCK_ID=0),并将 final_state_idx >= 0 改为 final_state_idx > 0 以确保仅存储有效索引。
  2. 修改 fused_sigmoid_gating.py 中的守卫条件:涉及 fused_sigmoid_gating_delta_rule_update_kernel 函数,进行类似更改,确保所有 FLA 内核一致处理填充。
  3. 更新注释:根据 review 反馈,将注释从引用 PAD_SLOT_ID(-1) 更新为明确引用 NULL_BLOCK_ID=0,以反映当前填充约定。
  4. 测试配套:PR body 中提到通过高并发测试验证修复,但未添加新测试文件;现有测试 tests/models/test_gdn.py 应继续通过。
文件 模块 状态 重要度
vllm/model_executor/layers/fla/ops/fused_recurrent.py FLA 内核 modified 5.23
vllm/model_executor/layers/fla/ops/fused_sigmoid_gating.py FLA 内核 modified 4.87

关键符号

fused_recurrent_gated_delta_rule_fwd_kernel fused_sigmoid_gating_delta_rule_update_kernel

关键源码片段

vllm/model_executor/layers/fla/ops/fused_recurrent.py core-logic

核心修改文件之一,包含 FLA 循环内核的守卫条件更新,修复状态索引无效时的内存访问错误。

def fused_recurrent_gated_delta_rule_fwd_kernel(
    # 参数列表省略
):
    # ... 其他代码
    # Load state index and check for invalid entries
    state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to(tl.int64)
    # Skip if state index is invalid (NULL_BLOCK_ID=0)
    # 修改前 : if state_idx < 0:
    if state_idx <= 0:
        return # 直接返回,避免访问无效状态
    p_h0 = h0 + state_idx * stride_init_state_token
    # ... 继续代码
    if INPLACE_FINAL_STATE:
        # Load state index and check for invalid entries
        final_state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to(tl.int64)
        # Only store if state index is valid (not NULL_BLOCK_ID=0)
        # 修改前 : if final_state_idx >= 0:
        if final_state_idx > 0:
            p_ht = ht + final_state_idx * stride_final_state_token
            tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
vllm/model_executor/layers/fla/ops/fused_sigmoid_gating.py core-logic

核心修改文件之一,包含 FLA 门控内核的守卫条件更新,与 fused_recurrent.py 类似修复。

def fused_sigmoid_gating_delta_rule_update_kernel(
    # 参数列表省略
):
    # ... 其他代码
    # Load state index and check for invalid entries
    state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to(tl.int64)
    # Skip if state index is invalid (NULL_BLOCK_ID=0)
    # 修改前 : if state_idx < 0:
    if state_idx <= 0:
        return # 跳过无效条目,防止内存访问错误
    p_h0 = h0 + state_idx * stride_init_state_token
    # ... 继续代码
    if INPLACE_FINAL_STATE:
        # Load state index and check for invalid entries
        final_state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to(tl.int64)
        # Only store if state index is valid (not NULL_BLOCK_ID=0)
        # 修改前 : if final_state_idx >= 0:
        if final_state_idx > 0:
            p_ht = ht + final_state_idx * stride_final_state_token
            tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)

评论区精华

守卫条件修改的正确性验证 正确性

vadiklyutiy 询问 state_idx=0 是否可能是真实索引,担心修改可能跳过有效条目。Alberto-Codes 回应称 NULL_BLOCK_ID=0 是保留哨兵,真实序列不会分配索引 0,因此修改正确。

结论:基于 PR #35431 引入的约定,确认 NULL_BLOCK_ID=0 仅为填充值,守卫条件更新是安全的。 · 已解决

注释更新建议 documentation

MatthewBonanni 建议将注释从引用 PAD_SLOT_ID 更新为仅引用 NULL_BLOCK_ID,以反映当前代码约定。

结论:采纳建议,在提交中更新注释以明确使用 NULL_BLOCK_ID=0。 · 已解决

风险与影响

技术风险包括:

  1. 回归风险:守卫条件从 < 0 改为 <= 0 可能意外跳过 state_idx=0 的有效条目,但根据讨论,NULL_BLOCK_ID=0 是保留值,因此风险较低。
  2. 性能影响:守卫条件改变可能轻微增加分支开销,但修复了崩溃,总体影响可忽略。
  3. 兼容性风险:与旧版本使用 PAD_SLOT_ID=-1 的代码不兼容,但这是修复回归的必要更改。
  4. 安全风险:原 bug 导致非法内存访问,可能引发数据损坏或安全漏洞,修复后消除此风险。

对用户的影响:修复了 GDN 模型(如 Qwen3.5-35B-A3B)在高并发和 TP>1 时使用 CUDA 图的崩溃问题,提升服务稳定性。对系统的影响:涉及 FLA SSM 内核的核心路径,确保状态管理正确,避免内存损坏。对团队的影响:强调了对代码约定(如 NULL_BLOCK_ID)的一致遵循,并需注意跨模块的守卫条件更新。影响范围限于使用 CUDA 图和 FLA 内核的特定场景,但修复了关键崩溃。

核心路径变更 内存访问错误修复 约定一致性风险

关联 Issue

#39025 [Bug]: CUDA illegal memory access with CUDA graphs enabled under high concurrency (Qwen3.5-35B-A3B, tp=2)

完整报告

参与讨论