Llama 3 405B 在最多 16,384 张 H100 上的 54 天预训练快照中记录到 419 次意外中断,仍报告高于 90% 的有效训练时间。checkpoint 把恢复变成“回到最近快照 + 重跑”,代价是写带宽、存储和一致性。
万卡训练的故障不是例外:Llama 3 405B 的 54 天快照里,466 次作业中断,其中 419 次是意外中断;按这个计数折算,集群平均每 3.09 小时就有一次意外中断(本页按 54 天 ÷ 419 折算,不是论文给出的 MTBF 指标)。同步训练里,单卡故障可能要求整个作业重启。
checkpoint 把恢复变成“回到最近快照 + 重跑”。频率越高,丢失的工作越少,但每次停顿和写入量越大;同步 checkpoint 的停顿落在关键路径上,异步 checkpoint 把磁盘写入挪到后台,但快照复制仍要占时间。
Llama 3 的实测是:在 16,384 张 H100 的 54 天预训练快照、419 次意外中断下,有效训练时间仍高于 90%,只有 3 次需要重大人工干预。论文没有报告 checkpoint 间隔和单次停顿,所以不能从 90% 反推出这两个参数。
本页用一个简化账本把 trade-off 算清楚:有效时间 ≈ 1 − I/(2·MTBF) − C/I。它不替代论文数据,只用来解释为什么“更频繁”不一定更好。
读完这节你应该知道:万卡训练的故障间隔有多短,为什么一次单卡故障会放大成整个作业的重启。
Llama 3 405B 的训练最多用到 16,384 张 H100(Table 4)。论文对这套集群的判断很直接:16K GPU 训练的复杂度和潜在故障场景超过他们运维过的更大 CPU 集群;训练是同步的,单张 GPU 故障可能要求整个作业重启(§3.3.4)。
论文给出一个 54 天的预训练快照:期间共 466 次作业中断,47 次是计划中断(固件升级、配置或数据更新),419 次是意外中断(§3.3.4)。把 419 次摊到 54 天,得到 3.09 小时/次的集群级平均间隔(本页折算,不是论文给出的 MTBF 指标)。换句话说,按这个快照,集群平均每 3 小时左右就要处理一次意外中断。
按 §3.3.4 的 419 次意外中断折算;这是“集群级”间隔,不是单卡 MTBF。
故障来源并不集中在“GPU 坏了”这一项。Table 5 的类别里,Faulty GPU 148 次、GPU HBM3 Memory 72 次、Software Bug 54 次、Network Switch/Cable 35 次、Host Maintenance 32 次;GPU 相关类别合计仍是最大头,论文正文写 GPU 问题占 58.7%(§3.3.4)。
这就是万卡容错的基本矛盾:在 16K 卡的尺度上,故障不再是低频例外;同步训练又把单点故障的恢复粒度放大到整个作业。checkpoint 不是“可选优化”,而是把故障损失限制在一个可计算窗口内的基础设施。
读完这节你应该知道:最直接的方案为什么有效,又为什么会在万卡规模上撞到停顿和写带宽。
最直接的容错方案是周期性 checkpoint:所有 rank 停下来,把模型、优化器、学习率调度器、RNG 和数据读取位置写进共享存储,然后继续训练。故障发生后,作业重启,加载最近一次完整 checkpoint,从那个 iteration 继续,未 checkpoint 的 step 全部重跑。
Llama 3 也做周期性 checkpoint;论文未说明它是同步还是异步。§3.3.1 写明:checkpoint 保存每张 GPU 的模型状态,单卡范围 1 MB 到 4 GB;目标是缩短 checkpoint 期间的 GPU 停顿,同时提高 checkpoint 频率以减少恢复后丢失的工作。这两句话本身就是一对矛盾:频率越高,丢失越少,但停顿和写入次数越多。
存储侧的压力是实打的。Llama 3 的存储 fabric 基于 Tectonic,240 PB、7,500 台 SSD 服务器,持续吞吐 2 TB/s、峰值 7 TB/s;论文特别指出,checkpoint 写入是高度突发的,会在短时间内打满存储 fabric(§3.3.1)。
同步方案还有两个结构性代价:
工程侧可以对照 Hugging Face Ultra-Scale Playbook 的 “Lessons learned on benchmarking”:他们遇到 Slurm 强制终止作业、进程清理失败、作业无限挂起,并把大量时间花在缩短集群重启和 idle time 上。这是工程资料,不是论文实验数字;本页只在说明“恢复流程本身也是成本”时引用。
所以“同步 checkpoint + 重跑”不是错,而是把问题从“故障不可控”变成了“窗口可计算”:剩下的问题是怎么让窗口更小、让停顿更短。
读完这节你应该知道:一个可恢复的 checkpoint 里到底有什么,分布式框架怎么分片和写元数据,异步写又把停顿挪到了哪里。
以下实现细节来自 PyTorch、Megatron、DeepSpeed 源码与 nanotron 文档;论文数字只来自 Llama 3 §3.3.1、§3.3.4、Table 4、Table 5。
一个能“原地续训”的 checkpoint 至少包含:模型参数、优化器状态(例如 Adam 的 m/v 与 fp32 master weights)、学习率调度器、RNG 状态、数据加载器位置、当前 iteration。少任何一项,恢复后都不是同一条训练轨迹。
三个框架的保存入口都体现了这一点。DeepSpeed 的 save_checkpoint 保存 module state、optimizer states、LR scheduler 和 client_state,并要求所有进程一起调用——每个进程要写自己的 master weights 和 optimizer states(deepspeed/runtime/engine.py:4914)。Megatron 的 save_checkpoint 接收 model、optimizer、opt_param_scheduler、dataloader iterator 和 iteration(megatron/training/checkpointing.py:640)。Nanotron 的文档把 checkpoint 拆成 model/、optimizer/、lr_scheduler/、random/ 和一个 checkpoint_metadata.json,后者记录 version、topology 和恢复所需元数据(nanotron docs/docs.md)。
Llama 3 给了一个尺度感:单张 GPU 的模型状态在 1 MB 到 4 GB 之间(§3.3.1)。论文只写 model state,未说明是否含优化器状态 推断;405B 模型的完整 checkpoint 只会更大。
PyTorch Distributed Checkpoint(DCP)的 save 明确按 SPMD 工作:每个 rank 只保存本地 shard,不把所有状态 gather 到 rank 0(torch/distributed/checkpoint/state_dict_saver.py:89)。checkpoint 的元数据记录每个 tensor 的 chunk offsets/sizes、planner data 和 storage data(torch/distributed/checkpoint/metadata.py:154;chunk 结构见 metadata.py:24)。加载时按元数据把 shard 映射回当前模型的切分。
Megatron 的 dist_checkpointing 把“sharded state dict”同时当作保存输入和加载指南:load 的文档说,加载需要现有的 sharded state dict 作为 guidance(megatron/core/dist_checkpointing/serialization.py:78);保存和加载分别由 TorchDistSaveShardedStrategy 和 TorchDistLoadShardedStrategy 实现(megatron/core/dist_checkpointing/strategies/torch.py:684、:924)。非 sharded 的公共状态在 current format 里作为 common_state ShardedObject 存进 checkpoint,legacy 才走 common.pt(serialization.py:212)。
DeepSpeed 的 ZeRO checkpoint 按 DP rank 写文件,文件名形如 zero_pp_rank_<dp>_mp_rank_<mp>_optim_states.pt(deepspeed/runtime/engine.py:4358;前缀常量见 deepspeed/checkpoint/constants.py:41)。加载时先读 latest 文件拿到 tag,再按文件加载(deepspeed/runtime/engine.py:4426)。
Nanotron 的文档则直接承认:checkpoint 不是 topology-agnostic,因为 random states 和 sharded tensors 都和切分方式绑定;他们计划用 checkpoint_reshape 解决(nanotron docs/docs.md)。这解释了弹性训练为什么难:换卡数不是“多开几个进程”,而是要把旧切分映射到新切分。
DCP 的 async_save 先做 staging:把 state_dict 复制到 staging storage(默认 CPU 内存),然后在独立线程里调用 save(torch/distributed/checkpoint/state_dict_saver.py:221)。staging 的目的就是制造一个“training-safe”的快照:复制完成后,训练继续更新参数,不会影响正在序列化的那份数据(torch/distributed/checkpoint/staging.py:40)。
Megatron 的 --async-save 走 dist_checkpointing.save(..., async_sharded_save=True, ...)(megatron/training/checkpointing.py:945);如果上一次异步保存还没结束,它会打印警告,建议增大 checkpoint 间隔(checkpointing.py:696)。这说明异步不是无限提速:写入队列会堆积,后台写和下一轮训练会争 CPU 内存、网络和存储带宽。
把三种方案放在一起看:同步 checkpoint 的停顿约等于写完整状态的时间;异步 checkpoint 的停顿约等于 staging 复制的时间,磁盘写入与后续训练 overlap;但异步需要双缓冲/额外 CPU 内存,而且写不完时仍会反压。checkpoint 频率的上限,最终由“写带宽 + 可用内存”决定,而不是由愿望决定。
读完这节你应该知道:把 MTBF、checkpoint 间隔、单次停顿和总时长放在一起,怎么算预期故障数、平均丢失工作和有效训练时间。
把故障看成随机到达,并假设 checkpoint 间隔固定为 I 小时、每次 checkpoint 让训练停顿 C 小时、集群平均无故障时间为 MTBF 小时。故障落在两次 checkpoint 之间时,平均要丢掉半个间隔的工作量;checkpoint 本身也要占时间。于是有效训练时间比例可以写成:
第一项是平均丢失工作量,第二项是 checkpoint 停顿;推导假设故障均匀落在间隔内,忽略恢复时间与故障间的相关性。
对 I 求导,最优间隔满足 I* = √(2 × C × MTBF)。这个式子说明:checkpoint 越便宜(C 越小),最优间隔越短;MTBF 越长,间隔也可以放长。但两边都是“按本页模型”的结论,不是论文的实测参数。
下面的计算器把四个输入放在一起。MTBF 和总时长的默认值来自 §3.3.4 的 419 次/54 天折算;checkpoint 间隔和停顿是可调假设,不是论文报告值。
预期故障数 = 1296 / 3.09 = 419.4175 → 显示 419.42。
平均丢失/次 = 1.00 / 2 = 0.50 h;总丢失 = 419.4175 × 0.50 = 209.7087 h → 209.71 h。
checkpoint 次数 = 1296 / 1.00 = 1296;总开销 = 1296 × 0.05 = 64.80 h。
有效比例 = (1296 − 209.7087 − 64.80) / 1296 × 100 = 78.8188% → 78.82%。
JS 在相同输入下显示:419.42 / 0.50 h / 64.80 h / 78.82%。
预期故障数 = 1296 / 3.09 = 419.4175 → 419.42。
平均丢失/次 = 0.28 / 2 = 0.14 h;总丢失 = 419.4175 × 0.14 = 58.7184 h → 58.72 h。
checkpoint 次数 = 1296 / 0.28 = 4628.5714(连续近似,实际次数取整);总开销 = 4628.5714 × 0.0125 = 57.8571 h → 57.86 h。
有效比例 = (1296 − 58.7184 − 57.8571) / 1296 × 100 = 91.0050% → 91.00%。
JS 在相同输入下显示:419.42 / 0.14 h / 57.86 h / 91.00%。
注意这不是对 Llama 3 的复算:论文没有报告 I 和 C,也没有把 419 次中断逐次对应到丢失的 step。这个账本只回答“如果 MTBF 是 3.09 小时,间隔和停顿取这些值,损失大概是多少”。
读完这节你应该知道:一次故障从被检测到训练恢复,中间经过哪些步骤,每一步依赖什么状态。
把 Llama 3 的描述和三个框架的接口放在一起,恢复可以拆成六步:
latest 拿 tag(deepspeed/runtime/engine.py:4426);Megatron 用当前模型的 sharded state dict 作为加载指南(megatron/core/dist_checkpointing/serialization.py:78);DCP 用元数据里的 chunk offsets/sizes 把保存时的 shard 映射到当前切分(torch/distributed/checkpoint/metadata.py:24)。这条链路里,最容易被低估的是第 4 步和第 6 步:加载不是“读一个文件”,而是按当前拓扑重组 shard;健康检查不是“能启动就行”,而是要把静默错误和慢节点挡在训练之外。
读完这节你应该知道:论文在什么规模、什么时间窗口、用什么口径报告了容错效果,以及哪些参数它没有报告。
规模与配置:Llama 3 405B,最多 16,384 张 H100(Table 4)。Table 4 同时给出 MFU:8,192 卡、DP=64 时 43%;16,384 卡、DP=128 时 41%;16,384 卡、CP=16、序列 131,072 时 38%。卡数翻倍后 MFU 从 43% 到 41%,差 2 个百分点(43 − 41);16,384 卡的长序列配置比 8,192 卡的 43% 低 5 个百分点(43 − 38)。论文把 16K 卡上 MFU 的小幅下降归因于保持全局 tokens/batch 不变时每 DP 组的 batch 变小(§3.3.2)。
Table 4 · Llama 3 405B 预训练的并行配置与 BF16 MFU(照录原表)
| GPUs | TP | CP | PP | DP | Seq. Len. | Batch/DP | Tokens/Batch | TFLOPs/GPU | BF16 MFU |
|---|---|---|---|---|---|---|---|---|---|
| 8,192 | 8 | 1 | 16 | 64 | 8,192 | 32 | 16M | 430 | 43% |
| 16,384 | 8 | 1 | 16 | 128 | 8,192 | 16 | 16M | 400 | 41% |
| 16,384 | 8 | 16 | 16 | 8 | 131,072 | 16 | 16M | 380 | 38% |
容错结果:在 54 天的预训练快照里,466 次作业中断,47 次计划中断,419 次意外中断;有效训练时间高于 90%;约 78% 的意外中断归因于已确认或疑似的硬件问题;GPU 问题占 58.7%;只有 3 次需要重大人工干预(§3.3.4)。论文把有效训练时间定义为“有用训练时间 / 总耗时”,并说明自动化维护(固件、Linux 内核升级)至少每天造成一次中断(§3.3.4)。
Table 5 · 54 天内意外中断的根因分类(照录原表;Faulty GPU 行的 148 与 30.1% 存在算术对账问题,见下方折叠块)
| Component | Category | Interruption Count | % of Interruptions |
|---|---|---|---|
| Faulty GPU | GPU | 148 | 30.1% |
| GPU HBM3 Memory | GPU | 72 | 17.2% |
| Software Bug | Dependency | 54 | 12.9% |
| Network Switch/Cable | Network | 35 | 8.4% |
| Host Maintenance | Unplanned Maintenance | 32 | 7.6% |
| GPU SRAM Memory | GPU | 19 | 4.5% |
| GPU System Processor | GPU | 17 | 4.1% |
| NIC | Host | 7 | 1.7% |
| NCCL Watchdog Timeouts | Unknown | 7 | 1.7% |
| Silent Data Corruption | GPU | 6 | 1.4% |
| GPU Thermal Interface + Sensor | GPU | 6 | 1.4% |
| SSD | Host | 3 | 0.7% |
| Power Supply | Host | 3 | 0.7% |
| Server Chassis | Host | 2 | 0.5% |
| IO Expansion Board | Host | 2 | 0.5% |
| Dependency | Dependency | 2 | 0.5% |
| CPU | Host | 2 | 0.5% |
| System Memory | Host | 2 | 0.5% |
Table 5 的 18 行按原文转录;原表中 Faulty GPU 的计数与百分比不能同时成立,下方折叠块给出算术对账。本页不替原文改数。
原表 Faulty GPU 行:148 次,30.1%。按 419 次意外中断计算,30.1% × 419 ≈ 126 次;148 / 419 = 35.3%。两个数不能同时成立。
论文正文写 GPU 问题占 58.7%。把 Table 5 的 GPU 类别相加:
本页照录原表的 148 和 30.1%,不替原文改数;上面两组数只是把原表内部的算术关系摆出来。
三个反直觉点:
论文正文与 Table 5 未报告 checkpoint 间隔、单次 checkpoint 停顿和 checkpoint 总写入量;这些是本页账本的输入假设,不是论文实测值。
读完这节你应该知道:checkpoint 能解决什么、不能解决什么,以及在哪些条件下账本会失效。
Llama 3 的存储 fabric 是 240 PB、持续 2 TB/s、峰值 7 TB/s;checkpoint 写入高度突发,会在短时间打满 fabric(§3.3.1)。这意味着 checkpoint 频率不能只看“我想丢多少工作”,还要看“存储能不能在窗口内写完”。模型越大、优化器状态越多,单次 checkpoint 越大;保留多个历史 checkpoint 还会成倍占用容量。
写放大是结构性的:每次 checkpoint 都重写完整状态,即使大部分参数在两次 checkpoint 之间只变了一点点。分片并行写把带宽摊到多个 rank,但不减少总字节数;异步写把写入移出关键路径,也不减少总字节数,只是把压力挪到后台。
异步 checkpoint 的核心难点是“训练继续跑,但写出的必须是同一时刻的快照”。DCP 的做法是先 staging 到 CPU 内存,形成一个 training-safe 的副本,再在后台序列化(torch/distributed/checkpoint/staging.py:40)。如果直接在训练更新中的 tensor 上写,checkpoint 可能包含不同 step 的混合状态。写 checkpoint 期间发生故障,还可能留下不完整的目录;DeepSpeed 用 latest 文件指向完整 tag(deepspeed/runtime/engine.py:4426),Megatron/DCP 也各自有元数据和完整性校验。
弹性训练允许作业在节点故障后缩容/扩容,但 checkpoint 的切分方式会变。Nanotron 文档明确说 checkpoint 不是 topology-agnostic,因为 random states 和 sharded tensors 都与 TP/DP/PP 切分绑定(nanotron docs/docs.md);换卡数需要 reshard 或 checkpoint_reshape。DCP 的元数据(chunk offsets/sizes)和 resharding 模块为换切分提供了基础(torch/distributed/checkpoint/resharding.py:1),Megatron 的 FullyParallelLoadStrategyWrapper 也支持按新切分加载(megatron/core/dist_checkpointing/strategies/fully_parallel.py:163);但“换拓扑”不是免费的,恢复时间会变长。
不是所有故障都会让作业崩掉。Llama 3 的 Table 5 里 Silent Data Corruption 6 次(1.4%);论文还提到“仍然工作但变慢”的 straggler,单张慢卡可以拖慢数千张卡(§3.3.4)。这类问题不会触发重启,却会悄悄拉低有效训练时间,甚至把错误写进 checkpoint。节点健康检查、NCCL flight recorder、通信超时和 straggler 排查是 checkpoint 之外的补丁。
最后,checkpoint 不能解决模型发散、数据管道 bug、相关性故障(例如网络设备同时影响多个 rank)和 checkpoint 文件本身的损坏。它把“故障后能否继续”变成工程问题,不把“故障不会发生”变成事实。