查看全课大纲(第 27 / 40 节)
- 1.从规则到学习:深度学习解决什么问题
- 2.表示学习与端到端训练
- 3.张量、计算图与自动微分
- 4.训练三要素:数据、模型、目标函数
- 5.从感知机到多层感知机
- 6.反向传播:手推一个两层网络
- 7.激活函数与初始化
- 8.优化器:SGD → Momentum → Adam
- 9.正则化:权重衰减、Dropout、BN/LN
- 10.卷积的直觉与参数共享
- 11.池化、感受野与下采样
- 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
- 13.残差连接与归一化为什么有效
- 14.迁移学习与特征可视化
- 15.RNN 与 BPTT
- 16.LSTM/GRU:门控如何缓解梯度问题
- 17.Seq2Seq 与编码器-解码器
- 18.注意力前史:Bahdanau 加性注意力
- 19.Self-Attention:QKV 与缩放点积
- 20.多头注意力:并行的多种关系
- 21.位置编码:正弦与可学习
- 22.残差、LayerNorm 与前馈层
- 23.从零实现一个最小 Transformer block
- 24.训练稳定性、复杂度与常见误区
- 25.自监督与语言建模目标
- 26.BERT:掩码语言模型与双向编码
- 27.GPT:自回归生成与缩放定律
- 28.指令微调与对齐:SFT → RLHF/DPO
- 29.参数高效微调:LoRA 与适配器
- 30.自编码器与 VAE
- 31.GAN:对抗训练与模式崩塌
- 32.扩散模型:前向加噪与反向去噪
- 33.扩散模型的应用与采样加速
- 34.对比学习与 CLIP
- 35.ViT:把图像当序列
- 36.多模态架构:对齐、融合与生成
- 37.能力边界、幻觉与评测
- 38.PyTorch 2.x 训练循环与混合精度
- 39.实验管理、评测与复现
- 40.推理部署与成本:量化/蒸馏概览
GPT:自回归生成与缩放定律
小象实战讲义 · 深度学习基础:从神经网络到 Transformer
在掌握了 Transformer 架构和预训练的基本概念后,我们面临一个核心问题:如何利用 Transformer 构建一个强大的通用语言模型?GPT 系列模型给出了一个简洁而有力的答案:自回归生成。本节将深入剖析 GPT 的核心机制——自回归语言建模,并探讨决定其性能的关键因素:缩放定律。学完本节,你将能回答:GPT 如何通过预测下一个词来学习?为什么模型和数据规模如此重要?以及如何根据计算预算最优地分配模型参数和训练数据量?
先修:因果语言建模目标与因果掩码(本章 6.1 节、第 5 章 5.6 节)、交叉熵与 softmax。后续:下一节《指令微调与对齐:SFT → RLHF/DPO》,再下一节《参数高效微调:LoRA 与适配器》。
💡 核心导读
- 自回归生成:GPT 是一个从左到右预测序列中下一个词(token)的模型,其核心是因果注意力掩码,确保预测时只能看到当前位置之前的信息。
- GPT 架构:基于 Transformer 解码器堆叠,移除了编码器-解码器注意力层,保留了掩码自注意力层和前馈网络。
- 缩放定律:语言模型的性能(损失)与模型参数量(N)和训练数据量(D)之间存在可预测的幂律关系。给定固定计算预算,存在一个计算最优的 (N, D) 分配点。
- 计算最优训练:Chinchilla 研究表明,当前大模型普遍“训练不足”,最优策略是让模型大小和训练数据量等比例缩放(例如,参数量翻倍,训练 token 数也应翻倍)。
- 上下文学习:足够大的自回归模型能够在推理时仅通过少量任务示例(上下文)来适应新任务,而无需更新模型权重。
🧭 前置知识
- Transformer 解码器:了解其掩码自注意力机制和前馈网络结构。
- 语言建模目标:理解用交叉熵损失最大化序列概率的似然估计。
- softmax 函数:用于将 logits 转换为概率分布。
- 矩阵乘法形状规则:理解
(batch, seq, dim)张量在注意力机制中的运算。 - 计算量(FLOPs)的基本概念:知道模型参数量和处理的 token 数共同决定了训练所需的浮点运算次数。(不展开计算细节,见站内数学基础课)
自回归语言建模:GPT 的核心机制
GPT(Generative Pre-trained Transformer)的核心思想是使用 Transformer 解码器架构进行自回归语言建模。给定一个 token 序列 ,模型的目标是预测下一个 token 的概率分布: 其中 是模型在处理前 t 个 token 后产生的最后一个隐藏状态, 是一个将隐藏状态映射到词表大小维度的权重矩阵。
为了实现自回归,GPT 在 Transformer 解码器的自注意力层中使用了因果掩码。这确保在计算位置 i 的表示时,只能关注到位置 1 到 i 的 token,无法看到未来的信息。
import torch
import torch.nn.functional as F
torch.manual_seed(0)
# 模拟一个简化的 GPT 单层自注意力前向过程,重点展示因果掩码
batch_size = 2
seq_len = 6
d_model = 8
num_heads = 2
head_dim = d_model // num_heads
# 1. 构造输入序列的嵌入表示 (模拟经过一层线性投影后的 Q, K, V)
# 形状: (batch, seq_len, d_model)
x = torch.randn(batch_size, seq_len, d_model)
# 拆分为多头,形状变为: (batch, num_heads, seq_len, head_dim)
q = k = v = x.reshape(batch_size, seq_len, num_heads, head_dim).transpose(1, 2)
# 2. 计算缩放点积注意力分数
# scores 形状: (batch, num_heads, seq_len, seq_len)
scores = torch.matmul(q, k.transpose(-2, -1)) / (head_dim ** 0.5)
# 3. 应用因果掩码 (上三角矩阵,未来位置为负无穷)
mask = torch.triu(torch.ones(seq_len, seq_len) * float('-inf'), diagonal=1)
causal_scores = scores + mask
# 4. 计算注意力权重和输出
attn_weights = F.softmax(causal_scores, dim=-1)
# 注意力输出形状: (batch, num_heads, seq_len, head_dim)
attn_output = torch.matmul(attn_weights, v)
print("因果掩码矩阵 (上三角为 -inf):")
print(mask)
print("\n应用因果掩码后的注意力分数 (仅展示第一个样本的第一个头,取前3行):")
sample_scores = causal_scores[0, 0, :3, :].tolist()
formatted_scores = [[round(v, 4) for v in row] for row in sample_scores]
for i, row in enumerate(formatted_scores):
print(f"行 {i}: {row}")
print("\n对应的注意力权重 (行和为1,未来位置权重为0):")
sample_weights = attn_weights[0, 0, :3, :].tolist()
formatted_weights = [[round(v, 4) for v in row] for row in sample_weights]
for i, row in enumerate(formatted_weights):
print(f"行 {i}: {row}")因果掩码矩阵 (上三角为 -inf):
tensor([[0., -inf, -inf, -inf, -inf, -inf],
[0., 0., -inf, -inf, -inf, -inf],
[0., 0., 0., -inf, -inf, -inf],
[0., 0., 0., 0., -inf, -inf],
[0., 0., 0., 0., 0., -inf],
[0., 0., 0., 0., 0., 0.]])
应用因果掩码后的注意力分数 (仅展示第一个样本的第一个头,取前3行):
行 0: [1.4232, -inf, -inf, -inf, -inf, -inf]
行 1: [0.4358, 0.9587, -inf, -inf, -inf, -inf]
行 2: [1.4955, 1.0747, 2.8283, -inf, -inf, -inf]
对应的注意力权重 (行和为1,未来位置权重为0):
行 0: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
行 1: [0.3722, 0.6278, 0.0, 0.0, 0.0, 0.0]
行 2: [0.1835, 0.1205, 0.696, 0.0, 0.0, 0.0]从输出可见,因果掩码确保了每个位置只能关注自身及之前的 token。例如,位置 2 的 token 可以关注位置 1 和 2,但无法关注位置 3、4、5、6。这使得模型非常适合用于生成任务:在预测下一个词时,它只依赖于已生成的上下文。
注意训练和推理的不对称:训练时整段序列一次前向就能并行算出所有位置的损失(因果掩码保证位置 只依赖前缀,等价于同时训练了 个预测任务);推理时只能一个一个生成,每步把新 token 接到末尾。工程上用 KV 缓存省掉重复计算——前 个位置的 、 缓存复用,每步只为新 token 算一份 、、,再做一次 的注意力。这样每步成本从 降到 ,整段长度 的总成本从 降到 ——序列一长,差的就是一个 倍。
生成时还有一个推理侧的旋钮:贪心(每步取 argmax)稳定但容易重复;按概率采样(temperature、top-k、top-p)能换来多样性,温度调高会更“有创意”,也更容易跑题。它只影响推理行为,不改变训练目标。
GPT 的演进与缩放定律
GPT 系列模型(GPT-1, GPT-2, GPT-3)的核心架构一脉相承,主要区别在于模型规模(层数、隐藏维度、注意力头数)和训练数据量的急剧扩大。GPT-3 论文 Table 2.1 给出的最大一档是 96 层、、96 个注意力头、1750 亿参数,上下文长度 2048;8 个尺寸的模型全部训练了 3000 亿 token(数据配比见 Table 2.2)。和 BERT_BASE(12 层 / 768 维 / 1.1 亿参数)摆在一起,就能看出这两条路线在体量上的分野。
这种扩大并非随意,而是基于对缩放定律的观察和研究。
缩放定律 描述了语言模型性能(通常用验证集损失衡量)与三个关键因素:模型参数量(N)、训练数据量(D,以 token 计)和计算量(C,以 FLOPs 计)之间的经验关系。在《Language Models are Few-Shot Learners》(Brown et al., 2020,arXiv:2005.14165)中,作者通过训练不同规模的模型(从 1.25 亿到 1750 亿参数)发现,损失随模型大小和计算量增加而平滑下降,遵循幂律关系。
计算预算可以写成 。为什么是 6:每个参数处理每个 token,前向大约 2 次浮点运算(一乘一加),反向要算对输入和对权重的两份梯度,约 4 次,合起来正好 6。所以“算力预算”这句话可以直译成“参数 × token 的乘积”。由此引出一个关键问题:给定固定的 ,应该如何最优地分配 和 ,以最小化损失 ?
早期研究(Kaplan et al., 2020,arXiv:2001.08361)认为,当计算预算增加时,应主要扩大模型规模(),而数据只需缓慢增加()。后续工作《Training Compute-Optimal Large Language Models》(Hoffmann et al., 2022,arXiv:2203.15556)推翻了这一结论。
Chinchilla 论文通过训练超过 400 个不同规模(从 7000 万到 160 亿参数)和不同训练量(5B 到 500B token)的模型,拟合出损失函数 。他们发现,对于计算最优训练,模型大小和训练 token 数量应该等比例缩放:,。换句话说,计算预算翻 4 倍时,参数量和数据量各翻 2 倍。
换算成工程经验值,大约是每个参数配 20 个 token(Chinchilla 的 70B × 20 ≈ 1.4T)。GPT-3 是每个参数约 1.7 个 token(300B / 175B),按这把尺子看明显“喂得不够”。
论文附录 D.2 给出的拟合形式是 ,其中 、、(、 用原始个数)。下面就用这组真实参数,算一遍“同一预算下,谁更划算”:
# Chinchilla 论文附录 D.2 拟合出的损失形式:L(N, D) = E + A/N^0.34 + B/D^0.28
# 注意:N、D 都用原始个数(不是十亿),常数才是论文的值
E = 1.69
A, a = 406.4, 0.34
B, b = 410.7, 0.28
def loss_fn(N, D):
return E + A / (N ** a) + B / (D ** b)
# 固定计算预算,近似取 GPT-3 的规模:C = 6*N*D
target_C = 6 * 175e9 * 300e9
rows = []
for N in [70e9, 100e9, 140e9, 175e9, 280e9]:
D = target_C / (6 * N) # 预算固定,D 由 N 反推
rows.append((N, D, loss_fn(N, D)))
print(f"固定计算预算 C = 6*N*D ≈ {target_C:.2e} FLOPs")
for N, D, L in sorted(rows, key=lambda x: x[2]):
print(f" N={N / 1e9:5.0f}B, D={D / 1e9:6.1f}B -> L ≈ {L:.4f}")固定计算预算 C = 6*N*D ≈ 3.15e+23 FLOPs
N= 70B, D= 750.0B -> L ≈ 1.9678
N= 100B, D= 525.0B -> L ≈ 1.9787
N= 140B, D= 375.0B -> L ≈ 1.9919
N= 175B, D= 300.0B -> L ≈ 2.0023
N= 280B, D= 187.5B -> L ≈ 2.0286模拟显示,在相同计算预算下,更小的模型(70B)配合更多的数据(750B token)预测损失更低。这正是 Chinchilla(70B 参数、1.4T token)的设计依据,它在多项评测中超越了更大的 Gopher(280B 参数、300B token)。
需要提醒一点:缩放定律拟合的是预训练损失,它并不自动等价于下游任务表现;数据质量、重复度、配比都会让同尺寸模型的实测差距超出这条曲线的预测。所以上面这张表只适合做”量级判断”,不适合当成选型的绝对依据。
上下文学习:大模型涌现的能力
当自回归语言模型被缩放得足够大时,会涌现出一种令人惊奇的能力:上下文学习。如 GPT-3 论文所述,模型可以在不进行任何梯度更新(即不微调)的情况下,仅通过将任务描述和少量示例作为输入文本的一部分(即“上下文”),就能解决新任务。
例如,要执行翻译任务,你可以将以下文本输入给模型:
Translate English to French:
sea otter => loutre de mer
cheese => fromage
pizza => pizza
computer =>模型会基于前面的模式,生成“ordinateur”作为“computer”的法语翻译。
这种能力被认为是模型在大量文本中学习到的内部表征和模式识别能力的体现。论文的对照很直接:同一个任务描述 + 示例,模型越大,few-shot 成绩越好;而在小模型上,示例加进去基本没用。也就是说,“给几个例子就能学会”不是提示词的魔法,而是规模到一定程度才涌现出来的能力。
它的代价也很明确:示例和任务描述都要占上下文窗口,且模型依然可能答非所问——提示只能规定“格式和套路”,规定不了“什么算好答案”。这正是下一节要引入指令微调与人类反馈的原因。
把 GPT 和上一节的 BERT 并排看会更清楚:两者都靠自监督预训练,区别只在一个字——目标。BERT 遮词猜词(双向、用编码器),GPT 预测下一个词(单向、用解码器)。下游用法也随之分叉:BERT 的典型姿势是“加个分类头做微调”,GPT 的典型姿势是“给段提示直接生成”。这条分叉并不是终局,下一节的指令微调会把“跟着指令走”重新缝回预训练模型里。
📝 动手练一练
题目:根据 Chinchilla 论文的等比例缩放思想,假设我们有一个计算最优的模型 A,其参数量 ,训练数据量 token。现在我们将计算预算 增加到原来的 8 倍。请计算在新的计算最优配置下,模型 B 的参数量 和训练数据量 应该是多少?(提示:计算预算 ,且 和 应等比例缩放)。
👉 点击查看参考答案
思路:
- 计算预算 。对于模型 A, 。
- 计算预算增加到 8 倍,即 。
- 根据等比例缩放原则,当计算预算扩大 倍时, 和 都应扩大 倍。因为 ,若 和 都扩大 倍,则 扩大 倍。因此 。
- 本题中 ,所以缩放因子 。
- 计算 。
- 计算 。
结论:在 8 倍计算预算下,计算最优的配置大约是 参数, token。
import math
N_A = 10.0 # 10B
D_A = 100.0 # 100B
k = 8.0
s = math.sqrt(k)
N_B = N_A * s
D_B = D_A * s
print(f"原始配置: N_A = {N_A}B, D_A = {D_A}B")
print(f"计算预算扩大倍数: k = {k}")
print(f"等比例缩放因子: s = sqrt({k}) ≈ {s:.4f}")
print(f"新计算最优配置: N_B ≈ {N_B:.1f}B, D_B ≈ {D_B:.1f}B")
# 验证: C_B / C_A ≈ (N_B * D_B) / (N_A * D_A) = s^2 = k
print(f"验证计算预算扩大倍数: (N_B * D_B) / (N_A * D_A) ≈ {(N_B * D_B)/(N_A * D_A):.4f} (目标: {k})")原始配置: N_A = 10.0B, D_A = 100.0B
计算预算扩大倍数: k = 8.0
等比例缩放因子: s = sqrt(8.0) ≈ 2.8284
新计算最优配置: N_B ≈ 28.3B, D_B ≈ 282.8B
验证计算预算扩大倍数: (N_B * D_B) / (N_A * D_A) ≈ 8.0000 (目标: 8.0)常见错误
| 错误理解或写法 | 现象或报错 | 原因 | 正确做法 |
|---|---|---|---|
| 在实现 GPT 自注意力时忘记应用因果掩码。 | 模型在训练时表现正常,但在生成文本时出现“信息泄露”,生成质量差或不合逻辑。 | 自注意力机制默认可以看到整个序列,这违反了自回归生成只能依赖过去信息的约束。 | 必须在 softmax 之前,把未来位置(,即键在查询之后的上三角)的分数填成 -inf 或一个极大负数(如 -1e9)。 |
| 认为模型越大越好,盲目增加参数量而不增加训练数据。 | 模型训练损失下降缓慢或早停,未能达到预期性能,计算资源浪费。 | 根据 Chinchilla 定律,在固定计算预算下,过度增大模型会导致“训练不足”,模型容量未被充分训练。 | 根据计算预算,参考缩放定律,平衡模型参数量(N)和训练数据量(D)。通常需要同步增加两者。 |
| 将上下文学习与微调混淆。 | 误以为在输入中提供示例后,需要运行反向传播来更新模型权重。 | 上下文学习是模型在推理阶段利用前向传播,根据输入上下文动态调整其“行为”,不涉及任何权重更新。 | 理解上下文学习是模型内部能力的体现。只需将任务描述和示例拼接成输入序列,让模型自回归地生成答案即可。 |
本章小结
- GPT 采用 Transformer 解码器架构和因果注意力掩码,通过自回归语言建模目标(预测下一个词)进行预训练。
- 缩放定律揭示了模型性能与参数量、训练数据量和计算量之间的幂律关系,是指导大模型研发的重要经验规律。
- Chinchilla 研究发现,对于计算最优训练,模型参数量(N)和训练 token 数(D)应等比例缩放,而非一味追求更大的模型。
- 当模型规模足够大时,会涌现出上下文学习能力,即无需微调,仅通过提供任务描述和少量示例就能解决新任务。
- GPT-3 等大模型展示了自回归预训练路径的强大潜力,但其成功严重依赖于巨量的模型参数、高质量的训练数据和庞大的计算资源。
📋 行动清单
- 在因果掩码的代码示例中,尝试将
seq_len改为 10,观察掩码矩阵和注意力权重的变化,加深对自回归约束的理解。 - 使用 Chinchilla 等比例缩放的计算器,假设你有一个 1B 参数、10B token 的基线模型,计算当计算预算增加 16 倍时的最优配置。
- 构思一个简单的上下文学习提示(例如,情感分类:
"I love this movie." -> positive\n"The food was terrible." -> negative\n"The weather is nice today." ->),并思考模型内部是如何可能完成这个任务的。
延伸阅读
- 《Language Models are Few-Shot Learners》(Brown et al., 2020,arXiv:2005.14165):GPT-3 的原始论文,详细介绍了模型架构、训练过程、上下文学习能力及其在众多任务上的评估结果。
- 《Training Compute-Optimal Large Language Models》(Hoffmann et al., 2022,arXiv:2203.15556):Chinchilla 论文,系统研究了模型规模与训练数据量的最优分配问题,提出了等比例缩放定律。
—— 小象教研组