Checkpoint
沿 PyTorch DCP 理解保存与恢复
核心解释
Checkpoint 保存训练在某一时刻的状态,让任务中断后能够继续。除了模型权重,还要接上优化器历史、学习率进度、随机状态和下一批数据。这里讨论训练状态的保存与恢复;用重计算换显存的 activation checkpointing 属于另一项技术。[1]
PyTorch Distributed Checkpoint(DCP)接收这些状态,协调各 rank 写出局部分片。保存计划决定谁写哪些数据,元数据记录张量及分片的位置。加载时先创建目标模型和优化器,DCP 再按目标布局读取所需区间,因此保存与恢复可以使用不同的分片方式。[2]
异步保存先取得稳定快照,再让后台写入与训练重叠。DCP 将状态搬到 CPU 缓冲区的过程称为 staging。采用异步 staging 时,源状态在拷贝完成前保持不变;拷贝完成后,训练可以修改源状态,后台继续写入这份快照。[3]
“训练可以继续”和“checkpoint 已保存完成”发生在不同的时刻。DCP 的默认文件系统路径先完成分片写入,再提交元数据。每次保存使用独立目录,收到成功完成信号后再更新 latest,恢复程序便能找到一份完整状态。[4]
优化保存要同时看训练停顿、后台写入耗时和恢复耗时。后台任务仍占用 CPU、内存与 I/O 带宽。最终验证也要走到下一次训练更新:加载对象、取出下一批数据,再检查 loss 和参数更新是否符合预期。[5]
资料
PyTorch:从吞吐曲线理解异步保存 ↗
先看 staging 与后台写入对应的吞吐变化,再理解进程隔离和保存计划缓存各自解决什么问题。
DCP 入门:保存分片,再按目标布局加载 ↗
从模型和优化器的 state_dict 写起,串起 Stateful、save、load 与加载时重新分片的完整流程。
DCP 异步教程:快照、后台写入与等待 ↗
重点读 CPU 缓冲区、并发保存数量和异步 staging 示例。把 staging_completion 与 upload_completion 对到训练循环。
DCP 源码:一次保存怎样走到文件系统 ↗
沿 state_dict_saver.py 的计划、写入、完成三个阶段阅读,再到 filesystem.py 看数据文件和元数据怎样提交。
StatefulDataLoader:恢复到下一批数据 ↗
看 sampler、随机变换与 iterable dataset 怎样保存状态,以及多个 worker 的状态怎样汇总和恢复。
CheckFreq:保存频率与两阶段快照 ↗
读论文中的 snapshot / persist 流水线和可恢复迭代器,再看保存频率如何随开销调整。
DataStates-LLM:让快照拷贝与训练重叠 ↗
图 3–5 对比几种保存时间线。重点看怎样利用参数暂不更新的窗口,将 GPU 到 CPU 的拷贝移出关键路径。
ByteCheckpoint:把分片表示与训练布局分开 ↗
读 §3 的全局元数据与加载时重分片,理解 checkpoint 怎样跨越训练恢复、评估和不同训练阶段。
QA
保存了模型权重,为什么还不能接着训练?
下一次更新还取决于优化器历史、学习率进度、随机状态和下一批数据。Adam 的一阶、二阶矩重置后,即使模型权重相同,下一次更新也会改变。使用动态 loss scaling 时,还要恢复 scaler 状态。
DCP 保存你交给它的状态。模型和优化器可以通过 Stateful 封装;scheduler、训练步数、RNG 和数据进度也要接入保存与加载。模型配置、tokenizer 和代码版本则用于重建运行环境。
一个 checkpoint 应该对应训练中的哪个时刻?
最容易管理的边界是一次完整的参数更新结束后:模型、优化器、scheduler 和训练步数都推进到同一步,数据状态指向下一批。保存接口应在这组状态一致时调用。
若在梯度累积中途保存,还要记录已累积的梯度、microbatch 位置及相关状态。仅保存模型和优化器,会丢掉这次尚未完成的更新。
调用 state_dict(),就已经得到独立快照了吗?
state_dict() 中的张量仍可引用训练正在使用的存储。把这个字典交给后台线程后继续修改参数,后台读到的值就可能跨越不同训练步。
DCP 的 staging 将状态复制到供保存使用的缓冲区。源状态在复制完成前保持不变,复制完成后,训练才能修改这些源张量。
async_save 返回、staging 完成、upload 完成,有什么区别?
async_save 返回表示调用已交还控制权。采用异步 staging 时,staging_completion 表示本地状态拷贝完成;upload_completion 表示这次 checkpoint 保存完成。同步 staging 路径通常返回一个代表保存完成的 Future,使用时要按所用版本和配置区分。
训练应在下一次修改源状态之前等待 staging 完成。参数通常在 optimizer.step() 更新,但模型 buffer、RNG 或数据状态可能更早变化。退出训练、释放保存缓冲区或复用同一 stager 前,还要等待保存完成并检查异常。
DCP 可以只让 rank 0 调用 save 吗?
默认分布式保存需要参与进程组中的各 rank 一起调用。每个 rank 提供自己的局部分片,DCP 协调保存计划和写入结果。协调者整理元数据,并不意味着所有模型张量都要先汇集到 rank 0。
已经收集成完整模型的单进程导出是另一种调用路径。它需要承担汇集和完整副本的开销,不能替代分片训练中的集体调用。
各 rank 都放一个 rng 或 dataloader 字段,DCP 会分别保存吗?
默认 Planner 会按键处理重复的普通值。相同键下,各 rank 内容不同的 RNG 或数据状态,不能直接当成同一份共享状态。
一种实现方式是先汇总各 rank 的小型状态,形成所有 rank 一致的映射,再交给 DCP。加载后每个 rank 取回对应项。需要改变并行度时,还要定义状态怎样映射到新 rank;简单按旧 rank 编号取值就不够了。
从 8 张卡恢复到 4 张卡,DCP 怎样找到对应的数据?
先按新的并行布局创建模型和优化器,再提供目标 state_dict。DCP 根据保存的全局张量元数据与目标分片,计算需要读取的区间。每个 rank 读取自己需要的部分,不要求它沿用原来的文件编号。
这依赖稳定的参数名、全局形状和受支持的分片表示。优化器状态也需要对应同一组参数。框架内部的扁平 buffer 还可能绑定原布局,例如 Megatron 的 dp_reshardable 与 fully_reshardable 支持范围就不同。
目录里已有 .distcp 文件,就能把它设为 latest 吗?
数据文件出现时,其他分片仍可能在写入。在默认集体保存路径中,DCP 等待各 rank 的写入结果,再调用 FileSystemWriter.finish() 写元数据,最后将临时元数据文件改名为 .metadata。
每次保存使用独立目录,等完成信号成功后再更新 latest。这样,写入中断时仍保留上一份完整 checkpoint。文件同步、重命名和持久性的保障取决于存储后端;关闭 sync_files 会改变故障后的保障。
记住 global_step 和随机种子,数据就能从原位置继续吗?
只有数据顺序能由这些信息完整重建时才够用。流式数据、随机变换和序列拼接还可能有迭代器游标、worker RNG 与尚未用完的 token buffer。checkpoint 要对应训练已经消费的数据,而不是后台预取已经读到的位置。
StatefulDataLoader 提供状态接口,并汇总 worker 状态。它的 iterable dataset 示例要求恢复时保持相同 num_workers。改变 DP 大小或 worker 数,需要数据管线另行支持状态重分配。
Adam checkpoint 的大小应该怎样估算?
按实际保存的张量逐项相加。设有 个参数,同时保存 BF16 权重、FP32 主权重和两个 FP32 动量,则这几项共 字节。70B 参数约为 980 GB,采用十进制单位。
这是该状态组合的未压缩张量大小。优化器可能省略或重建部分副本;共享存储、分片副本和文件格式也会改变磁盘占用。更新边界上的 checkpoint 通常不需要梯度,不能直接把训练显存估算当作文件大小。
load 没报错,怎样验证这份 checkpoint 真能恢复?
在固定配置下,从同一步分成连续训练和保存后重启两条路径。先核对模型、优化器、scheduler 和数据位置,再比较下一批样本、loss 与更新后的参数。这个对照能发现漏存状态和加载顺序错误。
验证故障恢复时,再中断一次正在写入的保存,检查程序是否选回上一份完整 checkpoint。换卡数时重做数据进度与分片检查。逐位一致还取决于算子、通信归约顺序和软件版本。