用空间换取时间:理解 LLM 的记忆开销
为什么不加 Cache 会让模型越跑越慢
在自回归生成中,第 $N$ 步生成的注意力计算需要第 $1$ 到 $N-1$ 步的所有 Key ($K$) 和 Value ($V$) 矩阵。如果没有 Cache,每生成一个新 Token,模型都要重新计算前面所有 Token 的特征。
- 计算浪费:原本已经计算过的 $K$ 和 $V$ 在每一轮都被重复计算,计算复杂度呈 $O(N^2)$ 增长。
- KV Cache 的作用:我们将已经计算好的历史 Token 的 $K$ 和 $V$ 缓存下来,每一轮只需要计算当前 Token 自己的 $K/V$,然后与缓存拼接。计算复杂度降为 $O(N)$。
第一阶段:KV Cache 的容量计算模型
KV Cache 极其消耗显存,是限制 LLM 并发(Batch Size)和上下文长度(Context Length)的第一杀手。你需要掌握如何精确计算其开销:
对于每一层 Transformer,每个 Token 的 KV Cache 占用字节数为: $Size = 2 \times (\text{Layers}) \times (\text{Hidden_Dim}) \times (\text{Precision_Bytes})$
案例分析: 假设使用 FP16(2 字节)推理 Llama-7B(32 层,4096 隐藏维度):
- 每个 Token 消耗:$2 \times 32 \times 4096 \times 2 = 524,288$ 字节 $\approx 0.5$ MB。
- 如果对话长度为 2048 Token:单用户消耗 $0.5 \times 2048 = 1$ GB 显存。
- 如果要支持 128 个并发用户:仅 KV Cache 就要消耗 $128$ GB 显存。
这解释了为什么 80GB 的 A100 显卡也无法在 FP16 下承载长上下文的高并发任务。
第二阶段:Prefill 与 Decode 的二段论
在工程实现中,KV Cache 的生命周期分为两个截然不同的阶段:
1. Prefill(预填充)
模型接收到 Prompt 文本,一次性计算所有输入 Token 的 $K/V$ 并存入 Cache。
- 特性:计算密集型。此时 GPU 满载,利用率高,属于 Compute-bound。
2. Decode(解码)
模型逐个生成新 Token。
- 特性:访存密集型。每生成一个词都要读一次全量 Cache 和模型权重,但计算量极小。此时 GPU 核心大部分时间在等待数据从显存搬运过来,属于 Memory-bound。
第三阶段:内存碎片与 PagedAttention 的诞生
传统的 KV Cache 分配是预先分配一段连续的显存空间。
- 痛点 1:内部碎片。如果预留了 4K 上下文但用户只聊了 10 个词,剩下的空间完全浪费。
- 痛点 2:外部碎片。不同请求的长度不同,显存被切得支离破碎。
- PagedAttention(vLLM 的核心):借鉴了操作系统的虚拟内存理念,将 KV Cache 存储在不连续的“块”(Blocks)中。这消除了 90% 以上的浪费,使系统的并发吞吐量提升了 2-3 倍。
实验与实战指南
在本项目中,你需要完成以下任务:
- 显存监控脚本:编写一段代码,记录生成过程中显存占用的线性增长趋势。
- 延迟对比实验:实现“不带 Cache”和“带 Cache”的生成器。你会发现随着序列变长,不带 Cache 的版本延迟呈指数级飙升,而带 Cache 的版本单字延迟几乎恒定。
- KV Cache 量化测试:尝试将 KV Cache 从 FP16 量化到 INT8。观察显存减半后,模型的长文召回率(如“大海捞针”测试)是否受损。
总结
KV Cache 是 LLM 推理系统的阿喀琉斯之踵。它让推理变得飞快,但也让显存变得极其紧缺。作为系统工程师,理解 KV Cache 的数学模型是进行架构优化、选择量化策略、以及估算服务器承载能力的基石。
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:项目 11:显存的吞噬者:KV Cache 机制与显存预算
本文链接:https://www.sshipanoo.com/blog/ai/llm-roadmap/lab-11-kv-cache/
本文最后一次更新为 天前,文章中的某些内容可能已过时!