Checkpoint the RNG the curriculum sampler actually draws from - #8460
Open
alanhuangyoo wants to merge 1 commit into
Open
Checkpoint the RNG the curriculum sampler actually draws from#8460alanhuangyoo wants to merge 1 commit into
alanhuangyoo wants to merge 1 commit into
Conversation
Fixes deepspeedai#8405. Every draw in DeepSpeedDataSampler goes through self.np_rng, a np.random.default_rng seeded in __init__: self.np_rng.shuffle(new_cluster) # get_new_cluster self.np_rng.choice(num_clusters, ...) # sample_from_clusters self.np_rng.shuffle(cluster) # reshuffle_clusters self.np_rng.shuffle(batch) state_dict() stored np.random.get_state() and load_state_dict() restored it. That is the process-wide legacy RandomState, which this sampler never touches, so the saved value reads the same whether the sampler has drawn nothing or a thousand batches. On resume __init__ re-seeds self.np_rng, load_state_dict leaves it alone, and the sampler restarts its own stream: the same cluster mix per step and the same shuffles it produced the first time. Restoring the global state also moved whatever else in the process draws from np.random. Save and restore self.np_rng.bit_generator.state instead. A checkpoint written before this holds the legacy tuple, which carries no sampler stream to restore; _load_np_rng_state recognises it, warns once on rank 0, and leaves the sampler on its configured seed -- where those checkpoints already resumed -- rather than refusing to load. tests/unit/runtime/data_pipeline/test_curriculum_sampler_rng.py: the saved state moves as the sampler draws, a resumed sampler continues the stream instead of replaying it (through load_state_dict, not by poking the generator), loading leaves the global numpy RNG alone, and a legacy checkpoint still loads. Two of the four fail on master. Reported by @ebarkhordar. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
alanhuangyoo
requested review from
loadams,
tjruwase and
tohtana
as code owners
September 8, 2026 12:06
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.
Fixes #8405, reported by @ebarkhordar.
Problem
Every draw in
DeepSpeedDataSamplergoes throughself.np_rng, anp.random.default_rngseeded in__init__:but the checkpoint stored and restored something else entirely:
That is the process-wide legacy
RandomState, which this sampler never touches. So the saved value reads the same whether the sampler has drawn nothing or a thousand batches, and on resume__init__re-seedsself.np_rngwhileload_state_dictleaves it alone — the sampler restarts its own stream, reproducing the same cluster mix per step and the same shuffles it produced the first time. Restoring the global state also moved whatever else in the process draws fromnp.random.Solution
Save and restore
self.np_rng.bit_generator.state.A checkpoint written before this holds the legacy tuple, and there is no sampler stream recorded in it to restore.
_load_np_rng_staterecognises the shape, warns once on rank 0, and leaves the sampler on its configured seed — which is where those checkpoints already resumed — rather than refusing to load.Verification
tests/unit/runtime/data_pipeline/test_curriculum_sampler_rng.py, four cases:[[0, 3, 3, 2], ...] != [[2, 1, 3, 2], ...]The resume test goes through
load_state_dictrather than poking the generator, so it fails on master for the behaviour rather than on a type error, and it asserts that a sampler on a fresh seed does not already agree — otherwise it would prove nothing.pre-commit runpasses on both files.