diffumatch / edm /training /training_loop.py
daidedou
forgot this
df60d6b
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# This work is licensed under a Creative Commons
# Attribution-NonCommercial-ShareAlike 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-sa/4.0/
"""Main training loop."""
import os
import time
import copy
import json
import pickle
import psutil
import numpy as np
import torch
import dnnlib
from torch_utils import distributed as dist
from torch_utils import training_stats
from torch_utils import misc
#----------------------------------------------------------------------------
def training_loop(
run_dir = '.', # Output directory.
dataset_kwargs = {}, # Options for training set.
data_loader_kwargs = {}, # Options for torch.utils.data.DataLoader.
network_kwargs = {}, # Options for model and preconditioning.
loss_kwargs = {}, # Options for loss function.
optimizer_kwargs = {}, # Options for optimizer.
augment_kwargs = None, # Options for augmentation pipeline, None = disable.
seed = 0, # Global random seed.
batch_size = 512, # Total batch size for one training iteration.
batch_gpu = None, # Limit batch size per GPU, None = no limit.
total_kimg = 200000, # Training duration, measured in thousands of training images.
ema_halflife_kimg = 500, # Half-life of the exponential moving average (EMA) of model weights.
ema_rampup_ratio = 0.05, # EMA ramp-up coefficient, None = no rampup.
lr_rampup_kimg = 10000, # Learning rate ramp-up duration.
loss_scaling = 1, # Loss scaling factor for reducing FP16 under/overflows.
kimg_per_tick = 50, # Interval of progress prints.
snapshot_ticks = 50, # How often to save network snapshots, None = disable.
state_dump_ticks = 500, # How often to dump training state, None = disable.
resume_pkl = None, # Start from the given network snapshot, None = random initialization.
resume_state_dump = None, # Start from the given training state, None = reset training state.
resume_kimg = 0, # Start from the given training progress.
cudnn_benchmark = True, # Enable torch.backends.cudnn.benchmark?
device = torch.device('cuda'),
):
# Initialize.
start_time = time.time()
np.random.seed((seed * dist.get_world_size() + dist.get_rank()) % (1 << 31))
torch.manual_seed(np.random.randint(1 << 31))
torch.backends.cudnn.benchmark = cudnn_benchmark
torch.backends.cudnn.allow_tf32 = False
torch.backends.cuda.matmul.allow_tf32 = False
torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = False
# Select batch size per GPU.
batch_gpu_total = batch_size // dist.get_world_size()
if batch_gpu is None or batch_gpu > batch_gpu_total:
batch_gpu = batch_gpu_total
num_accumulation_rounds = batch_gpu_total // batch_gpu
assert batch_size == batch_gpu * num_accumulation_rounds * dist.get_world_size()
# Load dataset.
dist.print0('Loading dataset...')
dataset_obj = dnnlib.util.construct_class_by_name(**dataset_kwargs) # subclass of training.dataset.Dataset
dataset_sampler = misc.InfiniteSampler(dataset=dataset_obj, rank=dist.get_rank(), num_replicas=dist.get_world_size(), seed=seed)
dataset_iterator = iter(torch.utils.data.DataLoader(dataset=dataset_obj, sampler=dataset_sampler, batch_size=batch_gpu, **data_loader_kwargs))
# Construct network.
dist.print0('Constructing network...')
interface_kwargs = dict(img_resolution=dataset_obj.resolution, img_channels=dataset_obj.num_channels, label_dim=dataset_obj.label_dim)
net = dnnlib.util.construct_class_by_name(**network_kwargs, **interface_kwargs) # subclass of torch.nn.Module
net.train().requires_grad_(True).to(device)
if dist.get_rank() == 0:
with torch.no_grad():
images = torch.zeros([batch_gpu, net.img_channels, net.img_resolution, net.img_resolution], device=device)
sigma = torch.ones([batch_gpu], device=device)
labels = torch.zeros([batch_gpu, net.label_dim], device=device)
misc.print_module_summary(net, [images, sigma, labels], max_nesting=2)
# Setup optimizer.
dist.print0('Setting up optimizer...')
loss_fn = dnnlib.util.construct_class_by_name(**loss_kwargs) # training.loss.(VP|VE|EDM)Loss
optimizer = dnnlib.util.construct_class_by_name(params=net.parameters(), **optimizer_kwargs) # subclass of torch.optim.Optimizer
augment_pipe = dnnlib.util.construct_class_by_name(**augment_kwargs) if augment_kwargs is not None else None # training.augment.AugmentPipe
ddp = torch.nn.parallel.DistributedDataParallel(net, device_ids=[device], broadcast_buffers=False)
ema = copy.deepcopy(net).eval().requires_grad_(False)
# Resume training from previous snapshot.
if resume_pkl is not None:
dist.print0(f'Loading network weights from "{resume_pkl}"...')
if dist.get_rank() != 0:
torch.distributed.barrier() # rank 0 goes first
with dnnlib.util.open_url(resume_pkl, verbose=(dist.get_rank() == 0)) as f:
data = pickle.load(f)
if dist.get_rank() == 0:
torch.distributed.barrier() # other ranks follow
misc.copy_params_and_buffers(src_module=data['ema'], dst_module=net, require_all=False)
misc.copy_params_and_buffers(src_module=data['ema'], dst_module=ema, require_all=False)
del data # conserve memory
if resume_state_dump:
dist.print0(f'Loading training state from "{resume_state_dump}"...')
data = torch.load(resume_state_dump, map_location=torch.device('cpu'))
misc.copy_params_and_buffers(src_module=data['net'], dst_module=net, require_all=True)
optimizer.load_state_dict(data['optimizer_state'])
del data # conserve memory
# Train.
dist.print0(f'Training for {total_kimg} kimg...')
dist.print0()
cur_nimg = resume_kimg * 1000
cur_tick = 0
tick_start_nimg = cur_nimg
tick_start_time = time.time()
maintenance_time = tick_start_time - start_time
dist.update_progress(cur_nimg // 1000, total_kimg)
stats_jsonl = None
while True:
# Accumulate gradients.
optimizer.zero_grad(set_to_none=True)
for round_idx in range(num_accumulation_rounds):
with misc.ddp_sync(ddp, (round_idx == num_accumulation_rounds - 1)):
images, labels = next(dataset_iterator)
images = images.to(device).to(torch.float32) / 127.5 - 1
labels = labels.to(device)
loss = loss_fn(net=ddp, images=images, labels=labels, augment_pipe=augment_pipe)
training_stats.report('Loss/loss', loss)
loss.sum().mul(loss_scaling / batch_gpu_total).backward()
# Update weights.
for g in optimizer.param_groups:
g['lr'] = optimizer_kwargs['lr'] * min(cur_nimg / max(lr_rampup_kimg * 1000, 1e-8), 1)
for param in net.parameters():
if param.grad is not None:
torch.nan_to_num(param.grad, nan=0, posinf=1e5, neginf=-1e5, out=param.grad)
optimizer.step()
# Update EMA.
ema_halflife_nimg = ema_halflife_kimg * 1000
if ema_rampup_ratio is not None:
ema_halflife_nimg = min(ema_halflife_nimg, cur_nimg * ema_rampup_ratio)
ema_beta = 0.5 ** (batch_size / max(ema_halflife_nimg, 1e-8))
for p_ema, p_net in zip(ema.parameters(), net.parameters()):
p_ema.copy_(p_net.detach().lerp(p_ema, ema_beta))
# Perform maintenance tasks once per tick.
cur_nimg += batch_size
done = (cur_nimg >= total_kimg * 1000)
if (not done) and (cur_tick != 0) and (cur_nimg < tick_start_nimg + kimg_per_tick * 1000):
continue
# Print status line, accumulating the same information in training_stats.
tick_end_time = time.time()
fields = []
fields += [f"tick {training_stats.report0('Progress/tick', cur_tick):<5d}"]
fields += [f"kimg {training_stats.report0('Progress/kimg', cur_nimg / 1e3):<9.1f}"]
fields += [f"time {dnnlib.util.format_time(training_stats.report0('Timing/total_sec', tick_end_time - start_time)):<12s}"]
fields += [f"sec/tick {training_stats.report0('Timing/sec_per_tick', tick_end_time - tick_start_time):<7.1f}"]
fields += [f"sec/kimg {training_stats.report0('Timing/sec_per_kimg', (tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg) * 1e3):<7.2f}"]
fields += [f"maintenance {training_stats.report0('Timing/maintenance_sec', maintenance_time):<6.1f}"]
fields += [f"cpumem {training_stats.report0('Resources/cpu_mem_gb', psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}"]
fields += [f"gpumem {training_stats.report0('Resources/peak_gpu_mem_gb', torch.cuda.max_memory_allocated(device) / 2**30):<6.2f}"]
fields += [f"reserved {training_stats.report0('Resources/peak_gpu_mem_reserved_gb', torch.cuda.max_memory_reserved(device) / 2**30):<6.2f}"]
torch.cuda.reset_peak_memory_stats()
dist.print0(' '.join(fields))
# Check for abort.
if (not done) and dist.should_stop():
done = True
dist.print0()
dist.print0('Aborting...')
# Save network snapshot.
if (snapshot_ticks is not None) and (done or cur_tick % snapshot_ticks == 0):
data = dict(ema=ema, loss_fn=loss_fn, augment_pipe=augment_pipe, dataset_kwargs=dict(dataset_kwargs))
for key, value in data.items():
if isinstance(value, torch.nn.Module):
value = copy.deepcopy(value).eval().requires_grad_(False)
misc.check_ddp_consistency(value)
data[key] = value.cpu()
del value # conserve memory
if dist.get_rank() == 0:
with open(os.path.join(run_dir, f'network-snapshot-{cur_nimg//1000:06d}.pkl'), 'wb') as f:
pickle.dump(data, f)
del data # conserve memory
# Save full dump of the training state.
if (state_dump_ticks is not None) and (done or cur_tick % state_dump_ticks == 0) and cur_tick != 0 and dist.get_rank() == 0:
torch.save(dict(net=net, optimizer_state=optimizer.state_dict()), os.path.join(run_dir, f'training-state-{cur_nimg//1000:06d}.pt'))
# Update logs.
training_stats.default_collector.update()
if dist.get_rank() == 0:
if stats_jsonl is None:
stats_jsonl = open(os.path.join(run_dir, 'stats.jsonl'), 'at')
stats_jsonl.write(json.dumps(dict(training_stats.default_collector.as_dict(), timestamp=time.time())) + '\n')
stats_jsonl.flush()
dist.update_progress(cur_nimg // 1000, total_kimg)
# Update state.
cur_tick += 1
tick_start_nimg = cur_nimg
tick_start_time = time.time()
maintenance_time = tick_start_time - tick_end_time
if done:
break
# Done.
dist.print0()
dist.print0('Exiting...')
#----------------------------------------------------------------------------