Skip to content

第 4 章 GPTConfig、参数规模与初始化 ​

4.1 一个配置怎样变成一组矩阵 ​

GPTConfig 是 dataclass,保存长度、词表、层数、头数、宽度、dropout 和 bias。默认值描述一个 GPT-2 小模型量级的结构:Tmax=1024,V=50304,L=12,H=12,C=768。其中 50304 是把 GPT-2 的 50257 向上补到 64 的倍数,不是另一套已经定义了 50304 个有效 token 的编码器。多出的输出行仍会参加 softmax,解码时未必有对应 token。GPTConfig 与 GPT 初始化

GPT.__init__ 先构造 ModuleDict:wte 是 token embedding,wpe 是绝对位置 embedding,drop 是输入 dropout,h 是长度为 L 的 ModuleList,ln_f 是最后的 LayerNorm。再创建无偏置的输出 lm_head,并让输入嵌入与输出矩阵共享一个 Parameter。用这些容器不是为了多一层包装;它们保证子模块被 PyTorch 注册,能出现在参数遍历、设备迁移和 state dict 中。

对象参数形状前向用途
token embedding(V,C)输入编号查行
position embedding(Tmax,C)位置编号查行
注意力 QKV 投影(3C,C)一次线性映射生成 Q、K、V
注意力输出投影(C,C)混合拼接后的各头特征
MLP 第一层(4C,C)扩展特征宽度
MLP 第二层(C,4C)回到残差宽度
每个 LayerNorm(C),可另有 (C) 偏置逐位置归一化后缩放平移
输出头与 token embedding 同一个 (V,C)特征转为词表分数

表中 Linear 权重写成 PyTorch 的存储形状:输出维在前,输入维在后。计算 xWT 时,(B,T,C) 才能变成 (B,T,3C)。不了解这个约定,读预训练转置代码时很容易误以为它在“修正数学”。

4.2 不靠打印的 M 数,自己数一遍 ​

先令 bias=False。一个 block 的注意力有 3C2+C2=4C2 个矩阵参数,MLP 有 4C2+4C2=8C2,两个 LayerNorm 各有 C 个缩放参数。因此整块为 12C2+2C。嵌入共 VC+TmaxC,最后 LayerNorm 再加 C,输出头因为共享不重复计数:

Nall=VC+TmaxC+L(12C2+2C)+C.

若 bias=True,每块的四个线性层多出 3C+C+4C+C=9C 个偏置,两个 LayerNorm 多出 2C,最后 LayerNorm 多出 C,得到:

Nall=VC+TmaxC+L(12C2+13C)+2C.

本书小测试采用 V=7,Tmax=6,L=2,C=8、bias=True。计算为 56+48+2(768+104)+16=1864。实际 sum(p.numel() for p in model.parameters()) 为 1864。get_num_params() 默认再减去位置嵌入 48,报告 1816。它所谓的“non-embedding”并没有去掉 token embedding,因为同一矩阵也承担输出投影。这是本仓库专用的计数口径,不能只凭函数名把它等同于“扣掉所有嵌入”。

头数 H 在固定 C 时不出现在上述参数公式里。增加头数把每头维数 d=C/H 变小,QKV 总宽度仍是 3C。这不意味着速度、注意力分布或内存完全不变,只说明参数数目没有随 H 直接倍增。

4.3 初始值如何进入残差网络 ​

_init_weights 给 Linear 和 Embedding 的权重使用均值 0、标准差 0.02 的正态分布,Linear 偏置设 0。LayerNorm 构造时已经把缩放设 1、偏置设 0。这样 LayerNorm 初始时执行标准归一化而不额外改变尺度。

随后源码再次遍历参数,把所有名字以 c_proj.weight 结尾的参数设为标准差 0.02/2L。注意力输出投影和 MLP 输出投影都叫 c_proj,两者都命中。每个 block 往残差流加两次输出,所以缩放中有 2L。可用一个粗略独立性假设理解:若 2L 个增量方差相近且独立,求和方差约乘 2L;将每个增量尺度缩小 2L,可以抑制初始残差方差随深度增长。真实网络的分支不是严格独立,所以这是初始化设计直觉,不是训练全程方差守恒定理。

共享权重在 self.apply 前建立,因此初始化遍历会经过 Embedding 和 Linear 两个模块所引用的同一参数。这可能导致该共享矩阵被初始化两次,但最后仍只有一份存储、一份梯度;不能因此把参数数目加倍。随机种子与模块初始化次序也会影响后续随机状态,所以“配置相同”之外,还需固定实现和执行顺序才能比较初始权重。

4.4 改结构时,先看哪个成本增长 ​

把 C 翻倍,block 主矩阵参数约增到四倍;把 L 翻倍,这部分约增到两倍。把词表 V 翻倍,输入/输出共享矩阵加倍,训练时 logits 的最后一维也加倍。把最大长度翻倍,只让位置参数线性增长,却可能让手工注意力矩阵按长度平方增长。参数数量不是训练显存的完整估算,第 14 章会把激活和优化器状态分开。

补充练习。 小测试把最大长度从 6 裁成 4,参数减少多少?把头数从 2 改为 4、宽度仍为 8 呢?

答案。 裁位置表减少 (6−4)×8=16 个参数;头数改变不改参数总量,但每头宽度从 4 变成 2。前者已在 verify_mechanics.py 断言,后者可直接用本节公式核算。恢复已有 checkpoint 时不能仅根据“参数总数一样”认定语义相同,头的划分已经改变。

本章来源 ​

保留各书来源与许可 · 示例运行条件以正文为准