← 返回资讯

重试的隐藏成本:为什么大模型 API 账单总比估算高一截

2026-08-07

上线前那份成本表是这么算出来的:月调用次数 × 单次 token × 单价。三个数一乘,报给老板,批了。一个月后财务把账单甩过来,比表上高了三成多。第一反应总是”是不是被刷量了”,查了一圈日志,没有异常 IP,没有爬虫,用户量也和预期差不多。

我见过不少团队卡在这一步,然后开始怀疑是不是平台算错了。绝大多数情况不是。差额也不是那个乘法算错了——那个乘法本身没毛病,问题是它统计的是”成功交付了业务价值的调用”,而账单统计的是”你实际发出去、并且产生了 token 的每一个请求”。这两个口径之间隔着一整类消耗,它们从来就没进过公式。

这篇就是把这一整类消耗一项项揪出来:每项讲清楚机制、怎么估、怎么减,最后用一组标注了假设的参数做一次完整的对比算术。

先把口径对齐:预算公式漏掉了什么

写预算的时候,脑子里的模型大概是这样的:

每个用户每天问 5 次 → 有多少用户 → 一个月多少次 → 每次大概多少 token → 乘单价。

这个模型隐含了四个”理想假设”:每次请求都成功、每次结果都可用、只有线上真实用户在调、每次的 token 数都接近你估的那个数。这四条在生产环境里没有一条完全成立。

破掉这四条,就得到六项隐性消耗:

项目破掉的假设是否算在预算里
重试(会计费的那部分)请求都成功
超时后的”幽灵成功”请求都成功
格式不合格的返工结果都可用
开发调试与 CI只有线上流量
长尾超长请求token 数接近均值部分低估
被丢弃的输出结果都被消费

如果你连基线那部分都还没系统地估过,建议先看上线前怎么估算月成本把基数做扎实,再回来加这六项——顺序反了的话,你会分不清超支到底是基数估错了还是隐性消耗吃掉的。

一、重试:先分清哪些白花钱,哪些不花钱

“重试很贵”是句半对的话。重试贵不贵,取决于失败发生在链路的哪一段

请求从客户端出发,要依次经过:连接建立 → 网关鉴权 → 限流闸门 → 排队 → 模型推理 → 返回。**只有走到”模型推理”这一步并且真的吐出了 token,才产生计费量。**在这之前失败的,通常不产生计费 token。

按这个切分,重试分成三堆:

第一堆:不产生计费 token,但重试有意义。 典型是 429(限流)和连接类错误。限流是在闸门处被拒的,模型压根没启动。这类重试你可以放心退避重试,多花的是延迟和连接数,不是钱。

服务端错误这一类要单独说,因为各家计费口径并不统一。目前我手上能拿到官方明确表述的只有 Groq:其官方错误码文档明确写着 5xx 响应不计费(来源:Groq 官方文档错误码章节)。这条只对 Groq 成立,其他平台的口径以各自官方文档为准,不要拿这条去套别家——真要确认,最稳妥的办法是自己做一次对照:在一段时间窗内统计 5xx 计数,再去账单里看对应时段的计费 token 有没有相应的凸起。

第二堆:不产生计费 token,而且重试注定失败。 这堆是纯粹的浪费——浪费的不是钱,是你的超时预算和用户的等待时间,更糟的是它会让重试队列堆积,把真正该重试的请求挤掉。

Groq 官方错误码清单里把这堆分得挺细,值得借来当分类模板:400 Bad Request 是语法非法,422 Unprocessable Entity 是格式合法但语义错误,413 Request Entity Too Large 是请求体过大,401 Unauthorized 是凭据缺失或无效,403 Forbidden 是权限不足,404 Not Found。这六类你重试一百次也是同一个结果,因为出错的是请求本身。同一份清单里还有两个自定义码值得留意:498 表示 Flex Tier 容量超限,499 表示调用方取消请求——498 属于容量类,是该退避重试的;499 是你自己取消的,重试与否取决于业务。

第三堆:真的会花钱。 这堆有个共同特征:模型已经跑了,token 已经生成了,只是你没用上。超时后重发、结果不合格重跑、内容不满意再来一次——都属于这堆。下面两节单独拆。

估算方法:从网关日志里按状态码分桶统计月度计数,第一堆和第二堆记为”零计费”,第三堆记为”每次一份完整调用”。别用”重试率 5%“这种笼统数字乘总量,那会把不花钱的部分也算进成本,反而误导优化方向。

二、幽灵成功:超时了,但服务端跑完了

这是最难查也最容易反复付费的一项。

客户端设了 30 秒超时。某次请求排队久了一点,31 秒才返回。客户端在第 30 秒断开、判定失败、发起重试。但服务端那边根本不知道你走了,它把这次推理完整跑完,token 照样计费。你重试的那次又跑了一遍,又计一份。一次业务需求,付了两次钱,拿到一次结果。

它难查,是因为你的应用日志里这次是”失败”,你的成本表里它是”成功”。两边对不上,但没人会去对。

更坏的情况是超时时间设得偏紧、而后端偶尔抖动的场景:超时→重试→新请求同样卡住→再超时→再重试。一个请求变成三份账单,业务侧看到的仍然只是”这个功能有点慢”。

减法有三层:

  1. 超时时间要按输出长度分档,不能全局一个值。生成 200 字摘要和生成 3000 字报告用同一个超时,必然有一档是错的。按最大输出 token 数估一个下限,再留余量。
  2. 用流式接收把”是否活着”和”是否完成”分开。流式下只要还在持续吐 token,就说明服务端在干活,此时该等的是”多久没有新 token”而不是”总共花了多久”。这一个改动能消掉相当一部分伪超时。
  3. 给请求加幂等标识,让重试可以被识别、被去重,而不是盲目再发一份。这块的做法参见大模型 API 调用的幂等设计

三、格式返工:结果拿到了,但用不了

只要你的调用是结构化输出——要 JSON、要固定字段、要能被下游程序解析——就一定有一部分响应解析失败。字段名漂了、多包了一层 markdown 代码围栏、数组里少了个逗号、该是数字的地方给了字符串。

这类失败的成本特别隐蔽,因为它在平台侧是彻头彻尾的成功调用:状态码 200,token 正常计费,监控面板一片绿。只有你的解析函数知道这次白跑了。而返工请求往往比原请求更贵——你通常会把错误信息和上一次的错误输出一起塞回去让它改,输入 token 反而涨了。

估算方法很直接:在解析层埋一个计数器,统计”解析失败次数 / 结构化调用总次数”,这个比例乘上结构化调用的平均 token 量,就是这一项。做过这个埋点的团队通常会被这个数字吓一跳,因为在没有约束的裸提示词下,这个比例达到个位数百分比是很常见的。

减法按性价比排:优先用平台提供的结构化输出约束能力(能否使用、参数怎么写以各平台官方文档为准),其次在提示词里给一个完整的输出样例而不是只描述字段,再次是把”解析失败的原始输出”存下来定期复盘——你会发现失败高度集中在两三种固定花样上,针对性改一句提示词就能砍掉大半。

顺带说一句边界:不要用重试去解决格式问题。格式不稳定是提示词/约束的问题,用重试兜底等于每次都多付一份钱去买一个本可以在源头解决的确定性。

四、开发调试与 CI:不在预算表里的那条流水线

这一项几乎从来不会出现在成本估算里,因为估算是产品同学按”用户行为”做的,而这部分流量来自工程师和流水线。

它由三块组成:

  • **日常调试。**调提示词的时候,一个字一个字试效果,一下午几十上百次调用是常态。人越多,倍数越大。
  • **CI 里的真实调用。**如果你的测试用例里有真调模型的集成测试,每次推送都会跑一遍。分支多、推送频繁的团队,这块的量能超过日常调试。
  • **数据回填与批量评测。**跑一次全量评测集就是几百上千次调用,而且往往用的是最贵的那个模型。

这块的可怕之处不在绝对量,而在于它和业务量完全脱钩:业务淡季账单该降,它不降;你还没上线,它已经在花钱了。

减法反而是六项里最容易做的,因为它不影响线上体验:把 CI 里的模型调用做录制回放,只在夜间定时任务里跑一次真实调用做冒烟;日常调试用一个专门的低成本模型档位,只在最后定稿时用生产模型验证;评测集做分层,日常跑抽样子集,全量只在发版前跑。

五、长尾超长请求:看分布,不要看均值

预算表里那个”单次 token 数”是怎么来的?大概率是产品同学挑了十几二十个典型样例,数了数,取了个”大概值”。

问题是,这个”典型样例”通常落在中位数附近,而 token 分布是明显右偏的:绝大多数请求不长,但少数请求特别长——用户上传了一整份合同、RAG 一次召回了十几段、多轮对话累积了很长的历史。右偏分布的均值会被这条长尾拽得明显高于中位数,而你付钱是按均值(准确说是按总量)付的。

所以第一条纪律是:**估算和监控 token 用量都要看分位数,不要只看均值,更不要用”典型样例”当均值。**至少要有 P50 / P90 / P95 / P99 四个点。P99 和 P50 差几倍,你心里就要有几倍的准备。

第二条纪律是,长尾往往还伴随着更高的失败率——请求越长越容易超时,越容易撞上单次请求体上限(Groq 的错误码清单里 413 Request Entity Too Large 就是这个场景)。也就是说长尾请求会同时抬高第一、二、三项的成本,是个乘数效应。

减法:给上下文长度设硬上限,超了先做召回裁剪或分段处理,而不是直接怼给模型;对超长输入单独走一条路径,用更便宜的档位先做压缩摘要再进主流程;把 P99 那批请求捞出来看几个真实样本,通常能发现某个特定入口在无脑拼接上下文。

六、被丢弃的输出:付了钱,没人看

流式输出的场景里,用户看到前两句觉得不对,直接点了停止;或者页面切走了、网络断了、上游服务把请求取消了。这时候已经生成的那部分 token 是不是要付钱?

各平台对”取消/中断请求已生成部分”的计费口径不同,以各自官方文档为准,我不会在这里给一个通用答案。但从工程角度,你必须假设它是要付钱的——因为算力确实消耗了。Groq 的错误码清单里专门有个 499 表示调用方取消请求,说明这个场景常见到值得给它一个独立状态码。

这一项在预算里被漏掉的原因很典型:预算的分母是”成功交付业务价值的次数”,而被放弃的请求不产生业务价值,所以它从来没进过分子,也没进过分母。

减法:在前端上给”停止”按钮接上真正的请求取消(而不是只停止渲染,后端还在跑);对长输出场景先返回一个短的大纲让用户确认方向,再生成正文;把用户放弃率作为一个产品指标看——放弃率高通常说明首句质量差或者响应太慢,改好了既省钱又提升体验。

用一组假设参数算一遍

下面这组参数全部是假设值,用来演示上浮比例是怎么累出来的。成本按计费 token 量折算(同一模型同一档位下,成本与计费 token 量成正比),所以下面不涉及任何价格,你替换成自己的真实参数照样能算。

基线假设:

参数假设值
月度成功业务调用100,000 次
单次输入 token(按典型样例估)1,500
单次输出 token500

基线计费量 = 100,000 × (1,500 + 500) = 200,000,000 token(200 M)。这就是预算表上那个数。

六项隐性消耗(每项标注假设):

项目假设增量 token占基线
① 幽灵成功2% 的调用因客户端超时重发,服务端实际都跑完了 → 2,000 次各多付一份完整调用2,000 × 2,000 = 4 M2.00%
② 格式返工结构化调用占 40%(40,000 次),其中 8% 首次解析失败需返工一次;返工请求带错误信息,输入涨到 1,800、输出仍 5003,200 × 2,300 = 7.36 M3.68%
③ 开发与 CI5 名工程师 × 60 次/工作日 × 21 天 = 6,300 次;CI 每天 2 条流水线 × 40 用例 × 21 天 = 1,680 次;合计 7,980 次,按 2,000 token/次7,980 × 2,000 = 15.96 M7.98%
④ 长尾抬高的真实均值典型样例估的 2,000 实为中位数附近,全量真实均值 2,300100,000 × 300 = 30 M15.00%
⑤ 被丢弃的输出除有效调用外,另有 3% 比例的请求被中途取消(3,000 次),平均已生成 60% 输出(300 token),假设已生成部分计费3,000 × 1,800 = 5.4 M2.70%
⑥ 不计费的重试429 / 连接失败 / 4xx 参数错等,模型未启动00%
隐性合计62.72 M31.36%

含隐性消耗的总计费量 = 200 M + 62.72 M = 262.72 M,是基线的 1.3136 倍

这就是”账单比估算高一截”的完整来源。注意几个结构性事实:

  • **最大的一项(④,15.00%)不是重试,是估算基数本身取错了统计量。**很多团队一超支就先去砍重试,砍错了地方。
  • 第二大的一项(③,7.98%)跟线上业务完全无关,它甚至在你上线前就开始花钱了。
  • 真正因为”失败”多花的(①+②=5.68%)比直觉中小得多,而且其中一半是格式返工,不是网络故障。
  • 不计费的重试(⑥)占 0。这也是为什么”重试率”这个指标不能直接换算成钱。

再算一次优化后的:假设把 CI 的 40 个用例里 30 个改成录制回放(CI 调用降到 1,680 × 10/40 = 420 次,省 1,260 次 = 2.52 M),同时把格式失败率从 8% 压到 2%(返工降到 800 次 × 2,300 = 1.84 M,省 5.52 M)。两项合计省 8.04 M,隐性消耗降到 62.72 − 8.04 = 54.68 M,上浮从 31.36% 降到 27.34%

这两项是六项里改动成本最低的,动的都是自己家代码,不碰线上稳定性。这就是优先级该有的样子。

把这些纳入监控:它们是成本指标,不只是稳定性指标

上面六项里,有五项在通用监控面板上是看不见的,因为默认的面板只有”请求量、延迟、错误率”。要把成本管住,得补这几个指标——而且要把它们挂在成本看板上,不是只挂在稳定性看板上。这是最关键的一句话:**同一个数字,挂在不同的看板上,会被不同的人在不同的场景下看见。**运维看错误率是为了判断要不要告警,财务和技术负责人看同一个数字是为了判断这个月的钱花在哪了。

要补的指标:

retry_count{code}          # 重试计数,必须按状态码分桶
                           # 只有"模型已产出"的那类才折算成本
timeout_count              # 客户端超时计数
timeout_retry_count        # 超时后实际重发的计数 → 直接对应"幽灵成功"
parse_fail_count           # 结构化输出解析失败计数 → 直接对应格式返工
cancel_count               # 请求被取消/中断计数
tokens_in / tokens_out     # 必须记录分位数 p50 / p90 / p95 / p99,不要只记 avg
env_label{prod|dev|ci}     # 所有调用打环境标签,才能把开发/CI 消耗单独摘出来
effective_call_ratio       # 有效调用数 / 实际请求数,这个比值就是浪费率

其中 env_label 是投入产出比最高的一个:加一行代码,就能把第三项从”混在总量里的一团黑账”变成一条可以单独看趋势的曲线。effective_call_ratio 则是最好的单一健康度指标——它掉下去了,说明上面某一项在恶化,具体是哪一项再下钻看分桶。

告警口径也要跟着改:错误率告警看的是”当前是不是出故障了”,成本告警要看的是”这周的浪费率比上周高了多少”。后者通常用周环比,阈值可以设得比稳定性告警宽松,但一定要有人定期看。埋点和面板的具体做法可以参考大模型 API 成本监控怎么做

该减什么,先减什么,什么最后再动

优化顺序不能按”哪项最贵”排,要按”改动成本 ÷ 风险”排。我的排序是:

**第一步,砍掉注定失败的重试。**这是唯一一个零风险、零权衡的动作。在客户端把可重试错误做成白名单——只对限流类、服务端错误类、连接类重试,其余一律直接失败上抛。很多默认的 HTTP 客户端重试策略是”非 2xx 都重试”,这会把 400、401、413、422 这些注定失败的请求反复发三遍,除了拖慢响应、堆积队列没有任何收益。

可重试:429、5xx、连接/读取超时、容量类错误
不重试:400、401、403、404、413、422 等请求本身有问题的错误

**第二步,减掉可避免的返工和开发浪费。**格式约束、输出样例、CI 录制回放、调试用低成本档位——这些都只动自己的代码,不影响线上用户,而且效果立竿见影(上面的算例里这两项合计能把上浮从 31.36% 压到 27.34%)。

**第三步,治理长尾。**上下文裁剪、超长输入走独立路径。这一步开始要碰产品逻辑了,需要和产品同学对齐”哪些超长请求是真实需求,哪些是代码在无脑拼接”。

**最后才动重试策略本身。**为什么放最后:重试是可用性的最后一道防线,调紧它是在拿稳定性换钱,而按上面的算例,不计费的那部分重试根本不花钱,会花钱的那部分主要来自超时误判——那应该去修超时时间和幂等设计,而不是砍重试次数。真要动,也应该是往”更聪明”的方向调(退避曲线、抖动、按错误类型分策略),而不是往”更少”的方向调。这块的具体参数取舍见大模型 API 的重试与退避策略

有一个反直觉但重要的判断:**如果你的账单只高出三成左右,其实是健康的。**真正危险的信号是高出两倍以上——那通常意味着某处存在无限重试循环、或者某个入口在把整份文档反复喂进去,那是 bug,不是成本问题,得当故障查。

自查清单

  1. 把网关日志按状态码分桶,算出上个月各类失败的计数,标出哪些产生了计费 token、哪些没有。
  2. 检查 HTTP 客户端的重试白名单:确认 400 / 401 / 403 / 404 / 413 / 422 这类请求错误不会被重试。
  3. 统计”客户端超时后重发”的次数,这个数直接就是幽灵成功的下界;同时检查超时时间是否按输出长度分档。
  4. 在结构化输出的解析层加计数器,拿到真实的解析失败率,别凭感觉。
  5. 给所有调用打 prod / dev / ci 环境标签,把开发与 CI 的消耗从总量里单独摘出来看一条曲线。
  6. token 用量改成记 P50 / P90 / P95 / P99,把 P99 那批请求捞几个真实样本出来看,确认长尾是真实需求还是拼接 bug。
  7. 建一个 有效调用数 / 实际请求数 的比值指标,挂到成本看板上做周环比。
  8. 用你自己的真实参数把本文那张对比表复算一遍,得出属于你们的上浮比例,写进下一版预算——把安全系数换成有依据的数字,比拍脑袋加 30% 靠谱得多。

最后提醒一句边界:本文除 Groq 官方明确的「5xx 响应不计费」外,不涉及任何平台的具体计费口径;取消请求的已生成部分是否计费、限流拒绝是否产生费用、缓存命中如何折算,各家规则不同,一律以你所用平台的官方文档和账单明细为准。算例中的所有参数均为假设值,用途是演示算术结构,不是行业基准。

相关阅读