09 - Scaling Laws 与训练资源规划¶
学习目标:理解大模型缩放定律的核心公式,掌握计算最优训练策略,学会训练资源估算与规划。 前置知识:大模型预训练基础(03-大模型预训练)、训练基础设施(02-训练基础设施)
目录¶
- Scaling Laws 概述
- Kaplan Scaling Laws (2020)
- Chinchilla Scaling Laws (2022)
- 计算最优训练策略
- 训练资源估算
- 前沿 Scaling 研究与争议
1. Scaling Laws 概述¶
1.1 什么是 Scaling Laws¶
Scaling Laws(缩放定律)描述了模型性能如何随模型参数量 (N)、训练数据量 (D) 和计算预算 (C) 的变化而变化。这些经验规律为训练大模型提供了科学指导。
graph TD
A[Scaling Laws 三要素] --> B[模型参数量 N]
A --> C[训练数据量 D<br/>token 数]
A --> D[计算预算 C<br/>FLOPs]
B --> E[交叉熵损失 L]
C --> E
D --> E 1.2 为什么 Scaling Laws 重要¶
| 用途 | 说明 |
|---|---|
| 训练预算规划 | 预估需要多少 GPU、多长时间 |
| 模型规模决策 | 给定预算下选择最优模型大小 |
| 数据量决策 | 确定需要多少训练 token |
| 小规模预测 | 用小模型实验预测大模型性能 |
| 架构对比 | 不同架构的 scaling 效率 |
2. Kaplan Scaling Laws (2020)¶
2.1 核心发现¶
OpenAI 的 Kaplan 等人在 2020 年发现,语言模型的交叉熵损失与三个因素呈幂律关系:
其中: - \(L\) 是交叉熵损失 - \(N\) 是模型参数量(不含 embedding) - \(D\) 是训练数据量(token 数) - \(C\) 是总计算量(FLOPs) - \(N_c, D_c, C_c\) 是常数 - \(\alpha_N \approx 0.076\), \(\alpha_D \approx 0.095\), \(\alpha_C \approx 0.050\)
2.2 关键结论¶
import numpy as np
import matplotlib.pyplot as plt
# Kaplan Scaling Laws 参数
N_c = 8.8e13 # 参数量常数
D_c = 5.4e13 # 数据量常数
alpha_N = 0.076
alpha_D = 0.095
def kaplan_loss_N(N):
"""模型参数量与损失的关系"""
return (N_c / N) ** alpha_N
def kaplan_loss_D(D):
"""数据量与损失的关系"""
return (D_c / D) ** alpha_D
# 可视化
Ns = np.logspace(7, 12, 100) # 10M 到 1T 参数
Ds = np.logspace(9, 13, 100) # 1B 到 10T tokens
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.loglog(Ns, kaplan_loss_N(Ns))
ax1.set_xlabel('Parameters (N)')
ax1.set_ylabel('Loss L(N)')
ax1.set_title('Kaplan: Loss vs Model Size')
ax1.grid(True, alpha=0.3)
ax2.loglog(Ds, kaplan_loss_D(Ds))
ax2.set_xlabel('Data (D tokens)')
ax2.set_ylabel('Loss L(D)')
ax2.set_title('Kaplan: Loss vs Data Size')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('scaling_laws_kaplan.png', dpi=150)
2.3 Kaplan 的核心观点¶
"更大的模型更高效" — 在固定计算预算下,训练一个更大的模型但训练步数更少,比训练一个小模型更多步数更有效。
这意味着:优先增大模型规模,而非训练更多数据。这个结论后来被 Chinchilla 推翻。
3. Chinchilla Scaling Laws (2022)¶
3.1 Chinchilla 的修正¶
DeepMind 的 Hoffmann 等人在 2022 年重新审视了 Scaling Laws,发现 Kaplan 的结论有偏差:
其中: - \(E\) 是不可约损失(数据的固有熵) - \(\alpha \approx 0.34\), \(\beta \approx 0.28\) - \(A \approx 406.4\), \(B \approx 410.7\)
3.2 Chinchilla 的核心结论¶
import numpy as np
# Chinchilla 参数
E = 1.69 # 不可约损失
A = 406.4
B = 410.7
alpha = 0.34
beta = 0.28
def chinchilla_loss(N, D):
"""Chinchilla 缩放定律"""
return A / N**alpha + B / D**beta + E
# 计算最优分配:给定计算预算 C,求最优 N 和 D
# C ≈ 6 * N * D (训练 FLOPs)
def optimal_allocation(C):
"""给定计算预算,求最优参数量和数据量"""
# Chinchilla 推导的最优解
# N_opt = G * C^a
# D_opt = G^{-1} * C^b
a = alpha / (alpha + beta) # ≈ 0.548
b = beta / (alpha + beta) # ≈ 0.452
G = (alpha * A / beta / B) ** (1 / (alpha + beta))
N_opt = G * (C / 6) ** a
D_opt = G**(-1) * (C / 6) ** b
return N_opt, D_opt
# 不同计算预算下的最优分配
budgets = {
"GPT-2 (1.5B)": 1.5e9 * 1e9 * 6, # ~9e18 FLOPs
"GPT-3 (175B)": 175e9 * 300e9 * 6, # ~3.15e23 FLOPs
"Chinchilla (70B)": 70e9 * 1.4e12 * 6, # ~5.88e23 FLOPs
}
print("=" * 60)
print(f"{'模型':<25} {'计算量 (FLOPs)':<20} {'最优N':<12} {'最优D':<12}")
print("=" * 60)
for name, C in budgets.items():
N_opt, D_opt = optimal_allocation(C)
print(f"{name:<25} {C:<20.2e} {N_opt/1e9:<12.1f}B {D_opt/1e9:<12.1f}B tokens")
3.3 Kaplan vs Chinchilla 对比¶
| 维度 | Kaplan (2020) | Chinchilla (2022) |
|---|---|---|
| 最优策略 | 更大的模型 | 模型和数据等比例增长 |
| N:D 比例 | N >> D | N ≈ D/20 |
| GPT-3 策略 | 175B 参数, 300B tokens | 应该用 ~70B 参数, 1.4T tokens |
| 核心观点 | 模型优先 | 数据同等重要 |
| 实验基础 | 有限数据量 | 更大范围数据量 |
3.4 Chinchilla 实验验证¶
# Chinchilla 的关键实验结果
results = {
"Gopher (280B, 300B tokens)": {"loss": 1.91, "params": "280B"},
"Chinchilla (70B, 1.4T tokens)": {"loss": 1.79, "params": "70B"},
}
# Chinchilla 用 1/4 的参数量,但 4.7x 的数据量,获得了更低的损失
# 证明:在相同计算预算下,更多数据 + 适中模型 > 更大模型 + 更少数据
print("Chinchilla 实验结论:")
print(f" Gopher: 280B 参数 × 300B tokens = 损失 1.91")
print(f" Chinchilla: 70B 参数 × 1.4T tokens = 损失 1.79")
print(f" → 用更小的模型但更多数据,效果更好!")
4. 计算最优训练策略¶
4.1 FLOPs 估算¶
def estimate_training_flops(
num_params: float,
num_tokens: float,
forward_flops_per_param: float = 2.0,
backward_flops_per_param: float = 4.0,
) -> float:
"""估算训练总 FLOPs
前向传播:约 2 * N * D FLOPs
反向传播:约 4 * N * D FLOPs
总计:约 6 * N * D FLOPs
Args:
num_params: 模型参数量
num_tokens: 训练 token 总数
forward_flops_per_param: 每个参数每 token 的前向 FLOPs
backward_flops_per_param: 每个参数每 token 的反向 FLOPs
"""
flops_per_token = num_params * (forward_flops_per_param + backward_flops_per_param)
total_flops = flops_per_token * num_tokens
return total_flops
# 示例:估算 LLaMA-2 7B 的训练 FLOPs
N = 7e9 # 7B 参数
D = 2e12 # 2T tokens
C = estimate_training_flops(N, D)
print(f"LLaMA-2 7B 训练 FLOPs: {C:.2e}")
# ≈ 6 * 7e9 * 2e12 = 8.4e22 FLOPs
4.2 GPU 时间估算¶
def estimate_training_time(
num_params: float,
num_tokens: float,
gpu_type: str = "A100",
num_gpus: int = 1,
model_flops_utilization: float = 0.5,
) -> dict:
"""估算训练时间
Args:
num_params: 模型参数量
num_tokens: 训练 token 总数
gpu_type: GPU 型号
num_gpus: GPU 数量
model_flops_utilization: MFU(模型 FLOPs 利用率)
"""
# GPU 理论算力 (BF16 TFLOPS)
gpu_flops = {
"A100-40GB": 312e12,
"A100-80GB": 312e12,
"H100-SXM": 990e12,
"H200": 990e12,
"B200": 2250e12, # 估计值
"V100-32GB": 125e12,
"RTX4090": 165e12,
}
peak_flops = gpu_flops.get(gpu_type, 312e12) # 修复:gpu_fpus → gpu_flops
effective_flops = peak_flops * model_flops_utilization * num_gpus
total_flops = estimate_training_flops(num_params, num_tokens)
time_seconds = total_flops / effective_flops
return {
"total_flops": total_flops,
"effective_flops_per_sec": effective_flops,
"time_seconds": time_seconds,
"time_hours": time_seconds / 3600,
"time_days": time_seconds / 86400,
"gpu_type": gpu_type,
"num_gpus": num_gpus,
"mfu": model_flops_utilization,
}
# 示例:LLaMA-2 70B 训练时间估算
result = estimate_training_time(
num_params=70e9,
num_tokens=1.4e12,
gpu_type="A100-80GB",
num_gpus=2048,
model_flops_utilization=0.45,
)
print(f"LLaMA-2 70B 训练估算:")
print(f" 总 FLOPs: {result['total_flops']:.2e}")
print(f" GPU: {result['num_gpus']} × {result['gpu_type']}")
print(f" MFU: {result['mfu']:.0%}")
print(f" 预计时间: {result['time_days']:.1f} 天 ({result['time_hours']:.0f} 小时)")
4.3 显存估算¶
def estimate_memory(
num_params: float,
precision_bytes: int = 2, # BF16 = 2 bytes
optimizer_type: str = "adam",
batch_size: int = 1,
seq_length: int = 4096,
hidden_dim: int = 4096,
num_layers: int = 32,
activation_memory_factor: float = 2.0,
) -> dict:
"""估算 GPU 显存需求
Args:
num_params: 模型参数量
precision_bytes: 每个参数的字节数
optimizer_type: 优化器类型
"""
# 模型参数
param_memory = num_params * precision_bytes
# 优化器状态
if optimizer_type == "adam":
# Adam: momentum (FP32) + variance (FP32) + master params (FP32)
optimizer_memory = num_params * 4 * 2 + num_params * 4 # = 12 bytes/param
elif optimizer_type == "adamw":
optimizer_memory = num_params * 12 # 同 Adam
elif optimizer_type == "sgd":
optimizer_memory = num_params * 4 # 只需 momentum
else:
optimizer_memory = num_params * 12
# 梯度
gradient_memory = num_params * precision_bytes
# 激活值(粗略估计)
activation_memory = (
batch_size * seq_length * hidden_dim * num_layers
* precision_bytes * activation_memory_factor
)
total = param_memory + optimizer_memory + gradient_memory + activation_memory
return {
"param_memory_GB": param_memory / 1e9,
"optimizer_memory_GB": optimizer_memory / 1e9,
"gradient_memory_GB": gradient_memory / 1e9,
"activation_memory_GB": activation_memory / 1e9,
"total_memory_GB": total / 1e9,
"recommended_GPU_memory_GB": total / 1e9 * 1.2, # 20% 余量
}
# 示例:LLaMA-2 7B 显存估算
mem = estimate_memory(
num_params=7e9,
precision_bytes=2, # BF16
optimizer_type="adamw",
batch_size=4,
seq_length=4096,
hidden_dim=4096,
num_layers=32,
)
print("LLaMA-2 7B 显存估算:")
for k, v in mem.items():
print(f" {k}: {v:.1f} GB")
4.4 最优模型规模选择¶
def compute_optimal_model_size(compute_budget_flops: float) -> dict:
"""Chinchilla 最优模型规模
给定计算预算,返回最优参数量和数据量
"""
# Chinchilla 最优比例: N ∝ C^0.5, D ∝ C^0.5
# 具体系数来自 Hoffmann et al. 2022
# N_opt ≈ 0.06 * C^0.5 (参数)
# D_opt ≈ C / (6 * N_opt) (tokens)
N_opt = 0.06 * compute_budget_flops**0.5
D_opt = compute_budget_flops / (6 * N_opt)
return {
"compute_budget_flops": compute_budget_flops,
"optimal_params": N_opt,
"optimal_tokens": D_opt,
"tokens_per_param": D_opt / N_opt,
}
# 不同预算下的最优规模
print("=" * 70)
print(f"{'计算预算 (FLOPs)':<20} {'最优参数量':<15} {'最优token数':<15} {'D/N比':<10}")
print("=" * 70)
budgets = [1e20, 1e21, 1e22, 1e23, 1e24, 1e25]
for C in budgets:
result = compute_optimal_model_size(C)
N = result['optimal_params']
D = result['optimal_tokens']
print(f"{C:<20.0e} {N/1e9:<15.1f}B {D/1e9:<15.1f}B {D/N:<10.0f}")
5. 训练资源规划¶
5.1 训练规划决策树¶
graph TD
A[确定训练目标] --> B{可用 GPU 显存}
B -->|≤ 24GB| C[小模型 ≤ 7B<br/>使用 LoRA/QLoRA]
B -->|≤ 80GB| D[中等模型 ≤ 30B<br/>使用 DeepSpeed ZeRO-2]
B -->|≥ 80GB × N| E[大模型 ≤ 70B+<br/>使用 3D 并行]
C --> F[确定数据量<br/>Chinchilla: 20× 参数量]
D --> F
E --> F
F --> G[估算训练时间<br/>FLOPs / GPU算力 / MFU]
G --> H[确定训练方案] 5.2 常见模型训练配置参考¶
| 模型 | 参数量 | 训练 Token | GPU 配置 | 训练时间 | 估算成本 |
|---|---|---|---|---|---|
| GPT-2 | 1.5B | 40B | 8× V100 | ~1 天 | ~$100 |
| GPT-3 | 175B | 300B | 1024× A100 | ~34 天 | ~$4.6M |
| Chinchilla | 70B | 1.4T | 2048× TPUv4 | ~30 天 | ~$5M |
| LLaMA-2 7B | 7B | 2T | 64× A100 | ~7 天 | ~$50K |
| LLaMA-2 70B | 70B | 1.4T | 2048× A100 | ~21 天 | ~$2M |
| LLaMA-3 8B | 8B | 15T | 8192× H100 | ~30 天 | ~$5M |
5.3 完整训练规划模板¶
class TrainingPlanner:
"""大模型训练资源规划器"""
GPU_SPECS = {
"A100-80GB": {"memory": 80, "bf16_tflops": 312, "cost_per_hour": 1.5},
"H100-SXM": {"memory": 80, "bf16_tflops": 990, "cost_per_hour": 3.0},
"H200": {"memory": 141, "bf16_tflops": 990, "cost_per_hour": 4.0},
"B200": {"memory": 192, "bf16_tflops": 2250, "cost_per_hour": 6.0},
}
def __init__(self, num_params_B: float, num_tokens_T: float):
self.N = num_params_B * 1e9
self.D = num_tokens_T * 1e12
def plan(self, gpu_type: str = "A100-80GB", mfu: float = 0.45,
max_gpus: int = 2048) -> dict:
"""生成训练计划"""
gpu = self.GPU_SPECS[gpu_type]
# 总 FLOPs
total_flops = 6 * self.N * self.D
# 单 GPU 有效算力
effective_flops_per_gpu = gpu["bf16_tflops"] * 1e12 * mfu
# 最少 GPU 数(基于显存)
model_memory_GB = self.N * 2 / 1e9 # BF16 参数
min_gpus_memory = max(1, int(model_memory_GB / gpu["memory"] * 3)) # 3x for optimizer
# 使用 GPU 数
num_gpus = min(max(min_gpus_memory, 8), max_gpus)
# 训练时间
total_effective_flops = effective_flops_per_gpu * num_gpus
time_seconds = total_flops / total_effective_flops
# 成本估算
cost = time_seconds / 3600 * gpu["cost_per_hour"] * num_gpus
return {
"model_params": f"{self.N/1e9:.1f}B",
"training_tokens": f"{self.D/1e12:.1f}T",
"total_flops": f"{total_flops:.2e}",
"gpu_type": gpu_type,
"num_gpus": num_gpus,
"mfu": f"{mfu:.0%}",
"training_days": time_seconds / 86400,
"estimated_cost_usd": cost,
"tokens_per_param": self.D / self.N,
}
# 使用示例
planner = TrainingPlanner(num_params_B=7, num_tokens_T=2)
plan = planner.plan(gpu_type="A100-80GB", mfu=0.45, max_gpus=64)
print(f"训练计划:")
for k, v in plan.items():
print(f" {k}: {v}")
6. 前沿 Scaling 研究与争议¶
6.1 Beyond Chinchilla¶
| 研究 | 年份 | 核心发现 |
|---|---|---|
| Kaplan | 2020 | 幂律关系,模型优先 |
| Chinchilla | 2022 | 数据同等重要,N ∝ D |
| LLaMA-3 | 2024 | 过度训练(over-training)在推理时更高效 |
| DeepSeek | 2024 | MoE 架构打破传统 scaling 规律 |
| MiniCPM | 2024 | 小模型通过密集数据可以超越大模型 |
6.2 过度训练(Over-training)¶
LLaMA-3 的策略与 Chinchilla 相反:用远超最优比例的数据训练模型。
# Chinchilla 最优: D ≈ 20 * N
# LLaMA-3 实际: D ≈ 1875 * N (8B 模型训练 15T tokens)
# 为什么过度训练?
# 1. 推理成本 >> 训练成本(模型被部署后服务大量请求)
# 2. 更小的模型推理更快、更便宜
# 3. 用训练成本换取推理效率
# 过度训练的 trade-off
print("过度训练策略对比:")
print(f" Chinchilla 最优: 70B × 1.4T tokens → 推理成本高")
print(f" LLaMA-3 策略: 8B × 15T tokens → 推理成本低,性能接近")
print(f" → 当模型需要长期部署服务时,过度训练更经济")
6.3 MoE 的 Scaling 特性¶
# MoE (Mixture of Experts) 改变了传统 Scaling Laws
# DeepSeek-V3: 671B 总参数, 37B 激活参数
# 传统 Dense 模型: FLOPs = 6 * N_total * D
# MoE 模型: FLOPs = 6 * N_active * D (只计算激活参数)
# 这意味着 MoE 可以用更少的计算获得更大的模型容量
# Scaling Laws 需要针对 MoE 重新校准
moe_comparison = {
"DeepSeek-V3": {
"total_params": "671B",
"active_params": "37B",
"training_tokens": "14.8T",
"effective_flops": "6 * 37B * 14.8T", # 等效于 37B dense 模型
"performance": "接近 GPT-4 水平",
},
"LLaMA-3 70B": {
"total_params": "70B",
"active_params": "70B",
"training_tokens": "15T",
"effective_flops": "6 * 70B * 15T",
"performance": "接近但略低于 DeepSeek-V3",
},
}
6.4 μP(Maximal Update Parameterization)¶
参考来源:本节内容参考了 diy-llm 第九章( Scaling Laws )对 μP 的详细分析,以及 Yang et al. (2022) 的原始论文。
μP( Maximal Update Parameterization ,最大更新参数化)是一种超参数迁移方法,允许在小模型上搜索最优超参数,然后直接迁移到大模型上使用。
核心问题:传统方法中,模型规模变化时,最优学习率、初始化方差等超参数也需要重新调整。μP 通过理论分析确定了超参数应该如何随模型规模缩放。
标准参数化(SP)vs 最大更新参数化(μP)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
标准参数化(Standard Parameterization):
├── 所有层的初始化方差相同
├── 所有层使用相同的学习率
├── 问题:模型规模变化时,训练动态不稳定
└── 大模型需要昂贵的超参数搜索
μP 的核心思想:
├── 不同层的参数应该有不同的缩放规则
├── 嵌入层:学习率 ∝ 1/√d(d 为隐藏层维度)
├── 隐藏层:学习率 ∝ 1/d
├── 输出层:学习率 ∝ 1/√d
└── 初始化方差也按相应规则缩放
μP 的优势:
├── 小模型上搜索的超参数可直接迁移到大模型
├── 训练曲线更稳定、更可预测
├── 显著降低大模型训练的超参数调优成本
└── MiniCPM 等模型验证了 μP 的有效性
# μP 参数化实现示例
def get_mup_scaling_rules(hidden_size, base_hidden_size=256):
"""
计算 μP 的缩放规则
Args:
hidden_size: 目标模型的隐藏层维度
base_hidden_size: 基准小模型的隐藏层维度
"""
import math
# 宽度缩放因子
width_ratio = hidden_size / base_hidden_size
scaling_rules = {
"嵌入层": {
"学习率缩放": f"1/√d → {1/math.sqrt(width_ratio):.4f}x",
"初始化缩放": "标准正态(不变)",
},
"隐藏层(Attention/FFN)": {
"学习率缩放": f"1/d → {1/width_ratio:.4f}x",
"初始化缩放": f"1/√d → {1/math.sqrt(width_ratio):.4f}x",
},
"输出层(LM Head)": {
"学习率缩放": f"1/√d → {1/math.sqrt(width_ratio):.4f}x",
"初始化缩放": "标准正态(不变)",
},
"残差连接": {
"缩放因子": f"1/√(num_layers) → 随深度调整",
},
}
return scaling_rules
# 示例:从 256 维小模型迁移到 4096 维大模型
rules = get_mup_scaling_rules(hidden_size=4096, base_hidden_size=256)
print("μP 缩放规则(256 → 4096):")
for layer, rule in rules.items():
print(f"\n {layer}:")
for key, value in rule.items():
print(f" {key}: {value}")
MiniCPM 的 μP 实践验证(参考 diy-llm 第九章)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 在小模型(约 100M 参数)上进行随机超参数搜索
2. 使用 μP 将最优超参数迁移到 1.2B/2.4B 模型
3. 结果:
├── 训练损失曲线更稳定
├── 不同规模模型的缩放曲线更可预测
├── 学习率无需在大模型上重新搜索
└── MiniCPM 在同等参数规模下超越其他开源模型
使用 mup 库实现 μP:
# pip install mup
import mup
import torch.nn as nn
# 1. 定义基础模型(小模型,如 hidden_size=256)
class TransformerBlock(nn.Module):
def __init__(self, hidden_size, n_heads):
super().__init__()
self.attn = nn.MultiheadAttention(hidden_size, n_heads)
self.ffn = nn.Sequential(
nn.Linear(hidden_size, 4 * hidden_size),
nn.GELU(),
nn.Linear(4 * hidden_size, hidden_size),
)
self.norm = nn.LayerNorm(hidden_size)
# 2. 使用 mup 初始化
base_dim = 256
target_dim = 4096
base_model = TransformerBlock(base_dim, n_heads=4)
delta_model = TransformerBlock(target_dim, n_heads=32)
# mup 会自动处理初始化缩放和学习率缩放
mup.set_base_shapes(delta_model, base_model)
# 3. 使用 mup 的 MuReadiness 检查器验证
# 确保 μP 参数化正确应用
💡 μP 实践建议:对于小规模实验(<1B),μP 的收益有限;但在训练 >10B 模型时,μP 可以节省数十次昂贵的超参数搜索,显著降低训练成本。Cohere、MiniCPM 等团队已在大规模训练中验证了其有效性。
6.5 数据墙(Data Wall)问题¶
# 高质量人类文本数据的总量估计
total_human_text_tokens = {
"Common Crawl (过滤后)": "~10T tokens",
"书籍": "~1T tokens",
"学术论文": "~500B tokens",
"代码 (GitHub)": "~500B tokens",
"维基百科": "~10B tokens",
"合计高质量数据": "~15-20T tokens",
}
# 按当前最大模型的训练速度,高质量数据可能在 2026-2027 年耗尽
# 解决方案:
solutions = [
"1. 合成数据(用强模型生成训练数据)",
"2. 多模态数据(图像/视频/音频作为额外数据源)",
"3. 数据高效训练(更好的数据选择和课程学习)",
"4. 自我改进(模型生成数据 → 过滤 → 再训练)",
"5. RL from environment(从环境交互中学习)",
]
📝 本章小结¶
| 概念 | 核心公式/结论 |
|---|---|
| Kaplan Laws | \(L \propto N^{-0.076}\), 模型优先 |
| Chinchilla Laws | \(L = A/N^\alpha + B/D^\beta + E\), 数据同等重要 |
| 训练 FLOPs | \(C \approx 6ND\) |
| 最优分配 | \(N_{opt} \propto C^{0.5}\), \(D_{opt} \propto C^{0.5}\) |
| 过度训练 | 推理密集场景下用更多数据训练更小模型 |
| MoE Scaling | FLOPs 由激活参数决定,非总参数 |
🤔 思考题¶
💡 思考题参考解答
**Q1: LLaMA 系列为什么选择"过度训练"(Over-training)而非遵循 Chinchilla 最优?** Chinchilla 的最优是**训练计算量最优**(给定 FLOPs 预算,如何分配 N 和 D 使 loss 最低)。但 LLaMA 的考量是**推理成本最优**: - 一个模型训练一次,但推理数十亿次 - 训练一个更小的模型(如 7B)用更多数据(1T tokens),推理成本远低于训练 13B 模型用 0.5T tokens - LLaMA-2 7B 训练了 2T tokens(远超 Chinchilla 最优的 ~140B tokens),但在推理时比 13B 模型便宜近一半 **Q2: 如何用 Scaling Laws 估算"我的预算能训练多大的模型"?** 步骤: 1. 确定计算预算 $C$(GPU 数 × 训练天数 × 单卡算力) 2. Chinchilla 最优:$N_{opt} \approx 0.6 \times C^{0.5}$,$D_{opt} \approx 20 \times C^{0.5}$ 3. 验证显存是否足够($M \approx 20N$ bytes for Adam + 模型 + 梯度 + 激活值) 示例:8 × A100 (80GB) 训练 30 天 - 总 FLOPs:$8 \times 312 \text{TFLOPS} \times 30 \times 86400 \approx 6.5 \times 10^{21}$ FLOPs - $N_{opt} \approx 0.6 \times \sqrt{6.5 \times 10^{21}} \approx 4.8 \times 10^{10}$ ≈ 48B 参数 - $D_{opt} \approx 20 \times \sqrt{6.5 \times 10^{21}} \approx 1.6 \times 10^{12}$ ≈ 1.6T tokens - 但 48B 模型在 8 × A100 上可能显存不够(需要 ZeRO-3),实际可能选 13B + 更多数据 **Q3: MoE 模型的 Scaling Laws 与 Dense 模型有何不同?** 关键区别: - **FLOPs 由激活参数决定**:MoE 的总参数可能是 100B,但每次只激活 10B(top-2 路由),实际计算量与 10B Dense 模型相当 - **Scaling Laws 需要重新校准**:Chinchilla 的 $N$ 应替换为激活参数 $N_{active}$,但总参数 $N_{total}$ 影响显存 - **数据需求可能不同**:MoE 的专家需要足够数据来分化,可能需要比 Dense 模型更多的训练数据 - **通信开销**:All-to-All 通信是 MoE 的额外瓶颈,Dense 模型不需要考虑 **Q4: μP(Maximal Update Parameterization)解决了什么问题?** **问题**:传统参数初始化中,超参数(如学习率、初始化标准差)需要针对每个模型规模单独调优。小模型上找到的最优 lr 放到大模型上可能完全不对。 **μP 的解决方案**:通过数学分析,设计一种参数化方式,使得: - 在小模型(如 256 维)上调优的超参数可以直接迁移到大模型(如 4096 维) - 避免了大模型上昂贵的超参数搜索 - 训练初期的 loss 曲线在不同规模下表现一致 **实际价值**:DeepSeek-V3 等大模型训练已采用 μP,节省了大量调参成本。🔗 参考资料¶
- Scaling Laws for Neural Language Models (Kaplan et al., 2020)
- Training Compute-Optimal Large Language Models (Hoffmann et al., 2022) — Chinchilla
- LLaMA: Open and Efficient Foundation Language Models
- DeepSeek-V3 Technical Report
- diy-llm 第七章:Scaling Laws (Datawhale)
- Will we run out of data? (Villalobos et al., 2022)