KV Cache优化全解析:从GQA/MLA到PagedAttention与KV量化 📅 发布时间:2026/9/8 17:52:37 👁 浏览次数: 做LLM服务化部署的人几乎都会遇到同一个怪现象一个小模型本身没多大可一旦并发和上下文长度上来GPU显存就像漏了一样。我自己最早部署7B模型时也踩过这个坑。FP16权重大概14GB结果输入输出写长一点加上几路并发显存一下子就飙到20多GB。多出来的这部分开销几乎全是KV Cache。KV Cache可以说是LLM推理优化里最核心的一环。只要你在用vLLM、SGLang、TensorRT-LLM这类推理框架或者只是自己在研究大模型推理性能最终都会回到一个问题上怎么把KV Cache的显存压下去。这篇文章就把KV Cache的来龙去脉以及目前主流的内存优化方法——GQA/MQA、MLA、PagedAttention、KV量化、滑窗注意力、Prefix Cache——串起来讲一遍重点说清楚它们各自解决什么问题实际落地时怎么选。1. KV Cache 到底存了什么1.1 自回归生成里的重复计算主流LLM都是decoder-only架构生成时走的是自回归流程给定prompt模型先整体算一遍生成下一个token然后把这个token拼回原始序列再预测下一个token。问题在于attention层里每个位置都要和之前所有位置做交互。如果不做缓存的朴素实现那么在生成第t1个token时模型会重新把前t个位置从头到尾完整算一遍重新得到它们的K和V再计算注意力分数。这相当于每一次解码都要重复之前算过的全部attention计算复杂度随序列长度呈平方级上升GPU利用率会掉到没法看。KV Cache的本质就是拿显存换算力把前t个位置已经算好的K和V矩阵缓存下来下一次解码直接取出来用只增量计算新token的K和V。这样做之后decode阶段每一步的计算量只跟单token相关虽然仍是串行生成但整体吞吐比朴素实现高出一个数量级。这里有个值得新手注意的点KV Cache并不是一个可选开关而是自回归解码的必然产物。只要你想避免重复计算就必须把历史K/V留给后面的解码步用。差别只在于你用什么数据结构存、存成什么样、能复用多少。1.2 KV Cache 体积怎么算一次推理需要缓存多少有个很直白的公式KV Cache Bytes 2 × 层数 × 头数 × 每头维度 × 当前序列长度 × Batch Size × 每元素字节数这里的2代表K和V两个矩阵。以常见的7B模型为例32层、32个注意力头、每头128维、FP16存储每个token产生的KV Cache大约是32层 × 32头 × 128维 × 2矩阵 × 2字节 512KB。也就是说一个token就要吃掉512KB显存。如果上下文长度是4096单条请求就是2GB8条并发就是16GB。而7B模型权重在FP16下也不过14GB左右。KV Cache一多直接反超权重成为显存天花板。扩展一下上下文长度单条请求KV8条并发KV20481GB8GB40962GB16GB81924GB32GB3276816GB128GB所以显存里的变量其实有两个一个是模型权重数量固定另一个是KV Cache会随着序列长度、并发数、量化位宽不停变化。这也是为什么部署大上下文模型时经常出现权重明明放得下并发一上来直接OOM的原因。1.3 为什么KV Cache会成为推理吞吐瓶颈推理过程可以分成两个阶段。prefill阶段把prompt一次性并行计算这个阶段主要耗在计算上KV Cache在建好后就已经完整落入显存decode阶段则是每步只生成一个token新增的KV少但每一步都要从显存里把历史KV读出来做注意力计算。随着序列加长每个新token需要读出的KV字节数也线性增长模型就会卡在显存带宽上GPU算力再高也吃不满。这就是为什么很多长上下文场景里decode速度明显变慢而不是因为模型算力不够。很多时候我们看显存只看到CUDA进程占用没有拆解它的构成。一次完整的推理显存大致是权重 激活 KV Cache 临时buffer。对于长上下文和并发高的场景KV Cache往往是最大项。KV Cache优化本质上有两个方向一是从结构上减少需要缓存的字节数二是从分配和调度上提高显存利用效率。讲完这些基础概念下面几种主流方法就都好理解了。2. 从模型结构上让它变小MQA/GQA/MLA2.1 MHA 的存储瓶颈在哪标准Transformer用的是MHAMulti-Head Attention每条注意力查询由多个头并行计算每个头有自己独立的K/V投影。多头设计给了模型很强的表达能力但代价是每个token、每一层都要为每个头单独存一份K、V。这就是1.2节那个512KB/token的主要来源。MHA是大多数经典LLM的默认结构比如早期GPT系列、很多开源小模型都用它。它的计算特点是灵活但内存特性不好。我们经常看到“同样的模型为什么新版本显存省这么多”的对比本质往往是新版本把MHA换成了GQA或MLA而不是权重变小了。2.2 MQA让所有头共享一组KVMQAMulti-Query Attention的思路非常激进所有注意力头共用一个K投影和一个V投影。这项技术最早可以追溯到2019年Shazeer的工作当时的出发点就是降低机器翻译场景里的显存和带宽压力。后来不少对吞吐要求极高的在线服务模型也沿用了这个设计。原本每个头单独存一份KV现在整个层只存一份KV Cache体积直接缩小到头数分之一。以32头的7B模型为例每token的KV从512KB降到16KB40万个token的KV也才一个多GB内存压力骤减解码带宽需求也随之下降。但MQA不是没有代价。所有头共享同一份KV意味着模型对上下文信息的拆解方式被限制住了在需要细粒度关注多样内容的场景下精度会有损失。因此MQA更多出现在对吞吐要求极高、对质量不是最敏感的任务里比如一部分机器翻译、对话场景以及早期的PaLM等模型。2.3 GQA分组共享实际落地最广MHA太费内存MQA又太省表达GQAGrouped-Query Attention取了一个中间值把注意力头分成若干组每组内部共享一份K/V。组数设成1退化成MQA组数等于头数退化成MHA。实际最常用的是8组、4组这类配置。很多近两年的主流模型都采用了GQA。Llama 2 70B、Llama 3系列都默认使用GQA常见KV组数是8。以32个Q头为例KV Cache能降到原来的8/32也就是四分之一。表达能力损失比MQA小得多显存收益又很明显所以GQA几乎成了新一代模型的默认选择。为什么8组这么常见因为8这个数字在表达能力和存储之间平衡得比较好。组太少KV Cache省得多但对上下文表达的细粒度会下降组太多省得有限结构优势就体现不出来。在Llama这个规模上8组基本能保持接近MHA的效果。这里需要提醒一点GQA/MQA是模型结构层面的事不是推理参数。你要是拿到一个MHA模型想通过配置把它变成GQA是不行的。不过这也不是完全不能改GQA论文里提过一种做法从MHA checkpoint出发把同一组Q头对应的K/V投影取平均得到一个GQA初始化再做短期的继续训练。这个叫uptraining属于改结构的范畴不是部署阶段能简单做到的。所以做推理选型时直接看模型config里的num_key_value_heads字段这个数越小KV Cache越省。下面做个直观对比方案每层KV组数相对MHA存储表达能力特点典型代表MHAh1倍最强内存最费早期GPT、不少开源小模型MQA11/h吞吐优先精度略损PaLM、部分翻译模型GQAgg/h接近MHA内存省Llama 2/3、Qwen2等MLA低秩压缩明显缩小接近MHADeepSeek-V2/V32.4 MLA把KV再压进低秩空间MLAMulti-head Latent Attention是DeepSeek-V2开始使用的方法。它的核心思路是不再把每个头完整的K/V直接存下来而是先用低秩矩阵把KV压缩成一个小得多的latent向量推理时缓存这个压缩后的向量等计算注意力时再通过升维投影把K/V还原出来。这个手段和量化完全不同。量化是把已有数值用更低精度表示MLA是从数学结构上减少了要存的信息量。以DeepSeek-V2为例每层每token的KV呈现为一个较低维度的latent表示相比传统MHA要存h×d的完整K/V矩阵存储压力明显小一个量级长上下文场景尤其受益。MLA还有一个容易被忽略的设计点它一般会额外保留一个低维的RoPE位置编码头把位置信息从压缩KV里分离出来。否则低秩表示容易削弱模型对位置的感知长距离依赖会受影响。这也是为什么MLA模型在配置上和传统MHA不太一样。从部署角度说MLA模型的KV Cache天然占比更低同显存能跑的并发更多。如果你正在选新模型同等参数规模和精度的前提下优先考虑GQA或MLA结构的型号。它们在KV Cache上的先天优势会直接转化成部署成本优势。3. 从内存分配与精度上优化PagedAttention 与 KV 量化3.1 PagedAttention 解决什么问题结构优化决定了每个token的KV有多大但显存怎么用、能不能用满是另一个问题。早期推理框架会为每条请求申请连续显存而且通常按最大序列长度预留。比如配置支持4096长度就算某条请求实际只生成200个token预留空间也按4096算。并发一多预留和实际使用的差距就非常可观显存碎片也很严重。PagedAttention是vLLM论文提出的方案思路和操作系统的虚拟内存几乎一模一样把KV Cache切成固定大小的块比如每块16个token再用一张block table记录每个逻辑块落在显存的哪个物理位置。需要多少块就分配多少块不要求物理连续。这样显存利用率能接近100%碎片也被摊平了。block size是PagedAttention里的一个核心参数。太小管理开销变大太大内部碎片又浪费多。vLLM默认块大小是16个token多数场景下够用。如果你发现并发很高但大部分请求偏短可以适当把block size调小一点反过来超长上下文为主的场景可以调大。3.2 实际收益与框架采纳这个机制落地后最直观的变化就是吞吐。vLLM相比当时非分页方案在同样的显存和模型下吞吐能提升数倍。原因不是单次计算变快了而是同等显存条件下能塞进的并发请求显著增多。后来这种设计也被其他框架吸收今天你看到的TensorRT-LLM、SGLang里的paged KV cache基本是同一个思想。这也解释了为什么vLLM部署时--gpu-memory-utilization不建议设成1.0。KV Cache是动态分配的一旦显存不够解码就可能OOM或者调度器会把新请求排队。经验值是给权重、激活和KV Cache整体预留10%上下的余量0.85到0.95之间比较常见具体取决于模型规模、上下文长度和并发预期。3.3 KV Cache 量化的基本思路再往下压就是精度维度。KV Cache量化就是把K、V矩阵从FP16存成更低精度的格式比如FP8、INT8甚至INT4。以FP8为例单元素从2字节变成1字节KV Cache体积直接减半INT8同理。这个方法简单有效但对量化粒度和outlier要格外小心。这里说的outlier是LLM里一个绕不开的现象注意力机制的少数维度会出现极大数值如果不单独处理统一缩放之后那些小数值会被压缩得几乎没有分辨率。大规模实验还会发现K和V对量化的敏感度不一样。K矩阵往往存在明显的通道级outlier少数channel数值特别大如果按整块统一scale误差会被放大V矩阵的分布相对均匀更适合细粒度量化。所以主流做法是混合粒度对K做per-head加per-channel量化对V做per-token加per-channel量化。更精细的方法还会专门处理outlier channel或按少量校准数据为每层选量化参数。下面把常见粒度整理一下量化粒度scale计算范围适用位置特点per-tensor整层一个scale不推荐单独用实现最简单outlier影响大per-channel每个输出通道独立scaleK矩阵缓解通道outlier内存开销较低per-token每个token独立scaleV矩阵跟V的分布更契合per-head每个注意力头独立scaleK矩阵增强对异常头/位置的鲁棒性3.4 框架里已经有现成开关好消息是这些方法不用自己实现。vLLM、TensorRT-LLM、SGLang基本都内置了KV Cache量化选项。以vLLM为例一行参数就能开启FP8 KV Cachepython -m vllm.entrypoints.openai.api_server \ --model /models/Qwen2.5-7B-Instruct \ --kv-cache-dtype fp8 \ --max-model-len 32768 \ --gpu-memory-utilization 0.9实测下来FP8或INT8的KV量化在绝大多数任务上损失很小尤其配合GQA结构通常可以无感使用。INT4要看模型和任务做长上下文或者对质量要求高的场景建议先在测试集上验证困惑度和下游指标再上不要因为省显存就盲目把位宽拉满。4. 从复用与上下文管理上优化Prefix Cache 与滑窗注意力4.1 Prefix Cache 解决什么问题真实业务里大量请求共享同一个开头。典型场景是对话机器人的system prompt几千字固定不动的功能定义、few-shot示例轮流出现RAG场景里知识库同一段文档会被不同用户反复查询。每次来一个新请求框架都要重新计算这部分KV。如果能把公共前缀的KV缓存下来后续请求直接复用首token延迟TTFT可以大幅下降。Prefix Cache就是把“已经算过的KV”按前缀路径缓存起来命中时跳过重复prefill。命中率取决于前缀是否完全一致。vLLM里叫Automatic Prefix CachingSGLang里叫RadixAttention底层通常是一棵前缀树一个请求结束后它的KV块不会立刻销毁而是挂在树上供下一个相同前缀的请求复用。4.2 工程上怎么让命中率更高想让Prefix Cache充分发挥作用prompt结构要设计得对缓存友好固定内容尽量放在最前面动态内容放到尾部。比如system prompt保持不变、few-shot固定、用户输入拼在最后。如果每次都把用户当前输入重新拼接在整个模板的前端前缀就会频繁变化缓存基本失效。在RAG知识库这种场景前缀缓存的收益尤其明显。同一份文档被多个用户查询时文档内容部分的KV可以被复用每个新问题只需要计算问题部分和回答部分的KV。命中率好的时候TTFT可以压到原来的几分之一这个优化在超长知识库场景里比单纯增加推理并行度更划算。框架层面vLLM有--enable-prefix-caching这样的开关SGLang的RadixAttention也会默认参与路由和缓存。不过具体到各个版本默认行为可能不同上线前最好确认一下文档别把开关写上去就以为生效了。还需要留意一个坑Prefix Cache匹配的是完全一致的token序列不是语义相似。只要系统提示词里多了一个空格、改了一个词前面那些块的缓存就作废只能从头prefill。所以线上如果想用前缀缓存吃满命中率prompt模板必须严格固定版本间变更也要谨慎处理。4.3 滑窗注意力控制上下文记忆边界另一个方向是限定到底要不要对所有历史token保留KV。滑窗注意力让每个位置只与最近W个token做注意力交互第t个token不会再去看第t-W之前的token。这样在内存管理上可以只保留一个窗口长度的KV旧token的KV块会被新token覆盖。W固定后KV Cache大小就和上下文总长度解耦了不管用户输多长KV都能维持在一个相对恒定的规模。从计算量上看传统注意力每个token要跟整个历史做交互复杂度接近序列长度的平方滑窗注意力把范围限制在W以内复杂度降成跟n×W同一量级这也是它能明显降低KV带宽占用的原因。Mistral 7B的模型结构里就用到了滑窗注意力窗口配置为4096。实际推理时往往配合滚动缓存实现新的token不断写入旧的token KV块被覆盖显存占用保持恒定。代价也很明确窗口外的上下文信息模型完全看不到长程依赖能力被削弱。所以滑窗适合对内存友好、又不太需要超长记忆的场景如果要支持64K上下文并保留全局依赖一般不会只靠滑窗。4.4 这些方案和前面的组合性需要强调一点滑窗通常是模型结构预先决定的不是部署参数。选了带滑窗的模型就要接受它的记忆边界选了长上下文模型就得靠GQA/MLA先把KV压小再用分页和量化提升显存利用率最后用Prefix Cache减少重复计算。每一层解决不同环节的问题这就是为什么现代推理框架会把它们全部集成在一起而不是只挑一个用。5. 工程落地组合式优化与实战参数5.1 不同部署场景怎么组合不同业务的显存瓶颈往往不一样组合策略要跟着场景走。我按常见场景做了个组合参考场景推荐组合理由高并发短对话GQA/MLA PagedAttention Prefix Cache并发多显存利用率优先前缀固定可复用长文档问答GQA/MLA FP8/INT8量化 PagedAttention上下文长KV占大头量化收益显著流式低延迟GQA/MLA Prefix Cache 适度量化减少首包延迟优先decode带宽要控本机显存有限滑窗模型或量化 PagedAttention显存有限接受一定精度取舍实际动手前我建议先做一个“显存快照”。找个压测脚本把并发从1加到8分别记录每档下的KV Cache占用量、每请求首token延迟和吞吐。这样能快速看出瓶颈到底在KV Cache总量还是在运行时调度再决定优先做结构级优化还是部署参数优化。压测时别只看首包要看稳态吞吐和P99延迟。KV Cache在压力测试中往往要到中后段才显示威力因为并发上来了、上下文也累积了。很多问题在低并发下完全看不出来一上生产就暴露。5.2 常见误区与排查技巧部署踩坑的案例我见过太多了把反复出现的问题汇总一下只估权重不看KV。很多新手估显存只算权重结果并发一起来直接爆显存。用7B那个例子就能提前算出KV大概占多少提前预留。max-model-len开得太大。上下文长度设到最大没有错但它会直接影响KV Cache总量。如果业务里大部分请求都用不到那么长建议按90分位长度去配置而不是按极限长度硬扛。量化直接上INT4。KV量化一提就有人直接上INT4。除非任务对精度极其宽容否则先从FP8/INT8起步用测试集验证后再考虑更低精度。并发过高导致排队/拒绝先怀疑模型坏了。经常看到服务明明有显存余量却不断拒掉新请求其实是KV Cache动态分配触顶调度器在保护已有请求。这类问题要看框架指标别急着重启。nvidia-smi看不到KV Cache占用。GPU进程显存是整体的vLLM、SGLang内部会单独管理KV块要用框架自己的指标看比如vLLM的Prometheus接口里就有vllm:num_requests_running、vllm:cache_usage等。做成速查表更直观现象常见原因排查方式显存充足但OOMKV Cache动态分配触顶查看框架cache_usage指标调低并发或max-model-lenTTFT时高时低Prefix Cache命中率不稳检查prompt前缀是否完全一致decode速度变慢KV过大显存带宽不够启用KV量化或换GQA/MLA结构模型并发上不去权重KV激活总和超限用压测脚本做显存快照拆解瓶颈5.3 我再分享几个实操经验第一优化顺序很重要。我个人的习惯是先看模型结构GQA/MLA选型带来的收益最大部署参数只是锦上添花再看调度和分配PagedAttention能释放的显存红利比想象中多最后才上量化因为量化要跟模型精度一起验证成本最高。第二KV量化最好跟着模型精度测试一起做。只测一个指标永远不够比如长文本任务要同时看困惑度、摘要任务ROUGE、问答命中率。量化的收益是显存减半但如果给业务带来1%的精度损失可能比多买一张卡更贵。第三Prefix Cache在高命中率场景下是优先级很高的优化。同样的显存下它不减KV总量但能把大量重复计算直接消掉。对话机器人和RAG这类固定前缀设计良好的业务TTFT能成倍下降这个体验提升用户感知很明显。于我而言KV Cache优化的收益顺序永远是先看模型结构再调分配调度最后才上量化。结构上省下来的内存是乘法级的后面做的所有优化也都会放大这份收益。希望这篇梳理能让你配置推理服务时少些试错多些底牌。