Download util/misc.py from yangjiange/STP: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/yangjiange/STP/resolve/main/util/misc.py
- Command line
-
hf download hf://yangjiange/STP/util/misc.py
-
curl -L -o misc.py https://huggingface.co/yangjiange/STP/resolve/main/util/misc.py
11.2 kB
| """ | |
| Runtime helpers: distributed setup, metric logging, AMP loss scaling, and | |
| checkpoint I/O. | |
| Written for STP. The distributed-helper and metric-logger patterns are the | |
| conventional ones for PyTorch DDP training scripts; the checkpoint format | |
| matches what the released STP weights were saved with, so `--resume` works on | |
| them directly. | |
| """ | |
| import datetime | |
| import os | |
| import time | |
| from collections import defaultdict, deque | |
| from pathlib import Path | |
| import torch | |
| import torch.distributed as dist | |
| # --------------------------------------------------------------------------- | |
| # metric logging | |
| # --------------------------------------------------------------------------- | |
| class SmoothedValue: | |
| """Running mean (and window mean) of a scalar.""" | |
| def __init__(self, window_size=20, fmt=None): | |
| self.deque = deque(maxlen=window_size) | |
| self.total = 0.0 | |
| self.count = 0 | |
| self.fmt = fmt | |
| def update(self, value, n=1): | |
| self.deque.append(value) | |
| self.count += n | |
| self.total += value * n | |
| def synchronize_between_processes(self): | |
| if not is_dist_avail_and_initialized(): | |
| return | |
| t = torch.tensor([self.count, self.total], dtype=torch.float64, device='cuda') | |
| dist.barrier() | |
| dist.all_reduce(t) | |
| t = t.tolist() | |
| self.count = int(t[0]) | |
| self.total = t[1] | |
| def median(self): | |
| d = torch.tensor(list(self.deque)) | |
| return d.median().item() | |
| def avg(self): | |
| d = torch.tensor(list(self.deque), dtype=torch.float32) | |
| return d.mean().item() | |
| def global_avg(self): | |
| return self.total / self.count | |
| def max(self): | |
| return max(self.deque) | |
| def value(self): | |
| return self.deque[-1] | |
| def __str__(self): | |
| return self.fmt.format( | |
| median=self.median, avg=self.avg, global_avg=self.global_avg, | |
| max=self.max, value=self.value) | |
| class MetricLogger: | |
| """Prints and averages a set of named meters, optionally across ranks.""" | |
| def __init__(self, delimiter='\t'): | |
| self.meters = defaultdict(SmoothedValue) | |
| self.delimiter = delimiter | |
| def update(self, **kwargs): | |
| for k, v in kwargs.items(): | |
| if v is None: | |
| continue | |
| if isinstance(v, torch.Tensor): | |
| v = v.item() | |
| assert isinstance(v, (float, int)) | |
| self.meters[k].update(v) | |
| def __getattr__(self, attr): | |
| if attr in self.meters: | |
| return self.meters[attr] | |
| if attr in self.__dict__: | |
| return self.__dict__[attr] | |
| raise AttributeError(f"'{type(self).__name__}' object has no attribute '{attr}'") | |
| def __str__(self): | |
| parts = [f'{name}: {str(meter)}' for name, meter in self.meters.items()] | |
| return self.delimiter.join(parts) | |
| def add_meter(self, name, meter): | |
| self.meters[name] = meter | |
| def log_every(self, iterable, print_freq, header=None): | |
| i = 0 | |
| if not header: | |
| header = '' | |
| start_time = time.time() | |
| end = time.time() | |
| iter_time = SmoothedValue(fmt='{avg:.4f}') | |
| data_time = SmoothedValue(fmt='{avg:.4f}') | |
| space_fmt = ':' + str(len(str(len(iterable)))) + 'd' | |
| log_msg = [ | |
| header, | |
| '[{0' + space_fmt + '}/{1}]', | |
| 'eta: {eta}', | |
| '{meters}', | |
| 'time: {time}', | |
| 'data: {data}', | |
| ] | |
| if torch.cuda.is_available(): | |
| log_msg.append('max mem: {memory:.0f}') | |
| log_msg = self.delimiter.join(log_msg) | |
| MB = 1024.0 * 1024.0 | |
| for obj in iterable: | |
| data_time.update(time.time() - end) | |
| yield obj | |
| iter_time.update(time.time() - end) | |
| if i % print_freq == 0 or i == len(iterable) - 1: | |
| eta_seconds = iter_time.global_avg * (len(iterable) - i) | |
| eta_string = str(datetime.timedelta(seconds=int(eta_seconds))) | |
| if torch.cuda.is_available(): | |
| print(log_msg.format( | |
| i, len(iterable), eta=eta_string, meters=str(self), | |
| time=str(iter_time), data=str(data_time), | |
| memory=torch.cuda.max_memory_allocated() / MB)) | |
| else: | |
| print(log_msg.format( | |
| i, len(iterable), eta=eta_string, meters=str(self), | |
| time=str(iter_time), data=str(data_time))) | |
| i += 1 | |
| end = time.time() | |
| total_time = time.time() - start_time | |
| total_time_str = str(datetime.timedelta(seconds=int(total_time))) | |
| print(f'{header} Total time: {total_time_str} ({total_time / len(iterable):.4f} s / it)') | |
| # --------------------------------------------------------------------------- | |
| # distributed | |
| # --------------------------------------------------------------------------- | |
| def is_dist_avail_and_initialized(): | |
| if not dist.is_available(): | |
| return False | |
| if not dist.is_initialized(): | |
| return False | |
| return True | |
| def get_world_size(): | |
| if not is_dist_avail_and_initialized(): | |
| return 1 | |
| return dist.get_world_size() | |
| def get_rank(): | |
| if not is_dist_avail_and_initialized(): | |
| return 0 | |
| return dist.get_rank() | |
| def is_main_process(): | |
| return get_rank() == 0 | |
| def save_on_master(*args, **kwargs): | |
| if is_main_process(): | |
| torch.save(*args, **kwargs) | |
| def init_distributed_mode(args): | |
| """Fill in args.distributed / rank / world_size / gpu from the launcher env. | |
| Works with `torchrun` (env:// rendezvous), SLURM, or single-process runs. | |
| """ | |
| if 'RANK' in os.environ and 'WORLD_SIZE' in os.environ: | |
| args.rank = int(os.environ['RANK']) | |
| args.world_size = int(os.environ['WORLD_SIZE']) | |
| args.gpu = int(os.environ.get('LOCAL_RANK', 0)) | |
| elif 'SLURM_PROCID' in os.environ: | |
| args.rank = int(os.environ['SLURM_PROCID']) | |
| args.gpu = args.rank % torch.cuda.device_count() | |
| else: | |
| print('Not using distributed mode') | |
| args.distributed = False | |
| return | |
| args.distributed = True | |
| torch.cuda.set_device(args.gpu) | |
| args.dist_backend = 'nccl' | |
| print(f'| distributed init (rank {args.rank}): {args.dist_url}', flush=True) | |
| torch.distributed.init_process_group( | |
| backend=args.dist_backend, init_method=args.dist_url, | |
| world_size=args.world_size, rank=args.rank) | |
| torch.distributed.barrier() | |
| setup_for_distributed(args.rank == 0) | |
| def setup_for_distributed(is_master): | |
| """Silence non-master ranks' stdout, including inside tqdm.""" | |
| import builtins as __builtin__ | |
| builtin_print = __builtin__.print | |
| def print(*args, **kwargs): | |
| force = kwargs.pop('force', False) | |
| if is_master or force: | |
| builtin_print(*args, **kwargs) | |
| __builtin__.print = print | |
| def all_reduce_mean(x): | |
| world_size = get_world_size() | |
| if world_size > 1: | |
| x_reduce = torch.tensor(x).cuda() | |
| dist.all_reduce(x_reduce) | |
| x_reduce /= world_size | |
| return x_reduce.item() | |
| return x | |
| # --------------------------------------------------------------------------- | |
| # mixed-precision loss scaling | |
| # --------------------------------------------------------------------------- | |
| class NativeScalerWithGradNormCount: | |
| """amp.GradScaler that also reports the (unscaled) gradient norm. | |
| Mirrors the interface of the MAE/DeiT training scripts so the training loop | |
| reads the same way, and so `scaler` round-trips through checkpoints. | |
| """ | |
| state_dict_key = 'amp_scaler' | |
| def __init__(self): | |
| self._scaler = torch.cuda.amp.GradScaler() | |
| def __call__(self, loss, optimizer, clip_grad=None, parameters=None, | |
| create_graph=False, update_grad=True): | |
| self._scaler.scale(loss).backward(create_graph=create_graph) | |
| if update_grad: | |
| if clip_grad is not None: | |
| assert parameters is not None | |
| self._scaler.unscale_(optimizer) | |
| norm = torch.nn.utils.clip_grad_norm_(parameters, clip_grad) | |
| else: | |
| self._scaler.unscale_(optimizer) | |
| norm = get_grad_norm_(parameters) | |
| self._scaler.step(optimizer) | |
| self._scaler.update() | |
| else: | |
| norm = None | |
| return norm | |
| def state_dict(self): | |
| return self._scaler.state_dict() | |
| def load_state_dict(self, state_dict): | |
| self._scaler.load_state_dict(state_dict) | |
| def get_grad_norm_(parameters, norm_type: float = 2.0) -> torch.Tensor: | |
| if isinstance(parameters, torch.Tensor): | |
| parameters = [parameters] | |
| parameters = [p for p in parameters if p.grad is not None] | |
| norm_type = float(norm_type) | |
| if len(parameters) == 0: | |
| return torch.tensor(0.) | |
| device = parameters[0].grad.device | |
| if norm_type == float('inf'): | |
| total_norm = max(p.grad.detach().abs().max().to(device) for p in parameters) | |
| else: | |
| total_norm = torch.norm( | |
| torch.stack([torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]), | |
| norm_type) | |
| if torch.isnan(total_norm) or torch.isinf(total_norm): | |
| total_norm = torch.tensor(0., device=device) | |
| return total_norm | |
| # --------------------------------------------------------------------------- | |
| # checkpoints | |
| # --------------------------------------------------------------------------- | |
| def save_model(args, epoch, model, model_without_ddp, optimizer, loss_scaler): | |
| """Write `checkpoint-<epoch>.pth` holding {model, optimizer, epoch, scaler, args}. | |
| Same layout as the released `0.95_aug0.8.pth`, so those weights | |
| can be loaded with `load_model(--resume ...)` and training continued. | |
| """ | |
| output_dir = Path(args.output_dir) | |
| checkpoint_path = output_dir / f'checkpoint-{epoch}.pth' | |
| to_save = { | |
| 'model': model_without_ddp.state_dict(), | |
| 'optimizer': optimizer.state_dict(), | |
| 'epoch': epoch, | |
| 'scaler': loss_scaler.state_dict() if loss_scaler is not None else None, | |
| 'args': args, | |
| } | |
| save_on_master(to_save, checkpoint_path) | |
| def load_model(args, model_without_ddp, optimizer, loss_scaler): | |
| if not args.resume: | |
| return | |
| if args.resume.startswith('https'): | |
| checkpoint = torch.hub.load_state_dict_from_url(args.resume, map_location='cpu', check_hash=True) | |
| else: | |
| from util.ckpt_io import load_checkpoint | |
| checkpoint = load_checkpoint(args.resume) | |
| model_without_ddp.load_state_dict(checkpoint['model']) | |
| print(f'Resume checkpoint {args.resume}') | |
| has_opt = 'optimizer' in checkpoint and 'epoch' in checkpoint | |
| if has_opt and not getattr(args, 'eval', False): | |
| optimizer.load_state_dict(checkpoint['optimizer']) | |
| args.start_epoch = checkpoint['epoch'] + 1 | |
| if 'scaler' in checkpoint and checkpoint['scaler'] is not None and loss_scaler is not None: | |
| loss_scaler.load_state_dict(checkpoint['scaler']) | |
| print('With optim & sched!') | |