jcm/train.py (256 lines of code) (raw):
# Copyright 2023 (c) OpenAI.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Training and evaluation for score-based generative models. """
import os
from typing import Any
import flax
import flax.jax_utils as flax_utils
import jax
import jax.numpy as jnp
import numpy as np
import logging
import functools
import haiku as hk
from . import checkpoints
import wandb
# Keep the import below for registering all model definitions
from .models import ddpm, ncsnv2, ncsnpp
from .models import utils as mutils
from . import losses
from . import sampling
from . import utils
from . import datasets
from . import sde_lib
import blobfile
def train(config, workdir):
"""Runs the training pipeline.
Args:
config: Configuration to use.
workdir: Working directory for checkpoints and TF summaries. If this
contains checkpoint training will be resumed from the latest checkpoint.
"""
# Create directories for experimental logs
sample_dir = os.path.join(workdir, "samples")
blobfile.makedirs(sample_dir)
rng = hk.PRNGSequence(config.seed)
# Initialize model.
score_model, init_model_state, initial_params = mutils.init_model(next(rng), config)
optimizer, optimize_fn = losses.get_optimizer(config)
if config.training.loss.lower().endswith(
("ema", "adaptive", "progressive_distillation")
):
state = mutils.StateWithTarget(
step=0,
lr=config.optim.lr,
ema_rate=config.model.ema_rate,
params=initial_params,
target_params=initial_params,
params_ema=initial_params,
model_state=init_model_state,
opt_state=optimizer.init(initial_params),
rng_state=rng.internal_state,
)
else:
state = mutils.State(
step=0,
lr=config.optim.lr,
ema_rate=config.model.ema_rate,
params=initial_params,
params_ema=initial_params,
model_state=init_model_state,
opt_state=optimizer.init(initial_params),
rng_state=rng.internal_state,
)
# Setup SDEs
sde = sde_lib.get_sde(config)
# Build one-step training and evaluation functions
train_loss_fn, eval_loss_fn, state = losses.get_loss_fn(
config, sde, score_model, state, next(rng)
)
ema_scale_fn = losses.get_ema_scales_fn(config)
train_step_fn = losses.get_step_fn(
train_loss_fn,
train=True,
optimize_fn=optimize_fn,
ema_scales_fn=ema_scale_fn,
)
# Pmap (and jit-compile) multiple training steps together for faster running
p_train_step = jax.pmap(
functools.partial(jax.lax.scan, train_step_fn),
axis_name="batch",
)
eval_step_fn = losses.get_step_fn(
eval_loss_fn,
train=False,
optimize_fn=optimize_fn,
ema_scales_fn=ema_scale_fn,
)
# Pmap (and jit-compile) multiple evaluation steps together for faster running
p_eval_step = jax.pmap(
functools.partial(jax.lax.scan, eval_step_fn),
axis_name="batch",
)
# Create checkpoints directory
checkpoint_dir = os.path.join(workdir, "checkpoints")
# Intermediate checkpoints to resume training after pre-emption in cloud environments
checkpoint_meta_dir = os.path.join(workdir, "checkpoints-meta")
blobfile.makedirs(checkpoint_dir)
blobfile.makedirs(checkpoint_meta_dir)
# Resume training when intermediate checkpoints are detected
state = checkpoints.restore_checkpoint(checkpoint_meta_dir, state)
# `state.step` is JAX integer on the GPU/TPU devices
initial_step = int(state.step)
rng.replace_internal_state(state.rng_state)
# Finished model initialization
# Build data iterators
train_ds, eval_ds = datasets.get_dataset(
config,
additional_dim=config.training.n_jitted_steps,
uniform_dequantization=config.data.uniform_dequantization,
)
train_iter = iter(train_ds)
eval_iter = iter(eval_ds)
# Building sampling functions
if config.training.snapshot_sampling:
sampling_shape = (
config.training.batch_size // jax.local_device_count(),
config.data.image_size,
config.data.image_size,
config.data.num_channels,
)
sampling_fn = sampling.get_sampling_fn(config, sde, score_model, sampling_shape)
# Replicate the training state to run on multiple devices
pstate = flax_utils.replicate(state)
num_train_steps = config.training.n_iters
# In case there are multiple hosts (e.g., TPU pods), only log to host 0
if jax.process_index() == 0:
logging.info("Starting training loop at step %d." % (initial_step,))
rng = hk.PRNGSequence(jax.random.fold_in(next(rng), jax.process_index()))
# JIT multiple training steps together for faster training
n_jitted_steps = config.training.n_jitted_steps
# Must be divisible by the number of steps jitted together
assert (
config.training.log_freq % n_jitted_steps == 0
and config.training.snapshot_freq_for_preemption % n_jitted_steps == 0
and config.training.eval_freq % n_jitted_steps == 0
and config.training.snapshot_freq % n_jitted_steps == 0
), "The number of steps jitted together must be divisible by the logging frequency."
for step in range(
initial_step, num_train_steps + 1, config.training.n_jitted_steps
):
# Convert data to JAX arrays and normalize them. Use ._numpy() to avoid copy.
try:
data = next(train_iter)
except StopIteration:
# Restart the iterator when the dataset is exhausted.
train_iter = iter(train_ds)
data = next(train_iter)
batch = jax.tree_util.tree_map(lambda x: x.detach().cpu().numpy(), data)
next_rng = rng.take(jax.local_device_count())
next_rng = jnp.asarray(next_rng)
# Execute one training step
(_, pstate), (ploss, p_log_stats) = p_train_step((next_rng, pstate), batch)
loss = flax.jax_utils.unreplicate(ploss).mean()
log_stats = jax.tree_map(
lambda x: x.mean(), flax.jax_utils.unreplicate(p_log_stats)
)
# Log to console, file and tensorboard on host 0
if jax.process_index() == 0 and step % config.training.log_freq == 0:
logging.info("step: %d, training_loss: %.5e" % (step, loss))
if "dsm_loss" in log_stats and "distill_loss" in log_stats:
logging.info(
"step: %d, dsm_loss: %.5e, distill_loss: %.5e"
% (step, log_stats["dsm_loss"], log_stats["distill_loss"])
)
wandb.log({"training_loss": float(loss)}, step=step)
for key, value in log_stats.items():
wandb.log({f"training_{key}": float(value)}, step=step)
# Report the loss on an evaluation dataset periodically
if step % config.training.eval_freq == 0:
try:
eval_data = next(eval_iter)
except StopIteration:
eval_iter = iter(eval_ds)
eval_data = next(eval_iter)
eval_batch = jax.tree_util.tree_map(
lambda x: x.detach().cpu().numpy(), eval_data
)
next_rng = jnp.asarray(rng.take(jax.local_device_count()))
(_, _), (peval_loss, peval_log_stats) = p_eval_step(
(next_rng, pstate), eval_batch
)
eval_loss = flax.jax_utils.unreplicate(peval_loss).mean()
eval_log_stats = jax.tree_map(
lambda x: x.mean(), flax.jax_utils.unreplicate(peval_log_stats)
)
if jax.process_index() == 0:
logging.info("step: %d, eval_loss: %.5e" % (step, eval_loss))
if "dsm_loss" in eval_log_stats and "distill_loss" in eval_log_stats:
logging.info(
"step: %d, dsm_loss: %.5e, distill_loss: %.5e"
% (
step,
eval_log_stats["dsm_loss"],
eval_log_stats["distill_loss"],
)
)
wandb.log({"eval_loss": float(eval_loss)}, step=step)
for key, value in eval_log_stats.items():
wandb.log({f"eval_{key}": float(value)}, step=step)
if config.training.loss.lower() == "progressive_distillation":
ema_scale_fn = losses.get_ema_scales_fn(config)
if step > 0:
scales = int(ema_scale_fn(step)[1])
last_scales = int(ema_scale_fn(step - 1)[1])
if scales != last_scales:
# Move to the next distillation iteration
if scales == 2 or scales == 1:
config.optim.linear_decay_steps = (
config.training.distill_steps_per_iter * 2
)
elif scales == 1:
config.optim.linear_decay_steps = config.training.n_iters - step
optimizer, optimize_fn = losses.get_optimizer(config)
state = flax.jax_utils.unreplicate(pstate)
state = state.replace(
target_params=state.params_ema,
params=state.params_ema,
opt_state=optimizer.init(state.params_ema),
)
pstate = flax.jax_utils.replicate(state)
train_step_fn = losses.get_step_fn(
train_loss_fn,
train=True,
optimize_fn=optimize_fn,
ema_scales_fn=ema_scale_fn,
)
# Pmap (and jit-compile) multiple training steps together for faster running
p_train_step = jax.pmap(
functools.partial(jax.lax.scan, train_step_fn),
axis_name="batch",
)
eval_step_fn = losses.get_step_fn(
eval_loss_fn,
train=False,
optimize_fn=optimize_fn,
ema_scales_fn=ema_scale_fn,
)
# Pmap (and jit-compile) multiple evaluation steps together for faster running
p_eval_step = jax.pmap(
functools.partial(jax.lax.scan, eval_step_fn),
axis_name="batch",
)
# Save a checkpoint periodically and generate samples if needed
if (
step != 0
and step % config.training.snapshot_freq == 0
or step == num_train_steps
):
# Save the checkpoint.
if jax.process_index() == 0:
saved_state = flax_utils.unreplicate(pstate)
saved_state = saved_state.replace(rng_state=rng.internal_state)
checkpoints.save_checkpoint(
checkpoint_dir,
saved_state,
step=step // config.training.snapshot_freq,
keep=np.inf,
)
# Generate and save samples
if config.training.snapshot_sampling:
# Use the same random seed for sampling to track progress
sample_rng_seed = hk.PRNGSequence(42)
sample_rng = jnp.asarray(sample_rng_seed.take(jax.local_device_count()))
sample, n = sampling_fn(sample_rng, pstate)
sample = (sample + 1.0) / 2.0
this_sample_dir = os.path.join(
sample_dir, "iter_{}_host_{}".format(step, jax.process_index())
)
blobfile.makedirs(this_sample_dir)
image_grid = sample.reshape((-1, *sample.shape[2:]))
nrow = int(np.sqrt(image_grid.shape[0]))
sample = np.clip(sample * 255, 0, 255).astype(np.uint8)
with blobfile.BlobFile(
os.path.join(this_sample_dir, "sample.np"),
"wb",
) as fout:
np.save(fout, sample)
with blobfile.BlobFile(
os.path.join(this_sample_dir, "sample.png"),
"wb",
) as fout:
utils.save_image(image_grid, fout, nrow=nrow, padding=2)
# Save a temporary checkpoint to resume training after pre-emption periodically
# Must execute at the last to avoid corner cases where the main checkpoint was not successfully saved
if (
step != 0
and step % config.training.snapshot_freq_for_preemption == 0
and jax.process_index() == 0
):
saved_state = flax_utils.unreplicate(pstate)
saved_state = saved_state.replace(rng_state=rng.internal_state)
checkpoints.save_checkpoint(
checkpoint_meta_dir,
saved_state,
step=step // config.training.snapshot_freq_for_preemption,
keep=1,
)