KV Cache的多种策略
在 粗浅理解大模型 KV Cache 中,我们已经了解了 KV Cache 的作用:缓存历史 Token 的 Key 和 Value,避免每次生成新 Token 时重新计算整个上下文。
但是,你会发现文章中通篇都在提 KV Cache 进行缓存数据,但并没有聊到在什么情况下会触发缓存,缓存又是什么都缓存还是有一定规则缓存?
这是因为 KV Cache 的缓存策略其实过多过于复杂,单单一两句话讲不清楚,下面让我们一起从 Hugging Face 官方文档给出的描述,慢慢了解 KV Cache 相关的缓存策略。
使用 Hugging Face 官方的描述是因为其较为主流,具有一定的代表性,但不完全等于整个行业的设计和发展方向。如有什么问题,恳请指出,本文会积极修改。
Hugging Face 官方文档提供了很多种 Cache:
- Dynamic Cache
- Static Cache
- Offloaded Cache
- Quantized Cache
- Sliding Window Cache
- Sink Cache
这些都是不同的缓存管理策略(Cache Strategy)。真正变化的不是 Transformer,而是如何管理 KV Cache。
为什么需要不同的缓存策略?
如果让我们自己实现一个最简单的 KV Cache,大概率会写成这样:cache = append(cache, newKV)
每生成一个 Token:
- 计算新的 K、V。
- 追加到缓存末尾。
- 下一轮继续使用。
整个流程非常简单。
但是,当上下文从 4K Token 变成 128K Token 甚至 1M Token,事情就没那么简单了。
这时候会遇到很多工程问题,例如:
- GPU 显存够不够?
- 一直 append 会不会导致内存重新分配?
- 多个用户同时推理怎么办?
- Beam Search 怎么共享缓存?
- 长上下文是不是一定要全部保留?
所以,现代推理框架开始研究:KV 应该怎么存?怎么释放?怎么压缩?怎么共享?
于是就有了各种 Cache Strategy。
各种缓存策略
Dynamic Cache(动态缓存)
这是最直观,也是 Hugging Face 默认使用的方式。
它的工作方式非常简单:
Token1
↓
KV1
↓
Token2
↓
KV2
↓
Token3
↓
KV3每生成一个 Token,就把新的 KV 追加到缓存中,缓存会不断增长。
优点
- 实现简单。
- 不浪费空间。
- 上下文长度可以动态增长。
缺点
由于缓存不断扩容,GPU 可能需要重新申请内存。
如果上下文非常长,就可能出现:
- 内存碎片
- 扩容开销
- Batch 管理困难
因此,Dynamic Cache 更适合普通聊天场景。
例如:
你好
↓
介绍一下 Go
↓
Go GMP 为什么高效?上下文长度无法提前预测。
Static Cache(静态缓存)
Static Cache 的思路正好相反。
它不会随着生成动态增长,而是在一开始就申请好全部空间。
例如:最大支持 128K Token 。
那么一开始就直接申请:
□□□□□□□□□□□□□□□□□□□□□□□□假设目前只有 100 Token
那么实际使用的是:
■■■■□□□□□□□□□□□□□□□□□□□□后面生成的新 Token,只需要继续往后写。
优点
整个缓存地址不会变化。
GPU 很喜欢这种固定地址,因为:
- 更容易优化
- 更适合 CUDA Graph
- 推理速度更加稳定
缺点
- 空间浪费。如果你的上下文只有
512 Token,却申请了128K Token,那么绝大多数空间都会浪费。
因此,Static Cache 更适合生产部署。
Offloaded Cache(缓存卸载)
KV Cache 最大的问题其实不是计算,而是 占显存。
假设 GPU 拥有 80GB,模型已经占用了 60GB,那么 KV Cache 可能很快就把剩下的显存占满。怎么办?
一种思路就是:把不用的 KV 放到 CPU 内存。
例如:
- GPU:最近
8K Token - CPU:更早的
100K Token
需要的时候再搬回来。
这个思想其实很像操作系统里的 Swap。
优点
可以支持非常长的上下文。
缺点
- CPU 和 GPU 之间的数据传输远远慢于 GPU 显存,因此推理速度会下降。
通常用于:
- 长文档分析
- 法律文本
- 小说生成
Quantized Cache(量化缓存)
KV Cache 本质上是一堆浮点数。
例如:FP16 每个元素占 2 Byte,如果改成 INT8,那么就只占用 1 Byte,显存占用直接减半。进一步 INT4 还能继续减少更多占用。
因此,量化 Cache 的目标就是用更少的显存保存更多 Token。
优点
- Context 可以更长。
- GPU 显存压力明显下降。
缺点
- 量化不可避免会带来一定精度损失。
不过大量研究表明:KV Cache 对量化其实比较敏感,但经过专门设计后,影响可以控制得很小。
因此,近年来 KV Quantization 已经成为热门研究方向。
Sliding Window Cache(滑动窗口)
如果一直保存所有 Token,缓存一定越来越大。那么能不能只保留最近的一部分?
例如:
窗口大小:8K Token
那么:
Token1
↓
Token8000一直保留。生成:
Token8001以后,最早的:
Token1直接丢弃。窗口变成:
Token2
↓
Token8001一直向前滑动。
优点
显存占用始终保持稳定。
缺点
- 模型会忘记很久以前的内容。
因此适合:
- 长时间聊天
- 实时对话
而不适合:
- 需要完整上下文的推理任务。
Gemini 的缓存思路,基本就是这个策略,其他的一些早期的大模型也是这种遗忘策略。
6. Sink Cache(Attention Sink)
Sliding Window 有一个问题:有些 Token 是不能删的。
例如:
System Prompt
↓
你是一位 Go 专家。如果把它删掉:
模型的人设就没了。
因此:
Sink Cache 会把:
- 开头的重要 Token
- 最近生成的 Token
同时保留,中间部分则允许被丢弃。
示意图:
[System Prompt]
↑
永远保留
......
[最近8K Token]
↑
永远保留因此,它比普通 Sliding Window 更智能。很多 Agent 系统都会采用类似策略。
这些策略可以组合使用吗?
多种策略其实是针对于不同场景使用,它们关注的是不同问题。
例如:
- 对策略 Static:缓存如何组织?
- Quantized:缓存如何压缩?
- Offloaded:缓存放在哪里?
- Sliding Window:缓存保留哪些内容?
因此完全可以组合。
例如:Paged Cache + Quantized Cache + Offloaded Cache 都是现实中存在的方案。
工程实践中如何选择 KV Cache?
了解完各种缓存策略后,一个很自然的问题就是:实际部署大模型时,到底应该选择哪一种?
事实上,并没有一种策略能够适用于所有场景。不同的缓存策略,本质上是在 性能、显存占用、上下文长度 三者之间做取舍。
下面举一些例子:
普通聊天机器人
特点:
- 上下文长度不可预测
- 更关注响应速度
- 用户数量较多
通常会优先选择:
- Dynamic Cache
- Paged Cache(多数现代推理框架默认采用)
这样既能够灵活增长上下文,又能保证较好的显存利用率。
长文档分析
例如:
- 法律合同
- 学术论文
- 技术文档
特点:
- Context 非常长
- 更关注能够容纳更多 Token
通常会结合:
- Quantized Cache
- Offloaded Cache
必要时还会配合更大的 Context Window,以换取更长的上下文处理能力。
在线聊天、Agent
对于持续数小时甚至更长时间运行的 Agent 来说:
无限增长的 KV Cache 并不现实。
因此很多系统会采用:
- Sliding Window
- Sink Cache
既保留模型的长期设定(System Prompt),又控制缓存大小,使显存占用保持稳定。
高并发推理服务
对于需要同时服务大量用户的推理服务器来说:
真正重要的不只是单次推理速度,而是:
- GPU 利用率
- 显存碎片
- Batch 调度效率
因此:
现代推理框架(如 vLLM、TensorRT-LLM、SGLang)往往会采用:
- Paged Cache
- Static Cache
- Quantized Cache
甚至组合多种策略,以获得更高的吞吐量。
从上述例子中可以发现缓存策略并不存在 "最好" 这一说,只有是否适合当前业务场景。
很多时候,一个成熟的推理框架都会同时结合多种缓存策略,而不是只依赖某一种实现。
总结
KV Cache 从来都不是一种固定的数据结构,而是一套缓存管理思想。
随着上下文越来越长、用户越来越多,真正影响推理性能的已经不仅仅是模型本身,而是 KV Cache 如何存储、如何压缩、如何释放、如何共享。
因此,不同 Cache Strategy 的目标其实可以归纳成三个方向:
| 目标 | 对应策略 |
|---|---|
| 如何存储 KV | Dynamic、Static、Paged |
| 如何减少显存占用 | Quantized、Offloaded |
| 如何管理超长上下文 | Sliding Window、Sink |
也正因为如此,如今众多推理框架,很多优化工作都集中在 KV Cache 的管理上。可以说,现代 LLM 推理的竞争,很大程度上已经从 "模型算法" 转移到了 "缓存工程"。
RoLingG | 博客
评论(0)