乐于分享
好东西不私藏

Kimi K3 注意力机制源码拆解:KDA + Gated MLA + AttnRes

Kimi K3 注意力机制源码拆解:KDA + Gated MLA + AttnRes

     Kimi K3(2.8T 参数,100 万字上下文)的注意力机制不是单点创新,而是三件套组合:KDA 扛 3/4 的层、Gated MLA 每 4 层兜底一次、AttnRes 重写层间信息流。这三件套都来自 Kimi Linear 架构(论文 2510.26692,48B/3B 激活),K3 在 2.8T 规模上把它跑通。下面按数学形式 + 工程实现拆开看。   

零、K3 架构速览:三件套在整体里的位置

     拆三件套的数学细节之前,先看 K3 整体长什么样。下面这张表是关键配置——KDA 和 Gated MLA 按 3:1 轮换扛注意力层,AttnRes 重写层间残差,Stable LatentMoE 管 896 个专家里挑 16 个。   

Kimi K3 架构配置速览
总参数
2.8 万亿(2.8T)
激活参数
约 32B(896 专家中激活 16 个)
上下文窗口
1,048,576 tokens(1M)
注意力混合
KDA : Gated MLA = 3 : 1(每 4 层 3KDA + 1MLA)
层间残差
AttnRes(Block 结构,N ≈ 8 层一块)
MoE 框架
Stable LatentMoE(1 共享 + 16 路由专家)
激活函数
SiTU(Sigmoid Tanh Unit)
优化器
Per-Head Muon(按注意力头独立调度)
训练精度
MXFP4 权重 + MXFP8 激活(QAT)
对比 K2
2.5× 扩展效率(K2 为 1T / 384 专家激活 8+1)

     数据来源:Kimi 官方博客(kimi.com/blog/kimi-k3)+ HuggingFace 模型卡。完整技术报告随权重 7 月 27 日发布。   

单 Block 数据流(3:1 轮换)

       输入 h_l     

AttnRes:α_(i→l) · h_i 跨层加权融合(softmax 对前面所有块输出做注意力)

注意力层(每 4 层轮换)

KDA
KDA
KDA
Gated MLA

前 3 层线性注意力 + 第 4 层全注意力兜底

       Norm + Router(路由打分)     

Stable LatentMoE

Shared共享专家         
16 / 896路由专家(激活)         

Quantile Balancing 负载均衡

       输出 h_(l+1)     

     一个 Block 的数据流:输入 → AttnRes 融合 → KDA 或 Gated MLA → MoE 选专家 → 输出。其中注意力层每 4 层轮换一次(3 层 KDA + 1 层 Gated MLA),MoE 每次只激活 896 个专家里的 16 个——这就是 K3 "该省省该花花"的工程哲学。   

Block AttnRes Backbone(深层堆叠)

每 N ≈ 8 层组成一个 Block,AttnRes 在块间做跨层加权融合

       Embedding     

Block 1

KDA
KDA
KDA
MLA

↕ AttnRes α_(1→2)

Block 2

KDA
KDA
KDA
MLA

Block N

KDA
KDA
KDA
MLA

       Output     

       Block N 的 AttnRes 可访问 Block 1 ~ N-1 所有输出α_(i→N) = softmax(h_i · h_N) ,权重和为 1     

     这张图回答了一个关键问题:K3 深到几十层,底层信息怎么不被淹没? 标准残差是"逐层均匀累加",深层会稀释;AttnRes 把注意力从 token 维度旋转 90° 到深度维度——每个 Block 对前面所有块的输出做 softmax 加权,权重 learned + input-dependent,想用哪层就调哪层。这就是三件套里 AttnRes 解决的问题,跟 KDA(扛序列长度)和 Gated MLA(扛精确检索)正交。   

     有了这个锚点,下面拆三件套的数学形式就不会迷失——每讲一招,你都能对应回上面这张图里的位置。   

一、KDA:带 per-channel 门控的 delta rule

     KDA(Kimi Delta Attention)是 Kimi Linear 的核心线性注意力算子,扛了 K3 里约 3/4 的注意力层。它的状态递推是这样的:   

# KDA 核心递推(单步)
         S_t = (I - β_t · k_t k_tᵀ) · Diag(α_t) · S_(t-1) + β_t · k_t v_tᵀ       
         o_t = S_tᵀ · q_t       
# 维度
         S_t ∈ ℝ^(d_k × d_v)  # 记忆状态矩阵(固定大小,与序列长度无关)
         k_t ∈ ℝ^(d_k),  v_t ∈ ℝ^(d_v),  q_t ∈ ℝ^(d_k)       
         β_t ∈ [0,1]  # 标量写入门(学习率)
         α_t ∈ [0,1]^(d_k)  # per-channel 对角衰减门
     delta rule 的本质:一个梯度下降步   

     把递推拆开看。(I − β_t · k_t k_tᵀ) 这一项是关键——它先用 key 自己做一次投影,把旧状态里与新 key "重合"的部分减掉,再写入新的 (k_t, v_t) 对。这不是覆盖,也不是简单累加,而是"先减后加"的增量更新。   

     论文里说得很直白:delta rule 本身就是一个梯度下降步——以 k_t 为输入、v_t 为目标,沿负梯度方向更新一次关联记忆 S。这让 KDA 的"记忆"不是无脑堆叠,而是会主动修正旧条目,避免重复 key 累积成噪声。   

     per-channel 门控:KDA 相对 GDN 的核心创新   

     KDA 的前驱是 Gated DeltaNet(GDN),GDN 用标量 α_t ∈ [0,1] 做遗忘——所有特征维度共享一个衰减率。KDA 把它升级成对角矩阵 Diag(α_t)α_t ∈ [0,1]^d_k 是一个向量,每个特征维度有独立的遗忘率。   

     这件事的意义在于:不同的语义维度有不同的"保鲜期"。比如编码语法位置的维度需要快速遗忘(局部位置信息过时快),编码实体属性的维度需要长期保留(人名、术语要一直记住)。标量门控只能"一刀切"地忘,per-channel 门控能让每个维度自己决定——这是 KDA 在长上下文检索任务上超过 GDN 的根本原因。   

     DPLR 优化:让 channel-wise 门控真正跑得动   

     per-channel 门控理论上很好,但直接实现会很慢——逐通道独立衰减破坏了矩阵乘法的批量性。KDA 的工程答案是 DPLR(Diagonal-Plus-Low-Rank)转换:把衰减拆成"对角部分 + 低秩修正",再把低秩向量 a = b = √β · k绑定到 key。   

     这个约束式参数化是关键工程权衡——它保留了 fine-grained DPLR 的表达力,同时把 chunkwise kernel 的非 matmul FLOPs 减半,让 Tensor Core 利用率接近纯 matmul。论文说算子速度比通用 DPLR 提升约 100%。没有这一步,channel-wise 门控在实际硬件上根本跑不动。   

# KDA 单步前向(PyTorch 风格,省略 chunkwise)
defkda_step(S, x_t):       
# x_t: [B, H, d_model]  S: [B, H, d_k, d_v]
             q = l2norm(conv(W_q(x_t)))  # [B,H,d_k]
             k = l2norm(conv(W_k(x_t)))  # L2 归一化,限制单次写入幅度
             v = conv(W_v(x_t))  # [B,H,d_v]
             β = sigmoid(W_β(x_t))  # 标量 [B,H,1]
             α = sigmoid(W_α(x_t))  # per-channel [B,H,d_k],低秩"蝶形"投影
# delta rule: 先减旧 key 投影,再加新 (k,v)
             kk = einsum('bhd,bhe->bhde', k, k)  # k k^T [B,H,d_k,d_k]
             S = (I - β * kk) * diag(α) @ S + β * einsum('bhd,bhe->bhde', k, v)       
             o = einsum('bhd,bhde->bhe', q, S)  # 输出 [B,H,d_v]
return o, S       

     注意几个工程细节:q/k 经过 short causal convolution(局部滑窗)再 L2 归一化——这让写入幅度受控,长序列不会因单次大写入把状态带偏;α 用低秩"蝶形"投影(down-up)生成,省参数;β 是单标量,便宜。真正部署时不会逐 token 循环,而是用 chunkwise 并行(WY 表示 + UT transform),这里省略。   

     KDA 的精髓不在"线性复杂度"(这线性注意力都有),而在 per-channel 门控 + delta 修正——前者让记忆按维度独立保鲜,后者让记忆会主动纠错而不是无脑堆叠。   
二、Gated MLA:低秩压缩的全注意力兜底层

     KDA 便宜但本质是"压缩记忆",精确回忆能力有上限。K3 的办法是每 4 层插 1 个全注意力层(layerwise 3:1 KDA:MLA),这一层用 MLA(Multi-head Latent Attention,源自 DeepSeek-V2/Kimi-K2)做完整注意力,并用门控控制写回。   

     MLA 的低秩压缩:存的是潜空间向量,不是完整 K/V   

     标准 KV cache 每个位置存完整的 K_t ∈ ℝ^d_k 和 V_t ∈ ℝ^d_v,长上下文下显存爆炸。MLA 的做法是:先把这个 token 的隐状态压缩成一个低维潜向量 c_t ∈ ℝ^r(r ≪ d_k),只存 c_t。用的时候再用上推矩阵升维恢复 K/V:   

# MLA 低秩压缩
         c_t = W_down · x_t    # 压缩 [d_model] → [r], r << d_k
         K_t = W_K^up · c_t    # 升维恢复 K [d_k]
         V_t = W_V^up · c_t    # 升维恢复 V [d_v]
# KV cache 只存 c_t,显存降为原来的 r/d_k

     关键点:这是数学上可逆的线性投影,不是有损"摘要"——只要 W_down, W_K^up, W_V^up 学得好,恢复出的 K/V 信息无损。这跟"把文档概括成摘要"是两码事,摘要必然丢信息,低秩投影理论上不丢。MLA 省的是显存,不是精度。   

     NoPE + 位置信息委托给 KDA   

     Kimi Linear 论文里一个反直觉的设计:MLA 层用 NoPE(无显式位置编码),完全不做 RoPE。位置信息哪里来?来自 KDA 层的隐式衰减——Diag(α_t) 那个 per-channel 门控本身就是一种"动态位置编码",越久远的状态衰减越多,位置信息被编码进衰减模式里。   

     这是个干净的分工:KDA 层管"位置感知"(通过衰减),MLA 层管"精确检索"(通过全注意力)。论文说这种解耦让模型在长上下文外推时更稳定——RoPE 的外推一直是痛点,NoPE 直接绕开了。   

     Gated:门控决定写回多少   

     "Gated" MLA 的门控加在输出端:MLA 算完全注意力后,过一个 sigmoid 门 g_t,决定这次复盘结果有多少真正写回主路。避免无关内容污染 KDA 已经维护好的压缩记忆。这是个轻量但关键的保护——1/4 的全注意力层如果不加门控,会把 KDA 辛苦压缩的状态搅乱。   

# Gated MLA 前向(每 4 层触发一次)
defgated_mla(x, kv_cache):       
             q = W_q(x)  # [B, H, d_k],无 RoPE(NoPE)
             c_t = W_down(x)  # 压缩 [B, 1, r]
             kv_cache.append(c_t)  # 只存 c_t,不存完整 K/V
             C = stack(kv_cache)  # [B, T, r]
             K = W_K_up(C)  # 升维 [B, T, d_k]
             V = W_V_up(C)  # [B, T, d_v]
             attn = softmax(q @ K.T / sqrt(d_k)) @ V       
             g = sigmoid(W_g(x))  # 门控 [B, H, 1]
return g * attn  # 门控决定写回多少
三、AttnRes:把注意力从 token 维度旋转 90°

     AttnRes(Attention Residuals)不在序列长度维度发力,而是在网络深度维度发力。它的核心思想一句话:模型已经学会按内容选择 token(标准注意力),为什么不能按内容选择层?   

     要解决的问题:PreNorm dilution   

     标准 PreNorm 残差展开后是这样的:   

# 标准残差展开
         y_L = x_0 + f_1 + f_2 + ... + f_L       
# 所有层输出"固定权重 1"累加

     问题在于:所有层用固定权重 1 累加,没有任何选择机制。层数一深,隐藏状态幅度按 O(L) 增长,单个层的贡献被前面累积的巨量表示淹没——论文称之为 PreNorm dilution(信息稀释)。深层想用浅层某个特征,根本"压不过"前面 47 层的累加。   

     AttnRes 的做法:深度维度的 softmax 注意力   

     AttnRes 把"固定权重 1 累加"换成"learned + input-dependent 的 softmax 加权":   

# AttnRes:第 l 层的输入
         x_l = Σ_i α_(i→l) · y_i    # i < l
# α_(i→l) = softmax over i,learned + input-dependent
# 权重和为 1,第 l 层主动"挑选"参考哪些历史层

     注意几个关键点:①权重 α_(i→l) 是 learned 且 input-dependent,不是固定常数;②经过 softmax,权重和为 1,所以不是"自由调取任意一份档案",而是"对所有历史层做加权融合";③第 50 层可以给第 2 层分配 0.8 的权重,直接回收浅层特征,不被中间 48 层稀释。   

     本质上,这是把注意力机制从"token 与 token 之间"旋转 90°,推广到"层与层之间"。序列维度有选择,专家路由有选择,现在深度维度也有了选择。   

     Block AttnRes:把开销从 O(Ld) 压到 O(Nd)   

     Full AttnRes 每层要看所有历史层,内存和通信开销 O(Ld),在 K3 这种深度的模型上不可行。工程方案是 Block AttnRes:把层分成 N 个 block,块内用普通残差快速累积,块间做注意力聚合。   

     论文实测 N ≈ 8 就能恢复全 AttnRes 的大部分收益,内存从 O(Ld) 降到 O(Nd)N=L 退化为 Full AttnRes,N=1 退化为标准残差——是个连续可调的光谱。   

# Block AttnRes 伪代码
         blocks = []  # 缓存的历史 block 表示
for block_n inrange(N):       
             h = input  # block 输入
for layer in block_layers[block_n]:       
# 块内:标准残差 + 块间注意力融合
                 attn_out = cross_block_attention(       
                     query=h, keys=blocks, values=blocks       
                 )  # 对历史 block 做 softmax 注意力
                 h = layer(h + attn_out)  # 融合后进本层
             blocks.append(block_n_output)  # 缓存本 block 表示

     工程上还有两个优化:①cache-based pipeline communication——流水线并行时,已接收的 block 表示缓存在本地,阶段间只传"新增 block"而不是全量历史,通信峰值从 O(C) 降到 O(P)(P 是物理流水线阶段数);②two-phase computation——推理时把一个 block 内所有层的 query 打包,对历史 block 做一次并行 inter-block attention(最贵的部分批处理),再按层做 intra-block attention,用 online softmax 合并。推理延迟增加 <2%。   

     AttnRes 不是"自由调取某层档案",而是"对所有历史层做 softmax 加权融合"——权重 learned + input-dependent,和为 1。本质是把注意力从 token 维度旋转 90° 到深度维度。   
四、三件套怎么在 K3 里配合

     把三件套串起来看 K3 一个 block 里发生了什么:   

• 步骤1 输入先经 AttnRes 融合——当前 block 的输入 = 上一层输出 + 对历史 block 的 softmax 加权(Block AttnRes)。   

• 步骤2 进注意力层:3/4 概率走 KDA(维护固定大小状态 S,per-channel 门控 + delta 修正),1/4 概率走 Gated MLA(全注意力 + 低秩压缩 KV cache + 门控写回)。   

• 步骤3 注意力输出进 MoE 层——2.8T 总参数里激活约 32B(K3 配置),每个 token 走少数专家。   

• 步骤4 输出进入下一个 block,AttnRes 把本 block 表示加入历史缓存,供后续 block 融合。   

     KV cache 对比:为什么能扛 100 万字   

     100 万字上下文,KV cache 是显存大头。K3 的混合方案对比传统全注意力:   

• 传统全注意力 每层每 token 存完整 K/V,长度 1M 时显存爆炸,根本跑不起来。   

• K3 的 3/4 KDA 层 不存 KV cache,只维护固定大小状态 S(d_k × d_v),与序列长度无关。这部分层的显存是 O(1) 每 token。   

• K3 的 1/4 MLA 层 存压缩后的潜向量 c_t ∈ ℝ^r(r ≪ d_k),显存降为原来的 r/d_k。   

     合起来,K3 的 KV cache 总量约为传统全注意力的 25%(论文数据)。这就是 1M tokens 解码吞吐量能到 6×(1.84ms vs 11.48ms per token)、RULER 128k 还能超过全 MLA(84.3 vs 81.3)的工程基础。   

五、这个组合的工程启示

     单看每一招,都不算全新——线性注意力、低秩压缩、跨层连接都是已有技术。K3 的价值在于把它们组合成一个能在 2.8T 规模、100 万字上下文上跑通的工程方案,每一处取舍都对应一个明确的工程约束:   

• KDA 的 per-channel 门控 解决线性注意力"记忆粗糙"问题,代价是 DPLR 优化的工程复杂度。   

• 3:1 混合比例 用 1/4 全注意力兜底精度,3/4 线性注意力扛量——不是"线性注意力替代全注意力"的二元选择,而是成本与精度的连续权衡。   

• NoPE + 位置委托给 KDA 绕开 RoPE 外推痛点,但要求 KDA 的衰减模式能编码足够的位置信息。   

• Block AttnRes(N≈8) 把深度维度的注意力开销压到可承受,同时保留 input-dependent 的层选择能力。   

     给工程实践三个启示:①长上下文不是"硬扛"出来的,是"该省省该花花"省出来的——3/4 省在 KDA,1/4 花在 MLA;②数学可逆的压缩(MLA 低秩投影)比有损的"摘要"更值得用,精度不丢;③深度维度的信息流值得显式建模,固定权重残差在超深网络上是瓶颈,AttnRes 给了一个开销可控的替代方案。   

     关注「Agent架构笔记」,拆 AI 工程里的数学与实现。