主题
第 6 章 权重共享、损失、预训练映射与上下文裁剪
6.1 从编号到每个位置的预测
GPT.forward(idx, targets=None) 先读出批次和长度,要求 0,...,T-1。token embedding 为
这套位置编码是学出来的绝对位置表。生成时若只保留最近窗口,留下来的 token 会重新从位置 0 编号;原代码没有旋转位置编码,也没有保留无限增长的全局位置。不要把别的模型的位置机制套到这里。
有 targets 时,输出头计算全部位置的 logits,形状为 targets 时,只取 x[:,[-1],:],保留长度为 1 的时间维,返回 None。优化只省掉最后输出头对早期位置的计算;各层注意力仍处理整个输入窗口,因此它不是 KV cache。
6.2 交叉熵为什么要把前两维展平
令
对所有非忽略位置求平均得到最终 loss。展平 logits 为
ignore_index=-1 允许把某些目标标为不参与损失,但输入编号依然不能是 -1:嵌入查表和目标掩码是不同接口。若所有目标都被忽略,均值没有有效分母,不应期待一个正常的训练损失。若有不同数量的有效目标,等权平均微批次损失也不再自动等于对全部有效 token 的平均,第 8 章讨论这个条件。
源码使用 targets.view(-1),要求内存步幅兼容。我们用 y[:,:4] 截取原来长度 6 的二维张量时,真实触发了 RuntimeError;调用 .contiguous() 后通过。原 get_batch 用 stack 生成连续目标,因此标准训练不受影响。自己改 loader 或对目标裁切时,应检查连续性,或在修改版中明确使用 reshape。不能把这个接口失败误诊为 checkpoint 损坏。
6.3 共享不是拷贝,梯度会从两个角色汇合
self.transformer.wte.weight = self.lm_head.weight 令两处引用同一个 Parameter。前向时它一边提供输入向量,一边把最终特征映回词表;反向时这两条使用路径对同一个参数的梯度相加。若只复制数值而创建新 Parameter,两份权重下一步就可能分离,那不叫权重共享。
以共享矩阵元素
得到 -0.11059136064694995,误差
参数遍历默认去重,configure_optimizers 又从 named_parameters 建字典;本书检查优化器内 Parameter 身份没有重复。否则同一权重可能被更新两遍,训练异常却很难从 state dict 的重复名字看出来。
6.4 从 GPT-2 导入时,名字和形状都要对齐
from_pretrained 只接受四个型号,并仅允许覆盖 dropout。它固定词表 50257、长度 1024、bias=True,再按型号确定
源码从 Hugging Face GPT2LMHeadModel 取 state dict,排除注意力 mask buffer,核对键数量,再逐键核对形状。四类权重需要转置:attn.c_attn.weight、attn.c_proj.weight、mlp.c_fc.weight、mlp.c_proj.weight。原因是上游 GPT-2 使用的 Conv1D 包装与本地 Linear 存储方向不同。例如从
拷贝在 torch.no_grad() 下进行,因为导入是状态赋值,不是优化目标中的可求导操作。键数量相等只是第一关;键名、形状、词表语义和层结构都匹配才有意义。本书没有下载这些权重,这部分是固定源码静态核对,不声称验证了当前 Transformers 版本兼容性或输出一致性。
6.5 裁长度会换 Parameter,时机很重要
crop_block_size(new_T) 要求新长度不大于旧长度,更新 config,截取位置表前 new_T 行并包装成新的 Parameter;手工注意力路径的 mask 也截到相同大小。它不能扩展位置表,不能让一个只学过 1024 位置的模型无代价支持更长上下文。
裁剪应在创建优化器之前完成,原 train.py 正是如此。因为位置表 Parameter 被替换,先创建的优化器可能还持有旧对象。恢复训练另有一层风险:如果先裁小模型,再加载原 checkpoint 的优化器状态,位置表的一、二阶矩形状仍可能对应旧长度,后续更新会冲突。权重裁剪成功不等于优化器状态也已裁好;需要同步迁移状态或明确重建优化器。
补充练习。 无 targets 的前向输出只有最后一个位置,能否用它直接计算整个训练窗口的交叉熵?
答案。 不能。它只有
本章来源
- forward、crop_block_size、from_pretrained
- 恢复与裁剪的顺序
- 本书梯度、共享身份及连续性反例:
examples/verify_mechanics.py。