LLM 推理的 Prefill-Decode 分裂:为什么你的 Agent 永远在等最后一个 token

LLM 推理分为 Prefill(并行计算)和 Decode(串行生成)两个性质截然不同的阶段。对交互式 Agent 来说,这个分裂意味着:随着对话轮次增加,Prefill 时间不断增长,但 Decode 的延迟几乎不动,最终被吞掉的不是生成速度,而是首 token 延迟。理解这个分裂,才能针对性地设计 Agent 的交互策略。

绝大多数延迟分析都搞错了重点

你打开一个 Chatbot,看着 token 一个一个蹦出来,觉得"嗯,延迟来自生成速度"。于是你优化模型——换更快的 LLM、做量化、上 specualtive decoding。Token 生成变快了,但 Agent 的交互体验似乎没质变。

问题出在:你只盯着 Decode 阶段在优化,但 Agent 场景下,Prefill 才是真正的隐形杀手

Prefill vs Decode:两个完全不同的计算模式

Transformer 推理时的 forward pass 一次完成两层计算:

Prefill(预填充)—— 计算密集型

输入 prompt 的所有 token 一次性进入模型,每个 token 做一次完整的并行 attention 计算。这是个稠密矩阵乘(GEMM)操作,GPU 利用率可以跑到 80-90%。

Prefill 时间 = f(prompt 长度)。线性增长。每增加 1K tokens,Prefill 增加几十毫秒。对于多轮 Agent 对话,prompt 从最初的几百 token 膨胀到几千甚至上万 token,Prefill 时间可以轻松从 20ms 涨到 300ms+。

Decode(解码)—— 内存密集型

生成第一个输出 token 之后,每次只生成一个新 token。每次 forward pass 只处理这一个新 token,做 attention 时需要读取之前所有 token 的 KV Cache。这是个带宽瓶颈操作(memory-bound),GPU 利用率只有 10-30%。

Decode 时间的决定因素:KV Cache 的大小(即之前所有 token 的总数),而不是新 token 的数量。每读一次 KV Cache 就是一次大带宽操作。

所以 Decode 的单步延迟主要由 prompt 总长度决定,和每次生成 1 个还是 10 个 token 关系不大。生成速度 = 内存带宽 / (KV Cache 大小 × 每 token 参数量)。

为什么 Agent 特别痛

单轮对话的用户不太在意 Prefill——用户输入一段话,等 100ms 看到第一个 token 开始输出,然后享受丝滑流式阅读。200ms 的 Prefill 在 3 秒的总交互时间里占比很小。

Agent 多轮对话是另一个故事。

以 ReAct 模式的 Agent 为例:

  1. 用户提问(prompt 500 tokens)
  2. Agent 思考 + 输出 tool call(生成 80 tokens,Prefill 50ms + Decode 300ms)
  3. 工具返回结果(新增 200 tokens)
  4. Agent 基于结果推理(prompt 780 tokens,Prefill 100ms + Decode 400ms)
  5. 输出最终回答(prompt 780 tokens,Prefill 100ms + Decode 800ms)

关键的 3→4 步:工具调用返回后,Agent 需要把工具结果拼回上下文重新推理。此时 prompt 已经包含了历史对话 + 之前的思考链 + 工具结果。Prefill 时间飙升,首 token 延迟从原始的 50ms 跳到 100ms+。

更糟的是,如果 Agent 需要多次调用工具(比如同时查询天气和航班),每轮推理都要重新 Prefill 整个上下文。你等的不是生成速度,是 prompt 被重新计算一遍。

实战对策:怎么绕过这个瓶颈

1. KV Cache 复用——最直接但最难做

Decode 阶段已经算好的 KV Cache 可以在连续推理中复用。但 Prefill 阶段每次都要重算。如果 Agent 两次推理之间的上下文变化很小(比如只追加了工具返回结果),可以复用之前的 KV Cache + 只对新 token 做 prefill。

问题是:大部分推理框架不支持增量 Prefill。vLLM 的 prefix caching 做了一部分——如果上下文的开头是固定的(system prompt),缓存这部分 KV Cache 可以跳过重算。但这要求你的 system prompt 不变。

2. 分叉推理(Speculative Prefill)——前沿方向

一次性做多轮推理的预计算。比如 Agent 第一轮还没输出完,就预测性地开始计算第二轮可能需要的 KV Cache。Google 的 Medusa 和后续工作探索了类似的思路——同时预测多个可能的 future tokens,预计算它们的隐藏状态。

对 Agent 场景来说,这意味着:当 Agent 正在 Decode 第一轮输出时,后台线程可以预计算第二轮(基于预测的工具返回结果)的 Prefill。如果猜对了,第二轮的首 token 延迟消失。

3. 系统级优化——Agent 框架的角度

最简单又最容易被忽视的优化:减少不必要的 Prefill

  • 不要每次把完整历史塞进 prompt。用滑动窗口 + 关键历史摘要替换完整对话。这直接把 Prefill 的时间从 O(n) 降到 O(1)。
  • 融合多步推理。一次 prompt 让模型输出多步 tool call,而不是一步一推理。Vercel AI SDK 的 maxSteps 参数就是干这个的——但它的实现是循环调用,每步仍然有独立的 Prefill 阶段。
  • Prompt 结构固定。保持 system prompt 不变 + 对话 history 变化部分尽量短。这样 vLLM 的 prefix caching 可以缓存大部分 KV Cache,Prefill 只计算增量部分。

一个思维模型

把 Prefill 想象成"读材料",Decode 想象成"写答案"。

人类也要读材料才能写答案。多轮 Agent 的问题是:每次写答案之前,它都得把前面所有材料再读一遍。工具调用返回的结果越长,下次读材料的时间就越长。材料 1000 字的时候读 50ms,材料 10000 字的时候读 500ms。

而写答案的速度(Decode)受限于记忆容量(KV Cache),材料越多,写一个字越慢。

这不是 LLM 的缺陷,而是 Transformer 架构的固有性质。理解这个分裂,你至少在架构层面知道该优化什么——而不是盲目追求更大的上下文窗口或更快的单 token 生成。

评论

此博客中的热门博文

我写了半年 prompt,最后发现最好的技巧就三个