| ''' |
| ----------------------------------------------------------------------------- |
| Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. |
| |
| NVIDIA CORPORATION and its licensors retain all intellectual property |
| and proprietary rights in and to this software, related documentation |
| and any modifications thereto. Any use, reproduction, disclosure or |
| distribution of this software and related documentation without an express |
| license agreement from NVIDIA CORPORATION is strictly prohibited. |
| ----------------------------------------------------------------------------- |
| ''' |
|
|
| import math |
| import torch.distributed as dist |
| import torch |
|
|
| from torch.utils.data import Sampler |
| from typing import TypeVar |
|
|
| T_co = TypeVar('T_co', covariant=True) |
|
|
|
|
| class DistributedSamplerPreemptable(Sampler[T_co]): |
| r"""Sampler that supports loading from an iteration. |
| This is very useful for preemptable jobs. |
| |
| Args: |
| dataset (torch.utils.data.Dataset): Dataset object |
| num_replicas (int): Number of replicas to the distribute the dataloader over. |
| This is typically the world size in DDP jobs. |
| rank (int): Rank of the current process. |
| shuffle (bool): Whether to shuffle the dataloader in each epoch. |
| seed (int): Random seed used for shuffling the dataloader. |
| drop_last (bool): Whether to drop the last batch. |
| """ |
|
|
| def __init__(self, dataset, num_replicas=None, rank=None, shuffle=True, |
| seed=0, drop_last=False): |
|
|
| if num_replicas is None: |
| if not dist.is_available(): |
| raise RuntimeError("Requires distributed package to be available") |
| num_replicas = dist.get_world_size() |
| if rank is None: |
| if not dist.is_available(): |
| raise RuntimeError("Requires distributed package to be available") |
| rank = dist.get_rank() |
| if rank >= num_replicas or rank < 0: |
| raise ValueError( |
| "Invalid rank {}, rank should be in the interval" |
| " [0, {}]".format(rank, num_replicas - 1)) |
| self.dataset = dataset |
| self.num_replicas = num_replicas |
| self.rank = rank |
| self.epoch = 0 |
|
|
| |
| self.start_index = 0 |
|
|
| self.drop_last = drop_last |
| |
| |
| if self.drop_last and len(self.dataset) % self.num_replicas != 0: |
| |
| |
| |
| self.num_samples = math.ceil( |
| (len(self.dataset) - self.num_replicas) / self.num_replicas |
| ) |
| else: |
| self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) |
| self.total_size = self.num_samples * self.num_replicas |
| self.shuffle = shuffle |
| self.seed = seed |
|
|
| def __iter__(self): |
| if self.shuffle: |
| |
| g = torch.Model() |
| g.manual_seed(self.seed + self.epoch) |
| indices = torch.randperm(len(self.dataset), generator=g).tolist() |
| else: |
| indices = list(range(len(self.dataset))) |
|
|
| if not self.drop_last: |
| |
| padding_size = self.total_size - len(indices) |
| if padding_size <= len(indices): |
| indices += indices[:padding_size] |
| else: |
| indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size] |
| else: |
| |
| indices = indices[:self.total_size] |
| assert len(indices) == self.total_size |
|
|
| |
| indices = indices[self.rank:self.total_size:self.num_replicas] |
| assert len(indices) == self.num_samples |
|
|
| |
| if self.start_index >= len(indices): |
| print('(Warning): Start index is less than len of dataloader. Goint to the last batch of dataset instead') |
| |
| self.start_index = len(indices) - 64 |
| indices = indices[self.start_index:] |
|
|
| return iter(indices) |
|
|
| def __len__(self): |
| return self.num_samples |
|
|
| def set_epoch(self, epoch): |
| self.epoch = epoch |
|
|
| def set_iteration(self, start_index): |
| self.start_index = start_index |
|
|