从模型配置估算 KV 缓存显存开销
文章通过层数、KV 头数、头维度和数据精度计算每个 token 的缓存占用,对比 GQA、MLA 与不同缓存精度的影响。示例展示长上下文如何放大显存需求,但这些结果是配置推算,并非实测性能。
AI 深度解读
模型权重能装进显卡,不代表它能接住长上下文请求。原文把推理成本拆到一个经常被忽略的项目:KV cache。作者的主张是,评估长上下文服务之前,先从 config.json 算清每个 token 要占多少缓存,再讨论 GPU 和优化。
这篇的价值是提供了一套能复算的容量估算方法。不过,作者给的是理论计算,没有实际测量;缓存大小、显存流量和最终账单之间,也不能直接画等号。
长上下文有多贵,先看每个 token 留下多少缓存
自回归模型每生成一个 token,都要利用前文的信息。KV cache 保存各层注意力计算中的 Key 和 Value,让模型不用重新计算所有历史 token 的这些表示。
省下重复计算的同时,历史缓存仍要占显存,并参与后续读取。作者因此认为,在 batch one,也就是一次只处理一条序列时,仅盯 FLOPs 和 GPU-hours,容易漏掉内存访问的成本。
普通多头注意力(MHA)和分组查询注意力(GQA)的缓存,可以按下面的公式估算:
bytes_per_token = layers × kv_heads × head_dim × 2 (K and V) × bytes_per_element
关键看 kv_heads:查询头有多少,不等于必须保存多少组独立的 K、V。
原文举例,将 KV heads 从 64 减到 8,缓存就缩小到原来的八分之一。作者还称,多头潜在注意力(MLA)通过保存压缩表示,在其比较中进一步缩小约 3.7 倍。因此,拥有 671B 总参数的 DeepSeek-V3,每 token 缓存可以小于 Qwen3-32B。
参数量不能单独决定 KV cache 的大小。
不过,提供的原文缺少作者提到的三款配置对照表,也没有完整列出配置字段。8× 的算术关系清楚,3.7× 的具体比较口径则无法仅凭现有正文完整核验。
「32B 能装进 H100」为什么还不够
原文用一笔显存账说明问题:32B 参数按 BF16 保存,权重约占 64 GB;一张 80 GB 的 H100,粗算剩下 16 GB。但同一个模型的一条 128k-token 序列,按作者口径需要约 34 GB 的 KV cache。
权重放得下,权重加缓存就放不下。
这里的数字属于容量估算,GB 与脚本中的 GiB 也要区分。实际部署还要给运行时、临时张量和其他开销留空间,不能把权重之外的余额全部分给缓存。
作者提供的脚本,可以对本地 config.json 估算不同上下文长度的缓存:
import json, sys
cfg = json.load(open(sys.argv[1]))
layers = cfg["num_hidden_layers"]
heads = cfg.get("num_key_value_heads") or cfg["num_attention_heads"]
dim = cfg.get("head_dim") or cfg["hidden_size"] // cfg["num_attention_heads"]
if "kv_lora_rank" in cfg: # MLA(DeepSeek 风格)
per_tok = layers * (cfg["kv_lora_rank"] + cfg.get("qk_rope_head_dim", 0)) * 2
else: # MHA / GQA
per_tok = layers * heads * dim * 2 * 2
for ctx in (32_768, 131_072, 1_048_576):
print(f"{ctx:>9,} tokens -> {per_tok*ctx/2**30:8.1f} GiB")
要看清两处分支最后的 2:这里按每个元素 2 字节计算,没有自动读取实际缓存精度。MLA 分支还依赖服务引擎确实采用相应的潜在缓存表示,不能仅靠模型配置保证部署结果。
作者限定的条件是 batch one、无前缀复用、无分页的理论稳态缓存。真实引擎还有分配器开销和 KV block 取整,实测占用会更高。这段代码适合初筛容量,不能直接充当通用计费器。
Agent 为什么更容易把预算吃光
普通聊天也会积累历史,但 Agent 往往增长得更快:工具返回、文件内容、失败尝试都被追加进上下文。
缓存随历史长度增长,每轮又要处理越来越长的历史。作者据此提出,任务总成本可能随循环长度近似平方增长。这个判断有条件:每轮新增内容大致稳定,历史持续保留,而且没有有效复用或裁剪。不能把它当作所有 Agent 的统一成本公式。
原文给出的优化方向有四类:
- KV cache 从 FP16 换成 FP8,理论存储减半;FP4 则被作者列为研究方向。
- 稳定的 system prompt 和工具 schema 使用 prefix caching,复用前缀计算。
- 清理旧工具输出,或用摘要替代原文,直接缩短后续上下文。
- 选模型时比较 GQA、MLA 等架构及缓存字段,不只比较参数量。
这些方法作用不同。前缀缓存省掉的是可复用部分的重复计算,并不自动消除生成阶段读取历史 KV 的成本;摘要和驱逐会减少信息,可能影响任务质量。
我的判断
我认同作者把缓存预算提前到模型选型阶段。长上下文部署只检查权重大小,确实少算了一项会持续增长的显存开销。
但「KV cache 就是推理账单」说得太满。缓存容量决定能装多少历史,内存流量影响生成速度,实际账单还受权重读取、输入处理、批处理方式和服务利用率影响。作者没有提供实测数据,也没有对照实验,无法据此断言所有场景里缓存优化都比 kernel fusion 更划算。
原文的优化排序也有前提。FP8 的 2 倍收益、GQA 的 8 倍差异,以及不同架构之间的 3.7 倍比较,基线各不相同,不能直接排成统一收益榜。质量损失、引擎支持和改造成本同样需要验证。
把这篇当作容量检查方法很有用,把它当作完整成本模型就不够了。
能拿来做什么
- 自部署模型前,先用实际
config.json计算目标上下文的 KV cache,再把权重和运行开销加进去,检查承诺的上下文长度能否兑现。 - 给日常 AI 编程 Agent 检查历史增长,优先处理反复追加的文件全文、冗长工具输出和失败日志,避免每轮机械保留全部内容。
- 使用支持前缀缓存的服务时,保持 system prompt 和工具 schema 稳定,并通过服务提供的命中信息验证是否发生复用。
- 尝试 FP8 cache 时,同时比较显存占用、延迟和任务完成质量。仅靠这份脚本,判断不了最终是否更省钱。