文章

deepseek-v4.1-技术报告学习

#llm
本文目录 10

Intro

之前v4-pro出来效果不怎么好,ds又上调api价格的时候,就给了我一种在驱逐用户回收算力做训练的感觉

本来以为是要去给v4重新做一下后训练再发布,没想到直接端出来一个v4.1-flash,而且甚至启动的是一套新架构,完全可以说是v5-flash了

在当下agent越来越流行的情况下,long-horizon的session越来越多,要求模型需要具备更长的上下文。于此同时,长文本在prefill阶段对计算施加了很大的压力;而decode阶段对cache的需求给存储带来了很大的压力

一个好的“系统“优化的问题就是在不断的去scaling,除了提升引擎就是优化模型的结构和dataflow,所以这里的keywords也就是:

  • less compute when prefilling
  • less cache when decoding
  • prefill和decode的非对称激活

Relevant Works

YOCO

You Only Cache Once

传统的transformer blocks是每层都有自己的Wq/k/vW_{q/k/v}来把hidden转换成Q/K/VQ/K/V矩阵,每一层的QKV都是等上一个layer输出hidden之后,再乘W矩阵得到的

这里则是有一些的layer直接复用前面某层的KV,所以需要cache的数量也减少了

IndexCache

对于一个sparse attention,相邻的layer对同一个token的query想要选取的kv indices是有高度相似的,因此某一层的layer选取的kv indice可以复用前面某层的的,避免lightning indexer的重复计算

有实验显示lightning indexer的开销甚至和sparse attention的开销差不多甚至更多,毕竟它是一个相当于全量的小的attention

模型架构

image-20260917173326275

这里的transformer layer被分成了两类:encoder和decoder,各自20层

CED, Casual Encoder-Decoder

把这些layer区分成两类,就是因为借鉴了YOCO的思想,后半段的所有的decoder使用的都是一个global kv,而它就是前半段encoder最终的输出

所以prefill阶段只需要跑encoder,把encoder每一层的kv和最终产生的hidden对应的global kv给缓存住就行;decode阶段的encoder部分就使用自己的kv,decoder阶段使用global kv

这里有关kv复用大概的意思是对的,下面解释注意力的时候有更详细的

最终产生的效果就是不对称的计算

CSA2

CSA2

CSA2的复用结构类以一个多级cache,Full Mode是复用范围最大的Anchor,生产出来的数据可以供后面的树用Reindex复用范围小一点,Reuse是最小的单位

三种mode

Full Mode

之前的v4的CSA+SWA的流程就是

历史 token
   │
   ├─→ 压缩后的 global KV
   │       ↓
   │    indexer
   │       ↓
   │    Top-K sparse attention
   │
   └─→ 最近 128 token
           ↓
          SWA

两版的CSA的区别有:

  • CSA2这里encoder的压缩比例是2,decoder的压缩比例是1也就是不压缩
  • CSA2首先把原本的CSA的压缩的overlap和绝对位置编码去掉了
  • CSA2的indexer K是从main KV投影出来的,替代原本CSA从hidden做一次运算
Reindex Mode

复用前面Full Mode生产出来的index K和main KV

Reuse Mode

复用前一个Reindex Mode选取的index,以及前一个Full Mode的index K和main KV

最终比例

Layer 0,1 : Window-only

Encoder(2~19) Full : 2, 8, 14 Reuse : 3-7, 9-13, 15-19

Decoder(20~39) Full : 20 Reindex : 24, 28, 32, 36 Reuse : 21-23, 25-27, 29-31, 33-35, 37-39


hierarchical sparse indexer

虽然已经使用了sparse attention,但是在面对更长的上下文的时候,去给所有的历史打一次分的开销还是很大,甚至比做真正的attention的开销还大

所以可以采用分级的思想来打分

所有 main KV entries
        ↓
先按连续位置分成 block
        ↓
先给所有的位置打分,再给每个 block 选一个分数(块内的最高分)
        ↓
选 Top blocks
        ↓
这些 block 展开成 candidate pool
        ↓
后续 Reindex 层只在这个 candidate pool 里再选 Top-K

这一步看起来是没有减少计算量的,甚至是增加了计算量的;但是后续的Reindex Mode里就直接复用这些已经选出来的block,而不会重新对全局的kv做打分选取了

这复用的真狠啊

encoder是每6层重算一个kv和indice;decoder直接全用前面的KV和indice Q,然后每5层复用一下index

Single-Pass mHC

mHC这里的变动就是把他变成了一个single-pass的运算过程

原版的公式是这样的,Xl+1=BlXl+ClFl(AlXl)X_{l+1} = B_l X_l + C_l F_l(A_l X_l)

而这个AiA_i其实是对XiX_i有依赖的,所以在进入layer之前需要先等待A_i的计算;

而这里改为了Xl+1=BlXl+ClFl(Al−1Xl)\boxed{ X_{l+1} = B_lX_l+ C_lF_l(A_{l-1}X_l) },A_i变成由上一层产生的了,因此这个依赖就被消除了,变成不用等待的single-pass的过程了

那为啥要消除这个依赖呢?在理想(没有依赖的)情况下,我们拿到的是上一层的输入的梯度和layer的输出Xl−1∈[m,d],Yl−1∈[1,d]X_{l-1}\in[m,d], Y_{l-1}\in[1,d];如果做单次的kernel运算就能得到这一层需要的Xi,Xi^X_i,\hat{X_i},那么只用(m+1)d(m+1)d次的读和写就可以完成,也就是总计2(m+1)d2(m+1)d次的运算:

HBM 读
X_{l-1}, Y_{l-1}
      ↓
同一个 GPU kernel 内部
   算各种 mixing
   算 X_l
   算 A_l
   算 X̂_l
      ↓
HBM 写
X_l, X̂_l

而如果存在依赖,这个就不能通过单次的kernel运算,而是需要多次的运算,三个 kernel:

  1. residual update:Xl=Bl−1Xl−1+Cl−1Yl−1X_l=B_{l-1}X_{l-1}+C_{l-1}Y_{l-1}

  2. coefficient prediction:(Al,Bl,Cl)=H(Xl)(A_l,B_l,C_l)=H(X_l)

  3. input mixing:Xl^=AlXl\hat{X_l} = A_l X_l

这个过程经过了4(m+1)d4(m+1)d次的读写,是理论下界的两倍

TODO

主要的模型的变化就这些,其他待拓展

Reference