| import json |
| import logging |
| import math |
| import os |
| import time |
|
|
| import numpy as np |
| import torch |
| import torch.nn.functional as F |
| from torch.nn.parallel.distributed import DistributedDataParallel |
|
|
| from training.distributed import is_master |
| from training.precision import get_autocast |
|
|
| try: |
| import wandb |
| except ImportError: |
| wandb = None |
|
|
| from open_clip import get_input_dtype, CLIP, CustomTextCLIP |
|
|
|
|
| class AverageMeter(object): |
| """Computes and stores the average and current value""" |
|
|
| def __init__(self): |
| self.reset() |
|
|
| def reset(self): |
| self.val = 0 |
| self.avg = 0 |
| self.sum = 0 |
| self.count = 0 |
|
|
| def update(self, val, n=1): |
| self.val = val |
| self.sum += val * n |
| self.count += n |
| self.avg = self.sum / self.count |
|
|
| def postprocess_clip_output(model_out): |
| return { |
| "image_features": model_out[0], |
| "text_features": model_out[1], |
| "logit_scale": model_out[2] |
| } |
|
|
| def unwrap_model(model): |
| if hasattr(model, 'module'): |
| return model.module |
| else: |
| return model |
|
|
|
|
| def backward(total_loss, scaler): |
| if scaler is not None: |
| scaler.scale(total_loss).backward() |
| else: |
| total_loss.backward() |
|
|
|
|
| def train_one_epoch(model, data, loss, epoch, optimizer, scaler, scheduler, dist_model, args, tb_writer=None): |
| device = torch.device(args.device) |
| autocast = get_autocast(args.precision) |
| input_dtype = get_input_dtype(args.precision) |
|
|
|
|
| model.train() |
| if args.distill: |
| dist_model.eval() |
|
|
| data[f'{args.clip_type}_pt'].set_epoch(epoch) |
| dataloader = data[f'{args.clip_type}_pt'].dataloader |
| num_batches_per_epoch = dataloader.num_batches // args.accum_freq |
| sample_digits = math.ceil(math.log(dataloader.num_samples + 1, 10)) |
|
|
| if args.accum_freq > 1: |
| accum_images, accum_input_ids, accum_attention_mask, accum_features = [], [], [], {} |
|
|
| losses_m = {} |
| batch_time_m = AverageMeter() |
| data_time_m = AverageMeter() |
| end = time.time() |
| for i, batch in enumerate(dataloader): |
| i_accum = i // args.accum_freq |
| step = num_batches_per_epoch * epoch + i_accum |
|
|
| if not args.skip_scheduler: |
| scheduler(step) |
|
|
| images, input_ids, attention_mask = batch |
| images = images.to(device=device, dtype=input_dtype, non_blocking=True) |
| input_ids = input_ids.to(device=device, non_blocking=True) |
| attention_mask = attention_mask.to(device=device, non_blocking=True) |
|
|
| data_time_m.update(time.time() - end) |
| optimizer.zero_grad() |
|
|
| if args.accum_freq == 1: |
| with autocast(): |
| model_out = model(images, input_ids, attention_mask) |
| logit_scale = model_out["logit_scale"] |
| if args.distill: |
| with torch.no_grad(): |
| dist_model_out = dist_model(images, input_ids, attention_mask) |
| model_out.update({f'dist_{k}' : v for k, v in dist_model_out.items()}) |
| losses = loss(**model_out, output_dict=True) |
|
|
| total_loss = sum(losses.values()) |
| losses["loss"] = total_loss |
|
|
| backward(total_loss, scaler) |
| else: |
| |
| with torch.no_grad(): |
| with autocast(): |
| model_out = model(images, input_ids, attention_mask) |
| model_out.pop("logit_scale") |
| for key, val in model_out.items(): |
| if key in accum_features: |
| accum_features[key].append(val) |
| else: |
| accum_features[key] = [val] |
|
|
| accum_images.append(images) |
| accum_input_ids.append(input_ids) |
| accum_attention_mask.append(attention_mask) |
|
|
| |
| if ((i + 1) % args.accum_freq) > 0: |
| |
| continue |
|
|
| |
| |
| |
| optimizer.zero_grad() |
| for j in range(args.accum_freq): |
| images = accum_images[j] |
| input_ids = accum_input_ids[j] |
| attention_mask = accum_attention_mask[j] |
| with autocast(): |
| model_out = model(images, input_ids, attention_mask) |
| logit_scale = model_out.pop("logit_scale") |
| inputs = {} |
| for key, val in accum_features.items(): |
| accumulated = accum_features[key] |
| inputs[key] = torch.cat(accumulated[:j] + [model_out[key]] + accumulated[j + 1:]) |
| losses = loss(**inputs, logit_scale=logit_scale, output_dict=True) |
| del inputs |
| total_loss = sum(losses.values()) |
| losses["loss"] = total_loss |
| backward(total_loss, scaler) |
|
|
| if scaler is not None: |
| if args.horovod: |
| optimizer.synchronize() |
| scaler.unscale_(optimizer) |
| if args.grad_clip_norm is not None: |
| torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip_norm, norm_type=2.0) |
| with optimizer.skip_synchronize(): |
| scaler.step(optimizer) |
| else: |
| if args.grad_clip_norm is not None: |
| scaler.unscale_(optimizer) |
| torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip_norm, norm_type=2.0) |
| scaler.step(optimizer) |
| scaler.update() |
| else: |
| if args.grad_clip_norm is not None: |
| torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip_norm, norm_type=2.0) |
| optimizer.step() |
|
|
| |
| if args.accum_freq > 1: |
| accum_images, accum_input_ids, accum_attention_mask, accum_features = [], [], [], {} |
|
|
| |
| with torch.no_grad(): |
| unwrap_model(model).logit_scale.clamp_(0, math.log(100)) |
|
|
| batch_time_m.update(time.time() - end) |
| end = time.time() |
| batch_count = i_accum + 1 |
| if is_master(args) and (i_accum % args.log_every_n_steps == 0 or batch_count == num_batches_per_epoch): |
| batch_size = len(images) |
| num_samples = batch_count * batch_size * args.accum_freq * args.world_size |
| samples_per_epoch = dataloader.num_samples |
| percent_complete = 100.0 * batch_count / num_batches_per_epoch |
|
|
| |
| for key, val in losses.items(): |
| if key not in losses_m: |
| losses_m[key] = AverageMeter() |
| losses_m[key].update(val.item(), batch_size) |
|
|
| logit_scale_scalar = logit_scale.item() |
| |
| |
| |
| |
| |
| |
| loss_log = " ".join( |
| [ |
| f"{loss_name.capitalize()}: {loss_m.val:#.5g} ({loss_m.avg:#.5g})" |
| for loss_name, loss_m in losses_m.items() |
| ] |
| ) |
| samples_per_second = args.accum_freq * args.batch_size * args.world_size / batch_time_m.val |
| samples_per_second_per_gpu = args.accum_freq * args.batch_size / batch_time_m.val |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| logging.info( |
| f"Train Epoch: {epoch} [{num_samples:>{sample_digits}}/{samples_per_epoch} ({percent_complete:.0f}%)] " |
| f"Data (t): {data_time_m.avg:.3f} " |
| f"Batch (t): {batch_time_m.avg:.3f}, {samples_per_second:#g}/s, {samples_per_second_per_gpu:#g}/s/gpu " |
| f"LR: {optimizer.param_groups[0]['lr']:5f} " |
| f"Logit Scale: {logit_scale_scalar:.3f} " + loss_log |
| ) |
|
|
|
|
| |
| log_data = { |
| "data_time": data_time_m.val, |
| "batch_time": batch_time_m.val, |
| "samples_per_second": samples_per_second, |
| "samples_per_second_per_gpu": samples_per_second_per_gpu, |
| "scale": logit_scale_scalar, |
| "lr": optimizer.param_groups[0]["lr"] |
| } |
| log_data.update({name:val.val for name,val in losses_m.items()}) |
| |
| |
| |
|
|
| for name, val in log_data.items(): |
| name = "train/" + name |
| if tb_writer is not None: |
| tb_writer.add_scalar(name, val, step) |
| if args.wandb: |
| assert wandb is not None, 'Please install wandb.' |
| wandb.log({name: val, 'step': step}) |
|
|
| |
| batch_time_m.reset() |
| data_time_m.reset() |
| |
|
|