07. 预训练:从预测下一个 token 到通用能力
上一篇把网页、代码、书籍过滤成十几万亿个 token。这一篇看模型怎样用这些 token 训练:整个预训练只有一个目标,预测下一个 token,模型却从中学会了语法、知识、写代码和做题。训练过程也不是一条直线跑到底,而是分成主训练、长上下文扩展、退火几个阶段,每段的学习率和数据都在变。
读完应能解释:为什么一个这么简单的目标能学出广泛的能力,又为什么会带来背诵训练数据的风险;学习率预热、余弦和 WSD 调度、退火各在做什么;基础模型为什么不好好回答问题;以及资料助手团队如果想用公司语料“继续预训练”,什么时候值得、要防什么。
只有一个目标:猜下一个 token
拿一句制度原文“员工申请年假需提前三个工作日提交”,假设它被切成 8 个 token。训练时把前 7 个作为输入,把整体右移一位的 7 个作为目标:看到“员工”猜“申请”,看到“员工 申请”猜“年假”,依此类推。一句话就是七道题,一起算。
模型在每个位置输出词表里所有 token 的概率,损失只看正确答案拿到的概率 p,取 −ln p:
text
单个位置的损失 = −ln p(正确的下一个 token)
整句损失 = 各位置损失的平均
示意数值:7 个位置的 p 为 0.05、0.30、0.20、0.60、0.45、0.85、0.40
损失 = (3.00 + 1.20 + 1.61 + 0.51 + 0.80 + 0.16 + 0.92) ÷ 7 ≈ 1.17
困惑度 = e^1.17 ≈ 3.2,相当于平均在 3.2 个候选里犹豫第一个位置只看到“员工”,后面可以接很多词,p 很低是正常的;“三个”之后接“工作日”几乎是确定的,损失接近 0。这就是第 02 篇讲过的交叉熵损失,只是每个 batch 里有几百万个这样的位置。
为什么猜下一个词能学出能力?因为要在海量文本上把这个损失压低,模型不得不掌握文本背后的规律。要猜对“《劳动法》规定,劳动者连续工作一年以上的,享受带薪……”的下一个词,需要知识;要猜对一段 Python 代码的下一行,需要懂语法和程序逻辑;要猜对数学题解里“所以答案是”后面的数字,需要会算。语料覆盖了什么,模型就被迫学什么。反过来,语料里少的东西模型也学不好,这正是上一篇配比实验要解决的问题。
同一个目标也带来记忆。最省事的降低损失的办法,有时是直接背下原文。训练数据里重复越多、模型越大,逐字背诵越严重,研究者已经从 GPT-2 中提取出数百条训练原文,其中包括姓名、电话和邮箱。所以上一篇的去重和去隐私不只是为了质量,也是为了减少模型“说出”不该说的东西。
模型结构:一叠只看左边的 Transformer 层
预训练的大语言模型几乎都是 Decoder-only Transformer:token 编号先查词嵌入表变成向量,经过几十层相同结构的计算(每层一个自注意力加一个前馈网络),最后由输出层映射回词表大小的分数。Llama-3.1-8B 是 32 层、隐藏维度 4096。注意力的原理见深度学习主要架构和推理系列的 KV Cache,这里只需要记住一点:因果掩码让每个位置只能看见它左边的 token,不能偷看答案。
正因为有因果掩码,训练时一条 8192 token 的序列只需一次前向计算,就能同时给出 8192 个位置的预测和损失,不用像推理那样一个一个生成(推理的过程见Prefill 与 Decode)。这是预训练能在十几万亿 token 上跑完的前提。
学习率怎样走:预热、下降、退火
第 02 篇讲过,学习率决定每一步参数改多少。预训练要跑上百万步,学习率不会从头到尾不变:
预热(warmup):训练刚开始时,参数是随机的,AdamW 对梯度二阶矩的估计也还没积累起来,一上来就用大学习率,更新方向和大小都不可靠,容易发散。所以先从接近 0 线性升到峰值。Llama 2 预热 2000 步;Llama 3 405B 预热 8000 步,峰值学习率 8×10⁻⁵。
余弦衰减:预热之后,学习率沿余弦曲线平滑下降。早期大步走、快速降低损失,后期小步走、在低谷里细调。Llama 3 405B 用 120 万步从 8×10⁻⁵ 降到 8×10⁻⁷;Llama 2 降到峰值的 10%。余弦调度的麻烦是总步数要事先定好,曲线形状依赖它,训练到一半想多训一些数据,就得改整条曲线。
WSD(Warmup-Stable-Decay,预热—稳定—衰减)把下降集中到最后:预热后学习率长期保持峰值不变,最后一段才快速降下来。MiniCPM 的实验发现,衰减段占总 token 约 10% 就够了,2.5% 不够;衰减一开始,损失会出现一段明显的快速下降。WSD 的好处是稳定段的任何一个检查点都可以作为起点:想要一个模型就从那里衰减,想多训就接着稳定段往下跑,不用事先决定总量。
学习率之外,批大小也在变。Llama 3 405B 开始时每步 400 万 token、序列长度 4096,训练 2.52 亿 token 后加倍到 800 万 token、序列 8192,训练到 2.87T token 时再加倍到 1600 万。早期用小批量更新更频繁,后期用大批量让梯度更稳、并行更高效。
退火:最后一段换上最好的数据
学习率降到很低的最后阶段,模型对数据的变化最敏感,又不会再被大步更新打乱,适合用来“精修”。这一段通常把数据换成质量最高的部分,叫退火(annealing)。
Llama 3 在预训练的最后 4000 万 token 里,把学习率线性降到 0,同时上采样极高质量的数据源,再把退火过程中的若干检查点做平均,得到最终的预训练模型。论文报告退火让 8B 模型在 GSM8k 和 MATH 验证集上分别提升 24.0% 和 6.4%,但对 405B 几乎没有提升:大模型本身已经足够强,最后一小段数据改变不了多少。MiniCPM 更进一步,在衰减段直接混入高质量的 SFT 数据。
这带来一个后果:近年发布的“基础模型”,在退火阶段已经见过不少问答和指令格式的数据,基础模型和对话模型的边界没有早年那么清楚。评估基础模型时,这也是要考虑的污染来源。
长上下文:单独放在后面训
Llama 3.1 支持 128K 上下文,但主训练只用 8K 序列。原因在算力:自注意力的计算量随序列长度平方增长,128K 序列的注意力开销是 8K 的 256 倍;而网上绝大多数文档本来就很短,用长序列训练大部分是浪费。
所以长上下文是主训练之后的单独阶段。Llama 3 用 6 个阶段把长度从 8K 逐步拉到 128K,共约 800B token,约占总预训练量 15.6T 的 5%。每个阶段结束要确认两件事:短文本评测的成绩完全恢复,以及在当前长度下“大海捞针”测试(在长文里埋一句话再问模型)全部答对,才进入下一阶段。
位置编码也要配合。Llama 系列用 RoPE 旋转位置编码,它用不同频率的旋转角度表示位置,base 参数控制旋转快慢;base 越大,旋转越慢,越长的位置也能区分开。Llama 3 一开始就把 base 设为 500,000,为后面拉长上下文留出余地。其他模型常见的做法是在长上下文阶段调大 base 或对位置做插值,具体方案要看各自的技术报告。
训练稳定性:防止 loss 突然炸掉
大模型预训练最怕loss 尖峰:曲线好好地下降,突然跳上去,有时恢复,有时直接发散成 NaN。PaLM 540B 训练中出现过约 20 次尖峰,处理办法是回到尖峰开始前约 100 步的检查点,跳过附近 200 到 500 个批次的数据,再继续训练。他们的分析是,尖峰来自特定数据批次与当时参数状态的组合,而不是数据本身坏了。读曲线和排查的方法见第 05 篇。
预防比补救便宜,几种常见手段:
- 梯度裁剪:梯度的整体范数超过阈值就按比例缩小,Llama 2 和 OLMo 2 都设为 1.0,防止一步更新过大。
- z-loss:输出层 softmax 的归一化项 Z 如果持续变大,数值会不稳定。PaLM 加了一个很小的辅助损失
10⁻⁴ × (ln Z)²,把 ln Z 拉向 0。 - QK-norm:注意力分数来自 Q 和 K 的点积,两者范数越来越大时分数会爆掉。OLMo 2 在计算注意力前对 Q、K 各做一次 RMSNorm。
- 对嵌入层不做权重衰减:OLMo 2 发现权重衰减把词嵌入压得过小,于是对嵌入层关掉了它。
这些手段各有出处和适用条件,加之前应在小模型上验证;混合精度相关的数值问题见混合精度。
基础模型和对话模型差在哪
预训练结束得到的是基础模型(base model)。拿资料助手的问题去问它,会看到和聊天助手很不一样的行为。下面是示意输出,没有实际运行:
text
输入:年假怎么申请?
基础模型(示意):
年假怎么申请?病假怎么申请?婚假有几天?产假怎么算?
以上问题请咨询人力资源部,电话……
对话模型(示意):
年假需要在审批系统中提交申请,一般需提前三个工作日,写明起止日期和工作交接人。
具体天数和规则以公司《休假管理制度》为准。基础模型没有做错,它只是在续写。网上一句孤立的问题后面,常常跟着更多问题(FAQ 列表)或论坛回帖,所以续写出一串问题是很合理的“下一个 token”。要让它回答问题,可以把提示写成文档的样子,比如先放两组“问:……答:……”,再放新问题和“答:”,让续写的最自然方向就是回答。
对话模型(instruct / chat model)是在基础模型上继续做指令微调和偏好对齐得到的,学会了“用户问、助手答”的格式、什么时候该拒绝、回答该多长。这是第 09 篇到第 12 篇的内容。基础模型适合当微调的起点、做补全类任务或研究;直接给员工用,应该选对话模型。
继续预训练:用公司语料再训一段
既然预训练让模型学会了语料里的东西,能不能拿公司的语料接着训,让模型“懂公司”?这叫继续预训练(continued pretraining),也叫领域自适应预训练:用和预训练相同的目标,在领域文本上再训一段。
它确实有效。2020 年的一项研究在生物医学论文、计算机科学论文、新闻、商品评论四个领域做过对比,继续预训练后,下游任务在四个领域都有提升。值得做的情况通常是:
- 领域语料足够大,至少数十亿 token,例如医学文献、法律判例、某种专门的编程语言;
- 要补一门底座覆盖很弱的语言,而且分词器对它的切分还算合理;
- 领域文本的用词和写法与通用网页差别很大,底座的困惑度明显偏高。
资料助手不属于这些情况。公司制度只有几百万 token,规则还经常修订,答案要能引用条款编号。继续预训练会把某个版本的制度“揉”进参数里,制度一改模型就过时,还说不清答案出自哪一条。这类需求应该用检索(见RAG 与工具调用),回答格式和口径用 SFT 解决。
真要做继续预训练,最大的风险是灾难性遗忘:只在领域数据上训练,通用能力会下降,比如中文写作变差、不会写代码了。2024 年一项系统研究给出了实用的配方:
- 重新预热学习率再衰减:预训练结束时学习率已经很低,直接接着训,新数据吸收得很慢;先重新升到较高学习率再降下来,适应新分布的效果好得多。代价是升高学习率本身就是旧数据上损失上升的主要原因,所以要配合下一条。
- 回放旧数据:在领域数据里混入一部分通用预训练数据。他们的实验中,英语到英语的弱分布变化回放 5% 就够,英语到德语的强变化需要 25%。
- 用评测盯住两边:领域评测看提升,通用评测看遗忘,两者都要在训练中定期跑。
规模也要估一下。在 8B 模型上继续预训练 100 亿 token:
text
算力 = 6 × 参数量 × token 数 = 6 × 8.03×10⁹ × 10¹⁰ ≈ 4.8×10²⁰ FLOPs
H100 BF16 稠密峰值 989 TFLOPS,假设实际利用率(MFU)40%:
每卡每秒 ≈ 989×10¹² × 0.4 ≈ 4.0×10¹⁴ FLOPs
总耗时 ≈ 4.8×10²⁰ ÷ 4.0×10¹⁴ ≈ 1.2×10⁶ 秒 ≈ 340 GPU 小时(估算)
8 张 H100 约 42 小时算力本身不算夸张,难点在准备 100 亿 token 的干净领域数据,以及训练后完整地评估遗忘。
从零预训练:一笔多数团队付不起的账
最后把从零训练一个 8B 模型的账再算一遍,用第 04 篇的方法:
text
算力 = 6 × 8.03×10⁹ × 15×10¹² ≈ 7.2×10²³ FLOPs
按 40% MFU:7.2×10²³ ÷ 4.0×10¹⁴ ≈ 1.8×10⁹ 秒 ≈ 50 万 GPU 小时(估算)
Llama 3.1 模型卡公布 8B 用了 146 万 H100 GPU 小时,约为估算的 3 倍即使按估算的 50 万 GPU 小时、假设每 GPU 小时 2 美元的租用价,也要约 100 万美元,按模型卡的数字则要约 300 万美元。这还只是最终那一次训练,不包括数据处理、配比和超参数的小模型实验、失败重来,也不包括后面的指令微调和对齐。对绝大多数团队,正确的起点是选一个许可证合适的现成底座。
预训练只用“预测下一个 token”这一个目标,靠海量多样的文本逼出语法、知识和推理能力,也会顺带记住一部分原文。训练过程分阶段:学习率预热后按余弦或 WSD 下降,最后用高质量数据退火;长上下文放在主训练之后逐步拉长;梯度裁剪、z-loss、QK-norm 等手段防止 loss 尖峰。基础模型只会续写,要经过后训练才会回答问题。继续预训练适合大规模领域语料,并要用学习率重新预热和回放旧数据控制遗忘。这么大的训练任务不可能放在一张卡上,下一篇看几千张卡怎样分工:08. 分布式训练。
