Chapter 02 · Transformer 推理与一次请求到底发生了什么
上一章回答的是“我们在学什么系统”,这一章回答的是“这个系统里一次请求究竟怎么走”。只有当你把 token 化、prefill、decode、sampling 这些阶段放到同一条链路里理解,后面谈缓存、调度、吞吐和延迟才不会漂在空中。
上一章回答的是“我们在学什么系统”,这一章回答的是“这个系统里一次请求究竟怎么走”。只有当你把 token 化、prefill、decode、sampling 这些阶段放到同一条链路里理解,后面谈缓存、调度、吞吐和延迟才不会漂在空中。
对很多初学者来说,模型回答问题像“黑箱里一下子算出来的”。这章就是拆黑箱:输入文本怎么变成 token,为什么 prefill 常常决定首 token 延迟,decode 为什么是一轮轮前进,sampling 又在链路中的什么位置。
一次 LLM 请求并不是“模型一次完成所有计算”,而是包含不同成本特征的多个阶段。
输入长度、输出长度、上下文历史、缓存复用,都会改变系统表现,而且它们影响的是不同阶段。
你会开始用“链路阶段”来分析问题,而不是笼统地说“模型响应慢”。
模型并不直接“读自然语言”。文本首先被 tokenizer 切成 token,再映射成 embedding 序列。这个步骤看似简单,但它已经决定了两个关键现实:第一,模型看到的是离散 token 序列而不是人类语句;第二,同样长度的自然语言文本,不同 tokenizer 下 token 数可能不同,从而直接影响后续成本。
这也是为什么工程上谈“上下文长度”时,大家更准确地用 token 数而不是字符数。因为系统真正付出的代价,是沿着 token 序列传播的。
以后当你看到一个 prompt 很长时,第一反应应该是“prefill 成本可能会显著上升”。
prefill 是模型第一次处理已有上下文的阶段。你可以把它理解成:系统先把当前输入序列整体过一遍模型,为后续生成建立好所需的中间状态和缓存。因为这一步要处理整段已有上下文,所以输入越长,prefill 成本越高。
在用户视角里,这常常体现为“首 token 为什么迟迟不出来”。很多在线产品最敏感的体验指标正是在这里形成的。也就是说,首 token latency 往往更像是 prefill 的窗口,而不是整个生成过程的平均值。
如果你理解了 prefill,就会知道长系统提示词、超长上下文和 RAG 拼接内容为什么会先打到首包体验上。
和 prefill 不同,decode 阶段不是一次处理完整输入,而是每一轮生成一个新 token,再把这个 token 接回历史上下文,继续生成下一轮。也正因为如此,输出越长,总延迟越容易拉长;同时,因为历史状态可以被缓存复用,decode 的组织方式也成为后面所有调度优化的核心。
这里有一个很关键的意识转变:对于系统来说,一次回答不是“一个大任务”,而是“一个长生命周期请求里的许多小步”。这正是为什么引擎会关心队列、状态、缓存和批处理。
prefill 更像“先把已有上下文吃进去”,decode 更像“沿着已有上下文一步一步往前走”。
很多人会把 sampling 看成纯产品层参数,好像只是为了让回答更有创造性。但从系统角度看,sampling 是 decode 每轮之后都要发生的步骤:模型给出下一个 token 的概率分布,系统再按策略挑选最终 token。temperature、top-k、top-p 这些参数,会进入服务接口、请求状态和结果控制逻辑。
这意味着 sampling 不是“链路末尾一个可有可无的插件”,而是生成路径的一部分。你后面看不同框架的 API 时,会发现它们都要把这些控制参数纳入请求协议。
第一,为什么长输入会让首字慢?因为 prefill 先重。第二,为什么长输出会让总时间长?因为 decode 多轮累积。第三,为什么缓存会在后面变成核心话题?因为 decode 不是从零开始,而是要不断利用历史状态。
一旦你能把问题拆到这个层面,后面遇到任何性能讨论时,都会自然追问:瓶颈到底更像输入阶段问题、输出阶段问题,还是缓存复用问题。
很多服务看起来“更快”,并不是总时延真的变短,而是系统在拿到第一个可展示 token 后就立刻往外推流。对用户来说,只要屏幕开始滚动,等待焦虑就会明显下降。因此流式输出把“首 token latency”放到了更靠前的位置。
这也解释了为什么同样的模型和同样的总时长,是否采用流式返回、服务端多早开始回传、客户端怎样渲染,都会让体验有明显差异。系统链路和产品体验在这里第一次紧密咬合。
当输入从几百 token 拉到几千 token,最先被拉长的通常不是 decode,而是 prefill 建立初始状态的阶段。
用户要求更长答案时,系统往往是在更多轮 decode 上持续累加成本,而不是一次性算更久。
一旦生成控制参数改变,系统对请求状态的推进路径也可能改变,所以它必须跟请求一起进入引擎。
这是典型的长上下文问题。即使输出只要一句话,prefill 也得先把整段输入走完,所以用户会感觉“想了很久才开口”。
这通常不是模型突然变笨,而是 decode 轮数显著增加。哪怕每轮速度差不多,总时长也会线性拉长。
把输入、prefill、decode 和流式输出放到一条图里,链路感会比纯文字更直接。
前半段决定首 token latency,后半段的生成循环更直接决定总 latency 和用户看到的持续输出速度。
| 阶段 | 系统在做什么 | 更容易被什么影响 | 常见外在表现 |
|---|---|---|---|
| Token 化 | 把文本转成 token 序列 | 输入形式、tokenizer 规则 | 相同文本长度但 token 数差异明显 |
| Prefill | 处理已有上下文并建立初始状态 | 输入长度、上下文窗口、系统提示词 | 首 token 延迟偏高 |
| Decode | 逐轮生成新 token | 输出长度、缓存组织、调度策略 | 总延迟随输出增长 |
| Sampling | 从概率分布选择下一个 token | 生成参数、服务接口、结果控制需求 | 回答风格和可控性变化 |
输入变长,先怀疑 prefill 压力;输出变长,先怀疑 decode 累积成本。
只谈“总延迟”通常太粗,要拆成首 token latency 和后续生成速度来看。
至少标出 tokenizer、prefill、decode、sampling、返回结果五个阶段。最低完成标准是:你能说清楚每一步的输入和输出。
不必做真实 benchmark,也可以先做逻辑分析。最低完成标准是:你能用 prefill 解释为什么长输入更容易拉高首包延迟。
最低完成标准是:你能用 decode 的多轮过程解释为什么输出越长,总时间越明显。
写出 temperature、top-k、top-p 分别在控制什么。最低完成标准是:你能用自己的话解释它们不是“模型能力参数”,而是“生成控制参数”。
一张完整的请求时序图。
一份“prefill vs decode”对比笔记。
一页 sampling 参数小抄。
如果你能把答案落到 prefill,就说明理解到位。
如果你的解释里没有“decode 多轮迭代”,说明还不够准确。
尝试把它和服务接口参数联系起来回答。
如果你能按阶段分开说清楚,说明链路意识已经建立。
适合理解推理时为什么要复用历史 KV 状态,以及缓存到底在帮你省什么。
适合把 sampling 相关参数放回真实推理接口和生成逻辑里看。
不是为了背公式,而是为了理解 Transformer 推理的基本结构背景。
读这篇时重点看它怎样把链路理解延伸到缓存与服务效率问题上。
适合建立“模型推理代码通常怎样组织生成逻辑”的第一印象。
虽然内容更偏后续章节,但现在先扫一眼能帮助你建立“链路问题最终会变成系统设计”的意识。
这样会让你忽略 decode 的迭代特性,也看不懂后面为什么要有请求状态和缓存。
它们通常分别先打在 prefill 和 decode 上,影响位置不同,优化思路也不同。
它也会进入推理接口和请求控制逻辑,是链路的一部分。
现在你已经知道一次请求如何分阶段流动。下一章会继续回答“为什么系统会慢”,把问题落到 GPU、CUDA、显存和带宽这些硬件与资源层面,开始建立真正的性能直觉。