DeepSeek V4 稀疏注意力完全解剖:1M context 从 84GiB 压到 9.6GiB 是怎么做到的
DeepSeek V4 稀疏注意力完全解剖:1M context 从 84GiB 压到 9.6GiB 是怎么做到的
前情说明:AI Infra 综述系列讲了推理引擎六大主线,其中"Attention 机制革命"是最重最难的一条。本篇是技术深度篇 D1,专门挖 DeepSeek V4 的稀疏注意力——从数学原理到 KV cache 布局,再到 kernel fusion 和多 stream 编排,一层不落地讲清楚。 素材主要来自 vLLM Blog《DeepSeek V4 in vLLM: Efficient Long-context Attention》(2026-04-24),一篇非常良心的深度技术文。所有数字、公式、架构决策都来自这篇官方 blog + 附录数学推导。
起手:不可思议的数字
先把这些数字放一起,就能明白为什么这个东西值得单开一篇深挖:
| 模型 | Context | KV cache/序列 | KV cache/token/layer |
|---|---|---|---|
| DeepSeek V3.2 | 1M | 83.9 GiB | 1408 bytes |
| DeepSeek V4 (bf16) | 1M | 9.62 GiB | 平均 ~160 bytes |
| DeepSeek V4 (fp4 indexer + fp8 attn) | 1M | ~4.8 GiB | 平均 ~80 bytes |
8.7 倍 KV cache 压缩。不是靠"减少 layer 数"或者"缩小 hidden dim"这类粗暴手段,是靠在 attention 机制上做四层组合优化。
更重要的是——质量几乎没有损失。DeepSeek V4-Pro(1.6T)和 V4-Flash(285B)都是当前 SOTA 之一。
这就是这一代大模型的关键秘密——软硬件 co-design 已经不够,还需要模型架构本身为长上下文 + 高效推理从头设计。DeepSeek V4 是当前最完整的示范。
第一层:Long-context 的两座大山
任何 1M+ context 模型都要同时解决两个爆炸问题:
问题 A:KV cache 内存爆炸
标准 attention 的 KV cache 是 O(N × H × D × L):
- N = context 长度
- H = KV head 数
- D = head dim
- L = layer 数
1M context × 61 layers × 每 token 每 layer 1408 bytes = 83.9 GiB 单请求。B300 那 288GB HBM 都只能装 3 个并发用户。根本不能商用。
问题 B:Attention 计算爆炸
Attention 是 O(N²)——1M query × 1M key = 1 万亿次点积。即使 KV cache 装得下,算也算不动。
DeepSeek V4 对这两座山分别下药:
- 对 KV 内存爆炸:三层压缩(Shared KV + 多粒度压缩 + 稀疏 attention 只读 top-K)
- 对计算爆炸:DSA 稀疏 attention + 短滑动窗口
第二层:四大机制拆解
机制一:Shared KV — 让 K 和 V 共用一份存储
标准做法:K 和 V 是两个独立 tensor,各存一份。
DeepSeek V4 做法:K 和 V 用同一份 tensor——立即省一半 KV cache。
天真到爆的问题:K 和 V 的物理含义不同——K 用来算 attention weight(跟 Q 做点积),V 是被加权求和的实际信息。逼它们相等等于说"用来查找的地址和被查找的内容一样"——这在数学上没道理。
但 DeepSeek 这么做了,还需要一个补偿操作——Inverse RoPE——保证 attention 输出的平移不变性(translation invariance)。
深潜:为什么必须做 Inverse RoPE
这是全文数学最硬的一段,慢慢来。(vLLM blog 附录里有推导,我给你翻译成人话)。
先回忆 RoPE (Rotary Position Embedding) 的性质:
- 对位置
m的 query 应用旋转R_m,对位置n的 key 应用R_n - 关键性质:
R_m · R_n^T = R_(m-n)(旋转矩阵的差等于位置差的旋转)
标准 attention(K 和 V 独立时):
output_m = Σ_n softmax((R_m · q)^T (R_n · k_n)) · v_n
= Σ_n softmax(q^T · R_(m-n) · k_n) · v_n ← 只依赖相对位置 (m-n)
平移不变——所有 token 一起挪 100 个位置,输出不变。这是好性质。
现在 DeepSeek V4 让 K = V:
output_m = Σ_n softmax(q^T · R_(m-n) · k_n) · (R_n · k_n)
└─ V 也带 RoPE,因为它就是 K
注意最后那个 R_n——它是绝对位置(不是相对位置)。这个 R_n 让输出带上了绝对位置信息——违反了平移不变性。
补救:对 output 应用 inverse RoPE:
output_m' = R_m^{-1} · output_m
= Σ_n softmax(q^T · R_(m-n) · k_n) · R_(n-m) · k_n
└─ 又变成相对位置!
平移不变性恢复了——R_(n-m) 只依赖 n-m。
代价:每次 attention 输出后要做一次 R_m^{-1} 旋转。计算量微不足道(元素级操作),但代码里必须显式加。vLLM 的实现里,Inverse RoPE 跟后面的 fp8 quant fused 成一个 kernel(2-3x 加速)。
机制二:多粒度 KV 压缩 — c4a 和 c128a
核心思想:几个连续的 token 的 KV 合并成一个。
具体两种压缩率:
c4a(compress ratio ≈ 1/4):
原始 8 个 token: t0 t1 t2 t3 t4 t5 t6 t7
压缩规则: 每 4 个 token 打一个包 (stride=4),包内含 8 个 token 的加权和
└────────────┬─────────────┘ 重叠区
└────────────┬─────────────┘
...
压缩后 tokens: T0(=t0..t7 加权) T1(=t4..t11 加权) ...
- 每 8 个原始 token 压成 1 个 compressed token
- Stride 4 → 相邻两个 compressed token 有 4 token 重叠
- 有效压缩率约为 1/4(因为重叠)
c128a(compress ratio ≈ 1/128):
- 每 128 个原始 token 压成 1 个(stride 128,无重叠)
- 极致压缩,但保留信息更粗糙
DeepSeek V4 的 61 层用不同压缩策略:
- 一部分 c4a 层(保留局部细节)
- 一部分 c128a 层(保留全局结构)
- 少量纯 SWA 层(只保留 128 token 局部窗口)
这是 DeepSeek 的深度洞察——不同 layer 的 attention 承担不同粒度的信息聚合职责,所以压缩率应该不同。近处细节靠 c4a,远处大结构靠 c128a。
机制三:DeepSeek Sparse Attention (DSA) — 只关注 top-K
问题:c4a 压缩后,1M context 还有 250K 个 compressed token。做 attention 还是要算 Q·K^T over 250K 长度——依然巨大。
解法:Top-K 稀疏 attention——每个 query 只跟 K 个"最相关"的 compressed token 算 attention,其他直接忽略。
具体 K 值:
- c4a:K = 512(1M context 上从 250K 里选 512,稀疏率 1/500)
- c128a:K = 8192(1M context 上从 8K 里选 8K,等于全 attention——因为 8K 不算大)
Top-K 怎么选? 这里需要一个"廉价的相关性打分器"——Indexer。
Indexer 机制:一个极小的辅助 attention,专门用来给所有 compressed token 打分,然后 top-K 送到 main attention。因为 indexer 本身很轻(用 fp4 存),所以它的开销可控。
这就是 DSA 的名字来源——Deepseek Sparse Attention。它是 top-K 稀疏 attention 加上一个 indexer 层来选 top-K。
机制四:Short Sliding Window (SWA) — 保留局部细节
问题:c128a 有个致命 bug——第一个 compressed token 覆盖 position 0-127,但 query 在 position 5 时按照 causality 不能看到 position 5 之后的信息,也就是不能看 c128a 的第一个 compressed token。query 早期完全无 KV 可看。
解法:加一个 128-token 的短滑动窗口,作用在未压缩的原始 token 上。
Query at position m:
Sees c4a/c128a compressed tokens (远处 & 中距离信息)
+
Sees uncompressed tokens in [m-128, m-1] (紧邻局部信息)
收益:保证 causality 的同时不丢局部细节。128 是个精心选的数字——够短所以 KV cache 开销小(每层多存 128 个未压缩 token),够长所以能覆盖到最近的 compressed token 之前的所有位置。
第三层:把这些机制塞进 vLLM 需要面对的三个工程难题
现在你懂了模型侧。真正的痛苦在推理引擎侧:
- 异构 layer:61 层里有 c4a、c128a、SWA 三种 attention 类型,KV 存储方式各不相同
- 动态状态:每个 request 在不同 layer 的压缩边界位置都不同(比如某个 request 在 c4a 层可能刚好在 stride 边界,在 c128a 层却在窗口中间)
- 多种量化:Prefill 用 bf16 KV,decode 部分用 token-wise fp8,indexer 用 fp4
KV cache allocator 之前只需要管一种 KV 布局(PagedAttention 的 fixed-size block)。现在要同时管五种不同 page size 的 cache——vLLM 是怎么做到不爆炸的?
三个精妙的设计。
设计 1:统一的"逻辑 block size" — 256 个原始 token
天真做法:为每个 layer 单独设计 block size,比如 c4a 层的 block = 64 个 compressed tokens(对应 256 个原始 tokens),c128a 层的 block = 2 个 compressed tokens(对应 256 个原始 tokens)。这样 block 内的 compressed entry 数是"漂亮的整数"。
问题:每个 layer 有自己的 page 布局。Slot mapping、scheduler 记账、prefix hit 检测都要按 layer 分类处理——巨大的复杂度。
vLLM 的做法:逻辑 block size 统一为 256 个原始 token 位置,不管是什么压缩率:
- c4a block 物理上放 256 / 4 = 64 个 compressed entries
- c128a block 物理上放 256 / 128 = 2 个 compressed entries
- SWA block 物理上放 256 个原始 tokens
关键收益:分配一个 block 永远等于占 256 个原始 token 位置——不管哪层。Scheduler 只需要按"原始 token 位置"记账,完全不用分支处理压缩率。
这就是抽象的力量——把物理布局的差异藏在"每个 block 装多少 entry"这个变量里,暴露给上层的接口保持完全一致。
设计 2:Compressor state 复用 SWA 的抽象
DeepSeek V4 每一个压缩 layer 都有一个"rolling residual"——它是当前尚未完全压缩的中间状态:
- c4a 层需要保留最近 8 个原始 token 的部分聚合状态(用来产生下一个 compressed token)
- c128a 层需要保留最近 128 个原始 token 的状态
天真做法:为每个 request 单独开一个 side buffer 存这个 residual。
问题一堆:
- Prefix caching 需要在每个可缓存边界快照 residual 状态,附到 hash key 上
- Disaggregated prefill 需要额外一条传输通道把 residual 从 prefill worker 传到 decode worker
- CUDA Graph、MTP、metrics 都要分别处理这个额外状态
vLLM 的洞察:residual 的行为跟 sliding window KV 一模一样——
- 都是每 request 固定大小
- 都是随着 decode 前进而滚动更新
- 窗口外的都可以丢弃或缓存
所以 vLLM 直接把 compressor state 注册为 sliding window 类型的 KV:
sliding_window = coff × compress_ratio(c4a 是 8,c128a 是 128)- 用 SWA-style block 存储,塞进同一个 hybrid KV manager
- Prefix caching / Disagg prefill / CUDA Graph 全部免费复用 SWA 的处理逻辑
这个决策的伟大之处:它把一个"看起来完全不同的新东西"识别成"其实是老朋友的另一副面孔"——减少了整个系统需要维护的状态种类。
好的抽象就是这样——把 N 个新问题变成 1 个已解决的问题的变体。
设计 3:统一 page size 到三个桶
新问题:即使有了统一 block size,不同 cache 种类的物理字节数还是不同:
- C4 indexer block:256 个原始 token × 1/4 × 每 indexer entry 大小 = X bytes
- c128a KV block:256 × 1/128 × entry size = Y bytes
- c4a compressor state:8 × entry size = Z bytes
- 等等...
如果每个 cache 种类独立开一个 block pool,还是会有跨池碎片——pool A 空着但 pool B 满了,请求过来只能拒绝。
vLLM 的解法:精心选择每种 cache 的三个因子(block_size × compress_ratio × per_entry_size),让所有 cache 种类压到只有 3 种 page size:
- 最大桶:c4a main KV / SWA KV / c4a compressor state / c128a compressor state
- 中等桶:C4 indexer KV / C4 indexer compressor state
- 最小桶:c128a main KV
同一个桶内的所有 cache 种类共享一个 block pool——分配是纯 O(1) 的 bucket lookup,零碎片。
这个设计很像 malloc allocator 的 size class——把大小相近的分配请求打到同一个 size class 的 pool。tcmalloc / jemalloc 这套思路搬到了 KV cache。
第四层:Kernel Fusion — 三大融合点
内存布局搞定了,性能还差半个 GPU——因为 DeepSeek V4 attention 涉及大量小的、mostly memory-bound 的 kernel。每个 kernel 都要一次 HBM 读+写往返,累加起来 decode 路径慢得可怜。
解决方案:Kernel Fusion(内核融合)——把多个小 kernel 融合成一个大 kernel,减少 HBM 往返。
vLLM 在 DeepSeek V4 上做了三处融合,每处收益都很惊人。
融合点 1:Compressor + RMSNorm + RoPE + Cache Insertion
流程(未融合时):
Compressor 层 → 输出 compressed K
↓ HBM 写入
↓ HBM 读出
RMSNorm 层 → 归一化后的 K
↓ HBM 写入
↓ HBM 读出
RoPE 层 → 加旋转位置的 K
↓ HBM 写入
↓ HBM 读出
KV Cache Insertion → 写入 KV cache
↓ HBM 写入
问题:4 次 HBM 读+写往返。每一步都是内存 bound,算力浪费。
融合后:一个大 kernel,一次读入、一次写出。中间状态全部在 shared memory / registers 里。
Kernel 分两个版本——一个给 indexer K cache,一个给 main-attention K cache——因为两者 head dim 不同,最优 parallelization 策略也不同。
收益:1.4-3x 加速。
融合点 2:Inverse RoPE + FP8 Quantization
流程(未融合时):
Main Attention 输出
↓
Inverse RoPE (前面讲的补偿)
↓ HBM 往返
FP8 Quant (量化到 fp8 供后续 o_lora projection 用)
↓ HBM 往返
FP8 Batched Matmul (o_lora projection)
融合后:Inverse RoPE + FP8 Quant 融合成一个 kernel,避免了中间 HBM 往返。
为什么这个融合特别有效? 因为它显著提升 arithmetic intensity——Inverse RoPE 本身是元素级操作(memory bound),FP8 quant 也是。融合后单次读入一次写出,把两次操作的算力全塞进一次 HBM 访问的时间里。
收益:2-3x 加速。
融合点 3:Fused Q Norm + KV RoPE + K Insert(水平融合)
Main attention 之前需要做几件事:
- Query 上做 Q Norm
- Uncompressed SWA 路径的 Key 上做 RoPE + KV insert
- Compressed 路径的 K 已经在融合点 1 里处理过了
融合前的问题:这三个操作元素级、彼此独立——都在争抢 HBM 带宽。
融合后:水平融合(horizontal fusion)——一个 kernel 同时并行处理 Q head 和 K head。
关键实现细节:static warpID dispatch:
- 一个 warp 完全负责一个 Q head 或一个 K head
- 不同 warp 之间无需通信——避免了 warp-level barrier / atomic
- 每个 warp 独立跑自己的计算,static 分配决定谁做什么
为什么这个技巧收益特别大? 因为 GPU 上 warp 间通信是最贵的——每次同步都要花上百个 cycle。static warpID dispatch 完全消除了这个开销,同时保持了并行度。
收益:10-20x 加速(是这三个融合里收益最大的,因为原始 kernel 太浪费了)。
第五层:Multi-Stream 并行 —— 让不同分支并发跑
Kernel fusion 让每个 kernel 更快,但kernel 之间还是串行执行——如果能让多个独立分支同时跑,还能压 5-6%。
DeepSeek V4 Attention 的分支结构
对于 c4a 层(有 indexer),main attention 前的操作可以拆成三条独立分支:
- 分支 A:Indexer 计算(给 DSA 打分选 top-K)
- 分支 B:Main-attention KV compression(c4a 主路径)
- 分支 C:Sliding-window token insertion(SWA 路径)
初始 projection 完成后,这三条分支互相独立——不共享中间状态。
CUDA Stream 编排
vLLM 用 两个 CUDA stream:
- Default stream (blue band):跑分支 B + C
- Indexer stream (amber band):跑分支 A
对于 c4a 层:indexer pipeline 完全并行跑在 amber stream 上,跟 blue stream 上的 KV compression + SWA insertion 并发。
对于 c128a 层(无 indexer):main KV compression 和 SWA token insertion 并发跑(在同一 stream 里也可能重叠)。
收益:低 batch size 下 5-6% 端到端延迟下降。
5-6% 听起来不多——但要知道这是在已经充分 kernel fusion 的基础上再挤出来的。这是"最后一公里"优化。
为什么低 batch 收益更明显?
高 batch 时 GPU 已经很饱和了,multi-stream 增益有限;低 batch 时 GPU 时不时空转,用 multi-stream 把不同 kernel 挤到同一时刻跑,显著提高利用率。
这跟前面讲过的 SGLang zero-overhead scheduler 是同一个哲学——GPU 永远不能停,能用的每一纳秒都要榨干。
第六层:CUDA Graph 收尾
最后一层——vLLM 对 DeepSeek V4 decode 路径整体做 CUDA Graph 捕获。
为什么需要 CUDA Graph?
CUDA API 有个隐性成本:每次 kernel launch 大约 5-10μs 的 CPU 开销。DeepSeek V4 一次 decode 涉及几十个 kernel(每层几个融合 kernel × 61 层),累加起来就是几百微秒的 launch overhead——对 10ms 级 decode 来说是5-10% 的成本。
CUDA Graph 一次捕获、多次 replay:
- 第一次 decode 捕获整个操作图
- 后续 decode 只需要"replay"这张图——launch overhead 几乎归零
关键前提:图的结构必须相同——batch size、sequence length(quantized 到某个 tier)都得固定。所以 vLLM 用 FULL_AND_PIECEWISE 模式,同时捕获多个尺寸的图(多个 tier),运行时选最贴合的 tier 用。
DeepSeek V4 的 config 里就有这一行:
--compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE", "custom_ops":["all"]}'
全景图:从模型到 GPU 的一次 decode
把前面所有内容串起来——DeepSeek V4 从"query 到达"到"下一个 token 生成"完整的一次 decode 流程:
1. Scheduler 决定这一步跑哪些 request
↓
2. 分配 KV block(256 原始 token / block)
- c4a layer: 64 compressed entries
- c128a layer: 2 compressed entries
- SWA layer: 256 uncompressed tokens
- compressor state 走 SWA 抽象
↓
3. Decode forward(每层重复以下步骤,全部 CUDA Graph replay)
↓
A. QKV Projection (Q RoPE + quant + weight multiply 融合)
↓
B. Q Norm + KV RoPE + K Insert(水平融合,static warpID)
↓
C. 多 stream 并行:
Stream A (indexer): 计算 indexer scores → top-K selection
Stream B (main): KV compression → RMSNorm → RoPE → cache insert (Fusion 1)
Stream C (SWA): Sliding window token insertion
↓
D. Main Attention(FlashMLA kernel,只 attend top-K compressed + local SWA)
↓
E. Inverse RoPE + FP8 Quant (Fusion 2)
↓
F. o_lora projection (fp8 batched matmul)
↓
G. MoE Layer (FlashInfer / DeepGEMM MegaMoE kernel)
↓
H. 下一层...
↓
4. 生成下一个 token → 返回 scheduler → 循环
每一个步骤都被优化到极致:
- Kernel fusion 消除 HBM 往返
- Multi-stream 消除 kernel 间空隙
- CUDA Graph 消除 launch overhead
- Static warpID 消除 warp 间通信
- KV cache 布局消除跨类型 fragmentation
这就是"生产级"的含义——不是一个算法能跑,是堆叠十几层微观优化,让它跑得像单一模型那么高效。
尾声:这一课的三个启示
从 DeepSeek V4 这个案例,能提炼出三个通用规律——不只关于 attention,是关于整个 AI Infra:
启示 1:模型架构和推理引擎必须 co-design
过去:模型团队做模型,engine 团队做 engine,"能对上就行"。
现在:DeepSeek V4 的很多设计(shared KV / compressor sliding window / 三种压缩率 mixed layers)没有 vLLM 团队参与讨论根本落不了地。反过来 vLLM 的三种设计选择(unified logical block / SWA 抽象复用 / 三桶 page size)没有对模型架构深入理解就做不出来。
未来所有头部模型都会走这条路——设计模型时 engine 团队就在参与。这个门槛会淘汰绝大多数没有推理团队的模型公司。
启示 2:好的抽象胜过精妙的优化
vLLM 团队最厉害的不是三个 kernel fusion,是**"把 compressor state 识别为 SWA 变体"**这个抽象决策。
它把一个"新增 5 种 cache 类型"的问题变成"复用现有 SWA 抽象"的问题。整个 prefix caching / disagg prefill / CUDA graph / MTP 都免费兼容。
这是软件工程的最高境界——用抽象消灭复杂度,而不是用优化对抗复杂度。
启示 3:性能是"层层碾压"叠加出来的
DeepSeek V4 的 8.7x KV 压缩不是靠一个大招——是靠:
- Shared KV (2x)
- c4a 压缩 (4x)
- c128a 压缩 (128x,部分层)
- DSA top-K (计算量 500x 稀疏率)
- FP8/FP4 量化 (2x)
- ... 每一项都是独立的 2x-100x 提升
性能不是"找一个 silver bullet"——是"在每一层堆一个 2x,最后乘起来"。
这也是为什么 AI Infra 领域会长期需要专家——每一层的 2x 都需要深入理解才能拿到。做通用工具的人只能拿到某一层的收益,做深度优化的团队能把 10 层 2x 全拿到。
附:素材来源
主线
- vLLM Blog · DeepSeek V4 in vLLM: Efficient Long-context Attention(2026-04-24)—— 本文核心素材来源
- vLLM PR · #40760 —— DeepSeek V4 完整实现
- 部署 recipes · recipes.vllm.ai/deepseek-ai/DeepSeek-V4-Pro
论文 & 参考
- MLA · DeepSeek-V2 paper
- DSA · DeepSeek Sparse Attention
- RoPE · 原始论文
- Manifold-Constrained Hyper-Connections · arxiv 2512.24880
- 苏剑林关于 Inverse RoPE 的讨论 · kexue.fm/archives/10862
社区解读
- DeepSeek V4 KV Cache Design
- DeepSeek V4 KV Cache Visual Walkthrough
- zyeric/v4-attn-notes - Compressed Sparse Attention (CSA) / Heavily Compressed Attention (HCA) 学习笔记
姊妹篇
- 《推理引擎 2026 · 六大主线》 - 宏观视角
- 《vLLM vs SGLang · 架构哲学》
- 《现代 LLM 推理引擎剖析》
- 《训练时代的黄昏》 - 为什么长上下文 & 高效推理成为主战场
整理于 2026-07-31 深夜。原文技术细节归属 vLLM 团队与 DeepSeek 团队。数学推导部分基于官方 blog 附录 + 苏剑林公开讨论。中文表达可能有解读偏差,请以原文为准。

Written by
Zoe
AI Infra Engineer · LLM Serving · GPU/RDMA · 造工具的偏执狂