合并再调用:无需降低用户体验即可削减成本的 LLM 请求批处理模式
大多数团队都是以同样的方式发现请求合并的:收到一张出乎意料的大额账单。他们上线了基于 LLM 的功能,使用量增长,然后账单仪表板显示他们每天为五万个请求付费,而仔细观察后发现其中大约三万个请求在问同一件事,只是措辞略有不同。每一个"总结这份文档"的改写都单独命中了模型。每一个近乎重复的请求都触发了完整的推理周期。成本随流量规模线性增长,而不是随用户实际想要的语义多样性增长。
请求合并正是解决这一问题的模式。它不是单一技术,而是一种分层架构:用于防止并发重复的飞行中去重、用于重复相同提示的精确缓存,以及用于捕捉中间改写变体的语义批处理。顺序很重要,阈值很重要,理解该模式何处会失效——尤其是围绕流式传输——是可用实现与那种在暂存服务器上节省了钱但在生产中引发隐蔽 bug 的实现之间的差别所在。
三个层次,以及为什么你需要全部三个
将请求合并视为单一问题,是大多数实现走向失败的原因。实际上存在三种不同的失败模式,每种都需要不同的修复方案。
第一层:飞行中去重。 这是最容易忽略、一旦引发事故最令人尴尬的问题。当多个并发用户发送相同的提示时——比如产品发布导致"运费是多少?"查询激增——每个请求都会竞争缓存,发现什么都没有,然后各自独立地触发 LLM 调用。结果是五十个相同的推理任务同时运行,每个都付全价,每个都返回相同的结果。在此层进行合并意味着让第一个请求"拥有"待处理的工作,而后续的相同请求则等待,然后接收相同的结果。没有冗余的 API 调用。该模式使用延迟/Promise 结构:第一个调用者注册一个待处理的计算,后续调用者订阅同一个 future,无论该突发窗口内有多少请求到达,LLM 只被调用一次。
第二层:精确匹配缓存。 去重之后,下一层很直接:用 SHA-256 对完整提示进行哈希,存储结果,在进行任何外部调用之前查找它。这花费亚毫秒时间,且不存在误报风险。在生产环境中,精确缓存通常处理 15–30% 的流量——返回同一文档的用户、逐字重复的系统提示,或反复查询相同分类提示的内部工具。实现很简单;错误在于跳过它直接跳到语义搜索。
第三层:语义缓存。 这是真正节省成本的地方,也是复杂性所在。生产流量研究持续发现,30–60% 的传入 LLM 请求是近乎重复的——改写的查询,如果由模型处理会返回相同的响应。语义缓 存将每个传入提示转换为向量嵌入,计算与之前缓存嵌入的余弦相似度,并在相似度超过阈值时返回缓存响应。对于每月 5,000 美元的推理支出,30% 的缓存命中率可转化为每月约 1,500 美元的节省,而基础设施成本保持在该节省额的 5% 以下。
相似度阈值是业务决策,而非技术决策
余弦相似度阈值决定了什么被视为"足够接近"以返回缓存响应。它是整个系统中最关键的配置,团队通常会犯这样的错误:将其视为需要调整的技术参数,而非需要明确做出的产品决策。
操作范围大约在 0.85 到 0.98 之间:
- 在 0.85 时,缓存是激进的。主题相关但语义不同的提示也会匹配。这适用于 FAQ 系统和支持机器人,其中"足够接近"的答案是可以接受的,但在任何精确措辞至关重要的场景中都会导致静默失败。
- 在 0.92 时,你能捕捉到明显的改写——"如何取消我的订阅"和"取消账户的流程是什么"——同时拒绝共享词汇但意图不同的查询。这是大多数生产部署的最佳点。
- 在 0.98 时,你实际上是在进行精确匹配缓存,对标点符号变化有小小的容忍度。当你需要精确缓存的行为但又想处理细微规范化差异时,这很有用。
实际测试方法:将你的阈值提供给你的支持团队,并要求他们找出五个缓存答案会出错的示例对。如果他们在你当前阈值下很容易找到, 就降低它。如果在更激进的阈值下他们一个都找不到,你可能让钱白白流失了。
还有两个额外复杂性:阈值应根据上下文长度和对话状态而变化。短提示对阈值配置错误更敏感,因为单个词的变化对语义的影响比例更大。多轮对话通常应在几轮后完全跳过语义缓存,因为累积的上下文使得相似度得分成为答案等价性的不可靠指标。
时间窗口权衡
请求合并和语义缓存作用于现有流量模式——它们优化已经到达的流量。互补技术是动态批处理:在短窗口(通常 20–100ms)内将新请求保留在队列中,并将积累的内容一起作为单个推理调用处理。
经济效益很有吸引力。将 32 个请求批处理在一起可以将每令牌成本降低多达 85%,因为你将固定开销摊销到更多令牌上,GPU 利用率大幅提升。连续批处理——模型处理一批请求后立即在空出的槽位中插入新请求——已被证明可以维持 90% 以上的 GPU 利用率,而静态批处理约为 40%。
权衡是延迟。50ms 的批处理窗口意味着任何批次中的第一个请求最多等待 50ms 才能开始推理,即使它在服务器完全空闲时到达。对于大多数后台和异步工作流,这是不可见的。对于交互式聊天界面,用户会注意到首个令牌时间超过约 100ms 的延迟。实际阈值:如果你的功能显示打字指示器或渐进式加载,超过 30ms 的批处理窗口开始感觉迟缓。如果你在运行文档处理管道或异步分类任务,200–500ms 的窗口不会有什么问题。
实现类似公交时刻表:批次要么在窗口关闭时出发,要么在填满容量时出发,以先到者为准。半空的公交车仍然准时出发;满员的公交车不会等待下一站。
为什么这个模式与流式传输根本不兼容
流式传输是请求合并中最大的架构不兼容性,而且团队经常只在分别实现这两个功能之后才发现这一点。
流式传输的工作方式是让模型在生成令牌时发出令牌——客户端接收一系列小块,而不是等待完整响应。这创造了一种即使总响应时间相同也感觉很快的用户体验。模型几乎立即开始,用户可以看到进度。
请求合并需要相反的方法。要合并请求,你需要在模型开始生成之前知道传入请求是否与待处理或缓存的内容匹配。对于飞行中去重,第二个相同请求必须等待第一个完成——它无法以通用方式订阅部分生成的令牌流。对于语义缓存,你是在返回完整的缓存响应,而不是流。对于动态批处理,你将请求保留在队列中,这意味着模型在窗口关闭之前不会开始。
实际解决方案:根据功能的延迟要求选择其中之一。
- 高吞吐量、容忍延迟的工作流(文档处理、分类、异步摘要):积极使用合并和批处理。只有在完整响应可用后才向客户端流式传输,你可以将其实现为一次性刷新完整响应的伪流。
- 交互式、对延迟敏感的功能(聊天界面、实时自动补全):直接流式传输并接受更高成本。仅应用具有非常高相似度阈值的精确匹配缓存,因为等待批处理窗口的延迟成本超过了节 省。
尝试在合并层之上实现流式传输会产生一类微妙的 bug,即某些用户以流式速度接收缓存响应,其他用户以流式速度接收新鲜响应,但首个令牌时间分布是双峰的,难以向注意到不一致性的用户解释。
组织摩擦问题
请求合并的技术实现是可以解决的。更难的问题是组织层面的:要有效合并请求,你需要跨功能的共享请求池。
考虑一个典型产品:搜索功能、聊天功能、推荐引擎和文档摘要器,全部使用同一个 LLM 提供商。每个团队都独立构建了各自的集成。每个都有自己的缓存层、自己的速率限制预算,以及对"请求"的自己的理解。搜索团队看不到聊天团队的待处理请求。跨功能的批处理机会——用户在查看文档时搜索栏正在处理类似查询——对任一团队来说都是不可见的。
构建一个跨功能汇集请求的共享 LLM 网关,需要团队放弃对他们已经上线的系统的本地自主权。搜索团队不希望他们的延迟预算受到推荐引擎批处理窗口的影响。聊天团队不希望他们的缓存被文档摘要器的低质量输出污染。这些都是合理的异议,它们解释了为什么大多数组织最终得到的是每功能缓存,每个捕获 10–20% 的流量,而不是可以捕获 40–60% 的集中层。
有效的组织模式:将网关实现为基础设施,而不是由一个团队拥有的共享服务。在功能级别使其可选加入,并为每个功能提供阈值和窗口的配置。给每个团队提供对其自身缓存命中率和成本归因的可见性。在 API 调用层面进行成本回收,使团队有直接动力提高命中率。将网关视为平 台拥有的成本中心的组织通常会看到采用;将其视为产品团队应该集成的平台服务的组织通常会看到阻力。
根据你的流量形态进行校准
并非所有 LLM 流量都同样可以合并,对错误功能进行过度优化会浪费工程时间。
按功能类型的预期命中率:
- FAQ 和支持机器人:40–60%。用户以细微变化问同样的问题。阈值 0.88–0.92 的语义缓存可以捕获大部分。
- 分类和路由:50–70%。输入空间受限;大多数真实世界输入都类似于已见过的内容。仅精确缓存就能处理相当大的比例。
- RAG 检索增强:15–25%。每个查询检索到的上下文变化足够大,使得大多数响应不可重用,但检索步骤本身可以单独缓存。
- 开放式聊天:10–20%。对话中的用户积极尝试获得新颖响应;这里的合并主要捕获重试风暴和突发流量。
- 代码生成:5–15%。提示差异显著,小的提示差异会产生大的输出差异。
从这个列表顶部的功能开始。支持机器人在每月 3,000 美元推理支出中达到 45% 的缓存命中率,比将代码生成成本降低 10% 节省更多的绝对金额。投资回报率计算应驱动优先级排序。
实践中的样子
实现所有三层— —飞行中去重、精确缓存、语义批处理——的团队通常在第一季度内在高流量功能上看到 40–60% 的成本降低。前 20% 来得很快,源于精确缓存和去重;它风险低,不需要阈值校准,且没有延迟影响。接下来的 20–40% 需要正确设置相似度阈值,并接受批处理窗口的延迟预算含义。
要实现的架构模式:构建或采用一个位于应用代码和提供商 API 之间的 LLM 网关。在网关级别配置去重,使其对应用代码透明。将精确缓存添加为薄内存层。将语义缓存添加为针对向量存储(Redis with vector search、Qdrant 或 Pinecone 都适用)的二级查找。每周按功能衡量缓存命中率和成本;将其作为常规基础设施成本审查的一部分进行审查。
要避免的错误:将"我们有缓存"与"我们有合并"混为一谈。对重复提示返回结果的缓存对于在第一个请求完成之前同时到达的五十个并发请求毫无作用。飞行中去重是一种独立机制,需要显式实现,否则你会看到 LLM 成本恰好在你期望缓存最有帮助的流量模式中激增。
