本指南涵盖了可用于在各种 LLM 相关用例中改善延迟的核心原则。这些技术来自于我们与众多客户和开发人员在生产应用中的合作经验,因此无论您正在构建什么——从细粒度的工作流到端到端的聊天机器人——它们都适用。
尽管技术种类繁多,但我们将它们归纳为七大原则,旨在提供一种改善延迟的高阶分类方法。
最后,我们将通过一个示例来演示如何应用这些原则。
七大原则
更快地处理 Token
推理速度可能是提到延迟时首先想到的因素(但正如您稍后将看到的,它远非唯一因素)。这指的是 LLM 处理 Token 的实际速率,通常以 TPM(每分钟 Token 数)或 TPS(每秒 Token 数)来衡量。
影响推理速度的主要因素是模型大小——较小的模型通常运行速度更快(且成本更低),正确使用时甚至能表现出优于大模型的性能。为了在较小模型上保持高质量性能,您可以探索
- 使用更长、更详细的提示词 (prompt),
- 添加(更多)少样本示例 (few-shot examples),或者
- 微调 (fine-tuning) / 蒸馏。
您还可以使用我们的预测输出 (Predicted outputs)功能等推理优化手段。当您预先知道大部分输出内容(例如代码编辑任务)时,预测输出可以显著降低生成延迟。通过向模型提供预测,LLM 可以更专注于实际更改,而无需处理那些保持不变的内容。
影响推理速度的其他因素包括您可用的计算资源以及您采用的任何额外的推理优化手段。
大多数人无法直接影响这些因素,但如果您对此感兴趣,并且能够控制自己的基础设施,那么更快的硬件或以较低的饱和度运行引擎可能会带来适度的 TPM 提升。如果您深入底层,还有无数其他
推理优化
超出了本指南的讨论范围。
生成更少的 Token
在使用 LLM 时,生成 Token 几乎总是延迟最高的一步:作为一个通用启发式规则,减少 50% 的输出 Token 可能减少约 50% 的延迟。减少输出大小的方法取决于输出类型:
如果您正在生成自然语言,简单地要求模型更加简洁(例如“20字以内”或“言简意赅”)可能会有所帮助。您还可以使用少样本示例和/或微调来教导模型生成更短的回复。
如果您正在生成结构化输出,请尝试尽可能最小化输出语法:缩短函数名称、省略命名参数、合并参数等。
最后,虽然不常用,但您也可以使用 max_tokens 或 stop_tokens 来提前结束生成。
切记:节省一个输出 Token,就是节省一毫秒(甚至更多)的等待时间!
使用更少的输入 Token
虽然减少输入 Token 的数量确实会降低延迟,但这通常不是一个显著因素——缩减 50% 的提示词可能只能带来 1-5% 的延迟改善。除非您处理的是极大的上下文(文档、图像),否则建议将精力放在其他地方。
话虽如此,如果您确实在处理海量上下文(或者您决心榨干最后一点性能,且已经尝试了所有其他选项),您可以使用以下技术来减少输入 Token:
- 微调模型,以取代对冗长说明/示例的需求。
- 过滤上下文输入,如修剪 RAG 结果、清理 HTML 等。
- 最大化共享提示词前缀,通过将动态部分(如 RAG 结果、历史记录等)放在提示词末尾。这使得您的请求对 KV 缓存(大多数 LLM 提供商都在使用)更友好,从而在每个请求上处理更少的输入 Token。
查看我们的文档以了解更多关于 提示词缓存 (prompt caching) 的工作原理。
减少请求次数
每次发送请求都会产生一定的往返延迟,这些延迟累积起来可能会变得很可观。
如果您的 LLM 任务包含多个连续步骤,请考虑将它们放在一个提示词中,并通过单次响应获取所有结果,而不是每一步发送一次请求。您将避免额外的往返延迟,并可能降低解析多个响应的复杂性。
实现这一目标的一种方法是在合并后的提示词中以枚举列表的形式收集您的步骤,然后要求模型将结果返回在 JSON 的命名字段中。这样您就可以轻松解析并引用每个结果!
并行化
在使用 LLM 执行多个步骤时,并行化非常强大。
如果步骤不是严格连续的,您可以将其拆分为并行调用。晾干两件衬衫所花的时间与晾干一件是一样的。
如果步骤是严格连续的,您或许仍然能够利用推测执行 (speculative execution)。这对于分类步骤(例如审核)特别有效,其中一种结果的可能性明显高于其他结果。
- 同时启动步骤 1 和步骤 2(例如,输入审核与故事生成)
- 验证步骤 1 的结果
- 如果结果不是预期的,取消步骤 2(并在必要时重试)
如果您对步骤 1 的猜测是正确的,那么您基本上是在零额外延迟的情况下完成了执行!
减少用户等待时间
等待与观察进展之间有巨大的差别——确保您的用户体验到后者。以下是一些技巧:
- 流式传输 (Streaming):这是最有效的方法,因为它将等待时间缩短到一秒以内。(如果 ChatGPT 在完成整个响应之前不显示任何内容,体验会大打折扣。)
- 分块 (Chunking):如果您的输出在显示给用户之前需要进一步处理(审核、翻译),请考虑按块处理而不是一次性处理。通过流式传输到您的后端,然后将处理后的块发送到前端来实现这一点。
- 显示步骤:如果您执行多个步骤或使用工具,请将此情况呈现给用户。能够展示的真实进展越多越好。
- 加载状态:加载图标和进度条大有裨益。
请注意,虽然显示步骤和使用加载状态主要具有心理影响,但流式传输和分块在考虑应用+用户系统后,确实真正降低了总体延迟:用户可以更快地完成阅读响应。
不要默认依赖 LLM
LLM 功能极其强大且通用,因此有时被用于本应使用更快的传统方法处理的场景。识别出这些场景可以显著降低延迟。请考虑以下示例:
- 硬编码: 如果您的输出受到高度限制,您可能根本不需要 LLM 来生成它。操作确认、拒绝消息和标准输入请求都是硬编码的理想候选。(您甚至可以使用老方法,为每个场景准备几种变体。)
- 预计算: 如果您的输入受到限制(例如类别选择),您可以提前生成多个响应,并确保从不向用户两次展示相同的响应。
- 利用 UI: 汇总指标、报告或搜索结果有时使用传统的、定制化的 UI 组件传达,比使用 LLM 生成的文本效果更好。
- 传统优化技术: LLM 应用仍然是应用程序;二分查找、缓存、哈希映射和运行复杂度在 LLM 世界中仍然非常有用。
示例
现在让我们看一个示例应用,识别潜在的延迟优化点,并提出一些解决方案!
我们将分析一个受真实生产应用启发的假设性客户服务机器人的架构和提示词。架构与提示词部分将奠定基础,而分析与优化部分将引导您完成延迟优化过程。
您会注意到,这个示例并没有涵盖每一个原则,就像现实世界的用例并不需要应用所有技术一样。
架构与提示词
以下是一个假设的客户服务机器人的初始架构。这就是我们将要修改的对象。

从宏观层面来看,流程图描述了以下过程:
- 用户发送一条作为正在进行的对话的一部分的消息。
- 最后一条消息被转化为一个自包含查询(参见提示词中的示例)。
- 我们确定是否需要额外(检索到的)信息来回应查询。
- 执行检索,产生搜索结果。
- 助手推理用户的查询和搜索结果,并产生回复。
- 将回复发送给用户。
以下是图中每一部分使用的提示词。尽管它们仍是假设且简化的,但它们的书写结构和措辞与您在生产应用中看到的相同。
出现如“[此处用户输入]”占位符的地方代表动态部分,这些部分将在运行时被实际数据替换。
分析与优化
第 1 部分:审视检索提示词
查看架构,首先显而易见的是连续的 GPT-4 调用——这些暗示了潜在的低效,通常可以替换为单次调用或并行调用。

在这种情况下,由于检索检查需要依赖语境化后的查询,让我们将它们组合成单个提示词,以实现减少请求次数。

事实上,添加上下文和确定是否需要检索是非常直接且定义明确的任务,因此我们很可能可以使用一个更小的、经过微调的模型来代替。切换到 GPT-3.5 将使我们能够更快地处理 Token。

第 2 部分:分析助手提示词
现在让我们关注助手提示词。当它填充 JSON 字段时,似乎正在进行许多不同的步骤——这可能表明存在并行化的机会。

然而,假设我们进行了一些测试并发现将 JSON 中的推理步骤拆分会导致响应效果变差,因此我们需要探索不同的解决方案。
我们可以使用经过微调的 GPT-3.5 来代替 GPT-4 吗? 可能可以——但通常来说,助手生成的开放式回复最好交由 GPT-4 处理,以便更好地处理各种各样的场景。话虽如此,观察推理步骤本身,它们可能并不都需要 GPT-4 级别的推理能力。它们定义明确、范围有限的本质使其成为微调的理想候选。
1
2
3
4
5
6
7
8
9
10
11
{
"message_is_conversation_continuation": "True", // <-
"number_of_messages_in_conversation_so_far": "1", // <-
"user_sentiment": "Aggravated", // <-
"query_type": "Hardware Issue", // <-
"response_tone": "Validating and solution-oriented", // <-
"response_requirements": "Propose options for repair or replacement.", // <-
"user_requesting_to_talk_to_human": "False", // <-
"enough_information_in_context": "True", // <-
"response": "..." // X -- benefits from GPT-4
}这引出了一个权衡的可能性。我们要保持这是一个由 GPT-4 完全生成的单次请求,还是拆分为两个连续请求,并对除最终回复之外的所有步骤使用 GPT-3.5?我们遇到了原则冲突的情况:第一个选项让我们减少请求次数,但第二个选项可能让我们更快地处理 Token。
与许多优化权衡一样,答案取决于具体细节。例如:
response字段与其他字段的 Token 比例。- 通过更快速处理大多数字段而减少的平均延迟。
- 执行两次请求而不是一次请求所增加的平均延迟。
结论因情况而异,做出决定的最好方法是使用生产环境的数据进行测试。在这种情况下,假设测试表明将其拆分为两个提示词对更快地处理 Token 有利。

注意: 我们将 response 和 enough_information_in_context 放在第二个提示词中,以避免将检索到的上下文同时传递给两个新的提示词。
实际上,既然推理提示词不再依赖检索到的上下文,我们可以进行并行化,并将其与检索提示词同时触发。

第 3 部分:优化结构化输出
让我们再看看推理提示词。

仔细观察推理 JSON,您可能会发现字段名称本身非常长。
1
2
3
4
5
6
7
8
9
{
"message_is_conversation_continuation": "True", // <-
"number_of_messages_in_conversation_so_far": "1", // <-
"user_sentiment": "Aggravated", // <-
"query_type": "Hardware Issue", // <-
"response_tone": "Validating and solution-oriented", // <-
"response_requirements": "Propose options for repair or replacement.", // <-
"user_requesting_to_talk_to_human": "False", // <-
}通过缩短它们并将解释移至注释中,我们可以生成更少的 Token。
1
2
3
4
5
6
7
8
9
{
"cont": "True", // whether last message is a continuation
"n_msg": "1", // number of messages in the continued conversation
"tone_in": "Aggravated", // sentiment of user query
"type": "Hardware Issue", // type of the user query
"tone_out": "Validating and solution-oriented", // desired tone for response
"reqs": "Propose options for repair or replacement.", // response requirements
"human": "False", // whether user is expressing want to talk to human
}
这个小改动减少了 19 个输出 Token。虽然使用 GPT-3.5 可能只会带来几毫秒的改进,但对于 GPT-4,这可能节省高达一秒的时间。

您可以想象一下,这对于更大规模的模型输出会有多么显著的影响。
我们甚至可以走得更远,使用单个字符作为 JSON 字段名,或者将所有内容放在数组中,但这可能会开始损害响应质量。再次强调,了解这一点的最好方法是通过测试。
示例总结
让我们回顾一下我们为客户服务机器人示例实施的优化:

- 合并了查询语境化和检索检查步骤,以减少请求次数。
- 对于新的提示词,切换到了更小的、经过微调的 GPT-3.5,以更快地处理 Token。
- 将助手提示词拆分为二,切换到了更小的、经过微调的 GPT-3.5 用于推理步骤,同样是为了更快地处理 Token。
- 并行化了检索检查和推理步骤。
- 缩短了推理字段名称并将注释移入提示词,以生成更少的 Token。