在大模型时代,我们经常听到一句话:Scale is all you need。
当然,这句话有点夸张。模型架构、数据质量、训练方法、对齐方法、工具调用、RAG 系统都很重要。但如果回到 GPT-3、GPT-4 以及今天各种 Foundation Model 的技术路线,有一篇论文绕不开:
《Scaling Laws for Neural Language Models》
这篇论文由 Jared Kaplan、Sam McCandlish、Tom Henighan、Tom Brown、Alec Radford、Dario Amodei 等 OpenAI 研究者发表于 2020 年。它研究了语言模型的性能如何随着模型参数量 N、数据量 D、训练计算量 C 的变化而变化。论文最核心的发现是:语言模型的 cross-entropy loss 会随着模型规模、数据规模和训练 compute 呈现稳定的 power-law 幂律关系,而且部分趋势跨越了超过六到七个数量级。
这篇论文的意义不在于提出了一个新的 Transformer 结构,而在于它把大模型训练从"经验主义"推进到"可预测的工程规划"。
一、论文内容:这篇论文到底讲了什么?
论文研究的问题非常直接:
如果我们增加模型参数、增加训练数据、增加训练算力,语言模型的性能会如何变化?
作者主要研究的是 Transformer 语言模型,训练目标是自回归语言建模,也就是根据前面的 token 预测下一个 token。论文使用 cross-entropy loss 作为主要性能指标,并在 WebText2 数据集上进行大量实验。WebText2 是 WebText 的扩展版本,论文中提到它使用 BPE 分词,并在 1024-token context 上优化自回归 log-likelihood。
论文围绕三个核心变量展开:
| 符号 | 含义 |
|---|---|
| N | 模型参数量,不包含 vocabulary 和 positional embedding |
| D | 数据集大小,通常以 token 数计 |
| C | 训练计算量,即训练过程中消耗的 compute |
论文发现,当其他因素不构成瓶颈时,模型 loss 与这些变量之间存在平滑的幂律关系:
scss
L(N) ≈ A / N^α + E
L(D) ≈ A / D^α + E
L(C) ≈ A / C^α + E
也就是说,模型越大、数据越多、算力越多,loss 会下降,但不是线性下降,而是边际收益递减的幂律下降。论文明确指出,性能在模型大小、数据集大小和训练 compute 三个维度上都有 power-law 关系。
这意味着,大模型训练可以做预测:
先训练一批小模型,观察 loss 曲线,然后外推更大模型可能达到的 loss。
这正是这篇论文最重要的价值。
二、论文提出的创新点和关键技术
1. 发现语言模型性能的 Scaling Laws
这篇论文最核心的创新是发现了语言模型性能与规模变量之间的稳定幂律关系。
过去大家也知道"模型更大通常效果更好",但这种认知比较模糊。论文进一步证明:
在足够大的范围内,语言模型的 loss 可以被模型参数量、数据量和计算量较好地预测。
论文摘要中明确写到,loss 会随着 model size、dataset size 和 training compute 按 power-law 缩放,且一些趋势跨越超过七个数量级。
这个结论非常重要,因为训练大模型成本极高。如果每次都靠拍脑袋决定"训 7B 还是 13B,训 500B tokens 还是 1T tokens",风险巨大。而 scaling law 提供了一套实验方法:
- 先训练多个小规模模型;
- 记录不同规模下的 validation loss;
- 拟合幂律曲线;
- 预测更大模型的收益;
- 决定是否值得投入更多 compute。
这让大模型训练变成一个可以预算、可以外推、可以优化的工程问题。
2. 证明"规模"比很多架构细节更重要
论文一个很重要的结论是:
模型性能强依赖 scale,弱依赖 model shape。
这里的 scale 指的是:
- 模型参数量;
- 数据集大小;
- 训练 compute。
而 model shape 指的是:
- 网络宽度;
- 网络深度;
- attention heads 数量;
- 其他架构超参数。
论文总结中明确说,在合理范围内,性能对深度和宽度等架构超参数依赖很弱,而主要取决于规模。
这个结论对大模型发展影响很大。它并不是说架构不重要,而是说在 Transformer 这个框架下,如果你想显著提升语言模型性能,优先级通常是:
数据质量 / 数据规模 / 模型规模 / 训练 compute > 小幅结构微调
这也是为什么后来很多模型路线会优先追求更大参数量、更大训练语料、更大计算预算。
3. 大模型更加 sample-efficient
论文还有一个很反直觉的发现:
大模型比小模型更加 sample-efficient。
直觉上,很多人会认为:模型越大,参数越多,就越需要更多数据,否则容易过拟合。
但论文发现,在合理训练策略下,大模型达到同样 loss 所需的优化步数和数据点更少。论文摘要中也明确说,大模型显著更加 sample-efficient。
这带来一个非常重要的训练策略:
固定 compute budget 下,与其训练一个小模型直到收敛,不如训练一个更大的模型,并在明显未收敛前停止。
论文称之为 convergence is inefficient。在固定算力预算下,最优策略往往是训练非常大的模型,然后显著早停,而不是把小模型训练到完全收敛。
这对大模型训练范式影响很深。
传统机器学习里,我们经常追求"把模型训练到收敛"。
但这篇论文告诉我们,从 compute efficiency 角度看:
训练到收敛 ≠ 最划算
更大的模型虽然单步更贵,但学习效率更高,因此在固定 compute 下可能取得更低的 loss。
4. 给出了固定计算预算下的最优分配思想
这篇论文不是简单说"越大越好",它真正关心的是:
如果总 compute 是固定的,应该如何分配给模型大小、数据量和训练步数?
论文通过经验 scaling law 研究了 compute budget 的 optimal allocation。作者发现,每个 compute budget 下都有一个较优模型规模;如果模型太小,即使用很久也浪费;如果模型太大,训练步数不足,也可能不够好。论文在第 6 节专门研究了 compute budget 的最优分配问题。
这对于大模型公司非常实际,因为它回答的是一个商业问题:
给定一笔 GPU 预算,我到底应该训多大的模型,训多久,喂多少数据?
这篇论文把这个问题转化成了一个可以实验拟合的问题。
5. 过拟合也可以被规模关系描述
论文还研究了数据量和模型量之间的关系。
当模型变大但数据不增加时,会进入收益递减甚至过拟合区域;当数据变多但模型太小时,也会受模型容量限制。论文发现,过拟合 penalty 主要与模型大小和数据大小之间的比例关系有关,并指出模型大小增加 8 倍时,数据大约增加 5 倍就能避免明显 penalty。
这带来的启发是:
数据量不一定要和参数量线性增长,但模型和数据必须协同扩展。
不过要注意,后来的 Chinchilla Scaling Laws 对 OpenAI 这篇论文的一些结论做了修正,认为在固定 compute 下,很多早期大模型其实训练 token 不够,应该用更小模型配更多数据。这说明 scaling law 不是永恒真理,而是一套不断被更新的经验规律。
三、实际应用场景
这篇论文的应用不是"直接做一个 app",而是指导 AI 工程决策。它更像是大模型时代的"投资回报模型"。
1. 大模型预训练前的成本预测
如果一个团队想训练一个中文大模型、金融大模型、代码大模型,最直接的问题是:
训多大?
用多少数据?
花多少 GPU?
预计 loss 能降到多少?
Scaling law 的方法可以帮助团队先训练一组小模型,然后外推大模型效果。
比如你想训练一个面向期货、现货、产业链、A 股公告、社媒情绪的金融语言模型,可以先训练:
100M 参数
300M 参数
1B 参数
3B 参数
然后记录 validation loss,拟合 N 与 loss 的关系。如果曲线显示继续扩大模型收益有限,就不应该盲目追求更大参数,而应该考虑数据质量、RAG、工具调用、回测系统等方向。
2. 判断"自训模型"还是"RAG + 强通用模型"
对创业团队来说,这篇论文的启发尤其重要。
如果你要做一个商品期货投研 Agent,数据源包括:
- 微博趋势;
- 雪球讨论;
- 抖音热点;
- A 股公告;
- 隆众资讯现货价格;
- 库存、基差、开工率;
- 期货行情;
- 政策新闻。
你不一定需要从零训练一个大模型。
Scaling law 反而会提醒你:自训大模型是一项高成本工程,必须有足够数据量和 compute budget 才值得。如果只是做垂直投研系统,更现实的路线通常是:
diff
强通用模型
+ 行业 RAG
+ 数据库查询
+ 行情/现货价格工具
+ 回测系统
+ 风控规则
+ Agent 工作流
也就是说,Scaling Laws 解决的是底座模型能力扩展问题;而实际交易/投研系统还需要工具、数据、流程和领域知识。
3. 企业私有模型的规模选择
很多企业想训练自己的客服模型、知识库模型、代码助手、金融助手,但不清楚应该选择 1B、7B、13B 还是 70B。
Scaling law 可以帮助企业做一个更理性的判断:
如果数据很少,盲目扩大模型可能不划算;
如果数据很多但模型太小,模型容量可能成为瓶颈;
如果 compute 很有限,应该寻找当前预算下的最优模型规模。
这比"别人都训 7B,所以我也训 7B"更科学。
4. 训练过程中的 early stopping 决策
论文指出,训练曲线本身也有可预测性,早期训练曲线可以粗略预测更长训练后的 loss。
实际训练中,这可以用于判断:
- 继续训练是否值得;
- 是否应该停止当前 run;
- 是否应该换更大模型;
- 是否应该增加数据;
- 是否应该调整 batch size;
- 是否应该重新做数据清洗。
对 GPU 预算昂贵的团队来说,这个决策非常重要。
5. 数据采集和数据质量评估
Scaling law 也可以用于判断"继续收集数据有没有价值"。
比如一个金融投研系统中,你可以问:
继续爬更多社媒内容,loss 是否明显下降?
加入上市公司公告后,验证集效果是否改善?
加入现货价格文本描述后,模型是否更懂产业链?
如果新增数据没有带来明显的验证集收益,可能说明:
- 数据质量不够;
- 数据与目标任务不匹配;
- 模型容量不足;
- 评测集设计有问题;
- 当前瓶颈不在预训练,而在工具调用或 RAG 检索。
四、最小可运行 Demo:用小模型观察 Scaling Law
下面这个 demo 不复现论文规模,而是复现论文的方法论:
训练不同大小的小语言模型,记录 validation loss,然后拟合
loss ≈ A * N^-alpha + E
这个 demo 使用字符级语言模型,数据很小,模型也很小,所以结果不能和论文数值比较。但它可以帮助你理解 Scaling Law 的实验流程。
1. 安装依赖
pip install torch numpy matplotlib tqdm
### 2. 新建文件:****scaling_law_demo.py
ini
import random
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm
import matplotlib.pyplot as plt
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
TEXT = """
In the beginning the universe was created.
This has made a lot of people very angry and been widely regarded as a bad move.
The quick brown fox jumps over the lazy dog.
Language models learn patterns from text.
Scaling laws describe how loss changes with model size data size and compute.
A larger model can often learn faster and achieve lower validation loss.
Machine learning is both science and engineering.
Transformers use attention to process sequences.
Deep learning models improve when trained on more data.
The purpose of this demo is to fit a simple scaling curve.
""" * 200
class CharDataset(Dataset):
def __init__(self, text, block_size=64):
chars = sorted(list(set(text)))
self.stoi = {ch: i for i, ch in enumerate(chars)}
self.itos = {i: ch for ch, i in self.stoi.items()}
self.vocab_size = len(chars)
self.data = torch.tensor([self.stoi[ch] for ch in text], dtype=torch.long)
self.block_size = block_size
def __len__(self):
return len(self.data) - self.block_size - 1
def __getitem__(self, idx):
x = self.data[idx:idx + self.block_size]
y = self.data[idx + 1:idx + self.block_size + 1]
return x, y
class TinyGPT(nn.Module):
def __init__(self, vocab_size, d_model=64, n_heads=2, n_layers=2, block_size=64):
super().__init__()
self.token_emb = nn.Embedding(vocab_size, d_model)
self.pos_emb = nn.Embedding(block_size, d_model)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=n_heads,
dim_feedforward=4 * d_model,
dropout=0.0,
batch_first=True,
activation="gelu",
)
self.blocks = nn.TransformerEncoder(encoder_layer, num_layers=n_layers)
self.ln = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, vocab_size)
self.block_size = block_size
def forward(self, x):
b, t = x.shape
pos = torch.arange(0, t, device=x.device).unsqueeze(0)
h = self.token_emb(x) + self.pos_emb(pos)
# causal mask: 防止模型看到未来 token
mask = torch.triu(torch.ones(t, t, device=x.device), diagonal=1).bool()
h = self.blocks(h, mask=mask)
h = self.ln(h)
logits = self.head(h)
return logits
def count_params(model):
return sum(p.numel() for p in model.parameters())
def train_one_model(config, train_loader, val_loader, vocab_size, block_size, device):
model = TinyGPT(
vocab_size=vocab_size,
d_model=config["d_model"],
n_heads=config["n_heads"],
n_layers=config["n_layers"],
block_size=block_size,
).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)
loss_fn = nn.CrossEntropyLoss()
model.train()
for epoch in range(config["epochs"]):
pbar = tqdm(train_loader, desc=f"training {config}")
for x, y in pbar:
x, y = x.to(device), y.to(device)
logits = model(x)
loss = loss_fn(logits.reshape(-1, vocab_size), y.reshape(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
pbar.set_postfix(loss=float(loss.item()))
model.eval()
losses = []
with torch.no_grad():
for x, y in val_loader:
x, y = x.to(device), y.to(device)
logits = model(x)
loss = loss_fn(logits.reshape(-1, vocab_size), y.reshape(-1))
losses.append(loss.item())
return count_params(model), float(np.mean(losses))
def fit_power_law(params, losses):
"""
拟合形式:
loss = A * N^(-alpha) + E
为了保持 demo 简单,这里对 E 做 grid search。
"""
N = np.array(params, dtype=np.float64)
L = np.array(losses, dtype=np.float64)
best = None
for E in np.linspace(0.0, min(L) * 0.95, 200):
if np.any(L - E <= 0):
continue
x = np.log(N)
y = np.log(L - E)
# log(L - E) = log(A) - alpha * log(N)
slope, intercept = np.polyfit(x, y, 1)
alpha = -slope
A = np.exp(intercept)
pred = A * (N ** (-alpha)) + E
mse = np.mean((pred - L) ** 2)
if best is None or mse < best["mse"]:
best = {
"A": A,
"alpha": alpha,
"E": E,
"mse": mse,
"pred": pred,
}
return best
def main():
set_seed(42)
device = "cuda" if torch.cuda.is_available() else "cpu"
block_size = 64
batch_size = 64
dataset = CharDataset(TEXT, block_size=block_size)
n = len(dataset)
train_size = int(0.9 * n)
val_size = n - train_size
train_set, val_set = torch.utils.data.random_split(dataset, [train_size, val_size])
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)
configs = [
{"d_model": 32, "n_heads": 2, "n_layers": 1, "epochs": 2},
{"d_model": 64, "n_heads": 2, "n_layers": 2, "epochs": 2},
{"d_model": 96, "n_heads": 4, "n_layers": 2, "epochs": 2},
{"d_model": 128, "n_heads": 4, "n_layers": 3, "epochs": 2},
]
params = []
losses = []
for config in configs:
n_params, val_loss = train_one_model(
config=config,
train_loader=train_loader,
val_loader=val_loader,
vocab_size=dataset.vocab_size,
block_size=block_size,
device=device,
)
params.append(n_params)
losses.append(val_loss)
print(f"params={n_params:,}, val_loss={val_loss:.4f}")
fit = fit_power_law(params, losses)
print("\nFitted scaling law:")
print(f"loss ≈ {fit['A']:.4f} * N^(-{fit['alpha']:.4f}) + {fit['E']:.4f}")
print(f"mse = {fit['mse']:.6f}")
params_np = np.array(params)
losses_np = np.array(losses)
order = np.argsort(params_np)
x_sorted = params_np[order]
y_sorted = fit["pred"][order]
plt.figure()
plt.scatter(params_np, losses_np, label="observed val loss")
plt.plot(x_sorted, y_sorted, label="fitted power law")
plt.xscale("log")
plt.xlabel("Number of parameters N")
plt.ylabel("Validation loss")
plt.title("Tiny Scaling Law Demo")
plt.legend()
plt.savefig("scaling_law_demo.png", dpi=160)
print("\nSaved plot to scaling_law_demo.png")
if __name__ == "__main__":
main()
3. 运行 Demo
python scaling_law_demo.py
你会看到类似输出:
ini
params=17,000, val_loss=2.65
params=105,000, val_loss=2.21
params=250,000, val_loss=1.98
params=620,000, val_loss=1.82
Fitted scaling law:
loss ≈ A * N^(-alpha) + E
Saved plot to scaling_law_demo.png
具体数值会因为机器、PyTorch 版本、随机种子略有不同。
4. 这个 Demo 对应论文里的什么?
它对应论文最基础的一条思想:
固定数据和训练设置,改变模型参数量 N,
观察 validation loss 是否随着 N 增大而下降,
然后拟合 power-law 曲线。
论文真正做的是大规模实验,研究了模型参数量 N、数据量 D、训练 compute C 三者对 loss 的影响。本文 demo 只复现最小版本:模型规模 N 与 loss 的关系。
所以不要把这个 demo 的 alpha 数值当成论文结论。它只是教学用途。
五、总结:这篇论文真正改变了什么?
《Scaling Laws for Neural Language Models》的核心贡献不是提出了一个新的模型结构,而是提出了一种大模型时代的工程世界观:
语言模型的能力增长在相当大范围内是可预测的。
模型参数、数据量和训练 compute 是最重要的三个规模变量。
在固定 compute 下,训练更大的模型并提前停止,往往比把小模型训练到完全收敛更高效。
大模型训练不应该只靠经验,而应该通过小规模实验拟合 scaling curve,再规划大规模训练。
对今天的 AI 应用开发者来说,这篇论文也有一个现实启发:
如果你的目标是做一个垂直行业系统,比如期货投研、产业链分析、A 股公告理解、社媒情绪分析,那么不一定要从零训练大模型。Scaling Laws 告诉我们,自训底座模型是一个高度依赖数据、算力和预算的工程问题。
更现实的路线可能是:
diff
强通用模型
+ 行业数据 RAG
+ 实时行情和现货价格工具
+ 回测系统
+ Agent 工作流
+ 风控和解释模块
也就是说,Scaling Laws 解决的是"底座模型能力如何随规模增长"的问题;而真正落地到交易、投研、企业应用时,还需要把模型能力接入真实数据、真实工具和真实业务流程。
这就是这篇论文对今天 AI 工程最大的启发。