Persist dataloader iteration and shuffle generator in save_state/load_state - #4232
Open
caiotheodoro wants to merge 1 commit into
Open
Persist dataloader iteration and shuffle generator in save_state/load_state#4232caiotheodoro wants to merge 1 commit into
caiotheodoro wants to merge 1 commit into
Conversation
caiotheodoro
force-pushed
the
checkpoint-dataloader-shuffle-state
branch
from
September 9, 2026 00:24
71dd495 to
29b9eda
Compare
5 tasks
…_state
save_state never wrote any dataloader state: the sampler branch in
checkpointing.py required an IterableDatasetShard together with a
SeedableRandomSampler, which cannot happen. A resumed run therefore
reseeded the seedable sampler from epoch 0 (iteration was lost) and,
with the default sampler in a multi-process run, recreated the private
generator prepare_data_loader had attached (its state was lost).
Save iteration and the generator state per prepared dataloader in the
existing sampler{_i}.bin slot and restore them in load_state.
Checkpoints without the file load as before.
Fixes huggingface#3996
caiotheodoro
force-pushed
the
checkpoint-dataloader-shuffle-state
branch
from
September 9, 2026 01:33
29b9eda to
e1d57bd
Compare
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 #3996
Resuming from
save_state/load_statereplays the epoch-0 shuffle order. I hit it with the reporter's scripts onmain@ 0f7e35f, then with a 2-process gloo run on CPU, same result both ways.The sampler branch in
checkpointing.py:137(and:273on load) only fires when the dataset is anIterableDatasetShardand the sampler is aSeedableRandomSampler. Those two never happen together, an iterable shard sits on_InfiniteConstantSampler, so nothing has been written for dataloaders since #2080. What's missing is two small things:use_seedable_sampler=True, the seed isinitial_seed + epoch(data_loader.py:99-101) and__iter__sets that epoch fromself.iteration(:582,:874for the dispatcher).iterationis never saved, so a resumed loader starts at epoch 0 again. That's also why fix(checkpointing): save/restore SeedableRandomSampler for map-style datasets #4019 couldn't fix it by saving the sampler object, the next__iter__overwrites the epoch anyway.num_processes > 1,prepare_data_loaderhands the sampler a private generator (data_loader.py:1247-1252) and keeps it assynchronized_generator. Its state is never saved, soprepare()recreates the same sequence on resume.So
save_statenow writes{"iteration", "generator_state"}per prepared dataloader into thesampler{_i}.binslot that already existed, andload_stateputs both back when the file is there, throughset_epochso the sampler and dataset hear about it too. The generator is whichever one the sampler draws from: the private oneprepareattached, or agenerator=the user passed to theDataLoader, which covers the plain PyTorch reproducibility recipe in a single process as well (that one replayed too, generator state was never written). One file from the main process is enough, the generator state is the same on every rank aftersynchronize_rng_states, same as the optimizer and scheduler files. Old checkpoints don't have the file and load as before. Withuse_stateful_dataloader=Truethe file is written and read as well, next to torchdata'sdl_state_dict.bin; the torchdata branch itself is untouched. I unwrap XLA'sMpDeviceLoaderWrappertoo but couldn't run XLA here.One thing this doesn't cover: a checkpoint taken inside an epoch still resumes that epoch on the next permutation. #4233 stacks on this one and handles that.
Before, the resumed order equals epoch 0's in both configurations. After, it equals the recorded continuation on every rank. torch 2.14.0, Python 3.13.12, CPU. #3242 is the same report from 2024 on 2 GPUs.
I also ran
examples/by_feature/checkpointing.pyend to end (bert-base-cased, MRPC cut to 256 train / 64 eval so it finishes on CPU, 2 processes,--checkpointing_steps epoch, the example's default sampler). Uninterrupted run, rank-0 loss sum per epoch: 5.152098, 4.962221, 4.925249. Resume fromepoch_0with this branch: 4.962221, 4.925249, same numbers. Resume from the same checkpoint onmain: 5.068580, 4.844967, so it trained epochs 1 and 2 on a different order.Drafted with Claude Opus / Fable 5.1. Reviewed by Muse Spark 1.3 and GLM 5.3 as judges before submission.
Before submitting
Pull Request section?
to it if that's the case.
documentation guidelines, and
here are tips on formatting docstrings.
Who can review?
@SunMarc