English
LLM 架构进化史 · 深度技术长文

22,580:从 GPT-2 到 Kimi K3
大模型架构七年进化全解

两万两千五百八十——这是能塞进一个 Kimi K3(2026)里的 GPT-2(2019)的数量。七年,参数规模膨胀了 22,580 倍。但这真的只是"规模"的故事吗?本文从零基础所需的五块数学积木讲起,沿架构演化主线走完从 GPT-2 到 Kimi K3 的每一步,公式逐项推导、参数逐笔对账。

原文:22580: From GPT2 to Kimi3, Explained · ali (@waterloo_intern) 本文:全文中文重写 + 大幅扩充(第 2 版,融合三稿精华) 阅读时长:约 90–120 分钟
cover
原文配图从 GPT-2 到 Kimi K3:本文将要走完全程的架构演化之路。

两万两千五百八十。

这是 GPT-2(2019 年,约 1.24 亿参数)能被装进 Kimi K3(2026 年,2.8 万亿参数)的次数。七年时间,我们把模型做大了 22,580 倍。但故事真的只是"变大"吗?

在这篇工作日志式的长文里,我们会一步一步追溯这些年真正发生的事——你会发现变的东西很多,没变的东西也不少。原文作者沿着"线性注意力 → DeltaNet → Gated DeltaNet → Kimi Delta Attention"这条线性化主线走到了 Kimi K3;本文在完整保留这条主线(含全部代码与推导)的基础上,额外补上了零基础友好的数学学前班、手算示例、参数账本,以及注意力家族(MQA/GQA/MLA)、状态空间模型(Mamba)、MoE 稀疏化、训练与推理优化方法四条支线。读完后,一个几乎不懂 LLM 的人也能搭建起完整的知识地图——而且是真的会算的那种。

阅 读 指 南

全文分四种内容:学前班(01)给零基础读者补齐全部数学前提,每块积木都手算一遍;主线章节(02、04–06、08、09、11、12)翻译并详解原文的架构演化链条;补课章节(03、07、10、13)是新增的背景知识;14、15 与附录 A/B 是时间线、总结与完整证明。所有代码均可直接运行验证,所有公式都给出直觉解释或逐步推导。如果你已经熟悉 Transformer 基础,可以从第 02 章直接开始。

01学前班:五块数学积木

PREREQUISITES · 向量 · 点积 · 矩阵乘法 · SOFTMAX · 交叉熵(本章为新增内容)

LLM 论文里的公式看起来很多,真正反复使用的积木只有五块:向量、点积、矩阵乘法、Softmax、对数。下面每一块都手算一遍——手算过一遍的东西,才是真正学会的东西。

1.1 积木一到五,各手算一遍

积木一:向量不是"一个意思",而是一组坐标

为什么一个 token 要变成几千个数?因为单个数值很难同时表达"词性、语义、语气、实体、位置"等大量属性。模型给每个 token 一个 \(d\) 维向量;每个维度不一定有能用人话命名的含义,信息通常分布在许多维的组合方向上。

一个二维玩具表示2-D TOY EMBEDDING

"猫" → \(x = [2,\ 1]\);"狗" → \(y = [1.8,\ 1.1]\);"税率" → \(z = [-1,\ 2]\)。

玩具例子里,"猫"和"狗"的方向比较接近;真实 embedding 可能是 768、4096 或 7168 维。

积木二:点积回答"两个方向有多匹配"

两个同维向量逐项相乘再相加,得到一个标量:\(q \cdot k = \sum_j q_j k_j\)。

手算:q = [2, −1],k = [3, 4]2 DIMENSIONS
  1. 对应位置相乘\(2 \times 3 = 6\),\((-1) \times 4 = -4\)。
  2. 把乘积相加\(6 + (-4) = 2\),所以 \(q \cdot k = 2\)。
  3. 几何解释\(q \cdot k = \|q\|\,\|k\|\cos\theta\)。长度固定时,方向越接近点积越大;方向相反点积为负。
为 什 么 这 能 做 检 索

把当前 token 的需求编码为 query \(q\),把每个历史 token 可被检索的标签编码为 key \(k\)。点积越大,就把对应 value 读得越多。注意:这是模型训练出来的匹配空间,不是人手规定的关键词匹配。

积木三:矩阵乘法就是"成批点积"

如果 \(A\) 是 \(m \times n\),\(B\) 是 \(n \times p\)(中间的 \(n\) 必须相同),结果 \(AB\) 是 \(m \times p\)。结果第 \((i,j)\) 项,就是 A 的第 i 行与 B 的第 j 列做点积。

手算:(2×3) × (3×2) → 2×2BATCHED DOT PRODUCTS
\[ \begin{pmatrix} 1 & 2 & 0 \\ -1 & 3 & 1 \end{pmatrix} \times \begin{pmatrix} 2 & 1 \\ 0 & 4 \\ 3 & -2 \end{pmatrix} = \begin{pmatrix} 2 & 9 \\ 1 & 9 \end{pmatrix} \]

检查左上角:\(1\times2 + 2\times0 + 0\times3 = 2\)。右下角:\((-1)\times1 + 3\times4 + 1\times(-2) = 9\)。

积木四:Softmax 把任意分数变成概率

为什么不直接拿点积分数当权重?

点积可能是负数,也没有固定总和。Softmax 先取指数让每项为正,再除以总和,让全部权重加起来等于 1:\(p_i = e^{z_i} / \sum_j e^{z_j}\)。

手算:logits = [2, 1, 0]3 CLASSES
  1. 取指数\(e^2 \approx 7.389\),\(e^1 \approx 2.718\),\(e^0 = 1\)。
  2. 求和\(Z = 7.389 + 2.718 + 1 = 11.107\)。
  3. 各自除以 Z\(p \approx [0.665,\ 0.245,\ 0.090]\),相加正好约等于 1。

数值稳定技巧:\(\text{softmax}(z) = \text{softmax}(z - \max(z))\)。给每个分数同时减 2,变成 \([0,-1,-2]\),概率不变,却避免计算溢出的大指数。FlashAttention 正是把这个技巧推广到分块计算(见 3.4 节与附录 A 的完整证明)。

积木五:对数把"概率很小"变成"惩罚很大"

若正确答案概率是 \(p\),交叉熵损失是 \(-\log p\)。预测越自信且正确,损失越接近 0;正确答案概率越小,损失越大:

正确答案概率 p损失 −ln p直觉
0.900.105很有把握地答对,惩罚小
0.500.693像抛硬币
0.102.303把正确答案压得很低,惩罚大
0.0016.908极其自信地忽略正确答案
为什么 Softmax + 交叉熵特别好优化?THE KEY GRADIENT

设 logits 是 \(z\)、Softmax 概率是 \(p\)、正确类别为 \(y\)。合在一起求导,会得到非常干净的结果:

\[ \frac{\partial L}{\partial z_i} = p_i - \mathbb{1}[i = y] \]

\(\mathbb{1}[i=y]\) 表示:如果 i 是正确答案就取 1,否则取 0。沿用上面的 \(p=[0.665, 0.245, 0.090]\),假设第二类才正确,则梯度为 \([0.665, -0.755, 0.090]\)。梯度下降会压低第一、三类分数,提高第二类分数。它不是一句"模型知道错了",而是每个 logit 都收到一个可计算的改动方向。这个简洁的梯度是整个 LLM 训练大厦的地基。

1.2 "预测下一个 token"到底在优化什么

GPT 类模型的训练目标极朴素:给定前面的 token,提高真实下一个 token 的概率。复杂能力并不是另写了一个"理解世界"的损失函数,而是在压缩海量序列规律时逐渐形成的。

先把一句话错开一格

输入
〈BOS〉 → 我 → 爱 → 猫
模型前向
每个位置只看左边
目标
我 → 爱 → 猫 → 〈EOS〉

训练时,四个位置可以一起算损失(这就是 GPU 并行训练的由来);但每个位置只能看自己和左边,不能偷看右边目标。这个约束由第 02 章的"因果掩码"强制执行。

\[ P(x_{1:T}) = \prod_{t=1}^{T} P(x_t \mid x_{<t}) \qquad\qquad L = -\sum_{t=1}^{T} \log P_\theta(x_t \mid x_{<t}) \]

第一行用条件概率链式法则把整段文本的概率拆成每一步;第二行取负对数,把"最大化乘积"变成"最小化求和",更适合数值计算。

模型为什么不直接记住训练文本?

它当然会记住一部分高频或重复内容,但参数容量、数据规模与训练过程迫使模型大量复用规律。想准确补全"巴黎是法国的___",需要实体关系;想补全代码,需要语法和变量依赖;想补全证明,需要推理模式。下一个 token 预测是目标,不代表内部只能学一个词级查表。

更准确地说,模型学习的是一个条件概率分布。生成时,我们再从这个分布里贪心取最大值或按温度采样。

从 token id 到概率:完整流水线

token id
整数
Embedding
token → 向量
Attention / KDA
跨 token 混合
MLP / MoE
每个 token 内变换
词表投影
向量 → 概率

一张总地图:模型每层只做两类混合——注意力类模块在 token 之间搬运信息(跨位置),MLP 类模块在每个 token 内部变换信息(位置内)。本文后面所有的架构创新,都是在替换这两个格子里的东西。

# 省略 batch 维;T=序列长度,d=隐藏维,V=词表大小
X = Embedding[token_ids]              # [T] → [T, d]
for layer in layers:
    X = X + Attention(RMSNorm(X))     # 跨 token 混合
    X = X + MLP(RMSNorm(X))           # 每个 token 内变换
logits = RMSNorm(X) @ W_vocab.T       # [T,d]×[d,V] → [T,V]
prob = softmax(logits, axis=-1)       # 每行成为词表概率
loss = -mean(log(prob[真实下一个token]))
Embedding 是不是一个神秘算法?

不是。它首先是一张可训练的表 \(E \in \mathbb{R}^{V \times d}\)。token id 是 17,就取第 17 行。若把 id 写成只有一位为 1 的 one-hot 向量,查表等价于矩阵乘法——只是工程上没必要真的构造巨大的 one-hot。

残差连接与归一化:深网络的稳定器

\[ u = x + \text{Attention}(\text{RMSNorm}(x)), \qquad y = u + \text{MLP}(\text{RMSNorm}(u)) \]

若新分支 \(F\) 暂时没学好,模型至少能沿着 "+x" 的直通路径传递信息与梯度;深层网络因此更容易训练。这就是著名的 Pre-Norm 残差结构。

手算 RMSNorm:x = [3, 4],取 g = [1,1]、ε = 0NORMALIZATION

\(\text{RMS}(x) = \sqrt{\frac{1}{d}\sum_j x_j^2 + \epsilon}\),\(\quad \text{RMSNorm}(x) = g \odot x / \text{RMS}(x)\)

\(\text{RMS} = \sqrt{(3^2 + 4^2)/2} = \sqrt{12.5} \approx 3.536\),所以归一化后约为 \([0.849,\ 1.131]\)。它没有把向量变成同一个方向,只是把整体尺度拉回稳定范围;可训练缩放 \(g\) 再决定每一维需要多大。

MLP:两次(或三次)线性变换

原始 GPT-2 使用两层 MLP:先从 \(d\) 扩到 \(d_{ff}\),过 GELU,再投回 \(d\)。现代模型多用 SwiGLU 三路结构:

\[ g = \text{SiLU}(xW_{gate}),\quad u = xW_{up},\quad \text{MLP}(x) = (g \odot u)\, W_{down} \]

\(\odot\) 是逐元素乘法。gate 分支决定哪些扩展维度通过,up 分支提供内容,down 再压回模型宽度。若忽略 bias,三张权重分别含 \(d \times d_{ff}\)、\(d \times d_{ff}\)、\(d_{ff} \times d\) 个参数,总计约 \(3 d d_{ff}\)——这条公式后面会直接解释 Kimi K3 的万亿参数从哪里来(见 11.2 节的完整对账)。

本 章 带 走
  • 五块积木:向量(坐标)、点积(匹配度)、矩阵乘法(成批点积)、Softmax(归一化权重)、−log(损失)。
  • 训练目标 = 逐位置最大化真实下一 token 的概率;Softmax+交叉熵的梯度是 \(p - \mathbb{1}[y]\),极其干净。
  • 总地图:每层 = 一次跨 token 混合(注意力)+ 一次 token 内变换(MLP),残差与归一化保驾护航。

02起点:解剖 GPT-2

GPT-2 · 2019 · DECODER-ONLY TRANSFORMER

要理解后面所有的改进,必须先把基准模型拆到骨头。GPT-2 是一个典型的 decoder-only(仅解码器)架构——今天几乎所有主流大模型(GPT、Llama、Qwen、DeepSeek、Kimi)都属于这个家族。它的完整前向传播用不到 15 行代码就能写完:

def forward(self, idx, pos):        # idx: (b, t) token id;pos: (t,)
    tok_emb = self.transformer.wte(idx)  # (b, t) → (b, t, n_embd)
    pos_emb = self.transformer.wpe(pos)  # (t,) → (t, n_embd)
    x = self.transformer.drop(tok_emb + pos_emb)  # (b, t, n_embd)
    for block in self.transformer.h:
        x = block(x)                     # 12 个 Transformer 块,形状不变 (b, t, n_embd)
    x = self.transformer.ln_f(x)         # 最终 LayerNorm,(b, t, n_embd)
    logits = self.lm_head(x)             # (b,t,n_embd)×(n_embd,V) → (b, t, vocab_size)
    return logits

2.1 从嵌入到输出:一次完整前向

输入文本先被 tokenizer 切成 token 序列(GPT-2 用 BPE,词表约 50,257 个)。每个 token 查表得到一个 768 维向量(token embedding),再加上它所在位置的向量(position embedding)——注意,GPT-2 的位置编码是可学习的绝对位置嵌入,它和 token 嵌入一样是一张待训练的查找表。这一点后来被彻底改变(见 3.2 节)。

token 与位置嵌入
原文配图token 嵌入与位置嵌入相加,形成模型的初始输入表示。图中还能看到一个重要事实:最终只有最后一个位置的 logits 被用来预测下一个 token。

嵌入相加后,数据依次流过 12 个结构完全相同的 Transformer 块。每个块放大看是这样的:

class Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln_1 = LayerNorm(config.n_embd, bias=config.bias)
        self.attn = CausalSelfAttention(config)
        self.ln_2 = LayerNorm(config.n_embd, bias=config.bias)
        self.mlp = MLP(config)

    def forward(self, x):               # x: (b, t, n_embd)
        x = x + self.attn(self.ln_1(x))   # 注意力子层 + 残差连接 → (b, t, n_embd)
        x = x + self.mlp(self.ln_2(x))    # MLP 子层(内部升至 4×n_embd 再降回)→ (b, t, n_embd)
        return x                      # (b, t, n_embd)
GPT-2 Transformer block
原文配图GPT-2 的单个 Transformer 块:LayerNorm → 注意力 → 残差相加 → LayerNorm → MLP → 残差相加。这个"三明治"结构七年间几乎没有变过——变的是里面的注意力机制和 MLP 换成了什么。
背 景 卡 · 残差连接为什么重要

注意 x = x + f(x) 这种写法。它让每一层只需学习"对输入的修正量"而非完整的新表示,梯度可以沿恒等路径无损回传,使得几十上百层的深网络可以稳定训练。2017 年原始 Transformer 把 LayerNorm 放在子层之后(Post-LN),GPT-2 开始把它移到子层之前(Pre-LN),深模型训练稳定性大幅改善——这个顺序延续至今。而残差本身在第 12 章(AttnRes)还会被再次革命。

2.2 手算一次因果注意力

每个块的核心是因果自注意力(Causal Self-Attention)。它的代码同样短得惊人:

B, T, C = x.size()  # batch、序列长度、嵌入维度;x: (B, T, C)

# 一次线性投影同时算出 q, k, v,再拆成多头
q, k, v = self.c_attn(x).split(self.n_embd, dim=2)  # (B,T,C)→(B,T,3C),拆成三份各 (B, T, C)
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)  # (B, nh, T, hs)
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)  # (B, nh, T, hs)
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)  # (B, nh, T, hs),hs = C/nh 为头维

# 注意力分数:每个 query 与所有 key 的点积,除以 √d_head
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))  # (B,nh,T,hs)×(B,nh,hs,T) → (B, nh, T, T)
att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))  # 因果掩码,(B, nh, T, T)
att = F.softmax(att, dim=-1)  # (B, nh, T, T),每行和为 1
y = att @ v          # (B,nh,T,T)×(B,nh,T,hs) → (B, nh, T, hs)
y = y.transpose(1, 2).contiguous().view(B, T, C)  # 拼回头部 → (B, T, C)
y = self.resid_dropout(self.c_proj(y))  # 输出投影 → (B, T, C)
return y

浓缩成公式就是:

\[ \text{Attention}(Q,K,V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_h}} + M\right)V \qquad Q = XW_Q,\; K = XW_K,\; V = XW_V \]

与其背公式,不如真正手算一次。设投影后已得到三枚 token 的 Q/K/V(二维单头),我们只求第 3 个 token 的输出,即取 \(q_3 = [0,1]\) 与三个 key 分别点积:

位置q(只用 q₃)kvq₃·k / √2
1[1, 0][1, 0][1, 0]0/√2 = 0
2[1, 1][1, 1][0, 2]1/√2 ≈ 0.707
3[0, 1][0, 1][1, 1]1/√2 ≈ 0.707
从分数到输出,不跳步ONE ATTENTION ROW
  1. 缩放分数\(s = [0,\ 0.707,\ 0.707]\)。
  2. Softmax指数约为 \([1,\ 2.028,\ 2.028]\),总和 \(5.056\),权重约 \(a = [0.198,\ 0.401,\ 0.401]\)。
  3. 加权 value\(o_3 = 0.198[1,0] + 0.401[0,2] + 0.401[1,1]\)。
  4. 逐维相加\(o_3 \approx [0.599,\ 1.203]\)。

注意力输出不是"选中一个词",而通常是多个 value 的连续加权混合。权重由当前 query 决定,因此同一段历史对不同查询会返回不同结果。

因果掩码:训练时也不能偷看未来

对第 \(t\) 行,只允许看 \(1 \dots t\)。未来位置的分数加 \(-\infty\),经过 Softmax 后权重变成 0:

\[ M = \begin{pmatrix} 0 & -\infty & -\infty \\ 0 & 0 & -\infty \\ 0 & 0 & 0 \end{pmatrix} \]

这使训练能并行计算全部位置,同时保持与逐 token 生成相同的信息约束。并行的是矩阵运算,不是让早期位置看到未来——这是"自回归生成"的数学保证,也是 decoder-only 与 BERT 类 encoder 的分水岭。

为什么一定要除以 √d_h?是不是拍脑袋加的缩放?

不是。假设 q、k 各维独立、均值 0、方差 1,那么乘积 \(q_j k_j\) 的方差约为 1;d 项相加后,点积方差约为 \(d\),标准差约为 \(\sqrt{d}\)。维度越大,未缩放的 logits 越极端,Softmax 越容易饱和成接近 one-hot,梯度随之消失。除以 \(\sqrt{d}\) 后方差回到约 1,不同头维度下数值尺度都稳定。

多头不是重复算同一件事

\[ \text{head}_i = \text{Attention}(XW_{Q,i}, XW_{K,i}, XW_{V,i}), \qquad \text{MHA}(X) = \text{Concat}(\text{head}_1,\dots,\text{head}_h)\, W_O \]

每颗头有不同投影矩阵,因而可以学习不同匹配子空间(语法、指代、位置邻近等)。若总隐藏维 \(d = h \cdot d_h\),拼接后仍回到 d 维,再经 \(W_O\) 混合各头。头的数量与组织方式将在第 03 章成为主角。

二次复杂度从哪里出现

\(QK^\top\) 生成 \(T \times T\) 分数矩阵:T 个 query 每个都和 T 个 key 比较,每次点积成本约 d,所以粗略计算量为 \(O(T^2 d)\),注意力权重显式存储为 \(O(T^2)\)。这张表感受一下"二次"的恐怖:

序列长度 T注意力分数个数 T²相对 4K
4,09616,777,216
32,7681,073,741,82464×
1,048,5761,099,511,627,77665,536×
重 要 区 分

FlashAttention(3.4 节)能避免把完整 T×T 矩阵写回显存,让计算更快、更省内存——但它计算的仍是精确 Softmax 注意力,算术复杂度仍是二次。而第 04 章的线性注意力则真正改写了数学结构。两条路线一个改工程、一个改数学,请勿混淆。

2.3 KV Cache:第一个工程妥协

最后一个 Transformer 块输出隐藏状态矩阵后,语言模型头(LM head)把它映射成词表大小的 logits。自回归解码时,每一步只需要最后一个位置的 logits 来采样下一个 token。

这就暴露了 decoder-only 生成的一个天然低效:模型为每一个输入位置都计算了完整表示,但每个解码步只消费最后一个位置的 logits。如果不做任何缓存,生成下一个 token 时所有这些计算都会被原样重做一遍。

KV Cache 来自一个朴素观察:把新生成的 token 接回输入后,之前所有 token 的 key 和 value 投影一字不差——因为因果掩码保证历史 token 的表示不受未来影响。那么把它们的 K、V 向量存下来,下一步只需为新 token 计算 QKV,再让它与缓存中全部历史 K、V 做注意力即可。计算量从每步 \(O(N^2)\) 降到 \(O(N)\)。

KV cache 解码示意
原文配图带 KV Cache 的解码:每步只为新 token 计算 Q/K/V,历史 K/V 直接复用。代价是显存里多了一块随序列线性增长的缓存。

这块存储就是 KV Cache。它保留之前 \(N-1\) 个 token 的 K/V 向量,大小为 \(2 \times N \times n_{layer} \times n_{head} \times d_{head} \times 2\) 字节(FP16),随序列长度线性增长,很快就会大到把推理变成显存带宽瓶颈而非计算瓶颈。请记住这句话——后面从 GQA 到 MLA 到线性注意力,几乎整条演化线都是在跟"KV Cache 太大"这件事作斗争。

KV 不是一直在"更新"吗,为什么说每个 token 都存了一份?

这是一个特别容易混淆的点:KV Cache 是"流水账",只追加,从不更新。生成第 1 个 token 时缓存里有 1 对 K/V;生成第 100 个时,里面是 99 对,一对都不少;再生成一个,就追加成 100 对。没有任何一对被覆盖或合并。

为什么不能合并?因为下一步注意力要做的是:"用我的新 query,逐个和历史上每一个 key 单独算点积,决定读哪个 token 的 value"。如果第 37 个和第 58 个 token 的 K/V 被合并了,模型就再也无法区分它们——而"每个 token 保留独立槽位、随时精确调取",正是标准注意力质量的根基。

算一笔账:GPT-2 里每个历史 token 要存 12 层 × 12 头 × 2(K、V)× 64 维 × 2 字节 ≈ 36 KB。序列每变长 1 个 token 就多吃 36 KB——是每多一个 token 多一份,不是总共一份。换成现代大模型(80 层、128 维头),每 token 约 0.3 MB,一个 128K 长上下文请求的缓存就要 40 GB 上下——这就是"显存爆炸"。

真正有"更新"语义的,是第 04 章线性注意力的状态 \(S\)(S = S + k @ v):所有历史被揉进一个固定大小的矩阵,旧信息会被新信息覆盖、干扰——那是"白板"。两种记忆方式的对抗就是本文主线:

标准注意力 KV Cache(流水账)线性注意力状态 S(白板)
结构每个 token 一个独立槽位(列表)所有 token 共用一个固定矩阵
操作只追加,永不覆盖只更新(累加/改写),永不增长
检索精确(能单独找到第 37 个 token)模糊(历史互相混叠)
显存随序列线性增长,会爆炸恒定,极小
代表GPT-2、Llama(配 GQA/MLA 压缩)线性注意力、DeltaNet、KDA(第 04–09 章)

从 MQA/GQA(少存几份)到 MLA(压缩每份的大小)再到线性注意力(干脆不存列表),七年来的架构创新,全都是在这张表的两端之间找平衡点。

2.4 参数账本:124M 怎么算出来

"参数"就是训练时会更新、推理时要读取的数字。看清每张权重矩阵的形状,参数量不再是厂商给出的神秘总数,而是一笔可以复核的加法账。Dense Transformer 一共四本账:

模块主要矩阵忽略 bias 后的参数量随什么增长
词嵌入\(E \in \mathbb{R}^{V \times d}\)\(Vd\)词表 V、宽度 d
注意力\(W_Q, W_K, W_V, W_O\) 各约 d×d\(4d^2\)宽度的平方
GPT-2 式 MLPd→d_ff→d\(2dd_{ff}\)宽度×中间维
SwiGLU MLPgate、up、down 三张\(3dd_{ff}\)宽度×中间维
归一化每维 scale(部分有 bias)\(O(d)\)相对很小
为什么模型宽度增加一倍,参数往往不只增加一倍?

因为主要矩阵两边都含 d。\(d \times d\) 在 d 翻倍后变成 \((2d)^2 = 4d^2\);如果层数也翻倍,主干参数大致变成 8 倍。参数扩展看的是矩阵面积,不是向量长度。

GPT-2 的配置:约 5 万词表、12 个块、12 个头、768 维嵌入:

vocab_size: int = 50304  # GPT-2 词表 50257,向上对齐到 64 的倍数以利计算
n_layer: int = 12
n_head: int = 12
n_embd: int = 768
GPT-2 Small 参数账本(V=50,257,d=768,L=12,d_ff=3,072)124,439,808
  1. Token embedding\(50{,}257 \times 768 = 38{,}597{,}376\)。
  2. Position embedding\(1{,}024 \times 768 = 786{,}432\)。
  3. 单层注意力权重\(4 \times 768^2 = 2{,}359{,}296\)。
  4. 单层 MLP 权重\(2 \times 768 \times 3{,}072 = 4{,}718{,}592\)。
  5. 12 层主权重\(12 \times (2{,}359{,}296 + 4{,}718{,}592) = 84{,}934{,}656\)。
  6. 合计\(38.60\text{M} + 0.79\text{M} + 84.93\text{M} \approx 124.3\text{M}\),加上 bias 与 LayerNorm 约 0.12M,总计 ≈ 124.44M。输出词表投影与 token embedding 共享权重,所以不再另加 38.6M。
GPT-2 参数量分解
原文配图GPT-2 的参数量分解可视化。可以看到 MLP 占了约三分之二的参数——这解释了为什么后来 MoE 选择在 MLP 上做文章(第 10 章)。

合计约 1.24 亿参数。而 Kimi K3 有 2.8 万亿参数——一个 K3 里大约装着 22,580 个 GPT-2。

22,580 倍规模对比
原文配图22,580 倍的参数规模差距。但正如后文所示,两者的差距远不只是数字——存储什么、如何更新、如何检索,全都被重新设计过。
本 章 带 走
  • GPT-2 = token/位置嵌入 + 12×(Pre-LN 注意力块 + Pre-LN MLP 块) + LM head,共 1.24 亿参数。
  • 自注意力 = 可微分查表:Q 匹配 K,加权取出 V;手算过一遍 3-token 示例就不再神秘。
  • KV Cache 用显存换时间,但随序列线性增长——它是后续七年大量架构创新的"靶子"。
  • 参数四本账:嵌入 \(Vd\)、注意力 \(4d^2\)、MLP \(2dd_{ff}\) 或 \(3dd_{ff}\)、归一化 \(O(d)\)。

03补课 I:注意力的家族演化

MHA → MQA → GQA → MLA · ROPE · NORM · FLASHATTENTION(本章为新增背景)

原文的主线从"线性注意力"直接开讲,但在那之前,工业界其实先走了另一条路:不改注意力的数学,只改它的存储与工程实现。这条支线孕育了 GQA、RoPE、RMSNorm、FlashAttention 等今天所有主流模型的标配,必须补上。

3.1 MHA → MQA → GQA → MLA:一场针对 KV Cache 的围剿

GPT-2 用的是多头注意力(MHA, Multi-Head Attention):12 个头,每个头有自己独立的 Q、K、V。问题在于 KV Cache——每个头都要为每个历史 token 存一份 K 和 V。12 层 × 12 头 × 2(K和V)× 64 维 × 每 token,长序列下显存爆炸。

演化链条清晰得像教科书:

  • MQA(Multi-Query Attention, Shazeer 2019):所有 query 头共享同一份 K 和 V。KV Cache 直接除以头数(12 倍压缩),解码吞吐大幅提升,但质量有可感知的损失。
  • GQA(Grouped-Query Attention, 2023,Llama 2 推广):折中方案——把 Q 头分成若干组,每组共享一份 K/V。Llama 2 70B 用 \(h=64\) 个 Q 头配 \(g=8\) 组 KV,缓存压缩 8 倍,质量几乎无损。GQA 此后成为 2023–2024 年开源模型的默认配置(Llama、Qwen、Mistral)。
  • MLA(Multi-head Latent Attention, DeepSeek-V2, 2024):换了个思路——不共享 K/V,而是把所有头的 K/V 联合低秩压缩成一个小的潜向量(latent vector)存起来,用时再解压。压缩率比 GQA 更高,质量反而更好。这是 DeepSeek-V2/V3 与 Kimi 系模型的核心技术,第 10.2 节会详细拆解。
MHA 每个 Q 头配独立 K/V Q₁..Q₄ 4 份 K/V · 缓存 ×1 MQA 全部 Q 共享一份 K/V Q₁..Q₄ 1 份 K/V · 缓存 ÷H GQA 分组共享 K/V(工业界标配) Q₁..Q₄(2 组) G 份 K/V · 缓存 ÷(H/G) MLA 低秩压缩成潜向量 Q₁..Q₄(推理时吸收) 潜向量 c 只存 c · 压缩更狠、质量更好
图 3-1(本文自绘):注意力机制的 KV Cache 围剿战。橙色是 Query 头,绿色是每 token 需要缓存的 K/V,黄色是 MLA 的压缩潜向量。
机制提出每 token KV 缓存量质量代表模型
MHA2017\(2 \cdot n_{head} \cdot d_h\)基准GPT-2/3、原始 Transformer
MQA2019\(2 \cdot d_h\)(÷头数)略降Falcon、PaLM 早期
GQA2023\(2 \cdot n_{group} \cdot d_h\)≈MHALlama 2/3QwenMistral
MLA2024\(d_c + d_h^R\)(潜维度,压缩 90%+)反超 MHADeepSeek-V2/V3Kimi K2/K3

3.2 位置编码:RoPE 的胜利

GPT-2 用可学习绝对位置嵌入,有两个硬伤:训练时见过的最长序列就是能力上限,且位置信息与内容信息纠缠在一起。演化路径:

  • 正弦位置编码(2017 原始 Transformer):固定公式生成,无需学习,可外推性稍好,但依然脆弱。
  • RoPE(Rotary Position Embedding,苏剑林 2021):把位置编码成对 Q/K 向量的二维子空间旋转——位置 \(m\) 的 query 旋转 \(m\theta\) 角。Llama 全系、Qwen、DeepSeek、Kimi 全部使用 RoPE,是近年少数"零争议"的技术共识。
  • 长度外推补丁:RoPE 直接外推到训练未见的长度会失效,于是有了位置插值(Position Interpolation)、NTK-aware 缩放、YaRN 等一系列"续长"技术,让 4K 训练的模型能用到 128K 甚至更长。
  • ALiBi(2022):不给词嵌入加位置,而是直接给注意力分数加一个与距离成正比的惩罚项,外推性极佳(Bloom 采用)。
  • NoPE / 因果位置涌现:另一个有趣发现是,decoder-only 模型即使不加任何位置编码也能学到一定位置感;而第 04 章要讲的线性注意力/RNN 类架构,位置感天然内建于递归结构中。
RoPE 的推导:让点积只依赖相对位置ROTARY DERIVATION

目标:找到一个位置编码方案,使 \(q_m\) 和 \(k_n\) 的内积仅依赖于相对位置 \(m-n\)

\[ \langle f_q(x_m, m),\ f_k(x_n, n) \rangle = g(x_m, x_n,\ m - n) \]

二维情形用旋转矩阵实现:

\[ R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}, \qquad f_{\{q,k\}}(x, pos) = R(pos \cdot \theta)\, x \]

关键恒等式:旋转矩阵满足 \(R(m\theta)^\top R(n\theta) = R((n-m)\theta)\),于是

\[ \big(R(m\theta)q\big)^\top \big(R(n\theta)k\big) = q^\top R\big((n-m)\theta\big)\, k \]

点积自然只依赖相对距离 \(n-m\)。推广到 d 维(偶数),把维度两两分组,每组分配自己的旋转频率 \(\theta_i = 10000^{-2i/d}\):高频组管近距离结构,低频组管远距离结构——位置信息直接编码在向量旋转中,无需任何额外的位置嵌入向量相加,且注意力随距离自然衰减。

3.3 归一化与激活:安静的地基

这些改动不性感,但每一个都省下了真金白银:

LayerNorm vs RMSNorm:可以扔掉的一半计算NORMALIZATION

标准 LayerNorm:

\[ \text{LayerNorm}(x) = \gamma \cdot \frac{x - \mu}{\sigma} + \beta, \quad \mu = \tfrac{1}{d}\textstyle\sum_i x_i, \quad \sigma = \sqrt{\tfrac{1}{d}\sum_i (x_i - \mu)^2} \]

RMSNorm 去掉均值中心化(re-centering),只做缩放:

\[ \text{RMSNorm}(x) = \gamma \cdot \frac{x}{\sqrt{\tfrac{1}{d}\sum_i x_i^2 + \epsilon}} \]

为什么可以去掉均值?实验表明 re-centering 对性能影响甚微,但省掉它能减少约 10–20% 的归一化计算量。归一化在 Transformer 每层出现两次,这个节省非常可观——Llama 之后 RMSNorm 成为事实标配。

组件为什么换
归一化LayerNorm(减均值除方差)RMSNorm(只除均方根)去掉均值平移,计算少约 10–20%,效果无损
归一化位置Post-LNPre-LN深层训练稳定,免去复杂 warmup;GPT-2 开创
稳定性补丁QK-Norm、z-loss、sandwich norm防止注意力 logits 与残差流数值爆炸(第 12 章 AttnRes 与此呼应)
FFN 激活ReLU / GELUSwiGLU:\((xW_1 \odot \text{SiLU}(xW_g))W_2\)门控线性单元,同参数下效果更优;隐藏维取 \( \frac{8}{3}d \) 补齐参数量
FFN 结构升维→激活→降维门控三路PaLM/Llama 确立;第 11 章的 SiTU 是这个家族的远亲

3.4 FlashAttention:不改数学,改世界

2022 年 Tri Dao 的 FlashAttention 可能是对工业界影响最大的单篇论文。它没有改变注意力的任何数学定义,输出与标准实现逐位等价——它改的是计算在 GPU 上的组织方式

关键洞察:标准实现的瓶颈不是 FLOPs,而是显存读写(IO)。朴素实现把 \(N \times N\) 的注意力分数矩阵完整物化到 HBM(高带宽显存),再读出来做 softmax、再写回、再读出乘 V——序列 8K 时这块中间矩阵就有数 GB。GPU 的 SRAM(片上缓存)快一个数量级但极小(每 SM 约 200KB)。FlashAttention 的做法:

  1. 分块(Tiling):把 Q/K/V 切成能塞进 SRAM 的小块;
  2. 在线 softmax(Online Softmax):边扫 K/V 块边维护运行中的最大值与归一化因子,一遍扫描完成精确 softmax,永不物化 \(N \times N\) 矩阵;
  3. 重计算:反向传播不存注意力矩阵,用到时重新算一遍——多花 FLOPs 省 IO,反而更快。
在线 Softmax 算法:FlashAttention 最精妙的一步ONLINE SOFTMAX

标准 Softmax 需要遍历向量两次(一次求最大值保数值稳定,一次求指数和)。向量太大装不进 SRAM 时,每次都得去 HBM 读。在线 Softmax 用一次遍历维护两个运行统计量 \((m, \ell)\):

  1. 初始化\(m_0 = -\infty\),\(\ell_0 = 0\)。
  2. 对每个 \(x_i\)更新运行最大值:\(m_i = \max(m_{i-1},\ x_i)\)。
  3. 重缩放累加\(\ell_i = \ell_{i-1} \cdot e^{m_{i-1} - m_i} + e^{x_i - m_i}\)。
  4. 最终输出\(o_i = e^{x_i - m_n} / \ell_n\)。

直觉:\(\ell_i\) 始终等于 \(\sum_{j=1}^{i} e^{x_j - m_i}\)——每个新元素到来时,旧的指数和乘上修正因子 \(e^{m_{i-1}-m_i}\) 被"换底",新指数直接加入。结果与两遍扫描逐位等价,却只需一遍。完整的归纳法证明见附录 A

标准注意力:反复读写 HBM HBM:Q, K, V 与 N×N 中间矩阵 SM 计算单元 4 次读写 N×N 矩阵 FlashAttention:一次扫描,永不落地 HBM:只存 Q, K, V, O SRAM 内分块计算 在线 softmax 流式归一 只读写 O(N·d) 数据
图 3-2(本文自绘):FlashAttention 的核心是把所有中间结果留在片上 SRAM,HBM 只流过输入输出。结果:2–4 倍训练提速、显存占用从 O(N²) 降到 O(N),并直接解锁了长上下文时代。

后续 FlashAttention-2(更好的并行划分)与 FlashAttention-3(针对 Hopper GPU 的异步流水线)继续压榨硬件。记住一个连锁反应:FlashAttention 让"全长注意力"在工程上变得便宜,一度压制了线性注意力的生存空间——直到长序列 + 高并发解码场景下 KV Cache 的带宽瓶颈再次把线性化路线请回牌桌。这正是第 04 章的历史背景。

本 章 带 走
  • KV Cache 是注意力的阿喀琉斯之踵:MHA→MQA→GQA→MLA 是一条"缓存压缩"演化线。
  • RoPE 用旋转矩阵让点积只含相对位置;RMSNorm + SwiGLU 是 2023 年后所有开源模型的隐形标配。
  • FlashAttention = Tiling + 在线 Softmax + 重计算:不改数学却改变了整个行业的成本曲线。

04线性注意力:把 RNN 请回来

LINEAR ATTENTION · KATHAROPOULOS ET AL. 2020 · TRANSFORMERS ARE RNNs

现在回到原文主线。第一条试图从数学上掀掉 \(O(N^2)\) 桌子的路线,是 2020 年的线性注意力(Linear Attention)。

Softmax 注意力在 \(q \cdot k\) 点积之后施加非线性(指数函数),这把每个 query 和每个 key 牢牢耦合在一起——你必须先算出完整的 \(N \times N\) 分数矩阵才能归一化。线性注意力换了个顺序:先对 q 和 k 各自施加一个特征映射(如 ELU+1),再让它们相乘

\[ \text{Softmax}: \; o_i = \frac{\sum_j e^{q_i k_j^\top} v_j}{\sum_j e^{q_i k_j^\top}} \qquad\Longrightarrow\qquad \text{Linear}: \; o_i = \frac{\phi(q_i)\, \sum_j \phi(k_j)^\top v_j}{\phi(q_i)\, \sum_j \phi(k_j)^\top},\quad \phi(x)=\text{ELU}(x)+1 \]

这个调换的魔力在于结合律:\(\phi(q)\cdot(\phi(k)^\top v)\) 可以先算 \(\phi(k)^\top v\)——而所有历史 token 的 \(\phi(k_j)^\top v_j\) 可以累加成一个固定大小的 \(D \times D\) 矩阵 \(S\)。于是整件事变成了一个 RNN:每来一个 token,就往状态 \(S\) 里加一点东西;读出时只需一次 \(q \cdot S\) 矩阵向量乘。因果版本可以写成递推:

\[ S_t = S_{t-1} + \phi(k_t)^\top v_t, \qquad o_t = \frac{\phi(q_t)\, S_t}{\phi(q_t)\, z_t}, \qquad z_t = z_{t-1} + \phi(k_t)^\top \]
def forward(self, x, mask=None, cache=None):
    # x: (b, t, d)
    b, t, d = x.shape
    d_head = d // self.num_heads          # 头维 dh = d/h
    qkv = self.qkv_proj(x)                # (b,t,d) → (b, t, 3d)
    q = qkv[:, :, :d].view(b,t,h,d_head).transpose(1,2)      # (b, h, t, dh)
    k = qkv[:, :, d:2*d].view(b,t,h,d_head).transpose(1,2)   # (b, h, t, dh)
    v = qkv[:, :, 2*d:].view(b,t,h,d_head).transpose(1,2)    # (b, h, t, dh)

    k = F.elu(k) + 1          # 特征映射:保证非负,(b, h, t, dh)
    k = k.transpose(-1,-2)    # (b, h, dh, t)
    q = F.elu(q) + 1          # (b, h, t, dh)

    S, z = cache if cache is not None else (0.0, 0.0)  # S: (b,h,dh,dh);z: (b,h,dh,1)
    S = S + k @ v             # (b,h,dh,t)×(b,h,t,dh) → (b,h,dh,dh),固定 D×D 不随序列增长
    z = z + k                 # (b,h,dh,1),归一化项累加

    o = q @ S                 # (b,h,t,dh)×(b,h,dh,dh) → (b,h,t,dh),一次矩阵向量乘读出
    denom = q @ z             # (b,h,t,1)
    o_scaled = o / denom      # (b,h,t,dh)
    ...
    cache = (S, z)            # 形状恒定,不随 t 增长
    return o_proj, cache      # o_proj: (b, t, d)
"线性"到底线性在哪里?

三个层面都线性:训练总开销从 \(O(N^2 d)\) 降到 \(O(N d^2)\)(对 N 线性);解码每步只读写固定大小的状态,与已生成长度无关(O(1));状态不随序列增长(O(1) 显存)。代价是引入了第二个维度 d²——当 \(N \gg d\) 时大赚,N 短时反而不划算。

原 文 作 者 的 一 个 有 趣 吐 槽

原论文开篇就说"Transformer 每步的开销随当前序列长度平方增长"——这句话其实不严谨。每步解码的真实成本是 \(O(N)\)(有 KV Cache 时),\(O(N^2)\) 的是训练期的总序列开销。作者一开始被这个表述绕晕了,随后反应过来:2020 年时 FlashAttention 还不存在(2022 年才发布),训练普遍会把完整的 \(N \times N\) 矩阵物化到显存,而参考实现经常连 KV Cache 都不用、每步重算全部历史。线性注意力的历史意义要结合当时的工程水平来看。

线性注意力的读写模式对比
原文配图上方是标准注意力的解码:每个解码步要从 HBM 读两次 O(ND) 数据(K、V 缓存各一次)、写两个一维向量,KV Cache 随序列 O(N) 增长。下方是线性注意力:状态固定为 D×D,每步读写量与序列长度无关。

代价是什么?表达力。把注意力拆解为三步看最清楚:

  1. 让 qk 分数非负——softmax 用指数函数,线性注意力用 ELU+1;
  2. 除以分数总和归一化;
  3. 对 value 做加权平均。

三步都还在,但第一步的"非负化"函数从指数换成了 ELU+1——这是对 softmax 核的一个低表达力近似。指数函数能制造尖锐的、赢者通吃的注意力分布(精准检索单个 token),ELU+1 造出来的分布则更平、更模糊。实际精度损失取决于架构和任务,但在需要"从长上下文中精确捞出某个细节"的任务上,朴素线性注意力的劣势是实打实的。

关 键 矛 盾 · 记 忆 容 量

还有一个更本质的问题藏在"固定大小状态"里:KV Cache 为每个历史 token 保留独立槽位(无损但无限增长);而 \(D \times D\) 状态把所有历史压缩进固定矩阵(有界但必然互相干扰)。这就像把无数张卡片不断塞进一个固定大小的抽屉——塞得越多,卡片之间的字越糊。下一章的 DeltaNet,就是来解决"抽屉塞满之后怎么办"的。

本 章 带 走
  • 线性注意力 = 把 softmax 换成特征映射,利用结合律把历史折叠成固定 D×D 状态。
  • 收益:解码每步 O(1) 读写、状态不随序列增长;代价:注意力分布变钝,精确检索能力下降。
  • 它把 Transformer 重新接回了 RNN 的血脉——此后所有线性化工作都在修这条血脉的两个缺陷:精度记忆管理

05DeltaNet:会改错的白板

DELTA RULE · SCHLAG ET AL. 2021 · FAST WEIGHT PROGRAMMERS

有限容量的缓存,必然面临覆盖与干扰。线性注意力里,第 \(i-1\) 个 token 的状态没有自己的槽位——它被进了同一个 \(D \times D\) 矩阵。新 query 再想精确读回某个早期 token 的独立表示,已经做不到了。

这个"加法"既是效率的来源,也是灾难的来源:用累加而非拼接来更新缓存,缓存才不至于 O(N) 膨胀;但也正是累加让信息互相污染。Schlag 等人在《Linear Transformers Are Secretly Fast Weight Programmers》里把问题说得极为透彻:

"当序列长度超过存储容量,模型就进入了'超容量'运行状态。要在此状态下正常工作,模型应该学会与记忆内容动态交互,有选择地决定保留哪些键值关联、删除哪些。纯粹累加的指令不适合这个目的……向有限记忆无休止地添加新关联, inevitably 会触及极限。"

累加状态的干扰
原文配图纯累加的状态矩阵:新关联(绿色)不断叠加到旧关联上,互不删除。容量耗尽后,读出的信息是各路历史的混叠。

5.1 先理解:一块联想记忆白板

先看单个关联是怎么存取的。写入 \(S = k^\top v\)(外积);用同一个 key 读出:\(k(k^\top v) = (kk^\top)v = \|k\|^2 v\)。也就是说,读出的正好是 value 乘以 key 的模长平方——只要把 k 归一化到单位长度(或读出后除以模长),就能无损取回 v。

外积联想记忆示意
原文配图外积 \(k^\top v\) 构成可寻址的联想记忆:同一 key 写入、同一 key 读出。多个不同 key 的关联可以共存于同一矩阵中——只要 key 之间近似正交,干扰就小;这就是"容量"的几何含义。

Q 在这里是一个可学习的指针:\(W_q\) 和 \(W_k\) 读取同一条残差流,某个事实的 query 会指向该事实当初被写入的 key 方向。这套机制与老世纪的 Hopfield 网络一脉相承——Transformer 的注意力本来就是一种现代联想记忆。

5.2 Delta 规则:先擦除,再写入

Delta 规则(Widrow-Hoff 规则的现代化身)的更新逻辑:

  1. 先用当前 key 去缓存:\(v_{old} = k_i S\),看看这个位置现在存着什么;
  2. 算出增量(delta):想存的新值与旧值之差,乘上写入强度 \(\beta\):\(u_i = \beta_i (v_i - v_{old})\);
  3. 把增量写回:\(S \leftarrow S + k_i^\top u_i\)。
def forward(self, x, mask=None, cache=None):
    # x: (b, t, d)
    b, t, d = x.shape
    ...
    q = F.normalize(F.silu(q), dim=-1)   # (b,h,t,dh),单位长度
    k = F.normalize(F.silu(k), dim=-1)   # (b,h,t,dh),归一化:保证读出无损
    beta = torch.sigmoid(self.w_beta(x)).view(b, 1, t, 1)  # (b,1,t,1),新增:逐 token 写入强度

    S = cache if cache is not None else 0.0   # (b, h, dh, dh)

    v_old = k @ S                        # (b,h,t,dh)×(b,h,dh,dh) → (b,h,t,dh),读出这个 key 位置上的旧信息
    u = beta * (v - v_old)               # (b,h,t,dh),delta:只写"真正新"的部分
    S = S + k.transpose(-1, -2) @ u      # (b,h,dh,t)×(b,h,t,dh) → (b,h,dh,dh),与之前相同的外积写入

    o = q @ S                            # (b,h,t,dh)×(b,h,dh,dh) → (b,h,t,dh),读出(不再需要归一化分母)
    o = o.transpose(1, 2).contiguous().view(b, t, d)  # (b, t, d)
    return self.o_proj(o), S             # (b,t,d);cache S: (b,h,dh,dh)

旧信息被定向移除,新信息写在原处——缓存从"只增不减的流水账"变成了"可以修改的白板"。\(\beta\) 由输入决定(sigmoid 门控),模型自己学会每个 token 该用多大力气改写记忆。

为 什 么 叫 "Fast Weight Programmers"

这个视角下,状态矩阵 \(S\) 本身是一个"快速权重"网络——普通权重靠梯度下降缓慢更新(训练期),而 \(S\) 在前向传播中被输入数据实时编程(推理期)。DeltaNet 让模型学会给自己写程序:根据当前内容,精确修改自己的记忆矩阵。这是 1990 年代 Schmidhuber 一脉思想的现代复兴,也是"in-context learning 即隐式梯度下降"理论家族的成员。

本 章 带 走
  • 线性注意力的状态是联想记忆;纯累加写入导致超容量后信息混叠。
  • Delta 规则 = 读出旧值 → 算差值 → 写回增量,实现定点修改而非盲目叠加。
  • 写入强度 β 由数据驱动,模型自学"何时改、改多重"。

06并行化 DeltaNet:分块的魔术

PARALLELIZING LINEAR TRANSFORMERS WITH THE DELTA RULE · YANG ET AL. 2024
原 文 作 者 的 话

"这是全文最难的一节,我花了大约七个小时才建立起可用的理解,所以我从实现出发来构建解释。"——本章我们保留这个路线:先看清问题,再看数学重参数化,最后落到代码。一句话概括:DeltaNet 把一阶线性递归的转移矩阵写成了广义 Householder 变换的乘积,从而能做分块并行的前向传播,把线性时间训练变得硬件友好。

6.1 问题:prefill 阶段的顺序依赖

推理时逐 token 递归很自然,但训练/预填充(prefill)时如果把 T 个 token 挨个过一遍 delta 规则,GPU 只能干瞪眼:

S = torch.zeros(b, h, dh, dh) if cache is None else cache  # (b,h,dh,dh)
outs = []
for i in range(t):                # 纯顺序循环,T 次小矩阵运算
    k_i = k[:, :, i:i+1]          # (b,h,1,dh)
    v_i = v[:, :, i:i+1]          # (b,h,1,dh)
    b_i = beta[:, :, i:i+1]       # (b,1,1,1),标量写入强度
    v_old = k_i @ S               # (b,h,1,dh)×(b,h,dh,dh) → (b,h,1,dh)
    u_i = b_i * (v_i - v_old)     # (b,h,1,dh)
    S = S + k_i.transpose(-1, -2) @ u_i   # (b,h,dh,1)×(b,h,1,dh) → (b,h,dh,dh),写入
    outs.append(q[:, :, i:i+1] @ S)       # (b,h,1,dh)
o = torch.cat(outs, dim=2)        # (b,h,t,dh)

这与标准注意力不同:delta 规则在每个 key 上都要先做一次"读出—修正",第 \(i\) 步依赖第 \(i-1\) 步修正完的状态,通向并行矩阵乘法的路径一点也不显然。其实哪怕没有 delta 修正,朴素的线性注意力 prefill 也同样是顺序的(\(S\) 逐步累加)。

6.2 分块:两种结合顺序的缝合

出路是分块(chunking)。把序列切成若干长度为 C 的块(实践中 C 常取 64 或 128,因为张量核指令在这个粒度上效率最高,如 NVIDIA 的 UMMA):

  • 块内:按 \(q(k^\top v)\) 的顺序算——先算分数再做掩码加权,即"真注意力"模式,块内复杂度 \(O(C^2)\);
  • 块间:按 \((k^\top v)q\) 的顺序走——每块结束把整个块的贡献折叠进状态 \(S\),下一块用一次矩阵乘法从状态读出,即"递归"模式。
分块计算示意
原文配图分块线性注意力的计算图:对角线上的小方块是块内注意力(score-first),其余部分通过状态 S 串接(state-first)。
中间块折叠进状态
原文配图每个块产生的中间 tile 被折叠进状态 S,作为下一块的"上文摘要"。
S = torch.zeros(b, h, dh, dh) if cache is None else cache  # (b,h,dh,dh)
outs = []
for i in range(t // C):                       # 只循环 t/C 次,每次是大矩阵乘
    q_c = q[:, :, i*C:(i+1)*C]          # (b,h,C,dh)
    k_c = k[:, :, i*C:(i+1)*C]          # (b,h,C,dh)
    v_c = v[:, :, i*C:(i+1)*C]          # (b,h,C,dh)

    o_prev = q_c @ S                            # (b,h,C,dh)×(b,h,dh,dh) → (b,h,C,dh),这一块之前所有历史的贡献
    attn = (q_c @ k_c.transpose(-1,-2)).tril()  # (b,h,C,C),块内掩码注意力
    o_curr = attn @ v_c                         # (b,h,C,C)×(b,h,C,dh) → (b,h,C,dh)
    o = o_prev + o_curr                         # (b,h,C,dh)

    S_new = k_c.transpose(-1,-2) @ v_c          # (b,h,dh,C)×(b,h,C,dh) → (b,h,dh,dh),整块折叠进状态
    S = S + S_new                               # (b,h,dh,dh)
    outs.append(o)
o = torch.cat(outs, dim=2)              # (b,h,t,dh)

成本的分解非常优雅:固定部分 \(2Ld^2\)(状态维护,与 C 无关)+ 增长部分 \(2LCd\)(对角线上的分数矩阵)。C = L 时退化为标准注意力(第二项变成 \(2L^2d\),二次);C = 1 时就是纯线性注意力。中间的 C 用一点点块内计算换取硬件利用率——纯 FLOPs 上 C=1 最便宜,但墙钟时间未必:GPU 只有在工作能映射到大块矩阵乘时才跑得快。

6.3 把 delta 修正塞进分块:WY 重参数化

把同样的分块思路直接套到 delta 规则上会遇到障碍:每个 key 的修正量依赖当时的状态,而分块并行要求块内所有修正一次性算完:

v_old = k_i @ S               # (…,1,dh)×(…,dh,dh) → (…,1,dh)
u_i = b_i * (v_i - v_old)     # (…,1,dh),每个 u_i 都依赖前面所有修正后的 S

我们需要按顺序经过每一个中间状态,才能算出该减去的量——不做数学重参数化就没法并行。作者们的做法是把递推

\[ S_t = S_{t-1}\big(I - \beta_t\, k_t k_t^\top\big) + \beta_t\, v_t k_t^\top, \qquad o_t = S_t\, q_t \]

展开后发现:\(S_T\) 可以写成初始状态左乘一串 \((I - \beta k k^\top)\) 的乘积,再加上各 \(v k^\top\) 项的加权和——这正是 WY 表示(数值线性代数中 Householder 变换乘积的紧凑形式)。修正量 \(u\) 可以通过对一个 C×C 下三角矩阵做前代法求逆一次性解出。从递推到展开式的完整四步推导见附录 B;这里直接落到代码——分块代码一次算出整块 C 个 delta:

def chunk_delta_rule_forward(Q, K, V, beta, C):
    # Q,K,V: (L, d);beta: (L,);C: 块长
    L, d = Q.shape
    # 分块
    Q, K, V = map(lambda x: x.reshape(-1, C, d), [Q, K, V])  # 各 (L/C, C, d)
    beta = beta.reshape(-1, C)            # (L/C, C)
    K_beta = K * beta.unsqueeze(-1)       # (L/C, C, d)
    V_beta = V * beta.unsqueeze(-1)       # (L/C, C, d)

    # 向量化前代法:求下三角矩阵 (I + 严格下三角) 的逆
    T = -(K_beta @ K.t()).tril(-1)        # (L/C, C, C),严格下三角
    for i in range(1, C):
        T[i, :i] = T[i, :i] + (T[i, :, None] * T[:, :i]).sum(-2)
    T += torch.eye(C)                     # (L/C, C, C),下三角、对角为 1
    W = T @ K_beta        # (L/C,C,C)×(L/C,C,d) → (L/C, C, d),修正后的 key
    U = T @ V_beta        # (L/C, C, d),修正后的 value(含全部 delta)

    # 分块并行主循环
    S = torch.zeros(d, d)                 # (d, d)
    O = torch.empty_like(V)               # (L/C, C, d)
    for i in range(L // C):
        q_i, k_i, w_i = Q[i], K[i], W[i]  # 各 (C, d)
        u_i = U[i] - w_i @ S              # (C,d) − (C,d)×(d,d) → (C, d),整块的修正量一次算完
        o_inter = q_i @ S                 # (C, d),历史贡献(递归模式)
        A_i = (q_i @ k_i.t()).tril()      # (C, C),块内注意力分数
        o_intra = A_i @ u_i               # (C,C)×(C,d) → (C, d),块内贡献(注意力模式)
        S += k_i.t() @ u_i                # (d,C)×(C,d) → (d, d),更新状态
        O[i] = o_intra + o_inter          # (C, d)
    return O.reshape(L, d)                # (L, d)
Delta 规则的分块并行
原文配图Delta 规则的分块并行化:块内 C×C 的依赖通过下三角矩阵求逆一次性解出,块间仍走状态传递。

至此我们来到第一个里程碑式的对比——标准多头注意力 Transformer vs DeltaNet Transformer:

MHA vs DeltaNet 对比
原文配图MHA(左)与 DeltaNet Transformer(右):结构几乎同构——QKV 投影、输出投影、残差流都一样;区别只在核心记忆机制:一个维护无限增长的 KV 缓存 + softmax 检索,一个维护固定大小、可定点修改的状态矩阵。
本 章 带 走
  • 分块 = 块内注意力(精确、二次)+ 块间递归(压缩、线性),C 是精度和效率的旋钮。
  • WY 重参数化把"逐个修正"变成"整块求解",delta 规则由此可以吃满张量核。
  • 注意全文的反复主题:数学等价变换换硬件友好性——与 FlashAttention 异曲同工。

07补课 II:状态空间模型与 Mamba

S4 → MAMBA → MAMBA-2/SSD · RWKV · RETNET(本章为新增背景)

下一章要讲的 Gated DeltaNet 里有一半血统来自 Mamba。所以在讲"遗忘门"之前,必须先认识线性注意力的平行宇宙——状态空间模型(State Space Models, SSM)

7.1 从控制论到深度学习:SSM 的数学基础

状态空间模型是控制论的老古董:一个连续时间的线性时不变系统

\[ h'(t) = A\, h(t) + B\, x(t), \qquad y(t) = C\, h(t) + D\, x(t) \]

其中 \(x(t)\) 是输入信号(LLM 中是 token 嵌入的一个维度),\(h(t) \in \mathbb{R}^N\) 是隐状态,\(A\) 是状态转移矩阵,\(B\)、\(C\) 是输入/输出投影。要处理离散的 token 序列,需要用零阶保持(Zero-Order Hold)离散化:

\[ \bar{A} = e^{\Delta \cdot A}, \qquad \bar{B} = (\Delta A)^{-1}\big(e^{\Delta \cdot A} - I\big)\cdot \Delta B \qquad\Longrightarrow\qquad h_t = \bar{A}\, h_{t-1} + \bar{B}\, x_t \]

离散化后的递推就是一个线性 RNN——每步成本与序列位置无关,这就是 SSM 实现 \(O(L)\) 总复杂度(而非 \(O(L^2)\))的原因。它和线性注意力的 \(S_t = S_{t-1} + k_t^\top v_t\) 神似并非巧合:两者都是"用一个固定大小的隐状态压缩全部历史",只是方言不同。

SSM 家族的关键节点:

  • S4(Gu et al., 2021):第一个能在超长序列上打赢 Transformer 的 SSM。核心是用 HiPPO 矩阵初始化 \(A\)(一种数学上最优的"历史压缩基"),并用对角化 + 卷积技巧实现并行训练。但 S4 的转移矩阵是固定的——不随输入变化,如同一台不会根据内容调整的记忆机器。
  • Mamba(Gu & Dao, 2023):让 \(B\)、\(C\) 和离散化步长 \(\Delta\) 全部变成输入的函数——"选择性(selective)"。模型可以按内容决定这一步是认真记还是忽略。代价是失去了卷积并行形式,于是配套发明了硬件感知的并行扫描(parallel scan)算法,在 SRAM 里完成递推。Mamba 在语言建模上第一次以线性时间架构逼平同尺寸 Transformer。
  • Mamba-2 / SSD(2024):揭示了 SSM 与线性注意力的严格对偶——标量门控的 SSM 就是"带遗忘门的线性注意力"。这一洞见统一了两个平行宇宙,也直接铺平了 Gated DeltaNet 的道路(下一章)。
Mamba 的选择机制:让参数随内容而变SELECTIVE SCAN

经典 SSM 的参数 \(A, B, C, \Delta\) 是时不变的——对所有 token 一视同仁,无法根据内容决定记住什么、忽略什么。Mamba 让 \(B, C, \Delta\) 依赖输入:

\[ B_t = s_B(x_t), \quad C_t = s_C(x_t), \quad \Delta_t = \text{softplus}\big(s_\Delta(x_t)\big) \]

其中 \(s_B, s_C, s_\Delta\) 是可学习的线性投影。效果是:\(\Delta_t\) 大 → 更关注当前输入("忘记"历史);\(\Delta_t\) 小 → 更多依赖历史状态。但由于参数变成输入依赖,递推不再是时不变的,无法直接用卷积并行——Mamba 为此设计了专门的硬件感知选择性扫描算法。

SSD 对偶:两个平行宇宙是同一件事STATE SPACE DUALITY

把 SSM 的递推展开成"卷积形式":

\[ y_t = \sum_{s=0}^{t} C_t\, \bar{A}^{\,t-s}\, \bar{B}_s\, x_s \]

对比线性注意力的展开 \(o_t = \sum_s \phi(q_t)\, \phi(k_s)\, v_s\)——\(\bar{A}^{t-s}\) 扮演的正是"随距离衰减的注意力分数"的角色。Mamba-2 利用这个对偶性设计了结构化矩阵乘法(SSD),让 SSM 也能像注意力一样用分块矩阵乘高效实现。这个对偶是第 08 章的钥匙:SSM 的衰减门 + DeltaNet 的精准写入,可以在同一个数学框架里合体。

SSM / Mamba 的世界观 输入 x₁ 状态 h h ← Āh + B̄x 输出 y₁ 递归传递 逐通道标量衰减:每个通道一个衰减率,向量状态 线性注意力 / DeltaNet 的世界观 token k,v 矩阵状态 S S ← S + kᵀu(外积) 读出 q·S key 定向写入:矩阵状态,可按 key 精确修改 Mamba-2 / SSD 的统一(2024) 标量门控 SSM ≡ 带标量遗忘门的线性注意力:两者是同一数学对象的两种参数化 → 自然的问题:SSM 的遗忘门 + DeltaNet 的精准写入,能不能合体?(第 08 章)
图 7-1(本文自绘):两个平行宇宙。SSM 维护向量状态、逐通道衰减;线性注意力维护矩阵状态、按 key 外积写入。Mamba-2 证明二者同源。

7.2 同一条赛道上的其他选手

架构年份核心思想一句话评价
RWKV2023把 RNN 重构成可并行训练的形式:token-shift + 指数衰减加权的"无 softmax 注意力"开源社区宠儿,证明 RNN 能训到 14B 级
RetNet2023微软提出 retention 机制:训练并行 / 推理递归 / 分块递归三种形式等价与线性注意力思路高度同构
Hyena2023用超长卷积 + 数据控制门控替代注意力长卷积路线的代表,后被 SSM 吸收
S4 / S4D2021+HiPPO 初始化 + 对角化 SSM理论奠基者,工程上被 Mamba 接棒
Mamba / Mamba-22023/24输入依赖的选择机制 + 硬件感知扫描;SSD 对偶理论线性化路线的另一面旗帜
xLSTM2024现代化 LSTM:指数门控 + 矩阵记忆(mLSTM)LSTM 发明者的"复仇",与 DeltaNet 思想相通
历 史 的 幽 默

1990 年代,RNN(LSTM)被 Transformer 革了命;2020 年代,研究者们前赴后继地把 RNN 请回来,只是为了干掉 Transformer 的 \(O(N^2)\) 和 KV Cache。但请注意结局:最终胜出的不是任何"纯种"架构,而是混血——下一章起你会看到,所有前沿模型都是"线性递归记忆 + 周期性全注意力"的混合体。


08Gated DeltaNet:学会遗忘

GATED DELTA NETWORKS · YANG ET AL. 2024

回到主线。DeltaNet 给了我们精确修改缓存的能力:每来一个携带新事实的 key,就能读出该位置旧信息并定向替换。但它有一个盲区——

它只能遗忘"有替代品的关联"。换话题时想清空一批旧记忆?想让不重要的事实随时间自然衰减来腾地方?Delta 规则做不到:它的橡皮擦一次只能擦一个 key 对准的那一小块。

反过来,如果我们做的是纯累加线性注意力,加"遗忘"反而简单——只需要一个控制遗忘的标量参数:

S_old = cache                          # (b,h,dh,dh)
S_new = k @ v                          # (b,h,dh,t)×(b,h,t,dh) → (b,h,dh,dh)
# cache = S_old + S_new          # 原来的纯累加
cache = alpha * S_old + S_new    # alpha: (b,1,t,1) 标量门 → (b,h,dh,dh),先衰减旧状态再全强度写入
遗忘门示意
原文配图遗忘门 α:每步先把整个状态矩阵按比例衰减,再写入新关联。状态不再无界增长。

这正是 Mamba-2 的贡献:先衰减旧缓存,再全量写入新内容,防止状态无界膨胀。但统一衰减有个明显缺陷——不区分关联的重要性。模型若想忘掉某个特定关联,所有关联都得跟着等比例变旧。而 Delta 规则恰好相反:能定点更新单个事实,却无法让其余事实批量衰减。

Gated Delta 规则 = Mamba 的门控 × Delta 规则的精准。引入参数 \(\alpha_t\):\(\alpha = 1\) 时退化为纯 Delta 规则;\(\alpha = 0\) 时清空记忆。更新式:

\[ S_t = \alpha_t\, S_{t-1}\big(I - \beta_t\, k_t k_t^\top\big) + \beta_t\, v_t k_t^\top \]

实现沿用第 06 章的 WY 重参数化,数学几乎不变,只多了一个 0 到 1 之间、由数据决定的标量来控制上一状态的衰减。难点仍在工程:分块并行形式里需要处理累积衰减——

Gated DeltaNet 代码
原文配图Gated DeltaNet 的分块代码。注意公式中的 γʳ/γⁱ 项:时刻 x 写入的 token 在 x+t 时刻被读出时,已经被乘了 αₓ·αₓ₊₁·…·αₓ₊ₜ——这是"前缀积",是前缀和在乘法世界的对应物,同样需要离散化技巧来并行。
Gated DeltaNet 架构
原文配图Gated DeltaNet 的最终形态:k/v/β/α 全部由输入投影产生,模型按内容自主决定"衰减多少、写多重、写什么"。
概 念 提 纯 · 记 忆 管 理 的 三 个 操 作

走到这里,线性化模型已经集齐了记忆管理的完整操作集:写入(外积累加,线性注意力)→ 定点修改(delta 规则)→ 整体衰减(门控)。这三者恰好对应人类记忆的"记住、纠正、淡忘"。还缺最后一块拼图——精细化的遗忘粒度,那是下一章 KDA 的事。

本 章 带 走
  • Delta 规则会改错但不会淡忘;Mamba 会淡忘但不会改错——Gated DeltaNet 两者兼得。
  • α 与 β 都由数据驱动:模型在上下文中实时决定记忆策略。
  • 累积衰减 = 乘法版前缀和,是分块并行化的主要工程障碍。

09KDA 与 Kimi Linear

KIMI DELTA ATTENTION · KIMI LINEAR · 2025

到这一阶段,研究者们开始尝试混合架构——在同一个模型里交错使用多种注意力形式,比如 Gated DeltaNet 与 Mamba 混搭,或线性层与全注意力层混搭。2025 年的 Kimi Linear 把这条路推到了一个标志性结论:

Kimi Linear 的 核 心 宣 称

在严格受控对比(同数据、同算力)下,Kimi Linear 全面超过全注意力基线——不是"接近",是反超;同时解码吞吐最高提升 6 倍,KV Cache 减少最多 75%。它把自己定位为"即插即用的架构替代品",而非长序列专用补丁。

9.1 KDA:细粒度门控

Kimi Delta Attention(KDA)对 Gated DeltaNet 的关键改进,是把遗忘从标量升级为逐通道向量:不再用一个 α 统一衰减整个状态矩阵,而是为每个特征通道学习独立的衰减率。

细粒度门控对比
原文配图标量门控 vs 细粒度门控:GDN 的 α 是每头一个标量(整块记忆同生共死);KDA 的 α 是逐通道向量(记忆的每个维度有自己的半衰期)——重要的通道衰减慢,噪声通道衰减快。

更新规则骨架不变,但代码里多了一次 reshape:

KDA 代码
原文配图KDA 的分块实现。注意 alpha.reshape(nb, C, d) 这一行——它承载了论文最重要的贡献:对记忆衰减的逐通道精细控制。

细粒度门控不是简单地"加参数"。它有明确的数学职能:给容量有限的记忆体装上分级回收制度——每个通道独立的衰减率,等价于让模型在特征空间里做软性的记忆分片管理。这正是第 05 章"超容量混叠"问题的工程化解法。

9.2 Kimi Linear 的完整配方

与 DeltaNet Transformer 并排对比,Kimi Linear 引入了三大变化:

  1. 混合架构:交错插入 MLA(Multi-head Latent Attention)层——3 层 KDA 配 1 层全注意力;
  2. MoE 替换 MLP:前馈网络换成混合专家层;
  3. α 投影扩容:通过 alpha 投影给 DeltaNet 机制增加容量(即上面的细粒度门控)。
Kimi Linear 架构
原文配图Kimi Linear 架构(右)vs DeltaNet Transformer(左):KDA 层承担大部分序列建模,周期性出现的 MLA 层提供无损的 softmax 精确检索;MLP 全部换成 MoE。

MLA 和 MoE 的细节放在第 10 章展开。这里先强调原文的一个重要论点:这不是盲目堆料。每一处新增容量都有具体的数学目的——逐通道 α 给的是记忆衰减的精细控制;周期性 MLA 补的是线性状态必然丢失的精确检索;MoE 加的是知识容量而不加每 token 的计算量。

Scaling law 依然有效,但容量必须加在正确的位置、以系统能消化的形式。这条演化线上的每个架构,都是在为前一个系统的某个具体缺陷定向扩容。

本 章 带 走
  • KDA = Gated DeltaNet + 逐通道遗忘门,记忆的每个维度有独立半衰期。
  • Kimi Linear = 3:1 混合 KDA/MLA + MoE,受控对比下反超全注意力,解码快 6 倍。
  • "混合"成为主旋律:线性递归负责效率,周期注意力负责保真。

10补课 III:MoE 与 MLA 深解

MIXTURE-OF-EXPERTS · MULTI-HEAD LATENT ATTENTION(本章为新增背景)

Kimi Linear 和 Kimi K3 的两根支柱——MoE 与 MLA——在原文里只是一笔带过。但它们是 2024–2026 年大模型军备竞赛的主战场(DeepSeek 的崛起几乎全靠这两样),值得各自一节。

10.1 MoE:稀疏化的艺术

回顾第 02 章的参数量分解:GPT-2 里约三分之二的参数在 MLP。MLP 被认为是模型的"知识仓库"(注意力负责路由信息,MLP 负责存储事实)。于是有个诱人的想法:把 MLP 复制很多份,但每个 token 只用其中几份——参数量(知识容量)暴涨,每 token 计算量几乎不变。这就是混合专家(Mixture-of-Experts)。

输入 token hidden h 路由器 softmax(W·h) 打分 专家 1 专家 2 ✓ 专家 3 专家 4 专家 5 ✓ 按门控权重加权求和 out = Σ gᵢ·Eᵢ(h)
图 10-1(本文自绘):MoE 前向。路由器为每个 token 给所有专家打分,只有 Top-K(图中 K=2)专家被激活,输出按门控权重加权。虚线专家本步不计算。

关键设计点与演化史:

  • 起源:Shazeer 等人 2017 年的 Sparsely-Gated MoE(LSTM 时代!),2021 年 Google 的 Switch Transformer(简化为 Top-1 路由)与 GShard 把 MoE 带上万卡规模。
  • 负载均衡:路由器容易"嫌贫爱富"——少数专家被疯狂选择、其余饿死,训练崩溃。经典解法是辅助损失(auxiliary load-balancing loss)强制均匀,但它会伤害模型质量;DeepSeek-V3 开创了无辅助损失路线:给每个专家一个动态偏置项,只影响路由选择、不进入梯度,均衡与质量兼得。
  • 细粒度 + 共享专家(DeepSeekMoE, 2024):把专家切得更小、数量更多(提升组合灵活性),并设置若干共享专家处理所有 token 的通用知识,路由专家只学专精知识——缓解知识冗余。Kimi K3 的 898 专家(2 共享 + 896 路由)正是这一思想的超大号版本。
  • 开源引爆点:Mixtral 8×7B(2023)证明中型 MoE 也能吊打同档稠密模型;此后 DeepSeek-V3(671B/激活 37B)、Qwen3-MoE、Kimi K2(1T/激活 32B)把 MoE 变成前沿模型默认形态。
  • 代价:显存必须装下全部专家(参数不减),训练通信复杂(专家并行 AllToAll),小 batch 推理时专家利用率低。MoE 省的是 FLOPs,不是显存。

10.2 MLA:把 KV Cache 压成精华

第 3.1 节说过 MLA 是 KV Cache 围剿战的最新武器,现在拆开它。GQA 的思路是"让 Q 头共享 K/V";MLA(DeepSeek-V2, 2024)的思路更激进:把所有头的 K、V 联合低秩压缩成一个潜向量 \(c_t\),只缓存它,用时再解压出各头的 K/V。

MLA 的投影结构:压缩-解压-吸收LATENT ATTENTION

标准 MHA:\(K = XW_K,\ V = XW_V\),需要缓存完整的 K、V。MLA 引入低维潜变量:

\[ c_t^{KV} = W^{DKV}\, h_t \;\in\; \mathbb{R}^{d_c} \qquad\text{(只缓存它和 RoPE 分量)} \] \[ k_t = W^{UK}\, c_t^{KV}, \qquad v_t = W^{UV}\, c_t^{KV} \]

典型配置:\(d_c = 512\),而标准 MHA 每 token 的 KV 缓存维度可能是 \(64 \times 128 = 8192\)——压缩比约 16 倍

矩阵吸收(absorption)是最精妙的一步:解压矩阵 \(W^{UK}\) 可以在推理前预先乘进 query 投影,\(W^{UV}\) 可以乘进输出投影——推理时根本不需要真的解压出 K/V,直接在压缩空间里算注意力。

解耦 RoPE(decoupled RoPE):RoPE 的旋转与"内容压缩"格格不入(旋转后的向量无法做低秩分解),所以 MLA 把位置信息拆到独立的少量维度(如 64 维)单独施加 RoPE,缓存里多存这一小段 \(k^R\),主压缩体保持纯粹的内容表示。

隐藏状态 h 维度 d(如 7168) 下投影 W^DKV 压缩 c + k^R 仅 ~576 维 上投影 W^UK → 各头 K (推理时吸收进 W^UQ) 上投影 W^UV → 各头 V (推理时吸收进输出投影) KV Cache 只存这个 ↓
图 10-2(本文自绘):MLA 的压缩-解压结构。缓存中只有一个低维潜向量(DeepSeek-V2 中 512 维 + 64 维 RoPE 分量),相比 MHA 压缩 90% 以上。

再叠加多头潜注意力对"头间冗余"的利用,MLA 做到了一个反常的结果:缓存比 GQA 小得多,效果却比 MHA 还好

为 什 么 MLA 配 合 线 性 注 意 力 是 绝 配

回到 Kimi Linear 的 3:1 混合:KDA 层状态固定、不产生 KV Cache;少数 MLA 层产生的缓存又极小——整个模型的长上下文推理成本被压到极低。这就是为什么"线性化 + MLA"成为 2025–2026 混合架构的黄金组合。第 11 章的 Gated MLA 还会再进化一步。

本 章 带 走
  • MoE:用路由把"参数量"与"计算量"解耦——加知识不加 FLOPs;核心难题是负载均衡。
  • MLA:低秩压缩 + 矩阵吸收 + 解耦 RoPE,缓存缩小 90%+ 而质量反超 MHA。
  • MoE 省训练/推理算力,MLA 省推理显存带宽——两者分别打击大模型成本的两条腿。

11Kimi K3:两万倍的终点(暂时)

KIMI K3 · 2026 · 2.8T 参数混合架构

所有铺垫到此汇合。Kimi K3 的语言骨干就是放大版的 Kimi Linear:包含 23 个四层大循环(macrocycle)——每个大循环里 3 层用 Kimi Delta Attention,第 4 层用 MLA;第 1 层用稠密前馈网络,其余全部换成潜空间 MoE。

相比 Kimi Linear,改动清单初看不起眼:

  • 规模大幅增长(至 2.8T 参数)
  • 每 12 层一次 Blockwise AttnRes(第 12 章详解)
  • MLA 的 query LoRA 与输出门控
  • 潜空间 MoE
  • SiTU 激活函数
  • Gated MLA

分工原则一如既往:KDA 提供恒定状态的递归记忆,周期性 MLA 层保留对上下文的全量 softmax 检索。一张简图统领全模型:

Kimi K3 整体架构
原文配图Kimi K3 简化架构图:23 × (3×KDA + 1×MLA) 的大循环结构,辅以 AttnRes、Gated MLA、潜空间 MoE 与 SiTU。

11.1 三个直接的变化:Gated MLA、潜空间 MoE、SiTU

Gated MLA:决定 MLA 检索出的特征有多少能进入残差流——用输入投影出一个门,与注意力输出逐元素相乘。本质是给"精确检索通道"也装上水龙头:检索虽准,但该放多少信息进主干,由上下文决定。

潜空间 MoE:传统 MoE 里,路由器用点积相似度把每个 token 发给若干专家。Kimi K3 共 898 个专家:2 个共享专家处理所有 token,剩余 896 个中由路由器为每个 token 选出 16 个。K3 的进一步创新是让专家在压缩的潜空间中工作:

潜空间 MoE
原文配图潜空间 MoE:输入先被下投影到共享专家的压缩空间,专家在其中前向,最终求和后再上投影回完整维度。专家前向的 FLOPs 几乎减半。

SiTU 激活:K3 还更换了专家内的激活函数。不再走"升维投影 → SiLU → 与门逐元素乘 → 降维"的老路,而是用 SiTU:

d = x.shape[-1] // 2                              # x: (..., 2d),前半 gate、后半 up
gate = x[..., :d].to(torch.float32)               # (..., d)
up = x[..., d:].to(torch.float32)                 # (..., d)
situ_a = self.beta * torch.tanh(gate / self.beta) * torch.sigmoid(gate)  # (..., d)
if self.linear_beta is not None:
    up = self.linear_beta * torch.tanh(up / self.linear_beta)            # (..., d)
return (situ_a * up).to(x.dtype)                # (..., d)

注意 tanh 项里有个可学习的 \(\beta\):门控的饱和区间变成了可学习的,模型可以按层调节激活的"软硬"程度。

工 程 现 实 · 融 合 算 子 的 重 要 性

这揭示了一个推理侧反复出现的难题:如果没有融合 kernel,新激活函数比原路径慢近 3 倍——数学上免费的东西,放到内存层级里全是读写。一个对冲优化是专家在压缩潜空间工作,前向快得多、FLOPs 几乎减半。架构创新必须与 kernel 工程协同落地,这是原文作者(Baseten 推理工程师)反复暗示的行业真相。

剩下的改动:query LoRA、输出门控、AttnRes

MLA 的 query 侧加 LoRA 低秩适配、输出加门控,以及每 12 层一次的 Blockwise Attention Residuals。AttnRes 只带来约 2% 的推理延迟,但换来两个重要收益:对早期表示的选择性检索(缓解残差稀释与隐藏状态数值增长),以及 1.25 倍的计算优势(等效算力下更强)。

最后一点值得停下来体会:AttnRes 和 MLA 从不同方向解决同一个底层限制。KDA 层状态恒定,必然丢弃信息;MLA 从序列维度(token 上下文)做无损检索补位,AttnRes 从深度维度(早期层的表示)做选择性检索补位。一个补横向记忆,一个补纵向记忆。

11.2 账本:1T 怎么变成 2.8T

第 02 章我们算了 GPT-2 的 124M,现在用同一套方法给 K3 对账。先回答一个常见的困惑:K3 的层数并没有比 K2 多几倍,参数怎么从 1T 涨到了 2.8T?答案藏在 MoE 的三项乘积里:

\[ \text{MoE 主导参数} \;=\; \text{MoE 层数} \times \text{每层专家数} \times \text{单专家参数} \]
项目Kimi K2Kimi K3对总参数的作用
总层数61(1 个 dense 层)93(1 个 dense 层)MoE 层实际 60 → 92,约 1.53×
路由专家 / 层384896约 2.33×
专家输入维7168LatentMoE 维度 3584K3 单专家输入更窄
专家中间维20483072K3 中间维更宽
每 token 选中8 / 384 + 1 共享16 / 896 + 2 共享影响激活计算,不直接改变总参数
官方总 / 激活1T / 32B2.8T / 104B总参数与单 token 路径是两套口径
K2:先算一个 SwiGLU 专家60 × 384 EXPERTS

单专家(gate、up、down 三张矩阵):\(P_{expert,K2} \approx 3 \times 7{,}168 \times 2{,}048 = 44{,}040{,}192 \approx 44.04\text{M}\)

60 个 MoE 层 × 每层 384 个路由专家:

\[ 60 \times 384 \times 44{,}040{,}192 = 1{,}014{,}686{,}023{,}680 \approx 1.015\text{T} \]

这是按维度估算的主导项——官方 1T 口径与之吻合。

K3:单专家更小,但专家网格大得多92 × 896 EXPERTS

单专家(潜空间输入 3584、中间维 3072):\(P_{expert,K3} \approx 3 \times 3{,}584 \times 3{,}072 = 33{,}030{,}144 \approx 33.03\text{M}\)

92 个 MoE 层 × 每层 896 个路由专家:

\[ 92 \times 896 \times 33{,}030{,}144 = 2{,}722{,}740{,}830{,}208 \approx 2.723\text{T} \]

再加共享专家、KDA/MLA 投影、embedding、dense 层与视觉编码器等,官方总量为 2.8T。

三项因子复核:\((92/60) \times (896/384) \times (33.03/44.04) \approx 1.533 \times 2.333 \times 0.750 \approx 2.68\)。所以答案不是"层数变多",而是 MoE 层数 × 专家密度 × 单专家大小三项一起变——单专家虽然缩小了 25%,网格的扩大远远盖过了它。(注:此为按公开维度做的主导项近似,具体 checkpoint 还涉及实现细节与计入口径。)

维度GPT-2 (2019)Kimi K3 (2026)
参数量1.24 亿2.8 万亿(MoE,激活约 104B)
层数12 层均质块23 大循环 × (3 KDA + 1 MLA),异质层
注意力MHA + KV Cache(O(N) 增长)KDA 恒定状态 + Gated MLA 潜缓存
FFN稠密 2 层 MLP潜空间 MoE:898 专家,2 共享 + 16 路由
激活GELUSiTU(可学习饱和区)
位置编码可学习绝对位置嵌入RoPE 家族 + 递归内禀位置感
残差恒等累加恒等累加 + 每 12 层 AttnRes 选择性混合
本 章 带 走
  • K3 = Kimi Linear 的定向扩容:3:1 KDA/MLA 大循环 + 潜空间 MoE + Gated MLA + SiTU。
  • 2.8T ≈ 92 MoE 层 × 896 专家 × 33M/专家 + 其余部件;激活参数仅约 104B。
  • 每个改动都对应一个具体缺陷:检索精度、记忆粒度、算力效率、数值稳定。

12AttnRes:跨深度的注意力

BLOCK ATTENTION RESIDUALS · 残差连接的再革命

(原文此节致谢 @chloey3k 的协助。)每次前向,输入穿过一叠层——每层是一个注意力块(KDA 或 MLA)加一个 MLP/MoE 块。传统上,每层的输入都是原始嵌入与之前所有层输出的等权求和

\[ h_l = h_1 + \sum_{i=1}^{l-1} f_i(h_i) \]

其中 \(h_i\) 是第 i 层输入,\(h_1\) 是当前 token 的嵌入,\(f_i(h_i)\) 是第 i 层输出。问题在于:没有选择性。不同类型的层收到同一份"大杂烩"状态,哪怕它们想加权不同的历史;且由于递推是纯加法的,越靠后的层越要学出更大的输出才能影响累积残差——这既造成"残差稀释"(早期信息被淹没),也导致隐藏状态数值持续增长、训练不稳。

AttnRes 的做法:不再一视同仁,而是给求和式每一项乘上专门权重:

\[ h_l = \alpha_0 \cdot h_1 + \sum_{i=1}^{l-1} \alpha_i \cdot f_i(h_i) \]

每个权重 \(\alpha_i\) 由 query-key 点积算出:query 按层学习,key 和 value 来自更早的残差流状态;分数归一化到和为 1,再加权组合。模型因此不必只条件于紧邻的前一层——每层都能用学出来的 query,从整个深度历史中检索对当前计算最有用的表示。这是把"注意力"从序列维度(token 之间)推广到了深度维度(层与层之间)。

AttnRes 示意
原文配图AttnRes:块边界处,当前表示作为 query,对所有历史块表示打分(softmax 归一),加权混合后送入下一段。残差流从"只加不选"变成"可检索的记忆"。

在块粒度上应用同一思想:一个"块"是 12 个解码层内注意力与 MLP 输出的逐元素累加,作为单个深度表示存下来,供后续 AttnRes 混合。每层都做残差注意力太贵,只在固定块边界做能以很小代价捕获大部分收益——K3 里每个边界相隔 12 层,23 个四层大循环共产生 8 个 AttnRes 块,并提升了推理速度。

下面这段可能是 block_attn_res 函数里最重要的部分:

V = torch.stack(blocks + [partial_block])       # [N+1, B, T, D] 全部历史块
K = norm(V)                                     # [N+1, B, T, D],归一化后用于打分
logits = torch.einsum('d, n b t d -> n b t', proj.weight.squeeze(), K)  # [N+1, B, T],每个历史块一个分数
h = torch.einsum('n b t, n b t d -> b t d', logits.softmax(0), V)        # softmax 沿块维 → 加权和 (B, T, D)
return h                                        # (B, T, D)

四行代码:堆叠所有历史块 → 归一化后用一个学习向量打分 → 在"块"这个维度上做 softmax → 加权混合。注意 softmax 是沿第 0 维(块维度)做的——竞争的对象不是 token,而是深度

概 念 提 纯 · 注 意 力 的 三 次 迁 徙

回望全文,注意力机制发生了三次"迁徙":① token 之间(2017,标准注意力);② 记忆状态与查询之间(2020+,线性注意力/DeltaNet,注意力退化为状态读写);③ 层与层之间(2026,AttnRes)。同一个"查询-匹配-加权"原语,在序列、记忆、深度三个维度上各开了一次花。

本 章 带 走
  • 残差流的纯加法递推导致稀释与数值膨胀;AttnRes 给每层配上对深度历史的检索能力。
  • 块粒度(每 12 层一次)是收益与成本的平衡点;约 2% 延迟换 1.25× 等效算力。
  • MLA 补序列维度的精确检索,AttnRes 补深度维度的精确检索——两者共同兜底 KDA 的信息损失。

13补课 IV:改变战局的优化方法

SCALING LAWS · MUON · PAGEDATTENTION · SPECULATIVE DECODING · RLHF/GRPO(本章为新增背景)

架构只是故事的一半。过去七年里,真正让 LLM 从实验室走向亿万用户的,还有一批不改变模型形态、却改变成本曲线和能力上限的方法。按训练、推理、对齐三条线梳理。

13.1 训练侧:把每一度电花在刀刃上

Scaling Laws 与 Chinchilla(2020–2022)

OpenAI 2020 年的 scaling law 论文证明:损失随参数、数据、算力呈平滑幂律下降——能力可以预测,这给了行业"敢烧钱"的底气。但 DeepMind 2022 年的 Chinchilla 纠正了一个广泛错误:此前模型普遍"太大、喂太少";最优配方是每个参数配约 20 个 token。700 亿的 Chinchilla 吊打 2800 亿的 Gopher。此后 Llama 时代干脆"超量喂食"(小模型吃几万亿 token),因为推理成本才是长期大头。这条思想脉络一直延续到今天:先定推理预算,再倒推训练配方。

优化器:Adam → Muon

AdamW 统治了八年。2024 年 Keller Jordan 的 Muon(用于 NanoGPT 竞速)以"对隐藏层权重矩阵做 Newton-Schulz 正交化更新"的思路,在多项基准上把样本效率提升约 2 倍;Moonshot 将其扩展到大模型训练(Kimi K2 使用带 QK-clip 的 MuonClip),Muon 由此进入一线武器库。embedding 与输出头等仍用 Adam——混合优化器成为新常态。

数值精度与系统

  • BF16 混合精度(2020+):训练标配;FP16 的溢出毛病被 BF16 的大动态范围治好。
  • FP8 训练(DeepSeek-V3, 2024):首次在超大规模上验证 FP8 混合精度可行,配合逐块缩放,训练成本再砍一刀。
  • 并行策略:数据/张量/流水/专家/序列并行的组合学(Megatron、ZeRO、FSDP)——万卡集群的有效算力利用率(MFU)从 30% 一路卷到 50%+。
  • 数据配方:去重、质量过滤、领域配比、退火(annealing)、合成数据——业内共识是"数据质量与配比的贡献不低于架构",只是各家秘方极少公开。

13.2 推理侧:吞吐量的战争

训练一次性花钱,推理性命攸关地天天花钱。这一战线的主角:

方法年份一句话原理影响
连续批处理2022+不等整批跑完,token 级动态插拔请求GPU 利用率数倍提升,所有推理框架标配
PagedAttention / vLLM2023像操作系统管理虚拟内存一样分页管理 KV Cache,消除碎片与预留浪费吞吐 2–4×,开源推理的事实标准
投机解码2023小模型起草多 token,大模型一次并行验证,数学上无损解码提速 2–3×;EAGLE/Medusa 等变体落地广泛
权重量化 GPTQ/AWQ2023权重压到 4bit 甚至更低,激活保持高精度消费级显卡跑 70B 成为可能
KV Cache 量化2024+缓存压到 8bit/4bit长上下文服务成本大降,与 MLA 叠加使用
前缀缓存2023+相同前缀(系统提示等)的 KV 复用多轮对话与 Agent 场景的隐形加速器
蒸馏经典/2023+大模型当老师教小模型(含推理链蒸馏)R1 蒸馏系列让小模型获得推理能力
分离式架构 PD 分离2024+Prefill(算力密集)与 Decode(带宽密集)拆到不同硬件池大规模推理服务的新范式

13.3 对齐与推理:RL 的第二春

RLHF 时代(2022–2023)

InstructGPT/ChatGPT 的三段式——监督微调(SFT)→ 训练奖励模型(RM)→ PPO 强化学习——让模型从"会续写"变成"会听话",是 LLM 产品化的关键一跃。但 PPO 训练又贵又脆,于是 DPO(2023)用闭式解直接对偏好数据做分类式优化,免去了奖励模型与在线 RL,一度成为开源社区标配。

推理模型时代(2024– )

OpenAI o1 揭示了新 scaling 维度:测试时算力(test-time compute)——让模型生成长思维链(CoT)再作答,数学/代码能力陡增。DeepSeek-R1(2025)把路径开源化:用 GRPO(组相对策略优化,免去 value 网络)对"可验证奖励"(数学答案对不对、代码过不过测试)直接做大规模 RL,涌现出自我反思与长链推理——RLVR(可验证奖励强化学习)成为后训练的新主菜。此后 Kimi k1.5、Qwen3 等全部跟进,"预训练 scaling + RL scaling + 测试时 scaling"三条曲线并行。

参数高效微调

LoRA(2021)冻结主干、只训低秩旁路矩阵,把微调成本砍掉几个数量级;QLoRA(2023)叠加 4bit 量化,让单卡微调 65B 成为现实。LoRA 的思想还以"MLA query LoRA"等形式回流进主干架构(见第 11 章)——低秩,是贯穿本书的隐藏主题之一。

一 张 图 看 懂 三 条 成 本 曲 线

架构(KDA/MLA/MoE)决定单位能力的理论成本;训练方法(scaling law/Muon/FP8)决定抵达能力的实际开销;推理优化(vLLM/投机解码/量化)决定能力的边际售价。七年 22,580 倍的规模扩张背后,是这三条曲线同时被压低——缺任何一条,今天的模型经济学都不成立。


14全景时间线 2017–2026

TIMELINE · 从 TRANSFORMER 到 KIMI K3
2017TransformerMoE 雏形 2018–19BERT / GPT-2MQA 2020GPT-3 · Scaling Law线性注意力 2021RoPE · S4DeltaNet(FWP) 2022ChinchillaFlashAttention 2023ChatGPT · LlamaGQA · Mamba · DPO 2024MLA · GDNo1 · Muon 2025R1/GRPOKimi Linear/KDA 2026Kimi K32.8T · 混合架构 红色 = 主线里程碑;绿色 = 当前最前沿。虚线十年,实做的事只有两件:让记忆更高效,让容量长在正确的地方。
图 14-1(本文自绘):2017–2026 大模型架构与关键方法时间线。
年份架构线方法线标志性事件
2017Transformer(MHA、正弦位置、Post-LN)Sparsely-Gated MoE《Attention Is All You Need》
2018–19BERT、GPT-2(Pre-LN、decoder-only 确立)MQA"大模型"概念萌芽
2020GPT-3 175B;线性注意力Scaling Laws规模即能力时代开启
2021RoPE、S4、DeltaNet(FWP)Switch Transformer;LoRA线性化与稀疏化两条暗线铺开
2022Chinchilla;FlashAttention;RLHFChatGPT 引爆世界
2023GQA、Mamba、RWKV、RetNetDPO;vLLM;投机解码;QLoRA开源模型大爆发(Llama)
2024MLA、Gated DeltaNet、Mamba-2、xLSTMMuon;FP8 训练;o1(测试时 scaling)DeepSeek-V2 重新定义性价比
2025KDA / Kimi Linear(反超全注意力)GRPO/RLVR;R1;混合架构主流化推理模型元年
2026Kimi K3:KDA+Gated MLA+潜空间 MoE+AttnRes22,580 × GPT-2

15总结:规模之外

WHAT ACTUALLY CHANGED

至此,从 GPT-2 到 Kimi K3 的旅程走完了。

核心变化从来不是规模本身。每一步架构演进,改变的都是三件事之一:模型存储什么、如何更新这些状态、以及如何检索那些固定大小状态留不住的信息。

Kimi K3 把四种机制缝合为一个系统:恒定状态的递归记忆(KDA)、周期性的 softmax 精确检索(Gated MLA)、稀疏的专家容量(潜空间 MoE)、跨深度的选择性残差访问(AttnRes)。每一份新增的容量都被花在有明确职能的地方。

全 文 的 一 句 话

一个固定容量的联想记忆(维度固定)必然需要驱逐策略——纯加法的线性写入在容量耗尽后只会不断叠加干扰。因此可学习的选择机制(门控、路由、衰减)是必需品;而注意力,依然是人类已发明的最有效的"选择性读取"机制。

最后,给几乎零基础起步、一路读到这里的你一张最小知识地图:

  • 骨架没变:嵌入 → N × (注意力 + FFN + 残差) → LM head。七年只动了内脏。
  • 主线矛盾:KV Cache(无损但无限)vs 固定状态(有界但有损)。全部线性化工作都在逼近"固定状态做出无损检索的效果",最终答案是混合
  • 记忆三操作:写入(外积)→ 定点修改(Delta 规则)→ 衰减(门控);KDA 把衰减细化到逐通道。
  • 效率三杠杆:MoE(稀疏参数)、MLA(压缩缓存)、FlashAttention/分块/WY(IO 与并行)。
  • 能力三曲线:预训练 scaling、RL scaling(GRPO/RLVR)、测试时 scaling。

下一次当你看到"某某新模型发布"时,试着问自己三个问题:它的记忆存在哪?它如何决定遗忘?它的容量加在了什么地方?——答案大概率就藏在这篇文章讲过的某条演化线上。


A附录 A:Softmax 数值稳定性与 Online Softmax 证明

APPENDIX · NUMERICAL STABILITY · 给想彻底搞懂的读者

A.1 为什么需要减去最大值

直接计算 \(e^{x_i}\) 的问题是:当 \(x_i\) 较大时,\(e^{x_i}\) 会溢出 FP16/FP32 的表示范围(FP16 在 \(x > 11\) 左右就溢出了)。设 \(m = \max(x_1, \ldots, x_n)\),数值稳定的 Softmax 为:

\[ \text{softmax}(x_i) = \frac{e^{x_i - m}}{\sum_j e^{x_j - m}} \]

为什么等价?分子分母同除 \(e^m\):

\[ \frac{e^{x_i - m}}{\sum_j e^{x_j - m}} = \frac{e^{x_i} / e^m}{\sum_j e^{x_j} / e^m} = \frac{e^{x_i}}{\sum_j e^{x_j}} \]

减去 \(m\) 后,最大指数为 \(e^0 = 1\),不会溢出;最小的指数 \(e^{x_{\min} - m} \approx 0\),不影响求和。

A.2 Online Softmax 的正确性证明(归纳法)

命题:每步都有 \(\ell_i = \sum_{j=1}^{i} e^{x_j - m_i}\)PROOF BY INDUCTION

归纳基础:\(i = 0\) 时,\(\ell_0 = 0\),成立。

归纳步骤:假设 \(\ell_{i-1} = \sum_{j=1}^{i-1} e^{x_j - m_{i-1}}\)。当 \(m_i > m_{i-1}\)(即 \(x_i\) 是新的最大值):

\[ \begin{aligned} \ell_i &= \ell_{i-1} \cdot e^{m_{i-1} - m_i} + e^{x_i - m_i} \\ &= \sum_{j=1}^{i-1} e^{x_j - m_{i-1}} \cdot e^{m_{i-1} - m_i} + e^{x_i - m_i} \\ &= \sum_{j=1}^{i-1} e^{x_j - m_i} + e^{x_i - m_i} = \sum_{j=1}^{i} e^{x_j - m_i} \end{aligned} \]

当 \(m_i = m_{i-1}\) 时,\(e^{m_{i-1} - m_i} = 1\),同样成立。证毕。

这个"换底"技巧意味着:无论把输入切成多少块、按什么顺序扫描,只要每块到来时重缩放一次累加器,最终结果就是精确 Softmax——FlashAttention 的分块正确性由此而来。注意力输出还维护一个类似的加权威组 \(acc_i = acc_{i-1} \cdot e^{m_{i-1}-m_i} + e^{x_i - m_i} v_i\),原理完全相同。


B附录 B:Delta 规则重参数化推导

APPENDIX · WY REPRESENTATION · 理解第 06 章的钥匙

DeltaNet 的循环更新:

\[ S_t = S_{t-1} + k_t^\top\, \beta_t\, (v_t - k_t S_{t-1}) \]
四步推导:从递推到并行形式FULL DERIVATION
  1. 展开括号 \[ S_t = S_{t-1} + \beta_t k_t^\top v_t - \beta_t k_t^\top k_t S_{t-1} \]
  2. 提取 \(S_{t-1}\) \[ S_t = S_{t-1}\big(I - \beta_t k_t^\top k_t\big) + \beta_t k_t^\top v_t \] 注意 \(k_t \in \mathbb{R}^{1 \times d_k}\),所以 \(k_t^\top k_t \in \mathbb{R}^{d_k \times d_k}\) 是一个 rank-1 矩阵。
  3. 展开递推 \[ \begin{aligned} S_1 &= S_0(I - \beta_1 k_1^\top k_1) + \beta_1 k_1^\top v_1 \\ S_2 &= S_1(I - \beta_2 k_2^\top k_2) + \beta_2 k_2^\top v_2 \\ &= S_0(I - \beta_1 k_1^\top k_1)(I - \beta_2 k_2^\top k_2) + \beta_1 k_1^\top v_1 (I - \beta_2 k_2^\top k_2) + \beta_2 k_2^\top v_2 \end{aligned} \]
  4. 一般形式 \[ S_C = S_0 \cdot \prod_{t=1}^{C} \big(I - \beta_t k_t^\top k_t\big) + \sum_{t=1}^{C} \beta_t k_t^\top v_t \prod_{s=t+1}^{C} \big(I - \beta_s k_s^\top k_s\big) \] 这个展开式说明:块内所有 C 个 token 的状态更新可以通过前缀乘积一次性计算。实现中通过下三角矩阵 \(T\)(其中 \(T_{ij} = -\beta_i\, k_i k_j^\top\),\(i > j\))和向量化的前代法(forward substitution)高效算出整块的修正量。
与 Householder 变换的关系

Householder 反射矩阵的形式是 \(H = I - 2\, vv^\top / \|v\|^2\)。DeltaNet 中的 \(I - \beta k k^\top\) 是一个广义 Householder 变换——\(\beta\) 不是固定的 \(2/\|k\|^2\),而是数据依赖的。这类变换在数值线性代数中有极好的性质(当 \(\beta = 2/\|k\|^2\) 时是正交变换,天然数值稳定),这也是为什么 DeltaNet 的递推可以展开成紧凑的 WY 表示——它本来就是一串"半个" Householder 反射的乘积。

这个推导还揭示了一个优美的几何画面:每一步 delta 更新,都是让状态矩阵在一个由当前 key 张成的方向上做一次"受控反射"——擦除该方向上的旧投影,写入新值。记忆的编辑,原来是几何变换的复合。