回放不是拷贝:推理服务压测的三处失真
做过在线推理服务的压测,多半踩过同一个坑:用录制的线上流量回放,监控面板上 QPS 和 TTFT 都挺好看,上线后一遇到真实用户,P99 延迟却翻了一倍。问题往往不在压测工具本身,而在流量回放的保真度:录下来的请求和线上实际到达的请求,已经不是一个东西了。
推理服务的压测跟传统 Web 接口压测有个根本区别。Web 接口的响应时间由业务逻辑和下游依赖决定,请求体大小对延迟的影响相对线性;推理服务不一样,输出 token 数是生成出来的,没法从请求里直接读到。请求里只有一个 prompt,输出多长取决于模型和解码参数。压测时如果只回放输入,不控制输出分布,测出来的吞吐和延迟就是一个偏乐观的数字。美团 Quake 那套流量录制回放机制设计得很完整,录制入口、回放施压、Mock 配置化都有,但它解决的是请求到达侧的保真;推理服务还要多管一层生成侧的保真。
下面按三条失真路径拆:输入分布、会话状态、并发与到达过程。每一条都对应压测工具里可以落地检查的点。
输入分布失真:长度与前缀缓存
线上流量的输入长度分布通常是个长尾:大部分请求几千 token,少量请求几万 token。按请求条数均匀回放,长请求的权重会被严重低估。但推理服务的算力消耗跟输入长度不是线性关系:prefill 阶段是 O(n²) 量级的注意力计算,几条超长请求就能把 TTFT 拉高。更麻烦的是前缀缓存。线上大量请求共享系统提示词,前缀缓存命中率高,prefill 成本被摊薄。回放时如果每条请求独立起会话,缓存命中率骤降,测出来的 TTFT 和线上对不上,还不能简单归因于服务变慢。
做法上可以考虑两层。回放集不按请求条数采样,而按 token 总量采样,保证输入长度分布的分位数跟线上一致;vLLM 在线压测这类工具会把输入输出长度作为配置项,需要显式指定。另外,前缀缓存要在回放前单独校验:可以跑一条已知共享前缀的请求序列,观察压测侧的缓存命中 log,命中率跟线上差超过一截就先修回放方式,别看性能数字。
会话状态失真:多轮对话被压成单轮
录制的原始 HTTP 流量里,多轮对话是分开的请求,每一轮带着完整历史。压测引擎常见的做法是把这些请求拆开独立回放,结果就是每条请求都变成一个无状态的单轮补全,对话里重复出现的系统提示词被重复计费,prefix cache 收益丢失,KV cache 的回收节奏也变了:线上因为共享前缀、复用会话,KV 块复用率高,回放时却是大量一次性块,显存压力偏高、淘汰更频繁。这两者叠加,容易把明明能跑的系统压出 OOM。
这件事量级有多大,取决于业务里多轮对话的比例。客服、Agent 类的推理服务,多轮占比能到七八成,失真就不可忽略。回放工具需要按会话 ID 聚合请求,按原始间隔串联重放。代价是回放时长被拉长,得在并发上做补偿,见下一节。
自然到达与倍速回放:泊松还是固定 QPS
流量录制回放工具通常支持固定 TPS,也支持倍速回放,这一点顺丰同城压测平台和 GoReplay 的改造里都有对应功能。但固定 TPS 回放会撒谎:真实流量的到达是带批次波动的,推理请求尤其明显,用户一次性提交、前端批量上传、定时任务整批打进来,这些会在秒级形成突发。固定速率恰好把这个突发抹平了,测出来的队列长度、批内超时几乎永远不会出现。
针对推理服务,可以在回放里引入到达过程控制:按录制到的每秒到达数按比例缩放,记录原始的突发形状,而不是一个平均 QPS。需要备注的是,如果要按方向压测,inference-perf 确实提供了几种输入输出长度分布(固定、高斯、min-max)和突发负载模式,值得配几组对照,但具体实施前要读一遍行为是否符合业务形态——这些维度是推理服务特有的,不能假定通用压测平台的参数直接可套。
输出长度算不算盲区
在输入长度之外,输出是压测引擎更难管住的部分。同一条 prompt 反复回放,最优的服务端会生成相同的合理响应,但解码热路径带来的吞吐影响远不如实际业务差异大。真实场景里同一个 prompt,用户在不同会话中要求的输出长度不一样:摘要、改写要求长度固定,对话、Agent 链负责完成则随时停。这个多维指标测量工具通常报告 TTFT、ITL、TPOT、E2EL,用来把每次回放快照单独打标,对比一组倍速下的解码延迟变化,如果固定输出量,回放就成了分组合并的那波序列,反而掩盖了热段。
折中的做法是控制 max_tokens 和线上分布对齐(比如按 p50/p90/p99 分档回放一定的比例),并且记录一批输出实际长度,在压测报告里区分下采样截断会影响生成的端到端延迟,但当前已经接受对生成侧回放做定量校准的工具不多,这还是个手工活。
一张压测前应检查的回放保真度清单
检查这几个点,会让压测结果对自己诚实很多:
- 回放集按 token 采样的分位数与线上对齐:输入长度 p50/p90/p99 是否大致吻合。
- 多轮会话是否按会话聚合成组回放、复用历史是否一致,前缀缓存命中率与线上同比。
- 到达 QPS 是否保留突发形状,倍速回放标注了实际平均 QPS 和峰值。
- 输出长度分布是否收到回放集里,max_tokens 设置是否跟线上同步;批内跨请求共用短前缀的语义参数没有被当成纯条数来估算,保持计数。
- 影子库/影子缓存是否杜绝旧前缀复用对压测场景的污染:录制的请求头部与挡板是否可用,避免真实下游被命中——这点美团 Quake 的「出口 Mock配置化」值得借鉴;检索资料也提醒,按异常规则保真而不干净的请求体来做。
- 保真度指标缺少显式报告,不确定就明说:比如缺少“回放命中与实际发生样本匹配”的定量值,如果没法算出来,就在记录文档里写下来,不把不确定标成确认过。这些检查比报告几个漂亮数字便宜,能避免很多事后归因的时间。
- 压测后对比报告时注明回放集的失真方向,哪些会让结果乐观、哪些让结果悲观,以便后续做偏差修正。
一些底层差异的提醒
压测本质上是在给系统上建模的负载,回放集是一个方便但不完整、不能要求百分之百保真的替身。上线估值时,可以把推理压测当作一个趋势指标(压测数字在迭代间可比),而不是线上必然对应的确定数。尤其是 GPU 这类受温度、ECC、卡间通信干扰的硬件,不同回放策略下的波动常常比优化本身的影响还大。这时候,再把固定 TPS 下的结果当成签名,反而是在浪费时间。同一套流量录制和回放链路一旦长期复用,把回放集版本号落进实验结果里,这件事本身就不小。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-dev/676.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
上下文工程:从 Prompt 到 Context 的工程化实践
客服 Agent 答错时,团队第一反应是换模型。把那次请求的上下文打印出来才发现,模型看到的是一份过期政策、两份互相矛盾的条款,还有一长串无关日志。上下文工程要解决的,就是这类问题。
2026/10/06
-
RAG 的下一站:Agentic Retrieval
用户问“我们去年买的套餐现在还能退吗”,经典 RAG 检索出一段通用退款政策就结束了。可这个问题至少要拆成订单时间、地区、套餐版本三件事。Agentic Retrieval 要解决的,就是这种“一次检索不够用”的场景。
2026/10/12
-
约束解码让 JSON 不再崩,但它会改坏推理
约束解码让 JSON 解析失败率归零,工程师可以删掉一堆重试和修复代码。但它会挤占模型的推理预算:强制格式在部分任务上带来10%以上的准确率下降。文章梳理 OpenAI、Anthropic、Gemini 和其他自托管引擎的现状,以及 schema 设计、两阶段模式与编译缓存几个实操要点。
2026/10/22
-
Agent 上下文压缩:四层策略与失败代价
把 Claude Code 的四段分层与 Hermes Agent 的双阈值压缩系统并排看重:200K 窗口下的 50%/85%、legacy 与 lean 尾部的实测差池、未处理到的摘要模型窗口溢出,以及工具对配对边界为什么是能不能跑的问题。
2026/10/23
-
推理模型输出被截断后怎么接回去
推理模型遇到输出截断,重试整条请求代价高;续写也有隐藏门槛。从识别边界、提示回灌到 prefill 与成本判断,聊聊工程上怎么做才不算亏。
2026/10/25
-
推理模型的并行思维链采样怎么用才不亏
并行思维链采样的成本不是白花的。结合推理模型测试时计算的几条实证结论,梳理best-of-N、束搜索、自洽性投票和自我修订在什么场景下划算、什么场景下只是在烧钱。
2026/10/26
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布