跳到主要内容

你的 P99 正在受陌生人流量的影响:托管 LLM 推理中的“吵闹邻居税”

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的仪表板很干净。昨天的部署也已干净地回滚。模型版本已锁定。提示词没有更改。但你的 TTFT p99 刚刚翻了一倍,客户成功渠道已经炸锅了,而你唯一能给出的诚实回答是“这是提供商的问题”。这个答案显得很苍白——就像耸耸肩一样——它通常会引出一个团队中没人能回答的后续问题:证明它。

这是托管 LLM 推理中营销页面不会讨论的部分。当你调用前沿模型 API 时,你正在与你看不见的负载共享 GPU、PCIe 结构、连续批处理和 KV 缓存预算。你的 p99 是这些负载突发的函数。大规模推理的经济性取决于租户的多路复用是否足够紧密,以使硬件利用率保持在 60-70% 以上,这意味着你的尾部延迟在结构上与同一分片上最大、最不规整、最不稳定的租户耦合。你购买的不是容量;你购买的是一个别人也排在其中的队列切片。

令人沮丧的是,中位数体验通常不错。托管 Claude、GPT 和 Gemini API 的 p50 延迟具有竞争力,通常令人印象深刻。问题在于,p50 和 p99 之间的差距才是生产环境真实存在的地方。过去 12 个月的独立测量显示,表现最好的提供商的 p95/p50 比例约为 1.8 倍,而在高峰时段,共享无服务器 GPU 平台的这一比例可达 4-5 倍。200 毫秒的 p50 配合 1,400 毫秒的 p99,与 200 毫秒的 p50 配合 350 毫秒的 p99 并非同一种产品——即使营销图表上会用同一个点来展示它们。

为什么托管推理共享的东西比你想象的更多

大多数工程师对托管推理带入的心智模型来自云数据库或 CDN:在数据平面共享,在每请求路径上隔离。LLM 服务在三个地方打破了这一模型。

第一个突破点是连续批处理 (Continuous Batching)。像 vLLM、TGI 以及前沿 API 背后的专有堆栈等现代推理服务器,并不是一次运行一个请求。它们将请求打包成滚动的微批次,共享 GPU 上的前向传播。你的解码步骤与陌生人的解码步骤位于同一个内核启动上。如果他们的上下文很长,内核运行时间就会变长,你的每秒 Token 数就会下降。Transformer 块内部没有公平份额调度器——只有被装箱进你的批次中的其他内容。

第二个突破点是 KV 缓存 (KV-cache) 争用。每个运行中的请求所占用的 KV 缓存内存与其上下文长度成正比。当缓存满时,调度器必须驱逐、抢占或拒绝——提供商选择的策略决定了是你的请求等待,还是别人的请求重新计算。同一分片上的长上下文租户不仅仅是使用了更多内存;他们正在重塑每个人的排队动态。最近关于 KV 缓存感知路由的研究报告称,对于适当分区的负载,缓存命中率约为 87%——这意味着如果没有这项工作,相当一部分延迟来自于并非由你引起的缓存抖动。

第三个突破点是 GPU 下方的 PCIe 结构和内存带宽。多租户 GPU 平台——即使是那些使用 NVIDIA MIG 对显卡进行切片的平台——也会共享主机 PCIe 通道,用于权重加载、KV 传输和 GPU 间的集合通信。当一个噪声邻居在你进行慢速流式解码时进行激进的预填充 (Prefill) 时,可能会以一种导致 TTFT 虚高的方式耗尽你的带宽,而没有明显的上游原因。最近的结构感知调度工作显示,仅通过 PCIe 感知放置,TTFT p99 就能提高 10-15%——这意味着原始基准测试中仅在总线争用上就损耗了 10-15% 的尾部延迟。

这三种争用源是乘性关系,而非加性。一个进行长上下文、高预填充、吞吐量贪婪生成的糟糕邻居会同时触发这三个问题。这就是增长最快的负载类别——编程代理、长文档 RAG、多轮语音。你的噪声邻居正变得越来越吵。

“昨天还好好的”是推理运维中最昂贵的一句话

这类事件之所以如此痛苦,并不是因为延迟峰值的大小。在 400 毫秒的基准线上出现 2 倍的 p99 退化很少会导致系统宕机。痛苦源于无法对其归因

当你自己的服务降级时,你有日志、链路追踪、部署标记,以及进行二分查找的选项。当提供商的共享基础设施因为别人的批处理任务启动而降级时,你在本地没有任何东西可看。提供商的状态页面要么显示绿色(“无故障”),要么粒度太粗,以至于“聊天补全延迟升高”成了唯一的信号——这在每周有十分钟是真实的,但对于证明你特定事故窗口内的因果关系毫无用处。

没有归因,随后的对话是可预见的且低效的。产品部门询问工程部门为什么功能变慢了。工程部门指向 API。采购部门要求提供证据以便进行投诉。工程部门没有证据。两周后,团队正在重写提示词并“优化”那些从未出过问题的代码路径,而真正的修复方案——调用不同的分片、切换到预留吞吐量或绕过受影响的区域——从未发生,因为没人能证明那是正确的举措。

解决这个问题的办法不是寄希望于好运。而是提供商健康可观测层 (Provider-health observability layer)——一个与你的生产流量并行运行的小型、连续的实验。

证明“不是你的问题”的可观测性模式

一个有效的服务商健康监测层包含三个部分。它们都不复杂,但我很少见到三者被同时部署。

针对服务商的合成探测(Synthetic probes),采用固定提示词和固定频率。 提示词应当简洁、确定,且在多次运行中保持一致 —— 五个固定的输入、固定的温度值以及固定的模型版本。每隔 15-30 秒从至少两个区域和一个带外(out-of-band)网络路径运行一次(这样你就能区分是服务商降级还是你自己的出口网络问题)。记录 TTFT(首字延迟)、TBT(Token 间延迟)、总延迟,以及响应头中返回的所有服务商侧元数据。探测请求不应与你的生产流量共享限流桶 —— 请使用独立的 API 密钥。在同一个仪表盘上绘制合成探测延迟与你的生产流量 P95/P99 指标。如果探测延迟在生产延迟飙升时也同步飙升,那么问题出在服务商。如果只有生产延迟飙升,则是你的内部问题。

按分群分层的延迟分析。 单一的全局 P99 数值聚合了模型、区域、提示词长度、输出长度、客户层级和功能路径。这种聚合会掩盖“嘈杂邻居(noisy-neighbor)”信号。至少按以下维度对延迟仪表盘进行分层:模型+版本、区域、提示词长度区间、输出长度区间以及功能界面。嘈杂邻居导致的降级几乎总是集中在一两个分群中(通常是恰好与违规负载共享分片的区域+模型)。一条平稳的全局 P99 曲线可能掩盖了某个特定区域 5 倍的延迟飙升,除非你进行切片分析,否则根本看不出来。

Request-ID 与服务商事件的关联。 每个服务商都会在响应头中返回 Request ID,例如 x-request-idanthropic-request-id 等。在每次调用时将其与你自己的 Trace ID 一起记录。当服务商发布状态事件时,你需要能够查询:“在事故窗口期间,我们有哪些请求?它们的 Request ID 范围是多少?” —— 因为这是服务商日志中唯一保留的凭证。如果没有这种关联,“我们受到了影响”只是一种猜测。有了它,你就有了一份受影响的用户会话列表,可以提交给采购部门、客户成功团队或服务商的客户团队。

拥有这三项能力的团队可以在事故发生后的前 10 分钟内回答“是我们的问题还是他们的问题”,而不是在前 10 个小时。这种“归因速度”才是可观测性工作的真正产出 —— 而不是仪表盘本身。

缓解措施:从低成本到结构性方案

一旦你能看清“嘈杂邻居”问题,你就可以根据成本采取分级的应对措施:

  • 带有抖动(jitter)地重试到不同的区域或分片。 成本低,仅需客户端开发。当资源竞争是区域性而非全局性时非常有效。注意:针对过载的服务商进行盲目重试会放大问题(即重试放大失效模式),因此请使用指数退避(exponential backoff)和断路器(circuit breaker)。
  • 当探测发现服务商异常时进行降级处理。 将合成探测作为功能开关(Feature Flag)—— 当探测延迟在连续 N 个样本中超过阈值时,切换到更小的模型、缓存的响应,或降级的 UI(提示“结果生成可能比平时更慢”)。这为用户争取了缓冲空间,而无需强制执行完全故障切换。
  • 针对受影响请求类型的多服务商路由。 如果“嘈杂邻居”问题集中在某个服务商的特定模型上,你可以将该流量转移到另一个服务商的同类模型上。这里的成本是实打实的 —— 由于每个服务商都需要单独的提示词调优、评测校准和异常处理,多服务商的可靠性成本往往超过 2 倍 —— 因此,请将此方案预留给那些 SLO 收益足以覆盖工程成本的流量。
  • 预置吞吐量 / 专用容量。 AWS Bedrock 的预置吞吐量(Provisioned Throughput)、Azure 的 PTU,以及头部 API 服务商提供的类似专用容量层级,通过为你提供预留的模型单元来消除“嘈杂邻居”问题,这些单元没有共享的速率限制,且延迟可预测。美中不足的是价格:预置吞吐量比按需计费贵得多,且通常需要 1 个月或 6 个月的承诺期。只有当持续利用率保持在预留容量的 65-70% 以上,或者当违反 SLO 的成本(收入损失、合同违约金)超过溢价时,这在经济上才是合理的。
  • 自托管的专用 GPU 推理。 这是对延迟有极致要求的团队的最终方案。到 2025 年,H100 云服务的价格已降至每小时 2-4 美元,这使得中等流量负载的方案在经济上趋于可行 —— 但你同时也需要付出运维人力成本:vLLM 调优、KV 缓存预算管理、自动扩缩容、模型升级以及评测基础设施。大多数团队在第一年都会将这项成本低估 3 到 5 倍。

最务实的决策树是:首先建立合成探测和分群仪表盘(在第一次事故中它们就能值回票价);其次是降级处理(它能保护收入且主要在客户端实现);最后才是结构性举措 —— 多服务商、预置吞吐量或自托管 —— 只有当常态下的 P99 违规率高到足以在财务上证明其合理性时,才考虑这些方案。大多数团队在还没有数据证明资源竞争是否严重到值得去消除之前,就急于跳转到“我们应该自托管”的结论上。

停止将延迟视为你一个人的问题

核心的转变在于观念。托管推理延迟并不是你代码的一个属性,而是一个通过薄薄的 API 层观察到的共享队列属性,你的流量只是众多工作负载中的一个。将尾部延迟视为可以完全从代码库内部调试的东西,就像将数据库性能视为 ORM 的属性一样——表面上看是对的,实际上却是错的。

处理得好的团队有三点不同。他们在需要之前就投资于供应商健康状况的可观测性,因此归因只需要 5 分钟的查询,而不是长达两周的调查。他们有一套明确的决策阈值,用于确定何时从按需容量升级到专用容量,并以“单次 SLO 违规的美元成本”而非模糊的“性能”目标来表达。而且他们接受许多事故的答案是“供应商今天下午状况不佳”——并且有足够的数据来确信地表态、围绕其进行规划,并在不进行无谓追责的情况下继续前进。

“昨天还好好的”是推理运维 (inference ops) 中最昂贵的一句话,因为它终止了本应开始的调查。解决方案是确保你可以立即将“昨天正常,今天挂了”分解为“对谁正常,对谁挂了,以及针对哪个供应商区域”——而无需通过提交工单来寻找答案。

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