对应论文 §2.1.1 的前半,公式 (1)、(2) 和 (6)。K3 的 93 层里有 69 层是 KDA,它来自 Kimi 团队 2025 年 10 月的 Kimi Linear。这一篇只讲递推形式,也就是「一个 token 进来,状态怎么变」;chunkwise 并行和 K3 的下界衰减放到下一篇。
序列 · KDA序列 · Gated MLA深度 · AttnRes宽度 · Stable LatentMoE输入 · MoonViT-V2零件
你现在在这里:每个重复块里的三层 KDA,以及第 1 层。
为什么要线性注意力
softmax 注意力的代价是 KV cache 随上下文线性增长:1M 上下文下,即便用 MLA 压到每 token 每层 576 个数,24 层也要 27 GB 的 BF16 cache,每生成一个 token 都要全读一遍。
线性注意力能把这个 cache 压成每头一个 128×128 的矩阵,原因只有一步改写:
- 两种注意力都是 ot=∑i≤twt,ivi,区别只在权重。softmax 的权重是 exp(qt⋅ki) 再归一化,线性注意力直接用点积 wt,i=qt⋅ki 当权重,不取指数,也不归一化。
- 线性的一项 (qt⋅ki)vi 可以改写成 qt⊤(kivi⊤)。qt 被提到了括号外面,括号里只依赖 token i,于是求和可以挪进括号:ot=qt⊤∑i≤tkivi⊤=qt⊤St。每个 token 到来时把自己的外积加进 S,不需要知道以后谁来查。
- softmax 做不到这一步:exp(qt⋅ki) 拆不成 f(qt)⊤g(ki),分母又是对所有 j 求和,qt 没法从权重里提出来,只能等 qt 来了再和每个 key 算一次。这就是 KV cache 存在的原因。
softmax:query 在权重里面分母:也含,且要遍历全部的指数和分母都依赖,求和挪不进括号只能等来了再对每个算一次,所以要一直存着这就是 KV cache线性:query 在求和外面128 × 128128 × 128128 × 128128 × 128每个 token 到来时把自己的外积加进去,不必知道以后谁来查16,384 个滑动求和本身不存在任何地方只和连一根线,读出精确,每步代价固定同样是对过去的 value 加权求和。softmax 的权重里有 query,求和只能等 query 来了再做,所以每个 key、value 都要留着;线性注意力的一项 (qt⋅ki)vi 可以写成 qt⊤(kivi⊤),括号里只依赖 token i,可以提前累加。
代价是记忆有损:128 维空间里最多只有 128 个两两正交的方向,序列超过 128 个 token 后,key 不可能两两正交,读出来的是若干相近的 key 对应的 value 混在一起。
St∈Rdk×dv 就是这一族模型的记忆,读出是 ot=St⊤qt。对一张固定大小的表,能做的操作只有三种:写入一条新记录;先擦除某个 key 下的旧记录再写入;让所有记录随时间衰减。从线性注意力到 KDA 的各种变体,区别就在于用了这三种操作里的哪几种、衰减做到多细。下文就用写入、擦除、衰减指代它们。
一步在线学习:公式 (1) 从哪来
S 是一张查找表:行是 key 通道,列是 value 通道,用 q 去查得到 S⊤q。第 t 个 token 带来一对 (kt,vt),意思是「以后拿 kt 来查,应该查到 vt」。Kimi Linear(Table 7)用一个统一的视角回答「S 该怎么变」:写一个只看当前 token 的损失 Lt(S),做一步梯度下降,步长取 1:
St=St−1−∇SLt(St−1).
等号右边只有 St−1 和当前的 (kt,vt),所以这就是一个 RNN,标题里的「递推形式」指的就是它。三种操作各对应损失里的一项,梯度对 S 都是线性的,所以每一步都能整理成 St=AtSt−1+Bt:
| 操作 | 目标项 | 梯度 ∇SLt | 落在递推里的位置 |
|---|
| 写入 | −⟨S⊤kt,vt⟩ | −ktvt⊤ | Bt=ktvt⊤ |
| 擦除 | 2βt∥S⊤kt−vt∥2 | βtktkt⊤S−βtktvt⊤ | At 里减 βtktkt⊤,Bt=βtktvt⊤ |
| 衰减 | 21∥Diag(1−αt)S∥F2 | Diag(1−αt)S | At 里乘 Diag(αt) |
写入的目标对 S 是线性的,没有最小值,只会不断累加。擦除把它换成回归目标,最小值处 S⊤kt=vt 精确成立;它的梯度里已经带了写入项,所以不需要再单独加写入那一项。衰减是把 S 往零拉的正则项,αt 取标量就是所有通道同速衰减,取向量就是每个 key 通道各有自己的保留率。
KDA 用的是擦除加逐通道衰减,并且先衰减,再对衰减后的状态做擦除和写入:
St=(I−βtktkt⊤)Diag(αt)St−1+βtktvt⊤,o~t=St⊤qt,(K3 Eq. 1)
其中 αt∈(0,1)dk,βt∈(0,1)。先衰减再擦除,意思是擦除的量按衰减后剩下的状态算;和直接把两项梯度加起来相比,差了一项 βt(1−αt)ktkt⊤St−1。GDN 和 KDA 用的都是这个顺序,下一篇的 chunkwise 形式依赖它。
三项梯度的完整推导
对矩阵求梯度只用一个办法:把损失展开成对每个元素 Sij 的求和,对 Sij 求偏导,再按 (i,j) 摆回矩阵。反复用到 (S⊤k)j=∑iSijki。
写入。
Lt(S)=−⟨S⊤kt,vt⟩=−j∑(i∑Sijkt,i)vt,j,∂Sij∂Lt=−kt,ivt,j,∇SLt=−ktvt⊤.代回梯度步:St=St−1+ktvt⊤,即 At=I,Bt=ktvt⊤。这是 Hebb 学习规则,也是最早的线性注意力。
擦除。 记残差 e=S⊤kt−vt,ej=∑iSijkt,i−vt,j:
Lt(S)=2βtj∑ej2,∂Sij∂Lt=βtejkt,i⇒∇SLt=βtkte⊤=βtktkt⊤S−βtktvt⊤.第二个等号是把 e⊤=kt⊤S−vt⊤ 代进去。代回梯度步并把含 St−1 的项归到一起:
St=At(I−βtktkt⊤)St−1+Btβtktvt⊤.同一个式子的三种读法:只写入残差 βtkt(vt−St−1⊤kt)⊤,记忆已经答对就什么都不写,这是 delta rule 名字的来源;先把 kt 方向上原来存的东西擦掉 βt 的比例再写新的,这是「先擦除再写入」;∥kt∥=1 时 I−βtktkt⊤ 把 kt 方向上的分量缩到原来的 1−βt 倍,其他方向不变,βt=1 时完全替换。
衰减。 正则按元素独立,逐通道版本第 i 行有自己的系数:
Lt(S)+=21ij∑(1−αt,i)Sij2,∂Sij∂Lt=(1−αt,i)Sij,∇SLt=Diag(1−αt)S.代回梯度步:S−Diag(1−αt)S=Diag(αt)S。标量版本把 αt,i 换成同一个 αt 即可。让 αt 依赖输入,模型就能逐 token 决定「前面的东西还要不要」,这就是 Mamba 系列强调的选择性(selectivity)。
合起来。 把擦除和衰减放进同一个目标,梯度相加,一步走完:
St=St−1−(βtktkt⊤St−1−βtktvt⊤)−Diag(1−αt)St−1=(Diag(αt)−βtktkt⊤)St−1+βtktvt⊤.GDN 和 KDA 实际用的是串行版本:令 S~t−1=Diag(αt)St−1,目标是 2βt∥S~t−1⊤kt−vt∥2,更新 St=(I−βtktkt⊤)S~t−1+βtktvt⊤,展开即公式 (1)。两者差一项 βt(1−αt)ktkt⊤St−1。
谱系:每一步加了什么
衰减路径:只加衰减,没有擦除擦除路径:先加 delta rule旁支+ 标量衰减内积目标 → 回归目标(擦除)标量 → 逐通道+ 标量衰减+ delta rule(擦除)标量 → 逐通道隐式解擦除 ×放开绑定线性注意力 2020线性注意力 2020只写入,目标无下界RetNet 2023 · Mamba2 2024RetNet 2023 · Mamba2 2024每头一个标量Mamba2:三个合成任务全部失败DeltaNet 2021DeltaNet 2021沿方向先擦除再写入Longhorn 2024Longhorn 2024一步 SGD 换成闭式解GLA 2023 · HGRN2 2024GLA 2023 · HGRN2 2024每个 key 通道一个Gated DeltaNet 2024Gated DeltaNet 2024先衰减,再擦除,再写入收敛慢于 KDAComba 2025Comba 2025擦除比写入弱(),读出用KDA 2025KDA 2025先逐通道衰减,再擦除,再写入RWKV-7 2025RWKV-7 2025一般 DPLR:擦除 key、写入 key各不相同衰减路径:只加衰减,没有擦除擦除路径:先加 delta rule+ 标量衰减→ 回归目标(擦除)标量 → 逐通道+ 标量衰减+ delta rule(擦除)标量 → 逐通道隐式解擦除 ×放开绑定线性注意力 2020线性注意力 2020只写入,目标无下界RetNet 2023 · Mamba2 2024RetNet 2023 · Mamba2 2024每头一个标量Mamba2:三个合成任务全部失败DeltaNet 2021DeltaNet 2021沿方向先擦除再写入Longhorn 2024Longhorn 2024一步 SGD 换成闭式解GLA 2023 · HGRN2 2024GLA 2023 · HGRN2 2024每个 key 通道一个Gated DeltaNet 2024Gated DeltaNet 2024先衰减,再擦除,再写入收敛慢于 KDAComba 2025Comba 2025擦除比写入弱(),读出用KDA 2025KDA 2025先逐通道衰减,再擦除,再写入RWKV-7 2025RWKV-7 2025一般 DPLR:擦除 key、写入 key各不相同
从线性注意力出发有两条路:左边只加衰减,先标量后逐通道,始终没有擦除;右边先加擦除,再补上衰减。两条路在 KDA 汇合。图里已经写了每个节点的递推式,下面每个节点只说它改了什么、还缺什么。
线性注意力
Katharopoulos 等,2020。只有写入,没有擦除和衰减。它说明 softmax 注意力可以换成固定大小的状态加递推,但旧的 key-value 关联只增不减、互相干扰,长上下文比 softmax 差很多。后面每个模型都是在补这个目标函数的缺陷。
RetNet 和 Mamba2
加上衰减,标量粒度:St=αtSt−1+βtktvt⊤。RetNet(2023)的 α 是每头一个固定常数,一个头一个时间尺度。Mamba2(2024)的 αt=exp(−ΔtAh) 由输入决定,写入项也乘同一个步长,不重要的 token 可以既不衰减旧状态、也不写入。两者都没有擦除:k 方向上原来存的东西只能等着淡出,不能被覆盖。下一节的合成任务里 Mamba2 全部失败,问题就出在这里。
GLA 和 HGRN2
衰减从标量变成逐通道:St=Diag(αt)St−1+ktvt⊤,αt∈(0,1)dk。GLA(2023)的 αt 来自低秩投影加 sigmoid,这个参数化被 KDA 直接沿用。HGRN2(2024)把写入门和遗忘门绑在一起,key 换成 1−αt,少一组参数。仍然没有擦除。
DeltaNet
Schlag 等 2021 提出,Yang 等 2024 给出并行形式。它不是在目标里加一项,而是把目标换成回归目标,也就是上一节的擦除。(I−βtktkt⊤) 是一个广义 Householder 变换,后面 chunkwise 并行全靠它。能精确地覆盖一个 key 下的旧 value,但没有任何衰减:除非同一个 key 再来一次,写进去的东西永远留在 S 里。
Longhorn
DeltaNet 的一个变体(2024)。不做一步梯度下降,而是把带正则的单步问题精确解出来,结果是 βt 换成 βt/(1+βtkt⊤kt),好处是 βt 不用再限制在 (0,1)。没有衰减。
Gated DeltaNet
Yang 等 2024。DeltaNet 加上标量衰减,顺序是先衰减、再擦除和写入。αt 每头一个,沿用 Mamba2 的参数化;βt 用 sigmoid。这是第一个同时有擦除和衰减的模型,Kimi Linear 的所有消融都拿它当基线。剩下的问题是一个头的 128 个 key 通道以同一个速率衰减。
Comba
GDN 的一个变体(2025)。擦除强度乘一个学习到的标量 b∈(0,1),让擦除比写入弱一些;读出前把 query 减去 d 倍的当前 key。衰减仍是每头一个标量。Kimi Linear 的 chunkwise 推导借用了它对转移矩阵连乘的写法,下一篇会碰到。
KDA
Kimi Linear,2025。把 GDN 的标量换成对角矩阵,或者说给 GLA 加上 delta rule,即公式 (1)。逐通道衰减的理由来自位置编码:RoPE 好用,是因为每个维度有自己的旋转频率,标量衰减让所有维度共用一个速率。下面「转移矩阵的连乘提供位置信息」一节展开。
RWKV-7
和 KDA 同期、思路最接近的工作(2025):同样是逐通道衰减加擦除,但擦除用的 key κ^t、写入用的 key k~t 和逐通道学习率 at 各自独立,转移矩阵是一般的「对角加低秩」D−ab⊤。KDA 是 a、b、写入用的 key 全绑在 kt 上的特例,表达能力弱一些,换来内核少算一半,下一篇讲。
一句话总结
按「有没有擦除」和「衰减的粒度」两个维度排开:
| 无衰减 | 标量衰减 | 逐通道(对角)衰减 |
|---|
| 无 delta rule | 线性注意力 | RetNet、Mamba2 | GLA、HGRN2 |
| 有 delta rule | DeltaNet、Longhorn | Gated DeltaNet、Comba | KDA、RWKV-7 |
三个合成任务:擦除和逐通道衰减各带来了什么
语言模型的 PPL 只差零点几,看不出每个设计各起了什么作用。Kimi Linear §5.1 用三个合成任务分别考察状态的三种能力(2 层 2 头、头维 128,序列长度 256 到 2048,比较 KDA、GDN 和 Mamba2):
| 任务 | 例子 | 考的能力 | 依赖的操作 |
|---|
| Palindrome | a b c d e | → e d c b a | 把每个 token 分别存下、按位置精确取回 | key 之间互不干扰,只叠加和衰减的记忆读出来是加权和,长了就分不开 |
| MQAR | A 4 · B 7 · C 2 · … · A ? → 4 | 同时保持很多组 k→v 绑定,同一个 key 再来要覆盖旧 value | 擦除:查出来不等于 v 就把差值写进去 |
| Stack | push 3 a · push 3 b · pop 3 → b · pop 3 → a | 同一个 key 下反复改写,pop 后露出上一层 | 擦除:没有擦除,pop 3 的两次回答不可能不同 |
结果:KDA 在三个任务、所有长度上准确率最高,在 Palindrome 和 MQAR 上收敛明显快于 GDN;Mamba2 三个任务全部失败。对着更新规则看:Mamba2 和 KDA 的差别是有没有擦除,Mamba2 全部失败,说明擦除是精确回忆和状态跟踪的前提;GDN 和 KDA 的差别只在衰减粒度,GDN 收敛慢,说明逐通道衰减带来的是收敛速度。这两条结论正好对应谱系图上汇入 KDA 的两条边。
公式 (1) 的图解:衰减和擦除分别删掉什么
上一节的合成任务说明擦除和衰减各自带来了不同的能力。这一节回到公式 (1) 本身,看这两个操作在状态矩阵 S 上分别动了什么。
新状态② 擦除① 衰减旧状态③ 写入=··+从右往左读:先把旧状态每一行按各自衰减,再把落在方向上的内容擦掉的比例,最后写入倍的新关联。读出:。行 = key 通道(,各自的衰减率),列 = value 通道()。
公式 (1) 一步做三件事,顺序固定:先衰减(把 St−1 每一行乘上 αt 里对应的数),再擦除(减掉与新 key kt 相关的旧内容),最后写入(加上新的 key-value 对 ktvt⊤)。把三步合到一起:
St=(Diag(αt)−βtktkt⊤Diag(αt))St−1+βtktvt⊤.
括号里是作用在旧状态上的转移矩阵,形如「对角矩阵减一个秩 1 矩阵」,文献里叫 DPLR(diagonal plus low-rank):D−atbt⊤,其中 D=Diag(αt),at=βtkt,bt=kt⊙αt。注意两个低秩向量 at、bt 都由同一个 kt 算出来,通用 DPLR 里它们是两个独立向量。这个共享是 KDA 内核比通用 DPLR 内核快约两倍的原因,下一篇细说。
衰减和擦除删的不是同一种东西
公式里有两处在「减」:衰减把 S 整体缩小,擦除减掉一个秩 1 项。既然已经有擦除,为什么还要衰减?因为它们要删的东西不同。
衰减删的是过时的信息。 把 S 想成一张固定大小的表,每个 token 都往里写一条,表不会变大,所以旧的记录必须慢慢变淡,才能给新的腾出位置。另外很多信息本来就有保质期:代码里一个变量被重新赋值,之前的值就没用了;一个子任务做完、话题换了,相关的记录也可以丢。衰减做的就是让旧记录随时间变淡,αt 由当前 token 决定,所以「这一步淡多少」是模型自己学出来的。它只看时间先后,不看内容:写得越早,被乘的衰减因子越多,淡得越厉害。这个「越早越淡」的副产品是一种位置信息,下一节展开。
擦除删的是和新 key 冲突的旧 value。 所有 token 共用同一张表,两个 key ki 和 kt 如果方向很接近,它们的 value 就叠在表的同一片区域里,之后用这个方向去查,查出来是两个 value 的混合。擦除项针对的正是这种情况。kt⊤St−1 的意思是「用新 key 去查一遍旧表,看它现在会答出什么」,然后减去 βtkt(kt⊤St−1),就是在写入新 value 之前,先把旧表对这个 key 的回答删掉,βt 控制删多彻底。和 kt 方向无关的那些行不受影响。这件事衰减做不到:衰减对整张表按时间均匀变淡,不知道哪一条记录和新 key 冲突。
简单说:衰减按写入时间删,越早写入的删得越多;擦除按内容删,只删和新 key 方向相近的那部分。
衰减为什么要按行做
还有一个容易糊涂的细节:Diag(αt) 是左乘在 S 上的,也就是按行缩放。S 的行对应 key 的维度(dk 个通道),列对应 value 的维度。按行缩放的意思是,第 j 个 key 通道下面存的所有 value 一起乘 αt,j。
为什么是行不是列?读出时算的是 S⊤q,query q 也是沿 key 的维度乘进去。所以 key 写入时用哪些分量,query 就从哪些分量取。把某一行变淡,等于削弱「key 的方向接近这个分量」的所有记录,这才是「忘掉某些 key 下面存的东西」。如果改成按列缩放,缩的是所有记录的同一个输出坐标,那不是忘记,而是把输出的某个维度整体调小。
从内核的张量形状也能看出这一点:状态 S 的形状是 [B,H,K,V],门 g 的形状是 [B,T,H,K],只有 key 轴 K,没有 value 轴 V。
按行衰减:列:value 通道,的第个分量行:key 通道的第个分量沿 key 轴进入按列衰减:列:value 通道,的第个分量行:key 通道的第个分量沿 key 轴进入第行是「方向接近第个 key 分量的 key」写进来的全部 value淡这一行 = 忘掉这一类 key 下面存的东西这是遗忘。门的形状:每个 key 分量一个门第列是所有存储 value 的第个坐标,不管是哪个 key 写的淡这一列 = 每个读出向量的第维一起缩小这不是遗忘,是改输出尺度。没有轴的门S 的两个轴各由 key 和 value 的 128 个分量索引,读出 S⊤q 沿 key 轴乘进去,所以 query 读取的和 key 写入的是同一组分量。只有沿 key 轴衰减,才对应「忘掉某一类 key 下面的记录」。
转移矩阵的连乘提供位置信息
K3 的 24 层 MLA 全是 NoPE,整个模型里没有 RoPE。不带位置信息的注意力层分不清「A 在 B 前面」和「B 在 A 前面」,那顺序从哪来?从 KDA 的递推里来。
记每一步的转移矩阵 Tj=(I−βjkjkj⊤)Diag(αj),从 S0=0 展开公式 (1) 再用 qt 读出:
St=i=1∑t(TtTt−1⋯Ti+1)βikivi⊤,o~t=St⊤qt=i=1∑tst,i(qt⊤(Tt⋯Ti+1)ki)βivi.
这和注意力的形状完全一样:对每个历史位置 i 算一个分数 st,i,给 vi 加权求和。区别在于 qt 和 ki 中间夹着一串矩阵,隔得越远夹得越多。RoPE 做的是同一件事:q~t=Rtqt、k~i=Riki,打分时
st,i=qt⊤(Rt)⊤Riki=qt⊤(j=i+1∏tR−1)ki,
也是中间夹一串矩阵,只不过每一个都是同一个 R−1。
KDA数据相关、非正交、逐通道衰减⋯RoPE固定、正交、逐维频率⋯:每一格由第个 token 决定,颜色深浅示意该步保留了多少。:同一个分块旋转矩阵累乘次,只和距离有关,和内容无关。
两条链并排看,差别有三处:
- RoPE 链上的每个矩阵都一样,固定不变;KDA 链上的第 j 个矩阵由第 j 个 token 的 αj、βj、kj 决定,随输入变化,可学习。
- RoPE 的 R 是正交矩阵,分数随距离震荡但不衰减;KDA 的 Diag(αj) 每个元素都小于 1,分数随距离衰减,天然偏向近处的 token。
- RoPE 每个维度有自己的频率,低频管远距离、高频管近距离;GDN 的标量 αj 相当于所有维度共用一个频率,KDA 的逐通道 αj 才对应 RoPE 的逐维频率。Kimi Linear 就是从这里出发把 GDN 的门换成了逐通道。
这就解释了 K3 的两个决定:每层 MLA 前面都有三层 KDA 往残差流里写入带位置信息的内容,所以 24 层 MLA 全部 NoPE;也不存在需要为长上下文重调的 RoPE 频率基或 YaRN 插值,K3 §3.4 从 8K 扩到 1M 不改任何位置编码参数。Kimi Linear Table 5 给了反面证据:MLA 层加回 RoPE 的变体在短上下文上持平,128K 的 RULER 从 84.3 掉到 78.8。第 3 篇讲 MLA 时再展开 NoPE 的工程收益。
参数化:从 xt 到 q,k,v,α,β
论文公式 (2),每个头 h:
qth,kthvthβthzth=L2Norm(Swish(ShortConv(Wq/khxt)))∈Rdk=Swish(ShortConv(Wvhxt))∈Rdv=Sigmoid(Wβhxt)∈(0,1)=Wα↑Wα↓xt+bαh∈Rdk
7168Linear7168 → 12288ShortConv(4) + SwishL2Norm逐头96 × 128Linear7168 → 12288ShortConv(4) + SwishL2Norm逐头96 × 128Linear7168 → 12288ShortConv(4) + Swish96 × 128Linear7168 → 96Sigmoid96 个标量Linear7168 → 128Linear128 → 12288有下界,96 × 128Linear(满秩)7168 → 12288SigmoidKDA 递推96 头 × (128 × 128)训练 / prefill:chunkwisedecode:逐 token 递推逐头 RMSNorm128 维,每头独立⊙输出门 96 × 128,Linear12288 → 71687168一层 KDA。粉色的那一行是 K3 的两个改动所在:衰减映射换成有下界的 scaled sigmoid,输出门从低秩改成满秩。
维度按 K3 的 d=7168、96 头、dk=dv=128:
- ShortConv。 depthwise 因果卷积(每个通道单独做卷积),核大小 4(
short_conv_kernel_size),给每个 token 的 q、k、v 混入前 3 个 token 的信息。Kimi Linear 的消融:去掉它 PPL 从 5.65 变 5.70。decode 时每层要为 q、k、v 各缓存最近 3 个 token 的投影,这是 KDA 除了 S 之外唯一的状态。
- q、k 的 L2 归一化。 让 ∥kt∥=1,I−βtktkt⊤ 的特征值落在 [1−βt,1],递推在数值上不会发散。
- β。 7168 → 96 的线性层加 sigmoid,每头一个标量,代码里叫
b_proj。
- α 的 logit z。 低秩投影 7168 → 128 → 12288(
f_a_proj、f_b_proj),加一个 12288 维偏置(dt_bias),每个头每个 key 通道一个 logit。从 logit 到 (0,1) 的映射,Kimi Linear 用 GDN 的负 softplus,K3 换成有下界的 scaled sigmoid,另有每头一个的可学习 log 尺度(A_log)。这是 K3 的改动之一,动机在 chunkwise 形式里,放到下一篇。
输出:逐头 RMSNorm、门、投影
递推读出 o~t=St⊤qt 之后,论文公式 (6):
yt=Wo[Sigmoid(Wgxt)⊙RMSNorm(o~t)].
RMSNorm 逐头,在 128 维上归一化;sigmoid 门作用在归一化后的每个通道上;然后 Wo(12288 → 7168)。代码里归一化和门融在一个 FusedRMSNormGated(activation='sigmoid') 里。
K3 的改动:门从低秩改满秩。 Kimi Linear 的门是 7168 → 128 → 12288 的低秩投影,论文自己说了原因:为了和基线做公平的参数量对比,并且「和满秩门性能相当」。K3 不需要这个约束,直接用 7168 → 12288 的满秩 Wg(use_full_rank_gate = true)。MLA 的输出门也一起改成满秩,第 3 篇会看到同样的公式。
# KimiDeltaAttention.forward 的收尾(HF modeling_kimi_linear.py)
if self.use_full_rank_gate:
g = self.g_proj(hidden_states) # 7168 -> 12288
else:
g = self.g_b_proj(self.g_a_proj(hidden_states)) # 7168 -> 128 -> 12288(Kimi Linear)
g = rearrange(g, '... (h d) -> ... h d', d=self.head_dim)
o = self.o_norm(o, g) # 逐头 RMSNorm,再乘 sigmoid(g)
o = self.o_proj(rearrange(o, 'b t h d -> b t (h d)'))
为什么是 sigmoid 门:Kimi Linear 的消融里 sigmoid 门 PPL 5.65,无门 5.67,GDN 默认的 Swish 门 5.81。sigmoid 的输出在 (0,1) 之间,接近 0 时相当于把这个通道关掉。这个设计引用的是 Qwen 团队的 gated attention 工作:门带来非线性和稀疏性,还能缓解 attention sink。
每层的状态有多大
一层 KDA 在 decode 时携带的状态:
| 状态 | 形状 | 数量 |
|---|
| S | 96 头 × 128 × 128 | 1,572,864 |
| 三路 ShortConv 的窗口 | 3 × 12288 × 3 个 token | 110,592 |
约 1.68M 个数,BF16 下约 3.4 MB,69 层约 232 MB,和上下文长度无关。对照 MLA:每 token 每层 576 个数,24 层 1M 上下文约 27.6 GB。KDA 全部 69 层的状态相当于大约 8K 个 token 的 MLA cache。完整的对比在第 3 篇。
术语坑
- α 在两篇论文里是同一个东西,Kimi Linear 写 αt=f(⋅) 没给 f,K3 把映射写全了(公式 (5))并且改了它。
- β 是学习率还是样本权重。 DeltaNet 原文把 βt 写成 SGD 的学习率,Kimi Linear Table 7 把它放进目标函数、步长取 1。两种写法得到同一个递推,本文跟 Table 7。
- 头数和头维。 KDA 和 MLA 共用
num_attention_heads = 96、head_dim = 128,但 MLA 的 query 和 key 实际是 192 维(128 加 64),第 3 篇解释。
- 行向量还是列向量。 论文按 S∈Rdk×dv 写,读出是 S⊤q;RWKV-7 和 Comba 原文用行向量约定,转移矩阵写在 St−1 右边,本文全部转成了列向量约定。FLA 内核里
transpose_state_layout=True 存的是转置,只影响内存布局。
- 打分式里因子的顺序。 Kimi Linear 公式 (12) 把每一步的转移写成 Aj(I−βjkjkj⊤),先擦除再衰减;本文按公式 (1) 写成 (I−βjkjkj⊤)Diag(αj),先衰减再擦除。两个矩阵不相等,真正实现的是公式 (1) 的顺序。
下一篇
递推形式一个 token 一步,训练时没法用 Tensor Core。下一篇讲 Kimi Linear 的 chunkwise 并行形式(WY 表示、UT 变换、块间递推加块内并行),然后就能看懂 K3 为什么要给衰减加一个 −5 的下界,以及这个改动怎么把对角 tile 也搬上 Tensor Core。
评论