Prhub

#33431 [Fix] Skip padded state slots in the chunked GDN kernel

原始 PR 作者 Oasis-Git 合并时间 2026-08-19 04:07 文件变更 1 提交数 5 评论 6 代码增减 +47 / -0

执行摘要

补 GDN chunk kernel 的 -1 哨兵索引回归测试,防越界崩溃

PR body 给出了完整的根因分析:_forward_metadata 会用 state index = -1 污染 padded request 行;decode kernel 已有 if idx >= 0 守卫,但 chunked extend kernel 没有,导致 -1 进入 initial_state + index * stride_init_state 的指针运算;boundary_check 只校验声明 block shape、不校验实际 allocation,因此拦不住。在 breakable-CUDA-graph prefill + DP attention 下,空闲 DP rank 会重放全 padded batch 的 extend,每一行都带 -1,kernel 以 cudaErrorIllegalAddress 崩溃;作者在 Qwen3-Next-80B 与 Qwen3.5-397B 上以单请求 --dp 4 即可复现,且指出 per-commit 套件没有覆盖 linear-attn hybrid 与 DP attention 的组合,所以问题此前未被发现。

值得精读:PR body 的根因分析是理解线性注意力内核 padded 批处理边界的优质样本,尤其是 boundary_check 局限与 idle DP rank 重放场景的还原。测试设计有可借鉴处:mixed 与 all_padded 双形态 + 强制同步暴露越界 + pool 未触碰断言三者配合,能同时捕获崩溃与静默写坏两类回归。建议后续跟踪:#33810 的源码修复需保持与本测试同步;可考虑在 nightly 多 rank 套件中增加 DP + linear-attn 的真实重放用例,弥补 per-commit 单 GPU 无法覆盖的场景。

讨论亮点

本 PR 没有实质 review 讨论(仅 BBuf 的 APPROVED 空评,review_comments = 0),最有价值的信息分散在 commit 消息与 body 中:

1) commit 4183cce 明确说明作者发现 upstream db8f3cdd11(#33810)已落地等效 guard(valid_state = index >= 0,同时作用于 initial-state load 与 in-place store),因此主动放弃本地源码补丁、采用上游形式,这是“上游已修、本地收敛为回归测试”的典型协作模式;
2) body 完整解释了 boundary_check 为什么拦不住——它只校验声明的 block shape,不校验实际 allocation;
3) issue 侧只有 CI 触发与结果链接,最终 “CI Passed”。

实现拆解

1. 根因定位(来自 PR body 的分析)

_forward_metadata 对 padded request 行写入 state index = -1 作为哨兵;decode 路径的 fused_recurrent 已有 if idx >= 0 守卫,但 chunked extend 路径(chunk_delta_h.py)缺失,导致 -1 直达 initial_state + index * stride_init_state 指针运算,同时影响初始 state 读取与就地 final-state 写入。boundary_check 只对照声明的 block shape 校验,无法识别这种“值在类型上合法、但指向 allocation 之外”的访问。

2. 触发场景还原

breakable-CUDA-graph prefill 与 DP attention 组合下,空闲 DP rank 会重放一个全 padded batch 的 extend,每行均携带 -1,kernel 崩溃为 cudaErrorIllegalAddress。body 明确给出复现条件:Qwen3-Next-80B 与 Qwen3.5-397B,单请求配合 --dp 4 即可在 pristine main 上复现。

3. 修复与上游对齐

首个 commit 在源码侧加守卫;后续 merge 中作者发现 upstream 以 db8f3cdd11(PR #33810)落地了等效修复,将检查提升为 valid_state = index >= 0 并同时作用于 initial-state load 与 in-place store。为不与上游分叉,本分支对 chunk_delta_h.py 不再做改动,直接采用上游形式。

4. 回归测试落库(本 PR 最终变更)

test/registered/attention/test_chunk_gated_delta_rule.py 新增 test_padded_state_index_is_skipped(+47/-0):构造 mixed [0, -1, 2, -1] 与 all_padded [-1] * B 两种 cache_indices 分别执行 chunked GDN kernel;通过 torch.isfinite(o.float()).all() 强制同步以暴露越界访问,并断言未命中的 state pool 行保持初值,防止 -1 被当作有效索引静默写坏 pool。

5. 配套验证

无配置、schema、部署配套改动;PR Test 与 PR Test Extra 均通过,作者在 issue 侧记录 “CI Passed”。

文件 模块 状态 重要度
test/registered/attention/test_chunk_gated_delta_rule.py 内核测试 modified 5.18

关键符号

test_padded_state_index_is_skipped

关键源码片段

test/registered/attention/test_chunk_gated_delta_rule.py test-coverage

本 PR 的最终唯一变更文件,新增 `test_padded_state_index_is_skipped` 回归测试,验证 chunked GDN kernel 对 -1 哨兵 state 索引的跳过行为,防止越界崩溃与 state pool 静默写坏。

def test_padded_state_index_is_skipped(self):
    """Rows carrying state index -1 must be skipped, not addressed.    Without the guard the sentinel reaches pointer arithmetic and
    addresses before the state pool.
    """
    device = get_device()
    dtype = torch.bfloat16
    B, T_per_seq, H, K, V, pool_size = 4, 64, 4, 128, 128, 8
    T = B * T_per_seq
    torch.manual_seed(0)
​
    # 初始化 state pool:chunked extend kernel 会按 cache_indices 逐行做
    # initial_state + index * stride_init_state 的指针运算,-1 会指向 pool 之前的
    # 内存,修复前表现为 cudaErrorIllegalAddress 或静默写坏相邻 allocation。
    pool_init = (
        torch.randn(pool_size, H, V, K, dtype=torch.float32, device=device) * 0.1
    )
​
    # B 条等长序列,cu_seqlens 用于让 kernel 按块切分元组。
    cu_seqlens = torch.zeros(B + 1, dtype=torch.long, device=device)
    cu_seqlens[1:] = (
        torch.arange(1, B + 1, dtype=torch.long, device=device) * T_per_seq
    )
​
    # 随机输入:q/k/v 为注意力张量,g 与 beta 为 GDN 的门控系数。
    q = torch.randn(1, T, H, K, dtype=dtype, device=device)
    k = torch.randn(1, T, H, K, dtype=dtype, device=device)
    v = torch.randn(1, T, H, V, dtype=dtype, device=device)
    g = torch.nn.functional.logsigmoid(
        torch.randn(1, T, H, dtype=dtype, device=device)
    )
    beta = torch.sigmoid(torch.randn(1, T, H, dtype=dtype, device=device))
​
    # mixed 覆盖部分 padded 的常规形态;all_padded 对应 idle DP rank 在
    # breakable-CUDA-graph 重放时的极端形态,修复前必现崩溃。
    for label, indices in (("mixed", [0, -1, 2, -1]), ("all_padded", [-1] * B)):
        with self.subTest(label):
            cache_indices = torch.tensor(indices, dtype=torch.int32, device=device)
            o, pool = self._run_chunk(
                pool_init, cache_indices, q, k, v, g, beta, cu_seqlens
            )
​
            # 强制同步:越界访问会在这一行以 CUDA error 暴露。
            self.assertTrue(torch.isfinite(o.float()).all())
​
            # 关键断言:padded 行既不能通过 -1 索引读写 pool,
            # 未命中的行必须保持 pool_init 的原始值。
            untouched = [s for s in range(pool_size) if s not in indices]
            self.assertTrue(
                torch.equal(pool[untouched], pool_init[untouched]),
                f"{label}: padded rows wrote into the state pool",
            )

评论区精华

上游 #33810 已落地等效 guard,本 PR 收敛为纯回归测试 other

commit 4183cce 说明:upstream 以 db8f3cdd11(#33810)落地了等效修复,将检查提升为 valid_state = index >= 0,并同时应用于 initial-state load 与 in-place store;作者采纳上游形式,本 PR 不再改动 chunk_delta_h.py,仅保留 main 缺失的回归测试。

结论:源码修复统一采用上游实现,本 PR 最终只提交 +47/-0 的测试变更。 · 已解决

风险与影响

原始缺陷(已由上游 #33810 修复)的风险集中在 chunk_delta_h.py:padded 行的 -1 哨兵直接参与 initial_state + index * stride_init_state 指针运算,同时影响 initial-state load 与就地 final-state store,会寻址到 state pool 之前的内存,表现为 cudaErrorIllegalAddress 或静默写坏相邻 allocation;boundary_check 只对照声明的 block shape 校验,无法防护 allocation 之外的访问。残余风险:本 PR 的回归测试运行在 base-b(1-GPU)per-commit 套件中,无法直接还原 idle DP rank 重放全 padded extend 的多 rank 真实验证路径,DP attention 与 linear-attn 的组合覆盖仍是盲区。本 PR 自身为纯测试新增(+47/-0),无运行时风险,但依赖 get_device() 提供的 CUDA/bfloat16 环境。另需注意:由于本 PR 最终不含源码改动,我无法在此 PR 内直接核对 #33810 守卫的实现细节,建议结合该 PR 确认守卫同时覆盖 initial load 与 in-place store。

用户影响:为 Qwen3-Next-80B、Qwen3.5-397B 等 GDN/线性注意力模型在 --dp 4 等 DP attention 配置下的稳定性提供回归防线,防止单请求即崩溃的故障复发。系统影响:避免 padded 行以 -1 索引静默写坏 state pool,保护后续 decode 阶段的中间状态正确性。团队与 CI 影响:新增测试进入 base-b(1-GPU,per-commit)内核测试套件,补上此前“linear-attn hybrid + DP attention 无任何 per-commit 覆盖”的空白;影响面集中在 attention 内核测试领域,不涉及 serve 路径、配置或部署。

-1 哨兵进入指针运算 boundary_check 不校验 allocation DP + linear-attn CI 覆盖盲区 breakable-CUDA-graph 重放路径 上游已修复,本 PR 仅补测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论