一个大模型的训练存在若干阶段。但如果只看损失函数,预训练、持续预训练和 SFT 都能写成下一 token 的交叉熵。这容易让我们产生疑问:既然公式相似,不同阶段有什么差异?本篇先讨论预训练阶段。
1. 自回归预训练
我们要学习一段文本出现的概率。给定 token 序列 $x_1,x_2,\ldots,x_T$,利用概率的链式法则,可以把联合概率写成
$$p_\theta(x_1,\ldots,x_T)=\prod_{t=1}^{T}p_\theta(x_t\mid x_{<t})$$
每一项都只依赖已经出现的前缀,正好对应逐 token 预测。我们对这些概率取负对数并求平均。若训练样本不单独监督第一个 token,则损失为
$$\mathcal L_{pretrain} =-\frac{1}{T-1}\sum_{t=2}^{T} \log p_\theta(x_t\mid x_{<t})$$
这里有一个下标容易混淆:预测 $x_t$ 时,模型读取的是 $x_{<t}$。常见实现把 $x_1,\ldots,x_{T-1}$ 作为输入,对应标签为 $x_2,\ldots,x_T$;输入位置 $i$ 可以看到自己和此前位置,并用来预测 $x_{i+1}$。训练时这些目标已经给定,我们可以一次计算所有位置的损失;推理时下一 token 尚未产生,才需要逐步生成。
一条最小训练样本可以是普通文本:
Attention assigns a weight to each visible token and uses the weighted values to update the current representation.
对这段文本,我们并不额外提供问答标签,下一 token 就来自文本本身。Tokenizer 完成编码后,只需按前述方式错开输入和目标。如果序列包含文档结束标记,它同样可以成为预测目标,模型便能学习文档在何处结束。
2. 预训练的数据处理
预训练数据需要经过抽取、语言识别、质量过滤、隐私与安全处理、去重、配比、Tokenizer 编码和分片。训练程序通常把多个文档拼接成固定长度序列,或用 packing 减少 padding 浪费。
假如两篇短文拼进同一个序列,我们还要决定后面一篇能否看到前面一篇。插入文档结束标记只是提供分隔信号,并不会自动切断注意力;若希望各文档独立计算,需要额外使用分段掩码。
数据配比不能只按原始 token 数决定。网页语料很大,若完全按规模采样,会压过代码、论文、书籍和低资源语言。训练中常对高价值小数据源上采样,但比例过高会增加重复。实际做法通常根据小规模消融实验和多组评测调整。
3. 优化过程
拿到一个 batch 并算出损失后,我们还需要把梯度变成参数更新。AdamW 是一种常见选择。它分别追踪梯度和梯度平方的指数滑动平均,设第 $t$ 步梯度为 $g_t$,则
$$m_t=\beta_1m_{t-1}+(1-\beta_1)g_t,$$
$$v_t=\beta_2v_{t-1}+(1-\beta_2)g_t^2$$
初始时两个平均量通常为零,前几步会偏小,因此使用 $\hat m_t=m_t/(1-\beta_1^t)$、$\hat v_t=v_t/(1-\beta_2^t)$ 修正。更新可写为
$$\theta_{t+1}=(1-\eta_t\lambda)\theta_t -\eta_t\frac{\hat m_t}{\sqrt{\hat v_t}+\varepsilon}$$
我们可以把 $\hat m_t$ 理解为经过平滑的梯度方向,把 $\sqrt{\hat v_t}+\varepsilon$ 看作逐分量的尺度调整;$\lambda$ 则控制独立的权重衰减。更新走多远仍由学习率 $\eta_t$ 决定。训练初期常用 warmup 逐渐增大学习率,后期再衰减;梯度裁剪用于限制偶发的大梯度。混合精度减少显存与计算成本,FP16 常配合损失缩放,BF16 通常不需要同样的处理。
模型规模较大时,数据并行、张量并行、流水线并行和 ZeRO/FSDP 会分摊参数、梯度和优化器状态。它们改变的是训练系统的存储与通信方式,不改变自回归目标本身。
4. 持续预训练与中期训练
如果我们已经有一个 Base Model,就不必每次都从随机参数开始。继续使用语言模型目标、但调整训练数据,便属于持续预训练。领域适配会增加医学、法律、代码或目标语言文本,让模型更多接触相应词汇和知识;这一阶段通常仍以自然文本为主,尚未专门要求模型按指令格式回答。
例如,要让通用模型补充机器学习论文知识,可以使用论文正文、教材和技术文档:
Layer normalization computes statistics across the hidden features of each token. Its computation is independent of the batch size.
现在把这段话改成一个问题和一份回答,再只监督 assistant 部分,就更接近 SFT。我们看到,区分阶段时要同时看数据、训练目的和 loss mask;单凭交叉熵这个公式,无法判断模型究竟在接受哪一种训练。
持续预训练最常见的风险是遗忘。目标领域比例过高时,模型可能提高专业评测,却损伤通用语言、多语种和推理能力。通常会混入一部分通用数据作为 replay,并同时监控领域与通用评价集。
5. 退火与分阶段数据调度
训练末期常降低学习率,并提高高质量数据的比例。较低学习率减少参数震荡,高质量混合则让模型在训练结束前更多接触目标分布。OLMo 2 等工作把这一过程称为 annealing,Qwen 系列也采用分阶段数据调度,分别加强通用能力、推理和长上下文。
长上下文也适合用类似方式单独加强。但我们若只是把许多互不相关的短文拼成长序列,模型可能仍只靠局部信息就能降低损失。真正需要远距离依赖的数据、位置编码的适配,以及长序列的显存开销,都要一起考虑。能接收长输入与会利用长输入,并非同一个评价结果。
6. 一次训练怎样判断是否正常
如果训练损失稳定下降,我们至少知道模型在更好地拟合当前样本,但还不能据此判断所有能力都在提高。技术上需要关注梯度范数、学习率、吞吐、MFU 和显存,效果监控则需要独立验证集及语言、代码、数学等不同任务。更换数据后,尤其要把目标领域的提升和其他能力的变化放在一起看。
数据污染也会制造虚假的进步。评测题、答案和近似改写应尽量从训练集排除;只做完整字符串匹配通常不够,还要检查片段和近似重复。持续预训练则要特别关注遗忘,不能只报告目标领域的增益。
7. 预训练阶段真正决定什么
这样再看阶段名称,我们就有了比较具体的判断依据:初次预训练建立广泛的基础表示,持续预训练调整模型接触的数据分布,训练末期的退火进一步改变更新幅度和数据侧重。接下来即使用相似的损失做 SFT,我们也会改变模型被要求生成的那一部分文本。训练阶段的差异,正是从这些选择中产生的。