专栏DeepSeek-V4.1 模型结构·输入端与优化器8 / 9
16 min学习

DeepSeek-V4.1 模型结构(7):输入端与优化器

主干之外的两头。输入端:从零训练的 DeepSeek-ViT,一张 1302 × 1302 的图怎么变成 994 个位置,文本和图像 token 为什么要各用一套负载均衡偏置。优化器:head-wise Muon 把 query 权重按头切开再正交化,用一个秩 1 的例子说明它改变了什么;Sinkhorn-balanced update 用行列交替归一化代替 Adam,只存一份动量就能训练 196B 参数的 Engram 表。

目录21 节

对应论文 §2.1.1 "Multimodal Architecture"、§2.5 "Optimization",训练配方的数字在 §4.2.2。这一篇讲两件互不相关的事:图像怎么进入主干,优化器改了什么。它们都不在主干的层里,所以放在一起。Muon 本身在 V4 连载的第 6 篇讲过,这里只讲 V4.1 的两处改动。

图像文本 token视觉特征写到图像 token 的位置上,和文本 embedding 排成同一条序列复制成 4 份,进入 4 条残差流残差流 ×4每条 5120 维重复 3 组第 2 – 19 层,6 层一组每组 1 层 Full + 5 层 Reuse重复 4 组第 24 – 39 层,4 层一组每组 1 层 Reindex + 3 层 Reuseencoder:第 0 – 19 层decoder:第 20 – 39 层prefill 时,绝大部分 prompt token 只算到这里decoder 的全局 KV 全部由投影得到只有滑窗分支,不读全局 KV40 层的注意力后面都接一个 MoE,下面各行省略不画写读写读重选索引读encoder 的三组各有一份全局 KV,由该组的 Full 层写入,组内 6 层共用encoder 共享池(每组一份)main KV:每 2 个 token 一条,512 维 FP4indexer K:每条 128 维 FP4top-512 索引:每个 query 一份,不进缓存decoder 只有一份全局 KV,由第 20 层从 encoder 末态投影出来,20 层共用decoder 共享池(只有一份)main KV:每个 token 一条,512 维 FP4indexer K:每条 128 维 FP4候选池:第 20 层选出的2048 块 × 8 = 16384 个位置,Reindex 层只在池内打分top-512 索引:第 20 层先写,每个 Reindex 层覆盖一次全局 KV 合计 890 B / tokenSingle-Pass mHC:每个子层一次读、一次写 + 混读用的是上一个子层算好的查表结果经门控后加进残差流logits最后一次只读:4 条流压成 1 条读主干第 37 – 39 层入口处 4 条流的平均一次前向出 5 个草稿 token 和各自的置信度encoder 末态:第 19 层的输出,也就是第 20 层的输入。decoder 所有层的全局 KV 都只从它投影(论文式 1)encoder 末态mHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 读算子:对 4 条流加权求和,得到子层的 5120 维输入。权重由上一个子层算好读 AmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 BmHC 写算子 C 与混合矩阵 B:子层输出按 C 分到 4 条流,同时 4 条流按双随机矩阵 B 互相混合写 C · 混 B从头训练的视觉编码器:2D-RoPE、RMSNorm、SwiGLU,hidden 1024DeepSeek-ViT32 层 · patch 143×3 pixel-unshuffle 把 9 个相邻 patch 拼到通道维,再过两层 MLP 投影到主干宽度3×3 重排 + MLPtoken ÷ 9 → 5120 维图像位置上的 embedding 被视觉特征覆盖Token Embedding129280 × 5120前两层只有滑窗分支,没有全局 KV滑窗注意力第 0、1 层 · 窗口 128sqrt(softplus) 打分;文本和图像 token 各用一套负载均衡偏置;routed expert 权重 FP4DeepSeekMoE每层的 FFN · 384 选 6 + 1 sharedFull 模式:自己压缩 main KV(每 2 个 token 一条),投影出 indexer K,打分选 top-512,全部写进共享池CSA2 · Full第 2 / 8 / 14 层 · m = 2Reuse 模式:只有自己的 query、滑窗 KV 和输出投影;全局 KV 与 top-512 索引都从共享池读CSA2 · Reuse ×5每组其余 5 层decoder 唯一的 Full 层:输入是 encoder 末态,每个 token 一条 main KV;另选出最多 16384 个位置作为候选池CSA2 · Full第 20 层 · m = 1 · 建候选池Reuse 模式CSA2 · Reuse ×3第 21 – 23 层Reindex 模式:全局 KV 和 indexer K 从共享池读,用自己的 indexer query 在候选池里重新打分,选出新的 top-512CSA2 · Reindex第 24 / 28 / 32 / 36 层Reuse 模式:用本组 Reindex 层选出的 top-512CSA2 · Reuse ×3每组其余 3 层最终归一化RMSNorm不与 embedding 共享权重LM Head→ 129280投机解码的草稿模块:三个 block 一次前向给出 5 个草稿 token 的 logits,Markov head 补上草稿 token 之间的依赖,confidence head 估计每个位置被接受的概率DSpark 草稿层 ×3滑窗注意力 + MoE(128 选 3)按 2、3、4-gram 的哈希查表,取出的向量经门控后加进 4 条残差流;两张表共 196B 参数Engram第 1、14 层入口各一个
序列 · CSA2序列 · 共享池序列 · CED序列 · 滑窗深度 · Single-Pass mHC宽度 · DeepSeekMoE记忆 · Engram解码 · DSpark输入与输出
你现在在这里:顶部的视觉通路,以及 MoE 里和图像 token 有关的那一处改动。

视觉通路:图像变成序列里的一段 token

V4.1-Flash 能直接读图,V4 不能。做法是常见的三段:视觉编码器把图切成 patch 并编码,一个投影层把视觉特征变到主干的宽度,得到的向量和文本 token 的 embedding 排成同一条序列。

图像缩放到 14 的整数倍例:1302 × 1302切成 14 × 14 的 patch每个 patch 588 个数线性层 588 → 1024DeepSeek-ViT,32 层图内双向注意力,2D-RoPERMSNorm,SwiGLU例:93 × 93 = 8649 个 patch3 × 3 pixel-unshuffle相邻 9 个 patch 拼到通道维9 × 1024 = 9216 维两层 MLP projector9216 → 5120 → 5120中间是 GELU视觉 token5120 维例:31 × 31 = 961 个写进主干的输入序列起始符 +(一行视觉 token + 换行符)× 行数 + 结束符例:1 + 31 × 32 + 1 = 994 个位置和文本 token 的 embedding 排在同一条序列里
一张图进入主干的过程。例子是一张缩放后 1302 × 1302 的方图,这是方图能取的最大尺寸。

DeepSeek-ViT

论文说它是从零训练的,在标准 ViT 上改了四处,每一处都是向语言模型的做法靠拢。对着官方代码 inference/vision.py 看:

标准 ViTDeepSeek-ViT
位置编码可学习的绝对位置 embedding2D-RoPE
patch embedding卷积线性层,588 → 1024
归一化LayerNormRMSNorm,pre-norm
FFN 的激活GELUSwiGLU,1024 → 2816 → 1024

其余的尺寸:32 层,隐藏维 1024,16 个头,每头 64 维。一个 block 约 12.9M 参数,全模型约 0.41B。

2D-RoPE 让它能吃任意分辨率。 绝对位置 embedding 的数量是固定的,换一个分辨率就要插值。RoPE 是按相对位置旋转 query 和 key,不需要一张固定大小的表。二维的做法是把每个头的 64 维分成两半:一半按 patch 所在的行号旋转,另一半按列号旋转。

线性的 patch embedding 是为了 Muon。 步长等于核大小的卷积,和「把每个 patch 拉平再乘一个矩阵」在数学上是同一件事。3×14×14=5883 \times 14 \times 14 = 588 个数乘一个 588×1024588 \times 1024 的矩阵。论文说换成线性层是为了和 Muon 兼容:Muon 是对矩阵做正交化的,参数要以矩阵的形式存在。

注意力是图内双向的。 一张图的所有 patch 互相都看得见,没有因果掩码。不同的图之间不互相看。

3 × 3 pixel-unshuffle 把 token 数除以 9

ViT 的输出是每个 patch 一个 1024 维向量。直接送进主干的话,一张 1302 × 1302 的图有 8649 个 patch,太多了。

pixel-unshuffle 的做法是把相邻 3×33 \times 3 个 patch 的向量沿通道拼起来,9 个 1024 维变成 1 个 9216 维。位置数除以 9,信息一点不丢,只是从空间维搬到了通道维。行数或列数不是 3 的倍数时,在边上补零。

之后是一个两层的 MLP:9216 → 5120,过 GELU,再 5120 → 5120。论文把它叫 MLP projector,代码里叫 Aligner。

一张图占多少个位置

主干看到的不只是视觉 token。代码里一张图在序列里的排法是:

plaintext
[起始符] + ( [视觉 token] × 每行个数 + [换行符] ) × 行数 + [结束符]

起始符、换行符、结束符各是一个可学习的 5120 维向量。换行符让主干知道一行在哪里结束,否则它从一维的序列里看不出图的宽高。

一张 h×wh \times w 个视觉 token 的图占 h(w+1)+2h(w+1) + 2 个位置。config 的 max_image_tokens = 1024 限制的是这个总数。

论文说支持的分辨率大约到 1344 × 1344。1344/14=961344 / 14 = 96 个 patch,除以 3 是 32,32×32=102432 \times 32 = 1024。但按代码算,把换行符和起止符也计入之后,方图的上限要小一点:

尺寸patch视觉 token占的位置
1344 × 134496 × 9632 × 3232×33+2=105832 \times 33 + 2 = 1058,超过 1024
1302 × 130293 × 9331 × 3131×32+2=99431 \times 32 + 2 = 994

超过上限的图会按比例缩小到能放下为止。这是我照着 image_processor.py 算出来的,论文没有这个细节。

另外两个预处理的事实:

  • 像素数少于 544×544544 \times 544(config 的 min_pixels = 295936)的图会先放大。
  • 尺寸对不上 14 的整数倍时,先等比缩放,再用灰色补边,不改变宽高比。

图像 token 在主干里的三处特殊对待

进了主干以后,图像 token 和文本 token 走同样的层。只有三处不同:

  • Engram 不处理它。 n-gram 不跨过图像,图像位置的门是 0。第 5 篇讲过。
  • MoE 的路由用另一套偏置。 下一节。
  • 所有图像位置在 token id 序列里是同一个 id(129264)。它们靠另一个标记区分是视觉 token、换行符还是起止符。

视觉编码器怎么训练

DeepSeek-ViT 在接入主干之前,自己先训练了两个阶段:

  1. 对比学习。 用 SigLIP 的 sigmoid 对比损失,约 470 亿个图文对,图像最大 224 × 224。论文说这一阶段用更高的分辨率有收益,但对最终模型帮助很小,因为高分辨率由下一阶段负责。
  2. 自回归微调。 把 ViT 接到一个 4B 的 MoE 语言模型上,用预测下一个 token 的目标训练 2360 亿 token,数据是图像描述、图表、OCR 这一类。分辨率在 544 × 544 到 1344 × 1344 之间。训完之后语言模型扔掉,只留 ViT。

然后才是主干的预训练。多模态数据从第一步就在里面,文本和多模态的 token 比例是 7 : 1。ViT 在主干预训练的大部分时间里是冻结的,只有它最后的归一化层和 projector 在训练。到学习率开始下降的时候,ViT 解冻,用更小的学习率和主干一起训练。

Kimi K3 的 MoonViT-V2 走的是另一条路:完全不做对比学习,直接用预测下一个 token 从零训练,见 K3 连载的第 6 篇。两个编码器的规模接近,都是 0.4B 左右、patch 14。K3 用 2×22 \times 2 的下采样,V4.1 用 3×33 \times 3。

文本和图像 token 各用一套负载均衡偏置

DeepSeekMoE 的负载均衡不靠辅助损失,靠一组偏置。每个 expert 有一个偏置 bib_i,选 expert 时加在分数上:

选中的 expert=top-6⁡i (si+bi).\text{选中的 expert} = \operatorname{top-6}_i\,(s_i + b_i).

每个训练步之后,负载偏高的 expert 把 bib_i 调低一点,偏低的调高一点。偏置只影响选谁,不影响权重:选中之后的权重仍然用不加偏置的 sis_i 算。这是 V3 起就有的做法,V4 连载的第 5 篇讲过。

加了图像以后,论文指出一个问题:图像 token 和文本 token 的表示分布不一样,可能偏好不同的 expert。把两种 token 的负载加在一起去平衡,会把各自模态内部的不均衡盖住。

下面的例子是我编的,用来说明「盖住」是什么意思。假设文本 token 占 7/8,图像 token 占 1/8。文本 token 在所有 expert 上分得很均匀。图像 token 全部挤在 1/4 的 expert 上。

  • 那 1/4 的 expert,总负载是平均值的 78+4×18=1.375\frac{7}{8} + 4 \times \frac{1}{8} = 1.375 倍。
  • 其余的 expert 是 78=0.875\frac{7}{8} = 0.875 倍。

从总负载看只是轻微不均。但对图像 token 来说,3/4 的 expert 完全没有用上。而且只有一套偏置时,为了压低那 1/4 的 expert 的负载,偏置会下调,文本 token 也被一起推开了,尽管文本本来没有问题。

V4.1 的做法是维护两套偏置:

选中的 expert=top-6⁡i (si+bi模态),模态∈{文本, 图像}.\text{选中的 expert} = \operatorname{top-6}_i\,\big(s_i + b_i^{\text{模态}}\big), \qquad \text{模态} \in \{\text{文本},\ \text{图像}\}.

每个 token 用自己那个模态的偏置。训练时两套偏置各按自己模态的负载更新,互不影响。代码里是 Gate 的 bias 和 bias_vl 两个向量:

python
bias = self.bias
if image_mask is not None and self.bias_vl is not None:
    bias = torch.where(image_mask.unsqueeze(-1), self.bias_vl, bias)
# the bias picks experts but does not scale them: weights come from the raw scores
indices = (scores + bias).topk(self.topk, dim=-1)[1]
weights = scores.gather(1, indices)

两套偏置的更新速度都是 0.001。V4 里那个权重 0.0001 的序列级平衡损失也保留着。

head-wise Muon:query 的权重按头分别正交化

下面换到优化器。先用一句话回顾 Muon:它不直接用梯度,而是把带动量的梯度矩阵 GG 的所有奇异值都换成 1,再当作更新量。设 G=UΣV⊤G = U \Sigma V^{\top},更新量是 UV⊤U V^{\top}。

论文建议把 Muon 看成一种带预条件的梯度下降,没有给式子。下面的推导是我补的。GG 一般不是行满秩的(query 的 GG 有 32768 行、1280 列),所以逆要换成伪逆:只对非零的奇异值取倒数,记作 (GG⊤)†/2(G G^{\top})^{\dagger/2}。

GG⊤=UΣV⊤VΣU⊤=UΣ2U⊤(GG⊤)†/2=UΣ†U⊤只对非零奇异值取倒数(GG⊤)†/2 G=UΣ†U⊤UΣV⊤=UV⊤.\begin{aligned} G G^{\top} &= U \Sigma V^{\top} V \Sigma U^{\top} = U \Sigma^2 U^{\top} \\ (G G^{\top})^{\dagger/2} &= U \Sigma^{\dagger} U^{\top} \qquad \text{只对非零奇异值取倒数} \\ (G G^{\top})^{\dagger/2}\, G &= U \Sigma^{\dagger} U^{\top} U \Sigma V^{\top} = U V^{\top}. \end{aligned}

最后一步用了 Σ†Σ\Sigma^{\dagger} \Sigma 在非零奇异值对应的方向上是单位矩阵,而 UU、VV 只取这些方向。所以 Muon 的更新等于梯度左乘一个矩阵 (GG⊤)†/2(G G^{\top})^{\dagger/2}。这个矩阵就是预条件矩阵。它由整个 GG 决定。

query 的投影矩阵有一个特点:它的输出是 64 个头拼起来的。GG 的行按头分成 64 段,每段 512 行。对整个 GG 做正交化,64 个头共用一个预条件矩阵。head-wise Muon 把 GG 按头切成 64 块,每块单独做正交化,每个头有自己的预条件矩阵。

Muon:整个矩阵做一次正交化head-wise Muon:每个头各做一次输入 1280 维头 1头 64更新所有的头共用一个预条件矩阵梯度大的头决定它输入 1280 维头 1头 64更新每个头有自己的预条件矩阵各头的更新幅度相同
左:标准的 Muon。右:head-wise Muon。行是输出维,按头分段。

一个秩 1 的例子

差别在哪,用最小的例子看。假设只有两个头,每个头只有一行,两个头的梯度指向同一个输入方向 uu(单位向量),只是大小不同:

G=[a u⊤b u⊤],a≫b>0.G = \begin{bmatrix} a\, u^{\top} \\ b\, u^{\top} \end{bmatrix}, \qquad a \gg b > 0 .

整个矩阵一起正交化。 GG 的秩是 1,只有一个非零奇异值 a2+b2\sqrt{a^2 + b^2}。把它换成 1:

UV⊤=1a2+b2[a u⊤b u⊤].U V^{\top} = \frac{1}{\sqrt{a^2 + b^2}} \begin{bmatrix} a\, u^{\top} \\ b\, u^{\top} \end{bmatrix}.

第一个头的更新幅度是 a/a2+b2≈1a / \sqrt{a^2 + b^2} \approx 1,第二个头是 b/a2+b2≈b/ab / \sqrt{a^2 + b^2} \approx b/a。梯度小的头,更新也小。正交化抹平的是整个矩阵各个方向之间的差异,两个头落在同一个方向上时,它们之间的大小关系原样保留。

按头分别正交化。 每一行自己是一个秩 1 的矩阵,各自把奇异值换成 1:

[u⊤u⊤].\begin{bmatrix} u^{\top} \\ u^{\top} \end{bmatrix}.

两个头的更新幅度相同,和各自梯度的大小无关。

论文的解释就是这个意思:各个注意力头的梯度性质差别很大,head-wise Muon 给不同的头不同的预条件矩阵,能更好地处理这种差别。论文说它实测比标准的 Muon 好,并提到 GLM-5 和 Kimi K3 也验证过。K3 的做法见 K3 连载的第 6 篇,那里叫 Per-Head Muon。

如果各个头的梯度落在互相正交的输入方向上,两种做法给出的结果相同。差别只出现在不同的头共用输入方向的时候。

用在哪些权重上

论文 §2.5 开头说 query 的权重按头切分,后面的基本配置里说 head-wise Muon 用于 query 和 key 的权重。V4.1 的注意力里,query 的展开矩阵 wq_b 是 1280→64×5121280 \to 64 \times 512,按头切成 64 块 512×1280512 \times 1280。KV 只有一条 512 维的向量,K 和 V 是同一个东西,没有头可分。论文说的 key 权重具体指哪个矩阵,没有展开。

Sinkhorn-balanced update:只存一份动量来训练大表

Adam 的状态放不下

V4 里 embedding 和 LM head 用 AdamW。Adam 要为每个参数存两个数:一阶动量和二阶动量。V4.1 多了 196B 参数的 Engram 表,再配两份同样大的状态,显存吃不消。

Muon 只存一份动量。论文没有说为什么不直接把 Muon 用在这些表上。这是我的解读:Muon 的正交化是为线性层的权重矩阵设计的,embedding 表不是这种矩阵:它的一行是一个 token,一步里只有出现过的 token 那几行有梯度。

V4.1 的做法保留 Muon 的流程,只把中间的正交化换成另一种归一化。它用在三类参数上:Engram 的表、token embedding、LM head。

算法

记权重 W∈Rm×nW \in \mathbb{R}^{m \times n},mm 是行数(词表大小,或者表的行数),nn 是隐藏维。论文的 Algorithm 1:

  1. 动量。 Mt=βMt−1+(1−β)GtM_t = \beta M_{t-1} + (1 - \beta) G_t,再取 Nesterov 形式 G^t=βMt+(1−β)Gt\hat G_t = \beta M_t + (1 - \beta) G_t。和 Muon 相同。
  2. 屏蔽近零的行。 算每一行的 ℓ2\ell_2 范数 ρi\rho_i 和它们的平均值 ρˉ\bar\rho。ρi≤τρˉ\rho_i \le \tau \bar\rho 的行整行置 0。
  3. 交替归一化 KK 次。 第奇数次,每一行除以自己的 ℓ2\ell_2 范数。第偶数次,每一列除以自己的 ℓ2\ell_2 范数。KK 是奇数,所以最后一次是行归一化。
  4. 换算成 RMS。 Δt=n U(K)\Delta_t = \sqrt{n}\ U^{(K)}。
  5. 更新。 Wt+1=Wt−γ ηt ΔtW_{t+1} = W_t - \gamma\, \eta_t\, \Delta_t。

超参:K=11K = 11,τ=10−3\tau = 10^{-3},γ=0.18\gamma = 0.18,动量 0.95,不加权重衰减。Engram 的学习率是别处的 5 倍(论文的原话是 Engram,没有细分表和投影层)。

拖动 K 可以看到每一步在做什么。K = 0 时各行的幅度相差几十倍,还有一行几乎是 0。第 1 步行归一化之后,每行的 RMS 都是 1,但各列还不均匀。第 2 步列归一化把列拉平,行又有一点偏。来回几次之后两边都接近 1。演示的矩阵很小,偶数步刚做完列归一化时,列的 RMS 恒为 6/8≈0.87\sqrt{6/8} \approx 0.87;K 取奇数,最后停在行归一化上,列的 RMS 是 0.94,因为 8 行里有 1 行被屏蔽了,7/8≈0.94\sqrt{7/8} \approx 0.94。取消「屏蔽近零的行」再拖到 K = 1,可以看到那一行本来几乎全是 0,被硬生生放大到和别的行一样大。

它配平的是行和列的幅度

交替归一化收敛之后,相当于给原来的矩阵左右各乘了一个对角矩阵(论文式 (7)):

Δt=n Dr G^t Dc,1n∑j=1n(Δt)ij2≈1,1m∑i=1m(Δt)ij2≈1.\Delta_t = \sqrt{n}\ D_r\, \hat G_t\, D_c, \qquad \frac{1}{n} \sum_{j=1}^{n} (\Delta_t)_{ij}^2 \approx 1, \qquad \frac{1}{m} \sum_{i=1}^{m} (\Delta_t)_{ij}^2 \approx 1 .

第二个式子说每一行的 RMS 约为 1,第三个说每一列的 RMS 约为 1。

为什么乘 n\sqrt{n}。 最后一步是行归一化,所以每一行的 ℓ2\ell_2 范数正好是 1,即 ∑jUij2=1\sum_j U_{ij}^2 = 1。乘 n\sqrt{n} 之后 ∑jΔij2=n\sum_j \Delta_{ij}^2 = n,除以 nn 是 1。n\sqrt{n} 把「范数为 1」换成了「RMS 为 1」。

行和列能同时为 1 吗。 能。每行的平方和是 nn,一共 mm 行,全部元素的平方和是 mnmn。如果各列是均匀的,每列的平方和是 mn/n=mmn / n = m,除以 mm 是 1。两个条件不冲突。

行和列各代表什么。 一行是一个 token,或者一个 n-gram。一列是隐藏态的一个特征。行归一化的意思是:不管一个 token 出现得多还是少、梯度大还是小,它的 embedding 这一步的更新幅度都一样。列归一化的意思是:每个特征维度得到的总更新量一样。论文的说法是它利用了这种「token × 特征」的结构。

和 Adam 比。 Adam 给每个元素一个二阶动量,让每个元素的更新幅度都在 1 附近。Sinkhorn 只保证每一行、每一列的平均幅度在 1 附近,行内各元素之间的相对大小保留着。它不需要存二阶动量。

γ=0.18\gamma = 0.18。 归一化后的更新 RMS 是 1,Adam 的更新 RMS 没有这么大。乘 0.18 是为了让幅度和 Adam 对齐,这样可以直接用同一个学习率。这个数和 Muon 用的缩放相同。

为什么要屏蔽近零的行。 论文只说是为了数值稳定。我的解读是:一个很久没出现的 token,它那一行的动量已经衰减到接近 0,剩下的基本是噪声。行归一化会把这行噪声放大到和别的行一样大,再写进权重里。

这里的 Sinkhorn 和 mHC 里的不是一回事

两处都叫 Sinkhorn,都是行列交替归一化,容易混:

mHC 的混合矩阵这里的更新矩阵
矩阵4×44 \times 4,非负m×nm \times n,有正有负
归一化的量行的和、列的和行的 ℓ2\ell_2 范数、列的 ℓ2\ell_2 范数
目标双随机矩阵行和列的 RMS 都约为 1
作用于前向计算里的一个矩阵优化器的更新量

论文说这种归一化此前在 SinkGD 里用于线性层的权重,V4.1 把它推广到了这几类大矩阵。

3.84 亿行的表怎么做归一化

每一步都把一张 3.84 亿行的矩阵来回重写 11 遍是不现实的。论文 §3.1.3 说实现上只维护两个向量:行的缩放系数和列的缩放系数。迭代更新的是这两个向量,不重写矩阵。行归一化和列的统计量累加还被融合进了一个 kernel。

哪些参数用哪个优化器

把 V4.1 的分工列成一张表:

参数优化器备注
主干里线性层的矩阵、Engram 的投影 wkv、视觉 projectorMuonNesterov 动量 0.95,权重衰减 0.1,更新的 RMS 缩放到 0.18
注意力里 query 和 key 的权重head-wise Muon同上,按头切开
Engram 的表、token embedding、LM headSinkhorn-balanced updateNesterov 动量 0.95,不加权重衰减,K=11K = 11
RMSNorm 的权重AdamWβ1=0.9\beta_1 = 0.9,β2=0.95\beta_2 = 0.95,ϵ=10−20\epsilon = 10^{-20},权重衰减 0.1
偏置、缩放系数这类非矩阵参数AdamW不加权重衰减

V4 里用 AdamW 的 embedding 和 LM head,在 V4.1 里换成了 Sinkhorn-balanced update。AdamW 只剩下向量和标量。

预训练配方的数字

放在这里方便查。V4.1 一列来自 §4.2.2,7 : 1 来自 §4.1;V4-Flash 一列来自 V4 的论文:

V4-FlashV4.1-Flash
预训练 token32T45T
batch 大小最大 75.5M token固定 100.6M token
峰值学习率2.7×10−42.7 \times 10^{-4}2.6×10−42.6 \times 10^{-4}
学习率的安排—前 2000 步线性升上去,保持到 28T;28T 到 40T 余弦下降到 2.6×10−52.6 \times 10^{-5};之后保持到 45T
稀疏注意力先稠密训 1T,再引入稀疏从第一步起就是稀疏,序列长度 64K
扩到 1M 上下文分几步,从 4K 起在 34T 处从 64K 扩到 1M
数据文本文本和多模态,token 比例 7 : 1

论文说 45T token 的训练过程没有出现不稳定。V4 为稳定训练用过的 Anticipatory Routing,在 V4.1 的论文里没有再提。

容易混淆的几点

  • 1024 不是视觉 token 的上限,是位置数的上限。 它包括换行符和起止符。方图最多 961 个视觉 token。
  • ViT 的注意力是双向的,主干是因果的。 图像 token 进入主干之后,后面的看得见前面的,前面的看不见后面的。
  • 两套偏置不是两套 expert。 expert 是共用的,只是选 expert 时加的偏置按模态分开。
  • head-wise Muon 不改变模型结构。 它只改变训练时更新量的算法。推理时没有任何区别。
  • Sinkhorn-balanced update 不是 Muon 的变体。 它借用了 Muon 的流程和缩放系数,核心的那一步完全不同:一个把奇异值变成 1,一个把行和列的范数变成 1。

下一篇

最后一篇回到 KV cache,讲它在部署系统里怎么管理:哪些存磁盘,哪些放内存,丢了之后怎么用 128 个 token 补回来,以及 CSA2 的跨层共享在流水线并行的训练里是怎么实现的。

资料

  • DeepSeek-V4.1-Flash 技术报告 §2.1.1、§2.5、§3.1.3、§4.2.2:arXiv 2609.19969
  • 代码:Hugging Face deepseek-ai/DeepSeek-V4.1-Flash 的 inference/vision.py、inference/image_processor.py,inference/model.py 的 Gate
  • Muon 与它的缩放:Moonlight,arXiv 2502.16982;本站 DeepSeek-V4 模型结构(6)
  • SinkGD:Scetbon et al.,2025,V4.1 引它作为 Sinkhorn 归一化用于优化器的出处
  • SigLIP:Zhai et al.,2023,视觉编码器第一阶段用的对比损失

评论