`use_stateful_dataloader` + `num_workers>0` draws epoch-0 permutation during `prepare()` before cross-rank RNG sync — corrupts DDP data partition
Maintainers usually reply within 1 day
@JohnnyWilson16 is already working on this.
Since Oct 19, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Domain
- distributed-systems
Research direction
Trace DataLoaderAdapter.init and prepare_data_loader to confirm when state_dict() materializes the iterator, then follow DataLoaderShard.iter and synchronize_rng_states. Run the provided two-process repro with seed and noseed modes. Done means prepare() no longer consumes divergent sampler RNG before iteration and epoch 0 has no missing or duplicated samples.
Written by the indexing model from the issue text.
Description
System Info
- `accelerate` version: 1.13.0 (and main branch)
- `torch` version: 2.13.0
- `torchdata` version: 0.11.0
- Platform: Linux
- Python version: 3.11
- Distributed environment: 2-process CPU/Gloo (reproducible without GPU)
Information
- The official example scripts
- My own modified scripts
Tasks
- One of the scripts in the examples/ folder of Accelerate or an officially supported
no_trainerscript in theexamplesfolder of thetransformersrepo (such asrun_no_trainer_glue.py) - My own task or dataset (give details below)
Reproduction
Summary & Root Cause
With DataLoaderConfiguration(use_stateful_dataloader=True), accelerator.prepare() wraps the dataloader as a torchdata StatefulDataLoader and immediately invokes base_dataloader.state_dict() inside DataLoaderAdapter.__init__ to record the initial dataloader state.
When num_workers > 0:
StatefulDataLoader.state_dict()callsself._get_iterator()whenself._iterator is Noneto snapshot iterator state.- The multiprocessing iterator's
_reset()spawns worker processes and primes the prefetch queue (prefetch_factor * num_workersitems), which iterates_sampler_iter. - For a vanilla
RandomSampler(generator=None), this immediately draws the epoch-0 permutation seed from the process's local global RNG atprepare()time. - Cross-rank dataloader RNG synchronization (
DataLoaderShard.__iter__->synchronize_rng_states) only happens when iteration actually starts, so any divergence in per-rank RNG beforeprepare()(e.g. no globaltorch.manual_seedset, deliberate per-rank seeding, or unequal RNG consumption across ranks during model/dataset setup) causes ranks to derive different epoch-0 permutations. BatchSamplerShardthen rank-interleaves divergent permutations, which corrupts the data partition during epoch 0 (samples are duplicated across ranks and others are dropped).
(Note: This affects uninterrupted training runs during epoch 0, independent of any checkpoint/resume operation).
Minimal Reproducible Example
Run with 2 processes on CPU (no GPU needed):
# Case A (diverged/unseeded RNG across ranks): partition is corrupted
python -m torch.distributed.run --standalone --nproc_per_node=2 repro_prepare_prefetch.py noseed
# Case B (identical seed explicitly set before prepare): partition is intact
python -m torch.distributed.run --standalone --nproc_per_node=2 repro_prepare_prefetch.py seed
repro_prepare_prefetch.py:
import sys
import torch
import torch.distributed as dist
from torch.utils.data import DataLoader, TensorDataset
from accelerate import Accelerator, DataLoaderConfiguration
MODE = sys.argv[1] # "seed" or "noseed"
N, BS = 96, 4
if MODE == "seed":
torch.manual_seed(1234) # identical on both ranks before prepare()
acc = Accelerator(cpu=True, dataloader_config=DataLoaderConfiguration(use_stateful_dataloader=True))
loader = acc.prepare(
DataLoader(TensorDataset(torch.arange(N)), batch_size=BS, shuffle=True, num_workers=6)
)
# Iterate through epoch 0
flat = [i for b in loader for i in b[0].tolist()]
gathered = [None] * acc.num_processes
dist.all_gather_object(gathered, flat)
acc.wait_for_everyone()
if acc.is_main_process:
union = [i for rank_items in gathered for i in rank_items]
union_set = set(union)
missing = len(set(range(N)) - union_set)
duplicates = len(union) - len(union_set)
print(
f"mode={MODE}: epoch-0 union={len(union_set)}/{N}, missing={missing}, duplicates={duplicates}"
)
Observed Output
mode=noseed: epoch-0 union=72/96, missing=24, duplicates=24
mode=seed: epoch-0 union=96/96, missing=0, duplicates=0
With num_workers=6 and no prior shared seed synchronization, 24 out of 96 samples are duplicated across ranks and 24 samples are completely omitted from epoch 0.
Expected behavior
accelerator.prepare(dataloader) should not draw from the sampler or consume generator RNG before iteration begins:
- Lazy state capture (Preferred):
DataLoaderAdapter.__init__should avoid materializing_get_iterator()during initialization, capturing state lazily on the first step/checkpoint instead. - Post-init cleanup / reset: If
state_dict()must be called duringprepare_data_loader, the early iterator should be cleared (dataloader.base_dataloader._iterator = Noneor equivalent) to reap worker pools and defer the sampler draw to iteration time. - At minimum,
synchronize_rng_statesshould run prior to adapter initial state capture ifuse_stateful_dataloader=Trueandnum_workers > 0.
- Dominant language
- Python
- Stars
- 9.9k
- Forks
- 1.6k
- Avg merge
- 20h 23m
- Merged PRs (30d)
- 12
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from huggingface/accelerate
-
FSDP2: prepare() keeps the whole unsharded model alive during sharding when cpu_ram_efficient_loading=FalsePossibly taken @Rajan-vish claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
huggingface/accelerate#4511 ·
Maintainers usually reply within 1 day
-
BatchSamplerShard padding mutates retained sampler batches and changes original loader samplesPossibly taken @davrens101 claimed this 4 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
huggingface/accelerate#4505 ·
Maintainers usually reply within 1 day
-
accelerator.reduce(tensor, "mean") fails on integer tensors when more than one process is usedPossibly taken @MaiAditya claimed this 13 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
huggingface/accelerate#4469 ·
Maintainers usually reply within 1 day
-
merge_fsdp_weights with remove_checkpoint_dir=True deletes the merged model when output_path is inside checkpoint_dirPossibly taken @assass1nlike claimed this 2 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 25/100
huggingface/accelerate#4514 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
huggingface/accelerate#4510 ·
Maintainers usually reply within 1 day
All issues in huggingface/accelerate
Similar issues
-
question
Difficulty 1/5 Under an hour Newbie friendliness 68/100
vllm-project/vime#524 ·
Maintainers usually reply within 7 days
-
WOPI editor page is scrollable by a few pixels, which breaks scrolling in the document (Firefox)Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
suitenumerique/drive#969 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
theupdateframework/python-tuf#3044 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
FAIRmat-NFDI/pynxtools#861 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
NVIDIA/nsight-python#88 ·
Maintainers usually reply within 1 day