拟合方法
对数线性回归简单但不稳健;NLLS 更可靠;Isoflop 曲线最精确
CS336 · 第 11 课
第 9 课建立了 scaling law 的宏观图景;这一课深入实际工程问题:如何从噪声数据中稳健拟合幂律?如何量化外推的不确定性?如何用 scaling law 在真实约束下做最优决策?
Spring 2026 官方来源:Lecture 11 PDF · 课程日程
本页是中文学习导读;讲次主题、教师与材料版本以 Stanford 官方页面和讲义为准。
本讲问题:第 9 课给出了 scaling law 的结论;本讲进一步处理真正困难的部分:有限、带噪声的实验怎样支持对更大训练规模的外推和决策。
选择函数形式 → 设计小规模 runs → 拟合参数与置信区间 → 检查 residual / regime shift → 外推候选规模 → 用敏感性分析做决策。
本课把“曲线看起来像直线”升级为可审计预测。Parameter 是模型中待拟合的系数,hyperparameter 是训练设置,两者不要混淆;residual = observation − fitted prediction,它的结构比单个 \(R^2\) 更能暴露模型错在哪里。
run design → observations → fit parameters
├→ held-out residuals
├→ bootstrap / uncertainty
└→ regime-shift checks
↓
candidate extrapolation → sensitivity analysis → decision
point estimate ≠ confidence interval ≠ guarantee参数规模从 \(10^9\) 到 \(10^{12}\) 是 1000×。即使 log-space 只向右移动 3 个 decade,任何很小的 exponent 或 floor 偏差都会被外推放大。用 runs 拟合后,应故意留出最大的若干规模做 pseudo-extrapolation:如果连已知的大 run 都预测不好,就没有理由信任更远预测。
更灵活的函数可追噪声并给出荒谬 asymptote;随机 split 也可能让相邻规模泄漏趋势。反例:所有 residual 在小 \(N\) 为正、大 \(N\) 为负,即使平均 residual 为零,也说明函数形式存在系统偏差。
第 9 课的核心结论:
这一课的核心问题:这些结论是从拟合得到的。拟合本身有噪声,外推有风险,实际决策有更多约束。我们需要理解这些机制才能安全地使用 scaling law。
# 参数 vs 损失(固定数据量)
L(N) = (N_c / N)^α_N
# 数据 vs 损失(固定参数量)
L(D) = (D_c / D)^α_D
# 联合形式(Chinchilla 论文)
L(N, D) = E + (N_c / N)^α + (D_c / D)^β
# 其中 E 是不可约损失(irreducible loss)
对幂律 L = (N_c / N)^α 取对数,得到:
log L = α log N_c − α log N
= a − α × log N (关于 log N 的线性函数)
# 算法:
# 1. 训练多个不同大小的模型(固定其他超参)
# 2. 记录 (log N, log L)
# 3. 做线性回归
# 4. 斜率 = −α,截距 = α log N_c
问题:在 log-log 图上,小模型的 loss 变化更剧烈,大模型变化平缓。简单线性回归对所有点等权对待,会被小模型区域主导。而且 log 变换会让小 loss 区域的噪声被放大。
直接拟合原始幂律形式,避免 log 变换的偏差:
import numpy as np
from scipy.optimize import curve_fit
def scaling_law(N, N_c, alpha, L_inf):
"""带不可约损失的幂律"""
return (N_c / N) ** alpha + L_inf
# 数据:多个模型的参数量和验证 loss
N_values = np.array([1e6, 3e6, 1e7, 3e7, 1e8, 3e8, 1e9])
losses = np.array([4.2, 3.8, 3.5, 3.2, 2.9, 2.7, 2.5])
# 拟合
popt, pcov = curve_fit(scaling_law, N_values, losses,
p0=[1e5, 0.1, 2.0],
bounds=([0, 0, 0], [np.inf, 1, np.inf]))
N_c, alpha, L_inf = popt
print(f"α = {alpha:.3f}")
print(f"N_c = {N_c:.2e}")
print(f"L_∞ = {L_inf:.3f}")
Chinchilla 论文使用的是同时变化 N 和 D 的联合拟合:
def chinchilla_law(N, D, E, alpha, beta, N_c, D_c):
"""
Chinchilla 论文的参数化形式
L(N, D) = E + A/N^α + B/D^β
其中 A = N_c^α, B = D_c^β
"""
return E + (N_c / N) ** alpha + (D_c / D) ** beta
# 实验设计:
# - 变化 N: 70M, 160M, 400M, 1B, 14B 等
# - 变化 D: 对应每个 N 训练不同步数
# - 总数据点:几百个 (N, D, L) 三元组
Chinchilla 论文采用的更精细策略:
Chinchilla 论文的三种拟合策略对比:
策略 1: 固定 D,变化 N → L(N)
策略 2: 固定 N,变化 D → L(D)
策略 3: Isoflop 曲线 → 找最优 (N, D)
策略 3 最可靠,因为:
- 直接在目标算力约束下优化
- 避免了外推到未探索区域
- N_opt(C) 和 D_opt(C) 可以直接读出
Scaling law 的拟合结果依赖于训练超参数:
| 超参数 | 影响 | 建议 |
|---|---|---|
| Learning rate | 太小→收敛慢,太大→噪声大 | 对每个 N 做 lr sweep |
| Batch size | 影响梯度噪声 | 随 N 增大而增大 |
| Training tokens | 太少→欠拟合 | 确保每个模型都收敛 |
| Architecture | 影响 α 和 N_c | 固定架构(只变宽度) |
| Warmup/cooldown | 影响训练稳定性 | 固定 schedule 形式 |
关键洞察:Scaling law 不是物理定律,而是在特定超参选择下的经验拟合。改变 optimizer 或 architecture,幂律指数 α 会变化。所以拟合时除了变 N 和 D,其他一切都要固定。
同一个 (N, D) 配置,多次训练可能得到不同的 loss:
# 估计噪声水平的实验:
# 固定 N=1B, D=20B,训练 5 次
seeds = [42, 123, 456, 789, 1024]
losses = []
for seed in seeds:
model = train(N=1e9, D=20e9, seed=seed)
losses.append(evaluate(model, val_set))
# 例: losses = [2.81, 2.79, 2.83, 2.80, 2.82]
# 均值 = 2.81, 标准差 = 0.015
# 这个噪声虽然小,但在拟合时不可忽略
给定拟合结果,如何量化不确定性?
from scipy.optimize import curve_fit
import numpy as np
def bootstrap_ci(N_data, loss_data, n_bootstrap=1000, alpha=0.05):
"""用 bootstrap 估计拟合参数的置信区间"""
params_list = []
n = len(N_data)
for _ in range(n_bootstrap):
# 有放回抽样
indices = np.random.choice(n, size=n, replace=True)
N_boot = N_data[indices]
loss_boot = loss_data[indices]
try:
popt, _ = curve_fit(scaling_law, N_boot, loss_boot,
p0=[1e5, 0.1, 2.0])
params_list.append(popt)
except RuntimeError:
continue # 拟合失败则跳过
params_array = np.array(params_list)
# 计算每个参数的置信区间
lower = np.percentile(params_array, 100 * alpha/2, axis=0)
upper = np.percentile(params_array, 100 * (1 - alpha/2), axis=0)
return lower, upper
外推(预测未训练过的尺度)的不确定性会指数增长:
| 外推距离 | 不确定性 | 风险 |
|---|---|---|
| 1 倍(拟合范围内) | 低 | 安全 |
| 3 倍 | 中等 | 可用 |
| 10 倍 | 高 | 谨慎 |
| 100 倍 | 极高 | 不可靠 |
实际案例:Chinchilla 用 70M-16B 范围的数据拟合,预测 70B 最优配置。这是 ~4 倍外推,尚可接受。但如果用 1M-1M 数据预测 1T 模型,那是 10^6 倍外推——完全不可靠。
幂律假设 L ∝ N^{-α} 在所有尺度成立。但实际上:
# 检测幂律弯曲的方法:
# 1. 拟合后看残差(residuals)
# 2. 如果残差有系统性模式 → 幂律不完全成立
predicted = scaling_law(N_values, *popt)
residuals = losses - predicted
# 如果残差在某个 N 范围内系统性为正或负
# 说明该区域的实际 α 与拟合值不同
训练 70B 模型需要数百万美元。我们不可能训练 100 个不同大小的 70B 模型来找最优配置。所以必须:
| 风险 | 原因 | 后果 |
|---|---|---|
| 幂律弯曲 | 大尺度时 α 变化 | 预测的配置不是真正的最优 |
| 架构差异 | 小模型最优架构 ≠ 大模型最优 | 拟合的 N_c 不适用于大模型 |
| 优化困难 | 大模型训练更不稳定 | 实际 loss 高于预测 |
| 数据质量 | 大数据集质量可能下降 | 预测的 D 不够 |
好的实验设计:
- 训练 10+ 个模型
- 覆盖至少 2 个数量级的 N(如 1M 到 1B)
- 每个 N 尝试多个 D
- 外推距离控制在 10 倍以内
不好的实验设计:
- 只训练 3-4 个模型
- 只覆盖 1 个数量级
- 外推到 100 倍
不要只给一个点估计,给出置信区间:
例:预测 70B 模型的最优 D
点估计: D_opt = 1.4T tokens
95% 置信区间: [1.0T, 2.0T]
实际决策:选择区间的保守端
因为 undertraining 比 overtraining 代价更高
(overtraining 只是多花训练钱,undertraining 是模型能力不足)
在拟合范围和外推目标之间,训练一个中等规模模型验证:
拟合范围: 1M - 1B 参数
外推目标: 70B 参数
中间验证: 训练一个 7B 模型,检查预测是否准确
如果 7B 的预测准确 → 外推到 70B 更可信
如果 7B 有偏差 → 修正模型后再外推
这是 Chinchilla 论文解决的问题:
已知: 总 FLOPs = C = 6ND
目标: 最小化 L(N, D)
方法: 用拟合的 L(N, D) 代入约束,求最优
如果 L(N) = (N_c/N)^α_N, L(D) = (D_c/D)^α_D
且 C = 6ND → D = C/(6N)
代入: L(N) = (N_c/N)^α_N + (6N D_c/C)^α_D
对 N 求导 = 0,得最优条件:
α_N × N_c^α_N × N^(-α_N-1) = α_D × (D_c × 6 / C)^α_D × α_D × N
化简: N_opt ∝ C^(α_N / (α_N + α_D))
D_opt ∝ C^(α_D / (α_N + α_D))
Chinchilla 的拟合结果:
α_N ≈ 0.46, α_D ≈ 0.54 (近似)
所以 N_opt ∝ C^0.46, D_opt ∝ C^0.54
比例: D_opt / N_opt ≈ 20
Chinchilla 只优化了训练 loss,但实际部署时推理成本也很重要:
推理成本 ∝ N(每个 token 的计算量)
总成本 = 训练成本 + 推理成本 × 推理次数
训练成本 ∝ 6ND
推理成本 ∝ N × num_inferences
关键洞察:
如果推理次数 >> 训练次数 → 推理成本主导
→ 应该用更小的 N(推理更便宜),用更多的 D 补偿
Llama 2 的实际选择:
- 训练成本 ≈ 数百万美元
- 推理次数 ≈ 数十亿次
→ 推理成本 >> 训练成本
→ 选择 D ≈ 27.5N(而非 Chinchilla 的 20N)
| 场景 | 最优 D/N | 原因 |
|---|---|---|
| 只关心训练损失 | 20N | Chinchilla 最优 |
| 推理次数多 | 25-40N | 推理成本主导,选更小 N |
| 推理次数极少 | 10-15N | 训练成本主导,选更大 N |
实际约束往往是时间 + 硬件,而非 FLOPs:
已知约束:
- T 天时间
- K 张 GPU(每张峰值算力 P FLOP/s)
- MFU(模型算力利用率,通常 0.3-0.5)
- 每张 GPU 内存 M GB
总 FLOPs = K × P × MFU × T × 86400
所以: 6ND = K × P × MFU × T × 86400
内存约束: 模型大小 + optimizer states ≤ K × M
对于 AdamW: 内存 ≈ 12N bytes (fp32 param + m + v)
所以: N ≤ K × M / 12
求解满足两个约束的 (N, D):
先由内存约束确定 N_max
然后 D = C / (6 × N_max)
实际决策需要同时考虑多个目标:
目标函数可以扩展为:
min L(N, D) + λ × inference_cost(N)
或者更复杂的形式:
min α × training_cost(N, D) + β × inference_cost(N) + γ × L(N, D)
其中 α, β, γ 是根据业务需求设定的权重
例:一个 API 服务
- 训练成本: 100 万美元(一次性)
- 推理成本: 0.002 美元/1K tokens
- 日活: 10M 用户 × 1000 tokens = 10B tokens/天
- 推理日成本: 20,000 美元/天
→ 推理成本在 ~50 天内超过训练成本
→ 应该选择更小 N + 更多 D
Chinchilla 假设所有 token 等价——每个 token 对降低 loss 的贡献相同。但这个假设在现实中不成立:
| 假设 | 现实 | 影响 |
|---|---|---|
| 所有 token 等价 | 高质量 token 价值 >> 低质量 | 实际最优 D/N 可能更低 |
| Token 独立 | 重复 token 边际价值递减 | 去重后需要的 D 更少 |
| 数据无限 | 高质量数据有限 | 可能需要在低质量数据上 overtrain |
| 幂律在所有尺度成立 | 大尺度可能弯曲 | 外推可能高估大模型收益 |
DCLM (DataComp-LM) 等研究表明,经过严格质量过滤的数据上训练,可以用更少的 token 达到相同 loss:
例:在原始 Common Crawl 上
7B 模型 + 140B tokens → loss = 2.5
在 DCLM(高质量过滤)上
7B 模型 + 50B tokens → loss = 2.5
结论: 高质量数据打破了 D=20N 的比例
实际最优 D/N 取决于数据质量
有时故意 overtrain(D > 20N)也有好处:
关键洞察:Chinchilla 的 D=20N 是在特定数据质量和特定目标(最小化验证 loss)下的最优。实际决策需要考虑数据质量、推理成本、下游任务需求等多个因素。
Overtrain 过多也有代价:
现代 LLM 训练的数据策略(综合 Chinchilla 和后续研究):
1. 预训练阶段
- 使用 D ≈ 20-30N tokens
- 优先使用高质量数据
- 后期混入更多低质量数据(课程学习)
2. Mid-training(可选)
- 切换到更高质量/更相关的数据
- 训练额外的 5-10% tokens
- 用于增强特定能力(长上下文、推理)
3. Post-training
- SFT: 数万到数十万个高质量样本
- RLHF/DPO: 数千到数万个偏好对
# 确定要变化的范围
# - N: 至少 10 个值,覆盖 2-3 个数量级
# - D: 每个 N 至少 3 个值(不同训练步数)
# - 其他超参:全部固定
model_sizes = [1e6, 3e6, 1e7, 3e7, 1e8, 3e8, 1e9, 3e9]
tokens_per_size = {
1e6: [2e7, 5e7, 1e8],
1e7: [2e8, 5e8, 1e9],
# ... 每个 N 对应多个 D
}
# 总实验数: 8 × 3 = 24 个模型
# 如果每个模型训练成本 100 美元 → 总成本 2400 美元
# 这比训练一个 70B 模型便宜得多
import json
results = []
for N in model_sizes:
for D in tokens_per_size[N]:
# 训练模型
model = train_model(N=N, D=D, seed=42)
# 记录结果
results.append({
"N": N,
"D": D,
"val_loss": evaluate(model, val_set),
"train_loss": evaluate(model, train_set),
"training_time": get_training_time(),
})
# 保存结果
with open("scaling_results.json", "w") as f:
json.dump(results, f, indent=2)
# 加载数据
results = json.load(open("scaling_results.json"))
N_data = np.array([r["N"] for r in results])
D_data = np.array([r["D"] for r in results])
loss_data = np.array([r["val_loss"] for r in results])
# 拟合联合模型
def joint_law(N, D, E, alpha, beta, A, B):
return E + A * N ** (-alpha) + B * D ** (-beta)
popt, pcov = curve_fit(
lambda x, E, alpha, beta, A, B: joint_law(x[0], x[1], E, alpha, beta, A, B),
(N_data, D_data),
loss_data,
p0=[2.0, 0.1, 0.1, 10.0, 10.0]
)
# 检查拟合质量
predicted = joint_law(N_data, D_data, *popt)
r_squared = 1 - np.sum((loss_data - predicted)**2) / np.sum((loss_data - loss_data.mean())**2)
print(f"R² = {r_squared:.4f}") # 希望 > 0.99
from scipy.optimize import minimize
C_target = 6 * 70e9 * 1.4e12 # 目标: 70B 模型, Chinchilla 推荐 1.4T tokens
def loss_at_optimal_D(N):
"""给定 N,计算最优 D 和对应 loss"""
D_opt = C_target / (6 * N) # 满足算力约束
return joint_law(N, D_opt, *popt)
# 在合理范围内搜索最优 N
result = minimize(loss_at_optimal_D, x0=[70e9], bounds=[(1e9, 200e9)])
N_optimal = result.x[0]
D_optimal = C_target / (6 * N_optimal)
print(f"最优配置: N = {N_optimal:.2e}, D = {D_optimal:.2e}")
print(f"预测 loss: {result.fun:.4f}")
验证 loss 是连续、平滑的,但下游能力可能是"涌现"的——在某个尺度突然变得可用。Scaling law 无法预测这种相变。
拟合的 α 和 N_c 是针对特定架构的。如果改变 attention 类型(如 GQA)、使用 MoE、或改变激活函数,整个 scaling law 会变化。
Chinchilla 的 70B 模型训练成本低于 GPT-3 175B,但推理成本更高(更多 tokens 需要处理)。对于推理密集型应用,Chinchilla 配置可能不是最优。
Scaling law 假设我们可以获得任意多的高质量数据。但现实中高质量数据有限,低质量数据可能引入噪声或偏见。
只优化验证 loss,但实际关心的是下游任务表现(MMLU、HumanEval 等)。Loss 和下游任务之间有关联但不是完美相关。
问题 1:对幂律 L = (N_c / N)^α 取对数后得到什么形式?
问题 2:如果你用 1M-1B 参数范围的数据拟合 scaling law,预测 1T 参数模型,外推距离是多少?
问题 3:如果一个模型会被调用数十亿次做推理,应该如何调整 D/N 比例?
问题 4:如果数据质量极高(如全部是教科书级别的数据),Chinchilla 的 D=20N 比例会如何变化?
问题 5:如何用 Bootstrap 方法估计 scaling law 拟合的不确定性?
小模型 residual 全为正、大模型全为负,但整体平均为零。Fit 是否可信?
答案:不可信;残差随规模有系统结构,说明函数形式或 regime 假设错误。
若只看均值,你把“误差互相抵消”错当成了“误差是无结构噪声”。
Scaling law 预测的是某个测量量;下一课追问这个量是否真的代表我们关心的能力,以及 tokenizer、prompt、judge 和 contamination 如何改变测量。
对数线性回归简单但不稳健;NLLS 更可靠;Isoflop 曲线最精确
不确定性随距离指数增长;保守预测 + 中间验证
算力 + 内存 + 推理成本联合优化,不是单目标
数据质量打破 token 等价假设;overtrain 有时合理