开工前先说明一句:这个系列的第一篇,我讲了性能工程的基础框架——从指标采集到瓶颈定位,从CPU/GPU profiling到火焰图分析。不少朋友看完私信问我,说你给的思路是对的,但真正落到生产环境,一堆具体问题还是不知道从哪里下手。所以这第二篇,我不讲理论了,直接上实操,把我自己在线上环境里折腾过的那些优化手段、踩过的坑、以及最终验证有效的方案,按一条完整的链路梳理出来。涉及的场景包括推荐系统在线推理、大模型文本生成服务、以及CV检测类的异步任务,都是真实业务里最常见的三类AI负载。
这篇内容的核心关键词是AI系统性能工程,定位是“能直接抄作业”的实战篇。适合正在做模型上线、推理服务优化、容量评估的算法工程师和后台开发,也适合刚接手AI基础设施、需要快速建立优化思路的负责人。读完你至少能回答三个问题:线上性能瓶颈该怎么系统性定位?模型推理侧有哪些优化手段是性价比最高的?服务侧的调度和扩缩容怎么配置才能不翻车?下面直接进入正文。
1. 性能工程到底在解决什么问题
1.1 性能不是“调优”,而是一条闭环链路
很多团队对性能工程的理解是“优化”两个字:模型慢了就量化,服务慢了就加机器,显存不够就砍并发。这种点状思维在业务初期勉强够用,但一旦模型迭代频率上来、流量波动变大,你很快就会陷入“按下葫芦浮起瓢”的循环。我今天想先把一个底层认知讲清楚:性能工程是一条闭环链路,至少包含四个环节——指标定义、感知监测、瓶颈定位、优化实施,缺一个都转不起来。
先看指标定义。很多团队上线AI服务时只盯着一个QPS,或者只看一个平均延迟,这远远不够。真实场景里,同一套模型服务,线上用户体感好不好,取决于TP50、TP95、TP99这三个分位延迟;系统稳不稳定,取决于最大尾延迟(Max Latency)和超时率;资源效率高不高,取决于GPU利用率、显存占用和每卡每秒处理的Token数/图片数。我见过太多团队,服务偶尔告警,打开监控一看全是平均值,完全看不出来猫腻在哪儿。性能工程的起点,必须是把这些指标按层拆开:在线服务层看延迟分位和错误率,资源层看GPU/CPU/内存利用率,模型层看推理的纯理论耗时(比如固定batch=1时的单次前向时间)。只有指标铺得足够细,后面定位问题才有依据。
第二步是感知监测。这块很多团队有误区,以为接了Prometheus就是做了监控。实际上,AI服务有一个特别容易被忽略的监测点:批次(batch)内部的尾延迟放大效应。在线推理为了吞吐,通常会做动态batching,但一个batch里只要有一个长尾请求,整个batch都得等它跑完,于是这个batch的延迟会被拉长到异常值。如果你监测的粒度是“每秒平均延迟”,这种效应根本看不见。正确做法是给每个请求打上序号,把延迟分位和batch_size这两个维度做交叉关联监控,一旦发现TP99的延迟曲线和batch_size曲线重合度很高,立刻能锁定是动态batching导致的排队放大。这个坑我在很多客户现场都见过,提前说一句,后面会详细讲怎么做。
瓶颈定位和优化实施我后面用专门章节展开,这里只需要记住一个结论:性能工程这件事,本质上不是某一次“调优大扫除”,而是把“发现慢请求→定位慢环节→实施改动→回归验证”变成团队的日常常态。谁先建成这个闭环,谁就掌握了整个系统的性能主动权。
1.2 分层优化的总体思路:模型、服务、资源三手抓
我给团队定的优化框架就三个层次:模型层、服务层、资源层。模型层解决的是“单次推理到底能不能更快”,服务层解决的是“单位时间到底能吞多少请求”,资源层解决的是“每张卡到底有没有被榨干”。这三层不是先后关系,而是互相制约的:模型层做到极致,服务层排队还是排死,QPS照样上不去;服务层调度做得再好,模型层面算子本身就是低效实现,资源层利用率也注定上不去。
具体到实操,我建议的优化顺序是“先服务、再模型、后资源”。为什么这么排?因为服务层改动通常成本最低、收益最快。比如调整一下并发度和动态batching的触发阈值,可能直接让吞吐翻倍;而模型层的量化、算子融合,往往涉及精度验证和回归测试,周期长;资源层优化则往往是前两层做完之后才能看到真实瓶颈的,一上来就调显存分配策略,很容易被表面现象误导。
这个顺序也决定了博主文的展开方式。下面先从压测和容量规划讲起,因为不管你对模型层还是服务层动手,都需要一个可靠的“测量基准”来判断改动到底有没有效。没有这个基准,一切优化都是嘴炮。
2. 性能基线、压测与容量规划
2.1 先把指标定义清楚:从延迟分位到有效吞吐
聊AI系统性能工程,第一步永远是回答一个问题:**这个系统“快”和“稳”用什么数字来衡量?**我的建议是,在线推理服务至少盯住四个指标:TP50、TP95(或者TP99)、错误率、有效吞吐。TP50代表多数用户的体感,TP95代表长尾体验,错误率代表可用性,有效吞吐代表真实处理能力。这里的“有效吞吐”特别容易理解错,它不是压测工具打出来的QPS,而是“在满足延迟SLO的前提下,系统每秒能成功处理多少请求”。举一个例子:你压测打出来了1000 QPS,但其中有300个请求延迟超过SLO 500ms,那么有效吞吐只有700 QPS,剩下300个虽然也返回了结果,但对业务是“无效”的。
大模型文本生成类的服务,指标定义还要更细一些。这类服务的流式输出特性,决定了它有两个独立延迟指标:首Token延迟(TTFT,Time To First Token)和Token间延迟(ITL,Inter-Token Latency),单位通常用毫秒/Token(或Token/s)。TTFT直接影响用户“开始响应”的体感,ITL决定生成流畅度。实践中我会把这两个指标分别定SLO:TTFT一般控制在200ms~1s以内(视应用场景浮动,聊天机器人要有对话感,TTFT要压到300ms内,内容生成工具可以放宽到2s),ITL稳定在50~100ms/Token(即每秒10~20个Token)是比较可接受的区间。如果只看整体平均延迟,TTFT和ITL各自的问题会被互相掩盖,这是大模型服务做性能评估时最典型的失误。
还有一类容易被忽视的任务:离线或近线的CV批量任务。这类任务没有在线延迟的体感压力,核心指标是“吞吐成本比”,即单卡每秒处理多少张图、每万张图消耗多少GPU时。它的性能优化思路跟在线服务完全相反,可以接受更激进的量化、更长的排队时间,只要能压成本。
2.2 压测到底怎么测:流量模型、工具选型与SLA判定
压测不是拿个工具随便打满CPU就行,一个不贴近真实场景的压测结果,甚至会把你带到坑里去。先说流量模型。真实线上流量的核心特征是不平稳:有请求稀疏的凌晨,有瞬间打满的峰值,有长尾的高延迟请求混在正常请求里。压测时如果只用恒定并发去压,测出来的只是系统的“峰值能力上限”,不是“真实负载下的表现”。我的做法是先拉线上前7天真实访问日志,提取请求间隔分布、请求体大小分布、batch并发情况,然后按比例回放到测试环境。
工具选型上分两类场景。在线HTTP推理服务,我常用的是wrk、k6这类通用压测工具,配合自定义脚本模拟真实请求体。专用AI推理压测,推荐用vLLM自带的benchmark脚本,或者开源的LLMPerf、原生的ghz(gRPC场景),它们能直接输出TTFT和ITL的分位数,省去自己解析日志的麻烦。如果你用的是Triton Inference Server,它自带的perf_analyzer是首选,能直接联动模型配置做动态batch压测。压测时长上,我有个硬性要求:单轮压测至少15分钟以上,且要覆盖“预热期→稳定期→衰减期”完整过程。AI服务都有预热特性(CUDA Kernel初始化、显存缓存、连接池建立),前1-3分钟性能数据是不能用的。
判定标准用的是SLO(服务等级目标),但这里的坑是SLO的粒度。假如业务定义的SLO是“95%请求延迟小于300ms”,压测时你不能只看整段压测总体的P95,而要按“每5秒切一个窗口,逐个窗口判P95是否达标”,最终统计达标窗口占比。这样能识别出“平均看起来达标,但频繁出现秒级抖动”的系统,否则上线第一天就会被真实的毛刺流量教育。
2.3 容量规划的算例:一套可以套用的计算方法
容量规划是性能工程里最容易被忽略、但业务价值最高的一项工作。我直接给一个真实案例的计算过程。背景是一个基于vLLM部署的7B参数Chat模型服务,单卡A100 80G,目标是支撑200路并发在线对话。先测出一个关键基础数据:单卡单请求(batch=1)时,平均to生成速度约45 Token/s,TTFT约280ms。再贪心算一下:200路并发,每路平均每5秒发一个带10 Token提示词的请求,每轮回答期望长度300 Token,整个人群的平均生成速率就是200路 × 300Token ÷ 5s = 12000 Token/s。单卡45 Token/s,理论上需要12000÷45=267张卡。这显然不现实,所以要靠“动态batching提升吞吐”,实际压测中单卡通过连续批处理(continuous batching)可以把吞吐拉到2800 Token/s左右,于是一台8卡的A100节点,理论吞吐约22400 Token/s,除了覆盖12000 Token/s的均值需求外,还能扛住大概1.8倍的流量峰值。这个“幅余量”就是容量规划的关键变量。
这个例子想说明两个结论。第一,容量规划必须基于“有效吞吐+SLO双约束”,只按模型单次延迟来算,结果会离谱。第二,一定要为峰值预留幅余系数。我见过很多团队按均值算好了机器数,结果上线遇到流量峰值的毛刺,直接超时告警。在线推理服务,峰值/均值比建议至少留1.5到2倍,具体看业务波动的剧烈程度。如果你的业务有明显的秒杀或营销活动,这个系数还要更大。
压测和容量估算做完,你对系统“现在什么水平”就有了相对可靠的基准线。下面就可以动刀了。先从模型层开始——这块的优化空间,绝大多数团队连一半都没挖出来。
3. 推理侧优化:把模型本身“瘦身”和“提速”
3.1 精度量化的三级跳:FP16到INT8/FP8/INT4
模型层优化里,通常首先做的、也是见效最快的就是量化。核心原理一句话:推理计算时,用更低比特的数据类型表示权重和激活值,减少显存占用,同时利用硬件对低精度计算的优化加速。主流的路径是从FP16/BF16起步,先降到INT8(或FP8),再按需尝试INT4。每一级都能省约一半显存,并在计算密集算子(比如GEMM矩阵乘)上得到可观加速。
但量化不是无脑降,最难的是精度损失的控制。我的经验是遵循“三步走”。第一步,先量化权重(weight-only),激活值保持FP16,这个改动对精度影响最小,适合大多数LLM场景;第二步,权重量化+激活量化,需要准备校准数据集(一般是训练集或真实推理样本里抽几百条),选好校准方法(如MinMax、Percentile、KL散度等);第三步,如果精度还不达标,做混合精度量化,把敏感层(比如注意力层的QKV投影)保留为更高精度,其余层用低精度。自动化的工具链层面,PyTorch的torchao、HuggingFace的Optimum、以及TensorRT的模型转换工具,都能做逐层敏感性分析,推荐先跑一遍量化敏感度热力图再决定哪些层保留高精度,而不是一刀切。
实际操作里,有一个特别容易踩的坑:校验量化效果时不要只对比整体精度指标(如BLEU、准确率),要拆层看激活值分布。尤其要注意极端离群值(outlier)。Transformer模型里,某些维度(通常是embedding层和注意力得分)激活值范围特别大,甚至个别通道超过正常值几个数量级,这些离群值一旦被INT8截断,会直接在输出层放大成明显的内容质量下降。解法一般两个方向:一是per-channel而非per-tensor做量化(对权重按行、对激活按通道缩放,本质是把每个通道单独量化,贴合真实分布),二是对含离群值的通道直接跳过量化。这两招解决了90%的量化精度翻车问题。
3.2 KV Cache与连续批处理:决定LLM吞吐的一对组合拳
大模型生成的性能核心,跟传统模型最大的不同是KV Cache。Transformer解码的时候,每生成一个Token,都要把当前Step的Key和Value缓存起来,下一个Step做Attention时直接复用。这套缓存对应的是显存里一块动态分配的区域。很多人对性能工程的怀疑就出在这里:明明模型本身很小,显存却动不动就爆。原因就是KV Cache和动态batch争抢显存。
KV Cache的管理,核心参数是max_seq_len、gpu_memory_utilization和max_num_batched_tokens。我给一组经过多轮压测验证的起始配置(以7B~13B模型、单卡A100/A800 80G为例):gpu_memory_utilization设为0.85~0.9(给CUDA context、计算图预留10%~15%的显存余量),max_num_batched_tokens设为4096~8192,max_seq_len按业务最长对话长度来定,比如业务允许最多10轮对话,每轮平均500Token,候选人最大序列就至少需要6000左右。这组参数不建议盲目拉到极限,显存利用率拉满到0.95以上,一旦遇到突发长对话会直接OOM,导致整个服务重启。
连续批处理(continuous batching)是另一个吞吐利器。传统静态batching要等同一个batch的全部请求都生成完毕才一起释放,而连续批处理做到“一个batch里有请求提前结束就立刻腾出位置,新请求马上补进来”,显存和算力的空窗期被大幅压缩。用过vLLM、TensorRT-LLM的朋友应该熟悉,vLLM默认开启该机制。但要注意,连续批处理在请求长度悬殊极大的场景下,会放大长请求的延迟,因为短请求虽然很快结束,但长请求占用的KV Cache会持续挤压新请求的空间。解决办法是按预估长度做排队分组:长对话走长序列队列,短查询走短序列队列,两个队列里的batch分别调度,避免“一个长尾巴拖死一整车”。
3.3 算子融合与计算图优化:0.5ms级别扣出来的性能
模型层的另一块重要优化是计算图层面的算子融合(Operator Fusion)。原理很好理解:GPU执行一个算子的开销,除了计算本身,还包括kernel启动、显存读写、数据搬运。两个相邻算子如果能合并成一个CUDA Kernel,省掉的kernel启动时间和中间量显存读写,积少成多非常可观。
最典型的成果是FlashAttention系列。传统Attention要保存完整的S矩阵(注:即中间注意力得分矩阵)到显存,再读出来做softmax,读写量巨大;FlashAttention分块计算,利用SRAM的片上高速缓存,不需要整块S矩阵落回HBM,attention的延迟和显存占用同时大幅下降。业界很多框架已经默认开启,你不需要自己实现,但做性能分析时需要知道:算子融合之后,profiling看到的kernel数量会变少,算子边界会变得模糊,不要拿旧的算子耗时台账去套新框架。
实操中自己动手做算子融合的场景,更多出现在自定义模型或非Transformer结构的网络里。推荐工具是PyTorch 2.0+的torch.compile,配合inductor后端可以自动做算子融合和代码生成。我在一个自研的3D点云检测模型上实测过,从FP32切到混合精度、再开torch.compile,推理单帧耗时从32ms压到18ms,优化幅度接近44%,而精度损失小于0.3%。收益很大,但有个注意点:torch.compile编译第一次调用有秒级的冷启动开销,在延迟敏感的服务上要结合预热机制使用,后面服务层部分我会讲到。
模型层优化路线走完,你在profiling里会看到单次推理的耗时已经明显下降。但这不是终点,因为在线服务的真实瓶颈,经常不在推理计算本身,而在调度、排队和缓存这些“服务层问题”上。接下来这部分,是很多人在优化时最容易迷茫的地方。
4. 服务侧优化:调度、缓存与扩缩容
4.1 推理引擎选型:vLLM、TensorRT-LLM还是自研
服务侧的起点是推理引擎选型。LLM生态里,目前主流的是vLLM、TensorRT-LLM、SGLang、以及Triton这种通用推理服务器。我的选型经验不是“谁火选谁”,而是按场景需求分三类。
纯追求吞吐、绑定额外卡型,vLLM是最省心的,PagedAttention对KV Cache的显存管理做得最成熟,社区生态最活跃,迭代出了很多实用特性(前缀缓存、自动抢占等)。如果你的部署环境是H20/A100这类N卡,且希望用最少的代码把在线LLM服务跑起来,vLLM基本闭眼选。追求极致硬件利用,TensorRT-LLM更合适,它能配合TensorRT的plan文件做深度图优化,对算子融合、显存布局的控制更细,但代价是模型转换流程繁琐,fp16/int8等精度组合要在模型构建阶段就确定,后期想改精度就得重新转plan文件,灵活度差一些。想统一服务多模型、多框架,Triton Inference Server是正路,它能把PyTorch、TensorRT、vLLM等后端统一挂在一个服务端口下,天然支持动态batch、并发模型实例,适合做企业级AI平台。
真实建议:如果团队里没有专门的推理优化工程师,别一上来就啃TensorRT-LLM。vLLM + 良好配置通常能把90%场景的性能吃透。务实的做法是先用vLLM上线跑流量,用压测数据确认瓶颈是否真的在引擎层面,如果发现单卡吞吐上不去了,再考虑上TensorRT-LLM这种底层的精细化优化。我见过几次团队提前折腾一个月转引擎,结果压测一测,瓶颈在显存配置和排队逻辑,白白消耗精力。
4.2 前缀缓存与大模型服务:80%请求复用的隐藏收益
LLM服务性能优化的一个“隐藏副本”是前缀缓存(Prefix Caching)。真实业务里,很多请求的Prompt前缀高度重合。典型的场景包括:多轮对话的系统提示词(System Prompt)完全一样,Agent工作流里不同请求共享同一段工具定义和上下文说明,知识库问答里检索回来的文档片段完全相同。如果每次请求都从头缓存KV,这些重复的前缀部分会重复计算Attention、重复占显存;如果能把前缀算好放进缓存、新请求按前缀匹配复用,TTFT和整体生成耗时能显著下降。
这类技巧,在vLLM里对应的是自动前缀缓存机制(enable_prefix_caching),SGLang则实现了RadixAttention,它用树状结构组织KV Cache,支持更细粒度、更大规模的前缀/子串复用。我做过一个Agent场景的实测:一个工具调用类Agent的Prompt前缀约1200 Token,开启前缀缓存后,命中率在80%以上,单请求TTFT从平均400ms降到180ms,整机可支撑的并发路数提升了约40%。如果你还没有注意到这一项,建议立刻检查一下线上请求日志,统计同前缀请求的占比,通常都不会让你失望。
但缓存不是永远开启就完事,有两个坑。第一个是显存放大风险:KV Cache如果开太大,复用不到的位置也全部留在显存里,会把正常的推理挤爆,需要控制缓存上限(vLLM的max_cache_block或等价配置)结合显存余量调。第二个是缓存策略跟动态batching的先后顺序:缓存命中的请求应该优先调度,命中率低的冷启动请求排后面,这需要在排队调度时把“是否命中前缀缓存”作为优先级权重。如果只是简单粗暴地全部压进一个队列,前缀缓存带来的收益会被冷启动请求稀释。
4.3 自动扩缩容与预热:别只盯着QPS
在线AI服务的扩缩容跟普通Web服务有很大不同。普通Web服务扩容看QPS和CPU,加机器后基本秒级生效;AI服务(尤其LLM)扩容有冷启动问题:模型权重加载、CUDA context初始化、显存分配、kernel缓存预热,都是分钟级的。如果你按QPS触发扩容,等机器拉起来,高峰可能已经过去了。
我的经验是设计两级联动。第一级,基于指标预测的定时扩容:用历史流量规律(比如每天早上10点的业务高峰),提前20分钟预先把应用扩到目标实例数,让服务有足够时间完成模型加载和预热。第二级,基于请求队列深度的实时扩容:在线服务里,入口网关统计“等待中的请求数量”而不是实时QPS,当排队数超过阈值,立刻触发扩容。这样避免“打满才扩容”带来的延迟雪崩。缩容也要留“冷却时间”,通常至少30分钟内不回收实例,防止频繁波动的震荡效应。AI服务缩容后,释放出来的实例如果被迅速回收再扩容,冷启动的代价比多留几个空闲实例昂贵得多。
预热这块,正式流量到达前一定要做“假请求预热”,让CUDA kernel完成编译缓存、显存分配稳定。我在上一篇文章里专门讲过预热请求的设计细节,这里只强调两点:预热请求的batch_size要与真实请求最常用的档位对齐,同时要有足够多的轮数(10轮以上)。否则你只预热了batch=1,线上来一个batch=8的请求,照样秒级延迟飙升。
服务层的调度、缓存、伸缩这些“上层建筑”搭好之后,整个系统的性能框架算是立住了。但我必须要说,再好的框架在实际运行中都难免出问题,而且AI性能问题往往有一个特性——在不同团队里反复以相同的方式出现。我下一部分就是把这些问题摊开,用实录的方式告诉你排查思路和避坑手段。
5. 常见问题与排查技巧实录
5.1 排查思路总览:从入口到模型的四步法
面对线上AI服务的性能问题,最忌讳拿到监控面板就乱翻。我给自己定的排查流程严格按四步走,基本能覆盖八成以上的问题。
第一步,先看入口网关。延迟飙升时,先确认是“请求量变多了”还是“单位请求变慢了”。入口层的P99延迟、超时率、排队长度,能快速区分这两类原因。如果QPS没变但延迟涨了,问题大概率在服务内部;如果QPS涨了,优先看是不是扩容跟不上。
第二步,看推理服务内部队列。AI推理服务基本都有输入队列(等待调度)和batch执行队列(正在计算)。这两个队列的深度,是判断瓶颈在调度端还是计算端的核心依据。等待调度队列长,说明调度吞吐吃紧;batch执行队列长且batch_size较高,说明计算资源可能不够;batch_size低但执行时间长,则大概率是单请求性能问题(算子效率、显存碎片等)。
第三步,看GPU/CPU资源利用率与计量单位。GPU利用率要看流式多处理器(SM)利用率,而不是“GPU利用率”这个笼统数字。有时候SM利用率很低但延迟很高,说明瓶颈在显存带宽、数据搬运或host端(CPU数据预处理),GPU在等数据的话SM利用率是上不去的。这个判断点我在很多线上案例里都用得上。
第四步,看模型层面的profiling。如果前三步都排除了,就要回到模型计算图,用Nsight Systems、PyTorch Profiler这类工具逐算子看耗时分布。注意对比“优化前/后”的profile差异,结合我们模型层提到的量化、算子融合方案做针对性调整。
5.2 高频问题与解法对照:一张表查着做
下面这张表,是我在过去一年多时间里在真实项目里遇到的高频问题的浓缩。按频率排序,覆盖了LLM和CV两类场景。
| 高频问题 | 典型表现 | 根因定位方向 | 推荐解法 |
|---|---|---|---|
| GPU利用率低但延迟高 | SM利用率低于40%,P99延迟超过SLO | 数据传输耗时过长或host端预处理慢;动态batch未触发 | 缩小数据预处理到GPU之间链路;加大batch阈值;检查是否H2D拷贝占大头 |
| 显存OOM导致服务重启 | 运行一段时间出现CUDA OOM | KV Cache预留不足或碎片化;并发设置过大 | 调低gpu_memory_utilization;开启KV Cache的显存复用;加长队列等待时间 |
| 量化后内容质量明显下降 | 输出重复率上升,事实性错误增多 | 校准集和线上分布偏差大;离群值通道未保护 | 重新采样线上真实Prompt做校准;per-channel量化;敏感层保留高精度 |
| 扩缩容后延迟没降反升 | 加机器后告警更多 | 冷启动未完成,新实例在重建缓存和kernel | 扩容提前量加长;用预置实例池;关闭频繁缩容 |
| 单卡吞吐上不去 | QPS压测到一定数值后上不去了 | 显存仍未跑满,算子存在低效模式 | 用Nsight做算子热力分析;开启torch.compile或改用TensorRT后端 |
| 多个模型混布时互相干扰 | 服务整体延迟漂移大 | 显存不隔离;算力争抢 | 用MPS或显存限额做资源隔离;按优先级分组调度 |
这张表不是看了就行,我建议你直接照着它去检查自己的环境,每一项都能落地验证。下面挑两个最典型的展开讲一下排查的心路。
5.3 性能回放:一个非常实用的“抓现场”技巧
最后分享一个我个人非常偏爱、但知道的人不多的技巧:流量录制与性能回放。原理跟网络抓包回放类似,但针对AI服务要做得更细。在入口网关层把真实线上请求录制下来(包括请求体、时间戳、上游特征、batch情况),然后离线导入到一个“影子环境”里重新压测。这么做有一个巨大的好处:当线上出现一个诡异的性能抖动时,你可以在测试环境“按帧复盘”,同样的请求序列、同样的并发波形,反复使用各种优化方案去还原、去实验。
我遇到过最典型的案例:一个线上服务每周三下午准时出现TP99飙高,排除了所有资源层面原因后,最后用回放才发现——每周三下午的那批定时任务会触发一批超长Prompt的请求,平均长度是正常请求的4倍,直接拖垮了batch效率。如果不用回放,这种周期性长尾问题很难靠肉眼看日志找出来。回放环境里,我可以用一批真实流量把问题还原,再验证“对长Prompt单独设队列”的解决方案是否有效,整个过程一个小时就能完成。
流量录制实现不复杂,入口网关(比如Nginx层)加一个log_format扩展,把请求体记录下来,后端用自研脚本离线回放。如果是gRPC接口,用gorpcap加自研脚本也能实现。重点是对录制的流量做脱敏,很重要的是去掉报文里的用户隐私字段,只保留长度、内容主题和结构信息。回放时注意调整时间戳,模拟真实请求的到达间隔,不要一股脑全打进去,否则压出来的数据失真。
结束语:性能工程是一场持续的手感积累
做AI系统性能工程这几年,我最大的体会是:真正困难的地方,并不是那些高深的算法或硬核的底层优化技巧,而是如何在纷繁复杂的线上信号里,准确区分“表现”和“根因”。这个手感没有捷径,只能靠一次次地压测、回放、分析、验证去积累。上面提到的所有方法,都是在实战里摔过跟头之后才沉淀下来的。我更想建议的是,每个团队都应该形成自己的“性能调优Checklist”——把我们这篇讲的指标定义、压测流程、量化步骤、服务配置、问题排查表格化,每次遇到新问题先查一遍,不要每次都从零开始摸索。AI系统性能工程还有太多没展开的细节,比如多模态模型的显存规划、异构算力调度、GPU虚拟化的性能损耗分析,这些之后的文章我会逐一展开讲。最后留给大家一个行动建议:拿你这周最核心的一个推理服务,先从记录延迟分位和batch_size的相关性开始,看看第一个问题能不能浮出水面。