多租户 LLM 问题:规模化部署中的嘈杂邻居、隔离与公平性
你的 SaaS 产品以十个设计客户的规模上线,一切运转完美。随后你陆续接入了一百个租户,其中一个——一位在复杂研究工作流中使用 20 万 token 上下文窗口的重度用户——导致了所有其他客户的延迟飙升。支持工单开始涌来。你查看监控面板,却看不到任何明显异常:模型健康,API 返回 200,p50 延迟看起来正常。而你的 p95 已经悄悄翻了三倍。
这就是嘈杂邻居问题,它对 LLM 基础设施的冲击比几乎任何其他共享系统都要剧烈。以下是它为何比数据库场景更难解决——以及真正有效的应对方案。
为何 LLM 打破了多租户的基本假设
传统多租户基础设施拥有数十年的工具积累:连接池、行级安全、每 schema 独立数据库、VPC。所有这些方案的核心抽象都是: 每次请求的资源消耗是有上限且大致可预测的。一次数据库查询耗时几毫秒,一次 Web 请求消耗几百微秒的 CPU。你可以根据请求速率来估算容量。
LLM 请求彻底打破了这一假设。单次推理调用可能消耗 512 个 token,也可能消耗 200,000 个。它可能在 300ms 内返回,也可能在复杂的多步骤链路中耗时四分钟。一个 Llama-3 70B 模型上单次长上下文会话的 KV 缓存可能超过 40GB——这些内存必须在整个会话期间驻留在 GPU 上。一块能服务 50 个并发短上下文用户的 GPU,可能被两个运行扩展研究工作流的用户完全占满。
这种变异性并非可以通过工程手段规避的缺陷——它是 LLM 有用性的根本所在。同一套基础设施既要处理简单的问答,也要处理文档分析和多轮 Agent 工作流。而这些工作负载落在同一硬件上。
故障模式在爆发前是隐形的。某个租户的高消耗会话驱逐了另一个租户的 KV 缓存,迫使重新计算。重新计算导致首 token 时间飙升。用户看到卡顿。你的监控显示 GPU 很忙——技术上准确,但对于诊断谁的会话消耗了谁的资源毫无帮助。
基于 Token 的速率限制不是可选项
大多数团队从每分钟请求数(RPM)限制起步。这很容易实现,并能立即阻止最严重的滥用。但它无法解决嘈杂邻居问题,因为请求并不等价。一个发送 100 次短文本分类请求的租户,和一个发送 3 次各含 5 万 token 上下文的请求的租户,对 GPU 的占用完全不同。
LLM 速率限制的正确原语是每分钟 token 数(TPM),同时计算输入和输出。输入 token 在请求时已知;输出 token 必须事后估算或追踪。实践中的做法是:在准入时统计输入 token 并对每个租户每个时间窗口强制执行 token 预算,然后在生成过程中将实际输出 token 计入该预算。
三种算法主导了生产环境的实现:
令牌桶(Token bucket) 以稳定速率填充,并允许突发到桶的容量上限。这很好地模拟了实际提供商的行为——大多数 LLM API 以固定 TPM 速率补充,并允许短期突发。令牌桶是大多数应用的正确默认选择,因为它在保障平均速率公平性的同时支持突发容忍型工作负载。
滑动窗口(Sliding window) 根据滚动时间间隔评估请求。它防止了固定窗口允许的边界利用(在一个窗口末尾和下一个窗口开头各发送最大请求量),但在分布式系统中实现更复杂。
优先级队列(Priority queuing) 不通过丢弃请求来限速——而是在系统过载时通过排队低优先级请求来优雅降级。交互式用户请求获得高优先级;后台批处理任务在有容量时处理。对于宁愿增加延迟也不想返回错误的应用,这是正确答案。
基于 token 的速率限制开销可以忽略不计——约 4ms——与模型生成时间相比微不足道。没有理由跳过它。
一个常被忽视的关键点:速率限制必须在 LLM 调用派发之前执行,而非之后。事后检查 token 预算意味着失控的租户已经消耗了你无法追回的资源。执行点属于你的 API 网关或推理代理,而非应用代码。
