news 2026/10/21 2:53:11

大模型性能测试新视角:从QPS到TTFT与FT的实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型性能测试新视角:从QPS到TTFT与FT的实践指南

前阵子帮一个团队复核性能测试报告,报告上写着“系统 QPS 达到 500,性能表现优秀”。我顺手问了句:那用户从输入到看到第一个字,要等多久?报告里没有。用户侧的真实反馈是“半天不出字,出来了又很慢”。这就是当前 AI 性能测试最尴尬的地方:我们还在用 2015 年的指标,测 2025 年的系统。当 RAG 已经成为标配,流式生成变成默认交互方式,QPS 这种传统吞吐指标越来越失真,TTFT(首 Token 时延)和 FT(完成整段回答的时间)这类指标必须走上台面。这篇文章我会把我这几年的测试复盘、踩坑和一套能直接落地的方案写清楚,适合正在做 LLM 应用性能评估、稳定性测试和容量规划的团队参考。

1. 为什么 QPS 在生成式 AI 面前“失真”:三个前提被打破

1.1 传统 QPS 的三个隐含前提

QPS 这个概念设计出来的时候,面对的是典型的“短请求-短响应”服务:一请求进来,服务端算一下,立刻返回一个固定大小的 JSON,然后连接就释放了。在这种模型里,QPS 是一个干净、直观的吞吐指标,它的前提有三个:请求处理时间短、响应长度基本固定、服务端不保留太多跨请求状态。

但大模型推理服务完全不是这套逻辑。一次流式生成请求可能持续 5 秒、20 秒甚至几分钟,响应是分块吐出来的,长度取决于生成内容,输入上下文又可能因为 RAG 拼接了很长的检索结果。三个前提全部失效。这时候你把 QPS 放在压测报告的核心位置,数字本身没问题,但解读方式已经错了。

我举个简化计算:假设服务端能同时处理 100 个并发会话,平均一次会话要跑 5 秒,那最大吞吐大概就是 100 / 5 = 20 QPS。数字很难看。但同一时刻服务端可能正以每秒几十个 Token 的速度往外吐数据,如果压测脚本把每个数据块都当成一次“成功响应”,瞬时 QPS 又能统计出几千。你说这个服务到底算 20 QPS 还是几千 QPS?两个数字都不能回答用户真正关心的问题——我得多快能看到第一个字、完整回答需要多久。

1.2 从“服务端吞吐”到“用户体感”

QPS 本质是从服务端资源消耗视角衡量系统容量,它默认“请求处理完毕”就是“用户体验完成”。但生成式 AI 的交互是流式的,用户看到的不是“请求完成”,而是一个连续展开的过程:等待第一个 Token、等待内容滚动、等待结束。这个过程里用户的心理时钟是完全不同的。

用 QPS 作为核心指标,还会掩盖两类问题。第一,长尾延迟被平均掉:大量请求在 200 毫秒内返回首包,但个别请求因为排队、资源竞争要等 5 秒,平均值仍然好看。第二,响应长度差异造成的吞吐假象:一个回答 20 个字的短问和一个生成 2000 字的长文,对系统资源的消耗差一个数量级,QPS 却把它们当成同一个“单位”来计数。

这也是为什么我觉得,“从 QPS 到 TT/FT”不仅仅是换两个指标,而是测试思维要从“评估服务器能吞多少请求”转变成“评估用户在多并发、多场景下需要等多久”。性能测试的意义不是给一个漂亮的吞吐数字,而是回答:系统在什么条件下会变得让人不可接受,瓶颈到底卡在哪一层。

2. TTFT 与 FT:从“每秒处理多少”转向“用户等多久”

2.1 指标定义和测量边界

TTFT 是 Time To First Token 的缩写,我把标题里的 TT/FT 理解为一组指标:TTFT 负责衡量“从请求发起后到收到第一个返回 Token 的时间”,FT 负责衡量“从请求发起到收到流式结束标记的时间”。后者也可以理解成整段回答的完成时延。它们合在一起,描述了一次生成式交互的完整生命周期。

测量边界非常关键。TTFT 计时起点是客户端准备发送请求的那一刻,终点是客户端收到第一个业务 Token。注意是“业务 Token”,不是 TCP 层第一个数据包,因为有些服务端会先把 HTTP 响应头、SSE 的注释行发过来,真正的内容还在后面憋着。如果测试脚本把“收到第一个字节”当首 Token 时间,TTFT 会被低估。

FT 的终点也有讲究。是读到流式接口的[DONE]结束标记,还是业务自己定义的结束符?我建议统一为“客户端判定业务完成”的事件。在 SSE 里通常是结束标记;在 WebSocket 里可能是服务端发来的特定消息。报告里如果不写清楚终点定义,后续团队对齐数据时一定会吵架。

2.2 为什么必须看分位数而不是平均值

延迟分布从来不是正态的,特别是有 GPU 调度、排队、缓存竞争存在的场景,尾部尤其肥。一个典型现象是:100 个请求里 90 个 TTFT 在 300 毫秒左右,9 个在 800 毫秒,还有 1 个因为排队等了 5 秒。平均值大约 490 毫秒,看起来完全没问题。但真实感受是每 100 次请求就有一次让人抓狂,而这个“抓狂请求”平均指标完全看不出来。

所以最关键的两个统计值是 P95 和 P99。P95 定义是“把所有请求的 TTFT 排序,取 95% 位置上的值”,代表大多数用户会遇到的最差体验;P99 则近似代表最倒霉的那个用户。我建议 SLO 以 P95 为主、P99 做参考,不要用平均值为准。平均值适合做容量趋势分析,不适合做体验承诺。

统计分位数时还要注意窗口长度。如果压测脚本每 10 秒算一次 P95,然后报告里写“P95 平均是 1.2 秒”,这个数字是没有意义的,因为你是把一组不同窗口的分位数值又取了一次算术平均。正确做法是:在稳定阶段选取一个完整时间窗口,把窗口内所有原始请求样本合并、排序,再取 P95。样本量至少几千条,不然尾部噪声会很大。

2.3 最简单的流式压测脚本长什么样

用伪代码演示一个测量思路,跑通之后再替换成你们团队熟悉的 HTTP 客户端和并发框架。核心点只有一个:必须逐块读取流式响应,在读取第一块时记录 TTFT,在流结束时记录 FT。

# 伪代码:测量一次流式请求的 TTFT 和 FT import time start = time.perf_counter() ttft = None async for chunk in stream_response(url, payload): if ttft is None: ttft = time.perf_counter() - start # 这里可以顺便累计 chunk 的字节数或 Token 数 # 便于最后计算 Token 吞吐 # 收到流式结束标记后 ft = time.perf_counter() - start return ttft, ft

很多人第一次写压测脚本时会犯一个致命错误:用普通 HTTP 客户端请求,等整个响应完整返回后再取数据。这么写,得到的“TTFT”其实等于 FT,因为客户端根本没有在首包到达时打点。还有另一个极端:统计脚本把服务端推来的每个 SSE 块都当成一次独立请求,QPS 瞬间飙升,但这个数字除了误导没有任何价值。

正确做法是:请求级别打点,每个请求记录 (ttft, ft, token数, 是否成功) 四条信息。并发跑完后再统一算 P50、P95、P99 和平均吞吐。

3. RAG 接入后,性能测试的“被测对象”发生了质变

3.1 检索链路:从模型单点测试到链路测试

RAG 成为标配之前,性能测试的核心对象是模型推理服务本身:发 prompt,收 completion,测生成性能。RAG 接入之后,一次请求要经历 Query 向量化、向量库检索、重排序、上下文拼装,最后才进入模型生成。任何一个环节变慢,都会直接体现在 TTFT 上。

我见过最典型的翻车案例:团队辛辛苦苦压测 LLM 服务,报告显示 TTFT 很优秀,上线后用户却觉得响应慢。一排查才发现慢在检索链路。Query 向量化服务和向量检索服务各自只有几十毫秒,看起来都不高,但串联起来再加上网络往返,就多出了 300 到 500 毫秒;如果重排序再引入一次模型调用,总时延可能翻倍。单点测模型当然发现不了这个问题。

所以做 RAG 应用的性能测试,首先要做的不是选指标,而是把被测链路画出来:客户端发起请求、查询向量化、检索、重排、构造上下文、模型排队、模型预填充、模型逐 Token 生成、返回流。每一跳都要有独立的埋点和耗时记录。用应用层中间件打点即可,不需要侵入模型内部。

3.2 上下文长度、缓存命中率、并发叠加的复杂效应

RAG 对性能的最大影响是上下文变长。原本一段 500 Token 的 prompt,挂上检索结果后可能膨胀到 3000 Token。模型预填充时间随输入长度显著增长,TTFT 会上升;长上下文还推高 KV Cache 内存占用,降低并发承载能力。如果测试报告不记录每次的 prompt Token 数,只写“TTFT 从 300ms 涨到 800ms”,别人根本不知道为什么会涨。

缓存命中率是另一个隐藏变量。RAG 应用普遍会做 query 缓存、文档检索缓存、甚至整段 prompt 的响应缓存。线上真实用户的 Query 千差万别,命中率可能很低;但如果压测脚本里只有几十条固定 Query 来回循环,测出来的数据会虚高到离谱。我在后面第四部分会详细展开这个问题。

并发场景下的叠加效应更要命。单个请求检索耗时 50ms,看起来毫无压力;但 100 个并发同时发起检索,向量库连接池、重排序服务的线程池、embedding 服务的批次排队全都会开始堆积。如果不做链路级并发测试,你看到的瓶颈永远只是“压测机不够努力”,而不是“服务端有瓶颈”。

3.3 怎么设计一个可复现的 RAG 基准场景

RAG 性能测试最怕不可复现。今天语料库 1 万篇文档,明天改成 10 万篇,检索耗时完全不一样。所以我建议在压测前把环境变量固定下来:固定文档库版本,固定切分参数和 topK 设置,固定向量检索服务的索引版本,固定 prompt 拼装模板,固定 max_tokens 和采样参数。

Query 集合需要按业务类型设计,而不是随机造几百条。我常用的分类方法是分成三组:高频短问,长度短、意图清晰,检索通常能拿到强相关片段;模糊长问,长度长、完整度低,检索结果不固定,召回数量和内容波动大;超长文档问答,需要召回的片段多,拼装后的 prompt 明显膨胀。每组至少准备几百条真实或接近真实的 Query,并保证组内不重复,降低缓存命中干扰。

每一轮测试都要记录场景基线:语料库大小、平均 prompt Token 数、平均检索到多少片段、缓存命中率是多少。有了这些基线,两轮测试之间才有可比性。否则你压测报告上的“本次 TTFT 比上次慢 20%”,根本说不清是模型变慢了、检索变慢了,还是数据集中不同导致的。

4. 真正卡住 AI 性能测试的五个隐蔽瓶颈

4.1 压测客户端反客为主,成了瓶颈

我看到过很多团队压测 AI 应用,并发加到 200 的时候客户端先崩了,然后得出一个“系统只能扛 150 并发”的错误结论。为什么客户端先崩?因为流式请求会让连接长时间占用,压测机需要同时维护大量挂起的 Socket 连接,内存、文件描述符、带宽都会被快速消耗。加上解析 SSE 本身存在 CPU 开销,压测客户端半路失效非常常见。

排查方法很简单:压测时同时监控压测机的 CPU、内存、TCP 连接数,并且看服务端接入层的连接数是否随并发线性增长。如果客户端已经 100% CPU,服务端还很空闲,说明瓶颈不在被测系统。应对方案是压测机横向扩展、复用连接池、减少每请求不必要的日志输出。另外,压测脚本不要记录每个 chunk 的完整内容,只统计长度即可,否则内存会被原始数据撑爆。

4.2 冷启动数据污染,结论没有参考价值

大模型服务冷启动阶段有一堆额外开销:权重加载、CUDA 上下文初始化、KV Cache 分配、缓存建立。如果上来直接压测,第一批请求的 TTFT 可能比稳定期慢 3 到 5 倍。更麻烦的是,冷启动期的慢请求会污染整个测试窗口的分位数统计,特别是当测试时长本来就不长时。

我的习惯是:正式压测前先跑 5 到 10 分钟的预热流量,目标是让模型推理服务进入稳定状态,让缓存命中分布也接近真实。然后丢弃预热阶段的所有采样,只统计稳定阶段数据。如果被测的是一套新部署的模型服务,预热时长还要更长。报告里要写明预热方式和丢弃数据的时间范围,否则换个团队来复测,结果一定对不上。

4.3 缓存命中率混入测试样本,数据虚高

RAG 应用的性能测试还有一个隐蔽污染源:响应缓存。如果压测 Query 集合规模很小,比如只有 100 条,多轮循环之后缓存命中率会非常高。服务端根本不需要重新检索、重新生成,直接返回缓存结果,TTFT 自然低得漂亮。但线上用户输入几乎不会完全重复,缓存命中率远低于这个水平,所以这份报告会误导容量规划。

对策是双管齐下。第一,Query 集合要足够大且有真实分布,尽量采集线上日志里的真实 Query,按频次加权采样来重建压测流量。第二,明确区分“缓存命中场景”和“缓存未命中场景”,分别压测和出报告。缓存命中场景适合评估服务端能支撑多大并发;缓存未命中场景才是评估真实用户体验和系统容量的关键数据。

4.4 指标统计口径不统一,团队对不上账

性能测试团队之间最经典的吵架原因是口径不一致。同样说“TTFT”,有人从连接建立完成开始计时,有人从发送请求体开始计时;有人计入 DNS 解析和排队时间,有人不计入。同样说“并发”,有人理解为每秒新建请求数,有人理解为同时在途的会话数。压测工具显示出的 200 并发,跟服务端监控里看到的活跃会话数,可能完全不是一回事。

我建议团队内部先定一套统一口径,写进测试规范:TTFT 的起止点是“客户端准备发送请求前”到“客户端收到第一个业务 Token”;FT 的终点是“流式结束标记到达”;并发指“同一时刻处于进行中状态的会话数”;分位数指“完整时间窗口内所有请求样本排序后的位置值”。任何报告的指标都必须附带口径说明,写不清楚视为无效数据。这个规则可以省掉一大半扯皮。

4.5 排队与重试:压力一大先崩的是中间层

当并发超过系统的实际处理能力时,请求并不会立刻失败,而是进入各种队列排队:应用线程池队列、任务调度队列、GPU 推理队列。排队时间会被记进 TTFT,但你从模型服务本身可能看不出异常,因为真正的问题在更靠前的调度层和接入层。

还有一个连锁问题:很多客户端设置了较短的超时时间,超时后自动重试,重试请求又排到队尾,导致更多请求超时,形成重试风暴。我在一次压测里见过:服务端实际只接到了 60% 的原始流量,剩余 40% 全是重试请求,最后所有请求一起超时,服务端日志还排得满满当当。排查方法是压测时分层观察:接入层的连接拒绝数、应用层的队列长度、推理服务的等待任务数、客户端的错误类型分布。只要发现超时和重试形成正反馈,就要先放宽超时时间、限制重试次数,再继续加压。

5. 从指标到结论:一套可以直接抄走的 AI 性能测试方案

5.1 场景矩阵先行,别先选工具

拿到一个 AI 应用要做性能测试,我建议你先别急着搭压测工具,先把场景矩阵列出来。参考模板如下:

场景输入特征并发目标核心关注指标
短问答prompt 200 Token 以内,输出 100 Token 以内高并发TTFT P95、成功率
长文生成prompt 500 Token 左右,输出 1500 Token 以上中低并发FT P95、Token 吞吐
RAG 问答固定语料库,检索片段拼装到 prompt中并发检索耗时、TTFT P95、FT P95
冷启动突发无预热,服务刚启动低并发TTFT P99、失败率

场景不同,核心指标完全不同。短问答主要看首 Token 时延;长文生成主要看完成时间和吞吐;RAG 场景必须拆开检索与生成两端分别评估。场景矩阵的作用是让所有人先对齐“要回答什么问题”,再去选具体工具,避免拿一把榔头把所有钉子都敲一遍。

5.2 指标组合和 SLO 设定

单看 TTFT 或单看 FT 都有偏颇,我建议固定用一组组合指标:

  • TTFT P95 和 P99:评估交互响应速度
  • FT P95:评估整段回答完成速度
  • 成功率:包括 HTTP 错误、超时、空响应
  • Token 吞吐:评估生成效率,单位可以是 Token/秒
  • 检索链路耗时占比:区分 RAG 场景下检索和生成的贡献

QPS 可以保留在报告里,但它不再是核心指标,而是作为容量表象的一个参考值。设定 SLO 时不要拍脑袋,可以从线上真实请求的历史分位数倒推。比如线上 TP95 的 TTFT 是 1.8 秒,那就先定 1.5 秒作为目标,留一点余量;等优化到 1.2 秒再压紧。SLO 设定也建议区分场景:短问答和长文生成的 FT 目标必须不同,用同一个阈值会导致短期调优严重偏向某一类场景。

5.3 压测执行与数据汇聚的实操细节

最后说一下执行流程,按顺序走完基本不会出大错:

  1. 固定环境:模型版本、索引版本、显存配置、并发上限、max_tokens、日志级别。
  2. 准备数据集和 Query 集,记录语料库大小和平均 prompt Token 数。
  3. 预热 5 到 10 分钟,丢弃预热数据。
  4. 分阶段加压,从 10 并发开始,每 3 到 5 分钟提升一档,观察 TTFT 和 FT 是否出现拐点。
  5. 找到容量拐点后进行 30 分钟以上的长稳测试,重点看连接泄漏、内存增长、响应延迟是否随时间劣化。
  6. 汇总报告,给出稳定阶段的分位数、吞吐、瓶颈分层和时间线拆分。

时间线拆分是我特别推荐的呈现方式。比如一次典型的 RAG 请求:Query 向量化 80ms、向量检索 45ms、重排序 120ms、prompt 拼装 5ms、模型排队 880ms、模型预填充 650ms、模型生成 4200ms。这张时间线一出来,瓶颈在哪个环节一目了然。没有这种拆解,报告上只有“TTFT 从 500ms 涨到 3s”,团队只能凭感觉猜。

我个人的实际体感是:AI 性能测试真正难的不是学会数 QPS 或者 TTFT,而是愿意先承认用户体感和服务端资源不是一回事。测过了几轮假数据之后,我现在每做一个压测场景,都会先问三句话:用户能等多久?服务端此刻有没有在排队?检索和生成各花了多少时间?把这三个问题回答清楚,性能报告就不太容易翻车。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 2:53:06

模型偏见量化与消减实战:从定义到落地全复盘

做模型评估做了快三年,我越来越觉得“偏见”这个词被严重误用了。很多人一听说某个模型“有偏见”,第一反应就是骂团队、骂数据、骂算法,但真正在一线调过模型的人都明白:偏见不是一种道德指控,而是一种可测量的统计偏…

作者头像 李华
网站建设 2026/10/21 2:53:05

没有Mac Mini也能搭智能管家:Windows/Linux下OpenClaw部署实战

1. 先给结论:这个智能管家,真的不是 Mac Mini 专属玩具前段时间有个朋友来问我,说自己看了好几篇"智能管家搭建"的帖子,全程都在说 Mac Mini 怎么怎么好,搞得他一度以为没有 Mac Mini 就不配玩这个东西。他在…

作者头像 李华
网站建设 2026/10/21 2:52:31

AI模型安全防护全攻略:从威胁建模到运行时监控的五层落地实践

前阵子有个做 AI 应用的同行问我:他们的智能客服上线三个月,现在最担心的反而不是回答效果,而是怕用户顺着聊天窗口把不该问的全问了。我说这个担心非常真实,模型安全到今天已经不是“要不要做”的问题,而是“从哪下手…

作者头像 李华
网站建设 2026/10/21 2:51:58

YOLO26自动计数实战:从检测框到类别统计的工程方案

有时候用户问的问题越简单,动手做的时候坑越多。前阵子我在迭代一版检测方案,模型用到的就是队里一直沿用的YOLO26内部版本,功能上已经能稳定输出边界框和类别了。结果下游同学过来问了一句:你能不能直接告诉我,这批测…

作者头像 李华
网站建设 2026/10/21 2:51:13

微信小程序+Django实现旧衣回收系统:全栈开发实战

前阵子接手了一个挺有意思的项目,内部代号叫 htgs84d4:基于微信小程序的旧衣回收商品系统。需求方是一家做本地环保回收的小团队,他们之前的回收业务全靠在微信群里手动登记,用户约时间、师傅上门称重、积分记录、兑换商品&#x…

作者头像 李华
网站建设 2026/10/21 2:50:51

知网AIGC检测原理与降AI工具实测:从标红到通过的完整攻略

去年有一阵子,实验室里的几位同学轮流来找我,说同一个问题:初稿明明是自己熬夜搭出来的框架,再用AI润了一轮,结果往知网AIGC检测系统一放,直接标红一片,相似度干到百分之四五十,要求…

作者头像 李华