CoolFace
Apppublic

breadlicker45/gpuGAN

sourceHugging Facecc-by-nc-4.0updated 8mo agoView on Hugging Face
0likes
training_stats.py269 linesDownload Raw Back to torch_utils
1# Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.2#3# NVIDIA CORPORATION and its licensors retain all intellectual property4# and proprietary rights in and to this software, related documentation5# and any modifications thereto.  Any use, reproduction, disclosure or6# distribution of this software and related documentation without an express7# license agreement from NVIDIA CORPORATION is strictly prohibited.8 9"""Facilities for reporting and collecting training statistics across10multiple processes and devices. The interface is designed to minimize11synchronization overhead as well as the amount of boilerplate in user12code."""13 14import re15import numpy as np16import torch17import dnnlib18 19from . import misc20 21#----------------------------------------------------------------------------22 23_num_moments    = 3             # [num_scalars, sum_of_scalars, sum_of_squares]24_reduce_dtype   = torch.float32 # Data type to use for initial per-tensor reduction.25_counter_dtype  = torch.float64 # Data type to use for the internal counters.26_rank           = 0             # Rank of the current process.27_sync_device    = None          # Device to use for multiprocess communication. None = single-process.28_sync_called    = False         # Has _sync() been called yet?29_counters       = dict()        # Running counters on each device, updated by report(): name => device => torch.Tensor30_cumulative     = dict()        # Cumulative counters on the CPU, updated by _sync(): name => torch.Tensor31 32#----------------------------------------------------------------------------33 34def init_multiprocessing(rank, sync_device):35    r"""Initializes `torch_utils.training_stats` for collecting statistics36    across multiple processes.37 38    This function must be called after39    `torch.distributed.init_process_group()` and before `Collector.update()`.40    The call is not necessary if multi-process collection is not needed.41 42    Args:43        rank:           Rank of the current process.44        sync_device:    PyTorch device to use for inter-process45                        communication, or None to disable multi-process46                        collection. Typically `torch.device('cuda', rank)`.47    """48    global _rank, _sync_device49    assert not _sync_called50    _rank = rank51    _sync_device = sync_device52 53#----------------------------------------------------------------------------54 55@misc.profiled_function56def report(name, value):57    r"""Broadcasts the given set of scalars to all interested instances of58    `Collector`, across device and process boundaries.59 60    This function is expected to be extremely cheap and can be safely61    called from anywhere in the training loop, loss function, or inside a62    `torch.nn.Module`.63 64    Warning: The current implementation expects the set of unique names to65    be consistent across processes. Please make sure that `report()` is66    called at least once for each unique name by each process, and in the67    same order. If a given process has no scalars to broadcast, it can do68    `report(name, [])` (empty list).69 70    Args:71        name:   Arbitrary string specifying the name of the statistic.72                Averages are accumulated separately for each unique name.73        value:  Arbitrary set of scalars. Can be a list, tuple,74                NumPy array, PyTorch tensor, or Python scalar.75 76    Returns:77        The same `value` that was passed in.78    """79    if name not in _counters:80        _counters[name] = dict()81 82    elems = torch.as_tensor(value)83    if elems.numel() == 0:84        return value85 86    elems = elems.detach().flatten().to(_reduce_dtype)87    moments = torch.stack([88        torch.ones_like(elems).sum(),89        elems.sum(),90        elems.square().sum(),91    ])92    assert moments.ndim == 1 and moments.shape[0] == _num_moments93    moments = moments.to(_counter_dtype)94 95    device = moments.device96    if device not in _counters[name]:97        _counters[name][device] = torch.zeros_like(moments)98    _counters[name][device].add_(moments)99    return value100 101#----------------------------------------------------------------------------102 103def report0(name, value):104    r"""Broadcasts the given set of scalars by the first process (`rank = 0`),105    but ignores any scalars provided by the other processes.106    See `report()` for further details.107    """108    report(name, value if _rank == 0 else [])109    return value110 111#----------------------------------------------------------------------------112 113class Collector:114    r"""Collects the scalars broadcasted by `report()` and `report0()` and115    computes their long-term averages (mean and standard deviation) over116    user-defined periods of time.117 118    The averages are first collected into internal counters that are not119    directly visible to the user. They are then copied to the user-visible120    state as a result of calling `update()` and can then be queried using121    `mean()`, `std()`, `as_dict()`, etc. Calling `update()` also resets the122    internal counters for the next round, so that the user-visible state123    effectively reflects averages collected between the last two calls to124    `update()`.125 126    Args:127        regex:          Regular expression defining which statistics to128                        collect. The default is to collect everything.129        keep_previous:  Whether to retain the previous averages if no130                        scalars were collected on a given round131                        (default: True).132    """133    def __init__(self, regex='.*', keep_previous=True):134        self._regex = re.compile(regex)135        self._keep_previous = keep_previous136        self._cumulative = dict()137        self._moments = dict()138        self.update()139        self._moments.clear()140 141    def names(self):142        r"""Returns the names of all statistics broadcasted so far that143        match the regular expression specified at construction time.144        """145        return [name for name in _counters if self._regex.fullmatch(name)]146 147    def update(self):148        r"""Copies current values of the internal counters to the149        user-visible state and resets them for the next round.150 151        If `keep_previous=True` was specified at construction time, the152        operation is skipped for statistics that have received no scalars153        since the last update, retaining their previous averages.154 155        This method performs a number of GPU-to-CPU transfers and one156        `torch.distributed.all_reduce()`. It is intended to be called157        periodically in the main training loop, typically once every158        N training steps.159        """160        if not self._keep_previous:161            self._moments.clear()162        for name, cumulative in _sync(self.names()):163            if name not in self._cumulative:164                self._cumulative[name] = torch.zeros([_num_moments], dtype=_counter_dtype)165            delta = cumulative - self._cumulative[name]166            self._cumulative[name].copy_(cumulative)167            if float(delta[0]) != 0:168                self._moments[name] = delta169 170    def _get_delta(self, name):171        r"""Returns the raw moments that were accumulated for the given172        statistic between the last two calls to `update()`, or zero if173        no scalars were collected.174        """175        assert self._regex.fullmatch(name)176        if name not in self._moments:177            self._moments[name] = torch.zeros([_num_moments], dtype=_counter_dtype)178        return self._moments[name]179 180    def num(self, name):181        r"""Returns the number of scalars that were accumulated for the given182        statistic between the last two calls to `update()`, or zero if183        no scalars were collected.184        """185        delta = self._get_delta(name)186        return int(delta[0])187 188    def mean(self, name):189        r"""Returns the mean of the scalars that were accumulated for the190        given statistic between the last two calls to `update()`, or NaN if191        no scalars were collected.192        """193        delta = self._get_delta(name)194        if int(delta[0]) == 0:195            return float('nan')196        return float(delta[1] / delta[0])197 198    def std(self, name):199        r"""Returns the standard deviation of the scalars that were200        accumulated for the given statistic between the last two calls to201        `update()`, or NaN if no scalars were collected.202        """203        delta = self._get_delta(name)204        if int(delta[0]) == 0 or not np.isfinite(float(delta[1])):205            return float('nan')206        if int(delta[0]) == 1:207            return float(0)208        mean = float(delta[1] / delta[0])209        raw_var = float(delta[2] / delta[0])210        return np.sqrt(max(raw_var - np.square(mean), 0))211 212    def as_dict(self):213        r"""Returns the averages accumulated between the last two calls to214        `update()` as an `dnnlib.EasyDict`. The contents are as follows:215 216            dnnlib.EasyDict(217                NAME = dnnlib.EasyDict(num=FLOAT, mean=FLOAT, std=FLOAT),218                ...219            )220        """221        stats = dnnlib.EasyDict()222        for name in self.names():223            stats[name] = dnnlib.EasyDict(num=self.num(name), mean=self.mean(name), std=self.std(name))224        return stats225 226    def __getitem__(self, name):227        r"""Convenience getter.228        `collector[name]` is a synonym for `collector.mean(name)`.229        """230        return self.mean(name)231 232#----------------------------------------------------------------------------233 234def _sync(names):235    r"""Synchronize the global cumulative counters across devices and236    processes. Called internally by `Collector.update()`.237    """238    if len(names) == 0:239        return []240    global _sync_called241    _sync_called = True242 243    # Collect deltas within current rank.244    deltas = []245    device = _sync_device if _sync_device is not None else torch.device('cpu')246    for name in names:247        delta = torch.zeros([_num_moments], dtype=_counter_dtype, device=device)248        for counter in _counters[name].values():249            delta.add_(counter.to(device))250            counter.copy_(torch.zeros_like(counter))251        deltas.append(delta)252    deltas = torch.stack(deltas)253 254    # Sum deltas across ranks.255    if _sync_device is not None:256        torch.distributed.all_reduce(deltas)257 258    # Update cumulative values.259    deltas = deltas.cpu()260    for idx, name in enumerate(names):261        if name not in _cumulative:262            _cumulative[name] = torch.zeros([_num_moments], dtype=_counter_dtype)263        _cumulative[name].add_(deltas[idx])264 265    # Return name-value pairs.266    return [(name, _cumulative[name]) for name in names]267 268#----------------------------------------------------------------------------269