KV Cache 为什么会吃显存?
梳理 KV Cache 的数据结构、显存估算方式,以及长上下文为什么会放大问题。
模型权重能装进显卡,为什么请求一长、并发一多,服务仍然会显存不足?从后端容量规划的角度看,权重只是一笔固定支出,每个活跃请求还在不断积累自己的 KV Cache。
可以从一个 token 算起:它在每一层都要留下 Key 和 Value,后续生成还会读取这些数据。算出这一份有多大,再乘上缓存长度和请求数,就能估计 KV 需要多少空间。下面以标准全注意力模型为例;滑动窗口、混合架构和跨请求共享会改变计算方式,需要另算。
从每个 token 算起
一个 token 在一层里留下两份张量,每份包含 个 head,每个 head 有 个元素。再乘上每个元素的字节数、层数和 token 数,就得到容量。假设所有请求长度相同、彼此不共享 KV,一批请求的理论容量为:
| 符号 | 含义 |
|---|---|
| Key 与 Value 两份张量 | |
| 并发持有缓存的请求数 | |
| 需要 KV 的注意力层数 | |
| 每个请求当前缓存的 token 数 | |
| 每层的 KV head 数 | |
| 每个 head 的维度 | |
| 每个缓存元素的字节数,BF16/FP16 为 2 |
hidden_size 不能直接代替 :标准 MHA 中它们通常相等,GQA/MQA 的 KV head 数少于 Query head 数,使用模型隐藏维度会高估这部分容量。各请求长度不同时,应把 换成各请求缓存长度之和。
一个容量例子
取 、、、,KV 使用 BF16。单请求占用:
| 只改变一项,其余保持不变 | 理论容量 |
|---|---|
| 基线:1 个请求 | 96 MiB |
| 请求数从 1 增至 2 | 192 MiB |
| 缓存长度从 2048 增至 4096 | 192 MiB / 请求 |
| KV head 数从 4 增至 16 | 384 MiB / 请求 |
这些是计算例子,不是运行测量。引擎实际占用还包括块取整、索引元数据、分配器开销等;进程总显存还包含权重和其他计算所需的张量。分页和前缀共享会改变物理分配,不能直接用进程显存读数核验上述单项公式。
容量之外还要看读取成本
到这里算出的是“存下多少数据”。生成下一个 token 时,全注意力 Decode 还要读取历史 KV,所以更长的上下文既占空间,也增加读取量。减少缓存精度或把一部分移到其他存储层可以缓解容量压力,但会带来质量、传输或调度方面的取舍,需要分别测量。
核对容量时,先固定并发、只增加请求长度,再固定长度、只增加并发,观察已使用 KV 块是否按预期增长。若引擎预先分配了缓存池,进程显存可能几乎不变,此时应检查池内已用块,而不能据此否定容量公式。
算出理论容量后,还需要知道请求如何分配和释放这些空间:连续批处理与分页 KV继续讨论这一步。GQA 与张量 shape 的上下文见推理总览。