大模型推理性能优化实战:从瓶颈定位到量化与调度
前阵子帮一个团队排查线上模型的耗时单请求尾延迟从60ms一路涨到240ms业务方催得急团队第一反应是换更高规格的加速卡。结果卡换上去指标纹丝不动——问题根本不在算力而在显存分配策略和调度线程的锁竞争上。这种“换硬件解决不了推理瓶颈”的情况我见过太多次了。很多人一听到推理性能瓶颈就以为是模型太大、GPU不够快但实际上推理性能是个系统工程模型结构、权重精度、推理引擎、显存管理、请求调度、框架开销任何一个环节掉链子都会让延迟和吞吐直接崩掉。这篇内容适合正在跑大模型服务、做AI应用落地、或者被老板追问“为什么推理这么慢”的开发者。我会从瓶颈的种类讲起然后把这几年来验证过有效的优化策略逐个拆开量化怎么做才不掉点、推理引擎怎么选、KV Cache的内存账怎么算、长文本下怎么调度请求最后给出我常用的性能剖析流程和踩坑记录。通篇是我在实际项目中反复用过的方案不整虚的。1. 先搞清楚推理耗时分布两类指标和三种瓶颈模型1.1 从两个指标讲起延迟和吞吐优化推理性能的第一步不是急着调参而是先搞清楚你优化的是哪个指标。因为延迟和吞吐的优化方向经常是打架的。延迟关注的是单个请求从发出到返回的速度细分下来又分首Token延迟TTFT和后续Token生成速度TPOT。首Token延迟决定了用户第一次感觉“有没有反应”一般在聊天机器人、RAG问答这类交互场景里最重要后续Token生成速度决定了“读起来流不流畅”比如每秒生成多少字这是流式输出的体感核心。吞吐关注的则是系统整体的处理能力通常用每秒生成的Token数tokens/s或者每秒完成的请求数来度量。离线批量打标、批量内容总结、大规模数据清洗这些场景你根本不在乎单个请求多快只在乎一晚上能跑完多少数据。我见过不少团队一开始就把“降低延迟”和“提高吞吐”混在一起优化最后把系统调得四不像。一个通用原则是在线交互服务优先保延迟离线批处理优先冲吞吐。如果你两类业务都有架构上最好拆成两套配置而不是共用一套服务硬扛。1.2 算力、访存、调度三类瓶颈的典型特征把指标定清楚之后再看瓶颈本身。我习惯把推理瓶颈分成三类算力瓶颈、访存瓶颈、调度瓶颈。算力瓶颈出现在计算量特别大的时候典型场景是大batch的矩阵乘法。模型推理本质上是大量矩阵运算当输入batch足够大、矩阵足够宽时GPU的计算单元会被打满。这时候GPU利用率通常接近100%但显存带宽可能还有余量。这种瓶颈的典型特征就是增大batch吞吐几乎不再上升单请求延迟反而上升。访存瓶颈则完全相反。自回归生成模型在解码时每生成一个Token权重都要从显存里读一遍但实际算的量却很小。权重加载的时间远大于计算时间这时候计算单元在“等数据”GPU利用率看着不高但显存带宽已经是极限了。一家之言大模型推理尤其是小batch单流场景绝大多数瓶颈是访存而非算力。调度瓶颈最容易被忽略。Python端的调度开销、多线程锁竞争、动态图逐个算子启动的CPU开销、请求排队不合理都会让GPU“饿着肚子干活”。典型特征是在GPU利用率不高的情况下延迟依然高得离谱加并发之后反而更慢。这一类问题靠换更强的GPU解决不了必须从引擎和调度层面动刀。有一个很实用的判断工具叫“计算强度”Arithmetic Intensity简单说就是一次运算里计算量除以外数据读取量的比值。这个值低系统多半是访存瓶颈这个值高才有资格往算力瓶颈上找原因。拿这个先定方向再去排查具体问题能省好几个小时。2. 模型侧的优化策略先减少计算量再避免计算浪费2.1 量化从FP16到INT4的收益与代价量化是目前性价比最高的模型侧优化手段没有之一。原理很直白把权重从FP1616位浮点压缩到INT88位整数甚至INT44位整数权重占用的显存直接砍半或者砍到四分之一。显存占用降下来访存瓶颈自然缓解因为每次读权重的时间变短了。实际项目中我把量化分成两类训练后量化PTQ和量化感知训练QAT。PTQ是拿现成模型直接转换不需要重新训练适合快速上线QAT在训练过程中就模拟量化误差模型对量化更“耐受”精度损失更小但成本高。对绝大多数应用场景PTQ 少量校准数据已经能获得可接受的结果。做PTQ时有几个细节直接影响效果。校准集的选择是关键中的关键——不要随便拿几百条文本就去校准最好覆盖你的真实业务场景比如代码模型就用代码做校准对话模型就用对话样本做校准。另外不要全模型一刀切用同一种量化精度。我的习惯是注意力层的Softmax、最后的分类层、位置编码相关的部分保持高精度因为这几处对误差特别敏感而FFN层的权重可以放心量化到INT4掉点通常不明显。还有一种做法是做混合精度量化比如大部分层用INT4个别敏感层用INT8或者BF16。虽然实现复杂度上升但在一些精度敏感模型上这是“既想省显存又想不掉点”时很实用的折中方案。我自己试过在一个中等规模模型上把敏感层挑出来量化后精度只回了零点几个百分点但显存占用和之前差不多效果很香。2.2 剪枝与蒸馏什么时候值得做剪枝和蒸馏是另外两条路但适用场景比量化窄需要更谨慎。剪枝的核心是砍掉不重要的权重或结构。非结构化剪枝会把不重要的单个权重置零模型变“稀疏”但在常规硬件上加速效果有限因为计算库对稀疏矩阵的优化并不普遍。结构化剪枝则是按行、按列、按通道整体删除结构比如删掉某些注意力头这种剪枝方式能直接减少计算量但需要重新训练模型才能恢复精度成本较高。蒸馏的本质是“以大教小”用一个大的教师模型输出包括软标签、中间层特征来训练一个小学生模型让小学生模型在更小的参数量下逼近大模型的效果。蒸馏适合的场景很明确——推理延迟要求极高、部署设备资源有限比如端侧设备、边缘盒子。之前我做过一个图像特征提取项目把一个大模型蒸馏成原来三分之一大小的模型速度提升了近三倍精度损失控制在一个可接受的范围内。不过我的建议是如果你没有明确的部署资源限制优先做量化而不是剪枝和蒸馏。量化的改动最小、见效最快、风险最可控。剪枝和蒸馏意味着重新训练时间成本和调参成本都是量化的好几倍。真正管用的组合拳是“大模型离线蒸馏出中模型 中模型在线量化加速”既保精度又保速度这是我在资源受限项目里屡试不爽的组合。3. 推理引擎与图优化把计算图加工成更高效的流水线3.1 引擎选型通用框架还是专用推理引擎很多人在PyTorch里把模型推理跑通之后就以为万事大吉了直接把eager模式部署上线。实际上通用训练框架在推理场景的效率并不高每个算子都要单独启动一个KernelCPU要一遍遍下发指令GPU每次启动还有额外开销小算子一多时间全耗在调度上。所以生产级推理一般会用到推理引擎或编译优化。不同引擎的定位差别很大我按实际场景做一个简单的选型对照引擎方向典型工具适合场景备注通用图优化TorchScript / 通用编译器中小模型、快速上线改造成本低但优化幅度有限专用推理引擎TensorRT等加速卡部署、高吞吐生产环境算子融合和显存优化很激进需要额外转换时间大模型推理框架vLLM / 类SGLang框架大模型在线服务、高并发内置连续批处理、KV Cache管理轻量部署llama.cpp等端侧、CPU环境对消费级硬件友好量化模型支持好选型的原则很简单先在通用框架里跑通再用专用引擎压测对比指标再决定上不上。不要听别人说哪个好就直接换推理引擎的收益和模型结构、部署硬件、请求模式强相关换了之后到底行不行只能拿自己的数据说话。3.2 算子融合、CUDAGraph与连续批处理专用推理引擎之所以快几个核心技术功不可没算子融合Kernel Fusion、CUDAGraph、连续批处理Continuous Batching。算子融合的思路是把多个小算子合并成一个大算子。比如LayerNorm后面跟一个残差连接在原始计算图里是两个Kernel中间还要把结果写回显存再读出来融合之后就变成一个Kernel中间结果直接留在寄存器或片上缓存里省了一大笔访存开销。原理很像工厂流水线原来每个工序都单独搬运原材料现在把几个工序合成一个工位搬运时间就没了。CUDAGraph解决的是CPU与GPU的通信开销。PyTorch eager模式每个算子启动都要经过CPU调度CPU下发指令的速度成了瓶颈。CUDAGraph的思路是把一串GPU操作预先“拍”成静态图一次性提交给GPU省掉中间大量CPU调度时间。对于小batch的延迟优化CUDAGraph带来的改善往往让人意外很多时候延迟能下降百分之二三十。连续批处理则是大模型在线服务吞吐提升的大杀器。传统批处理要等一个batch里所有请求都生成完毕才释放资源时间浪费在慢请求上连续批处理则是按Token级别动态调度哪个请求生成了新的Token立刻插入batch生成完了就腾出位置让别的请求补上。GPU的计算资源几乎一直在被使用吞吐自然就上来了。我测过一个部署在vLLM上的开源模型开启连续批处理之后吞吐量翻了一倍左右而延迟还保持平稳这是性价比极高的一项优化。4. 显存管理与长序列优化从KV Cache说起4.1 先算出KV Cache占多少显存长文本场景是推理优化的重灾区因为自回归模型每生成一个Token都要把历史Token的Key和Value缓存下来这个缓存叫KV Cache。序列越长KV Cache占用越大而且是不考虑上限的大。有个很实用的估算公式KV Cache内存 ≈ 2Key和Value两份× 层数 × KV头数 × 头维度 × 序列长度 × 精度字节数 × batch大小。我具体算一个例子70亿参数模型假设层数32、KV头数8、头维度128、精度用FP162字节那么单条请求生成2048个Token时KV Cache占用是 2 × 32 × 8 × 128 × 2048 × 2 约536MB。看着好像还行但如果是8条并发请求、上下文涨到8192这个数字会直接跳到2GB以上加上模型权重和中间激活值显存爆掉是很容易的事。所以在设计服务时第一件事就是按公式估算显存分配确定最大序列长度、最大并发数、模型权重大小剩下的才留给KV Cache和激活值。很多人上线前不看这个账结果并发一上来就显存溢出只能不停重启服务这种低级事故我见得太多了。4.2 分页注意力、KV Cache量化与调度策略KV Cache优化有三种主流手段每个都值得试。分页注意力PagedAttention是当前大模型推理框架普遍采用的方案。传统方式给每个请求预分配一整块连续显存长序列空间不够短序列空间浪费分页的思路则像操作系统的虚拟内存把KV Cache切成固定大小的块按需分配用完回收。显存利用率能提升不少可以支持的并发请求数也随之上升。我之前把一个部署服务的batch大小上限提升了近一倍就是因为换了这类框架显存碎片问题大幅缓解。KV Cache量化则是把缓存中的Key和Value从FP16压到INT8甚至更低。权重量化能省显存KV Cache量化同样能省显存而且因为每轮生成都要反复读写KV Cache这项优化对带宽的节省也很明显。精度影响方面我的经验是KV Cache量化到INT8在大多数模型上可以接受但要留意长文本下误差累积必要时保留一部分浮点精度做混合量化。调度策略同样重要尤其是请求长短不一的时候。如果长请求和短请求混合在一起短请求很快完成释放资源长请求却迟迟占着显存和算力。配合推理框架里的连续批处理机制再加上合理的最大序列长度限制和队列策略能在用户可接受范围里尽量腾出吞吐空间。我自己上线服务时一定会在代码里写明最大序列长度并且对超长请求做截断或提示这是防止显存被单条请求拖垮的关键防线。5. 瓶颈定位与性能剖析的实操工具箱5.1 从基线数据开始先记录哪些指标每次做性能优化我第一件事都是压测并记录基线。没有基线就没有对照改一个变量根本说不清楚是有效还是无效。最基础的基线数据包括四类延迟平均延迟、P90/P99/TTFT/TPOT、吞吐tokens/s、请求数/s、GPU利用率均值、瞬时峰值、显存带宽利用率。GPU利用率和显存带宽利用率这两项特别重要一个是判断算力紧不紧张一个是判断访存紧不紧张一对照就知道瓶颈在哪一侧。工具方面我常用的是GPU厂商自带的性能分析工具比如nsys和ncu做深度剖析。nsys能看到每个算子的耗时和调用顺序适合排查调度开销ncu能给出内核级更细粒度的指标适合算力和访存比的详细分析。Python层面的耗时可以用py-spy来抓线程栈排查锁竞争和Python层的卡点也很顺手。这些工具都是命令行式的跑一次就能生成报告操作起来不复杂关键是你得知道从报告里看哪些指标——优先看每个阶段的耗时占比而不是纠结单个算子的技术细节。5.2 排查路径我惯用的四个分析步骤我一般按下面四步走逻辑比较固定推荐你也试试。第一步先压测出基线延迟和吞吐同时记录GPU利用率和显存带宽。第二步判断瓶颈类型如果GPU利用率接近100%且带宽利用率不高多数是算力瓶颈如果利用率不高但带宽快满了就是访存瓶颈如果两边都不高大概率是调度框架的问题。第三步用分析工具抓耗时分布算力瓶颈就找耗时最长的矩阵乘法算子访存瓶颈就看哪些层读取权重最频繁调度瓶颈就用线程栈找锁竞争或多余拷贝。第四步锁定优化目标一次只改一个变量改完重新压测对比基线看有没有变好。我举一个真实排查过的场景某个服务GPU利用率才30%延迟却很高。按照步骤一看显存带宽已经90%以上所以不是算力不够是权重读取太大。后来引入量化把权重从FP16压到INT8带宽压力降了一半延迟和利用率同时恢复正常。如果当时只顾着换更高算力的GPU问题完全不会解决还白白花了成本。6. 常见问题与踩坑实录6.1 高频问题排查表把这几年遇到的推理性能问题整理成了表按出现频率排的适合直接当速查手册用。现象可能原因检查方式解决思路GPU利用率低但延迟高调度开销大、小算子太多、锁竞争nsys看CPU下发时间占比启用图模式、算子融合、CUDAGraphGPU利用率高但吞吐低算力瓶颈或batch配置不当ncu看矩阵算子耗时增大batch看是否线性增长不行再考虑量化并发一高就显存溢出KV Cache超出预算估算KV Cache内存占用限制最大序列长度、启用分页注意力、KV Cache量化量化后精度明显下降量化策略一刀切分模块对比精度混合精度、敏感层留高精度换了更强GPU延迟没变瓶颈不在算力访存或调度未解决先看带宽利用率和调度耗时优先做权重压缩和图优化再考虑换卡长序列响应越来越慢KV Cache增长访存压力上升对比不同序列长度下的耗时对超长请求做限制或预处理启用KV量化6.2 几条反直觉的经验第一GPU利用率高不等于吞吐就高。利用率分算力利用率和显存利用率有时候算力满了但大量时间在等待数据搬运实际有效产出并不高。我见过很多团队把GPU打到99%以为性能已经最优结果一算tokens/s离理论峰值差得远。真正要盯的是单位时间生成的有效Token数。第二不要一上来就做模型剪枝。剪枝的工程复杂度远高于量化而且在大模型上剪枝带来的收益往往不如量化直接。只有当你明确需要降低模型参数量本身比如端侧部署有硬性体积要求时剪枝才值得投入。顺序上先量化、再蒸馏、最后才考虑剪枝这是成本从低到高的路径。第三在线服务和离线批处理的优化方向要分开。在线服务优先保延迟需要小batch或者连续批处理离线批处理优先保吞吐batch可以往大了压。很多服务慢是因为离线和在线共用一个配置互相拖累。最理想的情况是两套部署分开调参各自跑出最优值。第四改动前一定要留好压测脚本和基线数据。因为优化之后不可能一次成功大概率要回滚重试。没有基线出了问题你连“到底什么改动导致变慢”都说不清等于盲人摸象。我个人这几年做推理优化的体会是大部分性能问题都不是硬件不够强而是软件链条上某个环节没拧紧。先把瓶颈类型定性清楚再用合理的工具去定量分析最后针对性地做量化和引擎优化80%的项目在这个流程下都能拿到非常可观的改善。如果你手头也有一个跑得慢的模型别急着加预算先按上面的思路过一遍大概率能省下一大笔钱。