
上个月帮朋友排查一个长上下文任务8B模型加载得好好的跑几千字对话却突然OOM。我第一反应是权重和激活值算错了翻来覆去找原因最后发现根因就在LLM推理时那个最容易被低估的变量单token的KV cache。很多做推理部署的人算显存时会把权重、优化器状态、激活值都列一遍却容易在KV cache这一项上拍脑袋。实际上当上下文长度从2K涨到128KKV cache的体量会从“可忽略”变成“比权重还要吃显存”而且不同模型之间单token KV cache的差距能到近十倍。这篇文章就把主流LLM的单token KV cache容量按公开配置完整算一遍解释这个数值是怎么来的、为什么有的模型特别省、以及它是如何决定你的长上下文能跑多长、batch能开多大。适合正在做大模型推理部署、做长文本Agent或者被“为什么A卡能跑、B卡跑不了”折磨过的人。1. 一个数字定生死为什么单token KV cache决定长上下文成本1.1 从“一条消息的上下文”说起权重只有一份KV cache按token翻倍模型加载进显存时权重是一次性占用的固定空间跟你聊天长短无关。真正随着对话长度线性增长的是KV cache。意思很直接每多生成一个token模型都要在每一层Transformer里记住这个token的Key和Value供后面所有token做注意力计算。权重是“房租”房东不管你住几个人每月固定收这么多KV cache是“水电费”家里每多一个人账单就往上加。短对话时水电费无所谓但当你连续跑几万字甚至塞进128K上下文时KV cache会急速膨胀。你会发现明明模型权重只有16GBKV cache却能吃掉二三十GB。这也是为什么我强烈建议任何涉及长上下文的推理任务动手部署前先把单token KV cache算出来。不算这个数你连“我这台机器到底能跑多长的输入”都没有一个清晰的预期OOM几乎是可以预见的。1.2 那些“突然爆显存”的时刻前置计算为什么重要举个实际例子。一台80GB显存的A100此处指常见的高显存加速卡加载8B模型的FP16权重大约要16GB左右看起来非常宽裕。这时如果接到一个128K上下文的任务输入文档内容很多KV cache会怎样先按后文会讲的公式估算Llama-3-8B这类结构FP16下单token KV cache大约128KB。128K token就是128 × 1024 × 128KB等于16GB。此时16GB权重加16GB KV cache再算上prefill阶段的激活值、临时buffer、CUDA context80GB看起来还能勉强塞下。但如果你想在服务端把batch加大到4或者8显存立刻就不够了。这里的关键是KV cache是按“token × batch”双份线性增长的。权重只是固定的一个基数KV cache才是长上下文和并发场景下真正的显存黑洞。你多开一路并发就等于多复制一份整个上下文的KV cache这在经济学上比多加载一份模型权重要昂贵得多。2. 一个token占多少字节KV cache计算方式与常见易错点2.1 K和V是什么注意力机制为什么要缓存它们要理解KV cache的容量先得理解它到底缓存了什么。Transformer解码时每生成一个token会对当前token计算Query同时对历史所有token计算Key和Value。Attention的公式是 softmax(Q·K^T / sqrt(d))·V当前token的Query要去“查询”历史所有token的Key然后再用这些权重去“加权”历史所有token的Value。如果每步都重新计算历史token的Key和Value计算量会随着序列长度平方级增长非常可怕。所以工程上采用KV cache已经生成过的token它们的K和V矩阵被保存在显存里下一步生成时直接复用。Q只属于当前token用完即弃不需要缓存K和V属于所有历史token必须持久化。所以KV cache的容量本质上是“每个历史token在所有层里要保存多少K和V”。这个数值由模型架构的隐藏维度、注意力头划分方式、层数和保存精度共同决定。2.2 标准公式与逐层拆分单token KV cache容量可以写成单token KV cache字节数 层数 × 2 × KV头数 × 每个头的维度 × 精度字节数拆开看层数每一层Transformer都有独立的注意力和独立的KV cache必须逐层累加乘2每个token既要缓存Key也要缓存ValueKV头数乘以每个头的维度这部分就是“每个token在单层单份K或V里占用的元素个数”精度字节数FP16/BF16是2字节FP8或INT8是1字节如果做4bit量化则是0.5字节左右。一个很容易踩的错是很多人只算单层的KV cache忘了乘层数结果差一个数量级。还有人把“总隐藏维度”当成“KV维度”在多组查询注意力GQA架构下会明显高估因为GQA的关键就是让多个Query头共享少数几个KV头。2.3 MHA/GQA/MQA/MLAKV头数决定命运的架构差异传统多头注意力MHA里Query、Key、Value的头数完全一样KV cache最大。而GQA分组查询注意力让多个Query头共享一组KV头比如Llama-3-8B有32个Query头但只有8个KV头KV cache直接缩小为MHA的1/4。MQA更进一步所有Query头共享1个KV头KV cache最小但表达能力往往有损。MLA则是DeepSeek在V2/V3里用的低秩压缩方案不是简单减少KV头数而是把K和V压缩到一个低维向量后期再解耦容量比GQA还小。架构选择直接决定了单token KV cache的数量级。2.4 手算一个真实模型Llama-3-8B以Llama-3-8B公开的HuggingFace配置为例层数32层隐藏维度4096注意力头32个Query头KV头8个GQA每个头的维度128保存精度FP16每个元素2字节代入公式KV cache 32 × 2 × 8 × 128 × 2 131072 字节 128KB/token其中每层的KV cache是4096字节也就是4KB32层叠加后就是128KB。这个数字在开源8B模型里属于中等偏上因为它用了8个KV头。后面你会看到KV头更少的模型能把同样的8B规模做到56KB/token差距超过一倍。3. 主流模型单token KV cache横向估算一张表看清差距3.1 估算口径说明以下估算基于各模型公开的HuggingFace配置和论文统一使用FP16/BF16精度得到的数值是“理论最小值”因为实际推理框架还可能增加少量元数据。KV cache量化、GQA/MQA、MLA等差异都会单独说明。请注意不同版本和不同开源仓库可能存在细节差异部署时以实际加载模型后的显存观测为准。3.2 主要开源模型估算表模型层数隐藏维度KV头数头维度注意力类型单token KV cacheFP16Llama-2-7B32409632128MHA512KBLlama-3-8B3240968128GQA128KBLlama-3-70B8081928128GQA320KBMistral-7B3240968128GQA128KBMixtral-8x7B3240968128GQA128KBQwen2.5-7B2835844128GQA56KBQwen2.5-14B4851208128GQA192KBQwen2.5-72B8081928128GQA320KBDeepSeek-V3/R1617168MLA压缩维度512—MLA约61KB级理论主体3.3 解读为什么同是8BKV cache能差一倍横向看最明显的是Llama-2-7B和Llama-2-7B之外的模型。Llama-2-7B用的是标准MHA32个KV头全量缓存单token就要512KB非常夸张。这也是为什么Llama-2在长上下文场景下很快碰壁4K上下文就要2GB KV cache服务端batch稍微一大就爆显存。技术演进到Llama-3时8B模型改成GQAKV头从32降到8单token KV cache立刻从512KB降到128KB直接缩到1/4。Qwen2.5-7B更激进KV头压到4个同时层数只有28层所以单token KV cache只有56KB。这意味着同样的长上下文任务用Qwen2.5-7B要比Llama-3-8B省一大半显存。很多人只看总参数量相近就以为显存开销差不多实际上在长上下文场景里KV cache的差异才是决定性的。MoE模型在这张表里的表现也有意思。Mixtral-8x7B虽然总参数量很高但它的注意力部分和Mistral-7B几乎一样都是GQA加8个KV头所以单token KV cache也是128KB。MoE改变的是FFN层对KV cache没有直接影响。这个结论可以用一句话概括决定单token KV cache的不是总参数量而是层数、KV头数和头维度。3.4 闭源模型的推测方向GPT系列、Claude系列、Gemini系列并没有公开完整的架构参数没法像开源模型一样精确计算。但从它们公布的超大上下文长度反推这些模型一定在注意力机制上做了大量压缩工作否则单token KV cache会直接击穿显存预算。 比如一个参数量在百B级别的模型假设用传统MHA单token KV cache轻松上MB级跑1M上下文就得1TB以上的显存这明显不现实。所以长上下文闭源模型几乎都使用了GQA、MQA、MLA或某种低秩缓存压缩这是工程上的必选项而不是可选项。4. 从单token到整段上下文KV cache如何一步步吃光显存4.1 扩容公式token乘以batch才是真正的显存账单token KV cache再小也要和序列长度、并发batch相乘总KV cache 单token KV cache × 序列长度 × batch大小这个公式看起来简单但很多人算的时候会把“序列长度”和“batch大小”忽略掉。长上下文不是简单的“显存吃紧一点”而是从单token的KB级直接跃升到GB级。4.2 实战计算8B模型的不同上下文和batch组合用一个单token KV cache为128KB的8B模型比如Llama-3-8B来计算上下文长度batch1batch8batch322K256MB2GB8GB32K4GB32GB128GB128K16GB128GB512GB128K上下文、batch1时KV cache是16GB已经接近甚至超过模型权重本身。batch8时KV cache高达128GB单卡完全装不下必须依赖多卡或优化手段。再看Qwen2.5-7B单token KV cache为56KB相同场景下的KV cache是上下文长度batch1batch8batch322K112MB896MB3.5GB32K1.75GB14GB56GB128K7GB56GB224GB同样是7B/8B级别的模型在128K上下文中Qwen2.5-7B的KV cache只有Llama-3-8B的不到一半对显存的影响不言而喻。4.3 Prefill和Decode阶段的压力差异推理过程分成两个阶段。Prefill阶段处理输入提示词一次性并行计算所有输入token的注意力这个阶段激活值非常大但持续时间短。Decode阶段逐token生成输出每个token都要读一遍已有KV cache参与计算所以KV cache的随机读写带宽决定了生成速度而KV cache的容量则决定了你能不能继续往序列里塞新token。这两个阶段对显存资源的需求是冲突的。Prefill阶段需要大量临时算力Decode阶段则需要持续稳定的KV cache空间。部署时如果只盯着权重量不考虑用户输入长度和并发数很可能在Decode进行到一半时突然OOM。4.4 反直觉结论上下文足够长时KV cache比权重还大一个很容易被忽略的事实是模型权重一旦加载固定不动而KV cache会随token持续增长。常见的7B模型FP16权重约14GB假设单token KV cache是128KB只要序列达到112KKV cache总量就已经和模型权重一样大了。再往后KV cache就是整个显存账单里的头号项目权重反而退居二线。服务端如果要做长文档问答、海量日志分析、代码库理解这类场景的输入经常是几万字甚至几十万字KV cache的体量会直接决定你需要多少张卡。我见过不少人一开始只按权重规划显存跑到一半发现KV cache不够临时换卡或改batch非常被动。5. 压低单token KV cache的工程手段从GQA到量化再到MLA5.1 GQA/MQA架构层面最有效的“四两拨千斤”GQA带来的收益直接体现在KV头数上。以Llama-3-70B为例如果沿用Llama-2-7B的MHA结构假设它有64个KV头80层Transformer单token KV cache会达到80 × 2 × 64 × 128 × 2 2621440 字节 ≈ 2.5MB而现在的GQA版本只有8个KV头单token是320KB直接缩到1/8。这就是为什么新一代大模型几乎全面转向GQA性价比太高了只要能接受一定程度的注意力表达能力损失KV cache就能成倍压缩。MQA更极端KV头数压到1比如某些早期针对长上下文优化的模型会用MQA。它省显存但在复杂推理任务里往往被诟病效果稍弱。现在的工程共识是GQA为主流MQA只在超长上下文的特定场景里出现。5.2 KV cache量化FP8/INT8把字节直接减半架构层面的省法比较“硬”但如果你已经选定了一个模型还能从数据精度上做文章。KV cache默认FP16是2字节一个元素转成FP8或INT8就变成1字节单token KV cache直接减半效果立竿见影。不过KV cache量化不是无脑做的。我实际测试下来的心得体会是Key和Value对量化的敏感度不同。Value的数值范围变化往往更剧烈有时候只量化K不量化V也能在精度损失很小的前提下省下约1/4的显存。要留意outlier。KV cache里某些维度的数值特别大直接用per-tensor量化容易炸per-channel或者per-head量化会更稳。量化后的精度损失和任务强相关。短文本任务几乎看不出来长文本、复杂推理、表格理解这些任务损失会被放大上线前一定要做评测对比。5.3 MLA低秩压缩DeepSeek把单token打到几十KB如果把GQA看作“减少KV头数”MLA则是“重新定义KV缓存”。DeepSeek-V3/R1里的MLA不是简单缓存完整的K和V而是把K和V压缩到低维潜在向量里。这个潜在向量的维度通常只有512维左右远小于原始KV展开后“KV头数 × 头维度”的体量。理论上的效果是GPT级别甚至更大的模型单token KV cache被压到每层1KB左右的量级整模型61层合计约60KB量级。和同为百B级的Llama-3-70B的320KB比压缩比接近5倍。这也是DeepSeek能以相对可控的显存成本支持128K上下文的关键工程原因。MLA不是简单的低精度量化而是在模型架构层面改变了“到底缓存什么”所以它带来的收益和GQA、FP8是可以叠加的。5.4 服务端的显存管理优化不等于缩小单token容量很多人会把vLLM里的PagedAttention、“显存复用”这些概念和上面几种手段混在一起其实要分清。PagedAttention之类解决的是KV cache的“碎片化”问题它把KV cache按页管理让有限的显存空间被利用得更充分能提升吞吐但并没有改变每个token需要占用的字节数。换句话说它是在一个既定容量里提高利用率跟“把单token从128KB降到56KB”是两回事。真正的容量优化只有三条路改架构GQA、MQA、MLA、降精度FP8、INT8、INT4、以及减少实际需要缓存的历史token比如滑动窗口注意力、上下文裁剪。部署层面能做的优化是让已有容量发挥更大价值却不能让物理容量凭空变小。5.5 选型建议我建议怎么根据容量规划做判断我自己的习惯是拿到一个模型先做两件事第一翻配置里num_key_value_heads和num_hidden_layers、head_dim几个字段手动算一遍单token KV cache。不要等部署后看显存报表那个太晚。第二用下面这种简短的脚本快速估算不同方案def kv_cache_bytes_per_token(num_layers, num_kv_heads, head_dim, dtype_bytes2): return num_layers * 2 * num_kv_heads * head_dim * dtype_bytes models { Llama-3-8B: kv_cache_bytes_per_token(32, 8, 128, 2) / 1024, Qwen2.5-7B: kv_cache_bytes_per_token(28, 4, 128, 2) / 1024, Llama-3-70B: kv_cache_bytes_per_token(80, 8, 128, 2) / 1024, } for name, kb in models.items(): print(f{name}: 单token KV cache ≈ {kb:.1f} KB)跑出来的结果能直接指导决策你是打算做32K还是128K上下文batch是想开到4还是16目标卡上还有多少余量这些都能在几分钟内估算清楚而不是等部署后反复试错。我在实际处理长文本项目中会先按这个公式给KV cache打底再考虑权重和激活值最后才看框架本身的开销。排查OOM时第一件事不是调框架参数而是拉出日志看KV cache实际分配了多大。这个看似简单的预判确实帮我避开了很多“显存不够”的无效加班。