团队成员更新了嵌入模型,或修复了摄取管道中的解析错误,或迁移了文档存储——然后,为了让修复追溯生效,启动了一次回填。它在后台静默运行。没有人会关注它,因为它是一个运维任务,而不是一个功能。三天后,财务警报触发,更糟的是,月末账单到了,才发现一次例行的重新索引竟然以全额 token 价格重新跑完了你整个 AI 账单。
这是 AI 系统中最可以避免的、多达五位数的意外之一,而它却一再发生,原因是我们为在线流量建立的成本直觉,对于批处理工作来说是完全错误的模型。在线场景中,成本是自我调节的:真实用户以人类的速度访问,并发受到限制,如果出现问题,你会在延迟中感受到它,远早于你在账单上感受到它。回填则没有这些调节机制。它会尽可能快地处理整个语料库,只要你的速率限制允许,无论单个项目的经济效益在这种规模下是否合理。
解决办法不是害怕重新处理——随着模型的不断迭代和管道的改进,你将持续进行重新处理。正确的做法是,将一次大型运行视为一个一流的操作,为其制定预估、上限和分阶段计划,就像你对待针对生产数据的 schema 迁移一样。
为什么批处理超出了在线流量的直觉
你对“这个功能每天大约花费 X 美元”的感知,是建立在边际请求上的。一个用户提出问题,你花费几千个输入 token 和几百个输出 token,这笔费用就消失在月度账单的噪音中。这种直觉是基于一系列小型的、独立的、人类速度的事件进行校准的。
回填一次性打破了所有这些假设。它不是一个数据流,而是在一次性运行中处理了整个群体。它不是以人类速度运行的,而是机器以你的速率上限进行处理的。而且这些事件不是独立的——它们都来自同一个作业,所以没有来自用户因厌倦而离开的自然反压。重要的量纲从“每次请求的成本”转变为“每个语料库的成本”,这两个数字可以相差四到五个数量级。
陷阱在于,单个项目的成本在隔离状态下看起来是无害的。重新处理一份文档花费 0.015 美元感觉就像免费。但重新处理一千万份文档就是 15 万美元,而在线心智模型中的任何地方都不会在途中发出警报,因为每一个单独的调用都和往常一样便宜。危险完全存在于乘数中,而乘数在你刻意将其写下来之前是不可见的。
在你按下“开始”之前先进行乘法计算
在任何大型运行之前,计算一个数字:语料库大小乘以每个项目的 token 数乘以每个 token 的价格,并区分输入和输出。这只需要十分钟,却是你能做的最具杠杆作用的一件事。
从真实样本中获取 token 数量,而不是猜测——对几百个有代表性的项目进行 token 化并取平均值,因为“小型”文档的分布通常有一个“长尾”,它主导了总数。然后应用当前每百万 token 的费率。截至 2026 年初,一个中端生成模型大约每百万输入 token 2.50 美元,每百万输出 token 10 美元;前沿模型是其数倍;并且输出成本几乎总是输入的 4 到 5 倍,因此生成长完成内容的任务比其输入大小所显示的要昂贵得多。
具体来说:一个包含大约 3,150 个输入 token 和 400 个输出 token 的项目,在中端模型上花费略高于一美分。对于单个项目来说,这几乎可以忽略不计;每万个项目大约 120 美元;而如果你的语料库有一亿个文档,则会超过一百万美元。在同一个前沿模型上运行相同的作业,每个项目的成本对于相同的工作会增加一个数量级。你为交互式功能随意做出的模型选择,在你将其乘以整个语料库的那一刻,就变成了具有承重作用的财务决策。
还有两个因素会悄悄地使实际数字高于简单估计。如果你正在重新嵌入已记录的 payload,详细日志记录或跟踪捕获可以使有效 token 消耗加倍。而检索增强步骤中,如果它抓取十个块而实际上只需要两个,则每次调用会使输入膨胀 3 到 4 倍。估算你实际运行的管道,而不是白板上的理想化管道。
一个令人欣慰的推论是:并非所有重新处理都是生成。重新嵌入语料库比在其上重新生成内容要便宜得多,因为嵌入模型的定价约为每百万 token 0.02 美元——比生成便宜一百多倍。为一个庞大语料库重新生成规范嵌入可能接近 30 美元,而在同一语料库上重新生成模型输出则接近 200 美元。知道你正在进行哪种重新处理,会立即告诉你是在“直接运行”的范围,还是在“先获得批准”的范围。
批处理端点和缓存改变了计算方式
一旦有了预估,接下来的问题就是你是否要支付标价。对于重新处理,你几乎从不应该支付标价,因为回填是获得菜单上两大折扣的理想形式。
第一个就是批处理端点。每个主要提供商都以大约同步速率一半的价格提供异步批处理,换取长达 24 小时的完成窗口。单个提交可以承载数万个请求——对于某些提供商,一个文件可包含约 5 万个请求,对于其他提供商则为 1 万个。回填从定义上来说,对延迟不敏感:没有人会在重新索引的另一端等待。支付实时溢价来通宵重新处理语料库,简直是白白浪费 50% 的成本。
第二个是提示词缓存。如果你运行中的每个项目都共享一个大的静态前缀——相同的系统提示词、相同的少样本示例、相同的评分标准或 schema——那么共享部分可以被缓存和重用。缓存读取的成本大约是正常输入价格的 10%(在中端模型上,大约每百万 token 0.30 美元对比 3.00 美元),这是对重复部分的 90% 折扣。但需要注意的是,缓存写入大约有 25% 的溢价,并且缓存的生命周期较短,所以盈亏平衡点大约是每个缓存前缀命中两次或更多次。在语料库范围的运行中,相同的前缀会在每个项目上重复,你会立即达到盈亏平衡。批处理和缓存可以叠加,它们共同可以在你改变一行逻辑之前,将一个天真的六位数估计变成一个五位数。
重点不在于确切的百分比——这些数字是会变化的。重点在于你在上一节计算的估算值是标价,而重新处理工作负载正是符合最大折扣条件的工作负载。在你决定运行成本过高之前,以及在你决定它足够便宜以至于可以跳过此检查清单的其余部分之前,请重新计算应用批处理和缓存后的数字。
用限速窗口而非总预算来封顶
这是一个总预算上限无法捕捉到的故障。一个原本预计花费约 20 美元的夜间代理任务,却运行了四个多小时,产生了 800 美元的账单,原因是一个递归摘要错误对每个文档发起了五个嵌套调用,而非一次。当时有一个 500 美元的会话预算,但它丝毫没有起作用,因为失控的循环在该上限之下运行了数小时才突破。等到总限额触发时,钱已经花光了。
总限额就像是一个火灾发生后房子烧毁了才响的烟雾探测器。你真正需要的是一个速率限制:一个滑动窗口,它在每分钟花费偏离预期时触发,而不是在累计花费达到某个遥远的数字时才触发。如果一个任务本应每文档发起两次调用,却在六十秒内突然发起两百次,那么一个每分钟的令牌窗口能在几分钟内捕捉到它;而总预算则可能需要数小时,甚至永远都捕捉不到。
围绕任务运行设置嵌套窗口——一个每分钟上限、一个每小时上限和一个每日上限——每个都固定在你预期速率的几倍之上,而非某个绝对的上限。例如,每分钟 10,000 个令牌,每小时 200,000 个,每天 2,000,000 个,根据你自己的估算进行调整,这将在每个时间尺度上为你提供一个跳闸线。将每分钟窗口设置为预期吞吐量的约 5 倍,它将允许正常工作通过,同时几乎立即终止失控的项目。当窗口触发时,任务应返回一个结构化的违规信息——哪个窗口、已用令牌与限制、距离重置的秒数——以便调用者可以暂停、在重置后重试或中止。这个紧急停止开关是 20 美元超支和 800 美元超支之间的区别,在语料库规模下,则是 200 美元超支和 50,000 美元超支之间的区别。
分阶段执行任务:先进行金丝雀测试,然后推断,最后提交
最后一项原则是,最值得从数据库迁移中借鉴的:永远不要一开始就运行整个过程。重新处理是高度并行且易于采样的,因此没有理由在整个语料库处理完毕后才发现真实成本。
运行一次金丝雀测试——可以是语料库的 1%,或者抽取几千个项目来匹配真实分布,而不是仅方便地选取开头部分。衡量实际的输入和输出令牌、实际的缓存命中率以及实际的每个项目成本。然后推断到整个语料库,并与你预先计算的估算进行比较。如果样本超出你的纸面估算 3 倍,那么你只是在一个 300 美元的实验中发现了这个问题,而不是在一个 90,000 美元的生产运行中,你可以在问题变得严重之前修复提示、更换模型层级或收紧检索。
使完整运行可恢复且幂等,这样金丝雀测试和生产运行使用相同的代码路径,并且触发的紧急停止开关或瞬时故障意味着“从检查点恢复”,而不是“从头开始整个语料库”——从零开始重新启动一个半途而废的回填任务本身就是让账单翻倍的一种方式。分阶段推出:金丝雀测试,然后 10%,然后剩余部分,每个阶段的成本都需确认下一阶段的预测。并将长尾任务安排在夜间通过批处理端点执行,那里有折扣,而且凌晨 2 点发现的错误成本是中午同步执行时发现的相同错误成本的一半。
重新处理是你应纳入预算的例行任务
一个令人不安的事实是,回填任务只会越来越频繁。现在模型以一定的节奏被弃用和替换,嵌入空间在你不知不觉中发生变化,并且每一次管道改进都会产生压力,要求你对所有已存储的数据进行追溯性修复。全语料库的重新处理并非需要临时处理的罕见事件;它是一个值得拥有常备运行手册的周期性操作。
这本运行手册应该简短。在运行之前写下乘法计算(估算)。应用批处理和缓存后重新计算。用限速窗口的紧急停止开关来包裹任务,而不仅仅是总限额。在提交整个语料库之前,对代表性样本进行金丝雀测试并推断。使其可恢复,这样停止的代价就很小。这些步骤都不难,其中任何一个单独都能捕捉到那笔五位数的意外支出——但意外之所以持续发生,是因为你的在线成本直觉告诉你,回填是一个例行的运维任务,而例行运维任务不会得到估算、上限或分阶段计划。给这个任务所有这三者(估算、上限和分阶段计划),下一次重新索引就会变成你预测的支出项,而不是一笔你必须解释的账单。
