Non‑deterministic batch ordering in PyTorch DataLoader with multiple workers
0 reputation · 03 Jan 2024, 14:41 UTC
0 reputation · 03 Jan 2024, 14:41 UTC
Goal: Achieve deterministic batch ordering when using torch.utils.data.DataLoader with num_workers>0 while preserving the performance benefits of multiprocess data loading.
Constraint: Setting a global seed via torch.manual_seed does not propagate uniquely to each worker, so workers may generate identical augmentation sequences unless a worker_init_fn is supplied. The documentation leaves seed assignment to the user, creating uncertainty about the best practice for reproducibility.
Questions:
worker_init_fn?32775 reputation · 04 Jan 2024, 00:13 UTC
PyTorch does not automatically adjust seeds for each DataLoader worker. The standard, officially endorsed pattern for deterministic ordering is to combine a fixed torch.Generator for the sampler with a worker_init_fn that seeds each worker based on its worker_id. This keeps the multiprocessing performance while guaranteeing reproducible batch order.
The sampler (e.g., RandomSampler) draws indices from a pseudorandom generator. By passing the same torch.Generator instance to every worker, you ensure the index sequence is the same across runs. The worker_init_fn then re‑initializes the global RNG state in each worker so that any augmentation code that relies on torch.manual_seed, random, or numpy.random behaves consistently.
torch.manual_seed(42) in the main process to set a baseline seed.torch.Generator and seed it with the same value:
gen = torch.Generator()
gen.manual_seed(42)
sampler = torch.utils.data.RandomSampler(dataset, generator=gen)
worker_init_fn that seeds each worker uniquely:
def worker_init_fn(worker_id):
# Use worker_id to derive a distinct seed
seed = 42 + worker_id
torch.manual_seed(seed)
# If you use numpy or random, seed them too
import numpy as np, random
np.random.seed(seed)
random.seed(seed)
worker_init_fn to the DataLoader:
dataloader = torch.utils.data.DataLoader(
dataset,
batch_size=32,
sampler=sampler,
num_workers=4,
worker_init_fn=worker_init_fn
)
num_workers>0 and yields deterministic ordering, but you must add the worker_init_fn boilerplate.num_workers=0) eliminates the need for seeding logic but incurs a noticeable slow‑down because data loading becomes synchronous with the training loop.To fine‑tune the recommendation, could you confirm whether you are using a RandomSampler or a custom sampler that introduces randomness? This determines whether the generator passed to the sampler is sufficient or if additional seeding logic is needed inside the dataset.
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 03 Jan 2024, 20:58 UTC
When num_workers>0, each worker inherits the same global RNG state. PyTorch exposes torch.initial_seed() inside a worker to give a unique seed that is a deterministic function of the main seed and worker_id. Using this seed to re‑seed torch, numpy and random inside worker_init_fn guarantees that any random transforms are independent and that the batch indices follow the same order across runs—provided all transforms themselves are deterministic.
def worker_init_fn(worker_id):
seed = torch.initial_seed()
torch.manual_seed(seed)
import numpy as np, random
np.random.seed(seed)
random.seed(seed)
After adding this worker_init_fn, a quick sanity check—comparing the list of batch indices from two runs—should show identical ordering. This pattern is what the PyTorch docs recommend for reproducible multi‑worker loading.