最近把 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:
推理时,历史每个 token 的 \(k_t\)、\(v_t\) 都要存下来(就是 KV cache),后面每个新 token 都要和它们做注意力。序列一长,这一份缓存就非常占显存。
低秩联合压缩:把 K、V 压进一个潜在向量
MLA 的关键是低秩联合压缩:先用一个降维矩阵把 \(h_t\) 压成一个很小的向量 \(c_t^{KV}\),缓存时只存这个向量;等要用的时候,再各自通过一个升维矩阵恢复出 \(k\)、\(v\)。
这里有两个关键词:
- 联合:K 与 V 共用同一个 \(c^{KV}\),一份缓存当两用。
- 低秩:经过 \(d_c\) 维的瓶颈,且 \(d_c \ll n_h d_h\)(头的总数 × 头维度),所以缓存从「两个大头」变成「一个小头」,内存省下非常多。
此外,query 也压缩了(省训练激活,不省缓存)
注意,query 的压缩不是为了省缓存——推理时 query 是「当前正在生成的 token」现算的,根本不需要缓存。它省的是训练时的激活内存(激活随 batch × 序列长度增长,训练里是显存大户)。
解耦 RoPE:为什么位置信息必须单独拆开
压缩本身不难,难点在于位置编码。先看如果不拆开会发生什么。
注意力分数只关心 query 和 key 的点积:
矩阵乘法是结合的,所以可以把 \(W^{UK}\) 挪到 \(q_t\) 那边:
这里 \(q'_t = W^{UK\top} q_t\) 和位置 \(j\) 无关——新 token 来了,算一次 \(q'_t\),就能直接和所有缓存的压缩向量 \(c_j^{KV}\) 做点积。这样只需要缓存小向量 \(c_j^{KV}\),不需要展开。
但一旦对 key 施加 RoPE,就不成立了。施加 RoPE 之后注意力分数变成:
现在想把 \(R_j W^{UK}\) 吸收到 \(q_t\) 那边:
注意:被吸收后的有效 query 变成了 \(W^{UK\top} R_j^{\top} q_t\),里面带着 \(R_j^{\top}\),是随位置 \(j\) 变化的。
所以对每一个前缀位置 \(j\),都得用各自不同的 \(R_j^{\top}\) 去变换 query——没有办法提前算好一个 query 复用于所有位置,也就无法只缓存小向量了。
论文中给出的解决办法是解耦:让位置信息走一条单独的支路,不经过压缩。
位置支路:q^R 与 k^R
解耦之后,query 分成内容和位置两部分:
其中 \(c_t^Q = W^{DQ}h_t\) 是 query 的压缩潜在向量(\(d'_c\) 维,很小)。这里直接复用前面为省激活内存而压出来的 \(c_t^Q\),再投影、切头、做 RoPE:
每个头有自己的位置 query \(q_{t,i}^R \in \mathbb{R}^{d_h^R}\)。
key 的位置支路则是:
注意这里直接用 \(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 隔了多远」是客观事实,和哪个头无关。不管这个头在关注语义、语法还是实体,它对「谁离谁近」的认知应该是一致的。
拼接与注意力
最后把内容、位置两部分拼起来:
注意力(value 用 content \(v^{C}\)):
点积可以拆成内容和位置两项:
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:
层输出(残差):
传统 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\) 为输出:
共享专家始终计算(没有 top-\(k\)),路由专家才做 top-\(k\) 选择。门控:
亲和度(token 与专家的相似度):
其中 \(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,并且只在选中的专家上做归一化得到门控值:
无辅助损失:给每个专家加一个动态偏置
V3 的核心做法是给每个专家引入一个偏置 \(b_i\),加到亲和度上再做 top-\(K\) 路由:
关键点:\(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。它的输入是:
意思是:在第 \(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 把这些专家的能力「合并」进一个统一的学生模型(知识合并):
为什么用反向 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
背景:注意力为什么是瓶颈
标准注意力的公式是:
给定长度为 \(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\)」:
然后对每连续的 \(m\) 个 token,用 Softmax 归一化后的权重 \(Z\) 做加权平均,压成 1 个条目:
(\(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\) 个。用一个小型索引器打分:
- 把 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\) 生成:
每个 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 但单路、无重叠:
然后 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)外面都套一条残差连接:
HC(超连接):把一条残差流变成 n 条
残差连接虽好,但太简单:恒等路径和计算路径只是「相加」,信息没法在路径间灵活交换。HC 的想法是:把 1 条残差流扩成 \(n\) 条平行流,让它们在层间互相混合。
设残差状态是一个矩阵(\(n\) 条流、每流 \(d\) 维):
HC 引入三个可学习线性映射来「读 / 混合 / 写」:
- \(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\) 层的信号由复合映射支配:
问题在于:\(B_l\) 是无约束的可学习矩阵,只要它的特征值略大于 \(1\),连乘起来就爆炸。
mHC 的解法:把 B 锁进双随机矩阵流形
mHC 的核心就一句话:强制 \(B_l\) 是双随机矩阵——非负、每行和每列的和都等于 \(1\):
这个约束带来三个硬保证:
- 谱范数 \(\|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:
数字例子(\(n=2,\ d=3\)):
为什么要展平?因为要生成连接权重,得同时看到所有流的所有特征,才能决定「这一层该怎么混合」。为什么归一化?消除量级差异,让投影权重学起来更稳。
第 2 步:线性投影,得到「动态部分」
把归一化向量 \(\hat X_l\) 乘一个小线性矩阵,得到「这个 token 专属的原始连接系数」。同一个套路套三次,只是形状不同:
- \(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) \in (0,1)\):读出权重永远非负,且不超过 1。
- \(C_l = 2\sigma(\tilde C_l) \in (0,2)\):写回权重非负,但允许到 2(写回需要比读出更强的更新幅度)。
为什么非负?如果不约束,\(A\)、\(C\) 的系数正负混杂,动态项和静态项互相抵消,读出的信号会「莫名其妙变弱或变号」,直接破坏信号传播。非负保证读出/写入的方向一致,幅度可控。
对 \(B\):Sinkhorn-Knopp 投影到双随机矩阵
双随机矩阵要求:非负 + 每行和 = 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:
SGD 的问题:所有参数用同一个学习率,而不同参数的量级、更新频率差很大,容易震荡或太慢。
Adam 的解法是「逐元素(element-wise)自适应」:为每个参数单独维护一阶矩 \(m_t\) 和二阶矩 \(v_t\),再逐元素缩放:
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\)),然后迭代:
这个迭代会把奇异值一步步推向 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}\):
几个细节值得注意:
- 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 静态偏置与门控 |