Skip to content

约 5 分钟阅读

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 像答题,只能一笔一画往下写,每写一个字还要回头看看前面写了什么。

两个阶段的差别可以列成一张表:

维度PrefillDecode
每次处理的 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 上。

有两个直接的推论,后面会反复用到:

  1. 上传一整本手册再提问,主要拖慢的是第一个字,因为 Prefill 要处理的 token 变多了。
  2. 让模型写一篇长文,主要拖慢的是整体完成时间,因为 Decode 的步数变多了。

模型会沿着自己的错误继续写 ​

自回归还有一个容易忽视的后果:已经生成的内容会成为后续预测的依据。如果某一步选错了,比如把“直属主管”写成了“财务部门”,后面的内容很可能顺着这个错误继续展开,而且读起来很通顺。

这不是推理引擎能解决的问题。涉及日期、金额、流程这类关键信息时,应用需要把回答和原始资料核对,必要时重新生成。推理优化只负责“算得快、算得省”,不改变模型算出什么。

文字先变成 token,模型每次只预测下一个 token,并把它接回输入继续预测;一次生成因此分成并行读题的 Prefill 和串行答题的 Decode。下一篇看 Decode 每一步到底要用到哪些历史信息,以及为什么把算过的结果存起来能省下大量重复计算:KV Cache:用显存换计算。

别急,先让缓存热一下。