大模型
KV-Cache 详解:大模型推理为什么越聊越慢,又怎么变快
长对话为什么会越聊越慢
用过本地大模型的人都有体会:前几句秒回,聊到几十轮之后,每个字都像在挤牙膏。问题不在模型变笨,而在推理的方式。大模型是自回归模型,一个 token 一个 token 地往外蹦,生成第 N 个 token 时,要把前面 N-1 个 token 全部"看一遍"。
看一遍的过程分两步:Prefill(预填充:把整段提示词一次性算完)和 Decode(解码:逐 token 自回归生成)。没有缓存的情况下,Decode 每走一步都要把历史所有 token 的注意力重算一遍,工作量随序列长度平方级增长——聊得越长,每一步越贵,于是越来越慢。
KV-Cache 做的事情很简单:既然历史的 Key 和 Value 算过就不会变,那就存起来,别每次都重算。
KV 为什么能缓存:从注意力公式说起
注意力机制的经典公式:
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d)) * V
Q = X * W_Q 查询(Query),"我要找什么"
K = X * W_K 键(Key), "我是什么"
V = X * W_V 值(Value), "我有什么"
生成新 token 时,新 token 的 Q 需要跟全部历史 token 的 K 做点积,再拿结果加权全部历史 token 的 V。也就是说:历史 token 的 K、V 被反复使用,而它们只由历史 token 决定,不会因为新 token 而变化。那就缓存起来:KV-Cache = 所有历史 token 的 K 和 V 的集合。
有了缓存,Decode 每步只需要:算新 token 的 K、V → 追加进缓存 → 用新 Q 跟整个缓存的 K、V 做注意力。复杂度从"重算全部"降为"只算新增",速度质变。
显存账本:缓存比权重还占地方
KV-Cache 不是免费的,它吃显存。占用量有明确的公式:
KV Cache = 2 × 层数 × KV头数 × head维度 × 序列长度 × 每元素字节数
(×2 是因为 K、V 各一份)
# 估算 KV Cache 显存占用(字节)
def kv_cache_bytes(n_layers, num_kv_heads, head_dim, seq_len, dtype_bytes=2):
return 2 * n_layers * num_kv_heads * head_dim * seq_len * dtype_bytes
# Llama-3-8B 级模型:32 层、8 个 KV 头、head_dim=128、128K 上下文、fp16
print(kv_cache_bytes(32, 8, 128, 128_000, 2) / 1024**3, "GB")
# ≈ 15.6 GB —— 比 8B 模型的权重(约 16GB fp16)还离谱
这就是为什么长上下文那么贵:上下文一长,KV-Cache 直接压垮显存,并发也上不去。2026 年各家冲 128K、256K、1M 上下文,比拼的其实是"谁能在同等显存下塞进更长的缓存"。
让缓存瘦身:GQA、MLA 与量化
要降显存,最直接的办法是让 KV 头变少、精度变低。
- MHA → GQA/MQA:多头注意力每个头各存一份 KV,GQA(分组查询注意力)让多个查询头共享同一组 KV 头。Llama 3 用 8 个 KV 头配 32 个查询头,KV-Cache 直接砍 4 倍;MQA 更极端,全部查询头共享一份 KV。
- MLA(多头潜在注意力):DeepSeek 的方案,把 KV 压缩进低维潜在空间再还原,KV-Cache 用量下降 90% 以上,是 2026 年长上下文模型的标配。
- KV 量化:fp16(2 字节)→ int8(1 字节)→ int4(0.5 字节)。KV 量化对精度影响比权重量化更温和,配合 per-channel/per-token 校准,显存减半甚至减 75%,是现成推理框架最常见的加速开关。
三招可以叠加:先 GQA/MLA 从架构上砍头,再量化把每个元素压小。
显存管理:PagedAttention 与连续批处理
缓存省下来了,还要用得好。传统推理框架给每个请求预分配"最坏情况"的连续显存块,序列实际没聊那么长,浪费 + 碎片化,并发一大就 OOM。vLLM 的 PagedAttention 借鉴操作系统的虚拟内存:把 KV-Cache 切成固定大小的 page,按需分配、非连续存放,用完即回收。碎片没了,同一块显存能塞下的并发请求翻几倍。
# vLLM 起服务时最常调的两个参数(OpenAI 兼容接口)
$ vllm serve Qwen3-8B \
--gpu-memory-utilization 0.9 \ # 显存利用率上限
--max-model-len 131072 \ # 单请求最大上下文
--enable-prefix-caching # 开启前缀缓存:相同 system prompt 只算一次
配合 prefix caching(提示词前缀缓存):同一段 system prompt 被大量请求复用,缓存命中后 Prefill 几乎零成本——美团 LongCat-2.0 实测缓存命中时可降低约 88% 的输入成本,就是这个机制。
投机解码:让小模型打前站
显存问题解决后,剩下的瓶颈是 Decode 的"一步一个 token"。投机解码(Speculative Decoding)换个思路:用一个小草稿模型快速连猜 n 个 token,主模型一次算完这 n 步并批量验证——猜对的直接接受,遇到第一个猜错的就回退重来。
# 投机解码示意:草稿先猜,主模型批量验证
draft = load("draft-1B") # 小模型,快
target = load("target-70B") # 大模型,准
while not done:
guesses = draft.generate(k=4) # 草稿连猜 4 个 token
scores = target.verify(guesses) # 主模型一次算 4 步(算力≈算 1 步)
n = first_wrong_index(scores) # 逐位接受,遇到错就停
emit(guesses[:n])
关键点:主模型验证 4 个候选的算力开销≈生成 1 个 token,而草稿模型本身便宜到可以忽略。接受率越高提速越多,理想情况吞吐翻 2-3 倍;OpenAI 2026 年 7 月公开的实测中,改进后的 token 生成效率提升超过 15%,这还是保守数字。工程上要注意:草稿模型质量和目标模型越接近,接受率越高。
工程落地清单
| 层面 | 做法 | 收益 |
|---|---|---|
| 调用侧 | 固定且精简的 system prompt,别把变量塞进开头 | 命中前缀缓存,Prefill 近乎免费 |
| 调用侧 | 长文本一次性传入,避免反复多轮重发 | 减少重复 Prefill 与缓存重建 |
| 服务侧 | vLLM/SGLang + PagedAttention + prefix caching | 高并发 + 缓存复用,吞吐翻倍 |
| 模型侧 | 选 GQA/MLA 架构模型,开启 KV 量化 | KV-Cache 减半到减少九成 |
| 推理侧 | 启用投机解码,草稿模型精调对齐 | Decode 提速 15%~3 倍 |
预算敏感的场景(Agent 高频调用、批量离线任务)先把 prefix caching + KV 量化 打开,这两个改动小、见效最快;追求极致延迟再加投机解码。
KV-Cache 是一场"用显存换速度"的交易 —— 缓存精打细算,推理又快又省;2026 年大模型比拼的不只是模型多大,更是缓存多聪明。