DeepSeek-V4_CSA-HCA与V4.1-CSA2详解

鱿鱼圈 Lv4

DeepSeek-V4 CSA/HCA 与 DeepSeek-V4.1 CSA2:从模型原理到推理基础设施

本文基于 DeepSeek-V4 与 DeepSeek-V4.1-Flash 官方技术报告和官方参考推理代码整理。重点解释 CSA2,包括 Compressor、Full/Reindex/Reuse、Hierarchical Sparse Indexer、CED Prefill/Decode、KV cache 布局、量化、分布式训练与推理实现。

预备知识假设:了解一点 MLA 和 DSA,但不要求熟悉 DeepSeek-V4 的具体实现。


目录

  1. 一句话结论
  2. 先建立 MLA、DSA、CSA 的坐标系
  3. DeepSeek-V4 的 CSA
  4. DeepSeek-V4 的 HCA
  5. 为什么 V4 要把 CSA 和 HCA 交替使用
  6. 从 CSA 到 CSA2:到底改了什么
  7. 重点:CSA2 的 Compressor 为什么叫“简化”
  8. 重点:Full、Reindex、Reuse 到底复用了什么
  9. V4.1 的精确层布局
  10. 重点:Decoder 中 ratio=1,没有压缩 token,收益在哪里
  11. Hierarchical Sparse Indexer
  12. CSA2 单层的完整张量流
  13. CED:为什么 Prefill 能跳过大部分 Decoder
  14. Decode 时的逐层数据流
  15. 对应官方代码的简化实现
  16. 为什么正好是 890 bytes/token
  17. KV Cache Manager 与持久化缓存
  18. SWA Bounded Replay
  19. 分布式训练与推理 Kernel
  20. 公开参考代码与生产系统的区别
  21. 复杂度与收益总结
  22. 常见误解与局限
  23. 参考资料

1. 一句话结论

DeepSeek-V4 的全局注意力由两种层交替构成:

  • CSA:小倍率序列压缩 + 稀疏 Top-K 检索;
  • HCA:大倍率序列压缩 + 对压缩结果做稠密 attention。

DeepSeek-V4.1 取消 HCA,改成纯 CSA2。CSA2 同时在三个维度压缩长期全局 KV:

  1. Entry 维度:64 个 Query head 共享一个 512 维 Main KV latent;
  2. Sequence 维度:Encoder 每 2 个 token 压成 1 个 Main KV entry;
  3. Layer 维度:38 个 CSA2 层实际只建立 4 份 global KV cache。

另外:

  • 只有少数层重新运行 indexer;
  • 大部分层复用上游层的 Top-K;
  • Decoder 后续 indexer 只在最多 16,384 个候选位置中搜索;
  • CED 让绝大多数 prompt token 只经过下半个模型;
  • SWA state 不长期持久化,丢失时通过 Bounded Replay 近似恢复。

因此 CSA2 的重点绝不只是“每几个 token 压成一个 token”,而是:

把 global memory 的内容、索引结构、访问位置和局部状态拆开,并赋予它们不同的生成频率、共享范围、精度和生命周期。


2. 先建立 MLA、DSA、CSA 的坐标系

设上下文长度为 ,模型层数为 。

2.1 MLA 压缩的是每个位置的 KV 宽度

普通 MHA 对每个 token、每一层保存很多个 KV head:

MLA 将每个位置的 KV 压缩为一个较小的 latent:

所以 MLA 主要压缩的是:

但是位置数量没有变:

个个

如果仍然对全部位置做 dense attention,那么每个 Decode token 仍需要扫描 个位置。

2.2 DSA 压缩的是实际参与主 Attention 的位置数

DSA 增加一个低成本 indexer:

  1. 用低维 Indexer Q/K 给历史位置打分;
  2. 从 个位置中选择 Top-;
  3. 真正昂贵的主 attention 只访问这 个位置。
个历史位置个位置

但是 DSA 通常仍然需要:

  • 保存每个历史位置的 Main KV;
  • 保存每个历史位置的 Indexer K;
  • 让 indexer 扫描很长的上下文。

所以 DSA 主要降低 core attention FLOPs,不会自动等比例降低 KV cache 与 indexer 扫描成本。

2.3 CSA 在 DSA 之前再做一次序列压缩

CSA 首先执行:

个个

再从 个压缩位置中选择 Top-:

因此可以把三者理解成:

方法 压缩的主要对象
MLA 每个 token 的 KV 宽度或 head 维度
DSA 真正参与主 attention 的历史位置数量
CSA 先压缩历史位置数量,再进行 DSA Top-K
CSA2 在 MLA/DSA/CSA 的基础上继续压缩 layer 维度,并复用索引

3. DeepSeek-V4 的 CSA

3.1 总体数据流

flowchart LR H["当前层历史 Hidden States<br/>N × d"] H --> C1["Main-KV Compressor<br/>N → N/m"] H --> C2["Indexer-K Compressor<br/>N → N/m"] X["当前 Query Hidden State"] --> QL["低秩 Query latent"] QL --> IQ["Indexer Q"] QL --> MQ["Main Q"] C2 --> SCORE["Lightning Indexer<br/>扫描 N/m 个压缩位置"] IQ --> SCORE SCORE --> TOPK["Top-K indices"] C1 --> GATHER["按照 indices gather<br/>K 个 compressed Main KV"] TOPK --> GATHER H --> SWA["最近 W 个未压缩 SWA KV"] GATHER --> CONCAT["逻辑拼接"] SWA --> CONCAT MQ --> CORE["Shared-KV MQA"] CONCAT --> CORE CORE --> OUT["Grouped Output Projection"]

V4-Flash 中:

  • CSA compression ratio:;
  • attention Top-K:512;
  • SWA window:;
  • Main Query heads:64;
  • Main head dimension:512;
  • Indexer heads:64;
  • Indexer head dimension:128。

V4-Pro 的 CSA 同样使用 ,但 Top-K 为 1024。

3.2 V4 CSA 的 Compressor

输入为:

CSA 生成两套 KV 与两套 gate:

其中:

第 个 compressed entry 使用:

  • 当前 block 的 个 ;
  • 前一个 block 的 个 。

权重来自:

这里需要注意三件事。

权重是逐 channel 的

每个 都是一个 维向量。因此并非“一个 token 只有一个标量权重”,而是每个 channel 都有独立的 token 权重:

不同 KV channel 可以从 block 内不同 token 中提取信息。这比 mean pooling 或单标量 attention pooling 更有表达力。

每个输出覆盖 个 token,但输出 stride 是

一个原始 block 会通过:

  • 进入当前 compressed entry;
  • 进入下一个 compressed entry。

所以相邻 compressed entry 存在信息重叠,但最终仍只产生 个 entry。

Compressor 内有 block 位置 bias

用于告诉 pooling:当前值位于压缩窗口的哪个槽位。CSA2 后来把这部分删除了。

3.3 Lightning Indexer

CSA 不直接使用 512 维 Main KV 扫描全部压缩位置,而是建立较小的 Indexer K:

V4 中,Indexer K 有一条独立 compressor 路径。对于 query token :

Indexer 分数为:

其中每个 indexer head 的组合权重由当前 query hidden state 预测:

最后:

主 attention 只读取 对应的 Main KV。

3.4 Shared Key-Value MQA

一个 compressed latent 同时充当 Key 与 Value:

即:

64 个 Query head 共享同一份 512 维 KV latent。这样压缩了 KV cache,但 64 个 Query head 的输出拼接后宽度达到:

所以 V4 采用 Grouped Output Projection:先将 heads 分组、每组降维,再统一映射回模型 hidden dimension。

3.5 为什么还需要 SWA

Compressed entry 只有在整个 block 完成后才能安全地对 query 可见。

例如 时,token 0、1、2、3 形成一个 entry。对于 token 0、1、2,如果提前读取这个 entry,就会间接看到未来 token,违反因果性。

因此每层额外保留最近 个未压缩 SWA KV:

SWA 的作用包括:

  • 覆盖当前未完成的 compression block;
  • 保留最近 token 的高精度信息;
  • 补偿压缩导致的局部细节损失;
  • 维持严格 causal semantics。

4. DeepSeek-V4 的 HCA

HCA 是 Heavily Compressed Attention,不要和 mHC 混淆。

flowchart LR H["当前层 Hidden States"] --> COMP["非重叠 Compressor<br/>m′ tokens → 1 entry"] COMP --> CKV["N/m′ 个 compressed KV"] H --> SWA["最近 W 个 SWA KV"] X["Query Hidden"] --> Q["Main Q"] CKV --> CONCAT["逻辑拼接"] SWA --> CONCAT CONCAT --> ATT["Dense shared-KV MQA"] Q --> ATT ATT --> OUT["Grouped Output Projection"]

V4 中:

一百万 token 被压缩为大约:

个 HCA entry。

HCA 不使用 indexer,也不做 Top-K,而是让 query 稠密访问全部 HCA entry,计算复杂度为:

HCA compressor 是非重叠形式:

它同样是逐 channel learned pooling,但压缩率远大于 CSA。


5. 为什么 V4 要把 CSA 和 HCA 交替使用

机制 序列压缩率 全局访问方式 优点 缺点
CSA Indexer Top-K 粒度较细,可以定位精确信息 Indexer 仍要扫描长上下文,存在选择错误
HCA Dense over compressed entries 不依赖 Top-K,始终覆盖全部全局摘要 每个 entry 很粗糙
SWA 不压缩 最近 128 token 局部精确、因果安全 不能覆盖远距离历史

可以把 V4 理解成:

  • CSA:稀疏但较精细的远程记忆;
  • HCA:稠密但粗粒度的全局摘要;
  • SWA:精确的近期记忆。

V4-Flash 前两层是纯 SWA,之后 CSA/HCA 交替;V4-Pro 前两层使用 HCA,后续继续交替。


6. 从 CSA 到 CSA2:到底改了什么

CSA2 不是简单地把 compression ratio 换成另一个数字。它主要包含四项变化。

6.1 Main Compressor 从双流重叠变成单流非重叠

V4 CSA:

  • 两套 KV projection:;
  • 两套 gate:;
  • 每个输出覆盖 个 token;
  • 相邻输出共享一个原始 block;
  • 有 block 内 absolute positional bias。

CSA2:

  • 一套 KV projection;
  • 一套 gate;
  • 每个输出只覆盖自己的 -token block;
  • 相邻输出不重叠;
  • 删除 compressor 内的 absolute positional bias。

6.2 Indexer K 从 Main KV 投影

V4 CSA:

CSA2:

即:

这消除了独立的 Indexer-K compression path。

6.3 Main KV 与 Indexer K 跨层共享

V4 基本上是每个 CSA/HCA 层保存自己的 global cache。CSA2 把多个层划成一组,只由 Full 层生成 global cache,后面的层共享它。

6.4 Top-K 也可以跨层共享

CSA2 将两个选择解耦:

  • 要不要复用 KV;
  • 要不要重新运行 indexer。

由此形成 Full、Reindex、Reuse 三种 mode。


7. 重点:CSA2 的 Compressor 为什么叫“简化”

7.1 用具体 token 看 V4 CSA

假设 ,输入 token 可以写为:

V4 CSA 的输出关系大致是:

图示如下:

flowchart LR B0["Block 0<br/>x0 x1 x2 x3"] B1["Block 1<br/>x4 x5 x6 x7"] B2["Block 2<br/>x8 x9 x10 x11"] B0 -- "a projection" --> Y0["Compressed y0"] B0 -- "b projection" --> Y1["Compressed y1"] B1 -- "a projection" --> Y1 B1 -- "b projection" --> Y2["Compressed y2"] B2 -- "a projection" --> Y2

因此:

  • 每个正常输出聚合 个 projected source;
  • 每个原始 block 通过两套参数进入相邻两个输出;
  • Decode 时需要同时维护前一个 block 和当前未完成 block 的 state;
  • gate 还要加入 block 内位置 bias。

它的优势是压缩边界更平滑,但实现和状态管理比较复杂。

7.2 CSA2 变成普通的非重叠 learned pooling

同样假设 :

flowchart LR B0["x0 x1 x2 x3"] --> Y0["Compressed y0"] B1["x4 x5 x6 x7"] --> Y1["Compressed y1"] B2["x8 x9 x10 x11"] --> Y2["Compressed y2"]

其计算为:

仍需强调:这里不是普通平均池化。对于每个 channel ,都有独立的 个 softmax 权重。

7.3 “简化”具体减少了什么

项目 V4 CSA CSA2
Main KV projection 两套 一套
Gate projection 两套 一套
单个输出源范围
相邻输出重叠 有 无
Compressor 位置 bias 有 无
Indexer K 从 hidden 走独立 compressor 从 Main KV 投影
Decode tail state 前一块 + 当前块,状态复杂 只维护当前未完成块

7.4 删除位置 bias 不等于删除所有位置信息

CSA2 删除的是 Compressor 内部的 block-slot absolute bias,不是删除整个 attention 的 RoPE。

压缩完成后:

  • Main KV latent 仍会施加 partial RoPE;
  • Indexer K 也有自己的 partial RoPE;
  • 一个 compressed latent 在官方代码中使用该 group 第一个 token 的位置;
  • 最近 128 个 token 仍由未压缩 SWA 保留细粒度顺序。

因此删除的是复杂的“窗口内部位置参数”,不是让模型完全不知道位置。

7.5 ratio=1 时 Compressor 退化为什么

Decoder 使用 。此时一个 group 只有一个 token:

再计算 gate 没有意义,所以直接退化为:

官方代码也只在 compress_ratio > 1 时创建 wgate。


8. 重点:Full、Reindex、Reuse 到底复用了什么

8.1 三种 Mode 的组件级数据流

flowchart TB subgraph F["Full Mode"] FX["当前层 hidden"] --> FMQ["本层 Main Q"] FX --> FSW["本层 SWA KV"] FX --> FMKV["新 Main KV"] FMKV --> FIK["新 Indexer K"] FX --> FIQ["新 Indexer Q"] FIK --> FINDEX["Indexer"] FIQ --> FINDEX FINDEX --> FTOP["新 Top-K"] FMKV --> FCORE["Sparse Core Attention"] FTOP --> FCORE FMQ --> FCORE FSW --> FCORE end subgraph RI["Reindex Mode"] RIX["当前层 hidden"] --> RIMQ["本层 Main Q"] RIX --> RISW["本层 SWA KV"] RIX --> RIIQ["新 Indexer Q"] SHKV["复用 Main KV + Indexer K"] --> RIINDEX["重新打分"] RIIQ --> RIINDEX RIINDEX --> RITOP["新 Top-K"] SHKV --> RICORE["Sparse Core Attention"] RITOP --> RICORE RIMQ --> RICORE RISW --> RICORE end subgraph R["Reuse Mode"] RX["当前层 hidden"] --> RMQ["本层 Main Q"] RX --> RSW["本层 SWA KV"] SKV["复用 Main KV"] --> RCORE["Sparse Core Attention"] STOP["复用最近 Top-K"] --> RCORE RMQ --> RCORE RSW --> RCORE end
当前层的行为 Full Reindex Reuse
计算自己的 Main Q ✓ ✓ ✓
计算自己的 SWA KV ✓ ✓ ✓
生成新的 Main KV ✓ 复用 复用
生成新的 Indexer K ✓ 复用 复用
生成自己的 Indexer Q ✓ ✓ 不生成
运行 indexer ✓ ✓ 不运行
生成新的 Top-K ✓ ✓ 复用
执行 core attention ✓ ✓ ✓
生成新的 attention output ✓ ✓ ✓

8.2 把共享对象看成三条“状态总线”

flowchart LR L0["Full Layer"] L1["Reuse Layer"] L2["Reuse Layer"] L3["Reindex Layer"] L4["Reuse Layer"] KV["Main-KV Bus<br/>由最近 Full 层写入"] IK["Indexer-K Bus<br/>由最近 Full 层写入"] TK["Top-K Bus<br/>由最近 Full/Reindex 层写入"] L0 -- "publish" --> KV L0 -- "publish" --> IK L0 -- "publish Top-K₀" --> TK KV -- "read" --> L1 TK -- "read Top-K₀" --> L1 KV -- "read" --> L2 TK -- "read Top-K₀" --> L2 IK -- "read and rescore" --> L3 L3 -- "publish Top-K₁" --> TK KV -- "read" --> L3 KV -- "read" --> L4 TK -- "read Top-K₁" --> L4

这三类状态的来源不同:

  • Main KV / Indexer K:只能由 Full 层更新;
  • Top-K:Full 或 Reindex 层都可以更新;
  • SWA KV:从不跨层共享,每一层都有自己的 local state。

8.3 一个 Decoder group 的实际例子

以 layer 24–27 为例:

flowchart LR KV20["Layer 20 创建的<br/>Main KV + Indexer K"] C20["Layer 20 创建的<br/>Candidate Pool"] L24["Layer 24<br/>Reindex"] L25["Layer 25<br/>Reuse"] L26["Layer 26<br/>Reuse"] L27["Layer 27<br/>Reuse"] KV20 --> L24 C20 --> L24 L24 -- "产生 Top-K₂₄" --> L25 KV20 --> L25 L25 -- "沿用 Top-K₂₄" --> L26 KV20 --> L26 L26 -- "沿用 Top-K₂₄" --> L27 KV20 --> L27

四层使用同一份历史 Main KV,但它们不是执行相同的 attention:

  • layer 24 用自己的 hidden 生成 Query,并重新选 Top-K;
  • layer 25–27 各自生成不同的 Query;
  • 即使 Top-K 相同,attention logits 和 attention output 仍不同;
  • 每层的 SWA KV、输出投影、MoE 和 residual/mHC 也不同。

可以把 shared Main KV 想象成一座共享图书馆:

  • Full 决定图书馆里存什么书;
  • Reindex 使用自己的问题重新制作借阅清单;
  • Reuse 沿用上一份借阅清单;
  • 但每一层都会用自己的 Query 阅读这些书,并形成自己的新 hidden state。

8.4 为什么层仍然不能并行

第 层的 Query 来自第 层的输出:

所以 Main KV 和 Top-K 的复用只减少 cache 与索引计算,不会消除 hidden-state data dependency。Transformer layers 仍需顺序执行。


9. V4.1 的精确层布局

V4.1-Flash 有 40 层:

  • 20 层 causal encoder;
  • 20 层 decoder;
  • 前两层只有 SWA;
  • 其余 38 层使用 CSA2。

官方配置为:

1
2
3
kv_source_layers    = [2, 8, 14, 20]
index_source_layers = [2, 8, 14, 20, 24, 28, 32, 36]
candidate_source = 20

完整映射:

层 区域 ratio Mode 作用
0–1 Encoder — SWA only 只有局部注意力
2 Encoder 2 Full 创建第 1 份 Encoder global KV
3–7 Encoder 2 Reuse 复用 layer 2 的 KV 和 Top-K
8 Encoder 2 Full 创建第 2 份 Encoder global KV
9–13 Encoder 2 Reuse 复用 layer 8
14 Encoder 2 Full 创建第 3 份 Encoder global KV
15–19 Encoder 2 Reuse 复用 layer 14
20 Decoder 1 Full 创建唯一 Decoder global KV 与 candidate pool
21–23 Decoder 1 Reuse 复用 layer 20
24 Decoder 1 Reindex 在候选池内重新选 Top-K
25–27 Decoder 1 Reuse 复用 layer 24 的 Top-K
28 Decoder 1 Reindex 重新选 Top-K
29–31 Decoder 1 Reuse 复用 layer 28
32 Decoder 1 Reindex 重新选 Top-K
33–35 Decoder 1 Reuse 复用 layer 32
36 Decoder 1 Reindex 重新选 Top-K
37–39 Decoder 1 Reuse 复用 layer 36
flowchart TB IN["Input embeddings"] --> S01["Layers 0–1<br/>SWA only"] S01 --> E2["Layer 2 Full, m=2"] E2 --> E37["Layers 3–7 Reuse"] E37 --> E8["Layer 8 Full, m=2"] E8 --> E913["Layers 9–13 Reuse"] E913 --> E14["Layer 14 Full, m=2"] E14 --> E1519["Layers 15–19 Reuse"] E1519 --> H20["Encoder final hidden H₂₀"] H20 --> D20["Layer 20 Full, m=1<br/>创建 Decoder global KV<br/>创建 Candidate Pool"] D20 --> D2123["Layers 21–23 Reuse"] D2123 --> D24["Layer 24 Reindex"] D24 --> D2527["Layers 25–27 Reuse"] D2527 --> D28["Layer 28 Reindex"] D28 --> D2931["Layers 29–31 Reuse"] D2931 --> D32["Layer 32 Reindex"] D32 --> D3335["Layers 33–35 Reuse"] D3335 --> D36["Layer 36 Reindex"] D36 --> D3739["Layers 37–39 Reuse"] D3739 --> OUT["LM Head / DSpark"]

总计:

  • 4 个 KV source;
  • 8 个 index source;
  • 30 个 Reuse CSA2 层;
  • 2 个 SWA-only 层。

10. 重点:Decoder 中 ratio=1,没有压缩 token,收益在哪里

这是理解 CSA2 最关键的问题之一。

10.1 先给直接答案

Decoder 的 sequence dimension 确实没有压缩:

但它在另外四个地方获得了主要收益:

  1. Layer dimension:20 个 Decoder 层只保存 1 份 global KV;
  2. Entry dimension:每个位置只有一个 512 维 shared-KV latent,并使用 FP4;
  3. Attention compute:每层主 attention 只读取 Top-512 + SWA-128;
  4. Indexer compute:20 层中只有 5 层产生 Top-K,后 4 个只在 16K candidate 中搜索;
  5. CED Prefill:绝大多数 prompt token 不经过 Decoder 主体。

10.2 从三维 KV 张量看收益

把普通 Decoder global KV 抽象成:

CSA2 Decoder 的变化可以写成:

普通逐层

虽然 position 维仍是 ,但 layer 维从 20 变成 1。

仅从结构上看,若每层都采用同样的 356-byte global entry,则逐层保存需要:

CSA2 Decoder 只需要:

也就是 Decoder global cache 的 layer 维理论上减少约 20 倍。

这不是说整个模型 KV 一定恰好减少 20 倍,因为还存在:

  • Encoder 的三份 global cache;
  • 每层 SWA state;
  • 不同基线的量化格式;
  • scale 与 metadata。

但它说明了 ratio=1 仍然能有巨大收益的原因。

10.3 Main KV 是共享的,但 Query 不是共享的

所有 Decoder 层可以读取同一份 global memory:

但每层仍生成自己的 Query:

所以同一份 Main KV 可以被不同抽象层次的 Query 以不同方式解释。共享的是 memory bank,不是 attention computation。

10.4 主 Attention 不会读取全部 个位置

ratio=1 只是表示 global memory 每个 token 一个 entry,并不表示 core attention 是 dense attention。

每层真正读取的数量大约为:

因此昂贵的 64-head、512-dim core attention 成本近似与 无关。

随 增长的主要部分转移到了轻量低维 indexer,而不是主 attention。

10.5 20 层并不都运行 Indexer

Decoder 中:

  • 1 个 Full 层运行全局 indexer;
  • 4 个 Reindex 层更新 Top-K;
  • 15 个 Reuse 层完全不运行 indexer。

即使不考虑 hierarchical candidate,索引次数也从“每层一次”降到:

再加入 candidate pool 后,大致变成:

而不是:

10.6 CED 让历史 token 不必穿过整个 Decoder

Decoder global KV 从 Encoder final hidden 生成,而不是从每个 Decoder 层自己的历史 hidden 生成。因此对绝大多数 prompt token:

  • 计算 Encoder final hidden;
  • 建立共享 Decoder global KV;
  • 不需要让这些 token 再经过 20 层 Decoder。

只有最后 128 个 token 需要经过 Decoder,以重建每层局部 SWA state 并得到最后位置 logits。

10.7 为什么不进一步把 Decoder 也设成

报告没有把它明确写成唯一因果解释,但从结构上可以得到一个合理判断:

  • Decoder 已通过 20 层共享一份 cache,大幅压缩 layer dimension;
  • Decoder 的 global memory 直接服务最终生成,更需要精细的逐 token 可寻址性;
  • Hierarchical Indexer 已将 core attention 控制在 Top-512;
  • FP4 又显著降低了每个 entry 的存储成本。

因此选择 是一种“用 layer compression 换取 token-level retrieval precision”的设计:保留逐 token 精度,同时依靠跨层共享、稀疏访问和 FP4 控制成本。


11. Hierarchical Sparse Indexer

如果每个 Reindex 层仍扫描百万上下文,那么每次 indexer 仍是 。V4.1 因此只在 Decoder 中使用两级检索。

flowchart LR ALL["全部 Main KV positions<br/>N 个,Decoder ratio=1"] Q20["Layer 20 Indexer Q"] --> FULL["Full index scoring<br/>扫描全部 N"] ALL --> FULL FULL --> TOP512A["Layer 20 自己的 Top-512"] FULL --> BLOCK["每 8 个位置组成一个 block<br/>block score = max position score"] BLOCK --> BSEL["选择最多 2048 个 block"] BSEL --> POOL["Candidate Pool<br/>≤ 2048 × 8 = 16384 positions"] POOL --> R24["Layer 24 Reindex"] POOL --> R28["Layer 28 Reindex"] POOL --> R32["Layer 32 Reindex"] POOL --> R36["Layer 36 Reindex"] R24 --> T24["自己的 Top-512"] R28 --> T28["自己的 Top-512"] R32 --> T32["自己的 Top-512"] R36 --> T36["自己的 Top-512"]

第一层 Full indexer 的 block score 为:

选择 2048 个 block,每块 8 个位置,得到最大 16,384 个 candidate。之后每个 Reindex 层使用自己的 Query 和 head weights,在候选池中独立选择 Top-512。

11.1 为什么 Candidate Pool 比 Top-K 大很多

如果 layer 20 只把自己的 Top-512 传给所有深层,那么浅层遗漏的位置永远无法恢复。

16K candidate pool 是一个中间召回集合:

  • Full 层自己的 attention 仍只读取 Top-512;
  • 后续 Reindex 可以在较大的 16K 集合里改变选择;
  • 这样在减少搜索范围的同时保留跨层多样性。

11.2 它仍然不是严格 Decode

仍然需要:

  • Decoder layer 20 扫描全部 ;
  • Encoder layer 2、8、14 各扫描大约 ;
  • 只是这些扫描在较小的 128 维 FP4 Indexer K 上进行;
  • 真正昂贵的 512 维、64-head core attention 始终只处理固定 Top-K。

所以报告所说的 Decode FLOPs “nearly constant”不是数学上完全没有线性项,而是线性项已经缩小到整体计算中的较小部分。


12. CSA2 单层的完整张量流

V4.1 的关键维度:

  • hidden dimension:5120;
  • Main Query heads:64;
  • Main head dimension:512;
  • Query latent rank:1280;
  • Indexer heads:32;
  • Indexer head dimension:128;
  • Top-K:512;
  • SWA window:128。

对 batch size 、本轮 query 数 ,张量形状为:

flowchart TB X["x: B × S × 5120"] X --> QRA["Wq_a + RMSNorm"] QRA --> QR["Query latent<br/>B × S × 1280"] QR --> MQ["Main-Q projection<br/>B × S × 64 × 512"] QR --> IQ["Indexer-Q projection<br/>B × S × 32 × 128<br/>仅 Full/Reindex"] X --> SWAKV["本层 SWA-KV projection<br/>B × S × 512"] X --> COMP["Compressor<br/>仅 KV source"] COMP --> MKV["Main KV<br/>B × N/m × 512"] COMP --> IKP["Indexer-K projection"] IKP --> IK["Indexer K<br/>B × N/m × 128"] IQ --> SCORE["Indexer score"] IK --> SCORE SCORE --> IDX["Top-512 indices"] MKV --> SPARSE["Indexed sparse attention"] IDX --> SPARSE SWAKV --> SPARSE MQ --> SPARSE SPARSE --> IROPE["Inverse partial RoPE"] IROPE --> GOP["Grouped low-rank O projection"] GOP --> Y["Attention output<br/>B × S × 5120"]

12.1 Partial RoPE 与 inverse RoPE

Main latent 同时充当 K 和 V。如果对其最后 64 维施加 RoPE,那么它作为 Value 被加权求和后,输出也携带绝对位置旋转。

因此对 attention output 再应用 query 位置的逆旋转:

来自位置 的贡献近似变成:

从而恢复相对位置信息。


13. CED:为什么 Prefill 能跳过大部分 Decoder

CED 中的 Encoder 仍然是 causal encoder,不是 BERT 式双向 Encoder。

下半 20 层对 prompt 的所有位置生成:

一般形式下,Decoder global KV 直接从 投影:

而不是从每个 Decoder 层自己的历史 hidden state 产生:

结合 V4.1 的 CSA2 schedule,实际只有 Decoder Full layer 20 创建 global cache,其余 19 层共享。

13.1 Prefill 数据流

flowchart LR P["完整 Prompt<br/>N tokens"] --> ENC["20-layer Causal Encoder<br/>处理全部 N"] ENC --> EH["Encoder final hidden<br/>H_E[1:N]"] EH --> DGLOBAL["生成一份 Decoder Main KV<br/>ratio=1, FP4<br/>供 20 个 Decoder 层共享"] EH --> TAIL["只取最后 W=128 个<br/>Encoder outputs"] TAIL --> DREPLAY["20-layer Decoder<br/>SWA Bounded Replay"] DGLOBAL --> DREPLAY DREPLAY --> DSTATE["每层 Decoder SWA ring state"] DREPLAY --> LOGIT["Prompt 最后位置 logits"]

普通 Decoder-only Transformer 的 Prefill 主体成本近似:

CED 的成本变为:

当 :

13.2 为什么早期 Prompt token 可以跳过 Decoder

未来 Decode 需要的 Decoder global KV 已经由 Encoder final hidden 产生。早期 prompt token 在 Decoder layer 21–39 的中间输出不会被保存为长期 global KV,也不会成为未来 token 的全局 Key/Value。

所以它们没有必要全部通过 Decoder。

但最近 token 的 Decoder hidden 仍然重要,因为每个 Decoder 层都有自己的 SWA KV。因此最后 128 个 token 必须顺序通过 Decoder,用来:

  • 建立每层 Decoder SWA ring;
  • 产生 prompt 最后位置的最终输出和 logits。

这意味着:

  • Prefill 可以跳过绝大多数历史位置的 Decoder 计算;
  • Decoder layers 之间仍然串行;
  • 每个新 Decode token 仍需通过全部 40 层。

14. Decode 时的逐层数据流

flowchart LR T["新 token t"] --> E["顺序执行 20 层 Encoder"] E --> HE["新的 Encoder final hidden"] HE --> DKV["追加 1 个 Decoder Main-KV entry<br/>Layer 20, ratio=1"] HE --> D["顺序执行 20 层 Decoder"] DKV --> IDX0["Layer 20 Full index<br/>全局扫描 + candidate pool"] IDX0 --> DG["Decoder layer groups"] DG --> REIDX["Layers 24/28/32/36<br/>在 candidate pool 中 Reindex"] REIDX --> REUSE["其余层复用最近 Top-K"] REUSE --> NEXT["Next-token logits"]

具体过程:

  1. 新 token 经过全部 Causal Encoder layers;
  2. Encoder layer 2、8、14 是 ratio=2 的 Full source:
    • 每两个 Decode step 产生一个 compressed entry;
    • 尚未凑满两个 token 时保存在 compressor tail state;
  3. Encoder final hidden 到达 Decoder;
  4. layer 20 生成一个新的 ratio=1 Decoder Main KV 与 Indexer K;
  5. layer 20 扫描全部 Decoder Indexer K,产生 Top-512 与 candidate pool;
  6. layer 24/28/32/36 在 candidate pool 中重新选择;
  7. 每个 CSA2 层计算自己的 Main Q、SWA KV 和 attention output;
  8. token 顺序通过 40 层,因此 Decode 激活参数约为 16B/token。

15. 对应官方代码的简化实现

以下代码是为了突出控制流,不是可以直接加载官方 checkpoint 的完整实现。

15.1 CSA2 Compressor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def compress(x, ratio, w_kv, w_gate=None):
# x: [B, S, D]
kv = w_kv(x) # [B, S, 512]

if ratio == 1:
# 一个 group 只有一个 token,softmax 权重必然是 1
return rms_norm(kv)

gate = w_gate(x) # [B, S, 512]

kv = kv.view(B, S // ratio, ratio, 512)
gate = gate.view(B, S // ratio, ratio, 512)

# 对 token/group 内槽位维度做 softmax;每个 channel 独立
weight = softmax(gate, dim=2)
latent = (weight * kv).sum(dim=2) # [B, S/ratio, 512]

return rms_norm(latent)

Decode 时 需要维护:

第二个 token 到达时才生成完整 latent,并追加到 global paged cache。

15.2 Indexer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def run_indexer(query_x, query_latent, index_k, candidates=None):
# [B,S,1280] -> [B,S,32,128]
index_q = index_q_proj(query_latent)

# 当前 query 对 32 个 index heads 的组合权重
head_weight = index_weight_proj(query_x) # [B,S,32]

if candidates is not None:
# 生产语义:先 gather,再计算,才能真正减少 FLOPs
index_k = gather(index_k, candidates)

score_per_head = einsum(
"bshd,btd->bsht",
index_q,
index_k,
)

score = (
relu(score_per_head)
* head_weight.unsqueeze(-1)
).sum(dim=2)

return topk(score, k=512)

15.3 三种 Mode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def csa2_layer(x, mode, shared):
# 每层必算
main_q = make_main_q(x)
local_kv, local_indices = update_swa_ring(x)

if mode == "full":
latent = compressor(x)

# 发布一份新的共享 global memory
shared.main_kv = append(
shared.main_kv,
quantize_fp4(latent),
)
shared.index_k = append(
shared.index_k,
quantize_fp4(index_k_proj(latent)),
)

# 发布新的 Top-K
shared.topk = run_indexer(
x,
query_latent(x),
shared.index_k,
)

elif mode == "reindex":
# 不建立 Main KV/Indexer K,只更新访问位置
shared.topk = run_indexer(
x,
query_latent(x),
shared.index_k,
candidates=shared.candidates,
)

elif mode == "reuse":
# 沿用 Main KV、Indexer K 和最近 Top-K
pass

indices = concat(local_indices, shared.topk)

# Sparse kernel 按 index gather,不必物化一份新的 Selected-KV cache
output = sparse_attention(
q=main_q,
kv=concat(local_kv, shared.main_kv),
indices=indices,
)

return grouped_output_projection(inverse_rope(output))

15.4 官方代码中的映射

官方 model.py 使用:

1
2
self.is_kv_source = layer_id in kv_source_layers
self.is_index_source = layer_id in index_source_layers

因此:

  • 同时是 KV source 与 index source:Full;
  • 不是 KV source、但是 index source:Reindex;
  • 两者都不是:Reuse。

共享状态集中在:

1
2
3
4
5
class SharedAttentionRuntime:
compress_kv
index_k
topk_idxs
candidates

物理 global cache 只在 KV source layer 分配;但每一层都拥有自己的:

1
window_kv_cache  # [batch, 128, 512]

因此代码层面可以概括为:

Global cache 是 group-owned,SWA cache 是 layer-owned。


16. 为什么正好是 890 bytes/token

报告称 V4.1 的 global KV footprint 为:

这个数字可以从模型配置和 FP4 scale 格式推导出来。

16.1 一份 Main KV entry

Main KV dimension 为 512,FP4 payload:

Main KV 每 16 个值有一个 1-byte E4M3 scale:

因此:

16.2 一份 Indexer K

Indexer K dimension 为 128:

每 32 个值有一个 1-byte E8M0 scale:

因此:

一份 Main KV + Indexer K:

16.3 三个 Encoder source,ratio=2

Encoder KV source 为 layer 2、8、14:

16.4 一个 Decoder source,ratio=1

合计:

这里的 890 bytes 是随上下文长度线性增长的 global cache。每层 128-entry SWA ring 是固定大小 ,不随 继续增长。


17. KV Cache Manager 与持久化缓存

17.1 V4 的异构 KV cache

V4 同时存在:

  • CSA Main KV;
  • CSA Indexer K;
  • HCA KV;
  • 每层 SWA KV;
  • CSA/HCA 尚未凑满 block 的 tail state。

由于 ,global paged block 需要兼容:

个原始 token 的对齐关系。

V4 将缓存分为两类。

Classical/Paged KV cache

存放已经完成压缩的:

  • CSA Main KV;
  • CSA Indexer K;
  • HCA KV。

State cache

为每个 active sequence 分配固定 state block,保存:

  • 每层最近 128 个 SWA KV;
  • 尚未完成压缩的 KV/gate tail state。

SWA 和 compressor tail 更像“当前位置决定的 sequence state”,不适合直接套用普通 append-only PagedAttention 管理方式。

17.2 V4.1 按复用时间尺度分层

V4.1 将状态进一步划分为:

长期 Global KV

  • 可以被大量后续 turn 复用;
  • active 部分驻留 HBM;
  • persistent copy 放在 SSD/host memory;
  • 报告中的典型长期保留时间至少约 72 小时。

短期 SWA state

  • 主要在分钟级活跃 session 内有价值;
  • 不再长期写入 persistent SSD cache;
  • 使用每台机器约 10% host DRAM 组成短 TTL 分布式池。

可重建 state

当 SWA state 淘汰后,通过 Bounded Replay 恢复,而不是一直存储。

其原则是:

不同状态拥有不同的生命周期、存储介质、淘汰策略和恢复成本。


18. SWA Bounded Replay

18.1 为什么精确恢复需要

虽然一层 SWA 只读取最近 个位置,但深层 token hidden 已经依赖浅层更早的局部历史。要精确重建所有层最后 个 SWA KV,需要逐层扩大 replay 范围,代价近似为:

个 token-layer。

18.2 Encoder SWA Bounded Replay

当 global prefix cache 命中但 Encoder SWA 丢失时:

  1. 读取已经缓存的 global KV;
  2. 取命中 prefix 的最后 个 token;
  3. 将它们与 uncached suffix 一起重放;
  4. replayed prefix 只重建 SWA,不覆盖原有 global KV;
  5. uncached suffix 同时建立新的 global KV 和 SWA。

由于 replay 起点截断了更早的 SWA 依赖,所以恢复结果是近似的,并且可能依赖 cache-hit position。

18.3 Decoder SWA Bounded Replay

每次 Prefill:

  1. 完整 prompt 通过 Causal Encoder;
  2. 从 Encoder outputs 建立 Decoder global KV;
  3. 只取最后 128 个 Encoder outputs;
  4. 让它们通过 Decoder;
  5. 生成所有 Decoder layers 接下来 Decode 所需的 SWA ring;
  6. 这些 Decoder SWA state 不进入长期 prefix cache。

这是 CED 能把 Prefill 主体从 降到约 的系统前提。


19. 分布式训练与推理 Kernel

19.1 V4 的 Context Parallel Compressor 边界

Context Parallelism 按 sequence 维切分,每个 rank 保存连续 token。Compression block 可能跨 rank 边界,而且 packed samples 的尾部长度可能不足 。

V4 使用两阶段处理:

  1. rank 把末尾最多 个未压缩状态发送给 rank ;
  2. 下游 rank 将边界状态与本地 token 一起压缩;
  3. 各 rank 产生固定上限长度、允许 padding 的 compressed entries;
  4. All-Gather 收集所有 compressed entries;
  5. fused select-and-pad 将有效 entry 重排,并把 padding 放到尾部。

V4.1 Encoder ratio=2 时仍有类似的 compression boundary 问题,只是窗口更小。

19.2 CSA2 跨 Pipeline Stage 共享

Full source 与 Reuse/Reindex consumer 可能被放到不同 Pipeline Parallel stage,因此训练系统需要:

Shadow Indexers

  • 在参与执行的 stage 放置轻量可执行副本;
  • 只有一个 logical owner 负责优化器和 checkpoint;
  • 同步参数并聚合梯度。

Pipeline Payload Extensions

跨 stage 传输:

  • 共享表示;
  • sparse routing information;
  • Top-K/candidate 等状态。

这些状态需要和 Context Parallelism 使用一致的分片方式。

Microbatch-Level Shared-State Manager

状态必须按照 microbatch 独立追踪,并跨越:

  • forward;
  • activation recomputation;
  • backward。

直到最后一个 consumer 完成才能释放,避免并发 microbatch 相互覆盖。

19.3 Sparse Attention Kernel

官方参考 TileLang kernel 展示了:

  • 按 int32 indices 直接 gather KV;
  • 每轮处理 64 个 sparse positions;
  • 256 threads;
  • FlashAttention 风格 online softmax;
  • 维护 running maximum 与 running sum;
  • 融合 learnable attention sink;
  • 不要求预先物化一份 Selected-KV cache。

生产报告还列出:

  • FlashMLA:融合 RoPE–Attention–inverse RoPE–cast;
  • DeepGEMM:Mega-Gate、Mega-mHC、Mega-MoE;
  • TileKernels;
  • DeepSelect TopK。

Reuse layer 因为没有 compressor 和 indexer,报告称只需:

  • Prefill:15 个 kernels;
  • Decode:11 个 kernels。

19.4 EPD Disaggregation

系统采用 Encoder–Prefill–Decode disaggregation:

  • Encoder deployment stage 主要处理多模态 vision encoding;
  • Prefill stage 面向长序列和高吞吐;
  • Decode stage 面向单 token 与低延迟。

三类资源可以独立扩缩容并重叠执行。这里部署语境中的 Encoder 不应与模型内部 20-layer Causal Encoder 完全混为一谈。


20. 公开参考代码与生产系统的区别

官方 README 明确说明公开实现是 readable reference implementation,而不是 production serving engine。

20.1 Hierarchical Indexer 的参考实现没有真正减少 GEMM 范围

论文中的生产语义是:Reindex 只计算 candidate positions。

公开 Python 代码为了可读性,先执行:

1
index_score = einsum(q, full_index_k)

再做:

1
index_score.masked_fill_(~candidates, -inf)

这样数值语义正确,但并没有在参考实现中省掉完整 score GEMM。生产系统必须先 compact/gather candidates,再运行 indexer,才能得到固定候选成本。

20.2 FP4 主要是数值模拟,不是完整 Packed Cache Manager

参考代码使用 in-place quantize/dequantize 模拟 FP4 QAT 数值,而 sparse-attention 接口仍然接收 BF16 tensor。它没有完整展示生产系统中的:

  • packed FP4 payload;
  • scale layout;
  • paged allocation;
  • HBM/DRAM/SSD transfer;
  • prefix cache metadata。

20.3 Top-K 与共享运行时

参考实现使用 torch.topk;生产系统使用更高性能的 DeepSelect TopK kernel。

参考实现还使用进程级 shared_attn singleton,适合一次顺序 forward。Continuous batching 中必须按以下粒度管理:

  • request/sequence;
  • microbatch;
  • Pipeline stage;
  • speculative branch;
  • cache hit/replay 状态。

否则一个 batch 的 Main KV、Top-K 或 candidate pool 可能覆盖另一个 batch 的状态。


21. 复杂度与收益总结

21.1 各机制对比

机制 长期 Global KV Core Attention Indexer
Dense MLA entries 无
DSA 通常仍约 entries 多层扫描
V4 CSA 每层约 CSA 层扫描
V4 HCA 每层约 无
V4.1 CSA2 只在 4 个 KV source 保存 只在 8 个 source 执行;Decoder Reindex 使用 candidate pool

21.2 一百万 token 的直观规模

当 :

Encoder 三个 source、ratio=2:

个 Main KV entry。

Decoder 一个 source、ratio=1:

个 Main KV entry。

合计为 份 source entries,而不是 38 个 CSA2 层各保存一份、形成 。

同时每层真正昂贵的 core attention 只读取约 640 个位置。

21.3 V4 与 V4.1 汇总

维度 V4 CSA/HCA V4.1 CSA2
KV entry shared-KV latent shared-KV latent + FP4
Sequence compression CSA 4:1、HCA 128:1 Encoder 2:1、Decoder 1:1
Layer compression 基本逐层独立 38 个 CSA2 层只有 4 个 KV source
Index reuse 无系统性跨层复用 Full/Reindex/Reuse
Reindex 搜索 全部 compressed positions Decoder 后续层最多 16K candidates
HCA 有 取消,纯 CSA2
SWA 每层 128 每层 128
Persistent SWA 存储、checkpoint 或精确重算 不长期存,Bounded Replay
Prefill Prompt 经过完整模型 全 Prompt 走 Encoder,Decoder 只 replay 尾部
Global KV precision RoPE BF16、其余 FP8 FP4
V4.1 runtime global KV — 890 bytes/token,约为 V4-Flash 的 1/4
V4.1 persistent KV — 约为 V4-Flash 的 1/8

22. 常见误解与局限

22.1 常见误解

  1. CSA compression 不是量化。 它是多个 token 到一个 latent 的 learned pooling;FP4/FP8 才是数值量化。

  2. CSA2 不是单纯的 token pooling。 最主要的新增收益是 layer-level KV 与 index reuse。

  3. Reuse layer 不是跳层。 它仍执行自己的 Query、SWA、core attention、输出投影、MoE 和 residual/mHC。

  4. Decoder ratio=1 不代表没有压缩。 它仍有 entry compression、20-to-1 layer sharing、FP4 和 sparse Top-K。

  5. 共享 KV 不代表 layers 可以并行。 下一层 Query 仍依赖上一层 hidden output。

  6. Candidate pool 不是新的 KV cache。 它是一组位置或 block routing state,Main KV 仍是原来的共享 cache。

  7. Hierarchical Indexer 不是完全消除 。 第一层 Full indexer 仍扫描完整上下文。

  8. Prefill 跳过 Decoder 不等于 Decode 也跳过 Decoder。 每个新生成 token 仍然经过完整 40 层。

  9. 删除 compressor absolute bias 不等于删除 RoPE。 Main KV、Indexer K、Query 和 output 仍有位置处理。

  10. 公开 Python 代码不是生产 Serving Engine。 它主要用于表达 checkpoint 结构、张量关系和数值语义。

22.2 主要局限

压缩损失

Encoder ratio=2 仍然是有损 pooling。虽然 SWA 保存局部精确信息,但远距离信息只能通过压缩 latent 被访问。

Index selection error

Top-K 可能漏掉真正相关的位置。Candidate pool 也可能在第一层 Full indexer 阶段漏掉某个区域,后续 Reindex 无法跳出候选集恢复它。

Reused Top-K 可能变旧

Reuse layers 的 hidden abstraction 在变化,但访问位置不变。V4.1 每四个 Decoder 层插入一次 Reindex,是精度与成本之间的折中。

Bounded Replay 是近似状态恢复

恢复结果可能依赖 prefix cache 命中位置,在极端输入或 cache-resumption boundary 上存在潜在鲁棒性风险。技术报告也明确把它列为未来需要继续压力测试的边界。

系统实现复杂

Layer sharing 会显著增加:

  • Pipeline Parallel 调度复杂度;
  • microbatch shared-state 生命周期管理;
  • continuous batching 的状态隔离要求;
  • prefix cache、replay 与 speculative decoding 的组合状态空间。

23. 参考资料

技术报告

官方参考实现

  • 标题: DeepSeek-V4_CSA-HCA与V4.1-CSA2详解
  • 作者: 鱿鱼圈
  • 创建于 : 2026-09-12 23:52:00
  • 更新于 : 2026-10-05 13:10:45
  • 链接: https://yuyanqi.com/2026/09/12/DeepSeek-V4_CSA-HCA与V4.1-CSA2详解/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
DeepSeek-V4_CSA-HCA与V4.1-CSA2详解