当每个请求的思考成本各不相同时的容量规划
传统的容量规划(capacity planning)建立在一个默认的假设之上:请求在大体上是可以互换的。Web 服务器处理登录、搜索、结账 —— 尽管这些操作有所不同,但它们的差异都在一个范围之内。你衡量每秒请求数(RPS),观察 p50 和 p99 延迟,乘以安全系数,然后进行资源配置。这个模型之所以有效,是因为工作的基本单位 —— 单个请求 —— 具有稳定的成本。
Agent(智能体)的工作负载从根本上打破了这个假设。你对 Agent 的一个查询可能通过一次简单的生成就解决了:300 个 token 输入,200 个输出,两秒钟搞定。但下一个查询,表面上看起来一模一样,却可能触发一个规划步骤,分发出 40 个工具调用(tool calls),在每一轮对话中重新读取不断增长的上下文,并在四分钟内消耗掉 120 万个 token。同样的端点(endpoint),同一个用户,同一条代码路径。单个请求的成本差异可能达到三个数量级,而且请求中没有任何信息能预先告诉你接下来会遇到哪种情况。
这不是你可以通过取平均值来抵消的噪声。这是工作负载的一种结构性属性,这意味着 你的 p50 几乎无法告诉你关于 p99 的任何信息。如果你针对中位数(median)进行资源配置,而长尾(tail)决定了你的账单,那么你会在两个方向上同时犯错:对常见情况配置过剩,对真正致命的情况配置不足。
工作单位不再是“请求”
对于确定性服务来说,“单个请求”是一个有意义的容量单位,因为它的计算量被你编写的代码所限制。分支是有限的,循环会终止,数据库查询会触达已知的索引。你可以通过阅读源代码来推断最坏的情况。
Agent 请求没有这种限制。它消耗的计算量是由模型在运行时根据任务、可用工具以及它在推理过程中恰好选择的路径决定的。行业测量数据显示,Agent 类工作负载的 token 消耗量是标准聊天对话的 5–30 倍,而且这个倍数本身是一个分布,而不是常数。同一个任务运行两次,token 消耗量可能相差 10 倍,仅仅因为模型第二次走了一条更长的路径。
因此,请求不再是一个有用的单位。你实际消耗的是 token 和工具调用,这些才是你需要预测的对象。以“每秒请求数”命名的容量规划是在衡量错误的东西 —— 这就像是在数信封的数量而不去称它们的重量。
实际的做法是停止预测请求数量,转而预测两个分布:每个请求的 token 数和每个请求的工具调用数。不是这些分布的平均值,而是完整的形状,尤其是高百分位值。你的基础设施账单是 token 分布的总和,而用户感受到的延迟则受工具调用分布的支配。这两者都是肥尾分布(fat-tailed distribution),而在肥尾分布的情 况下,平均值往往具有误导性。
为什么平均值掩盖了关键指标
假设一个工作负载中,95% 的请求消耗 5,000 个 token,5% 的请求消耗 800,000 个 token。平均值约为 45,000 个 token。如果你围绕这个平均值进行规划,你所构建的数字几乎无法描述你任何实际的请求 —— 大多数请求便宜 9 倍,而长尾请求贵 18 倍 —— 尽管长尾请求只占二十分之一,却贡献了约 90% 的 token 总支出。
这就是陷阱所在。平均值处于两个群体之间的一片虚无地带。按照平均值配置计算资源,你将严重低估长尾请求的需求,导致这些请求排队、超时或被限流。按照平均值制定预算,你收到的实际发票将是预测的两三倍,因为你忽略的长尾正是钱花掉的地方。
解决方法是拒绝过早将分布压缩为单个数字。将每个请求的 token 数和工具调用数的 p50、p90、p99 和 p99.9 作为一级指标进行跟踪,就像延迟敏感型服务跟踪尾部延迟(tail latency)一样。p50 和 p99 之间的差距不是需要被抹平的测量误差,它是关于工作负载最重要的事实。4 倍的差距和 400 倍的差距需要完全不同的架构,而两者的平均值可能完全相同。
还有一个值得提及的二阶效应。长尾不仅昂贵,它还与负载呈正相关。当系统繁忙时,连续批处理(continuous batching)会将更多序列挤进每个 GPU,KV 缓存(KV-cache)压力上升,而长时间运行的长尾请求恰恰是占用缓存槽位最久的。因此,长尾请求变慢的时机正是你遇到长尾请求最多的时刻。你在低负载下测量的 p99 对高峰期可能出现的 p99 过于乐观。
基于 token 和工具调用进行预测,而非请求
具体来说,应围绕两个输入重建容量模型。
每个功能的 token 分布。 不同的产品功能具有截然不同的形状。“总结此线程”功能可能分布较窄 —— p50 和 p99 在 3 倍以内。而一个开放式的“研究与起草”型 Agent 可能跨度达到 100 倍。将它们汇总在一起,你会得到一条毫无意义的混合曲线。单独衡量每个功能,因为每个功能的扩展方式不同,一个失控的功能就可能主导整个账单。
每个功能的工具调用分布。 工具调用对端到端延迟和并发压力的影响远超 token,因为每次调用都是一次往返 —— 网络、外部 API,通常还涉及队列。一个平均 3 次工具调用但 p99 为 60 次的 Agent,实际上是一个伪装成 token 问题的并发问题。那些 60 次调用的请求会占用 worker、上下文窗口和连接长达数分钟。
有了这两个分布,容量规划就变成了一项蒙特卡罗(Monte Carlo)模拟任务,而不是简单的乘法。根据你预期的请求组合,从真实的单功能分布中取样,运行几千次,然后读取生成的总 token 数、峰值并发工具调用数和 GPU 秒数的分布。输出是一个带有置信区间(confidence bounds)的范围 —— 这种方式更诚实,因为工作负载本身就是一个范围。点估计(point estimate)从来都是一种虚构;只不过以前它这种虚构还算接近现实。
这也重塑了自动扩缩容(autoscaling)。CPU 利用率在这里毫无用处,GPU 利用率也几乎同样糟糕 —— 在活跃的批处理期间,无论实际余量如何,GPU 利用率都会接近 100%。真正能追踪服务压力的信号是队列深度、队列等待时间、正在处理的序列数、KV 缓存占用率以及首个 token 时间(TTFT)。最近的推理服务研究对此达成了一致:应基于队列和缓存压力进行扩缩容,而不是基于硬件繁忙百分比,因为无论你游刃有余还是即将崩溃,硬件看起来都一样忙。
失控请求是容量事件,而不只是个 Bug
每一个在生产环境中运行 Agent 的团队最终都会遇到那种永远停不下来的请求。Agent 循环重试一个已失效的端点。它重新规划,重新读取不断膨胀的上下文,然后再重新规划。公开分享的事故并不罕见:一个重试循环在 11 天内悄无声息地花掉了 47,000 美元;一个 Agent 循环烧掉了 30,000 美元;数十万次 API 调用,却没有任何告警触发。
很容易将其归类为“Bug”。但从容量的角度来看,失控请求是单次请求成本无上限的工作负载的必然结果。如果单个请求原则上可以消耗 100 万个 Token,那么一个有 Bug 的请求就可以消耗 1000 万个,而阻隔在你与这一结果之间的唯一屏障就是明确的限制。针对这种工作负载的容量规划在设定上限之前是不完整的——因为如果你不设定上限,模型就会为你设定,而模型并不了解你的预算。
这种上限必须在 Agent 外部强制执行。让 Agent 自行遵守 Token 预算,距离它因推理出 错而跳过检查仅一步之遥;产生 Bug 的路径和检查预算的路径是同一段不可信的代码。限制应当存在于网关层——即转发 LLM 调用的那一层。限制每个请求的 Token 数、工具调用次数、实际运行时间以及连续的相同错误。当一个请求越界时,网关会将其终止。Agent 无法发起违反策略的调用,因为该调用根本发不出去。
将上限视为真正的容量参数,而不是紧急避险按钮。你的单次请求最坏情况限制乘以最坏情况下的并发请求数,就是你实际的资源配置上限——也就是你规划集群规模和月度预算的基准数字。如果没有强制限制,这个乘积就是无穷大,而你无法针对无穷大进行规划。有了限制,肥尾(Fat Tail)就变成了“肥但有界”的尾部,这是蒙特卡罗模型可以处理的情况。限制才是让工作负载变得可规划的关键。
为无法预知的成本进行设计
最深刻的转变是接受这一事实:在请求完成之前,你无法知道它的成本。确定性的容量规划高度依赖于预知——你在开始工作之前就了解工作量。这种杠杆已经不复存在,假装它还存在只会产生精确但错误的计划。
你能做的是设计一个系统,让它随着成本的显现而以受控的方式降级:
- 先分类再提交。一个廉价、快速的分拣步骤——例如小型模型或启发式算法——可以将传入的请求分为“可能较短”和“可能较长”的通道,并将其路由到不同的资源池。你有时会出错,但即使是粗略的划分,也能让短请求通道保持较低的 延迟,而不是排在长尾请求后面。
- 随走随计。实时跟踪请求过程中的 Token 和工具调用开销。一个已经使用了 80% 预算且仍未收敛的请求,是你可以在剩余 20% 耗尽之前采取行动的有效信息。
- 使长尾请求可抢占。运行时间较长的 Agent 请求应该是可以暂停、保存检查点或在大量廉价交互请求涌入时重新调度的。在推理系统中,跨实例的运行时调度正在成为标准,正是因为长尾请求在负载下需要向中位数请求让路。
- 按功能计费,按功能告警。混合成本仪表盘会掩盖正在失控的局部细节。按功能划分 Token 和工具调用预算,可以将缓慢的成本漂移转化为早期告警,而不是季末的“惊喜”。
这一切都不会让工作负载变得具有确定性。它使其变得可观察且有界——这才是现实的目标。你无法预测下一个请求的成本。你将根据实际测量过的分布进行资源配置,限制尾部使其不会无限延伸,并构建一个能够在长尾请求集中出现时优雅地舍弃或推迟昂贵任务的系统。
心态的转变是核心任务。不要再针对平均请求进行规划,因为根本不存在平均请求——所谓的平均请求其实是共用同一个 URL 的短请求和长请求。针对分布进行规划,对各分位数进行监控,强制执行上限,让中位数请求和长尾请求各自留在它们本该属于的独立世界。那些对 Agent 基础设施账单感到惊讶的团队并不是数学不好。他们只是在一个根本无法描述其工作负载的数字——平均值——上进行了正确的算术运算。
- https://portkey.ai/blog/rate-limiting-for-llm-applications/
- https://zuplo.com/learning-center/token-based-rate-limiting-ai-agents
- https://arxiv.org/html/2510.26585v2
- https://docs.cloud.google.com/kubernetes-engine/docs/best-practices/machine-learning/inference/autoscaling
- https://arxiv.org/pdf/2501.08090
- https://www.usenix.org/system/files/osdi24-sun-biao.pdf
- https://relayplane.com/blog/agent-runaway-costs-2026
- https://fountaincity.tech/resources/blog/ai-agent-cost-circuit-breaker/
- https://bentoml.com/llm/inference-optimization/llm-inference-metrics
