fix(train): crash-safe checkpoints — completeness marker gating resume#739
fix(train): crash-safe checkpoints — completeness marker gating resume#739jessiewei7 wants to merge 2 commits into
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds a checkpoint completeness marker mechanism to BaseCheckpointer, gating resume/previous-epoch detection on the presence of a ChangesCheckpoint Completeness Marker
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/speculators/train/checkpointer.py`:
- Around line 123-132: Clear stale complete markers before rewriting an existing
checkpoint epoch so resume logic in the checkpoint scanner does not trust a
partially rewritten directory. Update the checkpoint save flow in the save
method that writes to self.path / str(epoch) to either remove or replace
COMPLETE_MARKER_FILENAME before writing model/optimizer/scheduler/metrics, or
write into a temporary epoch directory and atomically promote it after all
writes succeed. Ensure the marker is only recreated after the full save
completes, and keep the existing scan logic that checks COMPLETE_MARKER_FILENAME
aligned with this new incomplete-then-complete lifecycle.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f2d3e154-7ca3-42ed-a96f-f1da3d77b2ce
📒 Files selected for processing (3)
src/speculators/train/checkpointer.pysrc/speculators/train/trainer.pytests/unit/train/test_checkpoint.py
ad52938 to
94624cf
Compare
orestis-z
left a comment
There was a problem hiding this comment.
LGTM — reviewed design, fsync ordering, distributed rank-gating, and test coverage. The completeness-marker pattern is sound, the stale-marker edge case is handled, and the regression test covers the core crash scenario.
Signed-off-by: jessiewei7 <jessiewei747@gmail.com>
Mid-epoch saves rewrite the same epoch directory; without clearing, a crash during the rewrite leaves the previous save's marker next to partially rewritten files, defeating the marker gate. Signed-off-by: jessiewei7 <jessiewei747@gmail.com>
94624cf to
c24216a
Compare
|
Rebased onto latest main. CI needs maintainer approval to run whenever you have a moment — thanks! |
fix(train): crash-safe checkpoints — completeness marker gating resume
Summary
save_checkpoint()writesmodel.safetensors/optimizer_state_dict.pt/etc. directly to their final paths, and
_get_previous_epoch()(which resumeuses to find the latest checkpoint) trusts any directory whose name parses
as an integer — nothing distinguishes a fully written checkpoint from one
truncated by a crash mid-save.
Failure mode:
save_checkpoint()for epoch N is killed mid-save_pretrained()(OOM,preemption, node failure) — directory
N/now exists with a truncatedmodel.safetensors._get_previous_epoch()seesN, returns it.load_model_state_dict()hits the truncated file: best case safetensorsthrows a parse error and the run is stuck until someone deletes
N/byhand; worst case (
optimizer_state_dict.ptviatorch.load) a partialread surfaces as a confusing unpickling error.
training_state.jsondoes not guard this: resume only reads it to pickmid-epoch vs end-of-epoch, and a missing/corrupt one silently falls back to
"advance to next epoch" — still loading model/optimizer from the broken dir.
Fix
SpecForge had the identical bug class; the fix follows the same design as
sgl-project/SpecForge#659: a completeness marker written last, gating resume.
BaseCheckpointer.mark_checkpoint_complete(epoch): fsync every file in theepoch dir, then write a
checkpoint_completemarker (fsync it and thedirectory). The fsyncs matter: after a hard host crash, files can survive
at full length with truncated content, so the marker must not claim
completeness before the data is durable.
Trainercalls it once all files of an epoch are in place (model,optimizer, scheduler,
training_state.json, val metrics — the last writeshappen in the trainer, which is why the marker call lives there).
_get_previous_epoch()skips integer dirs without the marker, logging awarning with explicit instructions (
touch .../checkpoint_complete) —the same warn-and-instruct pattern already used for
interrupted/dirs.This also covers checkpoints from before this change.
Test plan
tests/unit/train/test_checkpoint.py(extends the existing suite, CPU-only):complete checkpoint (the crash scenario);
mark_checkpoint_completemakes a checkpoint visible to a freshcheckpointer;
maybe_save_checkpointend-to-end writes the marker.Full
tests/unit/train/suite passes (113 passed; 1 pre-existing failure isa hub-connectivity issue in
test_setup_model.py, unrelated).