阶段一 · 建立认知

Chapter 02 · Transformer 推理与一次请求到底发生了什么

上一章回答的是“我们在学什么系统”,这一章回答的是“这个系统里一次请求究竟怎么走”。只有当你把 token 化、prefill、decode、sampling 这些阶段放到同一条链路里理解,后面谈缓存、调度、吞吐和延迟才不会漂在空中。

时长 2-3 天
难度 L1 入门
先修
token 生命周期 prefill / decode 链路视角

章节导读

对很多初学者来说,模型回答问题像“黑箱里一下子算出来的”。这章就是拆黑箱:输入文本怎么变成 token,为什么 prefill 常常决定首 token 延迟,decode 为什么是一轮轮前进,sampling 又在链路中的什么位置。

这一章的核心问题

一次 LLM 请求并不是“模型一次完成所有计算”,而是包含不同成本特征的多个阶段。

你要形成的直觉

输入长度、输出长度、上下文历史、缓存复用,都会改变系统表现,而且它们影响的是不同阶段。

读完之后的收益

你会开始用“链路阶段”来分析问题,而不是笼统地说“模型响应慢”。

本章目标

  • 理解文本、token、上下文窗口、输出序列之间的关系。
  • 能够解释 prefill 与 decode 的职责差异,以及它们为何对性能影响不同。
  • 建立“首 token latency”和“总 latency”是不同观察面向的意识。
  • 知道 sampling 发生在什么位置,以及它为什么会影响服务接口设计。

前置知识

必须知道

  • Transformer 是多数 LLM 推理路径的基础结构。
  • 文本进入模型前需要被 tokenizer 切分成 token。
  • 模型输出不是一次吐出整段内容,而是逐步生成。

建议知道

  • 注意力机制大致与“看历史上下文”有关。
  • 上下文窗口是序列长度约束,不是简单的“字符上限”。
  • 服务端可能同时处理多个请求,而不是只盯着一个用户。

本章关注重点

  • 不要求你推导公式。
  • 重点是理解阶段划分和工程后果。
  • 把链路看懂,比把名词背会更重要。

正文主线

1. 输入文本首先变成什么

模型并不直接“读自然语言”。文本首先被 tokenizer 切成 token,再映射成 embedding 序列。这个步骤看似简单,但它已经决定了两个关键现实:第一,模型看到的是离散 token 序列而不是人类语句;第二,同样长度的自然语言文本,不同 tokenizer 下 token 数可能不同,从而直接影响后续成本。

这也是为什么工程上谈“上下文长度”时,大家更准确地用 token 数而不是字符数。因为系统真正付出的代价,是沿着 token 序列传播的。

工程直觉

以后当你看到一个 prompt 很长时,第一反应应该是“prefill 成本可能会显著上升”。

2. Prefill 阶段到底在做什么

prefill 是模型第一次处理已有上下文的阶段。你可以把它理解成:系统先把当前输入序列整体过一遍模型,为后续生成建立好所需的中间状态和缓存。因为这一步要处理整段已有上下文,所以输入越长,prefill 成本越高。

在用户视角里,这常常体现为“首 token 为什么迟迟不出来”。很多在线产品最敏感的体验指标正是在这里形成的。也就是说,首 token latency 往往更像是 prefill 的窗口,而不是整个生成过程的平均值。

为什么重要

如果你理解了 prefill,就会知道长系统提示词、超长上下文和 RAG 拼接内容为什么会先打到首包体验上。

3. Decode 为什么是一轮轮推进

和 prefill 不同,decode 阶段不是一次处理完整输入,而是每一轮生成一个新 token,再把这个 token 接回历史上下文,继续生成下一轮。也正因为如此,输出越长,总延迟越容易拉长;同时,因为历史状态可以被缓存复用,decode 的组织方式也成为后面所有调度优化的核心。

这里有一个很关键的意识转变:对于系统来说,一次回答不是“一个大任务”,而是“一个长生命周期请求里的许多小步”。这正是为什么引擎会关心队列、状态、缓存和批处理。

和 prefill 的区别

prefill 更像“先把已有上下文吃进去”,decode 更像“沿着已有上下文一步一步往前走”。

4. Sampling 在链路中扮演什么角色

很多人会把 sampling 看成纯产品层参数,好像只是为了让回答更有创造性。但从系统角度看,sampling 是 decode 每轮之后都要发生的步骤:模型给出下一个 token 的概率分布,系统再按策略挑选最终 token。temperature、top-k、top-p 这些参数,会进入服务接口、请求状态和结果控制逻辑。

这意味着 sampling 不是“链路末尾一个可有可无的插件”,而是生成路径的一部分。你后面看不同框架的 API 时,会发现它们都要把这些控制参数纳入请求协议。

5. 用链路视角重新理解三个常见问题

第一,为什么长输入会让首字慢?因为 prefill 先重。第二,为什么长输出会让总时间长?因为 decode 多轮累积。第三,为什么缓存会在后面变成核心话题?因为 decode 不是从零开始,而是要不断利用历史状态。

一旦你能把问题拆到这个层面,后面遇到任何性能讨论时,都会自然追问:瓶颈到底更像输入阶段问题、输出阶段问题,还是缓存复用问题。

6. 流式输出为什么会改变用户的主观体验

很多服务看起来“更快”,并不是总时延真的变短,而是系统在拿到第一个可展示 token 后就立刻往外推流。对用户来说,只要屏幕开始滚动,等待焦虑就会明显下降。因此流式输出把“首 token latency”放到了更靠前的位置。

这也解释了为什么同样的模型和同样的总时长,是否采用流式返回、服务端多早开始回传、客户端怎样渲染,都会让体验有明显差异。系统链路和产品体验在这里第一次紧密咬合。

工程判断

长输入变慢,优先怀疑 prefill

当输入从几百 token 拉到几千 token,最先被拉长的通常不是 decode,而是 prefill 建立初始状态的阶段。

  • 先看 prompt 长度和上下文拼接方式。
  • 再看首 token latency 是否同步上升。
  • 不要一上来就把问题归到 sampling。

长输出变慢,优先看 decode 轮数

用户要求更长答案时,系统往往是在更多轮 decode 上持续累加成本,而不是一次性算更久。

  • 看输出 token 数而不是字符数。
  • 看 tokens/s 是否下降,还是轮数单纯增加。
  • 这会影响你后面如何理解缓存和调度。

Sampling 是执行语义,不是纯展示参数

一旦生成控制参数改变,系统对请求状态的推进路径也可能改变,所以它必须跟请求一起进入引擎。

  • API 协议必须能表达这些参数。
  • 请求对象要长期携带它们。
  • 调试生成结果时不能把它们排除在外。

场景拆解

输入变长 首包变慢 定位

RAG 拼了很多检索片段后,第一句迟迟不出来

这是典型的长上下文问题。即使输出只要一句话,prefill 也得先把整段输入走完,所以用户会感觉“想了很久才开口”。

  • 先检查检索片段数量和裁剪策略。
  • 观察首 token latency 是否显著高于短上下文请求。
  • 如果是,优化入口通常在输入组织而不是输出控制。
输出变长 持续时间 定位

同一类问题,要求“解释详细一点”后总时长翻倍

这通常不是模型突然变笨,而是 decode 轮数显著增加。哪怕每轮速度差不多,总时长也会线性拉长。

  • 看生成 token 数的变化。
  • 比较首 token latency 与总 latency 的分离程度。
  • 这类问题后面往往要靠调度与缓存组织来优化。

图解实验室

把输入、prefill、decode 和流式输出放到一条图里,链路感会比纯文字更直接。

一次请求链路图

前半段决定首 token latency,后半段的生成循环更直接决定总 latency 和用户看到的持续输出速度。

文本输入 用户 prompt、系统提示词和历史上下文进入服务。
Tokenize 自然语言被切成 token,形成模型真正接收的序列。
Prefill 处理已有上下文,建立初始状态和缓存。
首 token 准备完成 这一步后,系统才开始有第一段可展示结果。
生成循环 只要还没满足停止条件,请求就会在 decode、sampling 和结果输出之间反复推进。
decode sampling 追加到历史上下文 流式输出
首 token latency 更像在度量 tokenize 之后到 prefill 完成这段路径有多重。
总 latency 更像在度量生成循环跑了多少轮,以及每轮推进有多快。

关键表格与结论

阶段 系统在做什么 更容易被什么影响 常见外在表现
Token 化 把文本转成 token 序列 输入形式、tokenizer 规则 相同文本长度但 token 数差异明显
Prefill 处理已有上下文并建立初始状态 输入长度、上下文窗口、系统提示词 首 token 延迟偏高
Decode 逐轮生成新 token 输出长度、缓存组织、调度策略 总延迟随输出增长
Sampling 从概率分布选择下一个 token 生成参数、服务接口、结果控制需求 回答风格和可控性变化

判断 1

输入变长,先怀疑 prefill 压力;输出变长,先怀疑 decode 累积成本。

判断 2

只谈“总延迟”通常太粗,要拆成首 token latency 和后续生成速度来看。

动手任务

任务 1:画出一次请求的时序图

至少标出 tokenizer、prefill、decode、sampling、返回结果五个阶段。最低完成标准是:你能说清楚每一步的输入和输出。

任务 2:比较短输入和长输入对首 token latency 的影响

不必做真实 benchmark,也可以先做逻辑分析。最低完成标准是:你能用 prefill 解释为什么长输入更容易拉高首包延迟。

任务 3:比较短输出和长输出对总延迟的影响

最低完成标准是:你能用 decode 的多轮过程解释为什么输出越长,总时间越明显。

任务 4:整理一份 sampling 参数小抄

写出 temperature、top-k、top-p 分别在控制什么。最低完成标准是:你能用自己的话解释它们不是“模型能力参数”,而是“生成控制参数”。

阶段产出

产出 1

一张完整的请求时序图。

产出 2

一份“prefill vs decode”对比笔记。

产出 3

一页 sampling 参数小抄。

自测问题

  1. 1. 为什么首 token latency 不能用总延迟替代?

    如果你能把答案落到 prefill,就说明理解到位。

  2. 2. 为什么长输出比短输出更容易拖长总时延?

    如果你的解释里没有“decode 多轮迭代”,说明还不够准确。

  3. 3. sampling 是链路的一部分,还是模型外部附加逻辑?

    尝试把它和服务接口参数联系起来回答。

  4. 4. 输入长度、输出长度、上下文历史各自更先影响链路的哪个位置?

    如果你能按阶段分开说清楚,说明链路意识已经建立。

推荐资料

源码入口

实践建议

  • Practice vLLM:Paged Attention 设计说明

    虽然内容更偏后续章节,但现在先扫一眼能帮助你建立“链路问题最终会变成系统设计”的意识。

常见误区

误区 1:以为模型输出是“一次算完”

这样会让你忽略 decode 的迭代特性,也看不懂后面为什么要有请求状态和缓存。

误区 2:认为长输入和长输出对系统的影响是一回事

它们通常分别先打在 prefill 和 decode 上,影响位置不同,优化思路也不同。

误区 3:把 sampling 只看成“调风格”的业务参数

它也会进入推理接口和请求控制逻辑,是链路的一部分。

下一章衔接

现在你已经知道一次请求如何分阶段流动。下一章会继续回答“为什么系统会慢”,把问题落到 GPU、CUDA、显存和带宽这些硬件与资源层面,开始建立真正的性能直觉。