最近把 DeepSeek V2 到 V4 的论文看了一遍,顺手整理成这篇笔记。下面按版本整理训练与架构技术;这里主要只写了算法,系统工程优化(精度、通信、调度等)打算后面再写。(ds参与了本文内容和排版优化)
DeepSeek-V2:MLA 与 DeepSeekMoE
V2 的两大架构支柱分别是:
- MLA(Multi-head Latent Attention,多头潜在注意力):压缩 KV cache,让推理更省内存
- DeepSeekMoE:把 FFN 换成稀疏专家,让训练更省钱
论文:DeepSeek-V2(236B 总参数,其中 21B 被激活,上下文 128K)
这部分主要想讲清楚两件事:一是 MLA 为什么能把 KV cache 压进一个低维向量,以及它为什么必须把位置信息单独拆开处理;二是 DeepSeekMoE 比传统 MoE 多了哪两条关键设计。
背景:大模型的两个瓶颈
一个大模型要变大、上下文要变长,会撞上两个显存/算力瓶颈:
- KV cache 内存:每个 token 都要存一份 key 和 value,序列越长,占的内存线性增长。
- FFN 计算量:Decoder-only Transformer 每一层大致是
x → Attention(+ 残差) → FFN / MoE(+ 残差)其中 FFN 的参数量和计算量都占大头,模型越大越贵。
V2 的思路就是各打各的:MLA 治 KV cache,DeepSeekMoE 治 FFN。
Part 1: 多头潜在注意力(MLA)
标准 MHA 为什么要存那么多
先看标准的多头注意力 MHA。输入 $h_t$,投影出 query、key、value:
$$ q_t = W^{Q} h_t $$$$ k_t = W^{K} h_t $$$$ v_t = W^{V} h_t $$推理时,历史每个 token 的 $k_t$、$v_t$ 都要存下来(就是 KV cache),后面每个新 token 都要和它们做注意力。序列一长,这一份缓存就非常占显存。
低秩联合压缩:把 K、V 压进一个潜在向量
MLA 的关键是低秩联合压缩:先用一个降维矩阵把 $h_t$ 压成一个很小的向量 $c_t^{KV}$,缓存时只存这个向量;等要用的时候,再各自通过一个升维矩阵恢复出 $k$、$v$。
$$ c_{t}^{KV} = W^{DKV} h_t $$$$ k_{t}^{C} = W^{UK} c_{t}^{KV} $$$$ v_{t}^{C} = W^{UV} c_{t}^{KV} $$这里有两个关键词:
- 联合:K 与 V 共用同一个 $c^{KV}$,一份缓存当两用。
- 低秩:经过 $d_c$ 维的瓶颈,且 $d_c \ll n_h d_h$(头的总数 × 头维度),所以缓存从「两个大头」变成「一个小头」,内存省下非常多。
此外,query 也压缩了(省训练激活,不省缓存)
$$ c_{t}^{Q} = W^{DQ} h_t $$$$ q_{t}^{C} = W^{UQ} c_{t}^{Q} $$注意,query 的压缩不是为了省缓存——推理时 query 是「当前正在生成的 token」现算的,根本不需要缓存。它省的是训练时的激活内存(激活随 batch × 序列长度增长,训练里是显存大户)。
解耦 RoPE:为什么位置信息必须单独拆开
压缩本身不难,难点在于位置编码。先看如果不拆开会发生什么。
注意力分数只关心 query 和 key 的点积:
$$ \mathrm{score} = q_t^{\top} k_j = q_t^{\top}\, W^{UK}\, c_j^{KV} $$矩阵乘法是结合的,所以可以把 $W^{UK}$ 挪到 $q_t$ 那边:
$$ \mathrm{score} = \bigl(W^{UK\top} q_t\bigr)^{\top} c_j^{KV} = q'_t{}^{\top}\, c_j^{KV} $$这里 $q'_t = W^{UK\top} q_t$ 和位置 $j$ 无关——新 token 来了,算一次 $q'_t$,就能直接和所有缓存的压缩向量 $c_j^{KV}$ 做点积。这样只需要缓存小向量 $c_j^{KV}$,不需要展开。
但一旦对 key 施加 RoPE,就不成立了。施加 RoPE 之后注意力分数变成:
$$ \mathrm{score} = q_t^{\top} R_j\, W^{UK}\, c_j^{KV} $$现在想把 $R_j W^{UK}$ 吸收到 $q_t$ 那边:
$$ \mathrm{score} = \bigl(W^{UK\top} R_j^{\top} q_t\bigr)^{\top} c_j^{KV} $$注意:被吸收后的有效 query 变成了 $W^{UK\top} R_j^{\top} q_t$,里面带着 $R_j^{\top}$,是随位置 $j$ 变化的。
所以对每一个前缀位置 $j$,都得用各自不同的 $R_j^{\top}$ 去变换 query——没有办法提前算好一个 query 复用于所有位置,也就无法只缓存小向量了。
论文中给出的解决办法是解耦:让位置信息走一条单独的支路,不经过压缩。
位置支路:q^R 与 k^R
解耦之后,query 分成内容和位置两部分:
$$ q_t^{R} = \mathrm{RoPE}(W^{QR} c_t^{Q}) $$其中 $c_t^Q = W^{DQ}h_t$ 是 query 的压缩潜在向量($d'_c$ 维,很小)。这里直接复用前面为省激活内存而压出来的 $c_t^Q$,再投影、切头、做 RoPE:
$$ c_t^Q \in \mathbb{R}^{d'_c} \xrightarrow{W^{QR}\ \in\ \mathbb{R}^{d_h^R n_h \times d'_c}} \text{切成 } n_h \text{ 个头} \xrightarrow{\mathrm{RoPE}} q_{t,1}^R, \ldots, q_{t,n_h}^R $$每个头有自己的位置 query $q_{t,i}^R \in \mathbb{R}^{d_h^R}$。
key 的位置支路则是:
$$ k_t^{R} = \mathrm{RoPE}(W^{KR} h_t) $$注意这里直接用 $h_t$,不经过压缩。为什么?
key 和 query 不一样,key 是每个历史 token 都要存下来的。如果让 $k^R$ 也走低秩压缩(从 $c_t^{KV}$ 展开出来),那它就和内容支路纠缠在一起——缓存的 $c_t^{KV}$ 既要展开成 $k^C$ 又要展开成 $k^R$,位置信息搭了内容压缩的「便车」。而且一旦 $k^R$ 依赖压缩投影,想复用它做别的优化(比如把 $W^{KR}$ 吸收进 query)又会遇到旋转矩阵夹在中间的老问题。
所以位置支路不依赖任何压缩流。
还有一点:所有头共享同一条 $k_t^R$。「token 隔了多远」是客观事实,和哪个头无关。不管这个头在关注语义、语法还是实体,它对「谁离谁近」的认知应该是一致的。
拼接与注意力
最后把内容、位置两部分拼起来:
$$ q_{t,i} = [q_{t,i}^{C};\, q_{t,i}^{R}] $$$$ k_{t,i} = [k_{t,i}^{C};\, k_{t}^{R}] $$注意力(value 用 content $v^{C}$):
$$ o_{t,i} = \sum_{j=1}^{t} \mathrm{Softmax}_j\!\left( \frac{q_{t,i}^{\top} k_{j,i}}{\sqrt{d_h + d_h^{R}}} \right) v_{j,i}^{C} $$$$ u_t = W^{O}[o_{t,1};\ldots;o_{t,n_h}] $$点积可以拆成内容和位置两项:
$$ q_{t,i}^{\top} k_{j,i} = \underbrace{(q_{t,i}^{C})^{\top} k_{j,i}^{C}}_{\text{内容}} + \underbrace{(q_{t,i}^{R})^{\top} k_{j}^{R}}_{\text{位置(RoPE)}} $$MLA 计算流(示意)
h_t
│
├─► W^DQ ► c^Q ─┬─► W^UQ ► q^C ─┐
│ └─► W^QR ► RoPE ► q^R ─┼─ concat ► q_i
│ │
├─► W^DKV ► c^KV ─┬─► W^UK ► k^C ─────┼─ concat ► k_i
│ └─► W^UV ► v^C ──────► 加权用 v^C
│
└─► W^KR ► RoPE ► k^R (全头共享)MLA 的收益是三件事叠加——缓存只存小向量 $c^{KV}$(省内存)、query 压缩(省激活)、位置走独立支路(保证解耦设计可用)。
Part 2: DeepSeekMoE
从稠密 FFN 到稀疏 MoE
传统稠密 LLM 里,每个 token 都要过完整的 FFN:
$$ \mathrm{FFN}(u) = W_{\mathrm{down}}\,\sigma\!\big(W_{\mathrm{up}} u\big) $$层输出(残差):
$$ h'_t = u_t + \mathrm{FFN}(u_t) $$传统 MoE 的思路是把一层 FFN 复制成 $N$ 个专家 $\mathrm{FFN}_1,\ldots,\mathrm{FFN}_N$,外加一个路由器(router / gate):
- 对 token $u_t$ 算与各专家的亲和分数 $s_{i,t}$;
- 只取 top-$K$ 个专家真正前向;
- 用门控权重把专家输出混合回去。
这里的「传统」指 GShard / Switch / Mixtral 一类主流稀疏 FFN。
DeepSeekMoE 的两条关键思想
DeepSeek-V2 在传统 MoE 上做了两点改动:
- 更细粒度的专家切分:专家更多、更小,特化更强、组合更丰富;
- 隔离部分共享专家:通用知识由 shared 专家兜底,减轻路由专家之间的知识冗余。
设 $u_t$ 为第 $t$ 个 token 的 FFN 输入,$h'_t$ 为输出:
$$ \begin{aligned} h'_t &= u_t &+ \sum_{i=1}^{N_s} \mathrm{FFN}_{i}^{(s)}(u_t) &+ \sum_{i=1}^{N_r} g_{i,t}\, \mathrm{FFN}_{i}^{(r)}(u_t) \end{aligned} $$共享专家始终计算(没有 top-$k$),路由专家才做 top-$k$ 选择。门控:
$$ g_{i,t} = \begin{cases} s_{i,t}, & s_{i,t} \in \mathrm{Topk}\big(\{s_{j,t}\mid 1\le j\le N_r\}, K_r\big) \\ 0, & \text{otherwise} \end{cases} $$亲和度(token 与专家的相似度):
$$ s_{i,t} = \mathrm{Softmax}_{i}\!\left( u_t^{\top} e_i \right) $$其中 $e_i$ 是第 $i$ 个路由专家的中心向量。
| 符号 | 含义 |
|---|---|
| $N_s$ | 共享专家个数(始终计算,无 top-$k$) |
| $N_r$ | 路由专家个数 |
| $\mathrm{FFN}_i^{(s)}$ / $\mathrm{FFN}_i^{(r)}$ | 第 $i$ 个共享 / 路由专家 |
| $K_r$ | 每个 token 激活的路由专家数 |
| $e_i$ | 第 $i$ 个路由专家的中心向量 |
| $g_{i,t}$ / $s_{i,t}$ | 门控值 / 亲和度 |
数据流
u_t
│
├─► 全部共享专家 FFN^{(s)}_1 … FFN^{(s)}_{N_s} (永远算)
│
├─► 亲和度 s_{i,t} = Softmax(u_t · e_i) (对 N_r 个路由专家)
│ │
│ └─► TopK → 只保留 K_r 个 g_{i,t}
│ │
│ └─► 选中的 FFN^{(r)} 前向
│
└─► h'_t = u_t + Σ shared + Σ g · routedDeepSeekMoE 用「更多更小的路由专家」换更丰富的组合能力,再用「共享专家兜底」减掉路由专家之间的知识冗余。
DeepSeek-V3:无辅助损失负载均衡与 MTP
V3 在 V2 的基础上没有动架构主干,而是优化训练:
- 无辅助损失负载均衡:让 MoE 专家负载均匀,又不损伤性能
- 多 token 预测(MTP):训练信号更稠密,还顺带让推理更快
后训练从 DeepSeek-R1 蒸馏知识。
论文:DeepSeek-V3
Part 1: DeepSeekMoE 与无辅助损失负载均衡
问题:专家负载不均
MoE 里专家负载不均会导致两个后果:
- 路由崩溃:少数专家被疯狂选中,多数专家学不到东西;
- 专家并行效率低:不同 GPU 上的专家干活量差很多,快的等慢的。
传统方案靠 auxiliary loss(辅助损失) 约束负载——在损失里加一项惩罚,逼路由器均匀分配。但辅助损失和主任务损失是打架的:辅助损失权重越大,负载越均匀,但模型本身的表现越差。
V3 与 V2 的一个差异:sigmoid 亲和度
V3 的亲和度计算从 softmax 换成了 sigmoid,并且只在选中的专家上做归一化得到门控值:
$$ s_{i,t} = \mathrm{Sigmoid}(\mathbf{u}_t^{\top} \mathbf{e}_i) $$无辅助损失:给每个专家加一个动态偏置
V3 的核心做法是给每个专家引入一个偏置 $b_i$,加到亲和度上再做 top-$K$ 路由:
$$ g'_{i,t} = \begin{cases} s_{i,t}, & s_{i,t}+b_i \in \mathrm{Topk}(\{s_{j,t}+b_j\mid 1\le j\le N_r\}, K_r), \\ 0, & \text{otherwise}. \end{cases} $$关键点:$b_i$ 仅用于路由决策,和 FFN 输出相乘的门控值仍然来自原始 $s_{i,t}$——偏置只影响「谁被选中」,不影响「选中后权重多少」。
训练中持续监控每个 step 整 batch 的专家负载:
- 某专家过载,就把它的 $b_i$ 减 $\gamma$;
- 欠载,就加 $\gamma$。
$\gamma$ 是偏置更新速度(bias update speed)。动态调整让负载在训练全程保持均衡,而且没有辅助损失去拖累性能。
序列级辅助损失:兜底
主策略虽然是无辅助损失,但为了防止单条序列内的极端不均衡,仍保留了一个极小权重的序列级平衡损失作为补充防线。
节点受限路由:控制通信
每个 token 最多发往 $M$ 个节点——按各节点上专家最高的 $K_r/M$ 个亲和度之和来选节点。在这个约束下,训练框架几乎可以实现完全的计算—通信重叠。
不丢弃 token
有效的负载均衡让全程训练负载良好,所以 V3 训练不丢弃任何 token;推理侧也有专门部署策略保证均衡,推理也不丢 token。
为什么 V2 丢弃、V3 不丢弃?
V2 丢弃 token 是因为它的负载均衡手段只能「鼓励」均衡、无法严格保证,负载不均会造成算力浪费,所以用「丢弃」来兜底;V3 用动态专家偏置在训练全程精确保证了均衡,就不再需要丢弃 token,从而让全部 token 都参与训练。
Part 2: 多 token 预测(MTP)
背景:为什么要 MTP
普通语言模型训练只预测下一个 token:在位置 $i$ 预测 $t_{i+1}$。多 token 预测更进一步:在位置 $i$ 还要额外预测 $t_{i+2}$、$t_{i+3}$、…、$t_{i+D}$。好处是训练信号更稠密(每个位置学更多东西),模型被迫提前规划。
Gloeckle 等(Meta)率先提出了并行做法,V3 在它基础上改成了顺序做法。
Gloeckle 的并行方法:D 个独立头,一步跨到位
所有 $D$ 个输出头共享同一个锚点——主模型在位置 $i$ 的表示 $h_i$:
┌─ 输出头 1 ──→ 预测 t_{i+1}
│
h_i ──────────────┼─ 输出头 2 ──→ 预测 t_{i+2} (并行、互不通信)
│
└─ 输出头 D ──→ 预测 t_{i+D}关键缺陷:预测 $t_{i+2}$ 的头没有见过真实的 $t_{i+1}$,预测 $t_{i+D}$ 的头中间一个真实 token 都没见过。它等于从 $h_i$ 出发一次性「外推」$D$ 步,预测距离越远越飘,而且 $D$ 个预测之间没有信息流动。
V3 的顺序方法:D 个串联模块,逐级锚定真实 token
V3 用 $D$ 个串联的 Transformer 模块 $\mathrm{TRM}_1 \to \mathrm{TRM}_2 \to \cdots \to \mathrm{TRM}_D$,第 $k$ 个模块负责预测第 $k$ 个未来 token。它的输入是:
$$ \mathbf{h}_i^{\prime k} = M_k\bigl[\mathrm{RMSNorm}(\mathbf{h}_i^{k-1});\ \mathrm{RMSNorm}(\mathrm{Emb}(t_{i+k}))\bigr] $$意思是:在第 $k$ 个预测深度,把「上一层表征」和「未来第 $k$ 个 token 的嵌入」拼接起来,再线性投影,得到该层 MTP 模块的输入。这样训练的时候,每次都能从训练数据上得到更多信息。
和 EAGLE 的区别:一个是推理,一个是训练
- EAGLE 用因果链结构是为了推理加速:草稿模型快速生成候选 token,主模型并行验证。
- V3 的 MTP 借鉴了结构,但目的是提升训练质量:把「预测第 $k$ 个未来 token」这个难题分解成 $k$ 步小步逼近、每步都有真实信息校正,让每个深度都能给主模型提供更有效的梯度。也正因如此,推理时可以直接丢弃 MTP 模块,主模型独立正常工作——它只是训练期的辅助目标。
MTP 怎么帮助主模型
MTP 不是独立玩具,它站在主模型的表征 $\mathbf{h}_i^0$ 上训练,梯度会一路回传到主模型:
- 每个深度的损失都算进总损失,所有层的梯度都回传到共享的嵌入层和主模型。
- 为了配合 MTP 的「前瞻小测验」,主模型被迫在 $\mathbf{h}_i^0$ 里多存一些对后面有用的信息。
- 长期下来,主模型被练出更强的「预规划」能力——即使推理时把 MTP 全删掉,主模型自己猜下一个词也变得更准。
DeepSeek-V4:混合注意力、mHC 与 Muon
V4 的目标很直白:百万 token 上下文。V2/V3 的 MLA + MoE 还不够,V4 在架构和训练上动了大手术,本文整理其中三大设计:
- 混合注意力(CSA + HCA):实现 1M 上下文
- 流形约束超连接(mHC):把残差连接升级成多条流,又不让它信号爆炸
- Muon 优化器:把权重当矩阵来更新,收敛更快更稳
论文:DeepSeek-V4
模型概览
两个模型都在超过 32T 多样高质量 token 上预训练:
- DeepSeek-V4-Pro:1.6T 参数 / 49B 激活
- DeepSeek-V4-Flash:284B 参数 / 13B 激活
后训练:先培养领域专家,再统一合并
V4 的后训练采用两阶段范式:先独立培养领域专家,再通过 on-policy distillation(OPD)统一合并。
阶段一:培养领域专家。 对数学、代码、智能体、指令跟随等领域分别训练专家:基座先经领域高质量数据的 SFT,再以 GRPO 做 RL,由面向具体成功标准的奖励模型引导。
阶段二:OPD 合并。 用 OPD 把这些专家的能力「合并」进一个统一的学生模型(知识合并):
$$ L_{\mathrm{OPD}}(\theta) = \sum_i w_i \cdot D_{\mathrm{KL}}(\pi_\theta \,\|\, \pi_{E_i}) $$为什么用反向 KL
这里用的是反向 KL:$KL(\pi_\theta \,\|\, \pi_{E_i})$,期望照学生采样。与之相对的正向 KL 是 $KL(\pi_{E_i} \,\|\, \pi_\theta)$,期望照教师采样。
反向 KL 的性质很有意思:在学生没采样到的地方,权重 $\pi_\theta(x) \approx 0$,即使教师那里概率很高,也不罚。所以学生可以只挑一个最省事的模式去对齐,忽略其余模式——这叫模式选择(mode-seeking),输出更尖锐。
KL 是「两个分布差多少」的度量(不对称)。反向 KL 可以理解为:从学生视角看「我在采样的地方,教师认不认可」。它从学生采样,所以是 on-policy,而且会自动选择对齐「当前任务最相关的那个教师模式」——这正是多专家选择性合并想要的行为。
Part 1: 混合注意力:CSA + HCA
背景:注意力为什么是瓶颈
标准注意力的公式是:
$$ \mathrm{Attn}(Q,K,V) = \mathrm{Softmax}\!\left(\frac{QK^{\top}}{\sqrt{d}}\right) V $$给定长度为 $n$ 的序列,每个 token 生成 query、key、value。对每个 query 要和所有前面的 key 做点积(算相似度),再加权平均 value。上下文长起来之后,有两个致命开销:
| 开销 | 公式 | 1M 上下文时 |
|---|---|---|
| 计算量 | 每个 query 要和所有 key 算相似度,共 $O(n^2)$ | $n^2 = 10^{12}$ 次运算,天文数字 |
| KV cache 内存 | 每个 token 要存 key、value 两个向量,共 $O(n \cdot 2d)$ | 存 100 万个 token 的 KV,内存爆炸 |
V4 之前的优化方式
按压缩程度从轻到重:
- MQA(多查询注意力):所有注意力头共享同一份 KV。省 KV cache,但牺牲精度。
- GQA(分组查询注意力):头分组,每组共享一份 KV。折中方案(V3 用 GQA8,即 8 组)。
- MLA(多头潜在注意力):V2/V3 用的,把 KV 压缩进低维潜在空间再存储——比 GQA 省得多。
- DSA(DeepSeek 稀疏注意力):关键前提。用一个小索引器(indexer)给每个 query 先粗筛出最相关的 top-k 个 token,只在这 k 个上做注意力,把 $O(n)$ 降成 $O(k)$。
V4 的思路:压缩 + 稀疏 组合
1M token 下,$n^2$ 完全不可行,KV cache 也塞不进显存。V4 把两条减少显存的方向组合起来:
- 压缩:先把很多 token 的 KV 合并成 1 个「压缩条目」;
- 稀疏:再让每个 query 只挑少数几个最相关的压缩条目。
于是有了两个变体:CSA(压缩 + 稀疏,重细节)和 HCA(重度压缩、不稀疏,重全局)。
CSA(Compressed Sparse Attention,压缩稀疏注意力)
流程四步:token 级压缩 → Lightning Indexer 稀疏选择 → 共享 KV 的 MQA → 分组输出投影。
第 1 步:压缩 KV——每 $m$ 个 token 合成 1 个条目
每个 token 先生成「内容 $C$」和「压缩权重 $Z$」:
$$ C_a = H\,W^a_{KV},\quad Z_a = H\,W^a_Z \qquad (\text{维度 } n\times c) $$然后对每连续的 $m$ 个 token,用 Softmax 归一化后的权重 $Z$ 做加权平均,压成 1 个条目:
$$ C^{\mathrm{Comp}}_i = \sum_{j=mi}^{m(i+1)-1} S^a_j \odot C^a_j + \sum_{j=m(i-1)}^{mi-1} S^b_j \odot C^b_j $$($S$ 是 $Z$ 加可学习位置偏置后 Softmax 归一化的权重。)
数字例子(简化成 $m=4$,压缩维度 $c=2$),4 个 token 的 KV 值:
$$ > [1,0],\ [0,1],\ [1,1],\ [0,0] > $$学到压缩权重(Softmax 后)$[0.4,\ 0.3,\ 0.2,\ 0.1]$,则压缩条目为:
$$ > 0.4[1,0] + 0.3[0,1] + 0.2[1,1] + 0.1[0,0] = [0.6,\ 0.5] > $$4 个 token 变成 1 个「浓缩要点」,序列从 $n$ 压到 $n/m$——这就是 KV 内存省下来的根源。公式里特意用了 $a$、$b$ 两路(2m 个 KV 参与,但 $C^b$ 段和上一个块共用),做重叠压缩——相邻块之间有点重合,避免硬边界把信息切碎。
第 2 步:Lightning Indexer——只挑 top-k 个压缩条目
压缩完还有 $n/m$ 个条目,每个 query 只关心最相关的 $k$ 个。用一个小型索引器打分:
$$ I_{t,s} = \sum_{h=1}^{n^I_h} w^I_{t,h}\cdot \mathrm{ReLU}\bigl(\mathbf{q}^I_{t,h}\cdot K^{I\mathrm{Comp}}_s\bigr) $$- 把 query 先压到低维($\mathbf{c}^Q_t = \mathbf{h}_t W^{DQ}$),生成多个「索引头」的查询向量 $\mathbf{q}^I$;
- 每个索引头对压缩 key 打一个相似度分,用权重 $w$ 加权求和(ReLU 保证非负);
- 取分数最高的 $k$ 个:
第 3 步:共享 KV 的 MQA——低秩 query 直接做注意力
选出的压缩条目同时当 key 和 value(共享,一份顶两用,进一步省内存)。query 由共享低秩向量 $\mathbf{c}^Q_t$ 生成:
$$ \mathbf{q}_t = \mathbf{c}^Q_t\,W^{UQ}, \qquad \mathbf{o}_{t,i} = \mathrm{CoreAttn}(\mathbf{q}_{t,i},\ \mathcal{C}^{\mathrm{SprsComp}}_t,\ \mathcal{C}^{\mathrm{SprsComp}}_t) $$每个 query 只在选中的 $k$ 个条目上做注意力——复杂度从 $O(n)$ 降到 $O(k)$,这就是稀疏省下的计算量。
第 4 步:分组输出投影——省输出参数
$n_h$ 个头的输出维度 $c\cdot n_h$ 很大,直接投回 $d$ 代价高。改成:头分 $g$ 组,每组先投影到一个中间维 $d_g < c\cdot n_h/g$,最后再拼起来投回 $d$。中间多走一步、总参数量更小。
配套细节
- 滑动窗口旁路:query 只能看前面的压缩块,看不见自己压缩块内部的 token——但最近的 token 往往最重要。所以额外保留最近 $n_{\mathrm{win}}$ 个未压缩 KV,一起参与注意力。局部细节靠它兜底。
- Attention Sink:给每个头一个可学习「sink」分值 $z'_h$,softmax 分母加 $\mathrm{Exp}(z'_h)$——让注意力权重总和不必等于 1。防止某头把分数全压到不相关条目上导致信息丢失。
- Query/KV 归一化:注意力前做 RMSNorm,防止 logit 爆炸。
- Partial RoPE:只在最后 64 维加 RoPE 位置编码,输出端再施加反向旋转,让输出携带相对位置信息(KV 同时当 key/value 时位置编码会打架,这是对策)。
HCA(Heavily Compressed Attention,重度压缩注意力)
HCA 是「更狠的版本」:压缩率 $m' \gg m$,并且不做稀疏、不做重叠——就是把 KV 压到 $1/m'$ 后全部参与注意力。压缩方式同 CSA 但单路、无重叠:
$$ S_{m'i:m'(i+1)-1} = \mathrm{Softmax}_{\mathrm{row}}(Z_{m'i:m'(i+1)-1}+B), \qquad C^{\mathrm{Comp}}_i = \sum_{j=m'i}^{m'(i+1)-1} S_j \odot C_j $$然后 MQA + 分组输出投影,流程和 CSA 第 3、4 步一样。
CSA vs HCA 一句话:CSA 是「细看局部 + 远距离挑重点」(压缩率小、加稀疏);HCA 是「整段一段话浓缩成一个全局印象」(压缩率极大、不稀疏)。HCA 因为没有索引器开销、条目又少,计算极省。
为什么非要「混合」
1M 上下文下,两种注意力的定位不同:
| CSA | HCA | |
|---|---|---|
| 压缩率 | $m$(较小) | $m' \gg m$(极大) |
| 稀疏 | 是(top-k) | 否(全看) |
| 角色 | 局部细节 + 关键远距离 | 极粗粒度的全局概况 |
| 代价 | 中等 | 极低 |
不同层的 Transformer 块交替用 CSA 和 HCA。这样既保证局部精度(CSA),又让每个 token 都能低成本地「瞥一眼」整个超长上下文的全局轮廓(HCA)。
收益数字(相对 BF16 GQA8 基线,1M 上下文):KV cache 降到约 2%;相对 V3.2,Pro 为 27% FLOPs / 10% KV,Flash 为 10% FLOPs / 7% KV。加上 FP8/BF16 混合存储、Indexer 用 FP4,1M 上下文才真正「装得进、跑得动」。
Part 2: 流形约束超连接(mHC)
背景:残差连接
Transformer 每个块(注意力 + FFN)外面都套一条残差连接:
$$ x_{l+1} = x_l + \mathcal{F}_l(x_l) $$HC(超连接):把一条残差流变成 n 条
残差连接虽好,但太简单:恒等路径和计算路径只是「相加」,信息没法在路径间灵活交换。HC 的想法是:把 1 条残差流扩成 $n$ 条平行流,让它们在层间互相混合。
设残差状态是一个矩阵($n$ 条流、每流 $d$ 维):
$$ X_l = [x_{l,1};\ x_{l,2};\ \cdots;\ x_{l,n}]^\top \in \mathbb{R}^{n\times d} $$HC 引入三个可学习线性映射来「读 / 混合 / 写」:
$$ X_{l+1} = \underbrace{B_l X_l}_{\text{流内混合}} + \underbrace{C_l\, \mathcal{F}_l\bigl(\underbrace{A_l X_l}_{\text{读出 }d\text{ 维}}\bigr)}_{\text{层变换}} $$- $A_l \in \mathbb{R}^{1\times n}$:把 $n$ 条流聚合成一个 $d$ 维向量,喂给层 $\mathcal{F}_l$;
- $C_l \in \mathbb{R}^{n\times 1}$:把层输出写回 $n$ 条流;
- $B_l \in \mathbb{R}^{n\times n}$:让 $n$ 条流内部互相混合——这是 HC 性能增益的主要来源。
数字例子($n=2$):两条流 $X_1 = \begin{bmatrix}4\\8\end{bmatrix}$,混合矩阵 $B = \begin{bmatrix}0.6 & 0.4\\0.4 & 0.6\end{bmatrix}$,则:
$$ > BX_1 = \begin{bmatrix}0.6\cdot4+0.4\cdot8\\0.4\cdot4+0.6\cdot8\end{bmatrix} = \begin{bmatrix}5.6\\6.4\end{bmatrix} > $$两条流各自是对方和自身的加权平均——信息交换了。
HC 的致命伤:信号会爆炸
多层堆叠后,从第 $l$ 层到第 $L$ 层的信号由复合映射支配:
$$ x_{L} = \left(\prod_{i=1}^{L-l} B_{L-i}\right) x_l + \sum_{i=l}^{L-1} \left(\prod_{j=1}^{L-1-i} B_{L-j}\right) C_i\, \mathcal{F}_i(A_i x_i) $$问题在于:$B_l$ 是无约束的可学习矩阵,只要它的特征值略大于 $1$,连乘起来就爆炸。
mHC 的解法:把 B 锁进双随机矩阵流形
mHC 的核心就一句话:强制 $B_l$ 是双随机矩阵——非负、每行和每列的和都等于 $1$:
$$ \mathcal{M}^{\mathrm{res}} = \bigl\{ M \in \mathbb{R}^{n\times n} \ \big|\ M\mathbf{1}_n = \mathbf{1}_n,\ \mathbf{1}_n^\top M = \mathbf{1}_n^\top,\ M \ge 0 \bigr\} $$这个约束带来三个硬保证:
- 谱范数 $\|B_l\|_2 \le 1$:矩阵是非扩张的,前向和反向都不放大信号,梯度不会爆炸。
- 乘法封闭:两个双随机矩阵相乘还是双随机矩阵,所以任意深度的复合 $\prod B_i$ 仍是双随机,全深度稳定。
- 凸组合几何:双随机矩阵集合 = 置换矩阵集合的凸包(Birkhoff 多面体),所以 $B_l X_l$ 的每个输出分量都是输入各流的非负加权平均。
动态参数化:按 token 决定怎么连
三个映射的参数由动态(依赖输入)与静态(与输入无关)分量分解得到。
第 1 步:展平 + 归一化
当前的残差状态是 $n$ 条流组成的矩阵 $X_l\in\mathbb{R}^{n\times d}$。先把 $n$ 条流摊平成一根长向量(顺序拼起来),再做 RMSNorm:
$$ \hat{X}_l = \mathrm{RMSNorm}\bigl(\mathrm{vec}(X_l)\bigr) \in \mathbb{R}^{1\times n_{\mathrm{hc}}d} $$数字例子($n=2,\ d=3$):
$$ X_l = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \xrightarrow{\text{vec}} [\,1,\ 2,\ 3,\ 4,\ 5,\ 6\,] \xrightarrow{\text{RMSNorm}} \hat X_l \approx [\,0.26,\ 0.51,\ 0.77,\ 1.03,\ 1.29,\ 1.54\,] $$为什么要展平?因为要生成连接权重,得同时看到所有流的所有特征,才能决定「这一层该怎么混合」。为什么归一化?消除量级差异,让投影权重学起来更稳。
第 2 步:线性投影,得到「动态部分」
把归一化向量 $\hat X_l$ 乘一个小线性矩阵,得到「这个 token 专属的原始连接系数」。同一个套路套三次,只是形状不同:
$$ \tilde A_l = \alpha^{\mathrm{pre}}_l(\hat X_l W^{\mathrm{pre}}_l) + S^{\mathrm{pre}}_l, \qquad \tilde B_l = \alpha^{\mathrm{res}}_l\,\mathrm{Mat}(\hat X_l W^{\mathrm{res}}_l) + S^{\mathrm{res}}_l, \qquad \tilde C_l = \alpha^{\mathrm{post}}_l(\hat X_l W^{\mathrm{post}}_l)^\top + S^{\mathrm{post}}_l $$- $W^{\mathrm{pre}}$:$nd$ 维 → $n$ 维的投影,把「当前状态」压缩成「读出权重」;
- $W^{\mathrm{res}}$ 更宽:输出 $n^2$ 维,再 reshape 成 $n\times n$ 矩阵,对应 $B$;
- $W^{\mathrm{post}}$ 同 $W^{\mathrm{pre}}$,对应 $C$ 是 $1\times n$。
第 3 步:加静态偏置 $S$,拆成「动态 + 静态」两半
每个映射都由两项组成:
- 动态项 $\alpha\cdot(\hat X W)$:依赖输入,每个 token 不一样。含义是「根据这个 token 当前的表示,我应该怎么连」。
- 静态项 $S$:可学习的全局偏置,所有 token 共享。含义是「模型默认的基础连接结构」。
$\alpha$ 初始只有 $0.01$,动态项几乎不起作用,$\tilde A \approx S$——连接先由静态偏置主导,训练后期 $\alpha$ 慢慢变大,模型才逐渐学会「按 token 动态调节」。
为什么要这样拆 + 门控 $\alpha$ 初始化为小值?这是「先稳后动」的训练策略:训练初期模型还没学会啥,如果连接权重全靠瞎猜的输入去决定,信号流立刻乱掉;让 $\alpha$ 从小长大,等于先让网络在「接近固定、接近恒等的连接」上把大框架训稳,再慢慢放开动态调节能力。$\alpha$ 初始小值 → 初期 $\tilde B \approx S^{\mathrm{res}}$,而 $S^{\mathrm{res}}$ 通常初始化得接近恒等矩阵,所以训练一开始它差不多就是标准残差,不会崩。
施加约束:把原始值「锁」进合法区间
上面算出的 $\tilde A, \tilde B, \tilde C$ 是无约束的(可能为负、可能任意大)。直接拿去当连接权重会很危险,所以要加工。
对 $A$、$C$:Sigmoid 压到非负有界
$$ A_l = \sigma(\tilde A_l), \qquad C_l = 2\sigma(\tilde C_l) $$- $A_l = \sigma(\tilde A_l) \in (0,1)$:读出权重永远非负,且不超过 1。
- $C_l = 2\sigma(\tilde C_l) \in (0,2)$:写回权重非负,但允许到 2(写回需要比读出更强的更新幅度)。
为什么非负?如果不约束,$A$、$C$ 的系数正负混杂,动态项和静态项互相抵消,读出的信号会「莫名其妙变弱或变号」,直接破坏信号传播。非负保证读出/写入的方向一致,幅度可控。
对 $B$:Sinkhorn-Knopp 投影到双随机矩阵
$$ M^{(0)} = \exp(\tilde B_l), \qquad M^{(t)} = \mathcal{T}_r\bigl(\mathcal{T}_c(M^{(t-1)})\bigr) $$双随机矩阵要求:非负 + 每行和 = 1 + 每列和 = 1。Sinkhorn-Knopp 就是「反复把矩阵变成行列和为 1」的迭代算法:
- 先取指数 $\exp(\tilde B)$:把任意实数(含负数)变成正数——因为后续归一化需要矩阵元素全正;而且 $\exp$ 是光滑函数,梯度能正常回传。
- 交替行列归一化:把每一列除以列和($\mathcal{T}_c$),再每一行除以行和($\mathcal{T}_r$),反复迭代。
- 收敛到双随机矩阵 $B_l$,实践取 $t_{\max}=20$ 次。
整条流水线串起来
每个 token、每一层,mHC 都执行同样的「生成→约束→使用」:
X_l(n×d 残差状态)
│
├─→ vec + RMSNorm ─→ X̂(1×nd)
│ │
│ ┌──────────────────┼──────────────────┐
│ │ W^pre │ W^res │ W^post
│ ▼ ▼ ▼
│ Ã (1×n) B̃ (n×n) C̃ (1×n)
│ │ │ │
│ A=σ(Ã) B=Sinkhorn(B̃) C=2σ(C̃)
│ │ │ │
└────┴─────────→ X_{l+1} = B·X_l + C·F(A·X_l)(下一条多流状态)Part 3: Muon 优化器
Muon 是 V4 引入的一个新优化器,用来替换大部分矩阵参数的 AdamW。不是新概念。
背景:Adam 有什么短板
训练神经网络的本质是最小化损失函数:算出梯度 $\nabla_W \mathcal{L}$,然后往下降。最朴素的是 SGD:
$$ W_t = W_{t-1} - \eta\,\nabla_W\mathcal{L} $$SGD 的问题:所有参数用同一个学习率,而不同参数的量级、更新频率差很大,容易震荡或太慢。
Adam 的解法是「逐元素(element-wise)自适应」:为每个参数单独维护一阶矩 $m_t$ 和二阶矩 $v_t$,再逐元素缩放:
$$ m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t, \qquad v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 $$$$ W_t = W_{t-1} - \eta\, \frac{\hat m_t}{\sqrt{\hat v_t} + \varepsilon} $$Adam 把更新分解成每个元素独立的「除法」。这有一个隐含的缺陷:权重矩阵 $W\in\mathbb{R}^{n\times m}$ 在 Adam 眼里是一堆互不相干的标量,完全无视了矩阵的行列结构。可神经网络里绝大多数参数本来就是矩阵(注意力投影、FFN、权重矩阵)——矩阵的「行」和「列」之间有旋转、缩放这样的整体关系,Adam 一点都利用不上。
Muon 的核心思想:更新方向强制「正交化」
Muon 的思路很简单,把权重当矩阵而不是一堆标量来更新:
- 先算动量矩阵 $M_t = \mu M_{t-1} + G_t$($\mu\approx0.95$);
- 关键一步:对动量矩阵做 SVD:$M_t = U\Sigma V^\top$,然后丢掉中间的 $\Sigma$,只保留 $U V^\top$;
- 用这个「正交化后的方向」去更新权重:
为什么丢掉 $\Sigma$?$U V^\top$ 是正交矩阵(旋转),它的奇异值全等于 1——更新方向和「长度」脱钩,只保留「方向」。这有两层好处:
- 理论:矩阵形式的层(注意力、FFN 本质都是矩阵运算)在「旋转空间」里更新,能更快学到有用特征;
- 稳定:更新步长有界、方向干净,不像 Adam 可能被个别大梯度拽飞。
类比:Adam 像在棋盘上一格一格地挪动棋子(每个格子独立微调);Muon 像直接旋转整个棋盘——一次动作同时调整所有位置的关系,更适合「矩阵」这种天生带结构的对象。
SVD 太贵怎么办:Newton–Schulz 近似
精确做 SVD 每一步都很贵。Muon 用 Newton–Schulz 迭代近似「正交化」而不算 SVD:目标 $M = U\Sigma V^\top$ 的正交部分是 $U V^\top$。先把 $M$ 按 Frobenius 范数归一化($\|M\|_F \le 1$),然后迭代:
$$ M_k = a\, M_{k-1} + b\,(M_{k-1}M_{k-1}^{\top})M_{k-1} + c\,(M_{k-1}M_{k-1}^{\top})^2 M_{k-1} $$这个迭代会把奇异值一步步推向 1(即逼近正交矩阵)。V4 用混合系数:前 8 步用快速收敛的系数 $(a,b,c)=(3.4445,-4.7750,2.0315)$,最后 2 步用 $(2,-1.5,0.5)$ 精确稳定在奇异值 = 1。共 10 次,比 SVD 便宜得多。
数字小例:设 $M = \begin{bmatrix}2 & 0\\0 & 0.5\end{bmatrix}$(奇异值 2 和 0.5)。归一化后奇异值变 $[1, 0.25]$。经过 Newton–Schulz 迭代,大奇异值往下压、小奇异值往上抬,几次后都趋近 1——矩阵变成「纯旋转」。
V4 里 Muon 的具体算法
每步对每个权重矩阵 $W\in\mathbb{R}^{n\times m}$:
$$ G_t = \nabla_W\mathcal{L}_t(W_{t-1}) \qquad\text{(梯度)} $$$$ M_t = \mu M_{t-1} + G_t \qquad\text{(动量, } \mu=0.95\text{)} $$$$ O'_t = \mathrm{HybridNewtonSchulz}\bigl(\mu M_t + G_t\bigr) \qquad\text{(Nesterov 加速 + 正交化)} $$$$ O_t = O'_t \cdot \sqrt{\max(n,m)} \cdot \gamma \qquad\text{(缩放更新 RMS, } \gamma=0.18\text{)} $$$$ W_t = W_{t-1}\cdot(1-\eta\lambda) - \eta\, O_t \qquad\text{(weight decay + 更新)} $$几个细节值得注意:
- Nesterov:正交化之前先加一次当前梯度 $\mu M_t + G_t$(看向未来一步),收敛更快;
- 缩放更新 RMS:正交矩阵的「尺寸」是固定的,所以要用 $\sqrt{\max(n,m)}\cdot\gamma$ 把更新量重新缩放回一个和 AdamW 兼容的量级,这样能直接复用 AdamW 的学习率,不用重新调参;
- weight decay 单独对权重衰减 $\lambda=0.1$,与更新解耦。
为什么只对「大部分参数」用 Muon,其余用 AdamW
Muon 有个硬限制:它只对矩阵有定义(SVD/正交化是矩阵操作)。向量参数(偏置、归一化权重等)没法正交化,所以:
| 用 Muon | 仍用 AdamW |
|---|---|
| 注意力/FFN 的权重矩阵、MoE 专家矩阵等 | Embedding、预测头、RMSNorm 权重、mHC 静态偏置与门控 |