正确:逐行停止
第 0 步 · 继续生成
| 请求 | 新生成 token |
|---|---|
| 行 1 | 尚未生成 |
| 行 2 | 尚未生成 |
| 行 3 | 尚未生成 |
模型输出候选 token 的 logits,可以据此得到概率分布。生成程序则负责选择下一个 token、判断是否继续,以及截取下一轮要用的上下文。降低温度不会更新权重,top-k 也不会让模型学到新知识。它们改变的是同一组 logits 被使用的方式。
本章对应上游 ch05 的温度、频数、top-k、完整 generate 函数和生成练习;配套采样小实验只使用自造 logits,停止协议另用固定输出模型检查;这些结果都不用于评价自然语言质量。上游生成脚本
若 logits 为
贪心每一步选当前概率最大的 token,不等于整段序列联合概率最大。第一步稍低的候选可能带来后面更高概率的序列。贪心生成也可能反复选中某些高概率模式。不过,增加随机性只能改变选择方式,并不能保证生成的事实更正确。
温度
两个候选的比值为
程序通常把 temperature=0 定义为贪心分支,不真的除以零。负温度会颠倒排序,通常不属于这个接口的有效输入,本书实现直接拒绝。NaN、全负无穷 logits 也应提前检查。不过,当前本书实现尚未加入这些检查;这里说明的是接口应补足的防护,不能当作已有保证。否则,问题可能直到 multinomial 抽样时才暴露。
本书历史结果中,对 logits [0,1,2,3],温度 0.8 的完整分布约为 [0.016894,0.058965,0.205807,0.718335]。这四个数由同一公式计算,和接近 1。如果再用 top-k 过滤候选,就需要在保留下来的候选中重新归一化。上面这些未过滤的概率,不能直接当作最终采样概率。
top-k 找分数最大的
上游实现取第 k 大值作为阈值,只删除严格小于阈值的项。若出现并列,保留项可以超过 k。例如 [3,2,2,0]、k=2,会留下前三项。本书 sample 在正温度采样分支中使用 topk 返回的具体索引再 scatter,在这个例子里严格留下两个位置。两种策略都可以使用,但哪些 token 有机会被抽到并不相同。遇到并列分数时,也不能假定不同后端返回的具体索引顺序始终一致。
正温度缩放不改变排序,所以无并列、有限值时,先 top-k 再除温度与先除温度再 top-k 的保留集合相同。实际实现仍需考虑数值精度、NaN 和额外过滤规则:它们可能破坏上述假设,导致保留集合不同。
使用自造 logits,直接计算温度与 top-k 后的概率,再让你指定的 u 落进累计概率区间。这里没有运行语言模型,也不调用随机数;相同输入和 u 一定得到相同结果。
当前使用预设参数;修改输入后可随时切回。
第 1 步:读取 logits。 logits 是未归一化分数,不是概率。最大项为 ID 3。
表格可左右滑动;键盘聚焦后用左右方向键查看。
| 候选 | logit | 稳定分数 (z−max z)/τ | 过滤前概率 | 过滤后概率 | 状态 |
|---|---|---|---|---|---|
| ID 0 | 0 | 待计算 | 待计算 | 待计算 | 待过滤 |
| ID 1 | 1 | 待计算 | 待计算 | 待计算 | 待过滤 |
| ID 2 | 2 | 待计算 | 待计算 | 待计算 | 待过滤 |
| ID 3 | 3 | 待计算 | 待计算 | 待计算 | 待过滤 |
本例 top-k 严格保留 k 项;分数相同则优先保留原 ID 较小的项。比如 [3,2,2,0]、k=2 留下 ID 0、1。按第 k 大分数设阈值的另一种实现会保留前三项;两种策略的支持集不同。
正温度不改变候选排序。τ=0 在本例定义为确定性贪心,并列最大时选最小 ID;它不同于 τ→0⁺ 时在并列最大项间均分概率。贪心分支忽略 u 和 top-k 的过滤结果,但仍要求输入的 k 合法。
温度不能为负,top-k 必须是候选数以内的正整数,u 不能等于 1。有限精度下极小的概率可能下溢为 0;页面使用减最大值的稳定算法,不会用极大指数直接求和。这里不模拟 top-p、EOS、上下文截断或多步生成。
不操作也能读懂:[0,1,2,3]、τ=0.8、top-k=2 时只剩 ID 2、3,概率约 0.222700 与 0.777300。u=0.5 会选中 ID 3;u=0.1 会选中 ID 2。调温度不更新模型权重,也不保证回答更正确。
参数含义参考:Transformers 生成配置。严格 k 的并列规则、τ=0 分支和手动 u 是本例明确约定。
torch.multinomial(probs,1) 从分布抽样。重复 1,000 次后,各项出现的次数会围绕期望值波动,不必恰好等于各自概率乘 1,000。固定种子只是固定某次伪随机序列,并不使频数成为理论概率。
本书保存的历史记录中,对 [0,1,2,3]、温度 0.8、top-k=2,seed 2718 的 1,000 次抽样只出现 ID 2 和 3,次数分别为 230 和 770。本轮已核对这份记录,未重新抽样。过滤后理论概率约为
上游 print_sampled_tokens 重置 seed、循环抽样并用 bincount 统计,温度图比较同一 logits 的几种温度。bincount(minlength=V) 能显示一次也没抽到的末尾候选;不指定 minlength 时,输出长度只到实际最大 ID。因此,没有打印出的末尾项可能只是本次没抽到,并不表示词表里没有它。
沿着一次生成迭代看,程序先截取上下文并运行模型,取最后位置的 logits,做过滤与温度处理,再选择新 ID、检查 EOS,最后决定是否拼接。上游生成脚本在检查 EOS 后才拼接,因此返回序列不包含新采到的 EOS。其他库可能包含 EOS,两种约定都可以使用,但调用方需要知道返回值采用哪一种。
固定版脚本在指定 EOS 时只支持 batch size 1,并显式拒绝其他批大小;notebook 中较早的简单条件没有完整的批处理停止状态。对多个请求,不能直接用 if tensor == eos_id,因为多元素布尔张量没有唯一真值。通用批量生成需要每行的 finished 标记、已经结束行的填充策略,以及何时整个 batch 停止。
本书配套 generate 为了让停止逻辑容易检查,明确只接受一条非空提示词,返回结果不含 EOS,限制最大新 token 数,保存并恢复模型原来的训练模式。测试脚本使用一个总偏向 ID 3 的固定模型,并以贪心方式检查:EOS=3 时原提示 [1,2] 原样返回;不设 EOS、最多生成 3 个时得到 [1,2,3,3,3]。这些是脚本规定的停止协议断言,不是文本生成展示;对应的历史输出日志目前缺失,本轮未重新执行。
GENERATION · 逐行停止
为每行写好候选 token,逐步检查 finished、EOS 和 PAD。右侧的错误做法遇到任意一行 EOS 就停,看看它截断了谁。
这是独立教学扩展。原书第 14.5 节的代码只支持一条提示,且返回值不含新 EOS;本实验使用另一种明确协议:保留首次 EOS,再给已结束行补 PAD。页面没有运行语言模型。
切换案例或修改输入会从第 0 步开始。
提示已经正确左填充,attention mask 已给定。这里只检查后续停止协议,不改变提示布局。
| 请求 | 提示 token | attention mask | 初始 finished |
|---|---|---|---|
| 行 1 | PADPADq | [0, 0, 1] | false |
| 行 2 | PADEOSq | [0, 1, 1] | false |
| 行 3 | PADrs | [0, 1, 1] | false |
还没有生成新 token。固定提示都非空;提示中的 EOS 和 PAD 不会触发本轮停止。
宽表可在框内横向滚动;用 Tab 聚焦后,可按左右方向键查看完整 token。
| 步骤 | 本步候选 | 步前 finished | 实际追加 | 步后 finished | 全批结束? |
|---|---|---|---|---|---|
| 第 0 步:还没有追加新 token | |||||
下面只展示返回缓冲区的新生成部分;固定提示保留在前面。每行列数相同,包含首次 EOS;它之后的 PAD 只负责补齐。下方有效回答取首次 EOS 前的生成片段。
第 0 步 · 继续生成
| 请求 | 新生成 token |
|---|---|
| 行 1 | 尚未生成 |
| 行 2 | 尚未生成 |
| 行 3 | 尚未生成 |
第 0 步 · 继续生成
| 请求 | 新生成 token |
|---|---|
| 行 1 | 尚未生成 |
| 行 2 | 尚未生成 |
| 行 3 | 尚未生成 |
等待第一个新生成的 EOS。
token 右下角是 ID。EOS 首次结束符;PAD 已结束后的补齐。
先试“逐行遇到 EOS”:候选按列是 [EOS, x, y]、[a, EOS, z]、[a, b, EOS],实际追加会变成 [EOS, x, y]、[PAD, EOS, z]、[PAD, PAD, EOS]。行 1 后面的 a 不会让它复活。
再试“先到长度上限”:两步后,行 3 的有效回答是 y z,结束原因为长度上限。没有生成的 EOS 不能补算进去;到上限也不需要额外追加一列 PAD。
是否补 PAD 由步前 finished 决定。未结束行选到 EOS 的 ID 时,当前位置是首次结束符;已结束行追加相同 ID 时,当前位置只是补齐。仅凭最后返回的数字,不能替代位置与状态记录。
如果手动把 PAD 写进未结束行的候选,PAD 与 EOS 不同 ID 时,它不会触发 EOS 停止,也仍属于生成片段;共用 ID 时,它就是 EOS,会触发停止。这与提示中已有的 EOS、PAD 不同:提示不会被当作本轮新生成的结束符。本实验显示 token 片段,不模拟 tokenizer 如何隐藏特殊 token。
本实验选用 Transformers 4.57.1 的逐行停止语义,限定为普通逐 token 生成、EOS 和最大新 token 数两个停止条件。每行 finished 表示后续不再生成有效 token,可以由 EOS 或长度上限触发。
核对日期:2026-10-14。网页是 JavaScript 教学模型;仓库另有真实 Transformers generate 的 CPU 对照测试,使用随机初始化的小模型和强制候选,不下载权重或语料,也不评估文本质量。测试是否通过,以对应提交的运行记录为准。
本书讨论的无缓存循环每轮取最后 C 个 token,位置从 0 开始。对应的缓存循环在处理完提示词后,只读新 token,并保留累计位置。超过上下文后,不能既截掉输入前缀,又继续使用没有同步裁剪或重算的旧缓存,否则,输入看似已经截短,模型却仍通过旧缓存访问被截掉的历史。
即使设置 max_new_tokens,还应考虑 EOS、位置容量和服务端响应预算。提示词为空时没有“最后位置 logits”,需要规定 BOS 或其他初始化策略。生成函数不应悄悄接受空输入直到模型深处才报错。
模型权重、tokenizer 版本和词表、提示词字节、模板、上下文裁剪、dtype、设备、温度、top-k、EOS 规则以及随机数消费顺序,都可能改变输出。使用同一个随机数生成器时,前面多做一次采样,就可能改变后续使用的随机数,进而改变输出序列。GPU/MPS 与 CPU 即使 logits 极接近,也可能在候选分界处选择不同 token,后续自回归差异会迅速扩大。
上游集中生成脚本在温度分支 softmax 前减去行最大值,改善数值稳定;这不等于保证所有后端抽到同一序列。第 18 章区分随机数差异与计算误差。核对两种实现时,可以先比较 logits 和分布,再比较确定性贪心结果,最后分析随机文本的差异。
运行集中脚本时还要留意命令行参数。它的 CLI 接受 --prompt 和 --device,内部直接 torch.device(args.device);虽然帮助文字举了 auto,固定实现没有解析 auto 的分支,实际应传 cpu、cuda 或 mps 等有效设备并验证可用性。模型尺寸同时依赖配置字典,以及从模型名称中提取的尺寸字符串。修改时需要让两者对应,不能只改下载名字。
上游温度习题计算低概率候选在高温时的概率并观察抽样;贪心等价题比较 generate_text_simple 与 generate(temperature=0,top_k=None);另有已训练与更大预训练模型的生成演示。前两项涉及的机制已在本章推导,权重加载则见第 15 章。本书没有运行大权重模型的质量比较。
补充题一:对 logits [1,1,0],令温度趋近零,会得到什么?答案:概率集中到前两个并列最大项,各约一半;程序的 argmax 分支则按具体索引规则只取一个,不能把二者完全混同。
补充题二:为何 top-k=1 时正温度通常不改变结果?答案:前提是过滤策略确实只保留一个项。此时支持集中只剩一个项,归一化后概率为 1;若采用前面提到的阈值策略,并列最大项可能一起留下,就不能直接套用这个结论。补充题三:如果采样到 EOS 后不把它拼接,返回长度是否包含结束符?答案:不包含,调用者若需要显式记录结束原因,应另返回停止状态。
运行 python examples/core_training/loss_sampling_checks.py qa/core_training/loss_sampling.json 检查并列支持集、EOS 与模式恢复;主检查脚本记录 1,000 次采样频数。