大模型推理服务性能压测实战:从指标体系到瓶颈定位

📅 发布时间:2026/9/8 22:06:23
大模型推理服务性能压测实战:从指标体系到瓶颈定位
我见过不少团队把AI服务的性能测试直接套用Web接口压测那套方法论起一台压测机JMeter配100个线程跑10分钟看下平均响应时间和错误率然后得出一个“系统挺稳”的结论。等到上线那天GPU显存突然被打满推理队列里积压了几万个请求客户端全部超时重试最终服务雪崩。这种场景我一年能遇到好几次。AI系统性能测试之所以不能照搬传统套路是因为从请求进入服务到最终返回结果中间隔着预处理、排队、动态批处理、显存调度、模型推理、后处理这一长串环节每个环节都可能成为瓶颈而且它们之间的耦合关系比普通Web服务复杂得多。这篇文章我会从指标定义、环境校准、压测工具、监控采集、完整执行过程到常见误区把整套逻辑完整串一遍适合正在搭建AI性能测试体系的测试工程师也适合算法团队想自测推理服务性能的读者。1. 旧思路失灵传统压测方法论在AI系统上的四个盲区传统Web性能测试最熟悉的就是响应时间、吞吐量、错误率这三板斧。对无状态服务来说这套体系基本够用因为请求路径很短网关转发、业务处理、读存储、返回结果。但AI推理服务完全是另一码事一个请求进入服务后要经过校验、文本预处理、Token化、进入推理引擎排队、凑批、申请显存、执行前向计算、逐个输出结果中间任何一环出问题用户侧看到的表现可能都是同一个词——慢。1.1 传统指标只看得到出口看不到内部排队传统压测关心的响应时间是一个“出口指标”它回答的是“用户等了多久”但回答不了“时间花在了哪里”。在AI推理服务里这个问题会被无限放大。举一个我实测过的例子某个LLM推理服务在并发200时端到端平均响应时间是4.8秒初看好像还能接受。但如果把时间拆开真正在GPU上跑推理的时间只有600毫秒剩下4秒多全部消耗在请求排队上。这个排队时间就是动态batching机制带来的——推理引擎不会来一个请求就立刻算一个而是先攒一批攒够了或者等够了再一起算。当并发上来队列快速堆积用户感知到的“慢”实际上是排队慢不是算得慢。传统指标第二个盲区是延迟和吞吐的跷跷板效应。普通Web服务里响应时间增长通常是线性可预期的数据库慢查询、锁竞争这类问题相对容易定位。AI服务不一样给模型加大batch可以显著提升吞吐但单请求延迟也跟着涨而且是非线性涨。如果你只盯一个平均响应时间遇到batch从1调到8以后延迟翻倍的情况会完全摸不着头脑。第三个盲区是请求差异巨大。普通人刷一个网页后端处理逻辑几乎一样但AI请求完全不同输入几十个token和输入几千个token计算量差着两个数量级输出一句话和输出一篇长文占据GPU的时间也完全不同。用简单接口压测的思路去测相当于拿“所有人买一罐可乐”的模型去预测“有人买一辆车”的超市排队状况数据失真几乎是必然的。1.2 AI系统必须单独测量的性能指标矩阵既然传统指标不够用实际项目里我会把AI性能指标拆成三层来看。第一层是时延类。光有端到端延迟不够对生成型AI服务必须先量首Token延迟也就是从用户发出请求到屏幕上出现第一个字的时间这个指标直接决定了用户“卡不卡”的主观体验。然后是单Token生成间隔它反映了模型生成阶段每个字的生产速度最后才是端到端总时间。第二层是吞吐类。请求级QPS只是一个粗指标真正能衡量算力输出效率的是Token级吞吐也就是每秒生成多少个Token。两个服务可能QPS一样但一个每轮输出500字、一个每轮输出50字背后的算力消耗完全不同。另外还有理论吞吐上限和实际有效吞吐的比值这个值能暴露系统是否在空转。第三层是资源类。GPU利用率和显存占用要分开看利用率不是越高越好但如果压测时利用率只有30%说明调度有问题显存占用率到90%以上时OOM风险陡增。还要看排队深度、批大小分布、请求在队列里的等待时长。这些指标共同构成了一张“快照图”能回答瓶颈到底在CPU预处理、GPU算力、显存容量还是网络IO上。用生活化的比喻传统压测像考察便利店只有一个收银台你只看顾客结完账出门的总时长AI性能测试则是看顾客进门后排队、选购、结算、打包每个环节分别花了多久因为每个顾客要买的东西差异实在太大了。没有这套分层指标压测报告做出来就是自我安慰。2. 测试前的环境校准部署形态、框架与基准请求集很多人拿到AI服务的第一反应就是打开压测工具开始打流量这是一个非常错误的起步顺序。AI系统的性能数字对运行环境极其敏感环境不锁死、流量不标准测出来的结果根本不具备任何可比性。我甚至见过同一份压测报告里前测和后测用的模型量化方式都不一样最后被追问得下不来台。2.1 推理框架选型直接决定性能天花板同一个模型用不同推理框架部署吞吐量可以差出好几倍。原因不在模型本身而在框架的调度策略和显存管理方式。PyTorch 原生最灵活适合做实验也最“裸”。缺少高效的批处理和显存复用性能通常垫底。TorchServe官方生态完善部署方便但动态batching策略相对简单适合中低并发快速上线。Triton支持动态batching、模型并发实例、多模型共享GPU企业级特性齐全适合把多个模型放在同一批卡上管理。vLLM通过PagedAttention和continuous batching对LLM场景做了大量优化显存利用率和吞吐表现非常亮眼是目前大模型私有化部署的主流之一。TensorRT-LLM算子级优化最强需要把模型编译成TensorRT engine推理速度最快但部署复杂版本兼容和模型格式迁移成本也最高。选择哪个框架直接决定了你能压出多少性能所以压测之前的第一个动作是确定框架并且记录下来。我会在每次压测报告里放一张环境表列清楚GPU型号、显存大小、CUDA版本、推理框架及版本、模型格式比如PyTorch权重、ONNX、TensorRT engine、是否量化、量化方式、容器资源限制、卡的数量。没有这张表任何性能结论都是浮沙建塔。以vLLM为例max_num_seqs这个参数控制着同时处理的序列数量上限开大一点可以提升吞吐但显存压力跟着涨一旦超出显卡物理上限就直接OOM。我第一次压测7B模型时把max_num_seqs从默认的256调到512以为能翻倍吞吐结果压到第120个并发时GPU显存爆掉后面所有请求全部报错。所以压测第一件事是根据模型参数量、KV Cache估算和显卡显存大小先圈定一个安全区间再配合压测逐步试探。2.2 影子流量构造用线上的真实分布替代“hello world”基准请求集是性能测试可信度的另一个命门但也是被忽视得最严重的环节。我见过不少压测报告全是用一条“介绍一下你自己”这种短文本泡出来的测出来的QPS可能很高但完全不能代表线上情况。原因很简单输入越长prefill阶段的计算量越大输出越长decode阶段占用的时间和显存越多。长文本请求的比例一变整体性能立刻就会变。我推荐的做法是从线上日志采样至少500条真实请求脱敏后作为压测流量。采样时注意长度分布只留短文本不行全留长文本压力又过大最好按线上prompt长度的百分位数分布来抽样P50、P90、P99长度各留一定比例max_tokens也按线上配置做分层。如果系统还没上线没有线上日志就构造多档数据而不是单一数据我最常用的档位是128、512、2048、4096个token按7:2:1的比例混合。这个比例不是随便定的它模拟了“大部分用户问短问题、小部分用户传长文档、极少数极端场景”的真实分布。还有一个容易被忽略但坑很大的细节压测请求的body如果太大压测机本身的带宽和CPU会先被打满导致客户端成为瓶颈。尤其在流式输出的场景下每个连接保持的时间很长并发连接数会把压测机的文件句柄和内存吃光。这时候你压的其实不是AI服务而是压测机自己。3. 压测工具实操从JMeter快速验证到Python自研压测器工具选型完全看场景。AI服务的对外接口大多走HTTPJMeter这类传统压力工具有它的用武之地但如果要精确测量首Token延迟、单Token生成速度这些生成模型专属指标就必须上脚本自研压测器。现在AI编码助手能帮你快速生成压测脚本但生成出来的东西往往“能跑”指标口径对不对没人管这一点我在后面会详细讲。3.1 JMeter测AI推理服务配置方式与边界提醒用JMeter测AI服务最常规的配置是这样线程组设置并发数、Ramp-Up时间、循环次数HTTP请求取样器指向推理接口聚合报告看平均响应时间、吞吐量、错误率。想让指标和GPU监控对齐可以加上Backend Listener把实时数据上报到InfluxDB或者用简单数据写入器定期导出CSV。但有两个边界必须心里有数。一是如果接口是SSE流式返回JMeter默认的取样器会等整个流全部接收完才记录结束时间所以聚合报告里的响应时间其实是端到端总时间你拿不到首Token延迟。要拆首Token延迟需要用JSR223取样器写Groovy脚本手动解析HTTP事件流记录首Token到达时间这个复杂度已经不低了。二是JMeter跑大并发时压测机自身的TCP连接和内存成本很高尤其在流式长连接场景200个线程可能就把压测机的CPU打到80%。所以我的判断是JMeter适合快速功能摸底、简单并发验证、给领导演示“我们测了”但不适合做精细的生成类指标测量。真正要拿数字做容量评估和性能调优还是得自研。3.2 基于asyncio的自研压测脚本核心逻辑精确控制指标口径时我习惯直接用Python写异步压测脚本。核心逻辑分三块用信号量控制并发解析SSE流并记录首Token到达时间按时间窗口统计各类指标。下面是一个真实的骨架可以直接当模板用。import asyncio import aiohttp import time import statistics CONCURRENCY 50 # 并发请求数 DURATION 300 # 压测时长秒 URL http://model-service:8000/generate async def single_call(session, results, current_conc): prompt 请用三句话介绍机器学习的核心思想 payload {prompt: prompt, max_tokens: 128, stream: True} req_start time.perf_counter() try: async with session.post(URL, jsonpayload) as resp: first_token_time None token_count 0 async for line in resp.content: if line: if first_token_time is None: first_token_time time.perf_counter() - req_start # 按具体流协议解析token token_count 1 total_time time.perf_counter() - req_start results.append({ concurrency: current_conc, ttft: first_token_time, total: total_time, tokens: token_count, status: resp.status, }) except Exception as e: results.append({error: str(e), status: 0}) async def run_scenario(): semaphore asyncio.Semaphore(CONCURRENCY) results [] async with aiohttp.ClientSession() as session: async def bounded_call(): async with semaphore: await single_call(session, results, CONCURRENCY) tasks [] end_time time.time() DURATION while time.time() end_time: tasks.append(asyncio.create_task(bounded_call())) if len(tasks) 1000: await asyncio.gather(*tasks) tasks [] if tasks: await asyncio.gather(*tasks) return results这个脚本有几个设计点值得说。第一用asyncio.Semaphore控制并发数比在线程里sleep可控得多能保证压力稳定第二SSE流解析时在第一次收到数据的地方记录ttft这就是首Token延迟的真实口径第三统计窗口要单独输出tokens字段后面可以算出Token级吞吐。脚本跑完后再按P50/P95/P99做统计不要只看平均值。压测机自身的资源也要盯着如果压测机CPU打满所有结果直接作废。3.3 分场景设计基线、梯度加压与长稳定性验证脚本写好之后别上来就一把梭。我会把压测拆成三个场景每个场景解决不同的问题。第一个是基线测试只跑1个并发的请求连续请求50到100次拿到单请求的最佳延迟。这个数字是后面所有对比的地基如果第一步就慢后面调优再努力也白搭。第二个是梯度加压测试把并发数按50、100、150、200、300递增每个梯度稳定跑5到10分钟记录QPS、时延、GPU利用率的变化曲线。我要找的是那个“拐点”在某个并发之前QPS随着并发增长而增长过了这个并发之后QPS不再上升甚至下降时延开始暴涨这就是系统的过载区。容量规划时应该把运行水位压在拐点的80%左右而不是顶在拐点上。第三个是长稳定性测试在预估峰值负载的60%到80%水位下跑8到24小时重点观察显存有没有泄漏、内存有没有缓慢增长、连接有没有泄漏、时延有没有锯齿形波动。很多AI服务的坑不是压不出来而是跑久了之后表现断崖式下滑最常见的原因就是显存碎片化和Caching失效。4. 监控与指标采集只有响应时间根本定位不了瓶颈压测过程中如果只看压测端输出那你的调试手段只有“哦它变慢了”或者“哦它报错了”这一层。要定位性能瓶颈必须把系统内部和系统外部的监控数据同时采集下来并且在时间轴上对齐。没有监控的压测本质上只是给服务做了一次压力测试不叫性能分析。4.1 系统级监控DCGM、Prometheus与Grafana组合系统级监控我习惯用DCGMNVIDIA Data Center GPU Manager采集GPU指标配合Prometheus存储、Grafana展示。需要采集的GPU指标至少有这么几个GPU利用率、显存利用率、显存占用字节数、GPU温度和功耗、GPU显存带宽利用率。别小看显存带宽利用率很多LLM推理瓶颈不在算力而在显存带宽decode阶段尤其明显。CPU和内存层面用node_exporter就能覆盖重点看CPU核心使用率、负载均值、内存使用率。还有一个经常被忽视的是网络指标有条命令叫iftop可以看到实时的连接带宽。我遇到过一次压测吞吐上不去看了半天GPU完全闲着最后发现是压测机到GPU服务器之间的网络带宽被打满了请求是送到了但返回结果在网口排着队。建议在Grafana里把压测客户端记录的QPS/时延曲线和服务器的GPU指标/网络指标放在同一个时间轴面板里这样一旦出现“QPS下降、GPU利用率也下降”的情况基本可以快速判断是上游没把请求送进来而不是GPU算不过来。4.2 服务内部透视排队延迟、批大小、KV Cache占用系统级指标只能回答“资源用没用满”回答不了“请求在里面经历了什么”。这个时候就需要服务内部的指标了。vLLM这类现代推理框架自带metrics接口可以抓到排队队列长度、当前正在处理的请求数、KV Cache使用率、动态批大小分布等数据。Triton也有类似的能力可以看每个模型实例的请求队列深度、推理执行时间、批大小统计。我把这些内部指标分成三组看。第一组是排队指标包括队列长度和平均排队时间。排队时间持续上涨说明进来的请求速度超过了引擎处理速度系统正在进入过载状态。第二组是批处理指标包括当前batch大小、历史batch大小分布。如果batch长期只有1到2说明并发不够吞吐潜力没被挖出来如果batch长期贴着上限同时排队时间暴涨说明引擎已经拼不动了。第三组是显存内部指标KV Cache使用率超过90%时系统会开始做显存释放甚至拒绝新请求这时候QPS会断崖式下跌。这里还有一个实战经验压测过程中一定要记录客户端出现错误的时间点。AI服务超时后会重试而重试会把已经过载的服务再往上加码造成雪崩。我在一次压测里看到客户端错误率从0突然跳到30%服务端GPU利用率反而从95%掉到60%就是典型的超时重试导致的“拖垮自己人”现象。没有时间戳对齐这种问题靠肉眼几乎发现不了。4.3 手动记录关键节点压测日志里的“锚点”虽然工具能采集大量指标我仍然建议压测负责人手动维护一个简单的执行日志内容不用多就三列时间点、操作、备注。例如“14:30:00并发从100调到150”“14:35:12服务端日志出现OOM关键字”“14:36:40客户端开始大量报错”。这个日志看似原始但在复盘时价值极高因为它能把自动化指标和人工判断串成一条时间线帮你快速锁定现象出现的先后次序。AI系统的性能问题往往是多因素叠加的结果有了时间锚点至少能分清因果先后不至于瞎猜。5. 完整实例复盘一次7B模型推理服务的性能压测理论讲再多不如把一次真实的压测过程完整走一遍。下面是我前段时间为某个7B Chat模型推理服务做的性能测试复盘框架用的vLLM单卡A100 80G。为了不涉及具体业务部分参数我做了简化但执行思路完全是真实可复现的。5.1 测试环境与参数约定压测前锁定的环境如下GPUNVIDIA A100 80G 单卡推理框架vLLM 0.4.2max_num_seqs256max_model_len4096模型7B Chat模型BF16加载压测脚本自研Python asyncio脚本请求分布prompt长度按128/512/2048按7:2:1混合max_tokens两档输出128和512并发梯度50、100、150、200每个梯度跑10分钟前3分钟做预热丢弃只统计后7分钟之所以单独留预热时间是因为模型加载后CUDA kernel需要编译、显存页需要分配前几分钟的数据完全失真。这个细节常常被忽略但它对结果的影响很大不预热直接统计测出来的TP99能高出两倍。5.2 梯度加压数据与拐点判断压测结果我整理成了简化表方便看趋势并发QPS首Token P95单Token生成 P95GPU利用率显存占用503.80.8s45ms61%58%1006.91.4s48ms87%74%1507.23.9s52ms94%88%2006.18.6s66ms96%93%从数据里能读出几个关键结论。并发50到100时QPS从3.8涨到6.9接近线性增长说明系统在这个区间内是正常服务状态。并发100到150时QPS只从6.9涨到7.2几乎没动但首Token延迟P95从1.4秒跳到3.9秒GPU利用率冲到94%。这说明系统的计算资源接近饱和新增的并发并没有换来吞吐而是全部变成了排队时间。到并发200时QPS反而掉到6.1首Token延迟涨到8.6秒系统已经进入过载区大量请求在排队中等待部分客户端开始超时。拐点非常清晰地落在并发100到150之间。按照“运行水位压在拐点的80%”原则这个服务的上线推荐并发应该是100左右超过这个水位就需要扩容或者加副本。5.3 瓶颈定位与调优动作数据出来了接下来的问题是瓶颈在哪儿第一直觉肯定怀疑GPU算力但通过监控曲线我发现GPU利用率在并发100时已经87%可显存带宽利用率只有60%左右显存占用74%也不算顶格。这说明系统里还有一段路在“堵车”但不是GPU算力顶到了墙而是调度和批处理策略没有完全发挥硬件潜力。沿着这个线索往下查我在vLLM的服务日志里发现一个问题当并发上来后请求几乎都在等待调度器分配KV Cache队列轮转的等待时间占了请求总耗时的一半以上。这里有两个优化方向一是把max_num_seqs再往上调让调度器一次性允许更多序列进入KV Cache但这个操作会增加显存压力二是把模型量化到AWQ 4bit显著减小KV Cache的显存占用让同样的显存空间能容纳更长的序列和更大的批。我按两步走的思路做了优化。第一步先把max_num_seqs从256调到384显存占用率从93%上升到接近97%吞吐小幅涨了一点但排队延迟并没有明显下降方案不够彻底。第二步引入AWQ 4bit量化之后模型权重从约14GB降到约4GBKV Cache可用空间大幅增加最终把max_num_seqs稳定在512同时首Token延迟P95降回1.8秒QPS从6.9提升到9.5左右。调优效果非常明显但也暴露了一个事实AI性能测试的价值不在“压出问题”而在通过压测把问题逼出来、再通过监控数据判断该往哪个方向调。6. 经验沉淀高频误区与AI性能测试岗位的面试思路这部分内容来自我自己的踩坑记录和面试别人时候的常见回答观察希望能帮你少走弯路。6.1 我在实际压测中踩过的几个高频坑第一个坑是拿单条文本测所有场景。刚接触AI性能测试时我也图省事用过一条短文本跑完整个压测。后来发现线上某一个客户端的prompt长度是这条文本的20倍那个场景的延迟根本兜不住。现在的习惯是无论多紧急至少要构造三档以上长度的请求数据。第二个坑是混淆并发数和QPS。并发数是压测端同时建立的连接数QPS是服务端每秒完成的请求数这两者之间有一个系数等于单个请求的平均耗时。很多人把“并发200”直接等同为“每秒200个请求”这是完全错误的。这个混淆会导致压测场景设计严重失真。更合理的描述方式应该是先明确目标QPS再根据预估的单请求耗时反推需要多少并发。第三个坑是不预热就统计。模型加载、CUDA kernel编译、显存分配这些动作会让前几分钟的性能数据严重失真。我见过不止一次压测报告里前两分钟的TP99高到离谱但写报告的人没有丢弃预热期数据导致整个结论失真。现在我在所有压测脚本里都加了一个“前N分钟数据丢弃”的机制N取决于服务特性一般取3到10分钟。第四个坑是只看平均延迟。AI推理服务受batching影响延迟分布往往右偏严重。均值完全可能被一小撮长尾拖到不可信所以P50、P95、P99必须作为标配。尤其是首Token延迟P99和P50之间能差出五倍以上这是常态而不是异常。第五个坑是忽略压测机自身资源。压测机打满之后压出来的所有数据都“仅供参考”。我现在会在压测启动前和压测结束后各跑一次压测机的CPU/内存/网络基线确保客户端本身没有成为瓶颈。否则你优化了半天服务最后发现是压测机的网卡先投降了。6.2 面试官常问的性能测试问题应该怎么答AI系统性能测试的岗位面试题核心考察的是候选人能不能把“传统压测”和“AI推理特征”结合起来。我整理几个高频问题说说我的回答思路。“如何测试一个大模型推理服务的性能”不要只回答“用JMeter压接口”。我会从指标体系讲起首Token延迟、单Token生成速度、Token级吞吐、请求级QPS、GPU利用率和显存水位是必测项然后说测试数据构造强调线上流量回放和长度分布再说场景划分基线、梯度加压、长稳定性缺一不可最后提到监控和瓶颈定位的闭环。这样的回答能体现完整的工程思维。“LLM服务压测中QPS上不去怎么定位”这个问题我会按层次排查。第一层看后端负载GPU利用率低但显存占用高优先怀疑KV Cache碎片化GPU利用率高但QPS上不去优先怀疑批处理策略和并发限制参数GPU利用率和显存都低去看预处理阶段和网络链路。第二层看调度器队列长度是不是飙升、请求在等待什么。第三层看客户端是不是压测机自己先到瓶颈了。整个思路是“自底向上不要瞎猜”。“什么是动态batching和continuous batching对性能有什么影响”动态batching是等一小段时间把到达的请求拼成一个batch再统一推理能提高吞吐但会引入排队时延。continuous batching是vLLM这类框架的实现在生成阶段把已经结束的序列位置立即腾给新请求不需要等整个batch全部生成完所以对LLM这种“序列完成时间差异很大”的场景优化效果尤其明显。面试时能从“时延和吞吐的权衡”“显存碎片化”“序列级调度”这几个角度展开基本就是资深水平了。“显存和吞吐的取舍怎么平衡”答案没有绝对最优核心是目标约束。显存不够会导致OOM和拒绝请求显存越充裕KV Cache能容纳的序列和批大小越大吞吐潜力越高。实操方式是量化降低权重显存或开启更积极的显存管理策略同时用压测找到“临界前那个值”。这类问题没有标准答案面试官想听的是你有没有一套可靠的实验方法来逼近最优解。“如何设计一套性能测试方案”我的回答会强调三个东西现状基线、目标水位、增长路径。先测基线拿到当前系统的能力和短板再根据业务目标确定目标QPS、目标时延、目标资源利用率最后通过梯度加压和长稳测试验证系统能不能在目标水位下稳定运行。一个方案只要这三点清晰细节都是水到渠成的事。按我自己在实际项目里的体会AI系统性能测试最忌讳的就是把它当成一次一次的验收动作压完了写个报告下次调参再压一遍。真正有价值的做法是把压测沉淀成一套常驻的回归体系配合监控平台每次模型更新、框架升级、量化调整都自动跑一遍核心场景。这样才能保证性能问题在第一轮就被发现而不是上线当天由用户用超时来通知你。最后再分享一个小技巧压测报告一定要包含“复现条件”也就是我在2.1节强调的那张环境表从GPU型号到量化格式再到流量分布全部记录完整。这在跨团队沟通时能省下大量扯皮的精力——至少别人质疑你的数据时你可以把测试环境表拍在桌面上而不是红着脸说“我当时好像是那么配的”。