DeepSeek V4 KV-Cache 深度解析
概述
DeepSeek V4 之所以能实现极高的 KV-Cache 命中率,核心在于它改变了传统 KV-Cache 的存储策略:将计算最昂贵的长共享前缀压缩后持久化到 SSD,新请求到来时直接复用而非重新计算。
原始图片:
来源:小红书(基于 DeepSeek-V4 技术报告整理)
一、为什么 KV-Cache 是个问题?
Transformer 的推理过程
每次 LLM 推理分为两个阶段:
- Prefill(预填充):将输入 prompt 的所有 token 一次性送入模型,计算每个 token 的 K(Key)和 V(Value)状态
- Decode(解码):基于已有的 KV 状态,逐个生成新 token
问题在于:如果输入 prompt 很长(比如 64K tokens),prefill 阶段需要:
- 计算 64K × 64K 的注意力矩阵(O(n²) 复杂度)
- 为每一层、每个 head 存储完整的 K 和 V 矩阵
- 对于 64K 长度的 prompt,全量 KV-Cache 可能占用几十到几百 GB 内存
传统方案的致命弱点
每个请求独立存储全量 KV → 内存很快耗尽 → 被迫淘汰旧缓存 → 命中率极低
传统 KV-Cache 存在内存中,无法持久化,服务器一重启就全没了。而且 GPU/显存容量有限,只能存很少量的缓存。
二、DeepSeek V4 的四步流程详解
第一步:第一个请求——预填充生成长共享前缀
输入序列的组成
[System Prompt] [Tool Schema] [Repo Context/Docs] [Conversation History] [New User Tokens]
←────────────────────── 长共享前缀 ────────────────────────→ ← 独特后缀 →
为什么要这样划分?
在 Agent 场景中,每次对话的输入有大量重复部分:
- System Prompt:几乎不变的角色设定和指令
- Tool Schema:工具的 API 描述,多个对话共享
- Repo Context/Docs:代码仓库或文档上下文,同项目下共享
- Conversation History:同一对话的历史记录
- New User Tokens:只有用户新输入的这句是不同的
“长共享前缀”(Long Shared Prefix) = 前面 4 个部分,它们在不同请求之间高度重复。 “独特后缀”(Unique Suffix) = 最后用户新输入的部分,每次不同。
Prefill 阶段的计算
Model Prefill: "Compute attention over the long prefix and generate KV states"
Prefill 就是把整个输入序列送入模型,计算每一层的注意力(Attention),并为每个 token 生成对应的 K 和 V 状态。
为什么标注”这是最昂贵的部分”(The expensive part)?
- 64K tokens 的 prefill 计算量远大于生成几个新 token 的 decode 阶段
- 注意力计算是 O(n²),序列越长越贵
- 如果有 100 个并发请求都有相同的 64K 前缀,传统方案就要重复计算 100 次
产物:三种压缩缓存
Prefill 完成后,DeepSeek V4 不是保存完整的 KV 状态,而是生成三种压缩格式的缓存产物:
| 产物 | 全称 | 压缩比 | 用途 |
|---|---|---|---|
| CSA | Compressed Sparse Attention | 4x | 中等压缩,保留较多细节 |
| HCA | Heavily Compressed Attention | 128x | 极高压缩,存最老的历史 |
| SWA | Sliding Window Attention | 1x(不压缩) | 精确状态,存最近窗口 |
第二步:压缩后的 KV 存储到 SSD
三种压缩技术详解
1. CSA(Compressed Sparse Attention)—— 4 倍压缩
4 tokens → 1 compressed token state
- 原理:将每 4 个连续 token 的 KV 状态压缩成 1 个
- 稀疏选择(Sparse Selection):不是简单平均,而是有选择地保留最重要的信息
- 类比:就像把 4 帧视频压缩成 1 帧关键帧
- 适用场景:中间时间跨度的上下文,需要一定精度但可以接受少量信息损失
2. HCA(Heavily Compressed Attention)—— 128 倍压缩
128 tokens → 1 compressed token state
- 原理:将每 128 个 token 压缩成 1 个,压缩率极高
- 为什么可以压缩这么多? 最老的上下文(比如 system prompt、几轮前的对话)对当前生成的影响较小,即使丢失部分细节也不太影响结果
- 类比:就像把整章书的内容压缩成一段摘要
- 适用场景:非常久远的历史信息,主要作用是保持整体语境
3. SWA(Sliding Window Attention)—— 不压缩的精确状态
Exact states for the most recent tokens
- 原理:保留最近若干个 token 的完整、未压缩的 KV 状态
- 为什么不能压缩? 最近的 token 对下一个生成的 token 影响最大,压缩会显著降低生成质量
- 窗口大小:技术报告中提到的滑动窗口,通常覆盖最近几千个 token
- 类比:短期记忆,需要精确回忆
为什么存到 SSD 而不是内存?
On-disk cache (SSD): 高容量、持久化、可跨请求复用
| 对比维度 | 内存(GPU/DRAM) | SSD |
|---|---|---|
| 容量 | 有限(几十 GB) | 很大(TB 级) |
| 成本 | 贵 | 便宜 |
| 持久化 | 断电丢失 | 持久保存 |
| 速度 | 极快 | 较快(NVMe SSD 可达 7GB/s) |
关键洞察:prefill 计算的时间远大于从 SSD 读取的时间。所以即使 SSD 比内存慢,从 SSD 加载压缩 KV 也比重新计算快得多。
压缩后的存储大小
对于 64K tokens 的长前缀:
- 全量 KV:几十到几百 GB
- 压缩后(CSA + HCA + SWA):远小于 1 GB(<< 1 GB)
缩小了 100~1000 倍,这就是为什么能在 SSD 上存储数千个前缀。
第三步:第二个请求到来——前缀匹配检测
匹配检测机制
"Prefix match detected → KV cache hit"
当新请求到来时:
- 提取输入序列的前缀(System Prompt + Tool Schema + Context + History)
- 计算前缀的哈希或指纹
- 在 SSD 缓存中查找匹配的指纹
- 如果命中,加载对应的压缩 KV 状态
匹配覆盖率:92% 的含义
图中示例展示了92% 的匹配覆盖率,分为三部分:
[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] 100%
←──────── 绿色: Matched prefix ────────→ ← 蓝色 → ←橙色→
(可从缓存复用的部分) (最后完整 (必须重算
压缩块边界) 的不匹配尾部)
- 绿色部分(Matched prefix):92% 的前缀完全匹配,可以直接从缓存加载
- 蓝色部分(Last complete compression block):对齐到压缩块的边界。因为 HCA 是 128 tokens 一组、CSA 是 4 tokens 一组,所以最后一个完整压缩块之后的部分需要特殊处理
- 橙色部分(Unmatched tail):不匹配的尾部,必须重新计算。可能是因为新请求比缓存的前缀稍微长了一点
为什么不是 100%?
- 对话历史可能多了一轮
- 压缩块的对齐问题(128 的整数倍、4 的整数倍)
- 但即使只有 92% 命中,也比 0%(全重算)好得多
第四步:复用而非完全重计算
四步复用流程
On-disk cache (SSD) ──→ V4 Serving System ──→ Attention Engine
[CSA] [HCA] [SWA]
Step 1:加载压缩的 CSA/HCA 前缀缓存
"Read the compressed prefix KV from disk"
- 从 SSD 读取 CSA 和 HCA 的压缩 KV 状态
- 因为这些状态已经被大幅压缩,读取时间很短
- 加载后送入 Attention Engine
Step 2:恢复 SWA 或重新计算最近的几个 token
"Load the recent-window state or recompute the few latest tokens"
- SWA 的精确状态可能已经过期(比如对话又多了几轮)
- 可以从检查点恢复,或者直接重新计算最近这几个 token
- 因为只涉及少量 token,计算成本很低
Step 3:重新计算不完整的压缩块
"Compute the small unmatched tail to reach the current point"
- 就是第三步中橙色部分的 unmatched tail
- 因为只涉及少量 token(不到一个压缩块),计算很快
- 这一步将缓存状态”补齐”到当前请求的精确位置
Step 4:继续生成新 token
"Proceed to generate new tokens"
- 现在 Attention Engine 拥有了完整的、对齐到当前请求的 KV 状态
- 可以开始正常处理新后缀并生成回复
- 跳过了一次完整的 64K token prefill!
三、传统 vs DeepSeek V4 对比
传统全量 KV-Cache 的问题
长前缀(64K tokens)
↓
全量 KV-Cache 存储(每个请求独立)
↓ 巨大(几十到几百 GB)
难以存储大量对话
↓ 内存压力 + 淘汰策略
实际命中率低
核心问题:
- 每个请求独立存全量 KV,内存浪费严重
- GPU 显存有限,存不了几个缓存
- 一旦缓存被淘汰,下次来又要全重算
- 没有持久化机制
DeepSeek V4 的压缩前缀缓存
同样的长前缀(64K tokens)
↓
压缩缓存(CSA + HCA + SWA)
↓ 非常小(<< 1 GB)
SSD 上可存数千个前缀
↓ 持久化 + 高容量
实际工作负载中命中率极高
核心优势:
- 100~1000 倍压缩比:存得更多
- SSD 持久化:重启不丢失
- 跨请求复用:不同对话共享同一前缀缓存
- 三级分层:精度与空间的智能平衡
四、为什么”命中率高得离谱”?
图中的总结:
“Why the hit rate looks ridiculous: repeated long prefixes + very compact compressed KV + disk-backed prefix reuse”
三个因素叠加:
- 重复的长前缀:Agent 场景下,System Prompt + Tool Schema + Context 几乎不变
- 极紧凑的压缩 KV:64K tokens → << 1 GB,存得下、读得快
- 磁盘持久化复用:跨会话、跨重启都能用
这三个因素单独拿出任何一个都有用,叠加在一起就产生了”高得离谱”的命中率。
五、关键技术术语表
| 缩写 | 全称 | 说明 |
|---|---|---|
| KV-Cache | Key-Value Cache | Transformer 推理中存储的注意力 K 和 V 矩阵 |
| CSA | Compressed Sparse Attention | 4 倍压缩,带稀疏选择 |
| HCA | Heavily Compressed Attention | 128 倍压缩,用于最老上下文 |
| SWA | Sliding Window Attention | 滑动窗口,不压缩的精确状态 |
| SSD | Solid State Drive | 固态硬盘,大容量持久化存储 |
| Prefill | Pre-filling | 输入序列一次性送入模型的计算阶段 |
| Decode | Decoding | 逐个生成新 token 的阶段 |
| Hit Rate | 命中率 | 缓存命中/总请求数的比例 |
六、对 Agent 系统的实际意义
在 AI Agent 场景中,这个设计的价值尤其明显:
- System Prompt 通常占输入的 30-50%,且几乎不变
- Tool Schema 可能包含数十个工具描述,所有对话共享
- 代码仓库上下文可能有数万 token,同项目下完全重复
- 多轮对话的历史记录随对话增长而变长
如果没有前缀缓存复用,每个请求都要重新计算这些重复部分,浪费大量计算资源。DeepSeek V4 的方案让这些只算一次、多次复用。
七、进阶讨论:Multi-Agent 场景与 KV-Cache 机制辨析
在实际部署 Multi-Agent 系统时,关于 KV-Cache 有几个关键的技术细节和架构决策点。
1. Multi-Agent 是否会降低命中率?
答案取决于系统设计,而非 Agent 数量。
- Agent 类别有限(推荐):如果系统只有 5-10 个固定角色的 Agent(如“代码专家”、“产品经理”、“测试员”),它们的前缀(System Prompt + Tool Schema)是稳定的。KV-Cache 池只会缓存这几十个固定前缀,复用率极高,命中率不受影响。
- 动态千人千面(不推荐):如果每个用户或每个任务都生成完全不同的 System Prompt,缓存池会被大量一次性前缀塞满,导致“缓存污染”,命中率下降。
2. 是否需要隔离 API 来保护缓存?
-
场景 A:共享 API(单缓存池)
- 优势:跨 Agent 共享公共部分(如相同的项目文档、基础指令)。
- 劣势:如果 Agent 前缀差异大,会互相挤占缓存空间(Pollution)。
- 结论:适合角色少、上下文共享度高的系统。
-
场景 B:隔离 API(独立缓存池)
- 优势:Agent A 的缓存不会被 Agent B 污染,命中率稳定在高位(80-90%+)。
- 劣势:无法复用不同 Agent 间的公共前缀,管理多个 API Key。
- 结论:适合角色差异大、各自独立的系统。
3. KV-Cache vs 传统 Memory
KV-Cache 与传统意义上的 Memory(如 Redis、向量数据库)完全不同:
| 维度 | KV-Cache | 传统 Memory / Vector DB |
|---|---|---|
| 匹配方式 | 精确前缀匹配(Token 级) | Key 查找 / 语义相似度 |
| 顺序敏感性 | 极高(差一个 Token 即中断) | 低 / 无 |
| 存储内容 | 注意力计算的中间状态(KV 矩阵) | 数据本身或特征向量 |
| 智能程度 | 无(不懂语义,只看序列) | 可能具备语义理解能力 |
4. “只存上一次请求”是误区
KV-Cache 不是短期记忆,而是一个**“前缀图书馆”**:
- 容量巨大:利用 SSD 可存储数千个不同的前缀。
- 长期有效:只要空间足够且频繁使用,前缀会长期驻留,不会被轻易淘汰。
- 淘汰机制:通常采用 LRU(最近最少使用),淘汰的是“很久没来且不热门”的前缀,而不是“上一次”请求。
来源:小红书(基于 DeepSeek-V4 技术报告整理)