CoolFace
Apppublic

souging/TRELLIS_TextTo3D

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
data_utils.py227 linesDownload Raw Back to utils
1from typing import *2import math3import torch4import numpy as np5from torch.utils.data import Sampler, Dataset, DataLoader, DistributedSampler6import torch.distributed as dist7 8 9def recursive_to_device(10    data: Any,11    device: torch.device,12    non_blocking: bool = False,13) -> Any:14    """15    Recursively move all tensors in a data structure to a device.16    """17    if hasattr(data, "to"):18        return data.to(device, non_blocking=non_blocking)19    elif isinstance(data, (list, tuple)):20        return type(data)(recursive_to_device(d, device, non_blocking) for d in data)21    elif isinstance(data, dict):22        return {k: recursive_to_device(v, device, non_blocking) for k, v in data.items()}23    else:24        return data25 26 27def load_balanced_group_indices(28    load: List[int],29    num_groups: int,30    equal_size: bool = False,31) -> List[List[int]]:32    """33    Split indices into groups with balanced load.34    """35    if equal_size:36        group_size = len(load) // num_groups37    indices = np.argsort(load)[::-1]38    groups = [[] for _ in range(num_groups)]39    group_load = np.zeros(num_groups)40    for idx in indices:41        min_group_idx = np.argmin(group_load)42        groups[min_group_idx].append(idx)43        if equal_size and len(groups[min_group_idx]) == group_size:44            group_load[min_group_idx] = float('inf')45        else:46            group_load[min_group_idx] += load[idx]47    return groups48 49 50def cycle(data_loader: DataLoader) -> Iterator:51    while True:52        for data in data_loader:53            if isinstance(data_loader.sampler, ResumableSampler):54                data_loader.sampler.idx += data_loader.batch_size   # type: ignore[attr-defined]55            yield data56        if isinstance(data_loader.sampler, DistributedSampler):57            data_loader.sampler.epoch += 158        if isinstance(data_loader.sampler, ResumableSampler):59            data_loader.sampler.epoch += 160            data_loader.sampler.idx = 061        62 63class ResumableSampler(Sampler):64    """65    Distributed sampler that is resumable.66 67    Args:68        dataset: Dataset used for sampling.69        rank (int, optional): Rank of the current process within :attr:`num_replicas`.70            By default, :attr:`rank` is retrieved from the current distributed71            group.72        shuffle (bool, optional): If ``True`` (default), sampler will shuffle the73            indices.74        seed (int, optional): random seed used to shuffle the sampler if75            :attr:`shuffle=True`. This number should be identical across all76            processes in the distributed group. Default: ``0``.77        drop_last (bool, optional): if ``True``, then the sampler will drop the78            tail of the data to make it evenly divisible across the number of79            replicas. If ``False``, the sampler will add extra indices to make80            the data evenly divisible across the replicas. Default: ``False``.81    """82 83    def __init__(84        self,85        dataset: Dataset,86        shuffle: bool = True,87        seed: int = 0,88        drop_last: bool = False,89    ) -> None:90        self.dataset = dataset91        self.epoch = 092        self.idx = 093        self.drop_last = drop_last94        self.world_size = dist.get_world_size() if dist.is_initialized() else 195        self.rank = dist.get_rank() if dist.is_initialized() else 096        # If the dataset length is evenly divisible by # of replicas, then there97        # is no need to drop any data, since the dataset will be split equally.98        if self.drop_last and len(self.dataset) % self.world_size != 0:  # type: ignore[arg-type]99            # Split to nearest available length that is evenly divisible.100            # This is to ensure each rank receives the same amount of data when101            # using this Sampler.102            self.num_samples = math.ceil(103                (len(self.dataset) - self.world_size) / self.world_size  # type: ignore[arg-type]104            )105        else:106            self.num_samples = math.ceil(len(self.dataset) / self.world_size)  # type: ignore[arg-type]107        self.total_size = self.num_samples * self.world_size108        self.shuffle = shuffle109        self.seed = seed110 111    def __iter__(self) -> Iterator:112        if self.shuffle:113            # deterministically shuffle based on epoch and seed114            g = torch.Generator()115            g.manual_seed(self.seed + self.epoch)116            indices = torch.randperm(len(self.dataset), generator=g).tolist()  # type: ignore[arg-type]117        else:118            indices = list(range(len(self.dataset)))  # type: ignore[arg-type]119 120        if not self.drop_last:121            # add extra samples to make it evenly divisible122            padding_size = self.total_size - len(indices)123            if padding_size <= len(indices):124                indices += indices[:padding_size]125            else:126                indices += (indices * math.ceil(padding_size / len(indices)))[127                    :padding_size128                ]129        else:130            # remove tail of data to make it evenly divisible.131            indices = indices[: self.total_size]132        assert len(indices) == self.total_size133 134        # subsample135        indices = indices[self.rank : self.total_size : self.world_size]136        137        # resume from previous state138        indices = indices[self.idx:]139 140        return iter(indices)141 142    def __len__(self) -> int:143        return self.num_samples144 145    def state_dict(self) -> dict[str, int]:146        return {147            'epoch': self.epoch,148            'idx': self.idx,149        }150        151    def load_state_dict(self, state_dict):152        self.epoch = state_dict['epoch']153        self.idx = state_dict['idx']154        155 156class BalancedResumableSampler(ResumableSampler):157    """158    Distributed sampler that is resumable and balances the load among the processes.159 160    Args:161        dataset: Dataset used for sampling.162        rank (int, optional): Rank of the current process within :attr:`num_replicas`.163            By default, :attr:`rank` is retrieved from the current distributed164            group.165        shuffle (bool, optional): If ``True`` (default), sampler will shuffle the166            indices.167        seed (int, optional): random seed used to shuffle the sampler if168            :attr:`shuffle=True`. This number should be identical across all169            processes in the distributed group. Default: ``0``.170        drop_last (bool, optional): if ``True``, then the sampler will drop the171            tail of the data to make it evenly divisible across the number of172            replicas. If ``False``, the sampler will add extra indices to make173            the data evenly divisible across the replicas. Default: ``False``.174    """175 176    def __init__(177        self,178        dataset: Dataset,179        shuffle: bool = True,180        seed: int = 0,181        drop_last: bool = False,182        batch_size: int = 1,183    ) -> None:184        assert hasattr(dataset, 'loads'), 'Dataset must have "loads" attribute to use BalancedResumableSampler'185        super().__init__(dataset, shuffle, seed, drop_last)186        self.batch_size = batch_size187        self.loads = dataset.loads188        189    def __iter__(self) -> Iterator:190        if self.shuffle:191            # deterministically shuffle based on epoch and seed192            g = torch.Generator()193            g.manual_seed(self.seed + self.epoch)194            indices = torch.randperm(len(self.dataset), generator=g).tolist()  # type: ignore[arg-type]195        else:196            indices = list(range(len(self.dataset)))  # type: ignore[arg-type]197 198        if not self.drop_last:199            # add extra samples to make it evenly divisible200            padding_size = self.total_size - len(indices)201            if padding_size <= len(indices):202                indices += indices[:padding_size]203            else:204                indices += (indices * math.ceil(padding_size / len(indices)))[205                    :padding_size206                ]207        else:208            # remove tail of data to make it evenly divisible.209            indices = indices[: self.total_size]210        assert len(indices) == self.total_size211 212        # balance load among processes213        num_batches = len(indices) // (self.batch_size * self.world_size)214        balanced_indices = []215        for i in range(num_batches):216            start_idx = i * self.batch_size * self.world_size217            end_idx = (i + 1) * self.batch_size * self.world_size218            batch_indices = indices[start_idx:end_idx]219            batch_loads = [self.loads[idx] for idx in batch_indices]220            groups = load_balanced_group_indices(batch_loads, self.world_size, equal_size=True)221            balanced_indices.extend([batch_indices[j] for j in groups[self.rank]])222        223        # resume from previous state224        indices = balanced_indices[self.idx:]225 226        return iter(indices)227