KV Cache 显存如何估算?
简化版
单条序列的 KV Cache 可估算为:
2 × 层数 × KV 头数 × 每头维度 × 已缓存 Token 数 × 元素字节数
2 表示 Key 和 Value。总显存还要乘活跃序列数,并加上模型权重、激活、CUDA Graph、工作区和碎片余量。GQA/MQA 使用的 KV 头数少于 Query 头数,不能误用 Attention 总头数。
例如 32 层、8 个 KV 头、Head Dim 128、FP16、每条 4096 Token,则单序列约 1 GiB;若 20 条都达到该长度,仅 KV 就约 20 GiB。
详细版
估算步骤是:先从模型配置读取 num_hidden_layers、num_key_value_heads、head_dim 和 KV 数据类型,再用“Prompt Token + 已生成 Token”作为当前缓存长度。批量服务应对每条序列分别求和,因为请求长度不同。
KV_total = Σrequests (2 × L × Hkv × Dh × Si × B)
如果采用张量并行且 KV 头均匀分片,单卡理论 KV 约再除以并行度;但复制头、分页元数据、对齐与临时缓冲会产生额外开销。容量规划不能把 GPU 总显存全部留给 KV,应先扣除权重和运行时预留,再按安全水位计算可容纳 Token 数。
回答时还要说明三点:PagedAttention 主要减少预留浪费,不改变有效 KV 的理论字节数;KV 量化通过降低每元素字节数节省容量,但需质量和 Kernel 验证;滑动窗口或淘汰策略改变保留 Token 数,却可能损失长程信息。
完整版教学
1. KV Cache 为什么存在
自回归 Decode 每一步都需要历史 Token 的 Key 和 Value。如果不缓存,就要为整个前缀重复计算;缓存后只计算新 Token 的 K/V,并让 Query 与历史 K 做注意力。
step t input: new token yt
reuse: K1..Kt-1, V1..Vt-1
append: Kt, Vt
output: next-token logits
它用显存换计算,缓存随着上下文和生成长度增长,是在线并发的核心容量约束。
2. 基础公式如何推导
每层、每个 Token 都保存 Hkv × Dh 个 Key 元素和同样数量的 Value 元素,因此:
bytes_per_token = 2 × L × Hkv × Dh × B
bytes_per_sequence = bytes_per_token × S
其中 L 是层数,Hkv 是 KV 头数,Dh 是每头维度,B 是每元素字节,S 是当前缓存 Token 数。若用 FP16/BF16,通常 B=2;FP8 约为 1,但还可能有 Scale 元数据。
3. 为什么不能直接用隐藏维度
对标准 MHA,Hkv × Dh 等于隐藏维度;对 GQA/MQA 则不是。GQA 让多个 Query 头共享一组 K/V,MQA 只保留一个 KV 头。
| 注意力类型 | Query 头 | KV 头 | KV 容量特征 |
|---|---|---|---|
| MHA | 32 | 32 | 最大 |
| GQA | 32 | 8 | 约为对应 MHA 的 1/4 |
| MQA | 32 | 1 | 约为对应 MHA 的 1/32 |
估算时应读取 num_key_value_heads,若配置没有该字段,才通常按 Attention 头数处理。
4. 做一个可复核的算例
假设模型有 32 层、8 个 KV 头、Head Dim 为 128,KV 使用 FP16,一条序列缓存 4096 Token:
2 × 32 × 8 × 128 × 4096 × 2
= 1,073,741,824 bytes
= 1 GiB
因此 20 条都达到 4096 Token 时,理论有效 KV 为 20 GiB。注意简化版若采用同参数必须与此一致;任何容量结论都应写清单位是 GB(十进制)还是 GiB(二进制)。
5. Prompt 和输出都要计入
缓存长度不是只看生成 Token,而是:
current_sequence_length = prompt_tokens + generated_tokens
一个输入 8000、输出 200 Token 的 RAG 请求,KV 压力可能远高于输入 200、输出 1000 Token 的聊天请求。容量规划必须保留真实输入与输出联合分布,不能只拿平均输出长度乘并发。
6. 多请求为何应逐条求和
批内序列长度通常不同。理论有效 KV 应对每个请求的当前长度求和,而不是统一乘最大长度:
def kv_bytes(lengths, layers, kv_heads, head_dim, bytes_per_elem=2):
per_token = 2 * layers * kv_heads * head_dim * bytes_per_elem
return per_token * sum(lengths)
若引擎采用连续预留,则实际分配可能更接近最大长度;分页引擎则更接近已用 Token 向上取整到块边界的总和。
7. PagedAttention 节省的是什么
PagedAttention 将 KV 切成固定 Token 数的块,按需映射到物理显存。它减少按最大长度预留造成的浪费,也缓解请求增长和结束带来的外部碎片。
它不会让已经写入的 K/V 元素凭空变少。实际分配量近似为每条序列长度向上取整到块大小,再加块表和最后一块的内部碎片。
8. 张量并行时怎么估
若 KV 头沿张量并行 Rank 均匀切分,单卡有效 KV 理论上约为总量除以 TP 度。例如总 KV 20 GiB、TP=4,则每卡约 5 GiB。
但当 KV 头数不能整除 TP 度,或实现复制部分 KV 头时,不能直接相除。还要考虑通信缓冲、CUDA Graph 捕获内存和各 Rank 对齐,所以最终应以引擎的实际内存 Profile 校准。
9. KV 量化能省多少
从 FP16 降到 FP8,数据主体理论上减半;降到 INT4,理论主体约为四分之一。但分组 Scale、Zero Point、对齐填充和反量化工作区会让实际比例略高。
量化还可能影响长上下文检索、困惑度与生成稳定性。应在不同长度桶上回归质量并测 TPOT,避免容量变大但 Kernel 反量化导致服务变慢。
10. 可用 KV 预算怎样计算
GPU 总显存需先扣除固定和半固定占用:
KV budget = total VRAM
- model weights
- runtime workspace
- activations / temporary buffers
- CUDA Graph reservation
- safety margin
再用 KV budget / bytes_per_token 得到全实例可缓存 Token 上限。安全余量用于应对批次波动、通信库和内存碎片,不能把理论余量全部售卖给请求。
11. 如何反推并发
若可用 KV 预算为 24 GiB,单 Token KV 为 256 KiB,则理论容量约 98,304 Token。若规划每条最多 4096 Token,理论满长并发为 24;实际还应乘低于 1 的安全系数。
平均长度不能直接用来承诺并发,因为尾部请求会同时出现。更稳妥的是根据长度分布做离散事件压测,并设置 KV 水位准入控制。
12. 前缀共享与滑动窗口
多个请求共享完全相同的前缀时,部分引擎可复用对应 KV 物理块,节省重复缓存。缓存键必须包含模型、Tokenizer 和模板版本,并保证租户数据隔离。
滑动窗口只保留最近 W 个 Token,可把 KV 上限控制为 O(W);但模型可能无法访问被淘汰的远程信息。它是语义与容量的取舍,不只是内存技巧。
13. 怎样用监控校准公式
上线记录已用 KV 块、可用块、活跃序列、缓存 Token、前缀命中率、抢占和 OOM。将理论有效字节与引擎报告值比较,可识别块内碎片、复制头或额外元数据。
压测要覆盖 P50、P95、P99 长度组合,并观察高水位下 TTFT 和 TPOT。若只验证单条满长请求,就无法证明并发场景不会碎片化或抢占。
公式给出容量下界,真实可承载并发还取决于分页粒度、并行实现、运行时预留和流量尾部。
14. 常见误区与追问
- 误区:KV Cache 只和输出长度有关。 Prompt 与已生成 Token 都在缓存中。
- 误区:GQA 模型仍用 Query 头数估算。 应使用
num_key_value_heads。 - 误区:PagedAttention 会压缩有效 KV。 它主要减少预留浪费和外部碎片。
- 误区:总显存减权重就是全部 KV 空间。 激活、工作区、图捕获和安全余量也要扣除。
- 误区:张量并行后一定精确除以卡数。 KV 头复制与实现细节可能破坏该假设。
- 追问:为什么同样并发也会 OOM? 请求长度组合、块碎片和临时工作区峰值可能不同。
- 追问:如何提高可承载并发? 可用 GQA/MQA、KV 量化、分页、前缀共享或长度准入,但都需验证边界。
15. 加强记忆
- 先记系数 2:Key 与 Value 各一份。
- 再记维度:层数、KV 头数、Head Dim、Token 数、元素字节。
- 再记长度:Prompt 加已生成 Token。
- 再记并发:对活跃序列长度求和。
- 再记实现:分页取整、TP 分片、量化元数据会修正理论值。
- 再记预算:扣除权重、工作区和安全余量后才是 KV 空间。
- 最后记验收:用真实长度分布和引擎监控校准公式。