LLM推理优化技术综述:KVCache、PageAttention、FlashAttention、MQA、GQA
前阵子朋友找我诉苦。他刚训完一个7B模型,欢天喜地部署上线,结果用户一多,延迟直接炸了。他跑来问我:“是不是矩阵乘法不够快?我要不要换H100?”
我说:“你先别急。你跑一次推理,开个profiler看看,GPU到底在干嘛。”
半小时后他发来截图——核心利用率不到40%,但显存带宽吃满了。
你看,这就是大模型推理最扎心的真相:大部分时间,GPU不是在算,是在等——等着从显存里把数据一趟一趟搬过来。
而我今天要聊的,就是一个绕不开、躲不掉、又爱又恨的角色:KV Cache。
你以为瓶颈是计算?其实是搬运
先说清楚:Transformer自回归生成一个token,理论上要重新算一遍所有历史token的注意力。但你想,之前那些K、V都算过了,扔掉重来?脑子有病才这么干。
所以KV Cache出场了:把每层历史token的Key和Value一股脑儿缓存下来。每次新token来了,只算query,直接从缓存里拿K、V。
这招一下把计算复杂度从O(n²)降到O(n)。美不美?美。代价呢?显存爆炸。
我简单算一笔账。H20上跑一个32B的模型,模型权重吃掉64GB,剩下大概22GB给KV Cache——只够缓存大约几十K个token。现在动辄几十万上下文,并发稍微一高,显存直接穿。
这就是为什么我说:KV Cache是大模型推理的水泥封心——它给了你加速,但把你死死困在显存里。
Flash Attention:解决了搬运,没解决存储
说到这必须提Flash Attention。2022年我刚在A100上跑完,当场拍大腿:这玩意是神作。
传统attention你怎么跑?算完QK矩阵,写回显存;算完softmax,写回显存;再算加权结果,又写回去。每步都写回HBM,下一秒马上又要读——这不纯浪费带宽吗?
Flash Attention反着来:用分块计算,整个attention都在SRAM(片上缓存)里搞定,中间结果不落地。我实测过,Llama 2 7B上直接快2-3倍,精度还没损失——它用online softmax,数学上等价。
后来Flash Attention进化了好几代。FA1搞基础分块;FA2调了循环顺序,先算Q再算K V,更好配合Tensor Core;FA3只支持Hopper架构,用TMA和WGMMA让softmax和矩阵乘重叠执行。
但有个残酷事实你得记住:Flash Attention再快,也只是让单次计算变快。它没有减少KV Cache的大小。 你的显存要装不下这么长的序列了,Flash也救不了你。
MQA和GQA:从源头给KV Cache减肥
那咋整?一条路是让模型自己少生成点KV Cache。
最早碰到的优化方案是MQA——Multi-Query Attention,2019年Transformer论文作者之一Shazeer提出来的:所有query heads共享同一套K和V。你无论有多少个注意力头,K和V就维护一组。
你看,KV Cache直接缩到原来的八分之一甚至更小。代价呢?我测过翻译任务,比标准MHA掉了大概1-2个BLEU点。说白了,共享让表达能力变弱——每个头没法独立关注不同信息。
后来折中方案来了:GQA。把query heads分组,每组共享K、V。比如64个查询头分成8组,每组8个头共享1个KV头,KV Cache缩到标准的八分之一。
我自己的实践:Llama 2 70B用的就是GQA。1-2个精度损失基本测不出来,但推理速度明显提升,显存省了一大截。现在主流开源模型——Llama 3、Qwen 2、Mistral——全选了GQA。
可能有人会问:既然GQA这么好,那为啥还有模型用MHA?看场景。短文本、小于7B的小模型,KV Cache压力没那么大,精度反而更金贵。但超过70B?GQA几乎是必选项。
MLA:DeepSeek这波操作让我直呼好家伙
说到这不得不提DeepSeek的MLA,Multi-head Latent Attention。这玩意把压缩做到极致了——它不走共享,而是把K和V先压缩到一个低维的latent空间里存起来,用的时候再解压。
说实话,我第一眼看到这个设计时心想:这能行?压缩再解压,不得有损?
但DeepSeek V2的实际表现告诉我:他们做到了。KV Cache体积大概只有标准MHA的十六分之一,精度损失控制在2%以内。
代价呢?训练时多调一个压缩矩阵和解压矩阵,工程实现极其复杂。这也是为什么目前只有DeepSeek在大力推——成本太高了。
但我个人觉得,长远来看这个思路是对的。当模型参数到万亿级,GQA的八倍压缩也不够用,MLA这种极致方案会越来越有空间。
PagedAttention:解决的是碎片化管理
模型架构优化完了,KV Cache体积也压下来了,对吧?但还有两个问题没解决:碎片化和动态增长。
传统做法是先申请一块内存,不够了再扩容。但KV Cache的大小随序列长度变化而动态增长——于是显存里到处是碎片,利用率低得可怜。
后来有人想:既然像操作系统,那就学操作系统呗。PagedAttention应运而生:把KV Cache切成一页一页固定大小的page,用的时候再映射。vLLM把它做到了极致,我试过同一套硬件,vLLM比原始Hugging Face实现吞吐量高了2-4倍。
而且PagedAttention跟连续批处理配合起来,效果更炸:每次迭代动态批处理,不等一个batch全做完才开始下一个。谁先做完谁先走,新请求还能直接插队进来。
还有个容易被忽略的神器:分离式架构(PD分离)。它把预填充和解码拆到不同机器上跑,避免长请求霸占短请求的资源。
我见过一个真实案例:某电商客服场景,用户发了几百个token的聊天记录,然后问“今天天气怎么样”。结果系统先把所有时间花在处理长上下文上,简单问题被活活堵了几秒。PD分离就是为这类场景生的——花活儿各干各的,互不干扰。
那我到底该用哪个?
这些技术从不同角度下手:模型结构靠MQA/GQA/MLA,计算效率靠Flash Attention,系统优化靠PagedAttention和分离式架构。它们之间不冲突,可以组合使用。
我推荐一个直接上手的方案:
模型选型:
- 小于7B:MHA也没问题,别瞎加复杂度。
- 7B-70B:强烈推荐GQA版本(Llama 3、Qwen 2、Mistral都是)。
- 超过70B:认真考虑MLA,或者类似更猛的压缩手段。
推理部署:
- 基础配置:**Flash Attention 2 + vLLM**,标配。
- 高并发场景:再怼上**连续批处理**和**前缀缓存**。
- 超长上下文(32K以上):上**分离式架构**或**KV Cache卸载**。
我最近测过一套经典组合:GQA的Llama 3 70B + Flash Attention 2 + vLLM + 连续批处理 + 前缀缓存。在8张A100上,吞吐量是原始Hugging Face配置的4倍,P99延迟稳稳控在200ms以内。
所以我的结论很简单——
别只盯着算力压榨模型。先把KV Cache这个显存瓶颈打穿了,收益比训半个月模型还大。 你花两个月精调提1%的精度,可能还不如花两天把Flash Attention和PagedAttention用上,直接给你翻倍的吞吐。
说到这儿,你还在纠结要不要换显卡吗?先看看你的KV Cache,它才是那个默默扛下一切的亲爹。
读者评论 4