跳到主要内容

Agent 循环容量计算:为什么你的预置吞吐量只有你想象的一半

· 阅读需 13 分钟
Tian Pan
Software Engineer

我曾合作过的一个团队发布了一个他们称之为 “小规模” 的功能:一个供几百名分析师使用的内部研究助手。他们的容量模型认为一次用户请求等于一次模型调用,因此他们根据峰值用户 QPS,并预留了标准的 30% 突发余量来设定预置吞吐量(provisioned throughput)的大小。发布当天,他们在不到一小时内就遇到了 429 错误,原本应该只消耗 40% 预留容量的流量却让容量达到了 100% 的饱和状态。事后复盘发现了一个之前没有人算进去的数字:平均每个请求触发了 11 次模型调用,而不是 1 次。

这是我在 Agent 推广过程中看到的最常见的容量估算失误。其中的数学逻辑并不复杂,失败模式也并不罕见。团队问错了单位问题——他们以用户请求为单位进行规划,而计费表是按模型调用次数计次的——他们支付真金白银买下的预留容量,在一种如果换成聊天产品本应被视为轻负载的压力下化为乌有。

这种模式在每一个将 Agent 强行套用在请求型容量模型上的团队中不断重复。预置吞吐量合同(Azure 上的 PTU、Vertex 上的 GSU、Bedrock 和 Databricks 上的预置吞吐量端点)通常以每秒 Token 数、每分钟请求数或两者的结合来计算;这些平台上的容量工程已经非常成熟且文档齐全。但由于 Agent 循环介入其中,导致用户侧事件与计费侧事件之间的倍数关系缺乏文档记录。而这个倍数正是问题的关键。

扇出系数不是可选的,它是核心指标

一个简单的聊天调用大致是一对一的映射:一次用户请求,一次模型调用,双侧的 Token 数量都是可预测的。Agent 循环则不然。单个用户请求会触发一个规划步骤,接着是一个工具调用,然后是一个解释工具结果的模型调用,之后通常还有另一个规划步骤,最后往往还有一个格式化或自我批判(self-critique)环节。格式修正重试——即模型输出了无效的 JSON 或调用了不存在的工具——又会增加每一轮重试的往返。多 Agent 协作交接增加的调用则更多。

Token 成本比例已经过测算。Anthropic 官方给出的框架是:标准聊天的 Token 成本为 1 倍,单 Agent 循环约为 4 倍,而多 Agent 系统则在 15 倍左右。循环深度(Loop depth,即每个用户请求产生的模型调用次数)是衡量这一点的最清晰的单一指标。在实践中,我看到生产环境中的 Agent 每次请求通常产生 5 到 20 次模型调用,其中检索与工具调用(retrieval-and-tool-use)设计的典型值在 8 到 12 次之间。

两个后果紧随其后:

  • 根据用户 QPS 确定的预置 Token 吞吐量会过度承诺容量,其偏差正好是循环扇出系数(loop fan-out factor)。如果你的平均循环深度是 11,那么你预留的吞吐量实际上只有在相同用户负载下所需吞吐量的十一分之一。
  • 每个用户请求的成本也随循环深度同步增加。一个在你假设只有三次调用时还能实现盈亏平衡的功能,在十一次调用时就会变成一场毛利灾难。没有将循环深度纳入单位经济模型的业务团队会在下一个账单周期发现这一点。

第一个后果是可靠性问题。第二个是损益(P&L)问题。它们有着共同的根源:Agent 循环的扇出是容量和成本的单位,而将用户请求视为单位则掩盖了实际的工作负载。

利特尔法则,应用于真正关键的地方

衡量 Agent 系统的正确方法是将利特尔法则(Little's Law)应用于模型调用,而不是用户请求。利特尔法则指出,一个稳定系统中的平均项目数等于到达率乘以每个项目在系统中的平均停留时间。对于聊天产品,到达率是用户 QPS,系统停留时间是模型延迟。对于 Agent 产品,模型端点的到达率是用户 QPS 乘以循环深度,而系统停留时间是单次模型调用的延迟,而不是 Agent 端到端的延迟。

具体来说:如果一个聊天产品在模型延迟为 2 秒时处理 10 个用户 QPS,利特尔法则告诉你要为 20 个并发进行中的模型调用做好规划,并相应地配置内存、批处理容量和预留 Token。如果一个 Agent 产品处理 10 个用户 QPS,循环深度为 11,单次调用延迟为 1.5 秒,那么对应的数字是 10 × 11 × 1.5 = 165 个并发进行中的模型调用。这才是必须控制在你的预置吞吐量包络线内的数字,而不是 20。

这是最简单的修正方法,它能让容量规划产生一个数量级的变化。同样的修正也出现在你进行吞吐量计算的任何地方:每秒 Token 数预算是用户 Token 预算乘以循环深度;KV 缓存(KV-cache)的内存压力随并发调用量而非并发用户数扩展;批处理大小的需求追踪的是进行中的调用数,而非进行中的请求数。

在每一个 AI 功能说明书上都应该提出的容量规划问题不是 “我们的峰值用户 QPS 是多少”,而是 “我们的峰值模型调用率是多少”。这要求团队在功能上线前就确定循环深度的分布,包括平均值和长尾值(由于重试导致的长尾效应,P95 的循环深度通常是平均值的 2 到 3 倍)。

突发性比平均值所显示的更糟糕

平均循环深度是一个具有迷惑性的友好数字。均值决定了稳态成本,而尾部情况则决定了系统是否能维持运行。

Agent 循环在两个相互叠加的维度上具有突发性。首先,单个请求的深度是可变的:一条理想路径(happy path)可能在 5 次调用内完成,一条带有重试的路径会耗费 8 次,而一条陷入退化规划器状态的路径在触发实际时间限制(wall-clock cap)之前可能会耗费 20 次甚至更多。其次,当 Agent 并行扇出工具调用时,单个请求内的多次调用几乎同时到达模型端点,随后在工具执行期间又交替出现安静窗口。推理端点所呈现的负载形状不是平滑的流,而是一连串的微爆发(micro-bursts)。

预置吞吐量(Provisioned throughput)窗口可以部分吸收这些压力。例如,Vertex AI 的动态窗口强制执行机制允许 70k-tokens-per-second 的爆发通过,只要 120 秒的平均值保持在配额内。Azure 的漏桶(leaky-bucket)变体也执行类似的平滑处理。但平滑窗口是有限的,如果一大群 Agent 同时进入规划阶段,产生的峰值可能会超出窗口的吸收能力。

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