最近连续有两个团队找我帮他们看AI系统的性能测试方案,情况几乎一模一样:用JMeter把模型接口当普通HTTP接口压了一轮,跑出来几百页报告,核心内容却只有TPS、响应时间和错误率,算法团队看完摇头,业务方也不知道该信哪组数据。这个现象在AI项目里太典型了——大家不是不会用压测工具,而是没想清楚AI系统性能测试和传统Web压测到底差在哪。
模型推理耗时的不确定性、流式输出的分片机制、GPU显存和动态Batching的调度逻辑、Token吞吐和并发数之间的换算关系,这些变量叠加在一起,会直接让一套常规压测方案失真。这篇文章我把做AI系统性能测试的完整思路摊开讲一遍,从指标定义、链路拆解、场景设计到JMeter实战落地,再配上几个真实翻车案例的排查过程。内容适合正在搭建AI性能测试体系的功能测试、性能测试和SRE同学参考,也适合算法团队想搞清楚“服务端到底瓶颈在哪”时拿来对照。
1. 先想清楚:AI性能测试和普通Web压测差在哪?
1.1 传统Web压测的“老三样”为什么不够用
做传统Web性能测试出身的人,习惯了一上来就盯三件事:并发数、响应时间、TPS。这套方法在支付、电商、内容管理系统上验证过无数次,但搬到AI系统上会非常别扭。因为一个典型的AI对话接口,单次请求从发起到完全结束可能要几十秒甚至几分钟,但你真正需要优化的不是“整个请求耗时”,而是“用户等第一个字花了多久”“后续每个字生成速度是不是稳定”“GPU有没有被喂饱”。
我把两类系统的差异整理成一张表,刚开始搭AI压测方案的同学可以直接拿来当认知框架:
| 维度 | 传统Web系统 | AI系统 |
|---|---|---|
| 核心资源 | CPU、内存、数据库连接 | GPU显存、算力、KV Cache |
| 响应方式 | 一次性返回完整响应 | 多支持流式分片,边推理边返回 |
| 关键延迟指标 | 端到端响应时间 | 首Token延迟、Token生成间隔 |
| 瓶颈特征 | 线性增长,容易定位 | 非线性,受输入长度和Prompt内容影响大 |
| 错误定义 | 4xx/5xx、超时 | 超时之外还有截断、内容质量问题 |
| 稳定性要求 | 高并发下TPS波动可控 | 尾部延迟(P95/P99)更关键 |
这套差异不是理论推演,是我在真实项目中反复踩出来的。传统接口压测时,你给服务器加压力,CPU使用率和响应时间通常会呈现相对平滑的曲线;但AI模型接口在同样并发下,响应时间可能从2秒直接跳到30秒,而且没有任何中间过渡。原因就是显卡不是“排队处理”而是“批次处理”,一批算不完,下一批只能等着,队列一旦堆积,延迟就瞬间垮掉。
1.2 AI系统的性能指标要从端到端拆成三段
接手AI系统性能测试,第一件事就是别再只盯一个“接口响应时间”。我做这套测试时的核心原则是:把一次AI请求拆成三段来看,每一段都有单独的指标口径。
第一段是输入处理段。包括请求经过网关鉴权、业务编排、Prompt模板组装、必要时还要触发RAG检索或向量化召回。这一段的指标主要是请求到达模型服务之前的耗时,以及检索环节的P95延迟。
第二段是模型推理段。这里要关注的指标就专业了:
- TTFT(Time To First Token):从请求进入模型服务,到返回第一个输出Token的时间。这个指标直接决定用户感受到的“卡不卡”。
- TPOT(Time Per Output Token):每生成一个Token的平均耗时。它除以1000再取倒数,就是每秒生成Token数,也就是大家常说的“生成速度”。
- Prefill与Decode耗时:模型在预处理输入阶段和逐Token生成阶段的耗时通常是分开统计的,前者对长文本更敏感,后者决定整体的吐字速度。
第三段是输出返回段。模型把结果通过SSE或者WebSocket一帧一帧吐给客户端,网络传输的稳定性、网关是否拆包重组、客户端解析效率都会影响最终体验。这也是传统性能测试最常漏掉的一段。
这三段拆完之后,你才能真正回答业务方最关心的那个问题——“模型出字快不快”,而不是甩一个让人一头雾水的“平均响应时间”。
1.3 为什么“越智能”的系统反而越难压测
AI系统难测的核心原因在于它的输出是不确定的。同一个Prompt,你压十次,可能每次生成的字数都不一样,生成的节奏也不一样。如果你按照传统做法,用固定响应体做断言,用平均响应时间做结论,那报告基本没有参考价值。
我在实际项目里总结出一条处理原则:AI系统的性能数据不要用平均值说话,要看分位数和分布状态。用户感受到的“卡顿”往往是P95甚至P99的尾部延迟,而不是平均值。同时,压测数据要按输入长度做分层统计,200字以内的短问题和8000字以上的长文档总结,性能差异可能是数量级的,混在一起平均没有任何意义。这些认知是后面所有场景设计和方法论的地基,必须提前对齐。
2. 一次AI请求的完整链路:瓶颈点在哪,压测就要压哪
2.1 一次AI调用其实要过五六道工序
很多测试同学对AI请求的理解停留在“客户端发一个Prompt,模型返回一段文字”,这么想的话,压测只能怼到模型接口这一层,中间的大量性能损耗点全被忽略了。
我用一个生活化的类比来解释AI请求的完整链路:你把AI系统想象成一家餐厅。客户端是顾客,网关是门口迎宾,业务编排层是餐厅经理,模型是后厨大厨,RAG检索是冰箱里的食材管理员,流式响应是传菜员一盘一盘上菜。顾客感觉“这顿饭等了好久”,可能不是大厨炒菜慢,而是食材管理员找东西找了半天,或者传菜员被堵在了走廊里。
具体落到系统层面,一次AI调用通常要经历以下工序:
- 接入层:网关做鉴权、限流、路由转发。这是第一道关卡,连接数和网关线程池容易成为瓶颈。
- 业务编排:会话管理、Prompt模板拼接、历史上下文组合。对话类系统的上下文会越拼越长,直接影响后续模型的输入长度。
- 前置检索(可选):RAG系统在这里做向量检索、重排序、知识库召回。数据库连接池、向量索引性能都是变数。
- 模型推理:先做Prefill处理整个输入,再进入Decode阶段逐Token生成。GPU显存、Batching策略、KV Cache大小都在这里起作用。
- 后处理:输出内容过滤、格式修正、敏感词检查。如果这里逻辑复杂,也会拖慢返回节奏。
- 响应返回:通过SSE或WebSocket流式返回给客户端,网络带宽和连接稳定性决定最后的体验。
2.2 瓶颈要分“计算型”和“IO型”来看
链路拆开之后,下一步是把瓶颈分类。不同类别的瓶颈,压测时盯的数据完全不同。
计算型瓶颈集中在模型推理和向量化Embedding上。特征是:并发上去后,GPU利用率飙升或者显存被打满,但CPU和网络都还算空闲。压测这类瓶颈时,重点观察GPU利用率、显存占用、推理队列长度、Batch Size变化。
IO型瓶颈则集中在网关转发、数据库检索、外部API调用、大文件读取上。特征是:GPU利用率不高,但请求在服务端某个中间环节大量排队,机器CPU和网络IO居高不下。压测这类瓶颈时,重点观察数据库连接池活跃数、Redis响应时间、网关线程池拒绝率、网络带宽占用。
判断方法是做分层排查:先在网关层看请求耗时分布,再到业务服务看每个环节的Trace耗时,最后到模型服务看推理耗时。哪一层耗时占比最大,瓶颈就在哪一层。我见过一个案例,业务方一直以为是模型推理慢,结果一查发现是向量数据库并发一高就超时,模型服务大部分时间都在等检索结果,压根没进入推理阶段。
2.3 流式返回是AI压测里最容易被忽略的隐藏负载
传统HTTP压测工具默认的一次请求是“发出去,等完整响应回来”。但AI系统普遍采用的SSE流式返回彻底打破了这套逻辑:连接建立的瞬间,服务端就开始往客户端吐数据,客户端接收和处理的速度会反过来影响服务端的发送效率。
在JMeter里直接发一个正常的HTTP请求去测AI流式接口,你会发现响应时间长得离谱,因为JMeter默认要等整个流式传输结束才算请求完成。可真实用户是边收边看的,他在第一片数据返回时就已经开始阅读了。所以流式接口压测至少要拆出三个独立指标:建连耗时、首个数据包到达耗时、整份数据接收完成耗时。
更麻烦的是,流式响应会大量占用连接资源和带宽。一个20秒的流式请求,意味着这个连接在这20秒内被持续占用。传统压测工具如果按“请求数”判断压力,会严重低估服务器承受的连接压力。压测时一定要把“同时活跃的连接数”也作为一个观察维度,否则你压出来的结果和线上真实流量差距会非常大。
3. 从指标到场景:测试目标怎么定,压测模型怎么搭
3.1 先定义清楚这道题的及格线
做性能测试最怕目标不清晰,一顿压测猛如虎,最后不知道分数怎么打。AI系统上线前,我习惯先跟业务团队签一份SLA清单,里面每一项目标都对应到一个可量化的压测结论。
一个典型的AI对话系统SLA可以是这样:
| 指标 | 目标值 | 说明 |
|---|---|---|
| P95首Token延迟 | ≤1500ms | 用户发出消息后1.5秒内看到第一个字 |
| Token生成速度 | ≥12 token/s | 平均每秒生成12个Token,出字流畅 |
| P95单轮完整回复时间 | ≤30s | 长回复场景下的整体等待上限 |
| 错误率 | ≤0.5% | 包含超时、连接中断、截断错误 |
| 单节点并发会话数 | ≥20 | 单推理节点同时支持20路会话不降级 |
这些数字不是拍脑袋定的。首Token延迟1.5秒,来源于用户体验研究中“感知延迟”的常见阈值;Token生成速度12 token/s,对应人类阅读中速文本的速度。数字定下来之后,后面所有压测场景都围绕这五个目标展开,哪一项不达标就针对哪一项排查,报告验收也清晰得多。
3.2 压测场景不是“一个并发线程就完事”
AI系统压测场景设计的核心原则是按业务场景分类,而不是按并发数分类。我把常见场景拆成下面五类,每一类都要单独跑一轮:
- 短文本问答:输入100字以内,输出500字以内。最像日常聊天,压的是交互响应速度。
- 长文档处理:输入8000字以上,输出1000字以上。压的是Prefill阶段和显存上限。
- 多轮对话:模拟用户连续发10条以上消息,上下文不断叠加。压的是KV Cache和Prompt拼接效率。
- RAG检索问答:请求中触发知识库检索,压的是检索链路和模型推理的混合场景。
- 流式长回复:输出2000字以上,压的是长连接稳定性和网络带宽。
每类场景要设置一个流量权重。比如线上70%是短文本问答,15%是多轮对话,10%是RAG检索,5%是长文档处理,那混合压测时按这个比例发请求,才更接近真实负载。
3.3 Token和流量之间的换算关系要会算
很多测试同学问过我:AI系统压测到底该设多少并发?这个问题不能拍脑袋,得先算清楚一条基本链路:单个用户在一次完整交互里消耗多少Token,以及推理服务每秒能产出多少Token。
举个例子。假设一次交互的输入是200Token,输出是1000Token,模型生成速度是每秒15Token。那这个用户从发请求到完整结束的耗时大约是首Token延迟0.5秒加上1000除以15约67秒。也就是说,一个活跃用户大约每分钟产生不到1次完整请求。10个并发用户,稳态下的请求QPS撑死只有0.15左右。
但请注意,QPS虽然很低,模型服务的负载一点都不低。每个请求持续67秒,10个并发就意味着模型服务始终保持10个生成任务在跑。如果你的推理服务异构并行处理能力不足,这10个任务就能把GPU打满。所以AI性能测试里,并发会话数和QPS要分开看,模型服务的真实压力更接近“同时处理中的请求数”。
设计和汇报压测场景时,把这些换算过程写在报告里,别人就不会对“为什么QPS只有个位数”感到困惑了。
4. JMeter跑AI接口性能测试:从脚本设计到实战步骤
4.1 先把数据和环境准备好,JMeter本身别成瓶颈
很多人在JMeter里拿一个固定Prompt反复压,这是我在AI性能测试里见过的最严重错误。模型服务对相同输入会有缓存,同一个Prompt压一百次,后面的请求可能直接命中缓存,结果完全失真。正确做法是准备一份覆盖不同长度、不同领域、不同语句结构的Prompt数据集,通过CSV参数化随机取用。
环境准备方面,JMeter本身要跑在独立的压测机上,避免和被测服务抢资源。压测机内存建议至少16GB以上,JVM堆内存给到4到8GB,并关闭JMeter的响应数据日志,否则大量流式响应会把磁盘和内存写爆。跑较长压测时,优先用非GUI模式,启动命令是:
jmeter -n -t ai_stress.jmx -l result.jtl -e -o report_html非GUI模式下JMeter对资源的消耗会小很多,数据采集也更稳定。
4.2 构建一个能测流式回复的请求
先说不带流式的普通模型接口怎么测。假设服务暴露的是一个标准的OpenAI风格接口,请求路径是POST/v1/chat/completions,请求体是JSON。JMeter里添加一个“HTTP请求”采样器,方法选POST,JSON格式的请求体可以写成:
{ "model": "chat-test-v1", "messages": [ {"role": "user", "content": "${prompt}"} ], "stream": false, "max_tokens": 512 }${prompt}就是前面CSV参数化里定义的变量。这种场景适合验证模型服务整体的端到端耗时,但不适合模拟真实用户在流式接口上的体验。
想测SSE流式接口,JMeter原生HTTP请求采样器会一直等到所有数据包到达才结束。这里我推荐两种处理方式。一是开发提供Debug接口,关闭流式,让你先用普通HTTP方式压测,等整体链路优化好后再用真实流式接口做验证。另一种是直接用JSR223 Sampler配合Groovy脚本去解析SSE流,把“收到第一个数据块的时间”和“收到的完整数据大小”记录成自定义指标。这种方式工作量稍大,但能拿到流式场景最重要的首Token延迟数据。具体做法是用HTTPClient发起请求,逐行读取输入流,按data:前缀解析消息,并记录事件发生的时间戳。
4.3 线程组、定时器、断言和监控怎么配置
并发模型上,我推荐用JMeter插件里的Ultimate Thread Group做阶梯加压,避免一次性上大并发把服务打崩。参数可以设置成:每5分钟增加10个线程,持续运行30分钟,这样你能观察服务在不同压力点上的平滑度。
如果想让请求速率更稳定,可以用Constant Throughput Timer控制每分钟请求数。但注意这个定时器的“吞吐量”是按分钟算的,配置完之后要换算从每分钟到每秒钟的值,避免理解偏差。压测开始前,要把“看结果”的重心从聚合报告挪开,JMeter的聚合报告在AI场景里意义不大,建议通过Backend Listener把数据发送到InfluxDB,再用Grafana实时画曲线,重点观察响应时间随并发变化的走势。
4.4 利用AI生成测试脚本的实操思路
现在很多团队已经在尝试用AI工具生成JMeter脚本,我自己的经验是:AI生成脚本最擅长的是骨架搭建,不是业务逻辑。你可以先用浏览器开发者工具或者接口文档抓取模型接口的完整参数结构,再让AI生成一个基础JMeter脚本,包含线程组、HTTP请求采样器、CSV数据集配置这些通用组件。然后你手动把业务相关的内容填进去,包括鉴权头、动态参数、流式响应处理逻辑。
这套流程能把平时两三个小时的脚本搭建压缩到半小时左右。但生成出来的脚本一定要先在低并发下跑一遍冒烟测试,确认参数传递正常、响应解析正确,再上正式压测。我踩过一次AI生成脚本的坑,它生成的JSON路径表达式在一个字段上解析永远返回空,导致所有请求都走了同一个默认Prompt,压测结果直接废掉重测。
5. 压测现场最容易翻车的三个细节和完整排查链路
5.1 并发不高但GPU利用率上不去,问题出在哪
现象:JMeter显示10个并发用户,模型服务的GPU利用率只有20%,端到端延迟却快到SLA上限。业务方的直觉是“加机器”,但加机器之前需要把真正的瓶颈找出来。
排查过程从客户端开始自下而上推进。第一步,确认JMeter压测机本身没有瓶颈,CPU和内存占用都正常;第二步,看网关层监控,发现连接数和线程池都有大量空闲,排除网关问题;第三步,看模型服务日志,发现请求的处理时间大部分消耗在等待某个内部队列的调度;第四步,查看推理框架的配置,发现问题出在动态Batching的等待策略上——框架为了凑够一批请求再一起推理,把等待时间设得太长,并发量一低,每一个请求都在排队等“队友”。
解决方案是把Batch等待时间从500毫秒下调到100毫秒,并发不足时牺牲少量吞吐换取延迟的大幅下降。调整后GPU利用率提升到30%,P95首Token延迟从接近2秒降到0.8秒。这个案例的教训是:AI服务性能不只看资源利用率,还要看推理框架和调度策略的配置匹配度。
5.2 P95响应超长但平均值很好看,尾部延迟的排查
现象:压测报告里平均生成速度15 token/s,看着非常健康,但P95响应时间高达20秒,业务方表示线上确实有大量用户反馈“卡很久才出字”。
这种平均值和尾部延迟撕裂的情况,在AI系统里几乎都能找到一个类似Root Cause:长文本请求触发了系统的某种保护机制。排查链路是这样的:先把响应时间超过10秒的请求筛选出来,分析它们的输入长度分布,发现超时请求几乎都是输入超过4000字的长文档;再看模型服务显存监控,发现长文本请求会占用大量显存,在显存接近上限时,推理框架会降低并发Batch大小,后续所有请求都必须排队等显存释放。
解决方案是双管齐下:在应用层限制单请求的最大输入长度,超过阈值时走分片摘要流程;在模型服务层对长文本请求做显存配额保护,避免一个极端请求拖垮整批短文本用户。修改之后P95响应时间降到了8秒以内。这个案例最大的教训就是:AI性能报告如果只发平均值,等于把问题藏起来了。
5.3 压测机自己成了瓶颈,测出来的是JMeter的性能
现象:并发数往上加,已测服务端各项指标都很平稳,但响应时间却跟着并发数一起涨,看起来像是服务端扛不住了。
如果你在压测时盯着JMeter压测机看,会发现它的CPU已经打满,线程数飙升,GC频率高得吓人。AI系统的流式响应会让压测机的网络接收和解析压力成倍放大,一台8核16GB的机器根本扛不住50个虚拟用户的流式场景。这就是典型的“压力还没打到服务器,先把压测机自己压垮了”。
排查和解决方案比较成熟:把压测任务分发到多台JMeter机器上做分布式压测,控制单台机器的虚拟用户数不超过30;或者换用Go语言编写的压测工具作为补充验证。分布式压测时注意保持所有压测机的时钟同步,否则最终汇总的延迟数据会带上系统误差。
6. 可复用的指标口径与面试中常被追问的硬问题
6.1 把指标口径统一了再发报告
AI系统性能测试报告之所以经常被算法团队挑战,最大的原因是指标口径不统一。有人说响应时间5秒,有人说首Token延迟3秒,最后发现俩人说的根本不是一回事。我现在做报告会把标准格式固定下来,每一张表都包含这组字段:
| 指标 | 定义 | 统计口径 |
|---|---|---|
| QPS | 每秒钟完成的完整请求数 | 按客户端收到完整响应计数 |
| 并发会话数 | 同时处于活动状态的推理请求数 | 服务端统计 |
| TTFT | 从请求发出到收到第一个Token的耗时 | P50/P95/P99 |
| TPOT | 相邻两个输出Token的平均生成间隔 | P50/P95 |
| 生成速度 | 1/TPOT,每秒生成Token数 | 平均值 |
| 端到端耗时 | 从请求发出到接收完整响应的耗时 | P50/P95/P99 |
| 错误率 | 失败请求占总请求比例 | 含超时、断连、业务错误 |
报告发布前一定要标注模型版本、Prompt模板版本、GPU型号、显存大小、推理框架参数。这些因素任何一个变了,性能数据都会大变,不标注清楚的口径对后续迭代没有任何参考价值。
6.2 性能测试面试里经常被追问的几个方向
这两年性能测试岗位面试中对AI系统的关注度明显提升,我把自己复盘下来最常被问到的几个题目整理如下,准备换工作的同学可以参考:
一个问题是“如何定位AI系统性能瓶颈”。思路是按链路分层,网关、检索、模型推理、流式传输各层埋点,用Trace数据找到耗时占比最大的环节,再结合GPU指标判断是计算瓶颈还是调度排队问题。
另一个是“如何做容量估算”。核心链路是目标并发会话数乘以单会话平均需求Token数,再除以单个GPU节点的推理吞吐能力,得到节点数量估算区间,最后用压测验证。这个计算过程一定要写清楚单位换算。
还有一个是“模型更新之后如何做性能回归”。做法是维护一套固定Prompt集,包含不同长度和复杂度的样本,模型或Prompt模板每次变更都跑同一套场景,对比关键指标差量。由于模型输出有随机性,同一场景至少要跑三遍,取中位数或平均值对比。
6.3 做AI系统性能测试的几条个人体会
和AI系统性能测试打交道这几年,我最大的体会是这个方向没有标准答案。传统Web压测有大量成熟框架可以直接套用,但AI系统从模型结构、推理框架到业务形态千差万别,同一个方案换一个模型可能就失效了。
我自己坚持下来了三件事:第一,压测之前一定先把业务指标和系统指标对齐,哪怕多花半天开会,也比压完发现测错方向强;第二,所有报告必须区分平均值和分位数,凡是只给平均值的结果我一律打回重做;第三,每次压测都保留完整的Prompt集和模型版本快照,将来做性能回归时才有可比的基础。
跑AI系统的性能测试,心态上不能急躁。模型推理的每一个环节性能波动都很大,单次压测结果可能差异明显,多跑几轮、多看分布、多做分层,才能把真实瓶颈从噪声里剥离出来。这套方法论帮我稳住了好几个项目的上线节奏,也希望能给你在实际工作中提供一点参考。