你将运行的内容
Kernodeck 迷你项目包含一个小型合成数据集、一个带 dropout 的网络、一个训练循环和一个验证器。该流程强制使用 CPU,以隔离保存与恢复逻辑。它并不构成对 CUDA、ROCm、多卡或租用 GPU 性能的验证。
验证器会为连续流程、中断、完整恢复,以及一个不恢复随机数生成器的反例分别启动全新进程。最后一项的意义在于检验该检查能否检测出不完整的恢复,即使权重和步数看起来正确。
左右滚动表格即可查看所有列。| 流程 | 执行 | 验证的问题 |
|---|---|---|
| 连续 | 从初始状态进行 10 次更新。 | 不中断时能达到什么状态? |
| 中断 | 5 次更新,然后保存并停止。 | 中间点是否包含预期状态? |
| 完整恢复 | 新进程,加载第 5 步的检查点,然后进行 5 次更新。 | 在所选容差内,是否得到相同的输入、学习率和参数序列? |
| 不恢复 RNG 的恢复 | 新进程,使用相同的恢复点,但省略随机状态恢复。 | 该测试是否能检测到单纯加载权重会遗漏的漂移? |
前置条件与流程启动
下载压缩包,将其解压到工作目录,然后进入包含 train.py 和 verify_resume.py 的文件夹。使用安装了 PyTorch 和 NumPy 的 Python 环境。压缩包包含代码和合成数据;它不会下载任何模型,运行该练习也不需要 Kernodeck 账户。
所提供的证据是在 Python 3.14.6、PyTorch 2.11.0+cu128 和 NumPy 2.4.4 下执行的。程序强制使用 CPU、float64 精度和单线程 PyTorch。因此软件包后缀并不意味着恢复过程使用了 CUDA。在其他环境中,请运行您自己的验证。
选择一个尚不存在的输出目录。每个流程都会生成 checkpoint.pt、其哈希文件 checkpoint.pt.sha256 和 summary.json。验证器会将对比结果汇总到 verification.json。--steps 选项表示额外的步数:在第 5 步中断后,恢复命令会再执行 5 步,从而达到 10 步。Python 选项 -B 可避免在练习目录中生成字节码缓存。
python -B verify_resume.py --output runs/preuve-cpupython -B train.py --steps 10 --output runs/continu
python -B train.py --steps 5 --output runs/coupure
python -B train.py --steps 5 --resume runs/coupure/checkpoint.pt --output runs/reprise权重导出与恢复检查点的作用不同
首先,请选择您想要找回的内容。用于推理的导出旨在使用训练好的模型生成预测。训练恢复还必须恢复决定后续更新的状态。而基于文件的推理处理则需要一份可靠的已完成元素列表。这三种需求会产生不同的保存内容。
不要把这种持久化保存与激活检查点(activation checkpointing)混淆。后者通过在前向传播时丢弃部分激活值并在反向传播时重新计算来减少内存占用;仅靠它本身并不会生成可在中断后恢复的文件。因此,请在项目中明确“checkpoint”一词指的是内存优化还是恢复点。
需要放在一起的状态
模型的 state_dict 包含已注册的参数和缓冲区;优化器有自己独立的状态。在这里,Adam、StepLR、dropout 和三个随机数生成器都会影响后续的更新。检查点必须对所有这些都是同一时刻的表示。
还要记录代码版本、实验参数以及数据标识。在一个 epoch 中间,只知道 epoch 编号是不够的:必须能够找回样本的顺序以及下一个要消费的批次。这里出错可能会跳过某些条目或对它们处理两次。
这 24 行数据集描述了两个变量与一个目标之间的合成关系。网络共有 33 个参数,包含一个 8 神经元的层和 0.25 的 dropout。每个 batch 包含四行。经过五次更新后,游标停在 24 中的第 20 位:断点位于一个 epoch 的中间。十次更新消耗了 40 个观测值,这迫使校验过程跨越一次新的数据置换。
左右滚动表格即可查看所有列。| 状态 | 角色 | 需要执行的检查 |
|---|---|---|
| 型号 | 保留权重和缓冲区。 | 比较最终参数和一次评估输出。 |
| 优化器 | 保留下一次更新所使用的状态。 | 验证其重新加载,而不仅仅是其超参数。 |
| 调度器 | 延续学习率序列。 | 先比较下一次应用的学习率,再比较之后的学习率。 |
| Python、NumPy 和 PyTorch 的 RNG | 延续实际使用到的抽样。 | 验证不做恢复的负向演练确实会发散。 |
| 数据 | 恢复置换和游标。 | 比较断点之后的输入标识。 |
| 进度 | 解释步数和 epoch。 | 总共达到 10 次更新,既不重复也不遗漏。 |
| 配置 | 重建相同的实验。 | 保留维度、精度、设置和版本。 |
按正确顺序恢复
在加载各自的状态之前,先重建模型、优化器和调度器。调度器必须在 optimizer.load_state_dict() 之前创建:否则它的构建可能会覆盖已恢复的学习率。还要重新加载它自身的状态,然后验证下一步实际使用的学习率。
在构建了会消耗抽样的对象之后、就在继续工作之前,恢复随机数生成器。仅仅把初始种子设回去会让序列从头开始;这并不是找到第五次更新后达到的状态。在你的项目中,找出所有使用到的生成器,包括数据变换和数据加载所用的生成器。
在这个练习中,Python 为输入设置一个轻微的增益,NumPy 的 PCG64 生成器产生噪声和置换,而 PyTorch 产生 dropout。检查点保存它们在断点处达到的状态。校验器还会观察它们的下一次抽样,并立即恢复状态,以免干扰后续计算。
optimizer = torch.optim.Adam(model.parameters(), lr=0.03)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.5)
# 在恢复流程中,构建对象之后:
state = load_checkpoint(resume)
model.load_state_dict(state["model"])
scheduler.load_state_dict(state["scheduler"])
optimizer.load_state_dict(state["optimizer"])
progress = state["progress"]
history = state["history"]
restore_rng(state["rng"], generator)
model.train()选择一致的保存边界
设定一个明确的边界,例如在一次完整的优化器更新之后。如果你在该更新之前累积了多个 microbatch,在中间保存就还必须处理中间状态。当首次实现在一个已消耗完累积梯度的边界处保存时,更容易验证。
保留多代备份。将新文件写入一个不同的名称,等待写入完成,确认其可读,然后将其标记为可用。在此检查完成之前,不要替换您唯一有效的检查点。频率取决于您愿意重做的工作量以及观察到的写入时间;不能仅凭租用时长来推断。
该迷你项目在一次迭代完成后进行保存,然后导出文件及其指纹。它为每个运行过程使用新文件夹,不会替换先前的证据。如果你的训练使用带 GradScaler 的混合精度,其状态也属于恢复的一部分。此变体不在 CPU 练习的覆盖范围内。
阅读对比及其容差
该协议对比第 5 步之后的续跑:消耗的数据、学习率、损失以及达到的参数。仅步骤编号一致是不够的。重置的优化器可以继续循环,同时产生不同的更新。
本练习选择的容差是绝对容差:1e-12,相对容差为 0。该阈值是所提供的 CPU 协议的一部分;它并不是适用于你模型的通用规则。比较必须能标记非有限值以及结构差异,而不是静默接受无法使用的输出。
PyTorch 不保证不同版本、平台、CPU 与 GPU 之间结果一致。如果你移植练习,请在目标上重新做验证,并说明所采用的容差。不要仅仅为了让一个你尚未弄清原因的失败消失而放宽阈值。
在所提供的证据中,完整运行过程的所有偏差均为零:参数、优化器状态、损失、学习率和 MSE。行的顺序、进度、调度器状态以及后续抽样也一致。第 10 步之后的下一步学习率在两次运行中均为 0.00375。因此,结果并不仅仅取决于某个可能掩盖中间差异的最终指标。
左右滚动表格即可查看所有列。| 对比 | 完整恢复 | 不恢复 RNG 的续跑 |
|---|---|---|
| 权重最大偏差 | 0 | 0,011669328447718508 |
| 最终 MSE | 0,09538858591775097 | 0,0936034144665111 |
| MSE 相对连续运行的偏差 | 0 | 0,001785171451239867 |
| 一致性子测试的判定结果 | 在 1e-12 容差内一致 | 检测到偏差 |
为什么要保留未恢复随机性的反例
当你清楚某项检查能检测出哪种错误时,它才更有用。该反例变体重新加载相同的权重、优化器状态、调度器和进度,但有意省略 RNG 恢复。进程可以在没有 Python 异常的情况下结束,同时走上另一条轨迹。
在所提供的证据中,这一省略导致权重最大偏差大于 0.011,MSE 差异大于 0.0017。此处反例的 MSE 低于连续运行:这并不能说明续跑是正确的。目的在于复现同一次实验,而不是按最终误差给两个模型排名。
仅当完整运行过程一致且反例发生偏差时,验证器才会成功。此时它会输出 all_checks_passed: true、positive: true 和 negative_divergence_detected: true。协议成功时退出码为 0,比较失败时为 1,无法完成验证时为 2。
python -B train.py --steps 5 --resume runs/coupure/checkpoint.pt --omit-rng-restore --output runs/reprise-incomplete加载练习文件时不放宽保护措施
该项目仅加载你通过本练习创建并置于你控制之下的检查点。它显式使用 torch.load(..., map_location="cpu", weights_only=True)。Python 状态包含基本类型,NumPy PCG64 生成器状态包含整数和字符串,PyTorch CPU 状态包含一个字节张量。保存的 RNG 状态中没有放入任何任意 NumPy 数组。
加载器会校验关联的指纹、大小、架构、进度、版本以及代码和数据的身份。它会拒绝不一致的状态,而不是静默地重置缺失的元素。指纹能检测出修改,但不会验证文件的发送方身份。
不要仅仅为了消除加载错误就添加 weights_only=False。保存的格式与其重建过程必须保持一致。受限加载减少了反序列化的可能性,但并不会让一个未知文件变得可信。
分布式训练有哪些变化
当存在多个进程或状态分布在多个 GPU 上时,请确认谁写入了什么。由单个进程生成的文件不一定是分布式工作的完整备份。请使用你所采用策略中规定的备份流程,并等待相关参与方完成。明确标识属于同一恢复点的各个片段。
改变 GPU 数量可能需要重新分配状态,并改变数据的分片方式。分布式检查点机制可以处理某些变化,但这一可能性必须针对你的格式和配置进行验证。请在目标环境中做一次加载测试。在订单中增加批量并不会自动把单卡存档变成分布式程序。
以真正可恢复的导出收尾
在截止时间之前,请将有用的检查点连同其配置、指标、加载说明和数据标识符一起导出。核对已复制文件的大小和指纹,然后至少从目标位置加载一次备份。指纹一致可验证复制无误;重新加载则能确认内容确实足以重建工作。
只加载你已知来源的文件,并选择与之一致的格式及反序列化选项。在新的恢复点通过你的检查之前,请保留上一个已验证的恢复点。预期的输出是一个可恢复的文件夹以及一份简短的恢复证明:执行的命令、找回的步骤、通过的检查以及导出的结果。请把这部分时间纳入你的 3、7 或 30 天周期中。
证据范围与租赁选择
2026 年 9 月 24 日的证明在 CPU 上比较了四个全新进程,绝对容差为 1e-12,且无相对容差。它不覆盖 CUDA、ROCm、AMP、分布式训练或数据加载 worker。它验证的是所提供版本在所述环境中的恢复逻辑,并不衡量所租 GPU 的能力。
完成这个小练习后,请把同样的协议套用到你自己的模型、数据和后端上。80 GB 或 192 GB 的显卡并不能修正不完整的检查点:先选择兼容的链路,再根据真实步骤的内存占用进行规划。下面链接的套餐并非作为本次证明中经过测试的硬件来呈现。
在你的 3、7 或 30 天周期中,请预留第一轮“备份–停止–恢复”以及最终导出的时间。有用的输出是一个文件夹,你能说明其中的版本、恢复点、对比检查及其局限;仅有一个 .pt 文件并不能带来这种保证。