KV Cache 解析

1. 先说结论:KV Cache 到底是什么

一句话:

KV Cache 是 Transformer 在自回归生成过程中,把历史 Token 已经计算出来的 Attention Key 和 Value 保存下来,后面生成新 Token 时直接复用,避免重复计算历史 Token 的 K/V。

注意,它主要解决的是:

同一次生成过程中,历史上下文不要每生成一个 Token 就重新算一遍。

比如模型要生成:

复制代码
我 喜 欢 学 习

当已经生成:

复制代码
我 喜 欢 学

现在要生成"习"。

如果没有 KV Cache,模型为了生成"习",可能重新处理:

复制代码
我
我 喜
我 喜 欢
我 喜 欢 学

历史内容会被重复计算。

有了 KV Cache:

复制代码
我 喜 欢 学

这些历史 Token 对应的 K、V 已经保存了。

生成"习"时,只需要计算:

复制代码
新 Token 的 Q、K、V

然后:

复制代码
新 Q
   ↓
和历史所有 K 做 Attention
   ↓
利用历史所有 V 聚合

这就是核心。


2. 为什么偏偏缓存 K 和 V,而不是 Q?

先回到 Self-Attention。

假设输入 hidden state:

XX

会通过三个线性映射:

Q=XWQQ=XW_Q K=XWKK=XW_K V=XWVV=XW_V

Attention:

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V) = softmax \left( \frac{QK^T}{\sqrt{d_k}} \right)V

假设现在已经有:

复制代码
Token1 Token2 Token3 Token4

正在生成 Token5。

对于 Token5 来说,我们真正需要的是:

Q5Q_5

去查询之前:

K1,K2,K3,K4,K5K_1,K_2,K_3,K_4,K_5

再根据 Attention Weight 聚合:

V1,V2,V3,V4,V5V_1,V_2,V_3,V_4,V_5

也就是说,历史 Token 的 K/V 后面仍然会不断被查询。

但是历史 Token 的 Query:

Q1,Q2,Q3,Q4Q_1,Q_2,Q_3,Q_4

后面基本不会再用。

因为我们只需要计算当前新 Token 的输出

所以:

历史 K/V 有复用价值,历史 Q 没有。

因此叫:

KV Cache

而不是:

QKV Cache。


3. 没有 KV Cache 会发生什么?

假设:

复制代码
Prompt = 1000 tokens

模型开始生成 100 个新 Token。

第一个输出 Token:

复制代码
处理 1000 tokens

第二个:

复制代码
处理 1001 tokens

第三个:

复制代码
处理 1002 tokens

......

如果每一次都把历史 Token 完整重新经过 Transformer:

计算会大量重复。

粗略理解,没有 Cache 时生成长度为 NN:

1+2+3+⋯+N1+2+3+\cdots+N

是:

O(N2)O(N^2)

级别的重复历史计算。

有 KV Cache 后,每一步只需要对新增 Token计算新的 K/V,再和缓存的历史 K/V 做 Attention。

因此自回归 Decode 会快很多。


4. Prefill 和 Decode 是理解 KV Cache 的关键

LLM 推理通常可以拆成两个阶段。

假设用户输入:

复制代码
请分析下面这段 VPN 流量......

一共有 2000 tokens。

Prefill 阶段

模型一次性处理:

复制代码
2000 input tokens

在 Transformer 的每一层,都会生成这 2000 个 Token 的:

K,VK,V

然后把它们存进 KV Cache。

所以 Prefill 后:

复制代码
KV Cache =
Prompt 2000 tokens 对应的所有 K/V

接下来开始生成。

Decode 阶段

生成第一个 Token:

复制代码
只计算新 Token 的 Q/K/V

然后:

复制代码
Q_new
   ↓
Attention(K_cache + K_new)
   ↓
V_cache + V_new

生成完以后:

复制代码
K_new
V_new

继续 append 到 Cache。

然后生成下一个。

所以整个过程是:

复制代码
Prompt
   ↓
Prefill
   ↓
建立 KV Cache
   ↓
Decode Token1
   ↓
追加 K1/V1
   ↓
Decode Token2
   ↓
追加 K2/V2
   ↓
......

一句话背:

Prefill 建 Cache,Decode 用 Cache 并持续追加。


5. 为什么长上下文的 KV Cache 特别吃显存?

这就进入工程部分了。

KV Cache 不是只缓存一层。

假设一个 Transformer 有:

复制代码
32 layers

那么每层都需要缓存 K 和 V。

KV Cache 大小近似:

Memory≈2×L×T×Hkv×D×BytesMemory \approx 2 \times L \times T \times H_{kv} \times D \times Bytes

其中:

  • 22:K 和 V 两份

  • LL:Transformer 层数

  • TT:当前上下文 Token 数

  • HkvH_{kv}:KV Head 数

  • DD:每个 Head 维度

  • Bytes:数据类型,例如 FP16 是 2 Bytes

假设:

复制代码
32 layers
32 KV heads
head_dim = 128
context = 8192
FP16 = 2 bytes

KV Cache 近似:

2×32×8192×32×128×22 \times32 \times8192 \times32 \times128 \times2

我们算一下,大约就是几个 GB 量级。

而且这是:

单个 Request。

如果同时跑:

复制代码
Batch = 16

KV Cache 还会进一步膨胀。

所以在线 LLM Serving 里,很多时候真正限制并发的不是模型权重,而是:

KV Cache 显存。


6. 为什么长上下文模型越来越重视 GQA / MQA?

传统 Multi-Head Attention,可能:

复制代码
Q heads = 32
K heads = 32
V heads = 32

那么需要缓存 32 个 K head 和 32 个 V head。

但其实多个 Query Head 可以共享同一组 K/V。

于是就有了 MQA:

Multi-Query Attention

例如:

复制代码
Q heads = 32
KV heads = 1

所有 Q Head 共享同一套 K/V。

KV Cache 大幅下降。

后来又有 GQA:

Grouped-Query Attention

例如:

复制代码
Q heads = 32
KV heads = 8

每几个 Q Head 共享一套 K/V。

这是性能和质量的折中。

因此如果面试官问:

为什么现在很多 LLM 使用 GQA?

一个非常好的回答就是:

除了推理效率,GQA 能显著减少 KV Head 数,因此降低 KV Cache 显存占用,提高长上下文和高并发 Serving 能力。


7. KV Cache 为什么会随着生成不断变大?

因为每生成一个 Token:

复制代码
K_new
V_new

都会被加入 Cache。

例如:

复制代码
Prompt = 4000 tokens
Generated = 1000 tokens

最后 KV Cache 对应的是:

复制代码
5000 tokens

而不是只保存 Prompt。

所以:

KVMemory∝SequenceLengthKVMemory \propto SequenceLength

上下文越长:

复制代码
KV Cache 越大

生成越长:

复制代码
KV Cache 继续增大

这也是为什么:

复制代码
128K context

不只是 Attention 算得慢,KV Cache 也会非常巨大。


8. KV Cache 减少的是哪部分计算?

这个问题容易答错。

KV Cache 并没有让 Attention 变成完全 O(1)

因为当前 Token 的 Query:

QtQ_t

仍然要和历史所有:

K1,...,KtK_1,\dots,K_t

做 Attention。

所以当上下文变长,单步 Decode 成本仍然会上升。

KV Cache 节省的是:

不需要重新计算历史 Token 在每一层的 K/V 和其他前向过程。

但:

QtK1:tTQ_tK_{1:t}^T

仍然需要做。

所以更精确地说:

KV Cache 消除了大量历史状态重复计算,但当前 Token 仍需读取整个历史 KV。

这也是为什么长上下文推理仍然慢。


9. KV Cache 和 Prompt Cache 到底什么区别?

这是你现在最需要彻底区分的。

KV Cache

场景:

复制代码
一次 LLM 请求

例如:

复制代码
User Prompt
↓
生成 500 Tokens

这 500 个 Token 自回归生成时复用历史 K/V。

它主要解决:

单次生成中的历史计算复用。


Prompt Cache / Prefix Cache

场景:

复制代码
多次独立 LLM 请求

Call 1:

复制代码
System + Skill + Tools + User

Call 2:

复制代码
System + Skill + Tools + Observation

因为前缀:

复制代码
System + Skill + Tools

一样,所以推理系统可能复用之前 Prefill 的结果。

它解决:

多个请求之间相同 Prompt Prefix 的重复计算。

你可以直接记:

KV Cache:一次生成内部。

Prompt Cache:多次请求之间。

底层都可能和 KV state 有关系,但工程语义完全不同。


10. Prompt Cache 底层是不是也可能缓存 KV?

是。

这一点稍微深入一点。

如果两个请求共享:

复制代码
System Prompt
+
Skill
+
Tool Schema

那么这些 Token 的 Transformer 前向计算是相同的。

理论上完全可以缓存这些前缀 Token 在每层的:

复制代码
K/V state

下一次直接复用。

所以 Prompt Cache 底层通常与 prefix 对应的 KV 状态复用有密切关系。

但是面试时不要把两个词混着用。

因为:

KV Cache 描述的是模型自回归推理的基本机制。

而:

Prompt Cache 通常描述 Serving 系统跨 Request 的前缀复用策略。


11. 什么是 PagedAttention?

这个很值得你补一下,因为做 LLM Infra 经常问。

传统 KV Cache 有一个问题:

每个请求长度不同。

比如:

复制代码
Request A:2000 tokens
Request B:3700 tokens
Request C:900 tokens

如果每个 Request 都提前申请一大块连续显存:

复制代码
最大 8192 tokens

那么会造成大量空间浪费和显存碎片。

这和操作系统内存管理很像。

PagedAttention 的思路就是:

不要要求一个请求的 KV Cache 必须存储在一整块连续物理显存里。

而是把 KV Cache 切成固定大小的 Block/Page。

例如:

复制代码
Request A logical KV:

Block 0
Block 1
Block 2
Block 3

物理显存里可能是:

复制代码
Page 8
Page 21
Page 4
Page 17

由一个类似 Page Table 的结构做映射。

这样能提高:

复制代码
显存利用率
动态增长能力
Batch 并发能力

vLLM 的重要设计之一就是 PagedAttention。

你可以把它类比:

操作系统的虚拟内存分页。

非常容易理解。


12. 为什么 Continuous Batching 和 KV Cache 强相关?

普通 Batch:

复制代码
A B C D

一起开始,一起结束。

问题是:

A 可能只生成 20 个 Token:

复制代码
A 已经结束

D 可能还需要生成 1000 个。

如果必须等 D:

复制代码
GPU 利用率很差

Continuous Batching 的做法是:

A 一结束:

复制代码
立刻把新请求 E 塞进 Batch

于是:

复制代码
B C D E

继续执行。

但这样每个请求的 KV Cache:

复制代码
长度不同
开始时间不同
结束时间不同

因此 Serving 系统必须能够灵活管理不同 Request 的 KV Cache。

这就是为什么:

Paged KV Cache + Continuous Batching

经常一起出现。


13. KV Cache Quantization 是什么?

KV Cache 太吃显存怎么办?

一个办法是:

把 KV Cache 本身量化。

比如原来:

复制代码
FP16
2 bytes/value

改成:

复制代码
INT8
1 byte/value

理论上 KV Cache 显存可以接近减半。

甚至:

复制代码
FP8
INT4

进一步压缩。

但和模型权重量化一样:

会引入数值误差。

尤其 K 会直接参与:

QKTQK^T

所以量化误差可能影响 Attention 分布。

工程上需要在:

复制代码
显存
速度
质量

之间做 trade-off。


14. KV Cache Offload 又是什么?

如果 GPU 显存不够,可以把一部分 KV Cache:

复制代码
GPU
 ↓
CPU RAM

甚至:

复制代码
SSD

称为 Offload。

优点:

可以支撑更长上下文或更多并发。

缺点:

PCIe / 内存传输延迟很高。

于是可能变成:

复制代码
计算不慢
等数据搬运很慢

所以 Offload 是典型:

用 latency 换 capacity。


15. 什么叫 KV Cache Eviction?

假设上下文:

复制代码
128K

GPU 放不下全部历史 KV。

那可以考虑:

是否真的所有历史 Token 都必须保留?

一种思路就是:

复制代码
只保留近期 Token
+
少量重要 Token

删除不重要的历史 KV。

这叫:

KV Cache Eviction / Compression

但注意:

删除以后模型可能无法再完整关注被删掉的信息。

所以这是:

显存与上下文质量的 trade-off。


16. Sliding Window Attention 和 KV Cache 有什么关系?

如果模型规定每个 Token 只关注最近:

复制代码
4096 tokens

而不是全部历史:

复制代码
100000 tokens

那么 KV Cache 理论上也只需要保留最近窗口里的相关 KV。

例如:

复制代码
t = 10000

模型只看:

复制代码
5905 ~ 10000

那么早期 KV:

复制代码
1 ~ 5904

可以被丢弃。

这样:

KVMemoryKVMemory

不会无限随序列增长。

所以 Sliding Window Attention 的重要工程价值之一就是:

控制 KV Cache 增长。


假设普通生成只有一条路径:

复制代码
A → B → C → D

只维护一份 KV Cache。

Beam Search:

复制代码
A
├─ B1
│  ├─ C1
│  └─ C2
└─ B2
   ├─ C3
   └─ C4

不同 Beam 共享前面的 Prefix:

复制代码
A

但后面会分叉。

如果傻乎乎给每一个 Beam 复制完整 KV:

复制代码
显存爆炸

所以好的 Serving Engine 会尽量:

共享 Prefix KV,只复制分叉后的部分。

这和 Copy-on-Write 很像。


18. 为什么 Agent 系统也需要理解 KV Cache?

你可能会想:

我做的是 Agent,又不是 LLM Infra,为什么要学这么深?

因为很多 Agent 性能问题最终都会下沉到推理层。

比如你的 Agent:

复制代码
LLM
↓
Tool
↓
Observation
↓
LLM
↓
Tool
↓
Observation
↓
LLM

如果每轮都重新发送:

复制代码
System
Skill
Tools
History
Evidence

那么每一次都涉及:

复制代码
Prefill
KV Cache 建立
Decode

如果 Serving 层支持 Prefix Cache:

复制代码
前面稳定部分可以复用

否则:

复制代码
每轮重新 Prefill

所以你设计 Agent 时:

Prompt 结构、Tool Schema 稳定性、调用次数

都会间接影响底层 KV 计算。

这就是为什么那位面试官会从:

Plan Compiler 补一个 C

突然问到:

Cache 和 Token。

他其实已经从 Agent Workflow 追到了 Serving Cost。


19. 一个非常关键的区别:Prefill 是 Compute-Bound,Decode 更容易 Memory-Bound

这个是面试高级一点的问题。

Prefill:

复制代码
一次处理大量 Token

矩阵规模大,GPU Tensor Core 能充分利用。

因此更偏:

Compute Bound

Decode:

复制代码
每次通常只有一个新 Token

但每生成一个 Token 都要读取大量历史 KV Cache。

所以大量时间可能花在:

复制代码
显存读取 K/V

而不是算力。

因此 Decode 更容易:

Memory Bandwidth Bound

这也是为什么减少 KV Cache、使用 GQA、KV Quantization 等,对 Decode 很重要。

一句面试金句:

Prefill 更看算力,Decode 更看显存带宽和 KV Cache。


20. TTFT 和 TPOT

你以后做 LLM Serving 面试,这两个指标要认识。

TTFT

Time To First Token:

从请求到生成第一个 Token 的时间。

主要受到:

复制代码
Prompt 长度
Prefill
排队
Cache Hit

影响。

TPOT

Time Per Output Token:

后续每生成一个 Token 平均需要多久。

更受:

复制代码
Decode
KV Cache
Memory Bandwidth
Batch

影响。

因此:

Prefix Cache 通常更直接改善 TTFT。

而:

KV Cache 管理效率对 TPOT 和并发能力非常重要。


21. 面试官问:KV Cache 有什么缺点?

你不要只说"占显存"。

高分版:

KV Cache 用显存换计算,显著减少自回归生成中的重复计算,但代价是显存占用会随层数、KV Head 数、Batch Size 和 Sequence Length 线性增长。长上下文和高并发场景下,KV Cache 很容易成为 Serving 的容量瓶颈。

因此现在很多优化,比如 GQA/MQA、PagedAttention、KV Quantization、Offload、Sliding Window,本质上都在优化 KV Cache 的存储和访问效率。

这就是非常完整的答案。


22. 你现在需要达到的面试水平

如果有人突然问你:

什么是 KV Cache?

你 20 秒回答:

KV Cache 是自回归 Transformer 推理里的计算复用机制。Prefill 阶段先计算并保存历史 Token 在每一层 Attention 中的 Key 和 Value,后续 Decode 每生成一个 Token,只计算新 Token 的 Q/K/V,然后用新 Query 去 Attention 历史 KV,并把新的 K/V 继续追加到 Cache。这样不用每生成一个 Token 都重新计算整个历史上下文,大幅降低重复计算。代价是 KV Cache 显存会随着上下文长度和并发数增长,因此长上下文 Serving 经常通过 GQA、PagedAttention 和 KV Quantization 等技术优化。

如果继续问:

KV Cache 和 Prompt Cache 区别?

答:

KV Cache 主要是一次自回归生成内部 复用历史 K/V;Prompt Cache 是多个独立请求之间复用相同 Prompt Prefix 的 Prefill 结果。Prompt Cache 底层也可能复用对应的 KV 状态,但工程语义不同。

如果继续问:

为什么 GQA 能优化推理?

答:

因为多个 Query Head 共享更少的 K/V Head,KV Cache 的尺寸直接下降,从而降低显存占用和 Decode 的内存带宽压力。

如果继续问:

为什么 PagedAttention 有用?

答:

因为不同请求的 KV Cache 长度动态变化,连续大块分配容易造成显存浪费和碎片。PagedAttention 把 KV Cache 分成固定 Page/Block,通过映射管理逻辑连续、物理不连续的 KV,从而提高显存利用率和并发能力。

相关推荐
captain3761 小时前
网络原理(9)-数据链路层
java·网络协议·java-ee
liliangcsdn1 小时前
IVOL与偏度因子的对比测量分析
算法
惊讶的猫2 小时前
意图识别置信度和检索重排的分数
java
caoerzhong2 小时前
仓库管理数字化趋势解读:JeeWMS 开源 Java WMS 视角下的五个演进方向
java·开源
程序员JerrySUN3 小时前
Jetson Edge AI 实战01:Nano、Xavier、Orin 怎么选?Jetson 硬件选型详解【视频讲解】
java·数据库·redis·安全·mybatis
threerocks3 小时前
Jev 入门第一课
算法
Joy T4 小时前
Spring AI 2.0 Agent 进阶:Memory、State 与 Context Engineering 常见技术全景
java·人工智能·后端·spring·agent入门·agent state
估值探索者4 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
西柚研究生1234564 小时前
论文分析17:YOLOv11_UAVNet:无人机航拍图像专用目标检测算法
人工智能·python·深度学习·算法·目标检测