跳到主要内容

评测环境的延迟谎言:为什么你的 p95 在生产环境中翻倍

· 阅读需 12 分钟
Tian Pan
Software Engineer

评测团队在 PPT 上写下一个数字:“p95 延迟为 1.2s。” 产品上线。一周后,值班人员发布了一张图表:生产环境中的 p95 为 4.8s,并且在晚餐高峰期持续攀升。工程师们在接下来的五天里争论是否有性能倒退、为模型版本增加埋点、向供应商提交工单——最终发现,除了测量数字的地点之外,什么都没有改变。评测环境报告的是一台安静的机器在热缓存上运行串行调用的延迟。而生产环境是另一套系统。p95 从未出错;它只是在回答一个不同的问题。

这就是评测工具的延迟谎言。这并不是因为基准测试做得不好——大多数团队使用的工具都很合理,报告数字也很诚实。问题在于“模型延迟”与“用户感知的延迟”之间的鸿沟,以及你为开发构建的环境几乎总是测量前者,却暗示后者这一事实。一旦你理解了这一点,基于基准测试得出的延迟 SLO 就不再像是产品承诺,而更像是对一个没人能复现的私人测试环境的声明。

评测工具中的四个隐藏假设

每个评测套件都对其环境做出了假设。最危险的是那些没人写下来的假设,因为团队中没人注意到它们。在我见过的几乎每个环境中,都会出现以下四个假设。

Prompt 缓存已预先填充。 Anthropic、OpenAI 和 Google 都提供前缀缓存(prefix caching),而且节省的效果是真实的——Anthropic 的文档引用了长且重复的 prompt 能够降低高达 90% 的成本和 85% 的延迟。评测工具会连续数千次请求同一个系统 prompt,在第一次请求后,缓存对于剩余的运行就是热的。生产流量并非如此:用户冷启动进入页面,系统 prompt 可能还没有在该账户或该区域被缓存,会话的第一次调用需要支付完整的首个 Token 延迟 (TTFT)。由于评测环境在结构上无法产生冷启动,因此你的套件永远不会报告冷启动分布。

KV 缓存由于之前的运行而驻留。 在推理引擎内部,KV 缓存跨越同一个 worker 内的多次调用而存在。针对自托管端点的重复基准测试运行全都是热的。在 LMCache 和 llm-d 基准测试中,热缓存的 TTFT 可能比冷启动快 5 到 10 倍——llm-d 项目的研究显示,对于代表性工作负载,热缓存命中时的 TTFT 比冷启动快约 88%。如果你的评测环境始终保持 worker 绑定且从不清理缓存,那么你报告的是完全预热管道的稳态行为,而不是真实用户在模型升级、自动扩缩容事件或缓存轮换后支付的首字节延迟。

供应商环境是空闲的。 评测套件往往在团队认为方便的时间运行——深夜、上午、站会之间。无论这个时间表是什么,它都与供应商的负载相关。公开的基准测试反复显示,同一模型的 TTFT 可能会根据一天中的时间发生 2 到 3 倍的漂移,对于托管端点,最差的数值往往聚集在北美办公时间的高峰期。你的套件测量的不是模型;它测量的是太平洋时间早上 6 点的模型。生产用户遍布每个时区,而你在流量高峰期看到的负载并不是评测环境校准时的负载。

并发量为 1。 这是最关键的一个。大多数评测环境采用串行调用,或者使用极小的静态并发,从而轻松地处于供应商的 TPM 和 RPM 限制之下。真实的工作负载是突发的:一封营销邮件发出了,一个任务在整点运行,自动扩缩容反应迟缓。一旦你的并发超过了速率限制的天花板,你就会开始遇到 429 错误,而 429 错误与延迟密不可分——它们表现为同一请求跨度内的重试延迟。从用户的角度来看,一个被限流、等待两秒、重试并成功的请求是一个延迟增加了 2 秒的请求,而不是一个“错误”。如果评测环境将重试归类为错误并将其排除在延迟百分位数之外,那么它报告的是一个在面向用户的 API 端并不存在的数字。

这些影响并不是简单的累加,而是复合的。一个预先缓存、KV 预热、供应商空闲且串行运行的环境,与生产环境中服务于相同 prompt 分布的同一模型相比,可能会低估 3 到 5 倍的 p95 延迟。

数字究竟在哪里分叉

观察这种差距最清晰的方法是同时对两端进行埋点并对比。有三种模式几乎普遍存在。

第一种是 TTFT 的冷启动税。评测环境的 p95 TTFT 看起来很紧凑——比如 400ms。对于全新会话,生产环境的 p95 TTFT 是 1.6s,因为用户的前缀不在任何缓存中,模型 worker 的 KV 缓存中可能也没有持有其前缀,且请求正好落在了负载峰值期间。在整个用户群体中,“会话的首次调用”在所有调用中占有很大比例。而从未有过“首次调用”的评测套件将该分布压缩成了一个单一的紧凑区间,无法代表任何实际用户。

第二种是 速率限制引起的肥尾效应。大多数供应商会暴露 RPM 和 TPM 限制,而大多数团队配置的平均负载倍数在峰值期间会崩溃。一个在无排队时耗时 800ms 的请求,在必须退避一次时可能耗时 4.5s,在必须退避两次时可能耗时 8s。幼稚的重试逻辑会使情况变得更糟:如果你的客户端以固定延迟或不带抖动的方式重试,来自一百个并发客户端的重试会同时到达并再次触发限制。串行运行的评测工具永远不会触发此代码路径。人们第一次见到它通常是在生产图表飙升的那天,得出的结论往往是“模型变慢了”,而不是“我们的并发变得有意义了”。

第三种是 不会在平均值中显示的流式抖动。Token 间延迟 (ITL) 的方差——即 Token 之间的间隔——对于只报告 TTFT 和端到端时间的工具来说往往是不可见的。但对于流式用户体验,ITL 平滑的模型感觉很快,而带有抖动的模型即使总延迟相同也会让人觉得出了故障。生产负载会使 ITL 恶化:共享 GPU 的竞争、解码过程中的前缀缓存剔除以及队列深度飙升都会拉长单个 Token 之间的间隔。你的评测工具报告的是干净的中位数,会认为两个模型是等效的,但任何用户在五秒钟的流式体验中都能分辨出差异。

符合生产环境特征的评估是什么样的

解决方法不是“抛弃基准测试”。基准测试很有用 —— 它们只是需要回答 SLO 所提出的问题。符合生产环境特征的评估具有四个属性。

在各次测试之间清理 KV 缓存的冷启动运行。 在一部分测试之间增加清理步骤(或轮换 Worker),并分别报告冷启动和热启动的分布情况。核心的 p95 指标应该是反映你在生产中实际缓存命中率的 加权 组合,而不是渐进的热启动数据。如果你不知道自己的缓存命中率,那么在你发布延迟 SLO 之前,这是第一个需要埋点监测的指标。

流量回放工具,而非合成并发。 像 LLMPerf、GuideLLM 和 Gatling 这样的工具支持流量形状回放,而不是固定的 RPS(每秒请求数)。获取一天真实的生产追踪记录(或具有代表性的一段),针对你的测试端点回放时间模式,并报告该形状下的延迟分布。结果会与合成并发的结果不同 —— 通常更糟 —— 而这种差异正是 SLO 负责人需要的信息。

按时段的分层采样。 在一天中的多个时间段运行测试套件,持续至少一周,并报告带状 p95 而不是单点估算值。如果你的供应商的 TTFT 在太平洋时间凌晨 3 点和下午 3 点之间有 2 倍的波动,那么“p95 < 1.0s”的 SLO 可能在其中一个窗口成立,而在另一个窗口不成立,只有带状版本能告诉你到底是哪个。

考虑重试的统计方式。 将每个重试请求的实际耗时(包括退避时间)视为其延迟的一部分。如果一个请求被报了两次 429 错误,并在等待 3.2 秒后最终成功,那么这是一个延迟慢了 3.2 秒的成功请求,而不是三次独立的事件。将重试报告为单独的“错误率”,会将用户可见的频率限制压力成本隐藏在延迟页面上没人查看的指标中。

这里的纪律转变虽小但很重要:你的 SLO 是关于用户体验的声明,因此捍卫它的评估必须像用户流量一样建模,而不是像开发者运行的脚本。

组织架构失效模式:没人能复现的私有测试台

这个问题代价最高昂的版本并非技术问题,而是组织架构问题。一旦评估测试台成为延迟数据的来源,拥有它的团队就拥有了这些数据,并且该测试台会积累隐含的假设:它固定了哪些 Worker,如何处理重试,在什么时间点运行,采样什么样的 Prompt 分布,以及在测量前是否预热缓存。这些都没有被文档化,因为从来没有必要 —— 测试台只是产出一个数字,而这个数字在站会中被引用。

当生产环境出现偏差时,对话会变成这样。评估团队说:“p95 是 1.2s,这是运行记录。” 另一个人说:“p95 是 4.8s,这是生产环境图表。” 评估团队说:“我们又运行了一遍,还是 1.2s。” 另一个团队说:“我们针对同一个端点进行了流量回放,结果是 5.1s。” 两个团队对自己给出的数字都是正确的,但谁也无法复现对方的结果。一周的时间就在证明这种差异是环境因素而非性能回退中消逝了,而真正的修复方法 —— 退避 Bug、缓存预热间隙、资源配置不足 —— 只有当一个在双方都有权限的人坐下来手动对齐测试台时才能被诊断出来。

架构上的启示是,延迟不是模型的属性。它是 部署 的属性 —— 包括模型、缓存状态、并发量、网络路径、重试策略、时间段以及用户的地理位置。任何忽略这些因素的单一数字都在隐含地固定这些变量。解决方法是公布你所固定的内容:每一项延迟声明都应随附其测量时的环境,就像每一项准确性声明都已经随附其测量时的评估集一样。如果没有这一点,测试台就会变成一个私有产物,产出除了该工具本身之外没人能辩护的数字。

将测试台视为生产代码

弥合差距的文化变革很小:评估测试台是生产代码,而不是 Notebook 脚本。它需要与运行时路径相同的可观测性 —— 一个用于评估运行期间缓存命中率的仪表板,一个带状 p95 图表而不是单一数字,以及每个报告延迟中关于时间段窗口和并发级别的显式字段。它需要对假设进行版本控制:文档化的重试策略、文档化的 Prompt 分布、文档化的冷热启动比例。它需要一个负责人,当生产环境 p95 与评估 p95 出现偏差时,该负责人需要负责,而不是一个只交出数字并对下游发生的事情声明免责的团队。

做得好的团队并不是那些拥有最华丽工具链的团队。而是那些评估测试台产出的数字能让另一个团队使用自己的凭据、在自己的计划下、针对同一个端点进行复现的团队 —— 因为假设是显式的,测量环境随测量结果一同传递。在你的测试台达到这个标准之前,它报告的延迟只是关于一台私有机台的私有声明。将那个数字称为“p95”只是一种词汇借用,而这种借用正是让生产环境差距变得比原本更加痛苦的原因。

References:Let's stay in touch and Follow me for more thoughts and updates