Repository navigation
Conversation
The fully-async loop advances global_step before its final saves and before the save on an early epoch end, so those checkpoints and HF models were named one step past the last trained step. Save them under the last trained step, as the synchronous trainer does, and skip a save when that step already has one. Signed-off-by: Corey Hu <corey.hu@scale.com>
There was a problem hiding this comment.
Code Review
This pull request prevents duplicate checkpoint and model saves by tracking the last saved steps and adjusting 'self.global_step' to ensure saves are named after the last trained step. It also adds corresponding unit tests. The reviewer suggested wrapping the save operations in a try-finally block during early epoch termination to ensure 'self.global_step' is always restored even if an exception occurs.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 4ba4dda. Configure here.
The early epoch-end path lowers global_step to name the save after the last trained step. Put the restore in a finally block so a failed save leaves global_step naming the next step, as any other failure in the loop does. Signed-off-by: Corey Hu <corey.hu@scale.com>
Three cases left a resumed run with the wrong state after checkpoints were named after the last trained step: - Prompts filtered after the last trained step's checkpoint, when the epoch then ends early, were not recorded. Rewrite only that checkpoint's fully_async_state.pt so they are, without saving the model again. - An epoch that ended before any step trained saved global_step_0, which resume ignores. Nothing is saved before the first trained step. - Resuming a run with nothing left to train (step budget spent or every epoch done, including an epoch that ran out of prompts early) re-saved its last HF export. It now returns before syncing weights, generating or saving, like the max_training_steps early return in NovaSky-AI#2362. Signed-off-by: Corey Hu <corey.hu@scale.com>
A checkpoint loaded on resume can belong to another run or be a shared base (resume_mode=from_path), so it is never modified. If an epoch ends early after resuming and before any new save, the prompts filtered since are redrawn on the next resume instead. Signed-off-by: Corey Hu <corey.hu@scale.com>

Summary
The fully-async trainer advanced
global_stepbefore its final saves, and before the save it makes when an epoch runs out of groups early (sample_full_batch), so those checkpoints and HF exports were named one step past the last trained step. They are now saved under the last trained step, as inRayPPOTrainer, and a step that already has a checkpoint is not saved again. Around that:fully_async_state.ptis rewritten, so resume skips them. Model weights are not saved again, and a checkpoint loaded on resume is never modified.global_step_0).max_training_stepsearly return.Why
On main, two trained steps produce
global_step_1,global_step_2and an extraglobal_step_3, andmax_training_steps=3ends atglobal_step_4, so a resumed run starts one step late.Testing
pytest tests/train/test_fully_async_trainer.py: 23 passed; 9 of the 10 new cases fail on main (the ninth checksglobal_stepafter a failed save).tests/train/ tests/backends/skyrl_train/,-m "not vllm"): 2232 passed, 41 skipped (2222 on main); the same 2 Megatron import failures occur on main. pre-commit clean.This overlaps #2362 (the early return and the fully-async
save_checkpoints), so whichever lands second needs a small merge infully_async_trainer.py.AI assistance: Claude Code ported this from our patch set, wrote the tests, and drafted this description.
Note
Medium Risk
Changes checkpoint/resume semantics and when fully-async state is rewritten; mistakes could skew resume step counts or skip/repeat training, though behavior is heavily covered by new unit tests.
Overview
Aligns fully-async checkpoint and HF export naming with
RayPPOTrainer: saves use the last trained step instead of the loop’s “next” step, skips duplicate saves for a step already checkpointed, and avoidsglobal_step_0when nothing has trained yet.The
train()loop trackslast_ckpt_step/last_hf_step(andlast_ckpt_dirfor in-run checkpoints). On early epoch end undersample_full_batch, it decrementsglobal_stepfor the save, then restores it; if prompts were filtered after the last checkpoint, it may rewrite onlyfully_async_state.pton a checkpoint this run wrote—never the weights or a checkpoint loaded on resume. Resume can exit immediately when_nothing_left_to_train()(newis_epoch_complete()on the async dataloader) says the step budget or epochs are done.Checkpoint helpers are split into
_fully_async_state()and_write_fully_async_state(). Tests add stubbedtrain()cases for naming, early epoch end, filtered-UID state updates, and no-op resume.Reviewed by Cursor Bugbot for commit 8f700eb. Bugbot is set up for automated code reviews on this repo. Configure here.