02. 从输入到逐步生成
本篇导读
上一篇找到了应用、引擎和模型。本篇把镜头推进到一次回答的内部:先读完已知资料,再逐步续写。用“读题与写答复”的类比建立 prefill、decode 和 token 的直觉,再解释首字等待、吐字速度为何是两种问题。
先读题,再往下写
小林问“年假怎么申请?”,应用把制度片段也放入输入。模型先处理这些已经给定的内容,形成后续生成需要的状态,这一段叫 prefill,预填充。
之后它根据已有内容计算下一个片段的概率,选出一个,再把这个结果加入历史,继续计算下一个。这一段叫 decode,解码。像先读题再写答复:题目已经在纸上,而下一笔写什么,要根据此前已经写下的内容决定。
类比只描述已知输入和未知输出的依赖关系。Prefill 内部仍遵守因果限制,不意味着每个位置都可以看未来;模型也不承诺先在脑中想好一份完整答案再逐字抄出。
片段为什么叫 token
Token 是模型词表中的单位,可能是一段汉字、词片、标点或空白。下面只是方便观察的教学切分:
输入词块:
年假·怎么·申请·?→ 输出词块:先·提交·申请
真实切分由 tokenizer 决定,不同模型可能把同一句话切成不同数量的 token。服务还会先用 chat template 把系统规则、角色和历史整理成模型约定的序列,再分词;这些都算输入的一部分。
沿着一轮计算走一次
下面把四个输入词块简化成四个位置。每点一次“下一步”,观察已显示结果与已建立的历史状态分别怎样变化。
逐步观察一个请求教学 token,不是真实分词
- 1排队与分词
- 2Prefill
- 3Decode 1
- 4Decode 2
- 5停止与释放
输入年假怎么申请?
输出待生成待生成待生成
输入已转换为 4 个教学 token;尚未进行模型计算。
活跃 KV:0 个位置。刚采样出的 token,要在下一次前向中才建立自己的 KV。
1 / 5
Prefill 最后位置的输出,可以选出第一个回答 token。刚选出来的 token 还没经过下一次模型前向,因此自己的 KV 通常要到下一轮才建立。这里先把 KV 理解为“已经算过的位置留下的计算便笺”,第 4 篇再拆它的内容。
屏幕上两种不同的慢
首 token 时间 TTFT 是从客户端发出请求到第一个有效生成 token。它可能包含网络、准备资料、排队和 prefill。片段生成间隔对应回答开始后是否流畅;若能观测逐 token 事件,则可以统计 ITL。完整耗时 E2E 还受输出长度影响。
所以,首字很快却答得很慢,和一直不出首字,应该沿不同方向排查。把流式返回打开,只会让已生成内容更早显示,不会直接省掉生成完整回答所需的计算。
深入:首个网络包、首个 token、首个正文为什么不同?
首个 SSE 包可能只有角色字段;首个 reasoning token 也未必是用户看到的正文。一次 chunk 可以带多个 token,客户端测到的 chunk 间隔不能冒充精确 ITL。
输出超过一个 token 时,常见平均 TPOT 为 (E2E - TTFT) / (输出 token 数 - 1)。它会掩盖中途停顿,仍要查看分布;单 token 输出不适用。冷启动还包含权重读取、通信初始化、编译、图捕获与预热,应与热请求延迟分开。
下一块文字怎样决定
模型给出各候选的分数,采样器负责选择。Greedy 取当前最高分;temperature 改变分布尖锐程度;top-k、top-p 限定候选范围。可以把它理解为“选择下一笔的规则”,不会让模型自动少计算几层。
当生成 EOS、命中停止条件、达到输出上限,或用户取消时,请求结束。输出上限导致的截断不等于完整答复。用户关闭窗口后,也需要把取消传到引擎,才能避免继续做无用计算。
深入:执行时序与可复现边界
相同 seed 或 greedy 不保证跨引擎逐字一致。不同 kernel、归约顺序、batch 和精度可能改变临界候选分数。质量等价与字节级复现应分开验收,后者还需固定模型、模板、软件、硬件与调度。
已经显示半段答案后重试,也不能把新请求后半段直接拼到原答案上,新序列可能不同。应标明原结果未完成,再按业务选择重试或结束。
本篇回顾
- Prefill 处理已给定的输入;decode 依赖此前生成结果,逐步继续。
- Token 是模型单位,未必等于一个汉字;模板和资料也会占输入位置。
- 首次等待、持续生成和完整耗时分别对应不同瓶颈,流式传输只是更早交付片段。
既然每一步都要计算并保存便笺,下一篇就要打开机器看:显存里放了什么,为什么加几个人或一份长文档就不够用了? 进入GPU 与显存预算。
