降低 LLM 延迟是构建响应迅速的 AI 应用的工程师面临的最关键挑战之一。虽然大型语言模型(LLM)的能力持续增强,但它们逐 token 的生成方式会给最终用户带来令人沮丧的瓶颈,而漫长的等待时间会直接导致参与度下降和应用流失。因此,优化推理管线的速度是开发者的一项核心要求。本指南概述了如何配置提示词缓存、实现响应流式传输、构建边缘网络路由,以及使用无服务器配置来削减处理延迟。

[!TIP] 性能指标提示: 在测量 API 延迟时,请将首个 Token 时间(TTFT)与整体生成速度区分开来。较低的 TTFT 会让应用对用户产生即时的感觉,即便整个输出生成需要数秒,因为文本会立即开始渲染。

要点总结:

  • 提示词缓存: 复用静态前缀头以绕过解析状态,将 TTFT 降低 80%。
  • 响应流式传输: 通过服务器发送事件(SSE)推送 token,让用户看到即时的文本生成。
  • 边缘 Worker: 在靠近用户的区域边缘中心运行授权与请求路由。
  • 模型路由: 将简单的用户请求分流到轻量模型以优化速度。

LLM API 延迟的构成要素

要缩短响应时间,你必须首先了解哪些要素决定了整体 API 延迟。整体延迟是三个不同变量的累计总和。

首先,网络传输时间衡量一个请求从客户端到你的服务器,再到模型提供商的 API 所花费的时长。这使得传输距离成为一个主要瓶颈。

其次,首个 Token 时间(TTFT)表示模型从接收请求到生成第一个输出 token 之间的时长,这正是提示词缓存如此重要的原因。

最后,token 生成速度衡量硬件输出后续 token 的速率。硬件约束决定了生成速度,但开发者仍完全掌控传输时间和 TTFT,因此智能的路由与缓存能够大幅降低 LLM 延迟。

加速你的 LLM 集成

前置条件

在开始接入下面的优化之前,请确保你已准备好以下各项。它们都并不奇特,但漏掉其中任何一项往往会在后续引发令人困惑的故障。

  • 一个由你控制的中间件或边缘运行时。 示例使用 Cloudflare Workers,但任何能够代理请求的无服务器平台都可行。你需要一个位于浏览器与模型提供商之间的落脚点。
  • 支持流式传输和缓存的提供商的 API 凭证。 OpenAI 和 Anthropic 都支持。请将密钥存储为 secret(Wrangler secret 或环境变量),绝不要放在客户端代码中。
  • Node.js 18 或更高版本 —— 如果你想用 wrangler dev 在本地测试 Workers。全篇使用的全局 fetchReadableStream API 在该运行时和现代浏览器中均可用。
  • 一个基线测量值。 在改动任何内容之前,先记录你当前的首个 Token 时间和总响应时间,以便证明每一项优化确实带来了帮助。下面的基准测试部分会说明具体做法。
  • 对服务器发送事件(SSE)的熟悉。 流式响应以一系列 data: 行的形式到达,你将在客户端解析它们。

实现提示词缓存

对于围绕大型系统提示词构建的应用,提示词缓存是优化 TTFT 最有效的方式。当一个请求包含一段冗长的静态指令块(例如代理的系统提示词或 RAG 参考文档)时,模型提供商必须在每次执行时解析并编码这些 token。Anthropic 和 OpenAI 都支持提示词缓存,它将解析后的 token 状态保存在内存中。随后共享相同前缀的请求便会绕过解析阶段,将 TTFT 降低多达 80%。

缓存的存活时间因提供商而异。Anthropic 在大约五分钟无活动期间维持缓存,而 OpenAI 使用动态衰减模型。因此,安排定期的后台 fetch ping 可以让关键的系统指令在服务器内存中保持活跃。

两家提供商的机制略有不同,而把请求结构搭对正是决定缓存能否真正命中的关键。使用 Anthropic 时,你用 cache_control 显式标记一个缓存断点。断点之前的所有内容都会被存储,因此稳定的静态内容必须放在前面,而每次请求都会变化的易变内容必须放在最后:

 1import Anthropic from "@anthropic-ai/sdk";
 2
 3const anthropic = new Anthropic();
 4
 5const response = await anthropic.messages.create({
 6  model: "claude-opus-4-8",
 7  max_tokens: 1024,
 8  system: [
 9    {
10      type: "text",
11      text: SYSTEM_INSTRUCTIONS       // small, sent on every request
12    },
13    {
14      type: "text",
15      text: KNOWLEDGE_BASE,           // large, static reference block
16      cache_control: { type: "ephemeral" }
17    }
18  ],
19  messages: [
20    { role: "user", content: userQuestion }   // volatile — after the breakpoint
21  ]
22});
23
24// Confirm the cache is working
25console.log(response.usage.cache_read_input_tokens);

最常见的错误是把时间戳、请求 ID 或任何每次请求都变化的字符串放在被缓存块的前面。由于缓存是前缀匹配,断点之前任何位置只要有一个字节发生变化,就会使其后的所有内容失效,而缓存会悄无声息地永远不命中。可通过读取响应中的 usage.cache_read_input_tokens 来验证它是否工作:如果在相同请求间该值始终为零,说明有动态内容混入了前缀。还需注意,被缓存的前缀必须超过某个最小长度(视模型而定,大约在 1,024 到 4,096 个 token 之间),缓存才会启用。

OpenAI 采用更简单的方式:对于超过大约 1,024 个 token 的提示词,缓存是自动的,没有 cache_control 标志需要设置。不过同样的纪律仍然适用。请把静态指令块放在 messages 数组的最开头,并把变化的用户输入追加在末尾,让可复用的前缀在请求之间逐字节保持一致。

要了解提示词缓存的定价和参数结构,请查阅 Anthropic 提示词缓存指南


边缘计算与无服务器路由

在单个集中式服务器上处理 LLM 请求,会为全球用户引入巨大的网络跳数。将你的 API 中间件部署在无服务器边缘网络(如 Cloudflare Workers)上,能够极大地缩短这些路径。

边缘 Worker 接收客户端请求,对会话进行授权,并将其路由到最近的模型提供商数据中心。这种无服务器结构在 token 被计算出来的那一刻就将其传送到用户屏幕,因此界面感觉高度灵敏。下面的 JavaScript 中间件演示了如何直接从边缘运行时配置流式响应:

 1export default {
 2  async fetch(request, env) {
 3    const payload = await request.json();
 4
 5    // Call the streaming LLM endpoint
 6    const response = await fetch("https://api.openai.com/v1/chat/completions", {
 7      method: "POST",
 8      headers: {
 9        "Authorization": `Bearer ${env.OPENAI_API_KEY}`,
10        "Content-Type": "application/json"
11      },
12      body: JSON.stringify({
13        model: "gpt-4o-mini",
14        messages: payload.messages,
15        stream: true
16      })
17    });
18
19    // Forward the stream directly to the client browser
20    return new Response(response.body, {
21      headers: { "Content-Type": "text/event-stream" }
22    });
23  }
24};

这种无服务器结构在 token 被计算出来的瞬间就将其传送到用户屏幕。要了解如何构建边缘优化的后端,请阅读我们关于使用 Cloudflare Workers 构建无服务器 API 的指南。


在客户端解析流

从边缘转发流只完成了一半工作。浏览器仍然必须在这些块到达时读取它们并渲染每个 token,否则响应会在缓冲区中累积并一次性出现,从而失去意义。这一步是大多数教程略过的,也正是感知性能真正决胜负的地方。

响应体是一个由原始字节组成的 ReadableStream。SSE 帧以 data: 行的形式到达,但单个网络块可能包含多个帧,或者把一个帧拆分到两个块中,因此你必须缓冲不完整的行,而不是假设每个块都是一条完整的消息:

 1async function streamCompletion(messages, onToken) {
 2  const response = await fetch("/api/chat", {
 3    method: "POST",
 4    headers: { "Content-Type": "application/json" },
 5    body: JSON.stringify({ messages })
 6  });
 7
 8  const reader = response.body.getReader();
 9  const decoder = new TextDecoder();
10  let buffer = "";
11
12  while (true) {
13    const { value, done } = await reader.read();
14    if (done) break;
15
16    buffer += decoder.decode(value, { stream: true });
17    const lines = buffer.split("\n");
18    buffer = lines.pop();              // keep the trailing partial line
19
20    for (const line of lines) {
21      if (!line.startsWith("data: ")) continue;
22      const payload = line.slice(6).trim();
23      if (payload === "[DONE]") return;
24
25      try {
26        const json = JSON.parse(payload);
27        const token = json.choices?.[0]?.delta?.content;
28        if (token) onToken(token);
29      } catch {
30        // ignore keep-alive comments and malformed partial frames
31      }
32    }
33  }
34}

buffer.split("\n") 后面跟着 lines.pop() 是关键细节:它会保留任何不完整的行,直到下一个块将其补全。将 JSON.parse 包裹在 try/catch 中,可以在一个 keep-alive 注释或一个只收到一半的帧到达时保持循环存活。随后 onToken 回调会把每个片段追加到 DOM,因此文本会在模型产出它的那一刻出现。


测量与基准测试延迟

你无法优化你没有测量过的东西。在每次改动前后,记录首个 Token 时间和总生成时间,以便把改进归因于正确的原因。采样 TTFT 最快的方式是使用 curl,用 time_starttransfer 作为第一个字节到达客户端时刻的近似值:

1curl -w "TTFT: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
2  -X POST https://your-worker.example.com/chat \
3  -H "Content-Type: application/json" \
4  -d '{"messages":[{"role":"user","content":"Hello"}]}' \
5  -o /dev/null -s

对于应用级别的数字,请直接在客户端解析器中埋点。在请求发出时打一次时间戳,在第一个 token 到达时再打一次:

1const start = performance.now();
2let firstTokenAt = null;
3
4await streamCompletion(messages, (token) => {
5  if (firstTokenAt === null) firstTokenAt = performance.now();
6  render(token);
7});
8
9console.log(`TTFT: ${Math.round(firstTokenAt - start)}ms`);

每项测量运行多次并取中位数,而非单次采样,因为网络波动和冷启动会扭曲一次性读数。下表给出了一个运行良好的全球请求中时间通常花在何处的示例范围;请把它们当作用于比较的形态,而非固定数值,因为你自己的数字将取决于区域、模型和提示词大小。

阶段典型占比是否可控?
网络传输(客户端到边缘)10–60 ms是 —— 边缘路由可缩短它
边缘上的中间件处理1–15 ms是 —— 保持 Worker 精简
首个 Token 时间(冷提示词)400–1,200 ms部分 —— 缓存可大幅削减
首个 Token 时间(已缓存前缀)100–400 ms是 —— 通过提示词缓存
每 token 生成10–50 ms/token否 —— 由模型与硬件决定

值得盯着看的两行是已缓存与冷启动的 TTFT 数字。这一差距是大多数应用可获得的最大单项收益,这正是提示词缓存位居优化清单之首的原因。相比之下,生成速度由提供商固定,因此把较简单的请求路由到较小的模型是那里唯一的杠杆。


分步优化工作流

要优化你的软件应用速度,先从把静态系统指令与动态用户输入分离开始。这种划分让你能够干净利落地瞄准缓存入口点。

接下来,在你的 API 载荷中激活提示词缓存标志,以确保模型提供商把你的文本 token 保存在内存中。

始终使用标准 SSE 端点配置响应流式传输。通过编写轻量的前端解析器在块到达时处理它们,你可以改善用户感知的性能。然后建立模型回退路径:把基础的客户输入路由到较小的模型,将较大的推理模型保留给高级任务。最后,剖析网络跳数以确认无服务器 Worker 减少了传输延迟。有关数据库优化的细节,请查看我们关于 WordPress 与定制网页开发对比 的指南。


常见陷阱与故障排查

当团队首次推出流式传输和缓存时,有少数几种故障会一再出现。识别其症状可以省去数小时的猜测。

症状可能原因解决方法
cache_read_input_tokens 始终为零时间戳、UUID 或会话 ID 位于缓存断点之前,导致前缀每次请求都变化把所有动态内容移到静态块之后;对任何 JSON 进行确定性序列化
Token 一次性到达,而非逐步到达中间代理或 CDN 正在缓冲响应发送 Cache-Control: no-transformX-Accel-Buffering: no;确保设置了 text/event-stream 内容类型
流中途中断Worker 在上游响应体结束前就返回了,或触及了 max_tokens直接返回 response.body 而非等待完整文本;提高 max_tokens
尽管已缓存但首个 token 仍然很慢静态前缀低于提供商的最小可缓存长度整合指令使被缓存块超过约 1,024 token 的下限
客户端解析器在某些块上抛出异常一个帧被拆分到两个网络块中如上所示缓冲不完整的行,并把 JSON.parse 包裹在 try/catch

一个更隐蔽的陷阱是在边缘本身进行缓冲。如果你在 Worker 内部于返回之前执行 await response.text(),你就悄悄地把一个流式响应变回了阻塞式响应。请始终把流体直接透传。同样地,留意 Worker 的 CPU 限制:中间件中每次请求的繁重工作会直接叠加到 TTFT 上,因此要让授权和路由逻辑保持精简,并推迟任何昂贵的操作。


生产环境考量

让一个演示在浏览器中流式运行很简单;但要在真实流量下可靠运行则需要多几道防护。

在上游调用上设置一个合理的请求超时,使一个停滞的提供商连接无法无限期地占用一个 Worker,并将其与一个重试机制配对,当主提供商超时时回退到第二个提供商或较小的模型。由于提示词缓存对缓存写入收取少量溢价,对读取给予大幅折扣,只有当前缀被复用时它才划算;每隔几分钟的后台 ping 可以让一个热门系统提示词常驻,而无需在每次用户请求时都付费重写它。

要持续埋点,而不只是在上线时。为每次请求记录 TTFT 和每秒 token 数,并在中位数漂移时告警,因为提供商端的回退或提示词大小的变化会最先在那里显现。最后,尊重提供商的速率限制:一波并发流可能触及它们,因此要优雅地排队或卸载负载,而不是让请求悄然失败。这些措施能把一个快速的原型转变为一个在关键时刻仍保持快速的应用。


要点总结

  • 瞄准首个 Token 时间(TTFT)和传输时间以降低 LLM 延迟。
  • 利用模型 API 上的提示词缓存以绕过系统指令的解析开销。
  • 使用响应流式传输实时交付 token,改善感知速度。
  • 将 API 中间件部署在无服务器边缘运行时上,以缩短全球网络路径。
  • 将较简单的用户查询路由到轻量模型以优化执行速度。
  • 设置性能监控工具,在真实用户条件下持续分析并降低延迟。

常见问题(FAQ)

我如何在生产环境中降低 LLM 延迟? 要在生产环境中降低 LLM 延迟,你应当为静态指令实现提示词缓存、启用 token 流式传输,并部署边缘 Worker 以优化请求路由。通过将无服务器编排运行时部署到更靠近全球客户端的位置,开发者绕过了多个网络路由跳数,并实时交付首个响应 token。

什么是提示词缓存? 提示词缓存是一项 API 功能,它把解析后的文本状态存储在服务器内存中,使得使用相同前缀的后续请求运行得快得多。通过绕过针对大型指令数据集的系统解析周期,这一优化将首个 Token 时间(TTFT)降低多达百分之八十。

模型大小会影响延迟吗? 会,较小的模型具有快得多的 token 生成速度,使其非常适合以延迟为首要考量的简单任务。将直接的分类或抽取请求路由到专门的边缘模型,可确保快速的处理时间,同时把稠密模型保留给推理任务。

服务器发送事件(SSE)流式传输如何帮助降低感知延迟? SSE 流式传输在文本输出 token 被编译出来时,实时地把它们从模型主机推送到客户端屏幕。尽管这不会减少总执行时长,但它最小化了首个 Token 时间(TTFT),并给用户带来一个灵敏、活跃的应用界面。

我如何在边缘缓存动态的 LLM 响应? 你可以使用带有较短 TTL(生存时间)限制的 KV 数据库或 Redis 实例在边缘缓存动态响应。缓存动态响应对于重复的用户查询或常见的客服意图很有效,可完全避免对模型提供商的网络调用。