
我接手过一个线上问答服务的性能排查任务。现象很简单白天高峰时用户反馈一直转圈页面要等五六秒才出结果。监控面板上GPU利用率只有30%上下CPU也没到瓶颈内存充足看起来一切“正常”。团队花了三天时间先后怀疑过网络、数据库、模型并行配置改了七八个参数效果都很有限。最后用profiler把链路一层层剥开才发现问题出在一个很多人压根不会看的地方长文本输入的embedding计算被放到了CPU上跑而且和GPU生成环节是同步阻塞关系。“看起来正常”的监控恰恰是AI系统性能排查里最危险的信号。这个系列的第一篇我把AI系统性能工程的框架做了整体梳理——它的核心是让模型服务在成本、时延、吞吐之间找到工程上最优的平衡点而不是单纯“把某项指标拉到极限”。这篇我打算把落地环节讲透重点回答四个问题怎么建立一套可信的指标采集与性能基线怎么从全局到局部逐层定位真正的瓶颈优化手段那么多按什么顺序做性价比最高性能工程最后怎么落到容量规划和成本核算上这套方法论不绑定具体框架或厂商正在做模型服务化、推理平台、训练集群调优的工程师可以直接参考哪怕你只是想把一个开源模型部署起来跑得更快里面大多数思路也能直接照抄。1. 把AI系统性能链路拆成三层硬件、框架、模型各管什么1.1 传统性能调优经验为何在AI系统里频繁失效刚接触AI系统性能问题时我犯过一个典型错误拿Web服务那套思路来套——看CPU、看内存、看IO然后调缓存、调连接池、改线程数。结果很多时候CPU看着不高、内存也不紧张服务却慢得离谱。后来想明白了AI系统的性能链路比传统服务长得多而且每一段都可能成为瓶颈。一个很普通的在线推理请求要经历的完整链路是客户端发起请求、网关路由、tokenizer预处理、CPU上的数据增强或归一化、H2D数据拷贝、GPU上的模型推理、D2H结果回传、后处理、返回响应。传统Web调优的方法论更多关注“进程内计算网络数据库”而AI系统额外多了模型计算、显存管理、框架调度、算子内核这些全新的瓶颈层。用旧的性能分析方法去看新问题自然容易摸不到门道。1.2 三层模型从全局坐标系开始定位我把AI系统性能问题拆成三层来看。每一层都有各自的职责和典型的瓶颈特征先记好这个坐标系后面下钻才不会乱。硬件资源层包括GPU算力、显存容量与带宽、CPU算力、内存带宽、PCIe/网络互连。瓶颈特征通常表现为某一个资源的利用率接近100%其他资源却闲着。比如GPU利用率拉满但吞吐上不去多半是kernel本身执行效率低显存带宽打满而GPU算力利用率很低说明模型的访存策略有问题数据搬运时间远大于计算时间。框架运行时层包括推理引擎TensorRT-LLM、vLLM、ONNX Runtime等、算子库cuDNN、CUTLASS、调度器、内存管理器、batch策略、CUDA Graph等。这一层常见特征是GPU在等kernel启动、CPU和GPU职责分配不合理、batch策略导致大量空闲等待。很多团队换了推理引擎之后吞吐翻倍本质上就是这一层的优化空间被释放了。模型结构层包括网络层数、隐藏维度、注意力机制类型、量化位宽、输入序列长度。瓶颈特征是模型本身的计算量与访存量不平衡典型如解码阶段受限于带宽、某些层的算子效率特别差、激活函数带来额外开销等。三层模型的真正价值在于遇到性能问题时先判断问题大概率出在哪一层再针对性下钻而不是到处乱试。我经历过一个很能说明问题的场景同一个模型在PyTorch默认部署下GPU利用率只有20%换成TensorRT-LLM后跑到70%以上端到端时延直接降了3倍——这是框架层的优化空间。另一个场景是同一个模型把输入图片从1024×1024改成224×224性能提升好几倍——这是模型层的计算量差异。不区分层次很容易得出“加卡就行”或者“换框架包治百病”这种以偏概全的结论。1.3 性能工程闭环先有流程再谈优化我总结的性能工程闭环有五个环节缺一不可基线建立——把当前状态量化成一组可复现的数字。瓶颈定位——通过监控和profiling找到真正的限制因素。优化实施——按ROI排序逐个落地并记录实验。效果验证——A/B对比确认收益且排除精度回退。回归复盘——把有效方案固化进部署流程防止后续回退或被人“优化没了”。这套闭环看起来很朴素但绝大多数团队做得并不好。很多团队上来就“优化”——换引擎、调参数、加卡但没提前写基线结果改完说“好像变快了”要数据没数据要对比没对比出了问题甚至不知道回滚到哪一步。性能工程的第一步永远不是优化而是把现状搞清楚。2. 指标采集与基线建立先让“感觉慢”变成一组可复现的数字2.1 必盯的指标分四类一个都不能偏先给一个简单结论性能工程里的指标至少分四类只盯着某一类一定会出问题。类别核心指标适用场景时延类端到端时延、P50/P95/P99分位点、TTFT、TPOT在线服务、流式输出吞吐类QPS、tokens/s、并发处理数离线批处理、高负载服务资源类GPU利用率、显存占用、SM占用率、CPU利用率、内存带宽、PCIe传输所有场景判断瓶颈在哪一层精度类准确率、BLEU、ROUGE、余弦相似度任何涉及模型优化的场景在大模型在线推理里时延类指标还要进一步拆成TTFT首token时延和TPOT每输出token时延。为什么单独看TTFT流式输出的用户感知里“首屏速度”几乎决定了一多半的体验——首token出来够快哪怕后续逐字生成慢一点用户的耐心也完全不一样。反过来如果TTFT很快但TPOT很慢整个响应拖得很长用户依然会不满。这两个指标在调优时还可能互相矛盾为了降低TTFT把batch调小吞吐就掉下去为了冲吞吐开大batchTTFT和TPOT都会涨。所以实际调优矩阵里这组数据必须同时记录不能只看一个。2.2 采集工具链按层选工具别迷信单一面板工具选择按层来具体如下系统资源层Prometheus node_exporter DCGM exporter配合Grafana出面板。DCGM能拿到比nvidia-smi精细得多的GPU指标比如SM利用率、显存带宽利用率、温度、功耗。在线排障时直接跑nvidia-smi dmon -i 0 -d 1 -s pucvmet比watch nvidia-smi高效得多能按秒看到功耗、利用率、显存读写、温度、PCIe收发情况。框架算子层NVIDIA Nsight Systemsnsys看整体时间线Nsight Computencu看kernel微观指标PyTorch场景用自带的torch.profiler一分钟就能出结果。模型特征层统计FLOPs、参数量、访存量用ptflops之类的库或手动估算Roofline分析需要靠这层数据。一个小建议监控面板不需要一开始就做得很好看先保证四件事有数据——请求量、各环节时延、GPU/CPU资源曲线、显存水位。数据先采起来后面分析才有依据。很多团队把时间花在“把面板做得花哨”上结果关键的链路时延数据根本没记录这是典型的舍本逐末。2.3 压测设计并发梯度、预热期、分位点怎么定基线数据的可信度完全取决于压测设计。我常用的压测参数如下并发梯度1、2、4、8、16、32、64逐级往上压记录每个并发下的P50/P95/P99和QPS。只看单并发没有意义因为真实系统永远是在竞争状态下运行的。预热期至少让服务跑3到5分钟再采样排除冷启动、CUDA kernel首次加载、显存分配等瞬态影响。数据多样性不要只用一条测试样本。真实请求的输入长度分布很广长短混合压测才能暴露排队问题和长序列极端情况。超时与错误率记录超时比例和错误响应比例。时延分位点必须剔除错误请求否则4xx/5xx响应会严重拉低P99让你误判系统性能。压测工具方面在线推理服务用locust或k6都可以关键是脚本里要能区分“请求发出到响应完成”的端到端时延和“模型内部计算时延”。两者之差通常就是排队、网络、预处理或后处理耗时这些数据对定位瓶颈极其重要。2.4 GPU利用率这个“陷阱”指标单独看必踩坑这里专门说一个坑GPU利用率不一定是“越高越好”低也不一定代表“系统没事”。nvidia-smi里的GPU-Util本质是一个采样值反映的是采样周期内GPU上是否有kernel在执行而不是GPU计算单元真正忙到什么程度。经常出现的情况是有kernel在跑但这个kernel只用了很少的SM或者一直在等显存数据——从nvidia-smi看利用率可能很高实际有效吞吐很低。反过来GPU利用率低也不一定意味着要加卡。有些模型本身是轻量级推理CPU预处理和网络传输占了主导GPU计算占比自然低这时瓶颈在别处盯着GPU数字纯属南辕北辙。我处理过一个案子GPU利用率只有25%所有人都在讨论要不要加卡最后发现瓶颈是数据库中热点行的锁竞争——跟GPU一毛钱关系都没有。资源指标一定要结合时延和吞吐一起看单独下结论必踩坑。3. 瓶颈定位的三层下钻法从资源曲线到算子热点的完整路径3.1 第一层先看资源层曲线形态快速圈定方向拿到基线数据后第一步不是看代码而是看曲线形态。几种典型形态和对应线索如下曲线特征可能的瓶颈方向GPU利用率低 CPU利用率高CPU端预处理、tokenizer、数据加载、后处理是瓶颈GPU利用率高 QPS低于预期kernel执行效率低或模型配置有误导致重复计算GPU利用率低 显存逼近上限显存容量不足可能触发换入换出或OOM重试GPU利用率波动剧烈请求排队、冷启动、动态batch窗口不匹配显存带宽打满 SM利用率低访存密集问题LLM的decode阶段很典型网络或PCIe传输出现尖峰多卡通信、H2D/D2H拷贝是瓶颈这层看下来基本能把问题框定到某个方向。但注意这一步只能缩小范围不能下结论。比如GPU利用率高QPS低既可能是算子的算术强度过低也可能是框架里发生了不必要的同步等待需要下一层进一步确认。3.2 第二层框架层的kernel耗时与同步等待profiler能告诉你真相资源层定位到GPU侧之后用nsys或torch.profiler往下拆。torch.profiler的基本用法非常简单import torch from torch.profiler import profile, ProfilerActivity with profile(activities[ProfilerActivity.CPU, ProfilerActivity.CUDA]) as prof: outputs model(inputs) torch.cuda.synchronize() print(prof.key_averages().table(sort_bycuda_time_total, row_limit20))输出表格后重点看几件事哪些kernel累计时间最长是否由某一类算子主导比如attention、GEMM、LayerNorm如果某一个算子独占30%以上时间方向就明确了。CPU侧和GPU侧是否存在大量同步等待很多推理服务慢不是GPU慢而是CPU在计算预处理时GPU干等着。nsys的时间线里如果有大片空白但GPU kernel之间间隔很长十有八九是同步等待。H2D/D2H拷贝占比多少数据来回搬是纯开销。碰上小请求高频往返拷贝时间甚至会超过计算时间。kernel启动频率高不高小而碎的kernel数量过多GPU大部分时间在等启动指令这就是典型的launch-bound问题。常见解决手段是CUDA Graph或算子融合。这一层是三层里最容易被忽略的。很多人习惯只看GPU利用率看到利用率不高就直接加并发、加batch结果可能是问题被掩盖了而不是被解决了。3.3 第三层模型层的计算特征与Roofline分析判断算力还是带宽受限到了模型层最有效的分析工具是Roofline模型。核心思路算一个算子的算术强度AIFLOPs / 访存Bytes再和硬件的平衡点ridge point 峰值算力 / 峰值带宽做比较。AI远小于ridge point说明是访存受限——堆算力没用要减少访存AI大于ridge point则是计算受限——考虑降低计算量。拿大模型推理举一个经典例子假设是7B模型、FP16权重约14GB。decode阶段每生成一个token大致要把全部权重从显存读一遍约14GB计算量粗略按7B量级算约14GFLOPs。算下来AI大约只有1 FLOPs/Byte。H100的峰值算力约989 TFLOPS显存带宽约3.35TB/sridge point大约295 FLOPs/Byte。1远远小于295所以decode阶段是妥妥的访存受限。这就解释了为什么大模型推理时GPU利用率看着不算很高但提升困难——它在等显存喂数据算力根本喂不饱。prefill阶段则是完全另一种局面序列长度2048时计算量大约2.8e13 FLOPs访存还是约14GBAI约2000超过ridge point属于计算受限。所以对大模型推理正确的优化认知是prefill要算得快重点是提高计算效率decode要省着读权重重点是降低访存量。这也是为什么量化把权重从FP16降到INT4、访存量直接降到1/4对decode提速效果非常明显但对prefill的帮助相对有限——因为prefill根本不是访存瓶颈。3.4 一个完整定位案例GPU没跑满瓶颈却藏在同步等待里把三层下钻法穿起来看一个真实案例。现象一个图像推理服务单并发下P50时延才60ms并发拉到8时P99直接飙到800msGPU利用率只有30%。第一步看资源层CPU利用率到80%GPU利用率30%说明CPU侧开销很重但还不能确定一定是CPU预处理问题。第二步看框架层用nsys抓时间线发现GPU kernel实际执行只占了总耗时的不到15%大部分时间花在CPU预处理和torch.cuda.synchronize()的同步等待上。再细看预处理里包括图像解码、resize、normalize全在CPU上串行执行而且每张图都要同步等GPU结果返回后才开始下一张的预处理。第三步看模型层模型FLOPs约30G理论上A100单卡毫秒级就能算完计算量本身不是瓶颈。结论很清楚CPU预处理和GPU计算是串行阻塞关系并发一高CPU排队GPU空转。修复方案也不复杂把预处理改成异步管线让CPU和GPU重叠工作再把resize和normalize这类算子下沉到GPU上执行减少H2D传数据量。改完之后P99从800ms降到90msGPU利用率反而升到60%因为GPU终于有活干了。这个案例说明三层下钻法的价值不是“某个工具多厉害”而是用逻辑链条排除干扰项最终定位到真正的根因。4. 优化手段的ROI排序低成本动作先做最后才动模型4.1 零成本优化清单很多场景直接翻倍进入优化阶段后我的铁律是先做零成本动作再谈改模型。零成本不是指“没效果”而是指不改变模型权重、不引入额外训练成本改的是部署和调度方式。这类优化在多数场景下能带来50%以上的收益。切换推理引擎PyTorch默认部署换成TensorRT-LLM、vLLM、ONNX Runtime等专用引擎利用算子融合、图优化、内存复用这是性价比最高的一步。动态batching / continuous batching静态batching要等同一个batch内所有序列全部生成完才开始下一批任何一个慢序列都会拖住整批连续批处理则每个iteration动态调度生成完的序列立刻腾位置给新序列。实测中同样卡数下连续批处理往往能带来2到3倍的吞吐提升。CUDA Graph把一组kernel的启动过程固化下来省掉大量CPU侧launch开销。对kernel碎片化严重的小模型尤其明显。并发与队列调优调整并发上限、队列深度、超时时间避免请求在队列里排队过久。很多P99暴涨其实是队列堆积导致的而不是GPU慢了。精度模式确认FP16/BF16已开启。很多默认部署还在用FP32等于白白扔掉一半算力。这些动作不需要重新训练模型风险极低建议做成团队的“零成本优化检查清单”每次部署新模型前先过一遍。4.2 中等成本优化量化和KV Cache管理针对大模型decode最有效零成本动作做完了如果还是不够再上中等成本手段。这里重点是量化和KV Cache管理。量化是大模型推理绕不开的环节。几个主流方案的对比方案权重精度推理时延收益显存收益精度损失FP16/BF1616bit基线基线无INT88bit时延降低20%-40%显存减半很小多数任务可忽略INT44bit时延降低50%以上显存减到1/4左右部分任务有可感知损失FP88bit计算密集型场景有收益显存减半训练/推理均可接受选用哪种量化要根据模型规模和任务敏感度来。对话、摘要这类生成任务对INT4通常能容忍检索排序、数值类任务则要谨慎建议做充分评估再上线。KV Cache管理是另一个关键点。大模型推理时每个请求的中间状态KV Cache会随序列长度增长占用大量显存而且碎片化严重。vLLM的PagedAttention从操作系统虚拟内存分页得到启发把KV Cache切成小块按需分配大幅提升显存利用率。实际表现是吞吐提升30%-50%还能支撑更长序列。另外固定前缀系统提示词、长上下文开头可以做成prompt caching把一份KV Cache给多个请求复用省掉重复的prefill计算TTFT能降很多。4.3 高成本优化动模型结构必须建立在充分证据上如果零成本和中等成本手段都做完了还不够才考虑动模型结构蒸馏、剪枝、结构改造。这类手段周期以周甚至月计而且精度风险大一旦回退很难定位是哪个改动导致的。我的判断标准是只有当Roofline分析明确指向模型计算量或访存量本身失衡时才值得动模型结构。比如某个算子独占大量时间且没有现成的融合变体或者模型结构里有明显的冗余层。否则同等投入下换更好的推理引擎、做量化ROI要高得多。动模型结构的常见几个方向知识蒸馏用大模型当teacher教小模型训练成本高但推理收益显著。剪枝去掉影响较小或冗余的通道/层。结构化剪枝对延迟有直接收益但需要重新微调。注意力机制替换比如把标准attention换成FlashAttention、稀疏注意力能大幅降低长序列场景的显存和计算量。高成本手段的排序原则是先用profiler证明问题出在模型层再动手。否则你很可能花一个月改写模型结构最后发现瓶颈在框架层的batch调度上。4.4 如何验证优化真的有效A/B对比与回归测试每次优化落地都要走一遍验证流程否则无从判断这个改动是“变好”还是“换了一种坏法”。我常用的验证清单同一压测脚本、同一数据集、同一并发梯度对比优化前后的P50/P95/P99、QPS、GPU利用率。精度回退检查跑一遍离线评测集对比优化前后模型的精度指标。尤其量化场景必须做。长稳测试不只看瞬时指标要跑至少30分钟以上的稳定性测试观察显存水位是否持续上涨、时延是否逐渐劣化。回滚预案对每个改动保留回滚开关一旦线上出问题能立即退回。这里多说一句很多团队对优化很兴奋但对回滚预案不上心。性能优化最大的风险不是“没效果”而是“上线后出问题找不出是哪个改动引起的”。所以每次实验都要留记录改动内容、压测数据、精度结果、回滚方式。这是性能工程流程里最容易偷懒、也最不该偷懒的一环。5. 容量规划与成本核算性能工程最终要回答“需要几块卡”5.1 从压测数据推导GPU数量的估算方法性能工程做到最后老板一定会问一个现实问题这套服务到底需要多少卡这里给一个可以直接套用的估算路径。先明确目标容量例如线上目标200 QPS平均每个请求输出500 tokenTPOT目标是50ms。那么单请求的生成时长约为500×50ms25秒。需要的并行请求数约等于200×255000个按纯排队论也就是系统里同时在处理的请求数。如果单卡能支撑并发32个请求那么卡数约为5000/32≈156卡。这只是理论下限还要加上峰值波动、故障冗余、训练和推理混部等因素工程上一般再乘1.2到1.5。更简单的经验公式GPU卡数 ≈ 峰值QPS × 单请求平均耗时 / 单卡并发吞吐能力。其中单卡并发吞吐能力要靠压测得到不要拍脑袋。很多团队一上来就买卡扩容其实先把单卡压测数据摸清楚往往发现已有的卡远没有用满。5.2 成本模型与SLA设计时延和成本永远是权衡关系成本核算不能只看卡价要把时延SLA放进去一起算。我的经验是把业务需求拆成几个档位核心链路SLA要求P99在500ms内的交互式服务需要预留足够算力成本优先级低于体验。辅助链路P95在2s内即可可以用更小的模型或更低并发显著降成本。离线批处理没有实时SLA重点看吞吐和单位成本。可以排队、可以攒batch用满算力。设计SLA时还要注意P99和成本高度非线性。从P95压到P99可能需要多准备30%-50%算力。如果业务对极致时延没那么敏感把SLA定在P95能省一大笔钱。这是产品和技术一起做的权衡性能工程的角色是把“多花多少钱换多少时延收益”算清楚而不是单方面追求快。5.3 弹性伸缩按队列深度扩容比按资源利用率灵敏得多容量规划的另一个落地动作是弹性伸缩。很多团队基于CPU/GPU利用率触发扩容结果利用率指标滞后严重——等看到GPU跑到70%再扩容请求已经排队两秒了。我的建议是优先按队列深度或排队时延触发扩容。队列深度是一个更前置的指标请求从进入到开始被处理之间有明显等待时说明算力已经不够了。搭配双阈值策略队列深度连续超过阈值A持续30秒开始扩容低于阈值B持续3分钟缩容避免抖动。这里有个容易被忽略的点扩容要提前预热。冷节点启动后加载模型到显存、初始化CUDA kernel缓存需要几十秒到几分钟。如果没有提前准备等流量尖峰打到新节点上它还在慢吞吞加载模型扩容就变成了“远水救不了近火”。所以业务侧的容量预案要预先保留一部分“半热”节点或快速加载机制而不是依赖即时扩容。6. 一次真实案例复盘RAG服务从P95 4.2s降到700ms的全过程6.1 现象与基线用户感知的“转圈”到底是什么前面提到我接手的问答服务进一步交代背景那是一个RAG检索增强生成在线问答系统。用户提交一个问题系统先去知识库检索相关内容拼成上下文再交给大模型生成答案。业务反馈高峰时用户频繁中断截图里能看到“生成中”状态持续四五秒以上。先建立基线。用生产流量的回放做压测结果如下P50时延1.8sP95时延4.2sP99时延9.8s吞吐只有12 req/sGPU利用率在20%到40%之间剧烈波动显存占用长期60%左右。从数据看GPU利用率不高显存没满说明不是容量问题而是链路里存在明显等待。6.2 下钻定位三个问题叠加都不在模型本身按三层下钻法逐层看第一层资源层CPU利用率不算高GPU利用率波动大。波动本身说明请求处理和计算之间不同步存在排队或等待。第二层框架层用nsys抓时间线发现GPU kernel执行时间占比极低。碎片化的时间线里几乎每个请求都有一段很长的CPU侧空白——那是在调用embedding模型做向量编码。再细查embedding模型跑在CPU上用的是sentence-transformers默认配置单次编码就要200到400ms。而生成阶段虽然走了GPU但batch策略还是静态的每批固定4个请求必须等这一批全部生成完才进下一批导致GPU利用率上不去。第三层模型层生成模型本身计算量还好但显存带宽和KV Cache利用率有提升空间。结论三个问题叠加——CPU上的embedding计算串行阻塞、GPU静态batch窗口太小、KV Cache没有做显存优化。没有一个是需要改模型结构的。6.3 优化实施与效果对比同样的业务卡反而少了优化按ROI排序落地把embedding模型迁移到GPU执行和生成模型混部调度把CPU预处理和GPU计算重叠起来。生成引擎从默认PyTorch部署换成vLLM开启连续批处理batch窗口从静态4改成动态调度。对生成模型做INT8量化减权重复读取的带宽压力启用KV Cache分页管理。把系统提示词和固定的前缀上下文做成prompt caching绕过重复prefill。改造后的同一份回放压测结果指标优化前优化后P50时延1.8s320msP95时延4.2s700msP99时延9.8s1.5s吞吐12 req/s45 req/sGPU利用率20%-40%波动稳定70%左右算下来支撑同样的峰值流量GPU卡数反而减少了20%。这就是性能工程最有说服力的地方它不是逼着团队“省成本”而是用更科学的流程让同样的资源干了更多的事用户体感还更好。6.4 复盘教训性能工程最值钱的是流程和可验证性这个项目沉淀下来的几条经验我到现在都在用基线数据是最大的资产。没有基线后面所有优化都说不清楚收益。先看时间线再看利用率。GPU利用率这种聚合指标只能给方向真正定位要靠profiler把时间线拉出来看。优化要按ROI排队。我们这次三个问题里换个引擎收益最大量化排第二prompt caching是锦上添花。如果顺序反了先费劲做量化再做引擎替换效果会被掩盖返工成本很高。任何优化上线都必须带A/B验证。这次INT8量化后我们在评测集上专门检查过生成质量确认没有明显回退才放量。做性能工程这几年我最深的感受是它没有一招鲜的秘诀靠的是稳定可靠的流程。可量化的基线、逐层下钻的定位方法、按ROI排序的优化节奏这三件事做好大多数性能问题都能在两天内定位到根因一周内给出有效方案。反过来如果上来就换引擎、调参数、加机器全凭感觉走那性能工程很容易变成一次次“实验性碰运气”既浪费资源也消耗团队信心。另一个想特别提醒的性能工程一定要把用户可感知的时延放在第一位。内部指标再漂亮比如GPU利用率从20%拉到了80%但如果用户端P99还是没降下来那这个优化就是自嗨。所有工程动作最终都要为用户体感负责这是AI系统性能工程里最不该忘掉的一条底线。