缩放定律
假设你有 10 万美元的算力预算,模型该做多深、多宽?数据该用多少?这个问题不靠直觉回答——答案藏在一组幂律关系里。
这一节从幂律分布的基本概念出发,理解 Scaling Laws 三次范式转变,最后学会估算训练所需的算力和显存。
缩放定律(Scaling Laws)研究的是模型规模、数据量和训练计算量之间的定量关系。它的核心发现是一个经验规律:增大模型或增加数据,loss 都会下降,但下降服从幂律——每次翻倍带来的提升越来越小。这个规律决定了给定算力预算下,模型和数据的最优配比。
从 2020 年的 Kaplan 到 2022 年的 Chinchilla,再到 2023 年之后的过度训练范式,最优配比的结论被推翻了两次。这一节用具体数字追踪这两次转变 背后的数学依据。
上半场:缩放定律
1. 幂律分布
在进入缩放定律之前,必须先理解「幂律」这个概念。
1.1 线性递减 vs 幂律递减
假设你每个月存的钱翻倍:
-
线性递减:每次翻倍,收益减固定数值
- 存 100 → 收益 10
- 存 200 → 收益 20(+10)
- 存 400 → 收益 30(+10)← 每次翻倍多赚一样多
-
幂律递减:每次翻倍,收益减固定比例
- 存 100 → 收益 10
- 存 200 → 收益 18(+80%)
- 存 400 → 收益 32.4(+80%)← 每次翻倍多赚一样比例
LLM 的训练 loss 遵循的是幂律递减:模型大小翻倍,loss 降低一个固定比例。
用公式写:Loss(N) ≈ a × N^(-b) + c
其中:
- N = 模型参数数量
- a, b, c = 常数(通过实验拟合出来的)
- N^(-b) 表示「N 越大,这一项越小」
- c 表示「loss 的终极下限」——无论模型多大,loss 不可能低于 c
# === 幂律递减的手算演示 ===
print("=== 幂律递减:Loss 随模型大小变化 ===")
print()
# 模拟 loss = a * N^(-b) + c
# 用很小的数 字来演示
a, b_pow, c = 5.0, 0.05, 2.0
# 模型从 1M 参数到 100B 参数
sizes = [1, 10, 100, 1000, 10000, 100000] # 单位:百万参数
labels = ["1M", "10M", "100M", "1B", "10B", "100B"]
print(f"公式: Loss = {a} × N^(-{b_pow}) + {c}")
print()
print(f"{'模型大小':>10s} {'Loss':>8s} {'相比上一级':>12s}")
print("-" * 32)
prev_loss = None
for size, label in zip(sizes, labels):
N = size * 1e6 # 转成参数数量
loss = a * N**(-b_pow) + c
if prev_loss:
improvement = (prev_loss - loss) / prev_loss * 100
print(f"{label:>10s} {loss:>6.2f} 降低 {improvement:>5.1f}%")
else:
print(f"{label:>10s} {loss:>6.2f} —")
prev_loss = loss
print()
print("关键观察:")
print(" 1. 每次模型扩大 10 倍,loss 确实在降")
print(" 2. 但降低的幅度越来越小 → 边际收益递减")
print(" 3. 从 1B → 10B 降低的幅度 < 从 1M → 10M")
print()
print("这就是幂律的核心结论:越大越好,但越来越不值。")
print("你需要一个公式来告诉你:到哪一步就不值得再大了。")
2. Kaplan 缩放定律(OpenAI, 2020)— 「优先增大模型」
2020 年,OpenAI 做了大量实验,想知道:给定固定的计算预算 C(比如 1e20 FLOPs),模型大小 N 和数据量 D 怎么配比最好?
2.1 核心发现
他们发现,最优的配比是这样的:
N_opt ∝ C^0.73 ← 模型大小随计算预算增长快
D_opt ∝ C^0.27 ← 数据量随计算预算增长慢
指数 0.73 > 0.27 → 模型增长的速度比数据快得多。
用人话:如果你的计算预算翻倍了,应该把大部分多出来的预算用来增大模型,少部分用来增加数据。
2.2 用具体数字感受一下
# === Kaplan 定律手算 ===
print("=== Kaplan 缩放定律:给定计算预算,最优配置是什么? ===")
print()
# 参照点:假设 C=1e20 FLOPs 时,N=1B, D=20B tokens
N_ref = 1e9 # 10 亿参数
D_ref = 20e9 # 200 亿 token
C_ref = 1e20 # 计算预算
print(f"参照点: C={C_ref/1e20:.0f}×10²⁰ FLOPs → N={N_ref/1e9:.0f}B, D={D_ref/1e9:.0f}B tokens")
print()
# 计算预算不断翻倍
budgets = [1e20, 2e20, 4e20, 8e20, 1e21, 1e22]
budget_labels = ["1×", "2×", "4×", "8×", "10×", "100×"]
print(f"{'计算预算':>12s} {'模型大小(Kaplan)':>18s} {'数据量(Kaplan)':>18s} {'D/N比':>10s}")
print("-" * 64)
for C, label in zip(budgets, budget_labels):
ratio = C / C_ref
N_opt = N_ref * (ratio ** 0.73)
D_opt = D_ref * (ratio ** 0.27)
dn_ratio = D_opt / N_opt
if N_opt >= 1e9:
n_str = f"{N_opt/1e9:.1f}B"
else:
n_str = f"{N_opt/1e6:.0f}M"
d_str = f"{D_opt/1e9:.1f}B tokens"
print(f"{label:>12s} {n_str:>18s} {d_str:>18s} {dn_ratio:>8.1f}x")
print()
print("Kaplan 的判断:")
print(" • 计算预算越大,模型越大(远超数据增长)")
print(" • D/N 比越来越小 → 大模型可以「省着用」数据")
print(" • 比如 10B 模型只需要 40B tokens(而不是直觉上的越多越好)")
3. Chinchilla 缩放定律(DeepMind, 2022)— 「模型和数据同样重要」
3.1 为什么 Kaplan 错了?
DeepMind 重新做了实验,发现 OpenAI 之前做实验时,计算预算增大后,没有充分增加数据量——因为当时没有那么多高质量数据可用。
Kaplan 说的「优先增大模型」其实是在「数据不够」的前提下得出的结论。
3.2 Chinchilla 的新结论
DeepMind 控制了变量:对每个计算预算,尝试了几十种不同的 (N, D) 组合,找到真正最优的那个。结论:
N_opt ∝ C^0.5
D_opt ∝ C^0.5
→ 模型和数据同等重要!
→ D_opt ≈ 20 × N_opt (每个参数配约 20 个 token)
3.3 用同一个计算预算,Kaplan vs Chinchilla 会做出不同的选择
# === Kaplan vs Chinchilla 对决 ===
print("=== Kaplan vs Chinchilla:同一预算,不同方案 ===")
print()
# 一个具体的预算
C = 1e23 # 约 GPT-3 的训练量级
ratio = C / C_ref
N_k = N_ref * (ratio ** 0.73)
D_k = D_ref * (ratio ** 0.27)
N_c = N_ref * (ratio ** 0.5)
D_c = D_ref * (ratio ** 0.5)
print(f"计算预算: {C/1e23:.0f} × 10²³ FLOPs")
print()
print(f"{'':>20s} {'模型大小':>12s} {'数据量':>14s} {'D/N比':>10s}")
print("-" * 60)
print(f"{'Kaplan 说':>20s} {N_k/1e9:>8.1f}B {D_k/1e9:>9.1f}B tokens {D_k/N_k:>8.1f}x")
print(f"{'Chinchilla 说':>20s} {N_c/1e9:>8.1f}B {D_c/1e9:>9.1f}B tokens {D_c/N_c:>8.1f}x")
print()
print(f"对比:")
print(f" Kaplan 方案: 模型大 {N_k/N_c:.1f}×,但数据只有 Chinchilla 的 {D_k/D_c:.1f}×")
print(f" Chinchilla 方案: 模型更小,但数据更多")
print()
print(f"实验结果: Chinchilla 方案 loss 更低 → 推翻了 Kaplan")
print(f" → 数据少的大模型 < 数据多的中等模型")
3.4 Chinchilla 的震撼案例
DeepMind 用这个发现训了两个模型:
Gopher: 280B 参数,300B tokens 训练 — 按 Kaplan 配方
Chinchilla: 70B 参数,1.4T tokens 训练 — 按 Chinchilla 配方
训练计算量相同!
结果:Chinchilla (70B) >> Gopher (280B)
→ 小了 4 倍的模型,因为数据多了 4.7 倍,效果反而更好
这就是 Chinchilla 带来 的冲击——很多当时的大模型可能都「数据喂少了」。
4. 后 Chinchilla 时代:过度训练反而更好
Chinchilla 说 D/N ≈ 20 最优。但 2023-2024 年的实际做法完全突破了这个建议。
4.1 为什么?因为 Chinchilla 最优化的是「训练成本」
Chinchilla 的「最优」是指:在固定训练预算下,让 loss 最低。
但实际部署时,推理成本才是大头——模型上线后要服务百万用户,推理一次花的钱远超过训练时的钱。
所以最优策略变成了:训一个小模型,狂喂数据,让它超越预期能力。
LLaMA 3 8B: Chinchilla 说它只需要 8B×20 = 160B tokens
实际喂了 15T tokens → 是 Chinchilla 最优的 94 倍!
效果 → 远超 Chinchilla 最优下的 8B 模型应有的水平
这叫「推理-训练权衡」:多花训练成本,换取推理时用小模型省大钱。
# === 实际模型的 D/N 比 ===
print("=== 各大模型的 D/N 比 ===")
print()
real_models = [
("Chinchilla (最优)", 70, 1400, "20x"),
("LLaMA 7B", 7, 1000, "143x"),
("LLaMA 2 7B", 7, 2000, "286x"),
("LLaMA 3 8B", 8, 15000, "1875x"),
("DeepSeek-V2", 236, 8100, "34x (active: ~386x)"),
]
print(f"{'模型':<20s} {'参数':>8s} {'数据量':>12s} {'D/N':>10s}")
print("-" * 54)
for name, params_b, tokens_b, dn in real_models:
print(f"{name:<20s} {params_b:>5}B {tokens_b:>6}B tokens {dn:>10s}")
print()
print("观察: 2023 年后的模型 D/N 比远超 20x")
print(" → 行业共识已变成「小模型 + 海量数据」")
print(" → 因为推理时用小模型便宜得多,训练时多花点数据成本也值得")
5. µP:最大更新参数化
训练一个 70B 模型之前,需要先确定超参:学习率、初始化标准差、权重衰减系数……但在 70B 上做网格搜索不现实——跑一次就要几十万。
通常做法:在 10M 的小模型上调好参数,然后原样搬到 70B 上。
但这个方法经常失灵。 10M 上的最优学习率,搬到 70B 上可能训练不稳定——梯度爆炸或 loss 不下降。 原因在于,不同大小的模型在训练初期的激活值和梯度量级不一样。一个对 10M 模型合适的步长,对 70B 模型来说可能太大或太小。
µP 解决的就是这个问题。它对每一层的初始化方差和学习率做了特殊的缩放规则,使得不同大小的模型在训练初期的行为保持一致——10M 模型和 70B 模型的「手感」相同。
具体来说,µP 控制了每层权重矩阵的初始化方差和对应的学习率缩放。核心规则是:对于 hidden 维度的权重矩阵 W(d_out × d_in),初始化方差按 1/d_in 缩放,学习率按 1/d_in 缩放(对于输入权重)或 1/d_out 缩放(对于输出权重)。这样一来,无论模型宽度怎么变,每层的输出量级和梯度量级保持恒定。
不用 µP: 10M 上调出 lr=0.01 → 搬到 70B → 可能失稳,还要重新调
用了 µP: 10M 上调出 lr=0.01 → 搬到 70B → 更有机会迁移,但仍要验证
这节省的是巨大的人力和算力成本:小模型调出的超参更有机会迁移到大模型,但真实训练仍需要验证和少量调整。µP 是大模型超参迁移研究中的重要方法,也被不少训练系统和研究工作采用。参考:µP paper、microsoft/mup、Cerebras µP docs。
下半场:资源估算
6. FLOPs 估算
6.1 核心公式
C ≈ 6 × P × D