跳到主要内容

AI 功能的延迟预算:当核心组件是随机的,如何制定并达成 p95 SLO

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的系统平均端到端延迟为 400ms,p95 是 4.2 秒,p99 是 11 秒。在产品规格中你承诺了"亚秒级"体验。仪表盘上的每个指标看起来都很正常,直到有人问起 5% 的用户遭遇了什么——这时,你一直引以为傲的平均值才成了埋葬你的东西。

这就是 AI 功能的延迟预算问题,它与你之前解决过的问题有着本质区别。当核心组件是数据库查询或微服务调用时,p95 延迟大致可预测,并且适用标准 SRE 技术。而当核心组件是 LLM 时,响应时间的分布呈重尾特征,依赖于输入,并且部分由你无法控制的条件驱动。在制定诚实的 SLO 之前,你需要一套不同的思维模型——更别说去达成它了。

为什么 LLM 延迟会颠覆你已有的直觉

在典型服务中,延迟方差来自排队、GC 暂停和偶尔的慢磁盘。分布大致呈对数正态分布;p95 大约是中位数的 3–5 倍。你可以设置超时,观察百分位数,当 p99 漂移时触发告警。

LLM 延迟不是这么回事。两个因素使其在结构上与众不同:

输出长度是延迟计算的输入,而非常量。 生成 50 个 Token 的请求完成时间,只是生成 800 个 Token 的请求的一小部分——而你通常要到生成完成才能知道会生成多少 Token。这造就了一种不仅分布宽泛、甚至呈双峰的分布。短查询聚集在低延迟端,长文本生成聚集在高延迟端,而分布的形态随用户实际提问内容的不同而变化。

你在租用上游提供商的算力。 与自托管数据库不同,你的 LLM 推理算力与其他租户共享。冷队列、突发流量和提供商故障会引入延迟峰值,且这些峰值是相关的——当提供商变慢时,所有人同时变慢,而这恰恰是你最需要可靠性的时候。在正常负载下平均 TTFT 为 300ms 的系统,在高峰期可能达到 4 秒——不是因为你的代码变了,而是因为队列深度变了。

实际结论:如果你基于中位数设定 SLO,你是在向中位数用户做出承诺,同时悄悄地抛弃了其余用户。与传统服务不同,你无法简单地通过过度供给来解决这个问题——你并不拥有推理算力。

先测量:分解延迟栈

在设定 SLO 之前,你需要分解自己实际在测量什么。LLM 延迟有三个不同的组成部分:

首 Token 时间(TTFT)——从发送请求到第一个 Token 流回的延迟。这决定了 UI 是否感觉响应迅速。对于对话式界面,用户会注意到超过 200–300ms 的 TTFT。对于非交互式功能(后台摘要、异步分类),它的重要性要小得多。TTFT 由提示词处理、队列深度和模型预填充时间驱动。

每输出 Token 时间(TPOT)——首 Token 之后的 Token 生成速率。对于长输出,这主导了总生成时间。一个以 30 Token/秒生成的模型,无论 TTFT 多快,生成 300 个 Token 的响应都需要 10 秒。TPOT 主要由模型架构和 GPU 内存带宽决定。

端到端延迟——包括你的应用服务器、任何 RAG 检索、工具调用、后处理和网络开销在内的完整往返时间。这是用户实际体验到的延迟。

每个组成部分都需要独立的 SLO,并且需要分别设定。笼统的"p95 < 2s"目标对其中至少一个几乎肯定是错误的,而且无法告诉你应该修复栈的哪个部分。

实用起点:

  • 面向用户的对话功能:TTFT p95 < 500ms
  • 非对话功能(文档分析、报告生成):TTFT p95 < 2s
  • 包含工具调用的复杂 Agent 任务:端到端 p95 < 5s
  • 批量/异步功能:吞吐量 SLO,而非延迟 SLO

这些数字需要根据你的实际使用数据进行校准。在承诺 SLO 之前,先测量两周的真实分布。

制定延迟预算:谁负责什么

一旦你为 TTFT 和端到端分别设定了 SLO,就需要为栈中的每一层分配预算。这是团队通常出错的地方——他们把提供商的延迟视为整个预算,却忘记了应用代码、编排开销和检索步骤也都在消耗时间。

一个针对 RAG 聊天功能、SLO 为端到端 p95 < 1.5s 的现实预算可能如下:

  • 网络往返(客户端到服务器):50ms
  • 应用路由和鉴权:30ms
  • Embedding + 向量搜索:150ms
  • 上下文组装和提示词构建:50ms
  • LLM TTFT:400ms
  • 剩余生成(流式传输到客户端):无上限,但首内容在 480ms 内可见
  • 后处理和响应格式化:20ms
  • 到首个有意义内容的总预算:~700ms p95

一旦你把这些列出来,两件事就变得显而易见。首先,没有任何余量。每一层都需要在子预算内完成,否则即使模型很快,顶层 SLO 也会失败。其次,模型并不是唯一的变量——Embedding 延迟、p95 的向量搜索,以及你的应用服务器中的冷路径,都很重要,而且都可以单独测量。

将此预算视为团队间的契约。平台团队负责 LLM TTFT 预算,应用团队负责 RAG 和编排预算。当顶层 SLO 被违反时,你就知道是哪个组件消耗了预算。

真正有效的策略

一旦你有了 SLO 和预算,就有三类战术:降低预期延迟、降低尾延迟、向用户隐藏延迟。

降低预期延迟

提示词缓存是大多数团队利用不足的高杠杆优化手段。如果你的系统提示词很长且是静态的,在推理层缓存 KV 表示可以将 TTFT 降低 40–60%。这不需要代码更改——它需要你了解提示词缓存是一项需要主动启用并验证其有效性的功能。

输出长度控制是最被低估的杠杆。每增加一个输出 Token 都会消耗延迟。对响应格式的明确约束、在 API 调用中设置最大 Token 限制,以及将关键信息前置的输出模板,都能在不牺牲实际任务质量的情况下减少总生成时间。不要让 max_tokens 保持默认值。

分类步骤使用小模型——如果你的管道中有任何步骤需要做二元决策(路由请求、检查条件、情感分类),请在小而快的模型上运行该步骤。一个路由到大模型进行实际任务的分类步骤,并不需要 GPT-4 级别的推理能力。这类子任务的延迟降低通常可达 5–10 倍。

降低尾延迟

对冲请求是在不成比例地增加平均延迟的情况下,降低 p99 最有效的单一技术。这种模式基于 Google 的"大规模尾延迟"工作,原理如下:发送请求,如果在该类型请求的 p95 阈值到达时仍未响应,则向备用副本或提供商发送第二个请求。返回先到达的响应,取消另一个。生产部署报告显示,p99 延迟降低了 75–96%,而由于重复请求产生的额外成本开销仅为 5–10%。

对于 LLM,在以下情况下对冲效果良好:

  • 你可以访问多个推理端点(不同区域、提供商或副本)
  • 请求是幂等的(相同的提示词,预期得到相同的结果)
  • 你没有会产生危险重复的有状态工具调用

对于带有副作用的工具调用的 Agent 任务,对冲并不安全。在接入对冲之前,要清楚哪些请求类型是幂等的。

推测执行将对冲进一步推进:不是被动地发送重复请求,而是主动地预计算可能的路径。如果你的流程有分类步骤,随后是两个分支之一,可以同时启动两个分支,丢弃失败的那个。当一个分支明显更可能时,这种方法特别有效——如果 90% 的请求走快速路径,那么为丢弃的 10% 浪费的算力,相对于为所有用户节省的延迟来说是很小的。

超时分层取代了团队通常设置的单一静态超时。不再是"10 秒内无响应则失败",而是为每个延迟层设置独立的超时:

  • 触发对冲的"快速超时"(发送重复请求)
  • 返回降级响应的"慢超时"(展示已有内容,或回退到更简单的答案)
  • 硬失败并报错的"最终超时"

即使模型很慢,这也能保持系统的响应性。用户看到的是部分或降级的响应,而不是转圈后的错误。

向用户隐藏延迟

流式传输是降低感知延迟最有效的单一技术,即使它完全不降低实际的端到端延迟。一旦生成了首个 Token 就将其发送到客户端,意味着用户会在 TTFT 预算内看到响应出现,即使完整响应还需要几秒钟。对于大多数对话功能,这能将感知延迟降至一秒以内。

在提示词设计中将答案前置与流式传输相辅相成。如果你指示模型先给出最重要的信息,再给出支撑细节,那么边看流边阅读的用户会立即获得价值。模型天然倾向于在结论之前进行详细铺垫——你需要明确提示来对抗这一倾向。

非流式场景的进度指示器——对于流式传输不实际的功能(批量导出、文档分析、复杂报告生成),显示有意义的进度信号。"正在分析 47 个数据点"优于转圈动画。即使底层延迟不变,准确的进度信号也能改变用户体验。

在这些约束下运营

制定 SLO 是一次性工作,而针对 SLO 运营则是持续性工作。

追踪消耗速率,而不仅仅是当前 p95。 消耗速率告警会告诉你,你正在以 14 倍的可持续速率消耗错误预算,并将在两小时内耗尽——这比你当前的 p95 越过阈值要早得多。传统的延迟告警在你已经违规时才会触发;消耗速率告警在你即将违规时就会触发。

分别跟踪实时和批量 SLO。 批量工作负载不需要与实时用户交互相同的延迟目标,混为一谈会虚增你的表观 p95。分别追踪它们并设定不同的目标。这也让你能够做出深思熟虑的质量/延迟权衡:当实时算力受限时,将非紧急请求路由到更便宜、更慢的端点。

当错误预算低于 10% 时冻结部署。 并非每个团队都需要这项策略,但对于曾经历在高错误预算消耗期间部署触发级联故障的团队来说,这是一个强制手段,可以防止时机不佳将事故演变为生产危机。

在生产中分别监控 TTFT 和 TPOT。 仅对端到端延迟告警,无法告诉你问题是出在预填充(提示词太长、提供商队列积压)还是生成(模型运行缓慢、输出异常长)。两种情况的修复方案不同,你需要信号来路由给正确的团队。

一个不舒服的承认

上述技术将有意义地改善你的 p95。但它们不会让你的 p99 看起来像确定性服务的 p99。LLM 延迟中存在不可消除的方差,任何应用层技术都无法完全消除——提供商故障、长输出、冷启动和模型更新都会引入难以完全对冲的峰值。

诚实的工程做法是在 SLO 分层设计中承认这一点。某些功能在当前技术条件下确实无法保证 p95 < 1s,强迫它们这样做意味着要么降低质量(更小的模型、更短的输出),要么撒谎(把 p80 称为 p95)。更好的做法是定义两层 SLO——一个针对常见情况的更严格的目标,一个针对其余情况的诚实尾部预算——并明确设计降级策略。

一个知道复杂任务最多需要 10 秒的用户,比一个被承诺"即时"却遇到 30 秒超时错误的用户服务得更好。制定你真正能达成的 SLO,对一切进行监控,并从真实的基准出发迭代。


AI 性能工程的难点不在于优化技术——而在于构建测量基础设施,以便知道你是否在赢。从这里开始。其他一切都由此而来,前提是你对自己的实际分布有诚实的认知。

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