训一个前沿大模型要烧上千万美元、占满几万张 GPU 几个月。这意味着一个残酷的现实:你几乎没有试错的机会——不能先训一个 GPT-4 看看效果不好再调。那怎么决定"要多大模型、喂多少数据、花多少算力"?答案是 Scaling Laws(规模定律):在小规模上画一条曲线,外推到大规模,提前预测大模型的效果。
这是过去六年 AI 工业化的底层方法论——把"炼丹"变成了"工程预算规划"。今天拆四个核心:幂律规模定律(loss 怎么随规模下降)、Chinchilla 计算最优(固定预算下参数和数据怎么分)、涌现能力(为什么有些能力是"突然冒出来"的)、以及涌现是不是错觉(一个还没结论的争论)。一条主线:从可预测的平滑曲线,到不可预测的突变,再到追问"突变是真的还是尺子造的"。
像你做容量规划压测:在 1/10/100 台机器上各测一次吞吐,发现点连起来是一条漂亮的 log-log 直线,于是你能外推"上 1000 台会到多少"——不用真去搭 1000 台。Scaling law 就是把「模型规模 → loss」画成同样一条可外推的直线,让你在小规模上预测大模型。
痛点:大模型太贵,必须能提前预测。Scaling Laws for Neural Language Models(Kaplan 等 2020,arXiv 2001.08361)系统测了一批模型,发现一个惊人规律:语言模型的 cross-entropy loss(交叉熵损失,衡量"预测下一个词有多准")随三个量呈幂律(power-law)下降——模型参数量 N、训练数据量 D、训练算力 C。以参数为例:
L(N) ≈ (Nc / N)αN
每个符号:L 是 loss(越小越好),N 是参数量,Nc 和 αN 是拟合出来的常数(α 约 0.076,很小)。幂律的含义:两边取对数,log L 对 log N 是一条直线——这就是为什么在 log-log 图上能用小模型外推大模型。Kaplan 发现这条直线横跨 7 个数量级都不弯,干净得不像经验科学。
最反直觉的洞察:大模型样本效率更高。所以在固定算力下,最优策略是训一个非常大的模型,但不要训到收敛就提前停——把算力花在"更大"而不是"看更多遍数据"上。这条结论后来被 Chinchilla 修正了一半(见下一张卡)。
import numpy as np # 几个小模型实测的 (参数量 N, loss),拟合幂律外推大模型 N = np.array([1e6, 1e7, 1e8, 1e9]) # 参数量 loss = np.array([4.2, 3.6, 3.1, 2.7]) # 实测 loss # 幂律在 log-log 下是直线:log L = a*log N + b → 线性拟合 a, b = np.polyfit(np.log(N), np.log(loss), 1) def predict(n): return np.exp(b) * n ** a # L(N) = e^b * N^a print(predict(1e11)) # 外推到 100B 参数的 loss(没真训过) # a 是负数(loss 随 N 增大而降);real-world 还要加 irreducible 项
像固定预算下分配 CPU 和内存:你有固定的服务器开销,要在"算力强"和"内存大"间分。Kaplan 的结论被误读成"把钱都砸在 CPU(更大模型)";Chinchilla 重做实验发现:CPU 和内存(模型大小和数据量)该各加一半,砸偏了反而浪费。
痛点:给定一个固定的算力预算 C(C ≈ 6·N·D,参数量 × 数据量的函数),该怎么在"模型做大"和"数据喂多"之间分配,才能 loss 最低?Training Compute-Optimal Large Language Models(Hoffmann 等 2022,arXiv 2203.15556,即 Chinchilla 论文)训了 400 多个模型系统扫描,得出结论:
最优时:N ∝ C0.5,D ∝ C0.5 → 参数翻倍,数据也该翻倍
含义:参数量 N 和数据量 D 应当等比例放大,落地经验值约为 每个参数配 20 个训练 token。这直接打脸了当时的主流做法——GPT-3(175B 参数只训了约 300B token)严重「训不足」(undertrained)。Chinchilla 用同样算力,造了个更小(70B)但喂 4 倍数据(约 1.4T token)的模型,在 MMLU 等基准上反超了更大的 Gopher。
为什么 Kaplan 和 Chinchilla 结论不同?主要是 Kaplan 实验里学习率调度等设置让"数据维度"被低估了。Chinchilla 修正后,行业从"盲目堆参数"转向"参数和数据并重"——这也是为什么后来的模型参数没疯涨、但训练数据量暴增。
# 给定算力预算,估计计算最优的参数量与数据量 # 近似:训练算力 C ≈ 6 * N * D(FLOPs),且最优时 D ≈ 20 * N def compute_optimal(C): # 代入 D=20N → C ≈ 6 * N * 20N = 120 N^2 N = (C / 120) ** 0.5 # 最优参数量 D = 20 * N # 最优 token 数 return N, D N, D = compute_optimal(C=1e23) # 给一个算力预算(FLOPs) print(f"参数 ~{N:.1e}, 训练 token ~{D:.1e}") # 想要 70B 参数 → 该配约 1.4T token,正是 Chinchilla 的配方
像水到 100°C 突然沸腾:温度一直平滑上升,但到某个临界点状态突变。某些能力(多步算术、跟着示例推理)在小模型上几乎是零/随机,规模过了某个阈值突然飙起来——这种"在小模型上看不到、放大才冒出来"就是涌现。后端类比:分布式系统里单机看不到的群体涌现行为(如缓存雪崩、羊群效应),到一定节点数才显现。
张力:上一张卡说 loss 随规模平滑下降、可外推。但 Emergent Abilities of Large Language Models(Wei 等 2022,arXiv 2206.07682)指出——下游任务的表现不一定平滑。论文编录了一批能力:在某个参数规模之前,模型在该任务上的准确率几乎是随机猜(如多步算术、考试类问答、few-shot 跟例子学);过了临界规模,准确率陡然上升。
为什么这很重要?因为它意味着不能简单从小模型外推下游能力——你在 1B、10B 模型上测某任务全是随机,可能据此判定"这条路不行",但 100B 上它可能突然 work。这给了"继续放大规模"一个强动机:也许下一个能力就藏在下一个数量级里。CoT(chain-of-thought,思维链提示)的有效性也被观察到是规模相关的——小模型用 CoT 没用甚至更差,大模型才吃这一套。
import numpy as np # 用 exact-match(全对才算 1 分)这种"全或无"指标看某任务 sizes = np.array([1e8, 1e9, 1e10, 5e10, 1e11]) # 需连对 5 步才算对,单步正确率随规模平滑上升 per_step = np.array([0.30, 0.45, 0.60, 0.80, 0.92]) # 任务分 = 5 步全对的概率 = 单步^5 → 放大后才显著非零 task_acc = per_step ** 5 for s, a in zip(sizes, task_acc): print(f"{s:.0e}: {a:.2%}") # 0.2% → 0.6% → 7.8% → 33% → 66% # 看着像"突然涌现"——但这其实是下一张卡的伏笔
像用错了进度条:你用"全部测试通过才算完成"的二元指标看一个项目,会看到"某天突然 0% → 100%"的假突变;换成"通过测试数 / 总数"的连续指标,其实进度一直在平滑爬升。突变是尺子造的,不是事情本身突变。
反转:Are Emergent Abilities of Large Language Models a Mirage?(Schaeffer 等 2023,arXiv 2304.15004)对上一张卡的"涌现"提出尖锐质疑——很多被报告的涌现,是研究者选的「评测指标」造成的,不是模型行为的真实突变。
机制:关键在指标是非线性/不连续还是线性/连续。用 exact-match(多步全对才给分)这种"全或无"指标,单步能力的平滑提升会被幂次放大成"看起来突然跳起来"(正是上一张卡代码里 per_step ** 5 那个效应)。但换成连续指标——如逐 token 的损失、或部分给分——同一批模型的同一批输出,曲线就变得平滑、可预测,涌现消失了。论文的核心主张:是度量的选择,而非模型能力,制造了"涌现"的表象。
但要诚实:这不等于"涌现完全不存在"。Mirage 论文证明了很多报告的涌现是度量假象,但没有、也不能证明所有跃升都是假的。这是一个仍在进行的开放争论——它真正的贡献是逼整个领域重新审视自己的尺子。
import numpy as np sizes = np.array([1e8, 1e9, 1e10, 5e10, 1e11]) per_step = np.array([0.30, 0.45, 0.60, 0.80, 0.92]) # 单步正确率:平滑 # 尺子A:5 步全对才算对(非线性)→ 制造"突变" discontinuous = per_step ** 5 # 尺子B:连续指标,直接看单步正确率(已经很平滑) continuous = per_step print("非线性:", np.round(discontinuous, 3)) # [.002 .018 .078 .328 .659] 看着涌现 print("连续 :", np.round(continuous, 3)) # [.30 .45 .60 .80 .92] 平滑可外推 # 同一组底层能力,换尺子 → "涌现"出现或消失