跳到主要内容

何时跳过实时 LLM 推理:异步批处理流水线的生产实践

· 阅读需 10 分钟
Tian Pan
Software Engineer

某个团队此刻正看着他们的 LLM 月支出以 10 倍速度增长,而 p99 延迟徘徊在四秒左右。工程师们增加了更多重试。重试触发了速率限制。速率限制触发了回退。回退也是 LLM 调用。没有人停下来问:这个功能真的需要实时响应吗?

大多数 AI 产品团队都为"幸福路径"设计架构——用户发消息,模型响应,用户看到结果。同步调用模式是 API SDK 在第一个代码示例中演示的内容,因此它就这样上线了。但生产 LLM 工作负载中有相当大一部分与用户坐在键盘前等待毫无关系。它们是文档增强任务、内容分类流水线、向量嵌入生成、夜间摘要生成和后台质量评分。对于这些工作负载,实时推理是错误的工具——而坚持使用它所付出的代价是真实的金钱、级联故障,以及你要花费数月才能理清的运营复杂性。

默认选择让你多花 50%

当你同步调用 LLM API 时,你实际上在为两件事付费:处理你 token 的算力,以及保证空闲容量随时待命、能在毫秒内响应你的承诺。后者很贵。提供商维持着足够的余量来应对流量峰值,而这部分余量的成本被计入你每次实时调用的价格。

批处理 API 的存在恰恰是因为大多数请求不需要这种保证。Anthropic 和 OpenAI 都提供所有 token(输入和输出)50% 的统一折扣,以换取 24 小时的完成窗口。提供商在非高峰时段通过空闲容量路由你的批次。你获得相同的输出质量、相同的模型行为,以及一半的账单。Anthropic 目前的批处理定价为 Sonnet 每百万 token 1.50输入/1.50 输入 / 7.50 输出,而实时价格为 3.00/3.00 / 15.00;折扣与提示词缓存叠加,对缓存密集型工作负载可将成本降低至标准非缓存调用的 5%。

对于每月处理 100,000 份文档、每份 5,000 个 token 的团队,这意味着每月 150对比150 对比 300——仅 Haiku 就如此。以 Sonnet 的价格差距会更大。这个数学并不微妙。

盈亏平衡点究竟在哪里

团队在每个新 LLM 功能之前应该问的问题不是"我如何调用 API?"而是"这个功能延迟 15 分钟的代价是什么?"

如果答案是"用户看到转圈太久",你需要实时。如果答案是"数据库行 15 分钟后才被填充",你应该用批处理。如果答案是"说实话,延迟 2 小时也没问题,只要在早会前完成",你应该用批处理,并给予慷慨的时间窗口,让提供商使用他们最便宜的算力。

在实践中,尽管有 24 小时保证,大多数批次的实际完成时间都远低于一小时——提供商不会故意将你的任务拖到最后一刻,他们只是不保证更快。10 分钟实际完成时间与"实时"调用的 4 秒返回之间的区别,比工程师们假设的要小得多,尤其是当结果是写入数据库列而不是显示在屏幕上时。

生产环境中重要的延迟容忍类别:

  • 5 秒以内:用户正在盯着看。实时,别无选择。
  • 5–60 秒:用户在等待但可以忍受。考虑异步轮询加流式显示进度。
  • 几分钟到一小时:用户提交了内容后离开了。异步是正确的。
  • 几小时或隔夜:后台流水线。批处理是必须的;实时处理在这里是浪费。

那些本不该同步的功能类别

对生产 LLM 部署的分析揭示了一个一致的规律:团队最频繁地过度设计实时基础设施的功能,恰恰是那些用户实际上并不需要实时的功能。

文档增强与提取:发票解析、合同分析、从非结构化文本中提取结构化数据。用户上传了一个文件,他们会过几分钟再回来查看。提取结果写入数据库。这是批处理工作。同步运行意味着在 LLM 处理期间保持 HTTP 连接打开,跨越队列中的所有文档,假设用户正在盯着加载指示器——而他们其实没有。

大规模内容审核:字节跳动每天通过批处理导向的基础设施上的多模态 LLM 流水线处理数十亿个视频。内容审核不需要在视频上线前完成,如果你的架构将视频排队,并在审核窗口后才向用户展示——这种模式完全消除了同步依赖,而不是在其周围进行工程设计。

向量嵌入生成:构建或刷新 RAG 系统的向量索引不是实时操作。你在处理语料库,而不是响应用户。同步嵌入 100,000 份文档意味着要么在自己的代码中手动批处理,要么超载 API 速率限制。批处理 API 通过一次调用和一个轮询循环处理这个问题。

邮件摘要和环境智能体:任何按计划运行而非响应用户操作的智能体——每 10 分钟的邮件分类、夜间报告生成、每周分析摘要——本质上都是异步的。没有用户在等待。同步 API 在这里没有增加任何价值,却贵了两倍。

模型评估和离线测试:对新提示词变体运行评估套件不应该与生产流量竞争速率限制容量。批量评估运行允许你发出数百个测试用例,并在结果准备好时消费它们,而不影响生产吞吐量余量。

过度依赖同步调用的故障模式

同步 LLM 架构的故障模式不会自我宣告,直到它们代价高昂。

重试风暴:朴素的超时重试模式在单服务单元测试中看起来无害。在生产环境中,跨五服务请求链每层三次重试意味着在降级期间每个原始用户请求有 3^5 = 243 次后端调用。LLM 提供商的速率限制是共享资源;一个客户流量峰值引发的重试风暴会降低同一层的其他所有人的服务。发现这种模式的团队通常是在故障后检查支出图表时发现的,而不是在设计时。

超时级联:LLM 调用很慢——复杂生成通常需要 10–30 秒。上游代理、负载均衡器和 API 网关的 HTTP 超时通常设置在 30–60 秒范围内,这是在系统中最延迟敏感的调用是数据库查询时配置的。一个合法需要 25 秒的 LLM 调用,对于没有考虑到它的基础设施来说,与挂起的调用无法区分。

速率限制爆炸:LLM 提供商同时执行每分钟请求数(RPM)和每分钟 token 数(TPM)限制。只监控 RPM 的团队在正常看起来的请求量下 TPM 限制切断时会感到惊讶——因为他们的智能体学会了产生冗长输出,或者新功能添加了工具调用使上下文大小膨胀。批处理 API 在不同的、通常更高的吞吐量限制下运行,提供商控制调度,所以你的任务不需要保持在实时速率上限内。

没有熔断器的失控成本:一个有据可查的案例涉及一个不受检查地运行了 11 天的智能体循环,将 API 支出从每周 127增长到每周127 增长到每周 47,000。重试忠实地完成了它们的工作——重试每次超时——但没有人设置支出熔断器或迭代预算。同步 API 使这种故障模式容易重现,因为每次调用都会阻塞直到完成或超时,创建人类可能注意到的自然检查点。异步架构需要你明确构建监控——但它们也使在作业提交步骤中添加预算检查变得容易。

实践中的队列支持批处理架构

LLM 工作负载的生产批处理流水线不需要奇异的基础设施。模式是:

  1. 提交:你的应用程序代码编译一批请求并通过批处理 API 提交。每个请求获得一个你分配的 custom_id——这是匹配结果的唯一可靠键,因为批处理结果以任意顺序到达。
  2. 轮询或 Webhook:要么每隔 30 秒轮询批处理状态端点,要么为完成通知配置 Webhook。对于脚本和笔记本,轮询更简单。对于异步窗口内延迟很重要的生产系统,带有指数退避重试的 Webhook 值得设置。
  3. 结果处理:当批次完成时,下载结果,按 custom_id 匹配,并写入数据库。

对于规模化编排,Temporal 和 Airflow 等框架直接与批处理 API 集成,将批处理 ID 跟踪、轮询状态和重试管理作为一等关切处理,而不是应用层管道。

幂等性是强制要求的。 批处理任务中途失败。重试部分批处理意味着某些请求会被提交两次。如果你的下游写入不是幂等的——插入记录、发送通知、收取付款——重试会产生重复的副作用。使用 custom_id 作为幂等键,并确保你的结果处理代码可以安全地重新运行。

部分失败处理:与同步调用(全有或全无)不同,批处理任务可以在某些请求出错的情况下完成。为失败项目构建死信队列,并区分可重试错误(速率限制、瞬时超时)和永久失败(无效请求格式、认证错误)。重新提交永久失败会消耗配额而无法恢复。

实用决策

在生产部署中成立的规则很简单:如果结果写入数据库而不是用户界面,使用批处理。 如果有人在主动等待响应,使用实时。

这涵盖了绝大多数模糊情况。文档增强写入数据库。向量嵌入写入向量存储。审核分数写入内容记录。评估结果落入基准表。这些都没有人在阻塞连接的另一端等待。

早期把握这一点的团队不会将实时作为默认值并划出例外。他们将实时视为它本来的样子——一个昂贵的特殊情况——在 UX 真正需要时才合理使用,在其他地方则避免。批处理 API 50% 的基准节省在大型工作负载中快速积累,而不需要围绕同步故障进行工程设计的运营简洁性积累得更快。


批处理 API 是大多数生产 LLM 工作负载的正确工具,而大多数团队不使用它的原因是 SDK 首先向他们展示了同步调用。50% 的折扣是显而易见的论点,但更持久的论点是异步架构通过移除每次 LLM 调用都必须在下一行代码运行之前完成的假设,消除了整类故障——重试风暴、超时级联、失控成本螺旋。

References:Let's stay in touch and Follow me for more thoughts and updates