jcm/losses.py (864 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. """All functions related to loss computation and optimization. """ import optax import jax import jax.numpy as jnp import haiku as hk import jax.random as random from . import checkpoints from .models import utils as mutils from .utils import batch_mul from jcm import sde_lib import numpy as np def get_optimizer(config): """Returns a flax optimizer object based on `config`.""" if config.optim.optimizer.lower() == "adam": if hasattr(config.optim, "linear_decay_steps"): # for progressive distillation stable_training_schedule = optax.linear_schedule( init_value=config.optim.lr, end_value=0.0, transition_steps=config.optim.linear_decay_steps, ) else: stable_training_schedule = optax.constant_schedule(config.optim.lr) schedule = optax.join_schedules( [ optax.linear_schedule( init_value=0, end_value=config.optim.lr, transition_steps=config.optim.warmup, ), stable_training_schedule, ], [config.optim.warmup], ) if not np.isinf(config.optim.grad_clip): optimizer = optax.chain( optax.clip_by_global_norm(max_norm=config.optim.grad_clip), optax.adamw( learning_rate=schedule, b1=config.optim.beta1, eps=config.optim.eps, weight_decay=config.optim.weight_decay, ), ) else: optimizer = optax.adamw( learning_rate=schedule, b1=config.optim.beta1, eps=config.optim.eps, weight_decay=config.optim.weight_decay, ) elif config.optim.optimizer.lower() == "radam": beta1 = config.optim.beta1 beta2 = config.optim.beta2 eps = config.optim.eps weight_decay = config.optim.weight_decay lr = config.optim.lr optimizer = optax.chain( optax.scale_by_radam(b1=beta1, b2=beta2, eps=eps), optax.add_decayed_weights(weight_decay, None), optax.scale(-lr), ) else: raise NotImplementedError( f"Optimizer {config.optim.optimizer} not supported yet!" ) def optimize_fn(grads, opt_state, params): updates, opt_state = optimizer.update(grads, opt_state, params) params = optax.apply_updates(params, updates) return params, opt_state return optimizer, optimize_fn def get_loss_fn(config, sde, score_model, state, rng): likelihood_weighting = config.training.likelihood_weighting if config.training.loss.lower() in ["dsm", "ssm"]: ssm = config.training.loss.lower() == "ssm" train_loss_fn = get_score_matching_loss_fn( sde, score_model, train=True, likelihood_weighting=likelihood_weighting, ssm=ssm, ) eval_loss_fn = get_score_matching_loss_fn( sde, score_model, train=False, likelihood_weighting=likelihood_weighting, ssm=ssm, ) elif config.training.loss.lower().startswith( ("continuous", "consistency", "progressive_distillation") ): optimizer, optimize_fn = get_optimizer(config.training.ref_config) rng = hk.PRNGSequence(rng) ref_config = config.training.ref_config ref_model, init_ref_model_state, init_ref_params = mutils.init_model( next(rng), ref_config ) ref_state = mutils.State( step=0, lr=ref_config.optim.lr, ema_rate=ref_config.model.ema_rate, params=init_ref_params, params_ema=init_ref_params, model_state=init_ref_model_state, opt_state=optimizer.init(init_ref_params), rng_state=rng.internal_state, ) ref_state = checkpoints.restore_checkpoint( config.training.ref_model_path, ref_state ) # Initialize the flow model from the denoiser model if config.training.finetune: state = state.replace( params=ref_state.params, params_ema=ref_state.params_ema, model_state=ref_state.model_state, ) if config.training.loss_norm.lower() == "lpips": lpips_model, lpips_params = mutils.init_lpips(next(rng), config) else: lpips_model, lpips_params = None, None if config.training.loss.lower().startswith("continuous"): train_loss_fn = get_continuous_consistency_loss_fn( sde, ref_model, ref_state.params_ema, ref_state.model_state, score_model, train=True, loss_norm=config.training.loss_norm, stopgrad=config.training.stopgrad, lpips_model=lpips_model, lpips_params=lpips_params, dsm_target=config.training.dsm_target, ) eval_loss_fn = get_continuous_consistency_loss_fn( sde, ref_model, ref_state.params_ema, ref_state.model_state, score_model, train=False, loss_norm=config.training.loss_norm, stopgrad=config.training.stopgrad, lpips_model=lpips_model, lpips_params=lpips_params, dsm_target=config.training.dsm_target, ) elif config.training.loss.lower().startswith("consistency"): train_loss_fn = get_consistency_loss_fn( sde, ref_model, ref_state.params_ema, ref_state.model_state, score_model, train=True, loss_norm=config.training.loss_norm, weighting=config.training.weighting, stopgrad=config.training.stopgrad, dsm_target=config.training.dsm_target, solver=config.training.solver, lpips_model=lpips_model, lpips_params=lpips_params, ) eval_loss_fn = get_consistency_loss_fn( sde, ref_model, ref_state.params_ema, ref_state.model_state, score_model, train=False, loss_norm=config.training.loss_norm, weighting=config.training.weighting, stopgrad=config.training.stopgrad, dsm_target=config.training.dsm_target, solver=config.training.solver, lpips_model=lpips_model, lpips_params=lpips_params, ) elif config.training.loss.lower() == "progressive_distillation": train_loss_fn = get_progressive_distillation_loss_fn( sde, score_model, train=True, loss_norm=config.training.loss_norm, weighting=config.training.weighting, lpips_model=lpips_model, lpips_params=lpips_params, ) eval_loss_fn = get_progressive_distillation_loss_fn( sde, score_model, train=False, loss_norm=config.training.loss_norm, weighting=config.training.weighting, lpips_model=lpips_model, lpips_params=lpips_params, ) assert ( config.training.finetune ), "Finetuning is required for progressive distillation." state = state.replace( target_params=ref_state.params_ema, ) else: raise ValueError(f"Unknown loss {config.training.loss}") return train_loss_fn, eval_loss_fn, state def get_quarter_masks(t, ranges): return [(ranges[i] <= t) & (t < ranges[i + 1]) for i in range(len(ranges) - 1)] def get_consistency_loss_fn( sde, ref_model, ref_params, ref_states, model, train, loss_norm="l1", weighting="uniform", stopgrad=True, dsm_target=False, solver="heun", lpips_model=None, lpips_params=None, ): assert isinstance(sde, sde_lib.KVESDE), "Only KVE SDEs are supported for now." denoiser_fn = mutils.get_denoiser_fn( sde, ref_model, ref_params, ref_states, train=False, return_state=False, ) def heun_solver(samples, t, next_t, x0): x = samples if dsm_target: denoiser = x0 else: denoiser = denoiser_fn(x, t) d = batch_mul(1 / t, x - denoiser) samples = x + batch_mul(next_t - t, d) if dsm_target: denoiser = x0 else: denoiser = denoiser_fn(samples, next_t) next_d = batch_mul(1 / next_t, samples - denoiser) samples = x + batch_mul((next_t - t) / 2, d + next_d) return samples def euler_solver(samples, t, next_t, x0): x = samples if dsm_target: denoiser = x0 else: denoiser = denoiser_fn(x, t) score = batch_mul(1 / t**2, denoiser - x) samples = x + batch_mul(next_t - t, -batch_mul(score, t)) return samples if solver.lower() == "heun": ode_solver = heun_solver elif solver.lower() == "euler": ode_solver = euler_solver def loss_fn(rng, params, states, batch, target_params=None, num_scales=None): rng = hk.PRNGSequence(rng) x = batch["image"] if target_params is None: target_params = params if num_scales is None: num_scales = sde.N indices = jax.random.randint(next(rng), (x.shape[0],), 0, num_scales - 1) t = sde.t_max ** (1 / sde.rho) + indices / (num_scales - 1) * ( sde.t_min ** (1 / sde.rho) - sde.t_max ** (1 / sde.rho) ) t = t**sde.rho t2 = sde.t_max ** (1 / sde.rho) + (indices + 1) / (num_scales - 1) * ( sde.t_min ** (1 / sde.rho) - sde.t_max ** (1 / sde.rho) ) t2 = t2**sde.rho z = jax.random.normal(next(rng), x.shape) x_t = x + batch_mul(t, z) dropout_rng = next(rng) Ft, new_states = mutils.get_distiller_fn( sde, model, params, states, train=train, return_state=True )(x_t, t, rng=dropout_rng if train else None) x_t2 = ode_solver(x_t, t, t2, x) Ft2, new_states = mutils.get_distiller_fn( sde, model, target_params, new_states, train=train, return_state=True )(x_t2, t2, rng=dropout_rng if train else None) if stopgrad: Ft2 = jax.lax.stop_gradient(Ft2) diffs = Ft - Ft2 if weighting.lower() == "uniform": weight = jnp.ones_like(t) elif weighting.lower() == "snrp1": weight = 1 / t**2 + 1.0 elif weighting.lower() == "truncated_snr": weight = jnp.maximum(1 / t**2, jnp.ones_like(t)) elif weighting.lower() == "snr": weight = 1 / t**2 else: raise NotImplementedError(f"Weighting {weighting} not implemented") if loss_norm.lower() == "l1": losses = jnp.abs(diffs) losses = jnp.mean(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "l2": losses = diffs**2 losses = jnp.mean(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "linf": losses = jnp.abs(diffs) losses = jnp.max(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "lpips": scaled_Ft = jax.image.resize( Ft, (Ft.shape[0], 224, 224, 3), method="bilinear" ) scaled_Ft2 = jax.image.resize( Ft2, (Ft2.shape[0], 224, 224, 3), method="bilinear" ) losses = jnp.squeeze(lpips_model.apply(lpips_params, scaled_Ft, scaled_Ft2)) else: raise ValueError("Unknown loss norm: {}".format(loss_norm)) loss = jnp.nansum(losses * batch["mask"] * weight / jnp.sum(batch["mask"])) log_stats = {} ## Uncomment to log loss per time step # for t_index in range(sde.N - 1): # mask = (indices == t_index).astype(jnp.float32) # log_stats["loss_t{}".format(t_index)] = jnp.nansum( # losses * batch["mask"] * mask / jnp.sum(batch["mask"] * mask) # ) return loss, (new_states, log_stats) return loss_fn def get_progressive_distillation_loss_fn( sde, model, train, loss_norm="l2", weighting="truncated_snr", lpips_model=None, lpips_params=None, ): assert isinstance(sde, sde_lib.KVESDE), "Only KVE SDEs are supported for now." def loss_fn(rng, params, states, batch, target_params, num_scales): rng = hk.PRNGSequence(rng) x = batch["image"] indices = jax.random.randint(next(rng), (x.shape[0],), 0, num_scales) t = sde.t_max ** (1 / sde.rho) + indices / num_scales * ( sde.t_min ** (1 / sde.rho) - sde.t_max ** (1 / sde.rho) ) t = t**sde.rho t2 = sde.t_max ** (1 / sde.rho) + (indices + 0.5) / num_scales * ( sde.t_min ** (1 / sde.rho) - sde.t_max ** (1 / sde.rho) ) t2 = t2**sde.rho t3 = sde.t_max ** (1 / sde.rho) + (indices + 1) / num_scales * ( sde.t_min ** (1 / sde.rho) - sde.t_max ** (1 / sde.rho) ) t3 = t3**sde.rho z = jax.random.normal(next(rng), x.shape) x_t = x + batch_mul(t, z) dropout_rng = next(rng) denoised_x, new_states = mutils.get_denoiser_fn( sde, model, params, states, train=train, return_state=True )(x_t, t, rng=dropout_rng if train else None) target_denoiser_fn = mutils.get_denoiser_fn( sde, model, target_params, states, train=False, return_state=False, ) def euler_solver(samples, t, next_t): x = samples denoiser = target_denoiser_fn(x, t, rng=None) score = batch_mul(1 / t**2, denoiser - x) samples = x + batch_mul(next_t - t, -batch_mul(score, t)) return samples def euler_to_denoiser(x_t, t, x_next_t, next_t): denoiser = x_t - batch_mul(t, batch_mul(x_next_t - x_t, 1 / (next_t - t))) return denoiser x_t2 = euler_solver(x_t, t, t2) x_t3 = euler_solver(x_t2, t2, t3) target_x = jax.lax.stop_gradient(euler_to_denoiser(x_t, t, x_t3, t3)) diffs = denoised_x - target_x if loss_norm.lower() == "l1": losses = jnp.abs(diffs) losses = jnp.mean(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "l2": losses = diffs**2 losses = jnp.mean(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "linf": losses = jnp.abs(diffs) losses = jnp.max(losses.reshape(losses.shape[0], -1), axis=-1) elif loss_norm.lower() == "lpips": scaled_denoised_x = jax.image.resize( denoised_x, (denoised_x.shape[0], 224, 224, 3), method="bilinear" ) scaled_target_x = jax.image.resize( target_x, (target_x.shape[0], 224, 224, 3), method="bilinear" ) losses = jnp.squeeze( lpips_model.apply(lpips_params, scaled_denoised_x, scaled_target_x) ) else: raise ValueError("Unknown loss norm: {}".format(loss_norm)) if weighting.lower() == "snrp1": weight = 1 / t**2 + 1 elif weighting.lower() == "truncated_snr": weight = jnp.maximum(1 / t**2, jnp.ones_like(t)) elif weighting.lower() == "snr": weight = 1 / t**2 loss = jnp.nansum(losses * batch["mask"] * weight / jnp.sum(batch["mask"])) log_stats = {} return loss, (new_states, log_stats) return loss_fn def get_continuous_consistency_loss_fn( sde, ref_model, ref_params, ref_states, model, train, loss_norm="l1", stopgrad=False, lpips_model=None, lpips_params=None, dsm_target=False, ): assert isinstance(sde, sde_lib.KVESDE), "Only KVE SDEs are supported for now." score_fn = mutils.get_score_fn( sde, ref_model, ref_params, ref_states, train=False, return_state=False, ) def loss_fn(rng, params, states, batch): rng = hk.PRNGSequence(rng) x = batch["image"] # sampling t according to the Heun sampler t = jax.random.uniform( next(rng), (x.shape[0],), minval=sde.t_min ** (1 / sde.rho), maxval=sde.t_max ** (1 / sde.rho), ) ** (sde.rho) weightings = jnp.ones_like(t) z = jax.random.normal(next(rng), x.shape) x_t = x + batch_mul(t, z) if dsm_target: score_t = batch_mul(x - x_t, 1 / t**2) else: score_t = score_fn(x_t, t) if train: step_rng = next(rng) else: step_rng = None def model_fn(data, time): return mutils.get_distiller_fn( sde, model, params, states, train=train, return_state=True )(data, time, rng=step_rng) Ft, diffs, new_states = jax.jvp( model_fn, (x_t, t), (batch_mul(t, score_t), -jnp.ones_like(t)), has_aux=True ) if loss_norm.lower() == "l1": losses = jnp.abs(diffs) losses = jnp.mean(losses.reshape(losses.shape[0], -1), axis=1) elif loss_norm.lower() == "l2": losses = diffs**2 losses = jnp.sqrt(jnp.sum(losses.reshape(losses.shape[0], -1), axis=1)) elif loss_norm.lower() == "linf": losses = jnp.abs(diffs) losses = jnp.max(losses.reshape(losses.shape[0], -1), axis=1) elif loss_norm.lower() == "lpips": def metric(x): scaled_Ft = jax.image.resize( Ft, (Ft.shape[0], 224, 224, 3), method="bilinear" ) x = jax.image.resize(x, (x.shape[0], 224, 224, 3), method="bilinear") return jnp.sum( jnp.squeeze(lpips_model.apply(lpips_params, scaled_Ft, x)) ) losses = ( jax.grad(lambda x: jnp.sum(jax.grad(metric)(x) * diffs))(Ft) * diffs ) losses = jnp.sum(losses.reshape(losses.shape[0], -1), axis=1) else: raise ValueError("Unknown loss norm: {}".format(loss_norm)) if stopgrad: if loss_norm.lower() == "l2": pseudo_losses = -jax.lax.stop_gradient(diffs) * Ft pseudo_losses = jnp.sum( pseudo_losses.reshape((pseudo_losses.shape[0], -1)), axis=-1 ) loss = jnp.nansum( pseudo_losses * batch["mask"] * weightings / jnp.sum(batch["mask"]) ) elif loss_norm.lower() == "lpips": def metric_fn(x): x = jax.image.resize( x, (x.shape[0], 224, 224, 3), method="bilinear" ) y = jax.image.resize( jax.lax.stop_gradient(Ft), (x.shape[0], 224, 224, 3), method="bilinear", ) return jnp.sum(jnp.squeeze(lpips_model.apply(lpips_params, x, y))) # forward-over-reverse def hvp(f, primals, tangents): return jax.jvp(jax.grad(f), primals, tangents)[1] pseudo_losses = Ft * hvp( metric_fn, (jax.lax.stop_gradient(Ft),), (-jax.lax.stop_gradient(diffs),), ) pseudo_losses = jnp.sum( pseudo_losses.reshape((pseudo_losses.shape[0], -1)), axis=-1 ) loss = jnp.nansum( pseudo_losses * batch["mask"] * weightings / jnp.sum(batch["mask"]) ) else: raise NotImplementedError else: loss = jnp.nansum( losses * batch["mask"] * weightings / jnp.sum(batch["mask"]) ) quarter_masks = get_quarter_masks( t, np.linspace(sde.t_min ** (1 / sde.rho), sde.t_max ** (1 / sde.rho), 5) ** sde.rho, ) loss_q1 = jnp.nansum( losses * quarter_masks[0] * batch["mask"] / jnp.sum(quarter_masks[0] * batch["mask"]) ) loss_q2 = jnp.nansum( losses * quarter_masks[1] * batch["mask"] / jnp.sum(quarter_masks[1] * batch["mask"]) ) loss_q3 = jnp.nansum( losses * quarter_masks[2] * batch["mask"] / jnp.sum(quarter_masks[2] * batch["mask"]) ) loss_q4 = jnp.nansum( losses * quarter_masks[3] * batch["mask"] / jnp.sum(quarter_masks[3] * batch["mask"]) ) log_stats = { "loss": loss, "loss_q1": loss_q1, "loss_q2": loss_q2, "loss_q3": loss_q3, "loss_q4": loss_q4, } return loss, (new_states, log_stats) return loss_fn def get_score_matching_loss_fn( sde, model, train, likelihood_weighting=False, ssm=False, eps=1e-5, ): """Create a loss function for training with arbirary SDEs. Args: sde: An `sde_lib.SDE` object that represents the forward SDE. model: A `flax.linen.Module` object that represents the architecture of the score-based model. train: `True` for training loss and `False` for evaluation loss. likelihood_weighting: If `True`, weight the mixture of score matching losses according to https://arxiv.org/abs/2101.09258; otherwise use the weighting recommended in our paper. eps: A `float` number. The smallest time step to sample from. Returns: A loss function. """ def dsm_loss_fn(rng, params, states, batch): """Compute the loss function based on denoising score matching. Args: rng: A JAX random state. params: A dictionary that contains trainable parameters of the score-based model. states: A dictionary that contains mutable states of the score-based model. batch: A mini-batch of training data. Returns: loss: A scalar that represents the average loss value across the mini-batch. new_model_state: A dictionary that contains the mutated states of the score-based model. """ data = batch["image"] rng = hk.PRNGSequence(rng) if isinstance(sde, sde_lib.KVESDE): t = random.normal(next(rng), (data.shape[0],)) * 1.2 - 1.2 t = jnp.exp(t) else: t = random.uniform(next(rng), (data.shape[0],), minval=eps, maxval=sde.T) z = random.normal(next(rng), data.shape) mean, std = sde.marginal_prob(data, t) perturbed_data = mean + batch_mul(std, z) if isinstance(sde, sde_lib.KVESDE): score_fn = mutils.get_score_fn( sde, model, params, states, train=train, return_state=True, ) score, new_model_state = score_fn(perturbed_data, t, rng=next(rng)) losses = jnp.square(batch_mul(score, std) + z) losses = batch_mul( losses, (std**2 + sde.data_std**2) / sde.data_std**2 ) losses = jnp.sum(losses.reshape((losses.shape[0], -1)), axis=-1) else: score_fn = mutils.get_score_fn( sde, model, params, states, train=train, return_state=True, ) score, new_model_state = score_fn(perturbed_data, t, rng=next(rng)) if not likelihood_weighting: losses = jnp.square(batch_mul(score, std) + z) losses = jnp.mean(losses.reshape((losses.shape[0], -1)), axis=-1) else: g2 = sde.sde(jnp.zeros_like(data), t)[1] ** 2 losses = jnp.square(score + batch_mul(z, 1.0 / std)) losses = jnp.mean(losses.reshape((losses.shape[0], -1)), axis=-1) * g2 loss = jnp.nansum(losses * batch["mask"] / jnp.sum(batch["mask"])) quarter_masks = get_quarter_masks( t, np.linspace(sde.t_min ** (1 / sde.rho), sde.t_max ** (1 / sde.rho), 5) ** sde.rho, ) loss_q1 = jnp.nansum( losses * quarter_masks[0] * batch["mask"] / jnp.sum(quarter_masks[0] * batch["mask"]) ) loss_q2 = jnp.nansum( losses * quarter_masks[1] * batch["mask"] / jnp.sum(quarter_masks[1] * batch["mask"]) ) loss_q3 = jnp.nansum( losses * quarter_masks[2] * batch["mask"] / jnp.sum(quarter_masks[2] * batch["mask"]) ) loss_q4 = jnp.nansum( losses * quarter_masks[3] * batch["mask"] / jnp.sum(quarter_masks[3] * batch["mask"]) ) log_stats = { "loss_q1": loss_q1, "loss_q2": loss_q2, "loss_q3": loss_q3, "loss_q4": loss_q4, } return loss, (new_model_state, log_stats) def ssm_loss_fn(rng, params, states, batch): """Compute the loss function based on sliced score matching. Args: rng: A JAX random state. params: A dictionary that contains trainable parameters of the score-based model. states: A dictionary that contains mutable states of the score-based model. batch: A mini-batch of training data. Returns: loss: A scalar that represents the average loss value across the mini-batch. new_model_state: A dictionary that contains the mutated states of the score-based model. """ score_fn = mutils.get_score_fn( sde, model, params, states, train=train, return_state=True, ) data = batch["image"] rng = hk.PRNGSequence(rng) # DEBUG: beware of eps! if isinstance(sde, sde_lib.KVESDE): t = random.normal(next(rng), (data.shape[0],)) * 1.2 - 1.2 t = jnp.exp(t) else: t = random.uniform(next(rng), (data.shape[0],), minval=eps, maxval=sde.T) # t = random.uniform(next(rng), (data.shape[0],), minval=eps, maxval=sde.T) z = random.normal(next(rng), data.shape) mean, std = sde.marginal_prob(data, t) perturbed_data = mean + batch_mul(std, z) def score_fn_for_jvp(x): return score_fn(x, t, rng=next(rng)) epsilon = random.rademacher(next(rng), data.shape, dtype=data.dtype) score, score_trace, new_model_state = jax.jvp( score_fn_for_jvp, (perturbed_data,), (epsilon,), has_aux=True ) score_norm = jnp.mean(jnp.square(score).reshape((score.shape[0], -1)), axis=-1) score_trace = jnp.mean( (2 * score_trace * epsilon).reshape((score.shape[0], -1)), axis=-1 ) if not likelihood_weighting: losses = (score_norm + score_trace) * std**2 elif isinstance(sde, sde_lib.KVESDE): losses = score_norm + score_trace losses = ( losses * std**2 * (std**2 + sde.data_std**2) / sde.data_std**2 ) else: g2 = sde.sde(jnp.zeros_like(data), t)[1] ** 2 losses = (score_norm + score_trace) * g2 loss = jnp.nansum(losses * batch["mask"] / jnp.sum(batch["mask"])) quarter_masks = get_quarter_masks( t, np.linspace(sde.t_min ** (1 / sde.rho), sde.t_max ** (1 / sde.rho), 5) ** sde.rho, ) loss_q1 = jnp.nansum( losses * quarter_masks[0] * batch["mask"] / jnp.sum(quarter_masks[0] * batch["mask"]) ) loss_q2 = jnp.nansum( losses * quarter_masks[1] * batch["mask"] / jnp.sum(quarter_masks[1] * batch["mask"]) ) loss_q3 = jnp.nansum( losses * quarter_masks[2] * batch["mask"] / jnp.sum(quarter_masks[2] * batch["mask"]) ) loss_q4 = jnp.nansum( losses * quarter_masks[3] * batch["mask"] / jnp.sum(quarter_masks[3] * batch["mask"]) ) log_stats = { "loss_q1": loss_q1, "loss_q2": loss_q2, "loss_q3": loss_q3, "loss_q4": loss_q4, "loss": loss, } return loss, (new_model_state, log_stats) return dsm_loss_fn if not ssm else ssm_loss_fn def get_ema_scales_fn(config): if config.training.loss.lower() in ("dsm", "ssm", "continuous", "consistency"): def ema_and_scales_fn(step): return None, None else: def ema_and_scales_fn(step): if ( config.training.target_ema_mode == "fixed" and config.training.scale_mode == "fixed" ): target_ema = float(config.training.target_ema) scales = int(config.model.num_scales) elif ( config.training.target_ema_mode == "adaptive" and config.training.scale_mode == "progressive" ): start_ema = float(config.training.start_ema) start_scales = int(config.training.start_scales) end_scales = int(config.training.end_scales) total_steps = int(config.training.n_iters) scales = jnp.ceil( jnp.sqrt( (step / total_steps) * ((end_scales + 1) ** 2 - start_scales**2) + start_scales**2 ) - 1 ).astype(jnp.int32) scales = jnp.maximum(scales, 1) c = -jnp.log(start_ema) * start_scales target_ema = jnp.exp(-c / scales) scales = scales + 1 elif ( config.training.target_ema_mode == "fixed" and config.training.scale_mode == "progdist" ): start_scales = int(config.training.start_scales) distill_steps_per_iter = int(config.training.distill_steps_per_iter) distill_stage = step // distill_steps_per_iter scales = start_scales // (2**distill_stage) scales = jnp.maximum(scales, 2) sub_stage = jnp.maximum( step - distill_steps_per_iter * (jnp.log2(start_scales) - 1), 0, ) sub_stage = sub_stage // (distill_steps_per_iter * 2) sub_scales = 2 // (2**sub_stage) sub_scales = jnp.maximum(sub_scales, 1) scales = jnp.where(scales == 2, sub_scales, scales) target_ema = 1.0 else: raise NotImplementedError return target_ema, scales return ema_and_scales_fn def get_step_fn( loss_fn, train, optimize_fn=None, ema_scales_fn=None, ): """Create a one-step training/evaluation function. Args: loss_fn: The loss function for training or evaluation. It should have the signature `loss_fn(rng, params, states, batch)`. train: `True` for training and `False` for evaluation. optimize_fn: An optimization function. ema_scales_fn: A function that returns the current EMA and number of scales. Useful for progressive training. Returns: A one-step function for training or evaluation. """ def step_fn(carry_state, batch): """Running one step of training or evaluation. This function will undergo `jax.lax.scan` so that multiple steps can be pmapped and jit-compiled together for faster execution. Args: carry_state: A tuple (JAX random state, `flax.struct.dataclass` containing the training state). batch: A mini-batch of training/evaluation data. Returns: new_carry_state: The updated tuple of `carry_state`. loss: The average loss value of this state. """ (rng, state) = carry_state rng, step_rng = jax.random.split(rng) grad_fn = jax.value_and_grad(loss_fn, argnums=1, has_aux=True) if train: step = state.step params = state.params states = state.model_state opt_state = state.opt_state target_ema, num_scales = ema_scales_fn(step) if target_ema is None and num_scales is None: ( loss, (new_model_state, log_stats), ), grad = grad_fn(step_rng, params, states, batch) grad = jax.lax.pmean(grad, axis_name="batch") new_params, new_opt_state = optimize_fn(grad, opt_state, params) new_params_ema = jax.tree_util.tree_map( lambda p_ema, p: p_ema * state.ema_rate + p * (1.0 - state.ema_rate), state.params_ema, new_params, ) step = state.step + 1 new_state = state.replace( step=step, params=new_params, params_ema=new_params_ema, model_state=new_model_state, opt_state=new_opt_state, ) else: target_params = state.target_params (loss, (new_model_state, log_stats)), grad = grad_fn( step_rng, params, states, batch, target_params, num_scales ) grad = jax.lax.pmean(grad, axis_name="batch") new_params, new_opt_state = optimize_fn(grad, opt_state, params) new_params_ema = jax.tree_util.tree_map( lambda p_ema, p: p_ema * state.ema_rate + p * (1.0 - state.ema_rate), state.params_ema, new_params, ) new_target_params = jax.tree_util.tree_map( lambda p_target, p: p_target * target_ema + p * (1.0 - target_ema), target_params, new_params, ) step = state.step + 1 new_state = state.replace( step=step, params=new_params, params_ema=new_params_ema, target_params=new_target_params, model_state=new_model_state, opt_state=new_opt_state, ) else: target_ema, num_scales = ema_scales_fn(state.step) if target_ema is None and num_scales is None: loss, (_, log_stats) = loss_fn( step_rng, state.params_ema, state.model_state, batch, ) else: loss, (_, log_stats) = loss_fn( step_rng, state.params_ema, state.model_state, batch, state.target_params, num_scales, ) new_state = state loss = jax.lax.pmean(loss, axis_name="batch") mean_log_stats = jax.tree_map( lambda x: jax.lax.pmean(x, axis_name="batch"), log_stats ) new_carry_state = (rng, new_state) return new_carry_state, (loss, mean_log_stats) return step_fn