最近把 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 多了哪两条关键设计。

背景:大模型的两个瓶颈

一个大模型要变大、上下文要变长,会撞上两个显存/算力瓶颈:

  1. KV cache 内存:每个 token 都要存一份 key 和 value,序列越长,占的内存线性增长。
  2. FFN 计算量:Decoder-only Transformer 每一层大致是
text
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 计算流(示意)

text
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):

  1. 对 token \(u_t\) 算与各专家的亲和分数 \(s_{i,t}\);
  2. 只取 top-\(K\) 个专家真正前向;
  3. 用门控权重把专家输出混合回去。
\[ h'_t = u_t + \sum_{i=1}^{N} g_{i,t}\,\mathrm{FFN}_i(u_t) \]

这里的「传统」指 GShard / Switch / Mixtral 一类主流稀疏 FFN。

DeepSeekMoE 的两条关键思想

DeepSeek-V2 在传统 MoE 上做了两点改动:

  1. 更细粒度的专家切分:专家更多、更小,特化更强、组合更丰富;
  2. 隔离部分共享专家:通用知识由 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}\)门控值 / 亲和度

数据流

text
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 · routed

DeepSeekMoE 用「更多更小的路由专家」换更丰富的组合能力,再用「共享专家兜底」减掉路由专家之间的知识冗余。

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\):

text
                    ┌─ 输出头 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 把两条减少显存的方向组合起来:

  1. 压缩:先把很多 token 的 KV 合并成 1 个「压缩条目」;
  2. 稀疏:再让每个 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\) 个:
\[ \mathcal{C}^{\mathrm{SprsComp}}_t = \bigl\{C^{\mathrm{Comp}}_s \mid I_{t,s} \in \mathrm{Top\text{-}k}(I_{t,:})\bigr\} \]

第 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 上下文下,两种注意力的定位不同:

CSAHCA
压缩率\(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\} \]

这个约束带来三个硬保证:

  1. 谱范数 \(\|B_l\|_2 \le 1\):矩阵是非扩张的,前向和反向都不放大信号,梯度不会爆炸。
  2. 乘法封闭:两个双随机矩阵相乘还是双随机矩阵,所以任意深度的复合 \(\prod B_i\) 仍是双随机,全深度稳定。
  3. 凸组合几何:双随机矩阵集合 = 置换矩阵集合的凸包(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」的迭代算法:

  1. 先取指数 \(\exp(\tilde B)\):把任意实数(含负数)变成正数——因为后续归一化需要矩阵元素全正;而且 \(\exp\) 是光滑函数,梯度能正常回传。
  2. 交替行列归一化:把每一列除以列和(\(\mathcal{T}_c\)),再每一行除以行和(\(\mathcal{T}_r\)),反复迭代。
  3. 收敛到双随机矩阵 \(B_l\),实践取 \(t_{\max}=20\) 次。

整条流水线串起来

每个 token、每一层,mHC 都执行同样的「生成→约束→使用」:

text
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 的思路很简单,把权重当矩阵而不是一堆标量来更新:

  1. 先算动量矩阵 \(M_t = \mu M_{t-1} + G_t\)(\(\mu\approx0.95\));
  2. 关键一步:对动量矩阵做 SVD:\(M_t = U\Sigma V^\top\),然后丢掉中间的 \(\Sigma\),只保留 \(U V^\top\);
  3. 用这个「正交化后的方向」去更新权重:
\[ W_t = W_{t-1} - \eta\, 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 静态偏置与门控