从0训练LLM(旧版 · 存档中) · M3 阅读+实验 ≈ 70 分钟 页内实验 ×3

预训练

M2 搭好了 Transformer 的骨架,但它几千万个参数还全是随机数——现在它输出的是纯噪声。预训练就是用海量文本,通过「预测下一个词」这唯一一个任务,把随机权重训练成真正懂语言的模型。这是最烧钱、最考验工程的一步。
① 主线定位

这一步在整条主线的哪里

到这一节,「训练」二字终于名副其实。

M1 处理了数据、M2 搭好了架构。但那个模型现在毫无用处——权重是随机初始化的,你问它什么,它吐出来的都是乱码。预训练要做的,是让模型反复做一件事:给它一段文本的前半截,预测下一个 token;预测错了就调整权重让它下次错得少一点。在海量文本上重复几十亿次,模型就在这个过程里被迫学会了语法、事实、推理的雏形——因为要准确预测下一个词,这些都得懂。

🧭

上游: M1 的分词器 + M2 的模型架构,两者已就位。
本节: 真正烧 GPU 训练——损失函数、优化器、学习率、混合精度、看懂 loss 曲线,以及一个绕不开的大问题:该训多大的模型、喂多少数据。
下游: M4 把这个”只会续写”的预训练模型,微调成”会回答问题”的助手。

② 核心问题

怎么衡量「预测得对不对」,又怎么让它变好?

一个损失函数 + 一个优化器,就是训练的全部引擎。

「预测下一个词」这个任务,模型的输出其实是一个概率分布:对词表里每个词,给出”下一个是它”的概率。比如输入「今天天气真」,模型对「好」给 60%、对「热」给 20%。

问题答案(本节的两个主角)
怎么把「预测得对不对」变成一个可计算的数字?交叉熵损失:真实词是「好」而模型只给 60%,损失就是 −log(0.6)。给得越高损失越小,越离谱惩罚越重。
有了这个数字,怎么让它变小?梯度下降 + 优化器:算出损失对每个参数的梯度,用 AdamW 按梯度更新参数,重复几十亿次。
❓

听起来简单,但魔鬼在细节:学习率调大一点就发散(loss 变 NaN)、调小一点几百步都不动;显存永远不够;训到一半 loss 卡在平台期。这些”工程手感”才是预训练真正难的地方——下面的实验让你亲手体会一次。

③ 原理 · 直说

训练引擎的零件 + 一个绕不开的规模问题

这一节概念最多,但都围绕一件事:怎么让 loss 又快又稳地下降。

交叉熵损失与困惑度

交叉熵损失只看一件事:真实那个词,你给了多大概率。loss = −log(p_真实词)。给 90% → loss=0.10;给 10% → loss=2.30。给得越低惩罚越重,且是指数级加重。

**困惑度(perplexity)**是 loss 取指数:PPL = e^loss。直觉是”模型在犹豫的等可能选项个数”:PPL=1 表示完全确定;PPL=10 表示把握相当于”在 10 个词里瞎猜”。训练时盯 loss,汇报时常用 PPL。

计算器 · 交叉熵与困惑度(真算术)
模型读到「今天天气真」,对下一个词给出概率分布(真实下一词是「好」):
你在曲线上的位置:横轴 = p(给真实词的概率),纵轴 = loss = −ln(p)。曲线在左端爆炸、右端贴地——这个形状就是全部结论。

🎯

试三档:① 拖到 90%+ 看 loss 趋近 0;② 拖到 10% 以下看 −ln 怎么指数级爆炸;③ 注意 60%→90% 和 10%→40% 的 loss 降幅完全不同——训练初期 loss 掉得飞快、后期越来越慢,根源就在这条曲线的形状。

AdamW:为什么不是普通的 SGD

有了 loss 就能算梯度。最朴素的做法是 SGD(参数 −= 学习率 × 梯度),但训 LLM 几乎都用 AdamW,因为它给每个参数自适应的步长——梯度一直很大的参数走小步(防震荡),梯度很小的走大步(防停滞)。

🔍 W 是什么 AdamW 的 “W” 是解耦的权重衰减。有意思的是论文标题里根本没有”AdamW”——它叫《Decoupled Weight Decay Regularization》。贡献不是发明新优化器,而是修了一个大家都没注意到的 bug:Adam 里权重衰减的梯度被塞进梯度、又被自适应分母除掉,导致大权重该受的惩罚反而变小。解耦后衰减直接作用在参数上。arXiv 1711.05101,本地论文库有。

它的更新式就是下面这一行(每个参数独立执行)。M3 讲的显存大头、“每参数 12 字节”,全部能从这行公式里读出来:

AdamW 的一步更新(对每个参数 θ)
← θ − · ( / + )
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
显存账本对上了:m(4 字节)+ v(4 字节)+ fp32 参数副本(4 字节)= 每参数 12 字节的优化器状态——正是下面显存可视化里那块最大的橙色。

公式看完,放到真实地形上看它们各自怎么走——同起点、同学习率,三个优化器赛跑:

地形实验 · SGD vs 动量 vs AdamW(轨迹真实逐步计算)
— SGD— +动量— AdamW

地形是 f = 0.5x² + 8y²:y 方向比 x 方向陡 16 倍——损失面像一条狭长山谷,这正是真实训练的常态(不同参数方向的曲率天差地别)。hover 轨迹上的任何一个点,看那一步的全部数值。

🎯

三件事:① 默认学习率下看三条轨迹的形状差:SGD 锯齿、动量过冲、AdamW 直奔谷底;② 把 η 拖过 0.125——SGD 和动量当场发散,AdamW 还稳着(自适应分母在陡方向自动踩了刹车);③ hover 任意轨迹点,看那一步的 θ、梯度、m、v 具体数值——上面公式里的每个符号都在这。

学习率:warmup + cosine 衰减

学习率是最难调、也最致命的超参:太大 loss 直接发散成 NaN,太小几千步都不动。现代训练几乎都用同一套调度:

  • warmup(预热):前几百步让学习率从 0 线性爬升。因为初始权重随机,一上来用大学习率会剧烈震荡甚至发散——先小步让模型”稳下来”。(经验值:warmup 取总步数的 1%~10%,常用 3%。)
  • cosine 衰减:爬到峰值后按余弦曲线缓慢降到接近 0。前期快速学、后期精细调。

上面两句话描述的是一条曲线——与其读文字,不如直接把它画出来、拖着玩:

曲线实验 · 学习率调度(真算术)
点按钮开关曲线 · 在图上移动鼠标读某一步的具体 η

前沿注脚 · cosine 还是 WSD?WSD 的卖点是「随时可停」:稳定段任意位置截断、补一小段衰减就能出一个可用模型。 但 Kimi K3 技术报告 §3.2 做了一次公平对比——两种调度各自独立跑超参搜索(报告明说:两者最优超参差别很大,共用一套等于偏袒其中一个), 在各自最优设置下比较,结论是 cosine 的最终 loss 更低,所以 K3 用了 cosine。 (这是 K3 报告的自报结论,尚无第三方独立复现;值钱的是那套「两边各自调优再比」的方法论。M7 会展开。)

🎯

试三件事:① 把 warmup 比例拖到 0%——曲线开局就是满血峰值,这正是上一个实验(loss 诊断器)里「关掉 warmup + 大学习率 → 开头就发散」的那个配置;② 打开 constant 和 WSD 三条一起看,然后在图上移动鼠标,读同一步上三种调度各自的 η 具体数值——差距在末端最刺眼;③ 把总步数从 1k 拖到 100k,看 warmup 段和衰减段的绝对步数怎么跟着变:调度是按比例定义的,不是按绝对步数。

混合精度:bf16 为什么比 fp16 更适合训练

用 32 位浮点(fp32)存所有数值最精确,但显存翻倍、算得慢。混合精度用 16 位做大部分计算、关键处保留 fp32。16 位有两种:fp16 和 bf16。同样 16 位,bf16 牺牲精度换取更大的数值范围(指数位和 fp32 一样多)。训练时梯度可能非常小或非常大,fp16 范围不够会溢出(inf/nan),bf16 的大范围正好扛得住。所以现代训练几乎都用 bf16。

🔬

下面第一个实验是 loss 曲线诊断器——亲手调学习率、开关 warmup,看 loss 怎么正常收敛、发散成 NaN、卡平台期。这些”手感”看十遍文字不如自己调一次。

诊断器 · loss 曲线(真实训练动力学)

试三档:① 学习率拉到最右(太大)→ loss 震荡飙升变 NaN;② 拉到最左(太小)→ 几百步几乎不降;③ 中间合适 → 平滑下降。再关掉 warmup 把学习率调大,看开头就炸——这就是 warmup 存在的理由。

显存都花在哪:一个反直觉的事实

训练时显存不够是常态。多数人以为主要是”参数”,其实优化器状态才是大头。以 Adam + 混合精度为例,每个参数要存:fp16 参数、fp16 梯度、以及 Adam 的 fp32 参数副本 + 一阶动量 + 二阶动量。那三份 fp32 往往占到显存的一半以上——这就是为什么”省显存”的技术(8-bit 优化器、LoRA)优先砍这里。下面第二个实验实时画给你看。

可视化 · 显存都花在哪(真实计算)
参数(fp16)梯度(fp16) 优化器状态(Adam fp32×3)激活值

Scaling Laws:该训多大、喂多少数据

固定算力预算,该把它花在”训一个大模型”还是”喂更多数据”上?这就是 Scaling Laws 回答的问题。这里有一段教科书级的认知翻转:

  • Kaplan 2020:把模型训得尽量大,用相对适中的数据,在收敛前就停。
  • Chinchilla 2022 直接推翻了它:模型大小和训练 token 数应该等比例扩展——参数翻倍,数据也要翻倍。社区据此提炼出”参数量 × 20 ≈ token 数”的经验法则。Chinchilla 用同样算力,把参数砍到 Gopher 的 1/4、数据翻 4 倍,全面反超。

这两条结论直接冲突——冲突本身就是最好的教材。Chinchilla 的整套结论,来自拟合下面这条损失公式:

Chinchilla 损失公式(拟合 400+ 次训练得到)
= + +
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
配上算力约束 C ≈ 6·N·D(每个参数每个 token 约 6 次浮点运算),求「固定 C 时 L 最小」的 N*、D*——解出来两者等比例增长,社区据此提炼出 D ≈ 20N 的经验值。下面第三个实验算的就是这道题。
计算器 · Kaplan vs Chinchilla(同一预算,两种建议)
Kaplan 2020 的建议
Chinchilla 2022 的建议
先看线性图——几乎读不出任何结构,对吧?切到 log-log 试试。

同样的算力,Kaplan 让你训更大的模型、喂相对少的数据,Chinchilla 让你参数和数据均衡扩展。GPT-3(175B,只喂了 300B token)就是 Kaplan 时代的产物——按 Chinchilla 它其实训得远不够充分。

FlashAttention:不改结果,只改怎么算

M2 的注意力要算一个 N×N 的矩阵(N 是序列长度)。序列一长,这个矩阵占的显存和读写就爆炸。FlashAttention 的洞察是:GPU 的瓶颈不是算力而是显存读写(IO)——它用分块(tiling)技巧,在不把完整 N×N 矩阵写进显存的前提下算出完全相同的结果。GPT-2 上实测约 3× 加速,纯工程优化,现在是训练标配。

④ 亲手实验

上面三个实验,就是本节的核心

调一次学习率、看一眼显存分配、对比两条 Scaling Law,手感就有了。

回到上面四个交互,依次做:

  1. 交叉熵计算器:拖概率看 loss 的指数形状——理解”训练初期掉得快”的根源。
  2. loss 诊断器:把学习率拉满看它炸成 NaN,拉到最小看它卡住,中间看它健康下降;再关掉 warmup 看开头发散。
  3. 显存可视化:拖动参数量,盯着橙色那块(优化器状态)——它始终是最大的一块。
  4. Scaling 计算器:调算力预算,对比 Kaplan 和 Chinchilla 给出的参数量/数据量差多少。
真实训练 · 亲眼看一次完整的 GPT-2 复现

本模块的”真模型实验”是跟着 Karpathy 的 build-nanogpt 走一遍:他从空文件到训完 GPT-2 124M,每个 commit 对应视频里的一步——AdamW、梯度裁剪、warmup+cosine、bf16、FlashAttention 逐个加进去,每加一个都能看到吞吐/loss 的真实变化。

👀

看什么: ① commit 历史就是”每个优化值多少”的实验记录——bf16 一项吞吐从 ~3200 tok/s 提到 ~10500;② 对照上面 loss 诊断器里你调过的超参,看他实际选了什么值、为什么。

⑤ 项目推进 · 从 0 搭

真正训练我们的小模型

记住分工:代码 AI 来写,训练决策你来做。

本模块的项目任务单

步骤你的决策 / 动作说明
1. 定 token 预算按 Chinchilla:模型 ~50M 参数 × 20 ≈ 10 亿 token用上面计算器验证
2. 让 AI 写训练循环AdamW + warmup+cosine + bf16 + 梯度裁剪 + checkpoint约 100 行,参考 build-nanogpt
3. 调学习率先用小数据跑几百步,确认 loss 在降、没发散能看懂 loss 曲线是正常/发散/卡住(上面练过)
4. 正式训练盯 loss/PPL 曲线,记录 checkpoint会遇到 loss spike,记下来,这是常态
5. 验收让模型续写一段,看是否”像话”了能连贯成句就说明预训练成功
⚠️

成本要有现实预期: 调研实测——nanochat 训一个 561M 模型,8×H100 约 2 小时 / $48。我们的更小、更省。但训出来就是个”幼儿园水平”的续写模型(M4 才让它会回答问题)。价值在于全流程你亲手跑通了,不在模型多强。

预告 M4:这个预训练模型只会”续写”——你说”今天天气”,它接”真好”,但你问”今天天气怎么样?“它可能接”这个问题问得好呀”而不回答。因为它只学过”预测下一个词”,没学过”回答问题”。M4 的 SFT 就教它这个。

⑥ 坑与带走

常见的坑 + 该带走的

坑症状 / 后果怎么办
学习率太大loss 震荡后飙升成 NaN调小 + 用 warmup
没加 warmup训练开头几步就发散让学习率从 0 爬升
用 fp16 而非 bf16训到一半梯度溢出变 inf/nanbf16 数值范围大更稳
以为显存主要花在参数上估算严重偏低、爆显存优化器状态才是大头
盲信”参数×20”当铁律那是 Chinchilla 计算最优点,现代模型为推理便宜常刻意”过训”
loss 卡平台期就放弃误以为失败平台期后常突然下降;真发散(NaN)才要停

这一模块你应该带走的

🔭

概念索引(本模块登场): 交叉熵损失 · 困惑度 PPL · 梯度下降 · AdamW · 权重衰减 · warmup · cosine 调度 · 混合精度 bf16/fp16 · 显存分配 · 优化器状态 · Scaling Laws · Kaplan · Chinchilla · FlashAttention · loss spike · checkpoint

💬

有不明白的? 随时在对话里问我,我会记到 05-问答记录/M3-预训练-问答.md。

深入这一模块

想往下钻,按你愿意花的时间选

每条都经实测验证。Karpathy 那个 4 小时视频是本模块的骨架。

🎮 想更直观 10–30 分钟

动手工具,比读文字管用
15-20 min繁体中文需联网
PPL 与 Loss 是同一东西的两种写法(PPL = e^Loss),可动手调参数看数值变化。
唯一命中 perplexity 概念的交互工具,符合“页面内实验”的定位。
5 min英文需联网 官方
输入模型名+精度,给出加载所需显存,并列出 Adam 训练的额外倍数。
⚠️ 注意:它算的是推理加载显存,不含激活值。训练显存请用上面本页的显存可视化。

🔬 想吃透原理 1–3 小时

Karpathy 是骨架,其余补它讲快的地方
4h01m中英字幕需联网 36 万播放 · 配套代码 build-nanogpt
从空文件到可用的 GPT-2 124M,中段逐个引入 AdamW、梯度裁剪、warmup+cosine、梯度累积、bf16、FlashAttention——每个都先跑慢的版本、再讲为什么这样改能快。
本模块最高优先级。13 个概念它一个视频覆盖 6 个,和“主线驱动、遇坑解坑”完全同构。配套代码保留了视频每一步的 commit。
超长文中文(译文)需联网
从幂律数学基础 → Kaplan 实验设置 → Chinchilla 计算最优配比 → DeepSeek-V3 时代现状。
填补“李沐没讲 Scaling Laws”空缺的最佳中文替代。有原论文图表、公式、推导,作者是研究者本人。
书级英文需联网 HF/nanotron · 4100+ 实验
显存分解、混合精度、梯度检查点、梯度累积的实测数据支撑。
只读「Training on one GPU」第一章就够。后面的多机并行(ZeRO/TP/PP)超出单卡训小模型的范围。
40-60 min中文需联网
AdamW 的真正动机:Adam 里权重衰减被二阶矩分母除掉,解耦后才对。
讲清“为什么”的链条,且诚实提到 CV 上 SGD 可能略优——有反例的资料更可信。

📄 想读原文 按需

PDF 已在本地 01-论文库/,正确读法是「Kaplan → 被推翻 → Chinchilla」
Kaplan · 2020-01
loss 对参数/数据/算力呈幂律,跨 7 个数量级。建议“训很大的模型、收敛前停”——正是后来被推翻的那条。
DeepMind · 2022-03
模型与 token 数应等比例扩展。「×20」是社区从结论提炼的经验值,论文本身给的是等比例关系。
NeurIPS 2022 / ICLR 2019
FlashAttention 摘要用的是 GPT-2 3×(不是二手常引的 7.6×);AdamW 标题里没有“AdamW”三个字。
🚫

我替你排除了什么: 「十分钟速通优化器」类速通 · CSDN 同质化转载 · TensorBoard/wandb 教程(教画图 ≠ 教看图)· 各类推理/量化显存计算器(算的是推理不是训练显存)。
另注:李沐没有 Scaling Laws / Chinchilla / FlashAttention 的精读视频(查了他 paper-reading 仓库 32 篇全表核实);2 个 Karpathy B站字幕版已 404,只收实测可用的 BV12s421u7sZ。

← M2 手搭 Transformer:让 token 互相看见 M4 SFT 指令微调 →