feat(adapter): add Megatron Bridge and NeMo RL checkpoint adapters - #110
Draft
g-husam wants to merge 2 commits into
Draft
feat(adapter): add Megatron Bridge and NeMo RL checkpoint adapters#110g-husam wants to merge 2 commits into
g-husam wants to merge 2 commits into
Conversation
Megatron Bridge exposes a CheckpointManager protocol that a run can swap in via CheckpointConfig.custom_manager_class. MLFlashpointBridgeCheckpointManager implements it: non-persistent checkpoints (non_persistent_save_interval) are written by ML Flashpoint into node-local memory, while durable checkpoints (save_interval) keep going through Megatron Bridge untouched. Bridge takes the non-persistent branch only on steps that are not also durable-checkpoint steps, so the two cadences never collide. Resume works by publishing an MLFlashpointLocalCheckpointIndex under checkpointing_context["local_checkpoint_manager"], which is what Bridge's setup._should_load_checkpoint consults. Load prefers the newest recoverable ML Flashpoint container and falls back to Bridge's own path when there is none or when the in-memory read fails. NeMo RL needs a different entry point: it builds its Megatron state with Megatron Bridge but calls Bridge's functional save_checkpoint directly from MegatronPolicyWorker, so custom_manager_class is never consulted anywhere in that repo. install_into_worker wraps that one method instead, in either "augment" mode (durable writes unchanged, ML Flashpoint alongside) or "replace" mode (only every Nth checkpoint stays durable). Also adds a checkpoint-timing benchmark harness that parses Megatron's save-checkpoint timers and ML Flashpoint's own execution timings out of training logs and diffs two runs, plus a runbook for measuring the difference on a GKE training cluster, and source-verified research notes on all three codebases for future work.
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.
AI generated description
This change adds two new adapters so that jobs running on Megatron Bridge — and on
NeMo RL, which is built on top of it — can write their frequent crash-recovery
checkpoints into ML Flashpoint's node-local memory instead of durable storage,
while their durable checkpoints keep going exactly where they went before.
It also adds the tooling to prove the difference: a harness that pulls checkpoint
timings out of training logs and diffs two runs.
Why
Megatron Bridge already distinguishes two kinds of checkpoint. This change maps
ML Flashpoint onto the cheap one and leaves the expensive one alone:
save_intervalnon_persistent_save_intervalThe two cadences can't collide, because Bridge's own loop treats them as mutually
exclusive:
That
elifis why this adapter needs no skip logic, unlike the NeMo 2.0 adapter andits
skip_every_n_steps.Megatron Bridge: use the supported hook
Bridge lets a run swap in its own checkpoint manager via
CheckpointConfig.custom_manager_class.MLFlashpointBridgeCheckpointManagerimplements that protocol. The whole routing decision is four lines:
A durable checkpoint is untouched. A non-persistent one goes to ML Flashpoint, and
if that fails, training keeps running — losing a crash-recovery checkpoint should
never take down a job.
Turning it on
enable()setscustom_manager_class,non_persistent_ckpt_type="local"andnon_persistent_save_interval— and registersml_flashpointwith Bridge's importallowlist, which is easy to miss:
YAML works too, as long as that prefix is registered before
setupruns:Making resume find the checkpoint
Bridge decides whether to attempt a resume by asking the checkpoint manager's
context for a local checkpoint manager:
So the adapter publishes
MLFlashpointLocalCheckpointIndexunder that key. Itimplements only
find_latest()andlocal_ckpt_dir— deliberately notload(),because Bridge's own local-load path expects an NVRx
MCoreTensorAwareStateDictcontainer, which ML Flashpoint does not produce. Before falling back to Bridge, the
index is disabled so Bridge is never offered a container it can't read:
Load order is: newest recoverable ML Flashpoint container first; Bridge's durable
path if there is none, or if the in-memory read fails.
NeMo RL: the config hook doesn't reach it
NeMo RL never calls
create_checkpoint_manager. A grep forcustom_manager_classacross the entire NeMo RL repo returns zero hits. It buildsits Megatron state with Bridge, but drives checkpointing itself:
So a config-only integration is impossible here. Instead,
install_into_workerwraps that one method on a worker instance:
The wrapper is small enough to read in full:
Two modes:
augment(default) — every durable write still happens, with an ML Flashpointcheckpoint alongside. Faster recovery, unchanged durability.
replacewithdurable_every_n_saves=N— only every Nth checkpoint staysdurable. This is what removes checkpoint stalls from the RL loop.
For A/B runs,
install_from_env(worker)readsMLFLASHPOINT_NEMO_RL_ENABLED/_MODE/_DURABLE_EVERY_N_SAVES, so both armsshare one launch command.
Why not just inject a save strategy?
Seeding
checkpointing_context["save_strategy"]would be honoured by Bridge'ssave_checkpoint, and it's tempting because it needs no worker changes. It's wronghere for two reasons:
dist_checkpointing.savewritescommon.pton global rank 0only, so other nodes couldn't recover locally; and it writes into the caller's
weights_path, putting a node-local memory container inside the durable directory.Measuring it
Where the numbers come from
Bridge wraps every checkpoint in barriers and then logs it, so the recorded value is
the wall-clock time the whole job stalled:
That produces lines like this, in milliseconds:
The pair is
(min, max)across ranks, not across steps — so this is onecheckpoint, where the fastest rank finished in 18.45 s and the slowest in 18.51 s.
The parser keeps the max, because the barrier means every rank waits for the
slowest one. This line contributes a single sample of 18.51 s.
Turning that into a comparison
nis the number of checkpoints observed in that arm; the two middle columns aremean seconds per checkpoint.
Row 1 is the control, not the result. Durable checkpoints cost the same in both
arms (18.306 s vs 18.402 s) — which is exactly right, because this change doesn't
touch the durable path. A real difference here would mean the two runs differed in
something else, and would invalidate the rest of the table.
Row 2 is the new work. It shows
-andn/abecause the baseline has nonon-persistent cadence at all, so there is no same-named timer to subtract from.
The headline number is the cross-row comparison, which the tool deliberately
does not compute for you:
On steps where ML Flashpoint now holds the checkpoint, the loop stalls for ~0.6 s
instead of ~18 s. That is only a real saving in
replacemode, where those stepsgenuinely skip the durable write — in
augmentmode the durable write still happens,so row 2 is added cost rather than saved time.
docs/checkpoint-timing-experiment.mdis the runbook: cluster requirements (≥2 nodesso replication is exercised,
/dev/shmsized for two shards), a ~25-step run config,and how to read the result including the sample-size caveat.
Testing
Two of the tests caught real bugs while being written: a disabled manager still
handing back its runtime instead of
None, and the resume index never gettinginstalled because the context property waited on a runtime that Bridge builds later.
tests/adapter/conftest.pystubsmegatron.bridge.*when it isn't installed, so thesuite runs without the full NVIDIA stack (Transformer Engine, ModelOpt, NVRx). When
the real package is present, it is used instead.
What is not covered
results table in the runbook is intentionally empty.
it, so a first real run should be expected to surface friction.
pyproject.tomlstill pinsmegatron_core==0.13.1for themegatronextra,which is older than what
megatron-bridge==0.6.0requires. Resolution between thetwo extras is the most likely first snag. Three symbols that moved between mcore
releases (
unwrap_model,is_graph_safe_cuda_rng_tracker,convert_cuda_rng_state) are already resolved defensively.Notes for reviewers
notes/checkpointing-integration-research.mddocuments everything read out ofMegatron Bridge and NeMo RL source — the protocol, the allowlist, which hooks are
injectable and which aren't, and three places the Bridge docs are stale.
GEMINI.mdpoints at it. It's the file to read first if any of the above looks surprising.
bridge_state.pyrebuilds the state dict that Bridge assembles insidesave_checkpoint, using Bridge's own helpers. Three of those are private(
_build_sharded_state_dict_metadata,_load_model_state_dict,_clean_metadata_for_serialization) and are resolved withgetattrso a Bridgeupgrade produces a clear runtime error rather than an import failure.
AsyncCallsQueue, separate from the durable one. Thisisn't tidiness: a single queue finalizes in scheduling order, so fast ML Flashpoint
finalizations would stack up behind a slow durable save and pin their buffers until
the pool is exhausted. Same reasoning as
MLFlashpointAsyncFinalizableCheckpointIOin the NeMo adapter.
Checklist