feat(sc): recover replay buffer from native TQ checkpoints - #3480
Draft
macandro96 wants to merge 15 commits into
Draft
feat(sc): recover replay buffer from native TQ checkpoints#3480macandro96 wants to merge 15 commits into
macandro96 wants to merge 15 commits into
Conversation
…ting Port the replay-buffer checkpoint state capture from PR #3138 onto the split single-controller path. state_dict snapshots ready slots on the event loop, then fetches each group's DataPlane rows; unready reservations (in-flight rollouts) are dropped. load_state_dict validates the envelope (partition, group size, sample_id uniqueness) before any DataPlane write, truncates to the current capacity keeping the freshest groups, and re-puts rows while rebuilding the parallel slot lists. Staleness filtering is intentionally left to the sampler's first evict. Covered by 9 new unit tests (round-trip, preflight rejection, capacity truncation); 20/20 pass in tests/unit/single_controller/ test_tq_replay_buffer.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
Two additions the SC checkpointing path needs from the sampler layer: - resume_from_step: BaseSampler (and the three built-in policies + create_sampler) now accept the trainer step the run starts from — 0 for a fresh run, the restored current_step on resume. It seeds the dispatch cursor to preserve the fresh-start invariant _dispatch_index == trainer_version - 1. Without it a restored InOrderSampler stamps target_steps from 0 and every dispatched batch is instantly evicted (target < trainer_version), livelocking the train pump. create_sampler forwards the kwarg to custom samplers only on resume, so fresh starts don't constrain their constructors and an unsupported class fails loudly instead of silently running with an unseeded cursor. - supports_buffer_checkpoint: new PromptGroupSampler property gating replay-buffer save/restore. Only the ungated WindowedSampler returns True — gated policies dispatch a fixed quota per trainer step, so restored groups could never complete an already-consumed window. Covered by 8 new unit tests in test_sampler_interface.py (cursor seeding, gate behavior after resume, factory forwarding, custom fail-loud, checkpoint-support matrix). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
Replace the checkpointing NotImplementedError guard with the actual driver-side resume wiring, following the grpo.py setup pattern: - Build a CheckpointManager unconditionally and resolve the latest checkpoint: load_training_info() populates save_state (default GRPOSaveState when starting fresh) and get_resume_paths() yields the weights/optimizer paths. - _build_trainer takes kw-only weights_path/optimizer_path and forwards them to TQPolicy (previously hardcoded to None) on both the colocated and non-colocated build paths. - Restore the dataloader position from train_dataloader.pt when present (load_dataloader_state, with its dataset-swap guard); warn and start fresh otherwise. Runs before _clamp_max_num_steps as before. - Forward checkpointing.pretrained_checkpoint into the policy config. - SingleControllerActorArgs carries two new fields, save_state and last_checkpoint_path, for the actor-side restore (next step). Saving itself is not wired yet — that lands in the SingleControllerActor train pump next. Existing tests updated for the new surface: the setup tests' hand-built checkpointing block now carries the keys CheckpointManager indexes, and the pump tests pass the two new ActorArgs fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
Wire the actor side of SC checkpointing (the setup/resume half landed in the previous commit), with Megatron async_save supported end to end: - Restore: __init__ rebuilds counters (train_steps/trainer_version/ current_epoch/consumed_samples/total_valid_tokens) from the save_state loaded by setup, seeds the sampler with resume_from_step, and run() reloads replay-buffer groups (ungated samplers only, one capacity permit per restored group) before the pumps start. - Save: after each weight sync, _save_checkpoint mirrors async_grpo_train's block — finalize_pending flushes the previous background finalization, save_checkpoint returns after D2H staging under async_save, aux state (training info, dataloader position, replay buffer when the sampler supports it) is written synchronously, then begin_finalization defers the tmp->step rename until the async weight writes complete. run() flushes the last checkpoint via checkpointer.shutdown() on every exit path. - TimeoutChecker drives checkpoint_must_save_by: a timeout save also stops training early, matching the legacy loops. - latest_checkpoint_status.json is refreshed after each save for external watchdogs (reuses grpo's _write_latest_checkpoint_status). The pump tests' hand-built configs gain the checkpointing block the actor now reads (enabled=false keeps them write-free). Validated end to end on GB200 (Qwen3-0.6B, megatron async_save=true): 4-step run saves step_2/step_4 with no tmp_step_* leftovers; a checkpoint_must_save_by run stops early with a complete checkpoint; the resume run restores dataloader + 4 replay groups and continues from step 2 to step 4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
Port the checkpointing test suite from PR #3138 onto the split SC architecture (actor_args, the PromptGroupSampler protocol, the split trainer step API) and extend it for the async-save path: - counter/sampler-cursor restore, save triggers (period boundary, last step, checkpoint_must_save_by timeout, disabled, save_optimizer), metric_name handling, dataloader state round-trip with the dataset-swap guard, and setup resume wiring (get_resume_paths forwarded to the trainer factory, training_info.json loaded). - replay-buffer persistence is asserted against sampler.supports_buffer_checkpoint (windowed saves/restores with one capacity permit per group; gated samplers skip both sides). - new async-save coverage: the tmp->step rename stays deferred until finalize_async_save completes and is flushed by shutdown; a failed background finalization re-raises at the next save; _save_checkpoint records val_metrics into val_reward and a val:* metric_name. 32 tests, in-process actor with fakes (ray.cluster_resources patched); 108 passed together with the existing single_controller suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
On exit, run() now propagates a failed checkpoint finalization only on the clean path; when an exception is already propagating the flush is best-effort (warning), so the original training failure stays the raised exception — matching async_grpo_train's guarded cleanup shutdown. logger.finish() moves into its own finally so it runs either way. Also drop the stale "SC does not support checkpointing yet." comment from the SC exemplar config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: haitian-nvidia <haitianj@nvidia.com>
Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
Track Mooncake 1D promotion in durable TQ metadata so genuine singleton token columns retain their rank. Remove the obsolete pre-0.1.9 storage import fallback and add focused regression coverage. Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
Densify uniform nested reads for the simple backend without applying Mooncake's singleton squeeze. Make wire-field typing explicit, test invalid shape provenance, and warn when either TQ actor runtime patch is unavailable. Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
TransferQueue v0.1.9 dropped its numpy<2.0.0 pin, so the override's original justification no longer applies. The constraint it actually bypasses now is tensorrt-llm's numpy>=2.0.0,<2.4. Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
macandro96
marked this pull request as draft
August 3, 2026 21:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do ?
Summary
Adds authoritative recovery of completed, unconsumed Single Controller rollouts from native TransferQueue checkpoints.
TQ is the sole durable store for rollout tensor payloads. The SC checkpoint stores only a metadata replay index describing which TQ rows belong to ready prompt groups. On restart, TQ is loaded first and SC reconstructs its local replay-buffer indexes without copying tensors back into TQ.
This is a stacked change and currently depends on:
amahishi/sc-tq-native-checkpointMotivation
Previously, SC checkpoint recovery either serialized rollout tensors separately in
replay_buffer.ptor restarted with an empty rollout buffer.Serializing tensors in both the replay-buffer checkpoint and TQ:
This change makes the native TQ snapshot authoritative for tensors and keeps only the controller metadata required to resume consuming those rows.
Checkpoint contents
For every completed prompt group, the replay sidecar stores:
KVBatchMetainformation.It does not contain rollout tensors or
fields_data.Save workflow
flowchart LR Generation["Generation workers"] -->|"Completed prompt group"| Commit["TQReplayBuffer.commit"] Commit -->|"Tensor rows"| TQ["Canonical TQ partition"] Commit -->|"Ready-group metadata"| Index["SC replay index"] SC["Single Controller checkpoint"] --> Barrier["Exclusive checkpoint barrier"] Barrier --> TQSnapshot["Save native TQ snapshot"] Barrier --> Sidecar["Capture metadata-only replay index"] TQSnapshot --> Validate["Validate exact sample-ID inventory"] Sidecar --> Validate Validate --> Bundle["Finalize step_N bundle"]Commits and destructive clears participate in a shared/exclusive checkpoint barrier:
Checkpoint finalization fails if:
The previously finalized checkpoint remains available as the fallback.
Restore workflow
flowchart LR Bundle["Latest finalized step_N"] --> Trainer["Restore model, optimizer, dataloader"] Bundle --> Bootstrap["Clean TQ bootstrap client"] Bootstrap -->|"Load data_plane/"| TQ["Restored native TQ state"] TQ --> Client["Create normal SC data-plane client"] Bundle -->|"Load replay metadata"| Index["Rebuild local replay index"] Client --> Validate["Validate digest, group count, and exact inventory"] Index --> Validate Validate --> Pumps["Start rollout and train pumps"]Restore happens before
SingleControllerActor.run()and before any ordinary data-plane operation.Recovery validates:
No rollout tensor is re-serialized or re-put into TQ.
Sampler support
Authoritative replay recovery currently supports the ungated
windowedsampler.Gated samplers need additional durable dispatch/quota state before completed subsets can be recovered safely.
Sampler configs now declare their replay-checkpoint capability, and the sampler factory verifies that the declaration matches the runtime implementation. Custom sampler capability is checked after construction.
Configuration
Current native restore support requires the TQ
simplebackend. Unsupported configurations fail during setup rather than silently producing incomplete checkpoints.Compatibility
replay_buffer.ptcheckpoints are detected and rejected with an actionable error.Testing
bash tests/functional/grpo_dp_single_controller_tq_recovery.shbash tests/functional/grpo_dp_single_controller.shScope and limitations
This PR recovers completed, unconsumed prompt groups.
It does not yet recover:
The planned #3456 integration remains upstream of this mechanism:
Review guide
The primary areas to review are:
TQReplayBuffermetadata-only serialization and restore validation.Issues
List issues that this PR closes (syntax):
Usage
# Add a code snippet demonstrating how to use thisBefore your PR is "Ready for review"
Pre checks:
Additional Information