深色模式
KV Cache 与显存优化
摘要:KV Cache 是 LLM 推理显存的主要消耗者,长上下文下常超过权重本身。本文给出 KV Cache 显存占用的精确公式,讲解分页(PagedAttention)、KV 量化(FP8)、前缀缓存与抢占等优化手段,并演示如何按"目标并发 × 序列长度"反推所需显存。适用版本:vLLM 0.8.x–0.9.x 等主线引擎。
适用版本与前提
- 引擎:vLLM / TGI / SGLang(KV 机制相通)
- 需了解:Transformer 注意力、FP16/BF16 精度、显存(HBM)
核心概念:KV Cache 是什么、占多少
解码时,模型为避免重复计算注意力历史,会缓存每一层每个位置的 Key 和 Value 向量。对一个请求、一个 token 的 KV Cache 大小:
text
per_token_KV_bytes = 2 × num_layers × num_kv_heads × head_dim × bytes_per_elem1
2:Key 和 Value 各一份num_kv_heads:KV 头数(MHA = 等于注意力头数;GQA/MQA 时远小于 Q 头数,更省)head_dim:每个头的维度(如 128)bytes_per_elem:精度字节数(FP16/BF16 = 2,FP8 = 1,INT8 = 1)
对一个长度为 S 的请求,KV 占用约 per_token_KV_bytes × S。总显存 ≈ 权重 + Σ(各请求 KV)。
用公式算一笔(Llama-3.1-8B,FP16)
num_layers=32, num_kv_heads=8(GQA), head_dim=128, bytes=2: per_token = 2 × 32 × 8 × 128 × 2 = 131,072 字节 ≈ 128 KB/token。 一个 4k 上下文请求 ≈ 128KB × 4096 ≈ 524 MB。 若想并发 100 个 4k 请求,KV 约 50 GB——这就是为什么 7B 虽权重仅 15GB,却需要 80GB 卡才能高并发。
可见:GQA/MQA 模型(Qwen/Llama-3 系列)KV 显著更小,选模型时这是隐性的"显存成本"指标。
架构与原理:KV 是怎么被管理的
现代引擎把 KV Cache 当"池"管理,而非每请求独立大块:
- 分页(PagedAttention,vLLM):切成固定块,逻辑块表映射到物理块,碎片趋近于零,完成即回收复用。
- 前缀缓存(prefix caching):相同前缀(如同一 system prompt)只算一次 KV,后续请求直接复用。
- KV 量化:把 KV 从 FP16 压到 FP8/INT8,显存近乎减半,精度损失通常很小(需按业务评测)。
- 抢占(preemption):显存不足时,把低优先请求的 KV swap 到 CPU 或重算,保证高优先请求不饿死。
显存分配优先级
gpu_memory_utilization 决定给 KV 池的显存比例。典型 0.85–0.92:留一点给权重常驻与碎片。设太低 → KV 池小、并发上不去;设太高 → OOM 或碎片抖动。
生产实践:按并发反推显存
python
# 估算单卡能并发多少请求(示例,非生产工具)
num_layers = 32
num_kv_heads = 8 # GQA
head_dim = 128
bytes_fp16 = 2
per_token_KB = 2 * num_layers * num_kv_heads * head_dim * bytes_fp16 / 1024
seq_len = 4096
kv_per_req_MB = per_token_KB * seq_len / 1024
print(f"每 token KV ≈ {per_token_KB:.1f} KB; 4k 请求 KV ≈ {kv_per_req_MB:.0f} MB")
kv_budget_GB = 80 * 0.90 # 80GB 卡,90% 给 KV
concurrency = kv_budget_GB * 1024 / kv_per_req_MB
print(f"估算最大并发 ≈ {concurrency:.0f} 个 4k 请求")
# [未实测] 实际还要扣权重常驻与碎片;此脚本仅用于量级判断1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
bash
# 启动:FP8 KV 量化 + 高利用率(vLLM 示例,参数名以版本为准)
docker run --gpus all --ipc=host -p 8000:8000 \
-v /mnt/models:/models \
vllm/vllm-openai:latest \
--model /models/Qwen2.5-7B-Instruct \
--kv-cache-dtype fp8_e5m2 \
--gpu-memory-utilization 0.92 \
--max-model-len 32768 \
--enable-prefix-caching
# [版本相关] --kv-cache-dtype / --enable-prefix-caching 开关名随版本变化,以官方文档为准1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
验证
bash
# 看启动日志里这两行,确认 KV 池与并发估算
# INFO GPU KV cache size: 643,232 tokens
# INFO Maximum concurrency for 40960 tokens per request: 15.70x
# 用压测观察 vLLM 指标 gpu_cache_usage_sys 是否稳定在 80% 附近且 preempted=01
2
3
4
2
3
4
回滚与清理
KV 量化可能伤精度
启用 FP8 KV 前务必做质量回测。若业务对长上下文精度敏感(如代码/RAG 引用),量化后需抽样核对。异常回退:去掉 --kv-cache-dtype 重启。
故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| OOM | 权重+KV 超显存 | 降 --gpu-memory-utilization、量化 KV、降 --max-model-len、加 TP |
| 频繁 preempted | KV 池满 | 量化 KV/权重、降并发上限、加卡 |
| 长尾延迟高 | 同卡并发过多 | 限流、降 --max-num-seqs |
| prefix cache 命中低 | 前缀重复少 | 评估是否关闭(浪费管理开销)或改 prompt 设计 |
安全与合规
- 成本失控:长上下文 × 高并发 = 显存爆炸 = 单请求就能拖垮整卡。网关强制输入长度与
max_tokens上限。 - 越权:API 鉴权前置。
- 数据残留:KV Cache 可能含用户上下文,多租户场景用独立副本或请求隔离,避免跨请求泄露。
成本与性能(示意)
| 模型 | KV/token | 4k 请求 KV | 80GB 卡并发(估) |
|---|---|---|---|
| Llama-3.1-8B (GQA) | ~128 KB | ~524 MB | ~130[未实测] |
| Llama-3.1-70B (GQA) | ~1.1 MB | ~4.4 GB | ~15[未实测] |
| 同模型 FP8 KV | 减半 | 减半 | 约 ×2 |
选型隐含成本
优先选 GQA/MQA 架构(Qwen2.5、Llama-3、DeepSeek 系列)——同样 7B,KV 比 MHA 模型小很多,单卡能并发更多。KV 量化是"几乎免费的并发翻倍",但记得回测质量。