01|文本怎样成为概率:Tokenizer、模板、目标函数与梯度
本章目标:能够自己写出编码、标签移动、损失计算和梯度检查;能定位“相同一句话,在不同客户端得到不同行为”究竟发生在哪一层。先修:数组、矩阵乘法、导数。实验:
numerics.py、train_tiny_lm.py。
本章目录
沿着对象与状态理解本章
目标函数把“希望出现哪个后继Token”变成可优化数值;先固定标签位置,再区分概率、负对数与梯度。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
长期记忆不是永远正确的小纸条
新事实出现后,旧记忆怎样更新而不毁掉历史?
记忆要同时回答:这条信息说的是什么范围、何时有效、系统何时得知、依据在哪里。最新写入的记录不一定描述最新生效的事实。保留时间和来源,才能既回答当前问题,也准确解释过去。
输入与适用条件:独立配置案例;月份只用于展示时间顺序,不涉及真实产品当前参数。30秒与20秒属于不同有效期。
1 / 5 · 1月1日 生效
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
配置超时30秒
保留作用域与来源默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
跨会话复用有效知识,减少重复查找和重复错误。
怎样实现
保存范围、来源与两个时间;使用前核验;过期、冲突与撤销显式处理。
可能的代价与误解
把一次模型猜测写成通用规则,会在后续检索中放大错误。
如何判断是否做对
同时测试当前查询、历史查询、错误范围查询和来源已失效查询。
1. 先分开五种完全不同的对象
用户可见的字符串、客户端消息对象、服务端序列化模板、Token ID序列、模型中的浮点张量,不是同一个对象。客户端可能发送带有role和content的JSON;服务将角色、工具调用、图像等组织成模型训练时约定的表示,再进行相应编码。开放模型的聊天模板能够直接检查;闭源服务的全部最终模板通常不对客户端公开。不能把API请求日志称为“模型完整的原始输入”。[N01][N13]
例如同样是“读取config.py”,普通用户文本只是在请求一个动作;同一字符串放在工具返回字段中,语义上是外部数据。模板使模型学习角色差异,但角色不是CPU硬件权限位。真正是否允许读取文件,仍要由执行器检查。把角色机制误当成权限机制,是从底层认识错误一路传到工程风险的典型例子。
理解输入时应保存三个层次的可观测证据:应用提交的消息与工具版本、可见的Token用量、工具执行结果。不应伪造不可见的服务端提示或内部推理。调试本地开放模型时,才可以额外对模板渲染结果和精确Token序列逐项比较。
2. BPE到底在做什么
将UTF-8字节作为初始符号,可以从最细粒度建立可逆编码。训练BPE时统计相邻符号对,选择高频对合并为新符号,重复形成有顺序的合并规则。推理时不是重新统计用户输入决定词表,而是应用已经训练好的规则。实验中的小型实现按规则顺序扫描,相当于一个易读但低效的教学实现;生产实现还需要预切分、特殊Token、缓存和高效数据结构。
假设训练样本反复含有routing、route、router,常见片段可能被压为较少符号。但这不能保证任意新标识符也很省Token。中文字符可能跨多个字节;字节级可逆性保证重建原字节,不保证每个Token都对应完整可显示字符。输出流还可能在UTF-8字符中间截断,所以网络增量解码必须独立维护字节状态,见第07章。
本书bpe_train与bpe_encode的验收不是“看起来切得合理”,而是对中英混合、代码、空格、换行做decode(encode(text)) == text。缩短重复字符串是第二项性质,不能替代可逆性。编码改变意味着词表、模型输入嵌入、输出层和训练数据解释都可能改变,不是一个能随意热替换的字符串压缩器。
3. 自回归目标与训练标签移动
对Token序列x₀,…,xT,链式法则给出:
P(x₀,…,xT) = P(x₀) × ∏(t=1…T) P(xt | x₀,…,x(t−1))
训练输入:x₀, x₁, …, x(T−1)
训练标签:x₁, x₂, …, xT
L = −Σt mask(t) log Pθ(x(t+1) | x≤t) / Σt mask(t)
训练时一次处理很多位置,各位置通过因果掩码不能看到未来标签;生成时,下一位置的输入必须等前一步采样结果出现。这就是“训练可以按位置并行,标准自回归生成存在时间依赖”的原因。并非训练时模型已经知道所有答案而作弊,关键在于掩码阻止未来信息泄漏。[N07]
SFT常常只对助手回答位置计损失。这里的loss mask和attention mask必须区分:前者决定哪些预测误差参与优化,后者决定哪些位置可以被读取。一个用户Token可以不计损失,却仍作为助手回答的条件信息。若把两种mask混为一谈,就可能无意中把用户问题从注意力中遮住。
文档拼接也需要边界策略。两个独立样本直接拼起来,模型可能使用前一个样本预测下一个;这究竟可接受,取决于训练目标。做严格样本隔离时,应同步处理注意力分段、位置、标签与损失,不能只插入一个分隔字符串后便宣称完全隔离。
4. 从Logits到交叉熵,逐步求导
模型最后输出长度为词表大小V的向量z。稳定softmax先减最大值m,因为所有分子分母同时乘exp(−m)不改变比例:
pi = exp(zi−m) / Σj exp(zj−m)
目标类别为y:L = log Σj exp(zj−m) + m − zy
∂L/∂zi = pi − 1[i=y]
导数说明:目标Token的logit获得增大的方向,其余Token按当前概率被压低;这不是把整段答案作为数据库记录存进去。梯度穿过输出投影、前馈层、注意力和输入嵌入,更新大量分布式参数。某条事实是否能稳定复现,还受数据频次、干扰、优化和泛化影响。
numerics.cross_entropy返回损失和解析梯度。有限差分检查每个维度:(L(z+εei)−L(z−εei))/(2ε)。如果梯度误差很大,先排查标签移位、求和/平均口径、错误的广播和数值溢出,不要直接调学习率。测试还验证梯度和为零,因为给全部logits加同一常数不改变分布。这是由数学不变性导出的测试,比只比较某个固定浮点数组更有解释力。
5. Embedding不是外部检索向量
输入嵌入矩阵通常形状为[V,d],Token ID执行查表得到[B,T,d]。用于文档搜索的Embedding模型则需要把整段文本映射到检索表示,通常使用不同训练目标及池化方法。不能把任意语言模型某层向量直接平均,就断言获得高质量代码检索向量。
输出层可使用独立权重,也可与输入嵌入共享;共享减少参数,但具体架构应查实现。隐藏维度、词表大小以及是否共享,会影响显存估计。例如一个教学假设V=100000,d=4096的单矩阵有约4.096亿参数,仅BF16权重约0.819GB(十进制),还未计优化器状态。这是尺寸计算,不是任何商业模型配置。
6. 输入问题的工程排查顺序
当提示“完全相同”却行为变化,先比较模型标识、模板/系统配置、工具Schema、仓库指令、上下文历史和采样参数。再看缓存与服务是否使用了不同版本。只有这些条件足够一致,才谈模型随机性及数值实现差异。温度设零也不构成跨硬件、跨服务版本的逐位确定性承诺。
提示优化首先应消除目标歧义,明确输出对象与验证条件,而不是堆砌角色形容词。示例的价值是约束任务分布;示例若和真实任务不一致,也会把模型带偏。给出“好代码”的样例而不说明它解决的限制,会诱导风格模仿,不一定形成正确的工程判断。
同一“爱”出现两次,Embedding行的梯度怎样累加
输入与条件。词表中“爱”编号2,在一个批次的两个位置出现。设两处反传到其Embedding的梯度为[0.1,−0.2]、[0.3,0.4]。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1查同一行 | 两处都取表第2行 | 前向值共享参数,不是复制两份独立可训练参数。 |
| 2分别反传 | 各位置形成自己的梯度贡献 | 分别为给定两向量。 |
| 3参数聚合 | 对共享行求和 | 第2行梯度[0.4,0.2]。 |
| 4其他行 | 没有在本次查表出现 | 对纯输入Embedding路径没有此次贡献。 |
| 5更新 | 若学习率0.1,第2行旧值[0.8,0.6] | 新值[0.76,0.58]。 |
把过程连起来。共享参数从多个使用位置接收梯度之和;同一词多次出现不意味着模型存多行。
边界与反例。这里只计算输入查表路径;若与输出投影权重绑定,其他路径仍可能贡献梯度;padding_idx等规则依实现。
依据与适用范围:[EMBED]。示例输入和场景由本书明确设定。
7. 本章验收与答案
问题:助手回答的第一个Token预测依赖用户问题,但用户位置不计SFT损失,是否矛盾?答案:不矛盾。前向仍读取用户表示,反向从助手损失沿计算图传播;损失mask只取消特定预测位置的直接损失项。
问题:把代码路径中每个字符都切成一个Token会更精确吗?答案:可表达精度不等于任务效率。更细切分延长序列,增加注意力与生成成本,也改变训练分布;正确选择要在相同任务上评价,不能只看字符边界。
完成本章后,运行交叉熵有限差分、BPE可逆测试,再故意把训练标签移位取消。损失可能仍下降,但任务变成复制当前位置,不再是下一Token预测。必须能说明目标被换掉,不能把低损失当成成功。