主题
第 4 章 GPTConfig、参数规模与初始化
4.1 一个配置怎样变成一组矩阵
GPTConfig 是 dataclass,保存长度、词表、层数、头数、宽度、dropout 和 bias。默认值描述一个 GPT-2 小模型量级的结构:
GPT.__init__ 先构造 ModuleDict:wte 是 token embedding,wpe 是绝对位置 embedding,drop 是输入 dropout,h 是长度为 ModuleList,ln_f 是最后的 LayerNorm。再创建无偏置的输出 lm_head,并让输入嵌入与输出矩阵共享一个 Parameter。用这些容器不是为了多一层包装;它们保证子模块被 PyTorch 注册,能出现在参数遍历、设备迁移和 state dict 中。
| 对象 | 参数形状 | 前向用途 |
|---|---|---|
| token embedding | 输入编号查行 | |
| position embedding | 位置编号查行 | |
| 注意力 QKV 投影 | 一次线性映射生成 Q、K、V | |
| 注意力输出投影 | 混合拼接后的各头特征 | |
| MLP 第一层 | 扩展特征宽度 | |
| MLP 第二层 | 回到残差宽度 | |
| 每个 LayerNorm | 逐位置归一化后缩放平移 | |
| 输出头 | 与 token embedding 同一个 | 特征转为词表分数 |
表中 Linear 权重写成 PyTorch 的存储形状:输出维在前,输入维在后。计算
4.2 不靠打印的 M 数,自己数一遍
先令 bias=False。一个 block 的注意力有
若 bias=True,每块的四个线性层多出
本书小测试采用 sum(p.numel() for p in model.parameters()) 为 1864。get_num_params() 默认再减去位置嵌入 48,报告 1816。它所谓的“non-embedding”并没有去掉 token embedding,因为同一矩阵也承担输出投影。这是本仓库专用的计数口径,不能只凭函数名把它等同于“扣掉所有嵌入”。
头数
4.3 初始值如何进入残差网络
_init_weights 给 Linear 和 Embedding 的权重使用均值 0、标准差 0.02 的正态分布,Linear 偏置设 0。LayerNorm 构造时已经把缩放设 1、偏置设 0。这样 LayerNorm 初始时执行标准归一化而不额外改变尺度。
随后源码再次遍历参数,把所有名字以 c_proj.weight 结尾的参数设为标准差 c_proj,两者都命中。每个 block 往残差流加两次输出,所以缩放中有
共享权重在 self.apply 前建立,因此初始化遍历会经过 Embedding 和 Linear 两个模块所引用的同一参数。这可能导致该共享矩阵被初始化两次,但最后仍只有一份存储、一份梯度;不能因此把参数数目加倍。随机种子与模块初始化次序也会影响后续随机状态,所以“配置相同”之外,还需固定实现和执行顺序才能比较初始权重。
4.4 改结构时,先看哪个成本增长
把
补充练习。 小测试把最大长度从 6 裁成 4,参数减少多少?把头数从 2 改为 4、宽度仍为 8 呢?
答案。 裁位置表减少 verify_mechanics.py 断言,后者可直接用本节公式核算。恢复已有 checkpoint 时不能仅根据“参数总数一样”认定语义相同,头的划分已经改变。
本章来源
- model.py:GPTConfig、构造、计数和初始化
- 参数估算 notebook
- 本书实测:
qa/author/mechanics.json中parameters。