系列走到第 11 篇,KV Cache 这个词已经反复出现:第 2 篇用公式算过它的显存账,第 5 篇的 PagedAttention 为它发明了分页,第 7 篇的 RadixAttention 让它在请求之间共享。三篇都在跟它打交道,却始终没有正面回答:这东西到底是什么,为什么非有不可,凭什么长得这么快。本篇是《本地大模型部署实战》的 KV Cache 专题,把这些问题一次讲透。
KV Cache(键值缓存)是连接"模型结构"与"部署成本"的枢纽概念:往上,它由每层注意力投影的形状决定,属于模型设计;往下,它随上下文与并发线性膨胀,直接决定一张卡能开多长上下文、服务多少请求。全文按"为什么需要 → KV 从哪来 → 公式推导 → GQA 与量化两条省法 → 长上下文显存墙 → 缓存管理机制归位 → 三大家族速查"展开。公式与第 2 篇完全一致,但这次给出推导而非结论。
1、无缓存的思想实验
自回归解码的规则是:一次生成一个 token,第 i 步的新 token 要与前 i-1 个历史 token 全部做注意力——每个历史位置都要被"查"一遍。现在做一个思想实验:不缓存任何中间结果,每一步都把整个前缀重新过一遍全部层。生成 1000 个 token 的前向通过量如下。
生成 1000 个 token,两种策略的前向通过量
无缓存:第 i 步重算前缀全部 i 个 token
第 1 步 1 个 第 500 步 500 个
第 2 步 2 个 第 1000 步 1000 个
总通过量 = 1 + 2 + … + 1000 = 500500 token 次
有缓存:每步只前向 1 个新 token
总通过量 ≈ 1000 token 次
差距 500 倍,且随长度继续拉大(平方增长对线性增长)
解读:无缓存的总通过量是 1+2+…+n = n(n+1)/2,标准的 O(n²);有缓存则每步只算 1 个新 token,总量线性。1000 token 差 500 倍,序列翻倍差距跟着翻倍——这不是快慢问题,是根本跑不动的问题。
更进一步,这份缓存无法被"聪明地绕过"。第 i 步要读每个历史 token 的信息,而这些信息由历史 token 经每层 W_k/W_v 投影产生:要么把投影结果存下来(KV Cache),要么存原始隐状态、每步重算投影——后者每层照样得存一份,显存没省多少,计算一点没省。KV Cache 的本质,是用一份线性增长的显存,换掉平方级的重复计算。
至于查询向量 q 为什么不缓存:它只服务于当前这一个 token,本步注意力算完即弃,后面不会再用到。
2、KV 的产生位置
Transformer 的每个注意力层有三套独立的投影矩阵 W_q、W_k、W_v,输入隐状态 x 分别右乘它们,得到查询、键、值向量。解码阶段单个新 token 经过第 l 层的完整路径如下。
解读:这张图决定了后面公式的形状。第一,k_t 与 v_t 算出即入缓存,此后每步注意力都要读它们——K 是"被查的索引",V 是"被取的内容";第二,q_t 用完即弃,所以公式里没有它的份;第三,每层的 W_k/W_v 互不相同,缓存每层一份、互不共享——"层数"因子由此而来,K 与 V 两份则对应公式里的"2"。
3、显存公式逐项推导
有了第 2 节的结构,公式可以直接数出来。缓存一个 token 在一层:K 有 n_kv 个 KV 头、每头 d_head 维,共 n_kv × d_head 个数;V 同样一份;每层独立,全模型共 L 层;每个数按精度占 b 字节。乘起来就是第 2 篇直接给出的那条公式。
每 token KV 字节 = 2 × 层数 × KV 头数 × 头维 × 精度字节;总占用再乘序列长与并发数。
| 因子 | 含义 | 常见算错 |
|---|---|---|
2 |
K 与 V 各一份 | 只算 K 漏掉 V,结果减半 |
| 层数 | 每层独立缓存一份 | 误以为隔层共享 |
| KV 头数 | GQA 后远小于查询头数 | 误用 num_attention_heads,多算 4 倍 |
| 头维 | 每头的维度 | 拿 hidden_size 除头数去猜 |
| 序列长 | prompt 加已生成部分 | 忘乘并发数 |
| 精度字节 | fp16/bf16 为 2 | 量化后仍按 2 算 |
验算两笔。Llama 3.1 8B:2 × 32 × 8 × 128 × 2 B = 128 KB,32K 上下文 4.3 GB,与第 2 篇表格一致;Qwen3-8B:2 × 36 × 8 × 128 × 2 B = 144 KB,同上下文 4.8 GB。
顺带一个反直觉结论:KV 与参数量没有直接关系,只随层数与 KV 头数走。Qwen2.5-32B 与 Qwen3-32B 相隔一代,因为同为 64 层、8 头×128 维,每 token KV 都是 256 KB;70B 级的 Llama 3.3 也只比它们多 25%。参数翻倍主要翻在 FFN 上,注意力层深而窄——选型时别拿参数量估 KV。
4、GQA 与 KV 头共享
公式里唯一被模型设计"动过手脚"的因子是 KV 头数,三代方案的谱系如下。MHA(Multi-Head Attention,多头注意力)每个查询头配一套 KV 头,n_kv = n_q,Llama 2 7B 的 32×32 就是它;MQA(Multi-Query Attention,多查询注意力)让全部查询头共享 1 个 KV 头,KV 砍到 1/32,但质量损失明显,见 MQA 论文;GQA(Grouped-Query Attention,分组查询注意力)取折中,若干查询头一组共享一个 KV 头,质量接近 MHA 而 KV 省到位,见 GQA 论文。
数字最直观:同为 32 层、128 维头,Llama 2 7B 的 MHA 每 token KV 是 2 × 32 × 32 × 128 × 2 B = 512 KB;Llama 3.1 8B 改成 8 个 KV 头后降到 128 KB。KV 头数从 32 压到 8,每 token 直接省到四分之一,这就是 GQA 一代的省法。Qwen 系同样如此:Qwen2.5-7B 用 4 个 KV 头,14B 起用 8 个;Qwen3 dense 全系 8 个,MoE 版再减到 4 个。
配置怎么查:看模型仓库 config.json 的 num_key_value_heads 字段。Qwen3-8B 的关键字段如下。
{
"hidden_size": 4096,
"num_hidden_layers": 36,
"num_attention_heads": 32,
"num_key_value_heads": 8,
"head_dim": 128
}
解读:num_key_value_heads 是 KV 账的唯一入口,字段缺失按 MHA 处理(等于 num_attention_heads)。head_dim 要认显式值,别拿 hidden_size 除以头数去猜——Qwen3-4B 的 2560 除以 32 得 80,实际 head_dim 是 128,投影矩阵本就不是方阵。不想下载整个仓库,一行命令就能查。
curl -s https://huggingface.co/Qwen/Qwen3-14B/raw/main/config.json \
| grep -E "num_key_value_heads|head_dim|num_hidden_layers"
解读:回显三行就是 14B 的账本原料——40 层、8 个 KV 头、头维 128,这组数第 9 节练习要用。
5、KV Cache 的量化
第 4 节压头数,这一节压公式最后一个因子:精度字节。KV 量化与权重量化正交——权重是常驻成本,KV 随上下文与并发增长,长上下文场景压缩 KV 的收益更高。fp16 的 2 字节压到 fp8 或 int8 的 1 字节,整份 KV 减半。vLLM 的开关如下。
vllm serve Qwen/Qwen3-32B \
--max-model-len 131072 \
--kv-cache-dtype fp8
解读:fp8 档默认 e4m3 编码带缩放,另有 fp8_e5m2 与 int8 可选;不同硬件后端支持不一,改完要做回归验证,细节见 vLLM 量化 KV 文档。llama.cpp 侧对应 --cache-type-k q8_0 --cache-type-v q8_0,第 3 篇的 llama-server 同样适用。
精度影响的定性结论:fp8 档在常规对话、摘要任务上接近无损;长上下文检索、严格指令跟随与数学推理对低精度 KV 更敏感,4bit 档必须自己跑评测再上生产。另一条经验是 K 比 V 耐压,llama.cpp 允许 K、V 分档配置,就是给这个规律留的口子。还有一笔小账:量化块要带缩放元数据,q8_0 每 32 个值额外 2 字节,实际约 1.06 字节每值,省不到精确的一半。
注意:KV 量化换来的不是等比提速,而是同显存下更长的上下文或更多并发——回到第 5 篇的结论,KV 池容量决定服务能力上限。
6、长上下文的显存墙
把公式放到同一模型、不同上下文长度上,显存墙就现形了。以 32B 级 GQA 模型为例(Qwen2.5-32B 与 Qwen3-32B 同为 64 层、8 头×128 维,每 token 256 KB),权重取 Q4 的 18.9 GB,账目如下。
| 上下文 | KV 实占 | 权重 Q4 | 合计(含 2 GB 缓冲) |
|---|---|---|---|
| 4K | 1.1 GB | 18.9 GB | 22 GB |
| 32K | 8.6 GB | 18.9 GB | 29.5 GB |
| 128K | 34.4 GB | 18.9 GB | 55 GB |
权重一列纹丝不动:从 4K 到 128K 多出的 33 GB 全部是 KV。长上下文的边际成本几乎全部落在 KV,而不是权重——权重装不装得下与上下文无关,装得下之后能开多长上下文,是 KV 说了算。这正是第 2 篇那张表的推导版。再叠加并发:4 路会话各自 128K,KV 就是 137 GB,单卡物理无解。
架构自己也在回应这堵墙。Llama 4 Scout 的 48 层按 3:1 交织:36 个滑窗层只缓存 8192 窗口内的 KV,12 个全局层才全量缓存——128K 上下文下 KV 约 7.7 GB,全部按全量算是 25.8 GB。滑窗把"随长度线性涨"掰弯成"涨到窗口为止",代价是远距离注意力只由那 12 层全局层承担。这类混合设计已是 2026 年长上下文模型的主流路线,第 8 节逐家对账。
注意:
--max-model-len不是免费参数。从 32K 调到 128K 之前,先用本篇公式算出 KV 增量,再决定是加显存、砍并发,还是开 KV 量化。
7、缓存管理机制归位
公式在手,可以把前几篇的机制收拢归位:第 5 篇的 PagedAttention 与第 7 篇的 RadixAttention,管理的都不是"KV 要多大",而是"这份显存怎么分配、怎么复用"。三个正交维度如下。
KV Cache 显存账的三个正交问题
多大? 每 token 字节 × token 数
每 token 字节:层数与 KV 头数(第 3、4 节)+ 精度(第 5 节)
token 数:上下文 × 并发(第 2、6 节的账)
怎么装? 第 5 篇 PagedAttention:KV 切块、块表映射、按需分配
怎么省? 第 7 篇 RadixAttention:前缀入基数树,跨请求共享同一份 KV
解读:公式回答"要多大"——头数、精度、上下文、并发四个旋钮都在你手里;引擎回答"装得下吗、要不要重复装"——分页把碎片浪费从六成压到 4% 以内,基数树让多会话共享同一份前缀。第 5 篇算过的"同显存并发翻两番",靠的正是 KV 总量公式没变、利用率从 25% 提到 96%。缩小分子与提高利用率,攻的是同一面墙,互不替代。
8、三大家族 KV 速查
最后给一张工程速查表:三个主流家族 2026 年 10 月的在售主力,fp16 KV、32K 上下文、单并发口径,数字均可用第 3 节公式复算。
| 模型 | 注意力方案 | 层数 | 每 token | 32K 实占 |
|---|---|---|---|---|
| Qwen3-8B | GQA,8×128 | 36 | 144 KB | 4.8 GB |
| Qwen3-32B | GQA,8×128 | 64 | 256 KB | 8.6 GB |
| Llama 4 Scout | GQA+滑窗,8×128 | 48 | 192 KB | 2.8 GB |
| DeepSeek-V3/R1 | MLA,潜向量 576 维 | 61 | 69 KB | 2.3 GB |
两个脚注。Llama 4 行的 192 KB 是 48 层全展开口径,滑窗生效后 32K 实占约 2.8 GB(第 6 节同一笔账)。DeepSeek 行的 MLA(Multi-head Latent Attention,多头潜在注意力)把每层 KV 压成一个 576 维潜向量(512 维压缩加 64 维 RoPE),61 层合计每 token 约 69 KB,是同参数量级里最省的方案,配置见 DeepSeek-V3 仓库。
V4 代把这条路走得更远。2026 年 4 月发布的 DeepSeek-V4(285B 的 Flash 与 1.6T 的 Pro)在 61 层 V3.2 式堆栈上混用两种压缩注意力:c4a 层把 8 个 token 的 KV 加权合并成一份,约压到 1/4;c128a 层压到 1/128;再配 128 窗口的滑注意力保局部精度。vLLM 团队的测算:bf16 KV 下 1M 上下文单序列仅 9.62 GiB,比 V3.2 口径的估计小 8.7 倍。KV 设计从"部署时再算的账"升级成了模型结构的一等公民——这也是读 config.json 的能力越来越值钱的原因。
9、总结
回顾全文链条:
- 自回归每步要查全部历史,不缓存则前向通过量按 O(n²) 膨胀
- K/V 由每层 W_k/W_v 投影产生,每层独立、K 与 V 各一份,Q 用完即弃
- 公式:2 × 层数 × KV 头数 × 头维 × 序列长 × 精度字节,与参数量无关
- 省 KV 两条路:GQA 压头数(32 到 8 即省四分之三),量化压精度(fp8 减半)
- 长上下文的显存增量全在 KV;分页与基数树改的是利用率,不改总量公式
留三道练习:
- 练习一:用第 4 节的 curl 拉 Qwen3-14B 的配置,手算每 token KV 与 32K 实占(答案:40 层、8×128,每 token 160 KB,32K 约 5.4 GB)
- 练习二:给第 2 篇
vram_calc.py的PRESETS加一行dict(p=14, layers=40, kv=8, dim=128),跑budget与手算互验 - 练习三:把你在用模型的
--max-model-len提到 128K,先算 KV 增量,再决定砍并发、开 fp8 还是加卡
对于正在规划部署的读者,建议把第 4 节的 curl 命令加进选型清单:拿到任何新模型,先读 num_key_value_heads 与 head_dim,十秒算出它的 KV 脾气——这比看宣传页上的上下文长度参数可靠得多。

