01 · 数学原理:ChunkGatedDeltaRule 在算什么
在看任何 AscendC 代码之前,必须先弄清楚算子的数学语义。否则 kernel 里每个循环为什么是三角形的、为什么有一个"递归修正"步骤、为什么 state 要转置,全都看不懂。
1. 算子的来源
ChunkGatedDeltaRule 是 Gated DeltaNet(门控 Delta 网络)类线性注意力的 chunkwise(分块)训练算子。它对应的推理/训练公式来自 DeltaNet 一脉的线性注意力模型(Gated DeltaNet,也是 Qwen3-Next 等模型使用的线性注意力层)。
与标准 softmax attention 不同,DeltaNet 维护一个循环状态矩阵 S,逐 token 递推更新:
拆开看这个递推的每一项:
| 记号 | 含义 | 对应算子输入 |
|---|---|---|
| 循环状态("记忆"矩阵) | initialState (FP16),最终写回 finalState | |
| key 向量 | key,布局 [T, Nk, dk] | |
| value 向量 | value,布局 [T, Nv, dv] | |
| 写入强度(决定这步"写多少"进记忆) | beta,布局 [T, Nv] | |
| 遗忘门(g 是 log 域衰减,负数,所以 | g(可选输入,FP32),布局 [T, Nv] | |
| query 向量(读记忆) | query,布局 [T, Nk, dk],带缩放 scale | |
actualSeqLengths | 每个 batch 的真实序列长度 | 变长序列支持 |
每一步 t:
- 用 query 读旧记忆:
(这就是"注意力"输出的一部分); - 计算"应该写入的增量":
(当前记忆能解释多少 ,补差值——这就是 Delta Rule 名字的由来); - 先遗忘(乘
),再按强度 把增量写进记忆。
输出:
out[t]= 该步的注意力输出(由读历史记忆 + 块内互注意力组成,见下文); finalState= 序列结束时的状态。
注意 [T, Nv]),而 [T, Nk, dk])。通常 nvPerNk = NV_/NK_、qkHead = head_i / nvPerNk 就是干这个的(见 04 篇)。
2. 为什么分块(chunkwise)?
朴素递推是 chunkSize=64,与 910B 版本对齐,是硬编码常量),然后:
- 块内:64 个时间步之间的"互注意力"用一个小矩阵乘一次性并行算出(递推在块内被展开成矩阵形式);
- 块间:块与块之间只传一个状态矩阵 S,仍然串行,但串行次数从 T 次降到 T/64 次。
这是 flash-linear-attention 系列的标准做法,块内矩阵大小 64×64,正好可以放进片上存储。
3. 块内数学:把递推展开成矩阵
对一个块内的 64 步递推做代数展开(这是整个 kernel 的数学骨架)。记块内第 chunkLen),定义:
(遗忘因子), 的块内前缀和 (代码里的 gCumsumFp32),跨步衰减(这就是 decayMaskFp32);( kCumdecayFp32的初值,Phase 2ComputeKBeta);( chunkVFp32的初值,Step 1LoadVBeta)。
3.1 块内互注意力矩阵 A(含 delta rule 的递归修正)
块内
即:直接衰减项 + 间接路径修正。代码里分两步实现:
- 先算"直接项"矩阵(Phase 4
ComputeAttnMatrix前半段):A[i][j] = -(k_beta[i] · k[j]) * decayMask[i][j],其中k_beta[i]·k[j]用向量点积DotFp32算出,乘上decayMask(即)取负。 - 再做递归修正(
ComputeRecursiveAttn):按行迭代,row_i += A[i][k] * row_k(k 从小到大),把间接路径补上。这是纯下三角递推,串行但每步是一条向量指令 Axpy。
最后对角线置 1(A[i][i] = 1):写入自己这一步的增量是完整保留的。
3.2 块内输出与状态更新(kernel 的 7 个公式)
有了 A 和
(1) k_cumdecay(Phase 5 ComputeKCumdecay,结果覆盖进 chunkKFp32):
含义:每步的 key 按块内衰减路径"衰减到块尾"之后的等效 key。它用于把旧状态的贡献从写入里扣除。
(2) 块内新值(Step 2 ComputeValueNew → chunkAttnOutFp32):
即块内经典的因果注意力输出(对角线为 1,所以包含"自己")。
(3) 减去旧状态贡献(Step 4 ComputeVNew → 覆盖回 chunkVFp32):
含义:递推里每步写入的是
(4) 跨块注意力(Step 5 ComputeAttnInter → 写入 chunkAttnOutFp32):
含义:第
(5) 最终输出(Step 6 AccumOutput,累加进 chunkAttnOutFp32):
准确写法:out = attn_inter + tril(QK^T ⊙ decay) · v_new。其中 tril(QK^T ⊙ decay) 就是 Phase 4 后半段重写进 decayMaskFp32 的矩阵(原来存
(6) 状态更新(Step 7 UpdateAndWriteState):
含义:旧状态衰减一个整块(
3.3 七个公式 ↔ kernel 函数对照表
| 数学 | kernel 函数 | 位置 | 产出缓冲 |
|---|---|---|---|
ComputeGCumsum | chunk_gated_delta_rule.h:486 | gCumsumFp32 | |
PrepareDecayAndExp | chunk_gated_delta_rule.h:514 | expGCumFp32、decayMaskFp32 | |
ComputeAttnMatrix + ComputeRecursiveAttn | chunk_gated_delta_rule.h:576/317 | chunkScoresFp32 | |
ComputeKCumdecay | chunk_gated_delta_rule.h:617 | 覆盖 chunkKFp32 | |
ComputeValueNew | chunk_gated_delta_rule.h:732 | chunkAttnOutFp32 | |
ComputeVNew | chunk_gated_delta_rule.h:789 | 覆盖 chunkVFp32 | |
ComputeAttnInter | chunk_gated_delta_rule.h:811 | 覆盖 chunkAttnOutFp32 | |
AccumOutput | chunk_gated_delta_rule.h:832 | 累加 chunkAttnOutFp32 | |
UpdateAndWriteState | chunk_gated_delta_rule.h:849 | stateInFp32 → GM |
建议:把这张表打印出来放在手边。读 05/06 两篇时,每进一个函数先回来看它对应哪个公式、输入输出是什么,就不会迷路。