Source code for core.samplers.distributed_sampler_wrapper
import math
import torch
import torch.distributed as dist
[docs]
class DistributedSamplerWrapper(torch.utils.data.Sampler):
"""Wrapper for distributing any sampler across multiple processes.
This wrapper takes an existing sampler and distributes its indices across
multiple replicas (processes) in a distributed training setup. It ensures
each replica gets a unique subset of the data by splitting the indices
evenly across replicas.
Note: This wrapper supports samplers that may return a different number of
samples each epoch, as it recomputes the indices on each iteration.
Note: If the length of the sampler is not a multiple of num_replicas, some
samples will be dropped to ensure equal distribution across replicas.
Args:
sampler: The base sampler to wrap
num_replicas: Number of processes in distributed training
rank: Rank of the current process
"""
def __init__(
self,
sampler: torch.utils.data.Sampler,
num_replicas: int | None = None,
rank: int | None = None,
):
self.sampler = sampler
if num_replicas is None:
num_replicas = dist.get_world_size() if dist.is_initialized() else 1
if rank is None:
rank = dist.get_rank() if dist.is_initialized() else 0
self.num_replicas = num_replicas
self.rank = rank
self._indices_cache = None
def _prepare_indices_cache(self):
if self._indices_cache is not None:
return
indices = list(self.sampler)
# Force len(indices) to be a multiple of num_replicas
num_samples_per_rank = math.floor(len(indices) / self.num_replicas)
total_size = num_samples_per_rank * self.num_replicas
indices = indices[:total_size]
self._indices_cache = indices[self.rank : total_size : self.num_replicas]
def __len__(self):
self._prepare_indices_cache()
return len(self._indices_cache)
def __iter__(self):
self._prepare_indices_cache()
yield from self._indices_cache
self._indices_cache = None
[docs]
def set_epoch(self, epoch: int):
"""Pass the epoch to the underlying sampler if it supports it."""
if hasattr(self.sampler, "set_epoch"):
self.sampler.set_epoch(epoch)