大模型 API 并发怎么规划:RPM 和 TPM 到底该怎么算
容量规划做错,症状很少是”系统变慢了”。典型症状是上线当天,流量刚爬到平时的两三倍,日志里突然刷屏 429,一批任务集体失败,重试又把剩下的配额吃光,最后连本来能跑通的请求也一起挂掉。更让人别扭的是,事前所有人都觉得”我们量不大”——每天几万次调用,摊到每分钟才几十次,怎么看都富裕。
问题出在,多数人只按请求数估容量,忘了 token 也是一把尺子。一个每分钟只发 30 次请求的服务,如果每次都塞进去一份两万 token 的文档,它在 token 维度上的压力,可能比一个每分钟发 500 次短请求的服务还大。你按请求数算完觉得用了一成配额,实际早就把 token 配额顶穿了。
两个维度同时卡着你
绝大多数模型 API 的限速是两条线并行的:
| 维度 | 含义 | 什么业务先撞上 |
|---|---|---|
| RPM | 每分钟请求数 | 单次很短、调用很密的场景 |
| TPM | 每分钟 token 数(多数平台是输入+输出合并计) | 单次上下文很长、调用不密的场景 |
关键机制只有一句话:先撞到哪条,哪条生效。 两条线不是取平均,也不是互相补偿,而是取小。你有富余的那一条,一点忙都帮不上。
哪种业务先撞 RPM?短请求高频那一类。意图分类、敏感词判定、标题改写、给一段短文本打标签、结构化字段抽取——单次输入输出加起来可能只有几百 token,但业务侧是每来一条数据就调一次。这种形态下 token 配额往往还剩一大半,请求数已经见底了。
哪种业务先撞 TPM?长上下文低频那一类。合同/论文/财报的整篇分析、长会话 Agent(每轮都要把历史全带上,上下文像滚雪球一样涨)、把整个代码仓库的相关文件塞进去做审查、RAG 里一次召回十几段长文档。这种形态每分钟可能只发个位数请求,但每次都是万级 token,请求数配额闲着,token 配额已经报警。
还有一类最容易翻车的,是同一个 key 上混跑两种形态。线上主链路是短请求高频,后台还挂着一个批量文档处理的定时任务。平时各跑各的相安无事,某天定时任务的积压变多,长文档把 TPM 吃满,主链路的短请求跟着一起被拒——它明明只用了很少的请求数配额。这种故障排查起来特别费劲,因为出问题的服务和吃掉配额的服务不是同一个。
两个维度同时手算比较烦,尤其是要反复试”如果单请求 token 数从 3000 涨到 8000 会怎样”。站内的并发规划计算器就是干这个的:填进去额度 RPM、额度 TPM、单请求平均 token、单请求耗时和预留余量,它会分别算出”按 RPM 每分钟能发多少次”和”按 TPM 每分钟能发多少次”,取小的那个作为实际可用速率,并直接告诉你瓶颈是 RPM 还是 TPM。想验证上面说的形态差异,把单请求 token 数拉大再看瓶颈标签的变化,一眼就明白了。
具体每家平台、每个模型、每个账号等级的 RPM/TPM 是多少,这篇不写,也不该在文章里写死——它随等级、随模型、随时间调整,以你自己控制台和官方文档里显示的数值为准。方法论是通用的,数值必须现查。
稳态并发怎么算
这是整篇最核心的一个公式,来自排队论里的 Little 定律,但用在这儿不需要任何数学背景:
并发数 ≈ 每秒可发请求数 × 单次请求耗时(秒)
推导很直觉。假设一个请求从发出到拿到完整响应要 8 秒,而你希望每秒稳定发出 5 个请求。那么在任意一个时刻,正在”飞行中”(已发出、还没返回)的请求数量是多少?第 1 秒发的 5 个,到第 8 秒才陆续回来,这期间第 2 到第 8 秒又各发了 5 个。稳态下同时在途的就是 5 × 8 = 40 个。这 40 就是你的客户端需要维持的并发数——少了达不到目标速率,多了会超过配额。
把速率换成每分钟口径,公式等价于:
并发数 ≈ 实际可用 RPM ÷ 60 × 单请求耗时(秒)
下面用一组假设参数完整算一遍。再强调一次,这几个数字是为了演示算术而虚构的,不代表任何厂商的真实额度:
| 参数 | 假设值 |
|---|---|
| 额度 RPM | 600 次/分钟 |
| 额度 TPM | 900,000 token/分钟 |
| 单请求平均 token(输入+输出) | 3,000 |
| 单请求耗时(p95) | 8 秒 |
| 预留余量 | 30% |
第一步,两个维度各自换算成”每分钟能发多少次请求”:
- 按 RPM 算:直接就是 600 次/分钟。
- 按 TPM 算:900,000 ÷ 3,000 = 300 次/分钟。
第二步,取小者作为实际可用速率:min(600, 300) = 300 次/分钟。瓶颈是 TPM,请求数配额只用掉了一半。这时候去申请提高 RPM 一点用都没有,该提的是 TPM。
第三步,换算稳态并发:300 ÷ 60 = 5 次/秒,5 × 8 = 40。也就是打满配额需要维持 40 路并发。
第四步,留 30% 余量:40 × 0.7 = 28。客户端并发池配置 28,是一个能稳定跑、又不至于贴着天花板摩擦的值。
顺带一算:实际可用速率 300 次/分钟,一小时就是 18,000 次。如果业务峰值需要 420 次/分钟,缺口是 420 − 300 = 120 次/分钟,这个缺口靠调并发是补不上的,只能压缩单请求 token 数或者去申请提额。
换成短请求形态再算一遍,感受一下差别有多大。同样的额度,单请求平均 400 token、p95 耗时 1.5 秒:按 TPM 算 900,000 ÷ 400 = 2,250 次/分钟,按 RPM 算 600 次/分钟,取小得 600,瓶颈翻到了 RPM。稳态并发 = 600 ÷ 60 × 1.5 = 15,留 30% 余量后配 10。同一份额度,一个场景要 28 路并发,另一个只要 10 路——如果你拿别人项目里的并发数直接抄过来,多半是错的。
耗时必须用 p95,不能用平均
公式里的”单次请求耗时”是最容易填错的一格。很多人顺手填了平均耗时,结果算出来的容量在高峰期一定不够。
还是上面那组参数。假设平均耗时 4 秒、p95 是 8 秒。按平均值算,并发数 = 5 × 4 = 20。你把并发池配成 20,看起来也能跑。但吞吐量是并发数 ÷ 实际耗时:当流量上来、排队变长,实际耗时逼近 p95 的 8 秒时,20 路并发只能撑起 20 ÷ 8 = 2.5 次/秒 = 150 次/分钟——只有目标 300 的一半。于是任务开始积压,积压又拉长端到端延迟,看起来像”系统扛不住”,其实是并发池从一开始就配小了。
按 p95 算出来的 40,在平常(4 秒)会有一部分并发闲置,但那不是浪费——服务端限速本来就会挡住你,闲置的槽位在高峰期正好顶上。容量按坏情况规划,成本按常态计费,这两件事不冲突。
大模型 API 的耗时分布尾巴通常拖得比传统接口更长:输出长度不定、是否命中缓存、是否触发内容审核、模型侧排队情况,都会把个别请求拉长好几倍。所以这里更该看 p95 甚至 p99,而不是被大量短请求拉低的平均值。关于耗时波动本身怎么观测和处理,可以对照看并发控制与速率限制的实操写法。
余量留多少,被谁吃掉
不要按 100% 打满配额来规划。这不是保守,是因为有三样东西一定会来抢那部分你以为空着的容量。
第一是重试。 撞限之后客户端会重试,而重试请求同样占配额。这里有个恶性循环:配额满了 → 请求被拒 → 客户端重试 → 重试挤占本就紧张的配额 → 更多请求被拒 → 更多重试。如果重试策略写得糙(固定间隔、无上限、无抖动),一次小小的流量毛刺能被放大成持续几分钟的雪崩。所以规划容量时,必须把重试量算进去,而不是假设每个请求只发一次。退避怎么写才不放大故障,指数退避与重试策略里讲得更细。
第二是突发流量。 真实流量从来不是均匀铺在每一分钟里的。哪怕日均曲线很平,秒级看也是一阵一阵的:定时任务整点触发、上游批量推送、用户在某个活动节点集中操作。更麻烦的是,不少平台的限速统计窗口比一分钟短得多,你”每分钟只发了 300 次”但集中在前 10 秒发完,照样会被判超限。留余量本质上是给这种时间分布的不均匀性买保险。
第三是多环境共用配额。 这条最隐蔽。限速额度通常挂在组织或项目层级,而不是挂在你的某个服务上。如果开发、CI、预发、生产用的是同一个组织下的 key,它们在抢同一个池子。CI 里一个新加的集成测试跑起来,可能就是生产在午高峰被限流的原因。定位这类问题特别耗时,因为报错发生在生产,源头在流水线。可行的做法是按环境拆 key 或拆项目(能拆则拆),拆不了就给每个环境设死客户端侧的速率上限,别让测试环境有能力吃掉全部配额。
比例给多少合适?不给死数字,因为它取决于你自己的波动性:流量平稳、单请求耗时稳定、重试率低的服务,余量可以少留;流量尖峰明显、输出长度不可控、上游偶发抖动的服务,就得多留。方法是先按一个偏保守的比例上线,观察一段时间的实际限流命中率和峰谷比,再往回收。计算器里的”预留余量”就是这个百分比,把它从 10% 拉到 40% 看建议并发怎么变,比拍脑袋定一个数踏实。
撞限之后的正确反应
客户端限流优于让请求打出去被拒
很多人的第一反应是”被拒就重试呗”,把限流交给服务端去做。这是错的,理由有三层:
一是被拒本身不是免费的。请求打出去、连接建立、走完一个网络往返、拿回一个错误、客户端解析、进入重试队列——这些都消耗时间和资源,而你什么结果都没拿到。在自己进程里排队等 200 毫秒,比打出去被拒再等 200 毫秒重试划算得多。
二是服务端拒绝是无差别的。你的低优先级批处理任务和高优先级在线请求,在服务端眼里没有区别,谁先到谁占坑。而在客户端排队,你可以让在线请求插队、让批处理任务让路。这个能力只有把限流放在自己这边才有。
三是被拒会放大波动。前面说过重试的恶性循环。主动限流把这个正反馈从系统里拿掉了:请求根本不会超发,也就不存在”因为超发导致更多超发”。
429 的具体识别和分类(哪些该退避、哪些该直接失败、哪些是配额耗尽而非速率超限),可以参考429 限流错误怎么处理。
队列加令牌桶的基本思路
实现上不需要多复杂,两件东西就够:
一个令牌桶负责速率。桶的容量对应允许的突发量,按固定速率往桶里补令牌,请求必须先拿到令牌才能发出。token 维度也可以用同一套机制:发请求前按预估 token 数扣相应数量的令牌,请求返回后拿实际用量做一次校正。预估偏差是难免的,所以余量要留够。
一个队列加固定大小的工作池负责并发。工作池大小就是前面算出来的建议并发数,队列负责削峰。队列必须有界——无界队列在过载时不会拒绝任何请求,只会让延迟无限增长,最后所有请求都超时,这比早点拒绝几个请求糟糕得多。队列满了就快速失败或降级,让上游知道。
两者是配合关系,不是二选一:令牌桶管”每秒不许超过多少”,工作池管”同时在途不许超过多少”。只有并发限制没有速率限制,短耗时请求会瞬间超速;只有速率限制没有并发限制,长耗时请求会堆到把内存吃满。
什么时候该提额,而不是继续优化
优化到某个点就该停了。以下几种情况,继续在客户端抠是浪费时间:
- 算出来的实际可用速率,长期低于业务峰值需求。 这是硬缺口,排队只能把峰值摊平,摊不掉总量。像前面算例里那 120 次/分钟的缺口,队列再聪明也变不出来。
- 瓶颈在 TPM,而单请求 token 已经压无可压。 该裁的历史裁了、该换检索的换了、prompt 该精简的精简了,再压就要伤效果,那就是额度的问题。
- 队列等待时间已经影响用户体验。 排队的成本是延迟。如果在线请求要排队等几秒才轮到发出,用户感知到的就是卡,这时候该加的是配额不是耐心。
- 限流命中率长期居高不下。 说明你的规划值和真实需求之间存在系统性差距,不是偶发抖动。
申请提额时最好带着数据去:当前额度、实测的实际可用速率、业务峰值需求、缺口多少、期望调到多少。用并发规划计算器把目标并发倒推成需要的 RPM/TPM,比只说一句”不够用,请提高”有说服力得多——它同时接受一个”业务峰值(每分钟请求数)“输入,会直接告诉你够不够、缺口是多少。
成本视角:为什么宁可自己排队
并发规划常被当成纯稳定性问题,其实它也是成本问题。
先说清楚计费口径,别夸大:被限流直接拒绝的请求,多数平台不会产生 token 费用,因为模型压根没开始跑。真正花钱的是这几种:
- 流式输出到一半断开后重试。 已经生成的那部分 token,多数平台照计。你为一个没交付的结果付了钱,重试再付一次,这是实打实的双倍。
- 超时重试但上游其实跑完了。 客户端等不及断开,服务端仍然完成了生成并计费。这种”幽灵成功”在长输出场景很常见,重试一次就是付两次。
- 经过聚合网关或多层代理时的重复计费。 每一层都有自己的重试逻辑,如果层层都重试,一个用户请求可能对应上游好几次真实调用。具体是否计费、怎么计费,以你所用平台和网关的官方计费口径为准。
而排队的成本是什么?只有时间。请求在你自己进程的队列里等 500 毫秒,不产生任何账单。这就是”宁可自己排队也别打出去撞墙”的经济学理由:排队消耗的是延迟这种可以摊平的资源,撞墙消耗的是往返、连接、以及部分场景下真金白银的重复计费。
还有一层间接成本更容易被忽略:撞限触发的告警、值班响应、事后复盘、临时扩容的沟通,这些人力开销往往比多付的那点 token 费用贵得多。一次线上限流事故的处理成本,够你把容量规划认真做十遍。
上线前自查清单
- RPM 和 TPM 两个维度都算过了,明确知道自己的瓶颈是哪一条——而不是只看了请求数。
- 稳态并发用的是 p95 耗时,不是平均耗时;如果只有平均值,先去把耗时分布补上再算。
- 单请求平均 token 是按输入+输出合计口径估的,长上下文场景专门核过最坏情况下的上下文长度。
- 配额没有按 100% 规划,明确留了余量,并且清楚这份余量是给重试、突发和多环境共用留的。
- 开发、CI、预发、生产的配额归属查过了:是共用一个池子,还是各自独立;共用就给非生产环境设死客户端限速。
- 客户端有主动限流(令牌桶控速率 + 有界队列和固定工作池控并发),而不是靠服务端拒绝来兜底。
- 队列是有界的,满了会快速失败或降级,不会无限堆积把延迟拖垮。
- 重试有指数退避和抖动、有次数上限,并且重试量已经计入容量规划。
- 限流命中率、队列等待时间、实际 RPM/TPM 用量这三个指标都在监控里,能看出”该提额了”的信号。
把你自己的额度和请求特征填进并发规划计算器,先拿到瓶颈判定和建议并发数,再回来对着这份清单逐条过一遍。