跳到主要内容

共享 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:为核心营收流程设置专用模型池

当隔离性比效率更重要时,答案是完全停止共享供应商账号。核心营收路径拥有自己的供应商工作区、API 密钥、速率限制和账单。驱动续订的聊天助手不与内部工具共享配额,就这么简单。

这听起来很浪费——你在为每个池中未使用的冗余空间付费——但从账面上看,分离通常更划算。共享基础设施的隐性成本是事故成本,而付费产品一次长达数小时的停工,其损失通常超过专用密钥一整年未使用的容量。

这种模式自然也延伸到模型选择上。核心营收流程通常需要具有可预测行为的特定模型;实验性功能则可以路由到本季度最便宜或最快的任何模型。在同一个密钥上混合这些流量特征意味着,实验中的模型路由更改可能会改变生产模型的负载,而直到用户开始抱怨之前,没人会注意到这一点。

我见过出错的地方是:团队设置了专用密钥,然后在共享池耗尽时,将流量从共享池回退(fallback)到专用密钥。这样一来,专用密钥就成了它自己的“吵闹邻居”,这种隔离只是做做样子。回退应该降低优先级,而不是提高。

隔离模式 3:带抢占机制的优先级队列

速率限制是一个硬上限。一旦达到上限,就必须有人等待。而决定“谁”等待的就是优先级队列。

最简单的优先级方案有两个层级:面向用户的实时请求和其他所有请求。当令牌桶满时,实时请求会跳过队列,而批处理任务会被积压。这对大多数产品界面都有效,因为面向用户的流量很少是主要的令牌消耗者;真正耗尽令牌桶的是批处理任务和定期的回填(backfills)。

三层方案增加了“抢占”机制:如果队列资源匮乏,高优先级请求可以取消正在进行的低优先级请求。这种操作很危险——你在那些中途被杀掉的请求上浪费了令牌——但对于在持续批处理负载背景下、具有极高突发性用户流量的系统来说,抢占是防止 P99 延迟在高峰期崩溃的唯一方法。

陷阱在于,当所有东西都是高优先级时,优先级就变得毫无意义。如果没有积极的治理,每个产品经理都会游说争取第一梯队的地位,队列会坍塌成单一层级,你又回到了先到先得的局面。平台团队需要具备政治权威来保持优先级分配的真实性,这意味着优先级决策必须存在于具有全公司可见性的地方,而不是埋在每个服务的环境变量中。

隔离模式 4:基于租户的成本上限

AI 功能中的租户是一个双层概念。一层是内部租户,即哪个功能拥有哪个切片——即上述四种模式。另一层是面向客户的租户,即如何限制单个用户或账号。后者是防止单个企业客户失控的智能体(agent)烧掉你全月利润的关键。

成本上限在网关处以内存方式强制执行效果最好,无需针对每个请求进行数据库轮询。检查必须非常廉价,否则它会变成每个请求的延迟税。一个可行的设计是在共享内存存储(Redis 是常见选择)中保存每个租户令牌消耗的滑动窗口估算值,在每次请求完成时进行更新,并在未来一分钟的预计成本超过租户配额时拒绝请求。

有趣的产品设计问题不在于架构,而在于达到上限后该怎么办。硬拒绝(Hard rejection)很诚实但很生硬。质量降级(当租户超过软预算时,路由到更小、更便宜的模型)以牺牲可解释性为代价保留了用户体验。大多数生产系统最终采用混合方案:在达到软限制时降级质量,在达到硬限制时硬拒绝,并向客户管理员清晰地展示这两种状态。

一个隐蔽但代价高昂的失败模式:在应用程序代码而不是网关中跟踪使用情况。应用程序在调用模型之前就看到了请求,并乐观地更新了计数器。如果请求失败,计数器就是错的,经过数周的累积漂移,租户会不可预测地被限制流量或超量服务。计数器应根据实际的完成响应进行更新,而不是根据请求负载的估算值。

没人想要的组织结构启示

我观察过每一个经历这种转型的团队,他们都抵制同样的结论:共享 LLM 基础设施最终需要一个平台团队。不是因为技术难,而是因为“治理”难。配额决策必须集中制定,优先级分配必须顶住政治压力,成本上限必须统一执行,检测必须接入每个功能团队都能读取的统一仪表盘。

当这种责任处于非正式状态时,发生的事情是可以预见的。拥有原始 API 密钥的团队在没有预算或授权的情况下成为了事实上的平台团队。他们因为别人的事故而被呼叫(paged),但因为对其他团队的路线图没有发言权而无法强制执行限制。他们筋疲力尽,系统腐烂,一年后公司雇佣了一个“AI 基础设施”团队,不得不从头开始重新做一遍所有的决策。

正确的做法是尽早意识到 LLM 网关是具有平台团队特性的基础设施,就像 Kubernetes 或服务网格(service mesh)一样。它需要负责人、SLO、轮值机制和预算。引入该团队的决定通常是在最错误的时机做出的——即在发生事故之后,公司处于被动状态时——但即便如此,也比继续为突发停机付出代价要好。

稳态下的“优秀”标准

解决这一问题的团队通常具备几个明显的特征。有一个统一的地方,可以将每项功能的 Token 消耗量与其分配的份额进行对比绘图。每个 API 密钥都有记录在册的所有者、明确的功能服务列表以及层级分类。在平台团队分配容量份额之前,新功能无法进入生产环境,且该分配情况会根据实际使用量每季度审查一次。企业级租户的成本上限被默认强制执行,任何显式的覆盖操作都会被记录。

大多数团队不需要同时使用所有四种隔离模式。从检测开始——你无法修复你看不到的问题——然后添加基于功能的存储桶。接下来是专用池,即当单个功能的营收影响足以支撑其拥有独立资源池时。优先级队列适用于规模足够大、容量竞争已成为常态而非偶发情况的系统。基于租户的上限则适用于拥有外部客户、且你无法仅靠信任来限制其使用量的系统。

这项工作并不光鲜亮丽。没有人会发布一个“我们在凌晨 2:47 成功避免了一次停机”的功能。但正是这种基础设施决定了你的 AI 产品对付费用户而言是否可靠,而且在它第一次防止了那类足以登上新闻头条的事故时,它几乎总能证明自己的价值。

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