资讯动态

从量化到KV Cache:大模型推理优化核心路径解析

发布时间:2026/9/14 15:48:07 来源:尧图企业网站定制
做推理优化有一段时间了踩过的坑比写过的代码还多。这个主题说实话挺杂的从量化、批处理、KV Cache到服务框架选型每一块单独拎出来都能写几千字但真正能把链路串起来、知道每一步在解决什么问题的人其实不多。这篇内容我尽量用做项目的思路来讲从为什么慢、怎么瘦身、怎么上服务、到怎么调参把大模型推理优化的基础路线理清楚方便后面做部署或者做应用的同学直接参考。1. 先搞清楚大模型推理为什么慢优化之前必须得先理解瓶颈在哪。大模型推理慢不是因为“模型太大算不动”这么一句笼统的话而是因为它天生就是显存带宽密集型任务和传统的CPU密集型或者IO密集型任务完全不是一个套路。1.1 推理过程的两个阶段Prefill 和 Decode大模型生成回答是逐字逐token输出的但整个流程并不是从头到尾都一个节奏。它分为两个阶段Prefill阶段预填充用户输入的那段提示词prompt比如几百上千个token会一次性并行处理。这个阶段是典型的计算密集型GPU的算力利用率很高因为矩阵乘法可以充分并行。耗时主要花在大规模的矩阵运算上。Decode阶段解码模型一个token一个token地生成输出。每生成一个token都需要把整个模型的权重从头到尾读一遍同时还要读写不断增长的KV Cache。这个阶段是典型的显存带宽密集型任务GPU算力利用率往往很低大多数时间卡在数据搬运上。一个很直观的感受是Prompt很长的时候首token延迟TTFT可能反而很低因为Prefill是并行算的但Decode阶段每个token都要等前面的token算完才能算下一个完全没有并行空间这时候速度就慢下来了。1.2 为什么GPU动不动就“吃饱了不干活”我们用生活里的事打个比方。一个工厂流水线GPU负责搬运零件的工人显存带宽和负责组装的技工CUDA核心是两拨人。Prefill阶段相当于一批大箱子到了技工们热火朝天开箱组装搬运工压力不大算力利用率拉满。Decode阶段相当于每次只来一个小零件技工们大部分时间在等着搬运工把零件送过来——哪怕GPU的算力再强带宽跟不上算力也只能在那儿干等。这也是为什么很多人在本地跑大模型感觉7B、13B模型的速度差不了太多反而显存带宽更大的卡比如带宽高的游戏卡和专业卡差距明显体验会好得多。推理优化的核心思路本质上就是围绕“让搬运工少跑几趟”和“让技工不要闲着”这两件事展开的。1.3 衡量推理性能的几个核心指标做优化前先得有一个统一的衡量尺度不然优化完了不知道自己优化了什么。指标含义一般关注场景TTFT首token延迟用户发出请求到收到第一个token的时间对话产品、Agent流式输出TPOT每token输出时间每个token生成的平均耗时流式输出流畅度ITLToken间延迟相邻两个token的间隔和TPOT类似用户体验感知QPS每秒请求数系统每秒钟能处理的请求数量服务端吞吐压测并发数同一时间能处理的请求数量服务端在线服务说白了端到端延迟决定体验吞吐量决定成本。绝大多数优化动作都是在这两个维度之间做取舍。2. 模型瘦身三板斧量化、蒸馏与剪枝推理为什么慢一大半原因就是模型太大显存装不下权重读取太慢。所以优化第一步永远是考虑怎么在不严重影响效果的前提下把模型变“轻”。2.1 量化用更少的位宽存权重大模型默认用FP1616位浮点数或者BF16存储权重一个70B模型光权重就要140GB显存两张A100 80G都塞不下还得留出KV Cache和激活值的空间。量化的思路很直接用更少的位数去近似表达原来的数值比如INT88位整数甚至INT44位整数权重体积直接缩小一半甚至四分之三。常用的量化方法有两大类训练后量化PTQ拿已经训练好的模型用少量校准数据统计权重和激活值的分布然后确定量化参数直接转换。代表方法有GPTQ、AWQ、GGUFllama.cpp里的量化格式。量化感知训练QAT在训练或微调过程中就模拟量化误差让模型参数去适应低精度表示。效果通常比PTQ好但成本高一般用在需要极致压缩的场景。实操层面如果只是本地体验或者用llama.cpp跑模型GGUF格式的Q4_K_M、Q5_K_M量化档位是我用得最多的效果和体积的平衡点很好。如果是要上服务端用vLLMAWQ或者GPTQ的INT4量化更主流因为它们在批量推理场景下吞吐提升明显而且不会像GGUF那样在部分算子上有兼容性问题。# 用llama.cpp的quantize工具做GGUF量化 ./quantize ./models/llama-2-7b-fp16.gguf ./models/llama-2-7b-Q4_K_M.gguf Q4_K_M2.2 蒸馏与剪枝从结构上做减法量化的极限差不多压缩4倍再往下就得动模型结构了。知识蒸馏是拿一个大模型教师模型的输出作为监督信号训练一个小模型学生模型去模仿它。效果好的蒸馏能做到小模型在特定任务上逼近大模型效果但体积和推理速度都大幅改善。典型的例子就是DistilBERT、TinyLLaMA这类。剪枝则是把模型中贡献小、接近零的权重或神经元直接删掉让矩阵变稀疏。但说实话在LLM时代剪枝因为硬件利用率问题稀疏矩阵在很多GPU上反而更慢实际落地没有量化那么普遍更多是学术研究或者特定硬件场景在用。我的经验是如果是个人项目或者中小团队优先走量化路线性价比最高蒸馏太耗时耗力适合有大算力资源和明确端侧部署需求的团队。2.3 注意力机制侧的内存优化GQA与MQA这个属于结构层面的“软优化”不需要改模型权重而是改注意力头设计。标准的MHA多头注意力每个头都有独立的K、V矩阵KV Cache很大。GQA分组查询注意力让多个Q头共享一组K、VMQA多查询注意力则让所有Q头共享一组K、V。效果上MQA的KV Cache只有原来的1/8GQA则介于MHA和MQA之间。这也是为什么现在很多新模型LLaMA 2/3、Mistral等都采用了GQA而早期的GPT-3还是MHA——大头开销就在这个KV Cache上。如果手头的模型支持GQAKV Cache的显存压力能小一大截decode吞吐也有可见提升。3. 服务端硬核优化从Batching到KV Cache管理模型权重瘦身之后优化的主战场就到了服务端怎么高效地调度请求。很多人以为推理优化就是量化其实上了服务才发现QPS的大头往往取决于服务端能不能把GPU这块“大工厂”喂饱。3.1 连续批处理Continuous Batching传统的静态批处理是攒够一批请求一起前向传播但必须等这一批次最慢的那个请求跑完这一批才结束。这就有个大问题先提前生成完的请求只能干等着GPU算力白白浪费。连续批处理也叫动态批处理的思路则是不需要等整个批次跑完只要某个请求生成了终止符就立刻把这个请求移出批次同时把等待队列里的新请求加进来。这样GPU永远在处理“最需要算”的token不会因为一个慢请求拖垮整批吞吐。这套机制目前是vLLM、TensorRT-LLM、SGLang这些主流推理框架的标配。自己写Batching逻辑很容易踩坑比如要处理变长序列的padding问题算力浪费很严重所以我建议不要自己做轮子直接用成熟框架。3.2 KV Cache 与 PagedAttention前面提到Decode阶段会不断读写KV Cache。它的机制是模型每生成一个新的token都要基于之前所有token的Key和Value来计算注意力权重。所以过去的Key/Value需要全部缓存下来。传统做法是预先分配一个固定大小的连续显存空间给每条请求类似操作系统的连续内存分配问题是请求的实际token数往往远小于预留空间显存浪费严重。显存碎片化换入换出困难并发上不去。vLLM的PagedAttention借鉴了操作系统虚拟内存的分页机制把KV Cache切分成固定大小的块block不需要物理连续通过块表管理逻辑映射。这样显存利用率大幅提升不再需要预留大块连续空间碎片也被控制住了。这也是为什么同样一张A100用vLLM能做几十路并发而自己写个简单调用只能跑到个位数并发的原因。3.3 Preemption显存不够了怎么办即使有PagedAttention显存还是可能不够。当来的请求太多显存快满了框架就得做抢占调度Preemption。VLLM的处理方式是交换Swapping把一些请求的KV Cache从显存换到CPU内存等显存腾出来了再换回来。重计算Recomputation直接丢掉部分请求的KV Cache等调度回来的时候重算这些token。交换比重计算更轻量但需要保活CPU内存重计算省显存但会额外增加计算耗时。这块不用自己调得太细但需要了解现象——如果并发一高TTFT突然飙升很可能就是发生了Preemption请求被挂起或重算了。3.4 投机解码Speculative Decoding解决Decode阶段算力利用率低还有一个思路让一个小的草稿模型Draft Model先生成几个候选token再由大模型一次性验证。因为大模型验证多个token的时候是并行的所以如果草稿模型的预测准确率高比如在常识性内容上命中率高那么实际的单token推理速度会有接近翻倍甚至更高的提升。不过投机解码对草稿模型的质量要求比较高如果草稿模型的接受率很低反而会浪费大模型的验证算力。实际使用中Medusa这种多头投机解码机制比单独的草稿模型更稳定一些因为它不再需要额外训练一个小的语言模型而是在原模型上加了几个预测头。4. 推理框架选型与部署实操聊了这么多理论层面的优化手段到落地环节才是真正的分水岭。框架选不对前面做的模型优化可能都白搭配置参数调不对再好的框架也发挥不出来。4.1 四个主流框架怎么选框架核心优势典型适用场景踩坑点vLLMPagedAttention、吞吐高、生态好、兼容OpenAI API线上高并发服务AI应用后端对某些模型结构兼容性有要求小众模型需要改造TensorRT-LLMNVIDIA官方出品单卡极致性能延迟低生产环境N卡集群追求低延迟编译时间长动态shape灵活性差上手成本高SGLang原生支持复杂采样控制、RadixAttention优化需要复杂prompt缓存、结构化输出场景相对较新排错资料少llama.cpp / Ollama轻量、CPU也能跑、部署简单本地开发、个人电脑、边缘设备高并发吞吐不如vLLM批量能力弱个人建议如果做的是在线API服务直接上vLLM没有之一。它的吞吐和生态成熟度是最平衡的。Ollama适合开发调试和个人本地玩真正上了生产你会发现它的Batching能力跟不上。TensorRT-LLM适合那种“一张卡能跑多少并发算得死死的”的固定业务比如内部固定的几个模型不走动态路由。4.2 用vLLM启动并调参的实操案例我拿一个常用的7B对话模型举个完整的启动例子python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 32 \ --max-model-len 8192 \ --enforce-eager \ --quantization awq \ --dtype float16几个参数值得单独拿出来讲--gpu-memory-utilization 0.9告诉vLLMkv cache最多可以用90%的显存。剩下的10%用来给模型权重和激活值留余地。如果设得太高比如0.95以上当请求上下文特别长的时候容易OOM设太低KV Cache太小并发上不去。--max-num-seqs 32允许同时处理的请求数上限。这个值不是越大越好——并发太高时每个请求的KV Cache都会被压缩反而导致频繁Preemption整体性能下降。一般从32开始观察显存利用率和TTFT的变化再调整。--max-model-len 8192最大序列长度包含输入输出。8K对大多数业务场景够用了。如果业务上下文特别长比如代码仓库分析可以开到16K甚至32K但显存消耗会非线性增长。--enforce-eager关闭CUDA Graph捕获。CUDA Graph能减少kernel launch开销首次推理会慢一些但后续推理延迟更低。开发调试时开着eager模式方便快速启动线上环境建议去掉这个参数用CUDA Graph优化。--quantization awq告诉框架模型已经是AWQ量化过的权重。用错了量化方式和格式要么直接报错要么推理结果一团糟。以上只是一个基础起步配置。实际调优的时候我会用一个套路先不加并发单路压测看基准TTFT和TPOT然后逐步加并发观察QPS和显存KV Cache利用率找到性能拐点最后再回落几个档位留出安全余量。4.3 本地部署时的显存估算技巧很多人在本地部署时最头疼的问题就是我的显卡到底能不能跑起来这里有一个粗略但很好用的估算公式模型权重显存 KV Cache显存 激活值显存 ≈ 总显存需求推理时一个7B模型的FP16权重约14GBINT4量化后约3.5GB-4.5GB。KV Cache大概每token需要约2 × 层数 × KV头数 × 头维度 × 字节数的显存。粗略估算7B模型在8K上下文下KV Cache大约需要2GB左右。激活值中间结果一般再留2GB就差不多了。我帮朋友排查过好几次本地部署失败最后发现原因基本就两种一是用FP16跑7B模型8G显存直接就爆了二是以为量化后模型显存占用4G不到就开了特别大的max-model-len结果KV Cache把显存吃满了。本地部署一定先把max-model-len调下来4096足够用再谈其他优化。5. 常见问题与排查技巧实录这部分内容是真正踩坑踩出来的网上文档一般不会写得这么细。我按照从现象到解决思路的方式整理几个高频问题。5.1 并发一高就OOM现象单路请求正常并发跑到8路以上显存直接爆掉服务崩溃重启。排查思路第一步查显存监控。如果发现KV Cache增长异常快优先怀疑--max-model-len设置过大——每条请求的KV Cache上限是按这个值预留的而不是按实际长度预留的。第二步看--gpu-memory-utilization是否设置过高比如0.98这时候没有足够空间应对突发流量。第三步检查请求的输入长度。如果业务里有用户粘贴大段文本的场景一个1万token的请求就能吃掉大量KV Cache并发一高自然会爆。解决把max-model-len调小、降低并发上限、或者在业务层做输入长度限制比如超过4000字符直接截断。5.2 TTFT越来越高但TPOT没有明显变化现象刚开始压测的时候首token延迟200ms跑了10分钟后变成2秒以上。排查思路TTFT变高而TPOT稳定大概率不是模型算力问题而是请求排队了。用vLLM的--max-num-seqs观察并发数如果长时间打满说明请求进来的速度超过处理速度框架在不断做调度甚至Preemption新请求只能等待。解决要么加实例机器横向扩容要么做限流要么优化--max-num-seqs和--gpu-memory-utilization尽量提升单机吞吐。注意有时候把max-num-seqs调大反而更糟因为每个请求的KV Cache变小了频繁抢占导致整体性能下降。5.3 量化后推理结果明显变差现象AWQ或GPTQ量化后模型在标准测试集上效果差一大截或者特定业务场景下答案质量明显下降。排查思路确认量化校准数据是否和实际业务数据分布一致。GPTQ和AWQ都需要校准集来统计激活值分布如果用的是通用对话数据而你的业务是代码补全效果很可能崩。检查量化位宽。INT4和INT8差距其实挺明显的敏感任务尽量用INT8或者高精度的量化档位。看是不是模型结构和量化格式不匹配比如一些新模型结构还没有在量化工具的最新版本里适配量化时算子fallback到非优化路径精度受损但性能没有提升。解决业务敏感场景优先选INT8如果一定要INT4先做一个小样本效果对比测试用业务真实数据验证后再上。5.4 两张卡部署比一张卡还慢现象用--tensor-parallel-size 2把模型分到两张卡上理论上算力翻倍结果单路延迟不降反升。排查思路张量并行是有通信开销的。每层Transformer在做张量并行时都要通过NVLink或者PCIe做一次AllReduce通信。当模型卡在带宽瓶颈上比如Decode阶段多加一张卡意味着多一次通信反而拖慢速度。解决小模型7B以下单卡能塞下就别开张量并行。只有在模型单卡放不下或者并发高到单卡算力吃满了才值得上多卡并行。而且多卡机器一定要确认卡间通信走的是NVLink而不是PCIe两者的带宽差距是数量级的。5.5 输出第一个token很快但后边越来越慢现象首token秒回但后面的token生成速度逐渐变慢最后可能每生成一个token要等好几秒。排查思路这是典型的上下文过长导致的注意力计算开销增长。KV Cache是线性增长的但注意力分数计算和对KV Cache的读取量也是线性增长的当上下文超过一定长度Decode阶段每一步的计算量都显著增加。解决优先做业务层面的上下文压缩比如历史对话摘要化只保留最近N轮。其次考虑用支持长度外推的模型比如YaRN等但这只是缓解不能根治。也可以考虑在服务端把max-model-len限制下来防止极端长上下文拖垮整体性能。6. 一些真正的提效心得优化这条路走到后面我发现最有用的反而是几个看似不起眼的习惯。第一优化前先建好压测基线。不做baseline的优化都是耍流氓。我在每次优化动作前都会先记录三组数据单路TTFT、单路TPOT、并发16路的QPS。跑完优化动作后立刻对比这三组数据判断这次改动到底值不值。如果没有基线数据你可能优化了半天结果只是自我感觉良好。第二别迷信某一个优化手段的“论文指标”。量化论文上写的“几乎无损”在业务场景下可能是“明显变笨”投机解码论文里写的“2倍加速”在长上下文、复杂推理场景下可能只有1.1倍。任何优化手段都要在自己的数据和自己的场景里验证这是最花时间但也最值得的部分。第三GPU利用率高不等于性能好。Decode阶段算力利用率低是常态不要为了把利用率拉高而盲目加大并发——那只是让GPU在算一堆重复的注意力而已用户体验反而更差。判断优化是否有效的标准只有一个业务核心指标有没有变好。第四留好“降级方案”。生产环境的推理服务一定要有降级链路。比如主服务是vLLM跑满血的模型可以备用一个Ollama跑量化模型一旦高并发把主服务压垮了至少还能保证用户能用即使慢一点。我用这个思路避免过好多次线上事故了。优化这件事没有终点模型在更新、框架在迭代、业务场景也在变化。把基础原理搞透再保持持续观测和迭代的习惯比追着每个新框架跑要重要得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价