2026年10月7日

07 自回归:拼回输入,循环生成

LLM

这是生成过程的最后一环,也是把前面所有步骤串成一个完整回答的关键。前面说过,模型一次只能预测一个 token,那么一段几百字的回答是怎么来的?答案就是:把刚选出的 token 接到输入后面,再把整串送回模型,预测下一个,如此循环。 这种方式叫自回归生成(autoregressive generation)。

循环的具体过程

假设输入是"今天天气很":

  • 第 1 轮:输入"今天天气很",模型选出"热"
  • 第 2 轮:输入"今天天气很热",模型选出","
  • 第 3 轮:输入"今天天气很热,",模型选出"记得"
  • 第 4 轮:输入"今天天气很热,记得",模型选出"多喝水"
  • ……

每一轮,模型面对的都是一个"更长一点的新输入"。它并不知道哪些字是用户写的、哪些是自己刚生成的,对它来说都只是前文。这也是上次说"每个选择都不可撤回"的原因:选出的 token 立即变成了不可更改的上下文。

什么时候停下来

循环不会无限进行,有四种情况会让它停止:

1. 模型输出了结束标记。 这是最常见、最"自然"的结束方式。还记得对话模板里的 <|im_end|> 吗?它也在词表里,也会出现在概率分布中。当模型认为回答已经完整时,这个结束 token 的概率会变得很高,一旦被选中,生成就结束。模型"知道"什么时候该停,是在训练中学会的:训练数据里每个完整回答后面都跟着这个标记。

2. 遇到了 stop sequence。 这是由你在请求中指定的。每生成一段内容,推理程序就检查输出的末尾是否出现了你设定的字符串,比如 </answer>,出现了就立即停止。和结束标记不同,它是由外部程序检测的普通文本,而不是模型输出的特殊 token。另外,停止序列本身通常不会包含在返回结果中。

3. 达到了 max_tokens 上限。 你在请求中设置的输出长度上限。到达上限时,回答会被强制截断,可能停在半句话中间。做 agent 开发时要特别注意这一点:如果模型正在输出一段 JSON 时被截断,你就会拿到一个不完整、无法解析的结果。

4. context window 满了。 输入加输出的总长度达到模型上限,也无法继续生成。

在 API 的返回结果中,通常会有一个字段告诉你是哪种原因导致停止。以 Anthropic 的 API 为例,stop_reason 字段的值可能是 end_turn(自然结束)、stop_sequence(遇到停止序列)、max_tokens(达到上限),或者 tool_use(模型决定调用工具)。在 agent 代码中检查这个字段是一个好习惯,比如遇到 max_tokens 就说明结果可能不完整,需要特殊处理。

每轮都要重新计算整个输入吗?

按照字面理解,每生成一个 token 都要把越来越长的序列重新算一遍,似乎非常浪费。实际上并不需要,这里要用到上次讲的 KV Cache:已有 token 的 Key 和 Value 不会改变,所以存起来复用,每一轮只需要计算新加入的那一个 token。

这使得生成过程实际上分成了两个阶段(注意,这里的命名和之前讲的"预填充回答开头"恰好撞名了,但是不同的概念):

  • Prefill 阶段:一次性并行处理你的整个输入,建立 KV Cache。输入再长,也能利用 GPU 并行计算,速度很快。
  • Decode 阶段:一个一个地生成输出 token。每一步都依赖上一步的结果,无法并行,只能串行。

这个区别解释了几个你在使用中会注意到的现象:

  • 输出 token 通常比输入 token 贵。 输入可以并行批量处理,输出只能逐个生成,占用 GPU 的时间更长,所以 API 定价中输出的单价一般是输入的数倍。
  • 流式输出(streaming)。 既然 token 是一个一个产生的,就可以产生一个发送一个,于是你在界面上看到文字逐字出现,而不必等整段回答完成。
  • 首字延迟和生成速度是两个指标。 输入越长,prefill 越久,第一个字出来得越慢;之后的速度则取决于 decode 的效率。

和 agent 的联系

你会发现,agent 的运行模式其实就是把这个循环放大了一层:

  • 在 token 层面:模型生成一个 token → 拼回输入 → 继续生成,直到结束标记
  • 在 agent 层面:模型生成一次回复(可能包含工具调用)→ 程序执行工具,把结果拼回消息历史 → 再次调用模型,直到模型给出最终答案、不再调用工具

两层循环的逻辑一模一样:每一步的输出都成为下一步的输入,context 不断变长,直到满足停止条件。 所以设计 agent 时,也需要像 token 生成一样考虑停止条件:比如设置最大循环次数,防止 agent 陷入反复调用工具的死循环;以及在 context 接近上限时压缩历史,避免任务中途因为窗口满了而失败。

07 自回归:拼回输入,循环生成 · Kevin's Blog