我们从DeepSeek V4的技术报告出发,以此为蓝本来查漏补缺,并进一步建立相关知识储备。万丈高楼,始于平地。
Speculative Decoding与MTP
在进入核心的模型创新点之前,我们首先回顾一下Speculative Decoding以及从DeepSeek V3一并沿用至V4的MTP策略。需要明确的是,MTP是一种 训练目标或模型结构,speculative decoding 是一种推理加速方法。在DeepSeek的模型中,MTP本质上是为了更好地支持speculative decoding,同时还能提升模型能力。
对于一个标准的autoregressive LLM,在给定了$x_{\lt t}$的上下文之后,一次forward只生成一个token,训练目标为$p(x_t|x_{\lt t})$。这个策略所存在的问题是显然的:在每一次inference的时候,每一个token都会经历一个完整的forward过程。而为了提升效率,在训练时就引入MTP作为训练目标,也即每一个step都预测多个token:在DeepSeek-V3的架构中,除了主模型之外还引入了MTP modules专门用于预测主模型之后的token。
而在推理时,一般情况下的speculative decoding可以理解为先用便宜模型或模块草稿式地猜几个 token,再让大模型一次性检查这些token是否正确。注意:speculative decoding的操作可以保证其token的概率分布和朴素策略一致,推导如下
假设目标模型,也就是原本的大模型,其条件分布为$p(x_t \mid x_{\lt t})$;而 draft model 或 MTP module 给出的近似分布为$q(x_t \mid x_{\lt t})$,在普通 decoding 中,每一步都直接从$p$中采样:$x_t \sim p(\cdot \mid x_{\lt t})$;因此,生成$K$个 token 需要进行$K$次目标模型 forward:$x_t, x_{t+1}, \ldots, x_{t+K-1}$. 而在 speculative decoding 中,我们首先用较便宜的draft model一次性生成一段候选 token:$\hat{x}_t, \hat{x}_{t+1}, \ldots, \hat{x}_{t+K-1} \sim q$,随后,目标模型$p$并不是逐个重新生成这些token,而是对整段draft token进行一次并行验证。对于第$i$个draft token $\hat{x}_{t+i}$,目标模型会计算$p(\hat{x}_{t+i} \mid x_{\lt t}, \hat{x}_t, \ldots, \hat{x}_{t+i-1}),$并将其与 draft model 的概率$q(\hat{x}_{t+i} \mid x_{\lt t}, \hat{x}_t, \ldots, \hat{x}_{t+i-1})$进行比较。接受该 token 的概率通常写作:
$$ \alpha_i = \min\left( 1, \frac{ p(\hat{x}_{t+i} \mid x_{\lt t}, \hat{x}_{t:t+i-1}) }{ q(\hat{x}_{t+i} \mid x_{\lt t}, \hat{x}_{t:t+i-1}) } \right). $$直觉上,如果draft model给出的token在目标模型看来也足够合理,即 $p/q$ 较大,那么该token就会被接受;如果draft model过于自信地提出了一个目标模型并不认可的token,那么该token就更可能被拒绝。一个关键点是,标准speculative decoding并不是简单地“猜对就用,猜错就丢”,而是通过接受-拒绝采样保证最终生成分布仍然与直接从目标模型$p$解码一致。为了看清这一点,可以先考虑单个token的情况。draft model先采样:$\hat{x} \sim q(\cdot)$,若 $\hat{x}$ 被接受,则输出 $\hat{x}$。此时某个 token $x$ 被接受并输出的概率为:$$ q(x) \cdot \min\left(1, \frac{p(x)}{q(x)}\right) = \min(p(x), q(x)). $$如果draft token被拒绝,则不能直接重新从$p$中采样,否则会破坏分布一致性。正确做法是从residual distribution中采样:$$ p_{\text{res}}(x) = \frac{(p(x)-q(x))_+}{ \sum_{x'} (p(x')-q(x'))_+ }, $$其中$(a)_+ = \max(a, 0)$;因此,一个token $x$最终被输出的总概率为:$$ \underbrace{\min(p(x), q(x))}_{\text{accepted from draft}} + \underbrace{(p(x)-q(x))_+}_{\text{sampled from residual}} = p(x). $$这说明,只要接受-拒绝和residual sampling的过程设计正确,speculative decoding的最终输出分布就和直接从目标模型$p$中采样保持一致。它改变的是计算方式,而不是目标分布。
回到DeepSeek的MTP设计,其作用正是在这里体现出来。MTP module相当于为主模型提供了一个轻量级、与主模型高度对齐的internal drafter。由于MTP module在训练阶段就被要求预测未来token,并且与主模型共享部分表示或输出空间,它给出的draft token往往比一个外部小模型更加贴近主模型分布。
DeepSeek MoE
接下来是MoE的部分,我们一步步回顾一下目前已经成为基模主流的MoE设计的基本原理以及经典的优化与改进策略。我们知道,一个标准的Transformer层包含了attention和FFN的部分,而FFN往往占据了大量的参数和计算,因此MoE就希望不让每一个token都经过一个巨大的FFN,而是准备多个expert FFN,并让router为每一个token都选择少数几个expert。具体而言,一个标准的FFN可以写成
expert collapse:router总是偏向少数expert,导致部分expert过载,部分expert几乎不被使用。
expert redundancy:多个expert 学到相似知识,没有形成足够清晰的分工。
routing instability:训练早期router不稳定,导致token-expert分配波动较大。
communication overhead:在分布式训练中,不同token被送到不同expert,通常需要all-to-all communication,这会带来额外系统开销。
所以MoE的核心难点不是“把 FFN 拆成很多expert”这么简单,而是如何让expert既被均衡使用,又真正形成specialization。DeepSeek MoE针对这个需求提出了以下两点改进方案:
- Fine-Grained Expert Segmentation:本质上就是把较大的expert拆成较小的expert,例如原来是8个专家选2个,那么拆分之后可能变成64个专家用16个,这自然也就意味着模型可以为不同的token构造更加细粒度的expert组合,从而增强specialization
- Shared Expert Isolation: 虽然上述操作能让不同的expert学到更加细粒度的知识,但有些通用知识实际上所有的expert都会需要,如果不加处理的话就可能会导致不同expert有部分参数需要处理类似的知识。因此,DeepSeekMoE更进一步引入了$K_s$个shared expert(我们记这一集合为$\mathcal{S}$),不经过router选择而对所有token都激活
最终得到的MoE层可以写成
Manifold-Constrained Hyper-Connection
接下来就是mHC这一个核心的贡献,我们从Transformer中使用的最基本的residual connection出发,一步步推导mHC的来历和原理。对于一个普通的Transformer层,可以抽象地写成
这也可以帮助理解为什么当前许多大模型训练更倾向于采用 Pre-Norm 结构,即
$$ x_{l+1}=x_l+F_l(\text{Norm}(x_l)) $$如果采用Post-Norm,那么我们实际上有$$ x_{l+1}=\text{Norm}(x_l+F_l(x_l)) $$这实际上就破坏了$x_l$到$x_{l+1}$的无损传播路径,归一化参数更新时的梯度变化有可能导致梯度消失或爆炸
我们把Identity Mapping逐层展开,得到的结果如下所示,可以发现最初的信号$x_0$能够一直传递到深层。
为了解决这个挑战,HC把一条 residual stream 扩展为了$n$条。具体来说,我们记第$l$层开始前的多条residual streams为
另一种理解HC的方式是回忆LSTM的结构:LSTM中$h_{t}$其实同时依赖当前的输入$x_{t-1}$以及$h_{t-1}$;原始的$h_{t-1}$只是一个$d$维的向量,而HC的思想相当于$h_{t-1}$变成了一个$d\times n$的矩阵,其中$n$为residual条数。普通的residual connection自然就可以看成是HC的一个特例
接下来我们可以证明,HC在提供了相比于普通残差连接更加丰富通路的同时,却也重新引入了梯度消失和梯度爆炸的问题:如果我们HC的更新函数进行展开,会得到
因此,DeepSeek-V4中所引入的mHC正是为了解决这一问题,它通过约束residual stream mixing matrix中$R_l$的取值,使其满足$R_l\in\mathcal{B}_n$,其中$\mathcal{B}_n$被称为Birkhoff Polytope,为所有$n\times n$双随机矩阵的集合:
假设我们只考虑skip path:
$$ X_{l+1}^{\text{skip}}=R_lX_l $$我们定义所有residual streams的平均表示为$$ \tilde{x_l}=\frac{1}{n}\mathbf{1}^\top X_l. $$那么我们有$$ \tilde{x}_{l+1}=\frac{1}{n}\mathbf{1}^\top X_{l+1}^{\text{skip}}=\frac{1}{n}\mathbf{1}^\top R_lX_l $$由于$R_l\in\mathcal{B}_n$,因而我们有$$ \tilde{x}_{l+1}=\frac{1}{n}\mathbf{1}^\top R_lX_l=\frac{1}{n}\mathbf{1}^\top X_l=\tilde{x}_l $$这说明,在mHC中,residual stream mixing并不会改变多条streams的平均表示,只是对于信息的重新分配。进一步地,我们可以证明对于任意深度的复合residual mapping,$A_{0\rightarrow L}=R_{L-1}R_{L-2}\dots R_0$,依然有$A_{0\rightarrow L}\in\mathbb{B}_n$
在实际操作时,mHC通常不会直接学习一个已经满足双随机约束的矩阵,而是先学习一个uncontrained matrix,然后再进行投影和归一化操作来进行近似,使其符合双随机矩阵的性质。
Compressed Sparse Attention (CSA)
接下来我们分析DeepSeek-V4中另一个核心结构:Hybrid Attention with CSA and HCA。对于标准的attention,当序列长度为$n$时,每一个query token都需要和前面所有KV tokens做attention,因此整体复杂度随序列长度近似呈二次增长。为了解决这一问题,DeepSeek-V4 引入了Compressed Sparse Attention以及Heavily Compressed Attention,也即CSA和HCA。在本章节中,我们主要讨论CSA的思路:CSA同时结合了KV压缩和稀疏选择这两种思想:它首先把每$m$个token的KV cache压缩成一个compressed KV entry,然后再通过DeepSeek Sparse Attention的方式为每个query选择top-$k$ 个compressed KV entries参与核心attention计算。
我们先来看KV压缩的部分,这一块可以通俗理解为$m$个token的信息融合为一个token的pooling过程,但不同于最naive的average pooling,这里我们希望模型自己去判断不同token之间的重要性以及融合的策略。由于这一块之前接触的不是很多,因此我们先从high-level的角度对比一下传统的KV Cache以及CSA的思想
在标准attention里,我们通常有$K=[k_1,\dots, k_n]$,$V=[v_1,\dots, v_n]$,其中$n$为token总数,同时我们有
$$ o_t=\text{softmax}(q_tK^\top)V $$因此在标准的KV Cache中,会分别缓存$K_{1:t}$以及$V_{1:t}$来重复利用;但CSA作为一个KV Cache压缩的策略,并不是分别生成$K^{\text{comp}}$以及$V^{\text{comp}}$,而是生成一个$C^{\text{comp}}\in\mathbb{R}^{\frac{n}{m}\times c}$,其中每一行$C_i^{\text{comp}}\in\mathbb{R}^c$表示第$i$个压缩之后的KV entry。如果我们想要和标准的KV Cache对齐,我们可以粗略理解为$K_i^{\text{comp}}=C_i^{\text{comp}}$,$V_i^{\text{comp}}=C_i^{\text{comp}}$,也即$C^{\text{comp}}$同时作为压缩之后的key matrix和value matrix。总结而言,对于传统的KV Cache,我们是考虑每一层的$K$以及$V$;但在CSA中,我们是直接利用$H$来计算$C^{\text{comp}}$。我们将在之后的推导中,逐步展开这一套混合注意力机制的完整流程
设其输入的hidden states为$H\in\mathbb{R}^{n\times d}$,其中$n$为序列长度,$d$为hidden size。CSA不直接为每一个token都保存完整的key和value,而是先通过线性映射得到两组候选KV Entries:
接下来我们看$i$个压缩之后的KV entry是如何得到的,它对应的主窗口为$\{mi,mi+1,\dots, m(i+1)-1\}$。一个朴素的想法可能是直接把这$m$个token的候选KV entries聚合成一个向量,但CSA的设计逻辑稍微复杂一些:它同时使用两组候选entries $C^a$和$C^b$,并在生成第$i$个compressed entry时,使用$C_{mi:m(i+1)-1}^a$以及$C_{m(i-1):mi-1}^b$,分别负责当前block以及前一个block。对应的compression logits也就变成了$Z_{mi:m(i+1)-1}^a+B^a$以及$Z_{m(i-1):mi-1}^b+B^b$。将这两者拼接起来,可以得到一个大小为$2m\times c$的压缩打分矩阵,然后CSA对这个矩阵在token维度上做softmax操作,就得到了对应的压缩权重
要特别注意的一点是,对于CSA而言,attention中KV侧的组织方式已经和原始的Transformer有了明显的区别。在标准的Transformer中,每一个token都会对应一组token-level的$k_j$以及$v_j$,当前query通常需要在所有可见历史token的$K,V$上做attention。而现在这里我们得到的$C^{\text{Comp}}$可以理解为基于CSA层的输入$H$所得到的一个memory representation,而不是主干网络中继续向后传播的hidden feature,它的作用是作为后续attention计算中被query读取的压缩KV memory。
接下来,我们将进一步考虑attention的具体计算过程。假设原始序列长度为$n$,CSA经过压缩之后得到的compressed KV entries的数量大约是$n/m$,在超长上下文的context之下依然可能非常大。如果每一个query在计算attention的过程中依然需要dense attend(也即query和每一个compressed entry都要交互),那么attention的计算量依然很高。因此,CSA在得到了$C^{\text{comp}}$之后还会引入一个轻量级的sparse selection模块,也即论文中的Lightning Indexer,用来为每一个query token动态选择最相关的top-$k$个compressed KV entries
那么如何设计这个indexer的逻辑呢?我们首先需要明确这个indexer的输入:原始给到attention层输入自然是$H=[h_1,\dots, h_n]$,但是既然此处的目标是选择对应的query token和哪些Compressed Token来计算attention,因此这个indexer的输入应该也是Compressed之后的KV Entry。但是,这里并没有直接使用$C_i^{\text{comp}}$,而是使用刚刚介绍的一整套压缩策略来构造出另一组Compressed Token $K^{I,\text{Comp}}\in\mathbb{R}^{n/m \times c_I}$, 其中$c_I\ne c$为indexer的head dimension,一般$c_I\lt c$(Index中每一个token的特征维度会更小,这一设计也相对符合直觉)。也就是说,$K^{I,\mathrm{Comp}}$ 只用于计算index score,负责判断“哪些 compressed block 值得被选中”;而真正进入后续 core attention、作为key/value被读取的,仍然是对应位置上的$C^{\mathrm{Comp}}$
在得到了用于indexing的KV indexer $K^{I,\text{Comp}}$之后,接下来就需要考虑query token的计算了。这里同样要注意区分的是,相比于原始Transformer中的$Q=W_QX$的基本计算,在这里用于选择到底哪些KV Cache比较重要的query并不是原始值,而也是专门的index query,计算方式如下:
其中$h_t\in \mathbb{R}^d$是query token所对应的hidden state,$c_t^Q\in \mathbb{R}^{d_c}$是query压缩之后得到的结果,$d_c\lt d$为query压缩维度;$n_h^I$则定义了用于索引的query一共有几个head;$W^{DQ}\in\mathbb{R}^{d\times d_c}$以及$W_{IUQ}\in\mathbb{R}^{d_c\times c^In_h^I}$为索引query的投影矩阵,注意到最终投影得到的维度是$c^In_h^I$,也即$n_h^I$个head,每一个head所对应的维度$c^I$与前面计算得到的$K^{I,\text{Comp}}$是对齐的。这样,我们也就可以计算query token $t$以及第$s$个compressed block之间的index score $I_{t,s}\in\mathbb{R}$,其实就是$n_h^I$个head所对应的$q_{t,r}^I$分别与前面专门用于index的第$s$个索引$K_s^{I,\text{Comp}}$计算点积($s\lt\text{Floor}(\frac{t}{m})$,相当于选取的是query token的$t$所对应的前一个完整的block),外部再加一层可学习的权重即可,具体而言有:
其中$W^w\in\mathbb{R}^{d\times n_h^I}$为一个可学矩阵,$w_{t,h}^I\in\mathbb{R}$也即表示第$h$个索引头的权重。在上述推导中,需要注意$q_{t,r}^I\in \mathbb{R}^{c_I}$,$K_s^{I,\text{Comp}}\in \mathbb{R}^{c_I}$;在得到了index query $t$与compressed block $s$之间的分数之后,对于第$t$个token而言就只会保留top-$k$个compressed block来用于计算:
Heavily Compressed Attention (HCA)
在这一章中,我们将讨论HCA:HCA的思想与CSA基本类似,但是使用更加激进的压缩比,同时删除了CSA中使用的前后block组合压缩以及sparse selection的思想。假设输入的hidden states为$H\in\mathbb{R}^{n\times d}$,HCA先计算出待压缩的原始KV entry $C\in\mathbb{R}^{n\times c}$以及对应的weight logit $Z\in \mathbb{R}^{n\times c}$:
其中$W^{KV}, W^Z\in\mathbb{R}^{d\times c}$为可训练参数。类似CSA的思路,$C$中每$m'\gg m$个token都会根据压缩权重以及新引入的learnable positional bias $B\in\mathbb{R}^{m'\times c}$被压缩到一个token之中,得到$C^{\text{Comp}}\in \mathbb{R}^{\frac{n}{m'}\times c}$,其中每一个压缩之后项$C_i^{\text{Comp}}\in \mathbb{R}^c$如下计算:
通过这一操作,HCA就把KV表征的序列长度压缩到到原来的$\frac{1}{m'}$;之后,HCA也类似CSA来进行KV共享的MQA以及output projection。在这里,由于没有了sparse selection的环节,因此query token会和所有满足因果约束的历史的compressed KV entries来计算attention(而不是CSA中的其中一个子集)。具体而言,对于一个给定的token $t$,我们有
其中$h_t\in\mathbb{R}^d$为token t的hidden state;$n_h$表示query head的数量;$W^{DQ}\in\mathbb{R}^{d\times d_c}$以及$W^{UQ}\in\mathbb{R}^{d_c\times cn_h}$为可学的投影矩阵。最终,我们的便可以得到输出:
总结
在第一部分中,我们总结了DeepSeek-V4在模型结构层面的核心设计逻辑;而在之后的分析中,我们将进一步深入DeekSeek-V4的更多设计细节,例如Muon优化器的使用,推理框架以及预训练和后训练的框架。