02. 自回归生成:Prefill 与 Decode
上一篇认识了模型、应用、引擎和 GPU 的分工。这一篇进入模型内部:机器拿到“年假怎么申请?”这句话后,怎样算出回答。我们先看文字怎样变成数字,再看回答为什么只能一个片段一个片段地接出来,最后把一次生成切成 Prefill(预填充) 和 Decode(解码) 两个阶段。这两个阶段是整个系列的地基:后面讲到的几乎每一项优化,都可以先问一句“它在优化哪个阶段”。
文字先变成 token
模型只能处理数字。输入的文字会先被切成小片段,每个片段按一张固定的词表换成编号。这种片段叫 token,负责切分和编号的程序叫分词器(tokenizer)。
用一个简化的教学词表,“年假怎么申请?”可以切成:
text
文字: 年假 · 怎么 · 申请 · ?
编号: 21 83 46 9编号是为了演示随手选的。真实模型有自己的词表,Llama-3.1 的词表约有 12.8 万个 token;一个 token 可能是半个词、一个标点,也可能是几个汉字。同一句话换一个模型,切出的 token 数也可能不同。
所以推理服务里的长度、计费和限额,都按 token 计算,而不是按字数。粗略地说,中文里一个 token 常常对应一到两个汉字,但要以具体模型的分词结果为准。
每次只预测下一个 token
模型拿到输入以后,会给词表里每一个候选 token 打分,再换算成概率。例如读完“年假怎么申请?”之后,下一个 token 的概率可能是:
| 候选 | 概率(示例) |
|---|---|
| 先 | 60% |
| 请 | 30% |
| 可以 | 10% |
真实词表有十几万个候选,这里只列了三个。按某种规则从中选出一个(最简单的规则是选概率最高的,第 7 篇会讲温度、top-p 等其他选法),假设选中了“先”。
接下来是关键的一步:模型把“先”接到输入末尾,用“年假怎么申请?先”再预测下一个 token,可能是“提交”;再接上,再预测,得到“申请”……像玩接龙,每说出一个词,就把它当作新的线索去猜下一个。这种生成方式叫自回归(autoregressive)。GPT、Llama、Qwen 这些主流大模型都是这样工作的。
接出下一段文字固定候选概率的教学演示
问题:年假怎么申请?
已生成等待选择第一段
根据问题,下一段的候选为:
先 60%
请 30%
可以 10%
第 1 轮:当前最高的是“先”。
接龙什么时候停?有两种情况:模型生成了一个特殊的结束符(EOS,end of sequence),表示“我说完了”;或者生成长度达到了请求里设定的上限(常见参数名是 max_tokens)。
自回归带来一个绕不开的约束:第 N 个 token 必须等第 N−1 个 token 算完才能开始算,因为它的输入里包含第 N−1 个 token。生成是串行的。这个串行依赖,是后面几乎所有延迟问题的总根源。
读题与答题:两个阶段
虽然每一步都是“把输入送进模型算一遍”,但第一步和后面的步骤很不一样。
开始生成之前,问题、制度和聊天记录都已经给定了。假设它们一共 500 个 token,模型可以把这 500 个 token 一次性、并行地送进网络,算出每一层的中间结果,并预测出回答的第一个 token。这个阶段叫 Prefill(预填充)。
从第二个 token 开始,进入逐个生成的循环。每一步只有一个新 token 要处理(上一步刚生成的那个),算一遍完整的网络,得到下一个 token。要生成 200 个 token,就要串行地跑大约 200 次。这个阶段叫 Decode(解码)。
可以这样类比:Prefill 像读题,题目已经印在纸上,可以一眼扫过去;Decode 像答题,只能一笔一画往下写,每写一个字还要回头看看前面写了什么。
两个阶段的差别可以列成一张表:
| 维度 | Prefill | Decode |
|---|---|---|
| 每次处理的 token | 整段输入,几十到几万个 | 每步 1 个 |
| 并行度 | 高,输入里的 token 可以一起算 | 低,步与步之间必须串行 |
| 执行次数 | 每个请求 1 次 | 输出长度减 1 次 |
| 主要受什么限制 | GPU 的计算能力 | GPU 显存的读取速度 |
| 决定用户看到的哪段等待 | 第一个字出现前的等待 | 后面每个字之间的间隔 |
“主要受什么限制”这一行最反直觉:Decode 每步只算一个 token,计算量小得多,却往往是更“慢”、更浪费 GPU 的那个阶段。原因在于每算一个 token,都要把整个模型的参数从显存里读一遍。第 6 篇会用 GPU 的算力和带宽把这件事算清楚,现在先记住结论。
两段等待,分别来自哪里
回到聊天窗口,用户感受到的等待也分成两段:
- 发送后多久出现第一个字。这段时间里,系统要排队、读完整段输入(Prefill),再生成第一个 token。输入越长,Prefill 越久。
- 开始输出后,字出得快不快。这取决于每一步 Decode 花多少时间。
这两段等待有正式的名字:首 token 延迟(TTFT)和每 token 延迟(TPOT),第 5 篇会给出准确定义和计算方法。在我们的例子里,第 6 篇会算出:8B 模型在 H100 上读完 2000 个 token 的输入,理论下界约 32 毫秒;之后每生成一个 token,理论下界约 4.8 毫秒。实际耗时会高于下界,但这个量级已经说明:一段几百字的回答,大部分时间花在 Decode 上。
有两个直接的推论,后面会反复用到:
- 上传一整本手册再提问,主要拖慢的是第一个字,因为 Prefill 要处理的 token 变多了。
- 让模型写一篇长文,主要拖慢的是整体完成时间,因为 Decode 的步数变多了。
模型会沿着自己的错误继续写
自回归还有一个容易忽视的后果:已经生成的内容会成为后续预测的依据。如果某一步选错了,比如把“直属主管”写成了“财务部门”,后面的内容很可能顺着这个错误继续展开,而且读起来很通顺。
这不是推理引擎能解决的问题。涉及日期、金额、流程这类关键信息时,应用需要把回答和原始资料核对,必要时重新生成。推理优化只负责“算得快、算得省”,不改变模型算出什么。
文字先变成 token,模型每次只预测下一个 token,并把它接回输入继续预测;一次生成因此分成并行读题的 Prefill 和串行答题的 Decode。下一篇看 Decode 每一步到底要用到哪些历史信息,以及为什么把算过的结果存起来能省下大量重复计算:KV Cache:用显存换计算。
