DeepSeek V4 的架构详解
DeepSeek V4 的注意力机制详解
在 DeepSeek-V4 中,使用CSA(压缩稀疏注意力)和 HCA(重度压缩注意力)的混合注意力。CSA(压缩稀疏注意力)和 HCA(重度压缩注意力)并不是在同一层内计算并相加的,而是以交替(Interleaved)的方式在不同的 Transformer 层中使用。例如,第 3 层可能使用 CSA,第 4 层可能使用 HCA。

CSA这个机制的核心思想是:将长度为 nnn 的长序列,通过滑动窗口和加权融合的方式,压缩成长度为 nm\frac{n}{m}mn 的短序列,从而大幅减少后续计算的开销。
以下是其中压缩方法的详细步骤解析:
1. 基础特征映射
首先,模型接收输入序列的隐藏状态 H∈Rn×dH \in \mathbb{R}^{n \times d}H∈Rn×d(nnn 是序列长度,ddd 是隐藏层维度)。
接着,模型通过四个可训练的权重矩阵(WaKV,WbKV,WaZ,WbZW^{aKV}, W^{bKV}, W^{aZ}, W^{bZ}WaKV,WbKV,WaZ,WbZ),将 HHH 线性映射为四组不同的特征,维度均变为 n×cn \times cn×c(ccc 是注意力头的维度):
- KV 特征矩阵: CaC^aCa 和 CbC^bCb。它们是实际要被压缩和传递的内容信息。
Ca=H⋅WaKV,Cb=H⋅WbKVC^a = H \cdot W^{aKV}, \quad C^b = H \cdot W^{bKV}Ca=H⋅WaKV,Cb=H⋅WbKV
- 压缩权重矩阵: ZaZ^aZa 和 ZbZ^bZb。它们用于计算后续压缩时的“注意力得分”或“重要性权重”。
Za=H⋅WaZ,Zb=H⋅WbZZ^a = H \cdot W^{aZ}, \quad Z^b = H \cdot W^{bZ}Za=H⋅WaZ,Zb=H⋅WbZ
2. 计算压缩权重得分
这是该方法最巧妙的地方。为了生成第 iii 个压缩后的特征块 CiCompC^{\text{Comp}}_iCiComp,模型并不是简单地平均池化,而是结合了当前块和前一个块的信息,进行交叉加权。
对于第 iii 个输出,模型会抓取 2m2m2m 个元素参与计算:
- 来自 ZaZ^aZa 的当前块:索引从 mimimi 到 m(i+1)−1m(i+1)-1m(i+1)−1(共 mmm 个元素)。
- 来自 ZbZ^bZb 的前一个块:索引从 m(i−1)m(i-1)m(i−1) 到 mi−1mi-1mi−1(共 mmm 个元素)。
模型将这两组向量分别加上可学习的位置偏置 BaB^aBa 和 BbB^bBb,然后拼接在一起,并在行维度上进行 Softmax 归一化:
[Smi:m(i+1)−1a;Sm(i−1):mi−1b]=Softmaxrow([Zmi:m(i+1)−1a+Ba;Zm(i−1):mi−1b+Bb])[S^a_{mi:m(i+1)-1} ; S^b_{m(i-1):mi-1}] = \text{Softmax}_{\text{row}} ([Z^a_{mi:m(i+1)-1} + B^a ; Z^b_{m(i-1):mi-1} + B^b])[Smi:m(i+1)−1a;Sm(i−1):mi−1b]=Softmaxrow([Zmi:m(i+1)−1a+Ba;Zm(i−1):mi−1b+Bb])
结果: 得到了两组权重得分 SaS^aSa 和 SbS^bSb。由于经过了 Softmax,这 2m2m2m 个位置的得分加起来等于 1。
3. 加权求和得到压缩特征
拿到归一化的权重得分 SaS^aSa 和 SbS^bSb 后,模型将它们分别与对应的实际内容特征 CaC^aCa 和 CbC^bCb 进行哈达玛乘积(Hadamard product,即 ⊙\odot⊙,表示逐元素相乘),然后把这 2m2m2m 个加权后的向量全部相加:
CiComp=∑j=mim(i+1)−1Sja⊙Cja+∑j=m(i−1)mi−1Sjb⊙CjbC^{\text{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_jCiComp=j=mi∑m(i+1)−1Sja⊙Cja+j=m(i−1)∑mi−1Sjb⊙Cjb
结果: 输出了一个单一的向量 CiComp∈RcC^{\text{Comp}}_i \in \mathbb{R}^cCiComp∈Rc。这一个向量浓缩了原本 2m2m2m 个位置的信息。
💡 核心机制总结与关键点
- 重叠的感受野(Overlapping): 注意看公式,计算第 iii 个压缩块时,用到了 CbC^bCb 的前一个块。而计算第 i−1i-1i−1 个压缩块时,用到了 CaC^aCa 的同一个位置的内容。这种交错重叠的设计使得相邻的压缩块之间有信息的平滑过渡,避免了硬切分带来的边界信息丢失。
- 压缩比例: 虽然每次计算融合了 2m2m2m 个 KV 条目,但序列的步进(stride)是 mmm。因此,原始长度为 nnn 的序列最终会生成 nm\frac{n}{m}mn 个压缩特征。也就是实现了 1m\frac{1}{m}m1 的压缩率。
- 边界处理(Padding): 当 i=0i=0i=0(即序列刚开始,没有“前一个块”时),图片文本中特别指出:ZbZ^bZb 的对应部分会被填充为负无穷(确保 Softmax 后权重为 0),而 CbC^bCb 的对应部分会被填充为零。
深入解析:Lightning Indexer 的稀疏选择与核心注意力机制
在超长上下文的大模型推理中,如何快速且精准地定位关键信息是核心难点。Lightning Indexer 正是为此而生的轻量级“路由/打分”网络。它的核心使命是快速评估当前 Query Token 需要关注哪些压缩块,从而精准挑选出 Top-k 的块送入真正的“核心注意力(Core Attention)”层进行计算。
构建索引键与 Query 的低秩生成
为了实现极速的筛选,Lightning Indexer 在输入端做了精妙的降维与重构设计。
构建索引键 (Compressed Indexer Keys)
模型并没有直接复用前一步生成的 CCompC^{\text{Comp}}CComp 作为键,而是利用相同的压缩操作(即重叠滑动窗口机制),单独为索引器生成了一组压缩键:
KIComp∈Rnm×cIK^{I\text{Comp}} \in \mathbb{R}^{\frac{n}{m} \times c^I}KIComp∈Rmn×cI
这里的 cIc^IcI 是专门用于索引的头部维度(Indexer head dimension),它通常远小于实际用于注意力计算的维度。这种设计极大地节省了显存占用和计算带宽。
Query 的低秩生成 (Low-Rank Bottleneck)
为了进一步压榨计算复杂度,作者采用了“低秩瓶颈”结构来生成 Query 的索引向量,分为两步走:
- 降维 (Down-projection):将 Query Token ttt 的原始高维隐藏状态 ht∈Rd\mathbf{h}_t \in \mathbb{R}^dht∈Rd 通过矩阵 WDQW^{DQ}WDQ 压缩到一个极小的潜在空间,得到向量 ctQ∈Rdc\mathbf{c}_t^Q \in \mathbb{R}^{d_c}ctQ∈Rdc(其中 dc≪dd_c \ll ddc≪d)。
ctQ=ht⋅WDQc_t^Q = \mathbf{h}_t \cdot W^{DQ}ctQ=ht⋅WDQ - 升维多头映射 (Up-projection):将降维后的向量 ctQ\mathbf{c}_t^QctQ 通过矩阵 WIUQW^{IUQ}WIUQ 重新映射并切分,生成 nhIn_h^InhI 个不同的索引头 Query 向量。
[qt,1I;qt,2I;...;qt,nhII]=qtI=ctQ⋅WIUQ[\mathbf{q}_{t,1}^I; \mathbf{q}_{t,2}^I; ...; \mathbf{q}_{t,n_h^I}^I] = \mathbf{q}_t^I = \mathbf{c}_t^Q \cdot W^{IUQ}[qt,1I;qt,2I;...;qt,nhII]=qtI=ctQ⋅WIUQ
这种 d→dc→(heads×cI)d \to d_c \to (\text{heads} \times c^I)d→dc→(heads×cI) 的低秩设计,极大地减少了生成多头 Query 的参数量和计算 FLOPs,在算力密集的自回归生成阶段是至关重要的优化。
计算索引得分与稀疏选择
这是 Lightning Indexer 的核心打分逻辑,采用了动态加权的 ReLU 激活点积机制。
动态头权重与块相似度打分
模型不仅学习 Query 向量,还通过线性变换 WwW^wWw 为当前 Token ttt 预测出各个索引头上的动态重要性权重 wt,hIw_{t,h}^Iwt,hI。随后,计算 Query ttt 和第 sss 个压缩块之间的匹配得分 It,sI_{t,s}It,s:
It,s=∑h=1nhIwt,hI⋅ReLU(qt,hI⋅KsIComp)I_{t,s} = \sum_{h=1}^{n_h^I} w_{t,h}^I \cdot \text{ReLU}\left(\mathbf{q}_{t,h}^I \cdot K_s^{I\text{Comp}}\right)It,s=h=1∑nhIwt,hI⋅ReLU(qt,hI⋅KsIComp)
这里有一个非常关键的数学设计:通过 ReLU 激活函数,强制过滤掉了负相关的匹配(直接置零),在进入聚合前就引入了稀疏性,最后再进行加权求和得到最终标量得分。
稀疏选择 (Sparse Selection)
拿到所有的块得分 It,sI_{t,s}It,s 后,执行一个简单的非可导操作:选出得分最高的 kkk 个索引。
CtSprsComp={CsComp | It,s∈Top-k(It,:)}C_t^{\text{SprsComp}} = \left\{ C_s^{\text{Comp}} \;\middle|\; I_{t,s} \in \text{Top-k}(I_{t,:}) \right\}CtSprsComp={CsComp
It,s∈Top-k(It,:)}
系统会从原先的压缩 KV 缓存池中,把对应的 CCompC^{\text{Comp}}CComp 取出来,组成一个稀疏的集合 CtSprsCompC_t^{\text{SprsComp}}CtSprsComp。只有这 kkk 个被选中的高价值压缩块,才会真正进入后续的 Transformer 核心注意力层进行融合计算。
核心注意力计算与输出投影
当筛选完成后,模型进入核心注意力计算(Core Attention)和输出投影(Output Projection)阶段,这里同样充满了工程上的巧思。
Shared Key-Value MQA (共享键值的多查询注意力)
- 复用潜在 Query 向量:在索引器中已经计算过降维后的 Query 向量 ctQ\mathbf{c}_t^QctQ。这里直接共享该向量,仅用一个新的升维矩阵 WUQW^{UQ}WUQ 将其投影出用于核心注意力的 nhn_hnh 个查询头,避免了重新从高维隐藏状态 ht\mathbf{h}_tht 进行全连接映射,节省了大量计算。
- Key 和 Value 是同一个张量:这是最核心的创新。之前筛选出的稀疏压缩块 CtSprsCompC_t^{\text{SprsComp}}CtSprsComp 同时扮演了 Key 和 Value 的角色。结合 MQA(多查询注意力,所有头共享同一组 KV)的设定,这意味着模型在推理时需要维护的 KV Cache 体积被直接砍半。对于构建能够处理百万级别 Token 的智能体来说,这种 Shared KV 的设计极大地缓解了显存瓶颈。
Grouped Output Projection (分组输出投影)
针对 DeepSeek-V4 这种级别的超大模型配置,核心注意力计算后的输出维度 cnhc n_hcnh 极其巨大。传统的单一大矩阵映射 O(cnh×d)O(c n_h \times d)O(cnh×d) 会带来难以承受的计算灾难。作者为此设计了“沙漏型”的两阶段映射策略:
- 分组与降维:将所有的注意力头输出分成 ggg 个组。对于每一个组,先用一个较小的矩阵将其降维映射到一个中间维度 dgd_gdg(且满足 dg<cnhgd_g < c \frac{n_h}{g}dg<cgnh),得到中间特征 ot,iG′∈Rdg\mathbf{o}_{t,i}^{G'} \in \mathbb{R}^{d_g}ot,iG′∈Rdg。
- 拼接与升维:将这 ggg 个组的中间特征重新拼接起来(总维度 g×dgg \times d_gg×dg 远小于原始的 cnhc n_hcnh),最后再用一个矩阵将这个拼接后的向量映射回目标维度 ddd,得到最终的输出 o^t∈Rd\hat{\mathbf{o}}_t \in \mathbb{R}^do^t∈Rd。
HCA 层

这张图片介绍的是 HCA( Heavy Compression Attention) 的键值对压缩策略。
HCA 的核心逻辑与我们前面讨论的 CSA 非常相似,但它在设计上做了两个极其关键的简化与改动,目的是为了实现更激进的、粗粒度的长序列压缩。
我们可以通过对比之前的 CSA,来详细拆解 HCA 的这三个数学步骤:
单一特征映射(摒弃了 aaa 和 bbb 的双分支)
C=H⋅WKVC = H \cdot W^{KV}C=H⋅WKV
Z=H⋅WZZ = H \cdot W^ZZ=H⋅WZ
在 CSA 中,为了实现重叠窗口,模型把输入 HHH 映射成了两组特征(Ca,CbC^a, C^bCa,Cb 和 Za,ZbZ^a, Z^bZa,Zb)。而在 HCA 中,因为放弃了重叠压缩(does not perform overlapped compression),模型只需要最基础的单次线性投影。输入隐藏状态 HHH 直接通过 WKVW^{KV}WKV 和 WZW^ZWZ 得到单一的 KV 矩阵 CCC 和压缩权重矩阵 ZZZ。
独立窗口的局部 Softmax(无重叠感受野)
Sm′i:m′(i+1)−1=Softmaxrow(Zm′i:m′(i+1)−1+B)S_{m'i:m'(i+1)-1} = \text{Softmax}_{\text{row}}(Z_{m'i:m'(i+1)-1} + B)Sm′i:m′(i+1)−1=Softmaxrow(Zm′i:m′(i+1)−1+B)
- 这里是 HCA 和 CSA 最大的区别所在。
- 更大的压缩率:HCA 使用了步长 m′m'm′,并且明确标注了 m′≫mm' \gg mm′≫m(远大于 CSA 的步长)。这意味着它的分块窗口要大得多。
- 硬切分(Hard Chunking):HCA 在计算第 iii 个压缩块的注意力权重 SSS 时,仅仅截取了当前窗口内的 m′m'm′ 个元素(索引从 m′im'im′i 到 m′(i+1)−1m'(i+1)-1m′(i+1)−1),加上位置偏置 BBB 后直接做 Softmax。它不再像 CSA 那样去向后“借”前一个块的信息。×dg\times d_g×dg(这个维度远小于原始的 cnhc n_hcnh)。最后,再用一个矩阵将这个拼接后的向量映射回目标维度 ddd,得到最终的输出 o^t∈Rd\hat{\mathbf{o}}_t \in \mathbb{R}^do^t∈Rd。
块内加权聚合
CiComp=∑j=m′im′(i+1)−1Sj⊙CjC_i^{\text{Comp}} = \sum_{j=m'i}^{m'(i+1)-1} S_j \odot C_jCiComp=j=m′i∑m′(i+1)−1Sj⊙Cj
- 拿到当前独立窗口内的权重得分 SSS 后,与对应的特征 CCC 进行哈达玛乘积并求和,把这 m′m'm′ 个 Token 浓缩成 111 个高维向量 CiCompC_i^{\text{Comp}}CiComp。最终序列长度被压缩到了原来的 1m′\frac{1}{m'}m′1。
HCA 的后处理阶段(核心注意力和输出投影)
几乎完全复用了 CSA 的策略
这部分与 CSA 完全一致。为了降低计算量,模型没有直接用一个大矩阵生成多头 Query,而是通过一个瓶颈(Bottleneck)结构: - 先用 WDQW^{DQ}WDQ 将 Query Token ht\mathbf{h}_tht 降维到极小的 dcd_cdc 维度(得到 ctQ\mathbf{c}_t^QctQ)。
- 再用 WUQW^{UQ}WUQ 升维并切分成多头 Query qt,i\mathbf{q}_{t,i}qt,i。
共享键值的多查询注意力 - ⚠️ 关键差异点
ot,i=CoreAttn(query=qt,i,key=CComp,value=CComp)\mathbf{o}_{t,i} = \text{CoreAttn}(\text{query}=\mathbf{q}_{t,i}, \text{key}=C^{\text{Comp}}, \text{value}=C^{\text{Comp}})ot,i=CoreAttn(query=qt,i,key=CComp,value=CComp)
这里依然采用了极其省显存的 Shared KV 设计(压缩后的块既当 Key 又当 Value)。但是,请注意看传递进去的变量! - 在 CSA 中,传进去的是经过 Lightning Indexer 筛选的 CtSprsCompC_t^{\text{SprsComp}}CtSprsComp(稀疏 Top-k 集合)。
- 在 HCA 中,传进去的直接是 CCompC^{\text{Comp}}CComp(全量的重度压缩块)。
分组输出投影 (Grouped Output Projection)
这部分同样是为了应对模型极宽(注意力头极多)时的计算灾难。将所有头的输出切分成 ggg 组,先降维到 dgd_gdg,拼接后再映射回隐藏维度 ddd。
💡 架构视角的深度解析
- 细粒度与平滑过渡 (CSA): 使用较小的压缩率 mmm,且带有重叠窗口。它负责处理相对近期的历史信息,重叠设计保留了 Token 之间的序列连续性和边界细节,防止关键的局部逻辑断裂。
- 极致的粗粒度与大容量 (HCA): 使用极大的压缩率 m′m'm′(比如将几百个 Token 压成 1 个),并且使用无重叠的硬切分。无重叠设计省去了复杂的交叉计算和双分支投影,使得计算效率最大化。HCA 的目标不是保留细节,而是为了在极其有限的显存/KV Cache 预算下,强行塞入更久远的历史宏观语义。
这种“近期平滑、远期粗放”的双轨压缩策略,是在不无限增加显存的前提下,让大模型突破 100k 甚至 1M Token 上下文瓶颈的有效手段。对于需要处理海量文档或进行超长周期推理的 Agent 来说,这种在计算复杂度和信息保真度之间做出的 Trade-off 非常具有参考价值。
DeepSeek V4 的mHC机制详解
一、 终极目的:谱范数与非扩张 (Spectral Norm & Non-expansive)
我们要解决的核心问题是:在残差连接(或者超连接)中,信号在经过成百上千层的矩阵相乘后,为什么会发生梯度爆炸或消失?
1. 谱范数 (Spectral Norm)
想象一个向量 xxx(代表前向传播中的特征信号),当它乘以一个矩阵 BBB(代表网络层的权重映射)时,BxBxBx 的本质是对特征信号进行拉伸或压缩。
矩阵 BBB 的谱范数(通常记为 ∣∣B∣∣2||B||_2∣∣B∣∣2)衡量的是这个矩阵对任何非零向量 xxx 的最大拉伸比例。
用数学语言表达就是:
∣∣B∣∣2=maxx≠0∣∣Bx∣∣2∣∣x∣∣2||B||_2 = \max_{x \neq 0} \frac{||Bx||_2}{||x||_2}∣∣B∣∣2=x=0max∣∣x∣∣2∣∣Bx∣∣2
在线性代数中,它等于矩阵 BBB 的最大奇异值(σmax\sigma_{max}σmax)。
2. 非扩张 (Non-expansive)
顾名思义,“非扩张”就是 “绝不放大信号”。
如果一个矩阵变换是非扩张的,就意味着对于任意输入的信号 xxx,变换后的长度绝不会超过原来的长度:
∣∣Bx∣∣2≤∣∣x∣∣2||Bx||_2 \le ||x||_2∣∣Bx∣∣2≤∣∣x∣∣2
这就等价于要求矩阵的谱范数 ∣∣B∣∣2≤1||B||_2 \le 1∣∣B∣∣2≤1。
在模型中的意义: 如果 ∣∣B∣∣2>1||B||_2 > 1∣∣B∣∣2>1,经过 100 层累乘,信号会被放大 1.1100≈137801.1^{100} \approx 137801.1100≈13780 倍,导致数值爆炸(NaN)。如果保证 ∣∣B∣∣2≤1||B||_2 \le 1∣∣B∣∣2≤1(即非扩张),无论网络堆叠多深,信号都处于安全边界内。
二、 约束的概念:流形 (Manifold)
既然我们需要 BBB 的谱范数 ≤1\le 1≤1,最粗暴的方法是每次更新完权重就强行把矩阵缩放一下。但这破坏了梯度下降的自然连续性。更优雅的做法是让矩阵 BBB 只能在一个绝对安全的“空间”里更新。这个空间,就是流形。
什么是流形?
流形是一个几何概念,指局部看起来像平坦的欧几里得空间,但整体可能弯曲或有特定形状的空间。
- 通俗比喻: 地球表面。对站在操场上的人来说,地面是二维平坦的(局部欧几里得),你可以前后左右走动(梯度下降);但在宇宙视角看,它是一个被约束在三维空间中的二维球面(流形)。
在 mHC 的语境下,所有可能的 n×nn \times nn×n 矩阵构成了一个巨大的、无边无际的高维空间(充满着会导致信号爆炸的危险矩阵)。我们不想让残差矩阵 BBB 在这个无边无际的空间里乱跑,我们要把它“锁”在一个安全的特定几何表面上。 这个特定的表面,就是接下来要说的 Birkhoff 多胞形。
三、 具体的安全形状:双随机矩阵的 Birkhoff 多胞形
现在我们需要找到一个具体的数学流形,只要矩阵 BBB 在这个流形上,就绝对满足非扩张(∣∣B∣∣2≤1||B||_2 \le 1∣∣B∣∣2≤1)的要求。
1. 双随机矩阵 (Doubly Stochastic Matrix)
这是一个非常特殊的方阵,必须同时满足三个条件:
- 非负性: 矩阵里的每一个元素都大于等于 0(Bij≥0B_{ij} \ge 0Bij≥0)。
- 行和为 1: 每一行的所有元素加起来等于 1(∑jBij=1\sum_j B_{ij} = 1∑jBij=1)。
- 列和为 1: 每一列的所有元素加起来等于 1(∑iBij=1\sum_i B_{ij} = 1∑iBij=1)。
2. Birkhoff 多胞形 (Birkhoff Polytope)
- 多胞形 (Polytope): 你可以把它理解为高维空间中的“多边形”或“多面体”。
- Birkhoff 多胞形: 它是由所有 n×nn \times nn×n 的双随机矩阵构成的一个几何凸集(Convex Polytope)。
根据 Birkhoff-von Neumann 定理,这个多胞形的每一个“顶点”,恰好是所有的置换矩阵(Permutation Matrices,即只进行行/列交换,不改变数值大小的矩阵)。
在模型中的意义: 论文中说的“约束在流形上”,其实就是把残差矩阵 BBB 死死地限制在了这个 Birkhoff 多胞形的几何体内。
四、 终极串联:为什么这个设计能保证绝对稳定?
这是最惊艳的一步:只要 BBB 是一个双随机矩阵(身处 Birkhoff 多胞形中),它就必定是非扩张的!
在矩阵理论中,有一个著名的范数不等式(Schur界):
矩阵的谱范数(2-范数),一定小于等于它的 列和最大值(1-范数) 与 行和最大值(∞\infty∞-范数) 乘积的平方根。
∣∣B∣∣2≤∣∣B∣∣1⋅∣∣B∣∣∞||B||_2 \le \sqrt{||B||_1 \cdot ||B||_\infty}∣∣B∣∣2≤∣∣B∣∣1⋅∣∣B∣∣∞
因为我们把 BBB 约束成了双随机矩阵:
- 所有列的和都是 1 ⇒∣∣B∣∣1=1\Rightarrow ||B||_1 = 1⇒∣∣B∣∣1=1
- 所有行的和都是 1 ⇒∣∣B∣∣∞=1\Rightarrow ||B||_\infty = 1⇒∣∣B∣∣∞=1
代入公式:
∣∣B∣∣2≤1×1=1||B||_2 \le \sqrt{1 \times 1} = 1∣∣B∣∣2≤1×1=1
证明完毕! 没有任何外部的强制截断或粗暴缩放,仅仅通过让矩阵 BBB 的生成过程最终经过 Sinkhorn-Knopp 算法(该算法的作用就是把任意正数矩阵投影变成双随机矩阵),模型在数学机制上获得了两个极其强大的保障:
- 单层安全: 无论输入什么信号,经过残差矩阵 BlB_lBl 后,特征方差绝对不会放大。
- 无限深度的安全: 两个双随机矩阵相乘,结果仍然是双随机矩阵(Birkhoff 多胞形对乘法封闭)。这意味着即使你堆叠 1000 层 mHC,最终的复合残差映射也依然被完美锁定在这个多胞形内,谱范数永远 ≤1\le 1≤1。

下面用具体的数值走一遍算法,是理解矩阵变换最直观、最硬核的方式。
在开始推演之前,我需要稍微纠正一个小概念:Sinkhorn-Knopp 算法的“收敛”并不是指矩阵里的数值无限变小(趋于 0),而是指矩阵的“每一行的和”与“每一列的和”快速逼近于 1。 当然,由于原始的指数化矩阵数值通常很大,在归一化的过程中,数值确实会缩小并被限制在 (0,1)(0, 1)(0,1) 之间。
为了让你看得很清楚,我们构造一个简单的 2×22 \times 22×2 矩阵来进行一次完整的迭代(t=1t=1t=1)。
第一步:初始化 M(0)M^{(0)}M(0)
假设神经网络生成的原始残差映射参数矩阵 B~l\tilde{B}_lB~l 为:
B~l=[0.6931.0981.3860]\tilde{B}_l = \begin{bmatrix} 0.693 & 1.098 \\ 1.386 & 0 \end{bmatrix}B~l=[0.6931.3861.0980]
根据公式 M(0)=exp(B~l)M^{(0)} = \exp(\tilde{B}_l)M(0)=exp(B~l),我们对矩阵里的每一个元素求自然指数(这里为了计算方便,我特意取了 ln(2),ln(3)\ln(2), \ln(3)ln(2),ln(3) 等近似值):
M(0)≈[2341]M^{(0)} \approx \begin{bmatrix} 2 & 3 \\ 4 & 1 \end{bmatrix}M(0)≈[2431]
此时的状态检查(距离双随机矩阵有多远?):
- 列和 (Column Sums): 第 1 列是 2+4=62 + 4 = \textbf{6}2+4=6;第 2 列是 3+1=43 + 1 = \textbf{4}3+1=4。
- 行和 (Row Sums): 第 1 行是 2+3=52 + 3 = \textbf{5}2+3=5;第 2 行是 4+1=54 + 1 = \textbf{5}4+1=5。
- 结论: 行和与列和与目标值 1\textbf{1}1 差得很远。
第二步:列归一化 Tc\mathcal{T}_cTc (第一次迭代的前半步)
公式中的 Tc\mathcal{T}_cTc 表示列归一化,即把每一列的元素除以该列的总和,强行让列和变成 1。
- 第 1 列处理: 元素除以列和 6 →\rightarrow→ 2/6=1/32/6 = 1/32/6=1/3, 4/6=2/34/6 = 2/34/6=2/3
- 第 2 列处理: 元素除以列和 4 →\rightarrow→ 3/43/43/4, 1/41/41/4
我们得到了列归一化后的中间矩阵:
Mc=[1/33/42/31/4]≈[0.3330.7500.6670.250]M_c = \begin{bmatrix} 1/3 & 3/4 \\ 2/3 & 1/4 \end{bmatrix} \approx \begin{bmatrix} 0.333 & 0.750 \\ 0.667 & 0.250 \end{bmatrix}Mc=[1/32/33/41/4]≈[0.3330.6670.7500.250]
此时的状态检查:
- 列和: (1/3+2/3)=1(1/3 + 2/3) = \textbf{1}(1/3+2/3)=1,(3/4+1/4)=1(3/4 + 1/4) = \textbf{1}(3/4+1/4)=1。(列和完美符合要求!)
- 行和: 第 1 行是 1/3+3/4=1.0831/3 + 3/4 = \textbf{1.083}1/3+3/4=1.083;第 2 行是 2/3+1/4=0.9172/3 + 1/4 = \textbf{0.917}2/3+1/4=0.917。
- 结论: 列和搞定了,但行和被破坏了,不过比最开始的 555 已经非常接近 111 了。
第三步:行归一化 Tr\mathcal{T}_rTr (第一次迭代的后半步,完成 t=1t=1t=1)
公式中的 Tr\mathcal{T}_rTr 表示行归一化,即把 McM_cMc 每一行的元素除以该行的总和,强行让行和变成 1。
- 第 1 行处理: 行和是 13/1213/1213/12。
元素除以行和 →\rightarrow→ (1/3)/(13/12)=4/13≈0.308(1/3) / (13/12) = \textbf{4/13} \approx 0.308(1/3)/(13/12)=4/13≈0.308, (3/4)/(13/12)=9/13≈0.692(3/4) / (13/12) = \textbf{9/13} \approx 0.692(3/4)/(13/12)=9/13≈0.692 - 第 2 行处理: 行和是 11/1211/1211/12。
元素除以行和 →\rightarrow→ (2/3)/(11/12)=8/11≈0.727(2/3) / (11/12) = \textbf{8/11} \approx 0.727(2/3)/(11/12)=8/11≈0.727, (1/4)/(11/12)=3/11≈0.273(1/4) / (11/12) = \textbf{3/11} \approx 0.273(1/4)/(11/12)=3/11≈0.273
我们得到了完成一次完整迭代后的矩阵 M(1)M^{(1)}M(1):
M(1)=[4/139/138/113/11]≈[0.3080.6920.7270.273]M^{(1)} = \begin{bmatrix} 4/13 & 9/13 \\ 8/11 & 3/11 \end{bmatrix} \approx \begin{bmatrix} 0.308 & 0.692 \\ 0.727 & 0.273 \end{bmatrix}M(1)=[4/138/119/133/11]≈[0.3080.7270.6920.273]
此时的状态检查:
- 行和: (4/13+9/13)=1(4/13 + 9/13) = \textbf{1}(4/13+9/13)=1, (8/11+3/11)=1(8/11 + 3/11) = \textbf{1}(8/11+3/11)=1。(行和完美符合要求!)
- 列和: 第 1 列是 4/13+8/11≈1.0354/13 + 8/11 \approx \textbf{1.035}4/13+8/11≈1.035;第 2 列是 9/13+3/11≈0.9659/13 + 3/11 \approx \textbf{0.965}9/13+3/11≈0.965。
总结:我们看到了什么?
通过对比 t=0t=0t=0 和 t=1t=1t=1 的状态,我们可以清晰地看到 Sinkhorn-Knopp 算法的“收敛”魔力:
- 初始误差极大: M(0)M^{(0)}M(0) 的行和列和分别是 4, 5, 6,完全不受控。
- 迭代一次后误差极小: 仅仅经过一次 t=1t=1t=1 的交替归一化,矩阵的行和已经被强行拉到了 1,而列和变成了 1.035 和 0.965。
- 数值的变化: 矩阵里的数值从最初的 2,3,4,12, 3, 4, 12,3,4,1 迅速被压缩成了 (0,1)(0, 1)(0,1) 之间的小数。
如果你继续进行 t=2,t=3…t=2, t=3 \dotst=2,t=3… 迭代,列和与行和会像钟摆一样越来越小幅地振荡,直到在 t=20t=20t=20 时,行和与列和将无限逼近于 1.00000,最终完美收敛为一个真正的双随机矩阵。此时,这个矩阵参与网络层之间的残差相乘,就绝对不会导致数值爆炸了。
DeepSeek-V4 中引入的流形约束超连接(Manifold-Constrained Hyper-Connections, mHC)是一种用来替代传统残差连接的高级架构设计。在极深的网络中,传统的残差叠加或简单的超连接(Hyper-Connections, HC)极易导致数值不稳定和梯度爆炸。
mHC 的核心思想是将残差映射矩阵约束在特定的数学流形(双随机矩阵的 Birkhoff 多胞形)上 。这保证了残差变换是非扩张的(即谱范数始终 ≤1\le 1≤1),从而在不损失模型表达能力的前提下,极大地提高了深层网络前向传播和反向传播的稳定性。
以下我们将从数学公式、维度变化推演以及代码实现三个维度为你详细拆解。
一、 数学公式推导
标准的 Transformer 某层输入和输出均为 ddd 维,即 x∈Rdx \in \mathbb{R}^dx∈Rd。但在 mHC 中,残差流(Residual Stream)被拓宽了 nhcn_{hc}nhc 倍,变为了矩阵形式 Xl∈Rnhc×dX_l \in \mathbb{R}^{n_{hc} \times d}Xl∈Rnhc×d。
对于第 lll 层,其状态更新公式为:
Xl+1=BlXl+ClFl(AlXl)X_{l+1} = B_l X_l + C_l \mathcal{F}_l(A_l X_l)Xl+1=BlXl+ClFl(AlXl)
其中 Fl\mathcal{F}_lFl 代表当前层的实际计算模块(如 MoE 或 Attention)。为了实现上述公式,需要动态生成三个关键的线性映射:输入映射 AlA_lAl、残差映射 BlB_lBl 和输出映射 ClC_lCl。
1. 动态参数生成
将当前层输入展平并归一化,作为动态生成参数的条件 :
X^l=RMSNorm(vec(Xl))∈R1×nhcd\hat{X}_l = \text{RMSNorm}(\text{vec}(X_l)) \in \mathbb{R}^{1 \times n_{hc}d}X^l=RMSNorm(vec(Xl))∈R1×nhcd
随后通过线性层和可学习门控因子(α\alphaα),生成无约束的原始参数:
A~l=αlpre⋅(X^lWlpre)+Slpre\tilde{A}_l = \alpha_l^{pre} \cdot (\hat{X}_l W_l^{pre}) + S_l^{pre}A~l=αlpre⋅(X^lWlpre)+Slpre
B~l=αlres⋅Mat(X^lWlres)+Slres\tilde{B}_l = \alpha_l^{res} \cdot \text{Mat}(\hat{X}_l W_l^{res}) + S_l^{res}B~l=αlres⋅Mat(X^lWlres)+Slres
C~l=αlpost⋅(X^lWlpost)T+Slpost\tilde{C}_l = \alpha_l^{post} \cdot (\hat{X}_l W_l^{post})^T + S_l^{post}C~l=αlpost⋅(X^lWlpost)T+Slpost
2. 施加流形约束 (Constraints)
为了保证稳定性,需要对上述原始参数进行严格约束:
- 输入/输出映射 (Sigmoid 约束):保证非负性和有界性 。
Al=σ(A~l)A_l = \sigma(\tilde{A}_l)Al=σ(A~l)
Cl=2σ(C~l)C_l = 2\sigma(\tilde{C}_l)Cl=2σ(C~l) - 残差映射 (Sinkhorn-Knopp 算法):将 B~l\tilde{B}_lB~l 投影到双随机矩阵流形上,使其每行和每列的元素之和均为 1,且所有元素非负。具体通过迭代实现:
M(0)=exp(B~l)M^{(0)} = \exp(\tilde{B}_l)M(0)=exp(B~l)
M(t)=Tr(Tc(M(t−1)))M^{(t)} = \mathcal{T}_r(\mathcal{T}_c(M^{(t-1)}))M(t)=Tr(Tc(M(t−1)))
通过交替进行列归一化 Tc\mathcal{T}_cTc 和行归一化 Tr\mathcal{T}_rTr,通常迭代 tmax=20t_{max} = \textbf{20}tmax=20 次后收敛,得到最终的 Bl=M(20)B_l = M^{(20)}Bl=M(20) 。
二、 维度变化推演 (以 Batch=2, Seq=1000, d=8192 为例)
我们设定 mHC 的超参数配置遵循 DeepSeek-V4 的设定:扩展因子 nhc=4n_{hc} = \textbf{4}nhc=4,核心隐藏层维度 d=8192d = \textbf{8192}d=8192。
| 处理阶段 | 操作说明 | 张量维度 (形状) |
|---|---|---|
| 0. 初始状态 | 上一层的输出 XlX_lXl,其在每个 Token 位置维护了 4 个不同的 ddd 维向量。 | [2, 1000, 4, 8192] |
| 1. 展平与归一化 | 在 nhcn_{hc}nhc 和 ddd 两个维度上展平,并进行 RMSNorm 。4×8192=327684 \times 8192 = 327684×8192=32768。得到条件变量 X^l\hat{X}_lX^l。 | [2, 1000, 32768] |
| 2. 生成 A~l\tilde{A}_lA~l 与 AlA_lAl | X^l\hat{X}_lX^l 经过线性层 WpreW^{pre}Wpre 投影到 4 维,加上静态偏置,经过 Sigmoid 约束。这就是各个通道的输入加权系数。 | [2, 1000, 1, 4] |
| 3. 生成 C~l\tilde{C}_lC~l 与 ClC_lCl | X^l\hat{X}_lX^l 经过线性层 WpostW^{post}Wpost 投影到 4 维,经过 2×Sigmoid2 \times \text{Sigmoid}2×Sigmoid 约束。这就是各个通道的输出分配系数。 | [2, 1000, 4, 1] |
| 4. 生成 B~l\tilde{B}_lB~l 与 BlB_lBl | X^l\hat{X}_lX^l 经过线性层 WresW^{res}Wres 投影到 16 维,Reshape 为 4×4 矩阵 。利用 Sinkhorn-Knopp 迭代 20 次,得到双随机矩阵 BlB_lBl。 | [2, 1000, 4, 4] |
| 5. 准备模块输入 | 输入系数矩阵 AlA_lAl 与 XlX_lXl 进行矩阵乘法 (Al⋅XlA_l \cdot X_lAl⋅Xl),将 4 个通道的特征缩减为 1 个 ddd 维输入特征。 | [2, 1000, 8192] |
| 6. 模块前向传播 | 经过实际的 Transformer 计算层 Fl\mathcal{F}_lFl(如 Attention 或 MoE)。 | [2, 1000, 8192] |
| 7. 还原输出维度 | 将模块的输出 Fl\mathcal{F}_lFl 与输出系数矩阵 ClC_lCl 相乘,将 1 维输出重新广播/分配到 4 个通道中。 | [2, 1000, 4, 8192] |
| 8. 残差传递 | 原状态 XlX_lXl 经过双随机矩阵 BlB_lBl 进行通道间的特征融合和流转 (Bl⋅XlB_l \cdot X_lBl⋅Xl)。 | [2, 1000, 4, 8192] |
| 9. 最终叠加 | Xl+1=BlXl+ClFl(AlXl)X_{l+1} = B_l X_l + C_l \mathcal{F}_l(A_l X_l)Xl+1=BlXl+ClFl(AlXl)。 | [2, 1000, 4, 8192] |
三、 PyTorch 代码实现
以下代码展示了如何使用 PyTorch 实现 mHC 模块,包含其动态参数生成机制与核心的 Sinkhorn-Knopp 算法。
import torch
import torch.nn as nn
import torch.nn.functional as F
class mHC_Layer(nn.Module):
def __init__(self, d=8192, n_hc=4, t_max=20):
super().__init__()
self.d = d
self.n_hc = n_hc
self.t_max = t_max
self.flat_dim = n_hc * d
# 动态参数的生成权重 (W) 和静态偏置 (S)
self.W_pre = nn.Linear(self.flat_dim, n_hc, bias=False)
self.S_pre = nn.Parameter(torch.zeros(1, 1, 1, n_hc))
self.W_res = nn.Linear(self.flat_dim, n_hc * n_hc, bias=False)
self.S_res = nn.Parameter(torch.zeros(1, 1, n_hc, n_hc))
self.W_post = nn.Linear(self.flat_dim, n_hc, bias=False)
self.S_post = nn.Parameter(torch.zeros(1, 1, n_hc, 1))
# 可学习门控因子 (初始化为较小的值)
self.alpha_pre = nn.Parameter(torch.ones(1) * 0.01)
self.alpha_res = nn.Parameter(torch.ones(1) * 0.01)
self.alpha_post = nn.Parameter(torch.ones(1) * 0.01)
def sinkhorn_knopp(self, B_tilde):
# 确保正值矩阵 M^(0)
M = torch.exp(B_tilde)
# 迭代 t_max 次,交替进行列归一化和行归一化
for _ in range(self.t_max):
M = M / (M.sum(dim=-2, keepdim=True) + 1e-6) # 列归一化
M = M / (M.sum(dim=-1, keepdim=True) + 1e-6) # 行归一化
return M
def forward(self, X_l, F_l_module):
"""
X_l: 形状 [batch_size, seq_len, n_hc, d]
F_l_module: 当前层的实际计算逻辑 (如 MoE 层)
"""
b, seq, _, _ = X_l.shape
# 1. 展平并归一化
# [b, seq, n_hc * d]
X_flat = X_l.view(b, seq, -1)
X_hat = F.rms_norm(X_flat, (self.flat_dim,))
# 2. 动态生成原始参数
# [b, seq, 1, n_hc]
A_tilde = self.alpha_pre * self.W_pre(X_hat).unsqueeze(2) + self.S_pre
# [b, seq, n_hc, n_hc]
B_tilde = self.alpha_res * self.W_res(X_hat).view(b, seq, self.n_hc, self.n_hc) + self.S_res
# [b, seq, n_hc, 1]
C_tilde = self.alpha_post * self.W_post(X_hat).unsqueeze(3) + self.S_post
# 3. 施加约束
A_l = torch.sigmoid(A_tilde)
C_l = 2 * torch.sigmoid(C_tilde)
B_l = self.sinkhorn_knopp(B_tilde) # 投影到双随机矩阵流形
# 4. 前向传播计算
# 输入投影:[b, seq, 1, n_hc] x [b, seq, n_hc, d] -> [b, seq, 1, d] -> [b, seq, d]
module_input = torch.matmul(A_l, X_l).squeeze(2)
# 经过具体的 Transformer 块
module_output = F_l_module(module_input)
# 输出重分配:[b, seq, d] -> [b, seq, 1, d]
module_output = module_output.unsqueeze(2)
# [b, seq, n_hc, 1] x [b, seq, 1, d] -> [b, seq, n_hc, d]
output_contribution = torch.matmul(C_l, module_output)
# 残差投影:[b, seq, n_hc, n_hc] x [b, seq, n_hc, d] -> [b, seq, n_hc, d]
residual_transformation = torch.matmul(B_l, X_l)
# 5. 最终叠加
X_next = residual_transformation + output_contribution
return X_next
在 DeepSeek-V4 中,将前 3 层的密集前馈网络(Dense FFN)替换为使用 Hash 路由(Hash Routing)的 MoE 层,是一个兼顾参数容量扩展与计算效率的精妙工程设计。
以下是关于“为什么只在前 3 层使用”的深入剖析,以及 Hash 路由与原本动态路由的机制对比。
DeepSeek V4 的Hash 路由详解
一、 为什么只对前 3 层进行 Hash 路由?
这个决定是由 Transformer 模型不同深度的特征表达性质决定的:
- 浅层关注词法(浅层特征),深层关注语义(上下文特征):
在 Transformer 的最初几层,Token 的隐藏状态还没有充分融合周围的上下文信息,其特征主要由 词汇本身(Token ID) 决定。在这些浅层使用基于上下文的动态路由(计算注意力/特征的相似度)有点“杀鸡用牛刀”,因为此时的特征还不够丰富,路由网络很难学到复杂的语义分配。 - 低成本实现参数规模的暴涨:
在 DeepSeek-V3 及更早的版本中,前几层通常是 Dense(密集)层,因为浅层做动态 MoE 收益不高。但 Dense 层的参数量有限。V4 通过引入基于 Token ID 的 Hash 路由,实际上是在浅层实现了一个极其庞大的、非线性的“扩展词表嵌入(Extended Embedding)”。它让浅层拥有了 MoE 级别的参数容量,却完全不需要承担动态路由网络的计算和通信开销。 - 为什么深层不能用?
到了第 4 层及以后,Token 的特征已经高度上下文化(例如,苹果公司的“苹果”和水果的“苹果”,虽然 Token ID 相同,但此时的隐藏状态 xxx 已经完全不同)。如果此时还在用固定的 Hash 路由,就会阻碍模型根据上下文动态选择专家的能力。因此,Hash 路由只适合前 3 层。
二、 Hash 路由是如何操作的?(具体例子)
Hash 路由的本质是**“静态的、基于身份的分配”**。它直接根据输入的 Token ID 来决定去哪个专家 ,而不看这个 Token 所在的上下文。
举例推演:
假设前 3 层的某一层有 N=256N = 256N=256 个路由专家。
- 输入:句子 “The apple is red”,假设 “apple” 在词表中的 Token ID 是 4567。
- Hash 计算:系统使用一个预定义的哈希函数(例如最简单的取模运算):
Target_Expert=Hash(4567)(mod256)\text{Target\_Expert} = \text{Hash}(4567) \pmod{256}Target_Expert=Hash(4567)(mod256)
假设 4567(mod256)=2154567 \pmod{256} = 2154567(mod256)=215。 - 结果:无论是哪篇文档、哪个语境,只要遇到 “apple”(ID: 4567),在前 3 层它永远且必定会被分配给第 215 号专家进行处理。
三、 Hash 路由与原本路由(DeepSeekMoE)的速度对比
传统的 MoE 路由(如 DeepSeek-V4 第 4 层及以后使用的路由)是**“动态的、基于特征的分配”**。
1. 传统 DeepSeekMoE 路由的计算流程
对于每一个 Token,其隐藏状态向量 x∈Rdx \in \mathbb{R}^dx∈Rd 需要经历以下步骤:
- 矩阵乘法 (GEMM):与门控权重矩阵 Wgate∈Rd×NW_{gate} \in \mathbb{R}^{d \times N}Wgate∈Rd×N 相乘,计算出与所有 NNN 个专家的亲和力分数(Logits)。
- 激活函数:在 DeepSeek-V4 中,使用 Sqrt(Softplus(⋅))\text{Sqrt}(\text{Softplus}(\cdot))Sqrt(Softplus(⋅)) 函数计算最终的分数 。
- Top-K 排序:在 NNN 个分数中进行排序,选出得分最高的 KKK 个专家(V4 中 K=6K=6K=6)。
- 负载均衡计算:为了防止某些专家被“饿死”或“撑死”,还需要引入偏置更新和序列级的平衡损失计算。
2. 为什么 Hash 路由更快?
对比之下,Hash 路由在速度和系统调度上具有压倒性优势:
- 计算复杂度从 O(d×N)O(d \times N)O(d×N) 降为 O(1)O(1)O(1):Hash 路由只需要对一个整数(Token ID)进行一次数学哈希运算,完全消除了门控网络的矩阵乘法(GEMM)。
- 零负载均衡开销:由于现代哈希函数在数学上具有均匀分布的特性,词表中的 Token ID 会被自然、均匀地打散到各个专家中。因此,Hash 路由不需要任何辅助损失(Auxiliary Loss)或动态偏置调整来维持负载均衡。
- 极佳的系统确定性:在传统的 MoE 中,由于路由是动态的,底层系统(如 Expert Parallelism)在运行时需要等待路由结果出来后,才能知道 GPU 之间需要通信多少数据。而 Hash 路由是静态的,只要拿到 Input IDs,系统在进入 Transformer 层之前就可以预先知道并调度所有专家的通信,极大地掩盖了通信延迟。
更多推荐


所有评论(0)