跳到主要内容

共享 LLM 基础设施中的“吵闹邻居”问题:AI 功能的租户模型

· 阅读需 13 分钟
Tian Pan
Software Engineer

告警在凌晨 2:47 响起。面向客户的聊天助手正为一半的付费用户返回 429 错误。工程师们在仪表板中忙乱寻找,试图找到那天下午发布的 Bug。他们一无所获 —— 代码没问题。真正的罪魁祸首是另一个团队在当晚启动的批量摘要任务,它共享了同一个供应商 API 密钥,耗尽了该账户接下来四小时的每分钟 Token 预算。没有人拥有这个共享密钥,也没有人负责这个限制。

这就是“喧闹邻居”(noisy-neighbor)问题。与经典的 API 配额事故不同,它在 LLM 系统中表现出一种独特的残酷性。一个达到速率上限的 REST 端点会迅速失败并进行重试;而 LLM 的“每分钟 Token”(TPM)桶是根据请求内容非对称消耗的。因此,一个生成 8K Token 的功能可能会使一个进行低成本 200 Token 分类调用的功能陷入饥饿,而这一切在请求计数图表中甚至都不会显现。流量在你所测量的维度上并不“喧闹”。

大多数团队发现这一点的方式正如上文提到的团队:一个无关团队的任务与付费用户的会话发生冲突,而两者唯一的共同点只是环境变量中的一个字符串。

为什么 LLM 的速率限制有所不同

供应商的速率限制通常同时体现在两个维度:每分钟请求数(RPM)和每分钟 Token 数(TPM),Anthropic 甚至将 Token 进一步细分为输入和输出桶。这些桶采用的是令牌桶算法 —— 它们会持续填充,因此瞬时突发流量会被部分吸收,但持续的压力会迅速耗尽容量。一次 70B 级别的模型调用可以在单个请求中消耗数千个 Token,而智能体(agent)中的单个工具调用循环可以在几秒钟内消耗数万个 Token。

这种与传统容量规划的不匹配正是难点所在。在典型的 Web 服务中,你根据每秒请求数(RPS)进行扩缩容,且每个请求的成本大致固定。在 LLM 服务中,由于 Prompt 大小、输出长度、重试次数以及是否启用推理(reasoning)的不同,两个请求的实际成本可能相差 100 倍。如果用户恰好使用了具有长上下文的功能,那么 1% 的用户增长可能会转化为 30% 的 Token 消耗增长。

供应商端并没有原生概念来规定“此功能拥有账户 40% 的 TPM,其余功能平分剩余配额”。OpenAI 和 Anthropic 提供的都是账户级别或工作区级别的限制,而非功能级别的分配。如果你每个供应商只有一个 API 密钥,且其后挂载了三个功能,那么你实际上已经默许了流量最大的功能有权让其他两个功能陷入饥饿。

检测问题先于隔离问题

大多数团队会跳过检测直接进入修复阶段。这导致的架构可能正在隔离喧闹邻居,但没人能证明这一点。检测层需要三个信号,而这些信号在默认的可观测性方案中往往是缺失的:

第一是按功能划分的 TPM 消耗,并按方向(输入 vs 输出)拆分。不是请求计数,不是延迟,也不是错误率。而是按功能划分的 Token 吞吐量。原因在于:一个功能可能在请求量上很安静,但在 Token 消耗上却极具破坏性。此外,供应商的 429 错误会先击中那些高 RPS、低 TPM 的功能。如果你的仪表板只跟踪请求计数,那么接到告警的团队永远不是引发事故的那个团队。

第二是每分钟的余量与限制比率(headroom-to-limit ratio)。绝对 TPM 数值告诉你使用了多少;而相对于供应商层级的比率则告诉你下一次突发流量何时会崩溃。一旦这个比率超过 ~70%,你就离多功能故障仅差一个突发任务。我见过的大多数监控栈仅对 429 响应报警,这已经太晚了 —— 故障已经发生。

第三是按功能划分的预算占比,以滑动窗口计算。这是捕捉喧闹邻居的真实信号:如果一个功能在总 Token 消耗中的占比在 10 分钟内翻倍,无论绝对数值是否触发了限制,它都是一个信号。如果没有这个信号,你只有在下游发生故障时才会发现喧闹邻居。

一个实用的检测阈值是:如果任何单个功能在连续两个五分钟窗口内超过总 Token 吞吐量的 60%,就向平台团队发送告警,除非该功能拥有专用配额。这种例外机制很重要,因为某些功能确实理应占据主导;而告警之所以重要,是因为大多数占据主导的功能都是意外造成的。

隔离模式 1:基于功能的 Token 桶

最简单的隔离模式是为每个功能分配供应商 Token 预算的固定份额,并在本地强制执行 —— 通常是在 LLM 网关或代理(proxy)内部。如果你的层级提供 1,000,000 TPM,你可能会给聊天助手 600,000,给批量摘要器 200,000,并预留 200,000 作为新功能和突发流量的全局池。

有两件事使得这种模式说起来容易做起来难。第一,分配的总和需要略低于供应商的限制,而不是完全相等 —— 供应商的速率限制核算存在测量滞后,100% 的分配会在你的本地核算系统反应过来之前就触发 429 错误。一个有用的默认设置是将本地桶的大小设置为供应商限制的 85%,并将其余 15% 作为测量偏差和跨区域复制的缓冲余量。

第二是固定分配在负载不均的情况下会互相拖累。聊天助手在凌晨 3 点处于低谷,而批量任务达到了配额上限并被本地限流 —— 即使供应商此时仍有充足的容量。动态重分配系统通过将每个功能的允许份额计算为当前活跃功能的加权比例(而非静态数字)来解决这个问题。如果只有批量任务运行,它将获得整个池子;如果聊天助手被唤醒,批量任务的配额会在几秒钟内按比例收缩。

对于大多数团队来说,动态分配是正确的默认选择。静态分配适用于你有硬性合同 SLA 需要维护的情况 —— 审计员通常更喜欢固定数字,而非公平性算法。

隔离模式 2:为核心营收流程设置专用模型池

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates