import itertools import os import random import inspect from dataclasses import dataclass from tqdm import tqdm import hydra.utils import lightning as L import numpy as np import torch import torch.nn.functional as F import transformers import wandb from torch.cuda.amp import autocast import torch.distributed as dist import dataloader import metrics import models import utils from omegaconf import ListConfig @dataclass class Loss: loss: torch.FloatTensor nlls: torch.FloatTensor prior_loss: torch.FloatTensor num_tokens: torch.FloatTensor class LogLinear(torch.nn.Module): def __init__(self): super().__init__() self.eps = 1e-3 # To be consistent with SEDD: https://github.com/louaaron/Score-Entropy-Discrete-Diffusion/blob/0605786da5ccb5747545e26d66fdf477187598b6/noise_lib.py#L56 def forward(self, t): t = (1 - self.eps) * t alpha_t = 1 - t dalpha_t = - (1 - self.eps) + t * 0 assert alpha_t.shape == dalpha_t.shape return dalpha_t, alpha_t def sample_categorical(categorical_probs, temperature=1.0): categorical_probs = categorical_probs.to(torch.float64) if temperature != 1.0: categorical_probs = categorical_probs.pow(1.0 / temperature) gumbel_norm = ( 1e-10 - (torch.rand_like(categorical_probs) + 1e-10).log()) return (categorical_probs / gumbel_norm.to(categorical_probs.dtype)).argmax(dim=-1) def _unsqueeze(x, reference): return x.view( * x.shape, * ((1,) * (len(reference.shape) - len(x.shape)))) class TrainerBase(L.LightningModule): def __init__( self, config, tokenizer: transformers.PreTrainedTokenizer, vocab_size=None): super().__init__() self.save_hyperparameters() self.config = config if hasattr(self.config.algo, 'ignore_bos'): self.ignore_bos = config.algo.ignore_bos else: self.ignore_bos = False if hasattr(self.config.algo, 'loss_type'): self.loss_type = config.algo.loss_type self.tokenizer = tokenizer if vocab_size is None: self.vocab_size = len(self.tokenizer) else: self.vocab_size = vocab_size self.sampler = self.config.sampling.predictor self.antithetic_sampling = self.config.training.antithetic_sampling self.parameterization = self.config.algo.parameterization if self.config.algo.backbone == 'dit': self.backbone = models.dit.DIT( self.config, vocab_size=self.vocab_size) elif self.config.algo.backbone == 'dimamba': self.backbone = models.dimamba.DiMamba( self.config, vocab_size=self.vocab_size, pad_token_id=self.tokenizer.pad_token_id) elif self.config.algo.backbone == 'hf_dit': self.backbone = transformers.AutoModelForMaskedLM.from_pretrained( config.eval.checkpoint_path, trust_remote_code=True) self._pending_ema_state = None self.T = self.config.algo.T self.num_tokens = self.config.model.length self.softplus = torch.nn.Softplus() self.p_nucleus = self.config.sampling.p_nucleus # Noise Schedule self.noise = LogLinear() self.metrics = metrics.Metrics( gen_ppl_eval_model_name_or_path=self.config.eval.gen_ppl_eval_model_name_or_path, eval_ppl_batch_size=self.config.eval.perplexity_batch_size) if self.config.training.ema > 0: self.ema = models.ema.ExponentialMovingAverage( self._get_parameters(), decay=self.config.training.ema) else: self.ema = None self.lr = self.config.optim.lr self.sampling_eps = self.config.training.sampling_eps self.time_conditioning = self.config.algo.time_conditioning self.neg_infinity = -1000000.0 self.fast_forward_epochs = None self.fast_forward_batches = None self.target_tokens = None def _validate_configuration(self): assert self.config.algo.backbone in {'dit', 'hf_dit'} if self.config.algo.parameterization == 'ar': assert not self.config.algo.time_conditioning assert self.config.prior.type == 'none' if self.parameterization in {'score', 'mean'}: assert self.time_conditioning if self.T > 0: assert self.parameterization != 'score' def to(self, *args, **kwargs): self = super().to(*args, **kwargs) self.metrics.to(*args, **kwargs) return self def q_xt(self, x, alpha_t): raise NotImplementedError def _get_parameters(self): return itertools.chain(self.backbone.parameters(), self.noise.parameters()) def _eval_mode(self): if self.ema and not self.config.eval.disable_ema: print('Copying EMA parameters to model') self.ema.store(self._get_parameters()) self.ema.copy_to(self._get_parameters()) else: print('No EMA parameters') self.backbone.eval() self.noise.eval() def _train_mode(self): if self.ema: self.ema.restore(self._get_parameters()) self.backbone.train() self.noise.train() def load_state_dict(self, state_dict, strict=True): if any('_orig_mod' in k for k in state_dict.keys()): new_state_dict = {} for k, v in state_dict.items(): new_key = k.replace('._orig_mod.', '.') new_state_dict[new_key] = v state_dict = new_state_dict if hasattr(self, 'teacher_model') and self.teacher_model is not None: filtered_state_dict = {} for k, v in state_dict.items(): if not k.startswith('teacher_model.'): filtered_state_dict[k] = v state_dict = filtered_state_dict ret = super().load_state_dict(state_dict, strict=strict) if self.ema: ema_sd = getattr(self, "_pending_ema_state", None) ema_loaded = False if ema_sd is not None: try: self.ema.load_state_dict(ema_sd) current_params = list(self._get_parameters()) if len(self.ema.shadow_params) == len(current_params): shapes_match = all( s.shape == p.shape for s, p in zip(self.ema.shadow_params, current_params) ) if shapes_match: ema_loaded = True else: print("[WARNING] EMA shape mismatch - will reinitialize from loaded weights") else: print("[WARNING] EMA count mismatch - will reinitialize from loaded weights") except Exception as e: print(f"[WARNING] Failed to load EMA after weights load: {e}") if not ema_loaded: print("Initializing EMA from loaded model weights") import models.ema self.ema = models.ema.ExponentialMovingAverage( list(self._get_parameters()), decay=self.config.training.ema ) self._pending_ema_state = None return ret def on_load_checkpoint(self, checkpoint): if self.ema: self._pending_ema_state = checkpoint.get('ema', None) # Copied from: # https://github.com/Dao-AILab/flash-attention/blob/main/training/src/datamodules/language_modeling_hf.py#L41 self.fast_forward_epochs = checkpoint['loops'][ 'fit_loop']['epoch_progress']['current']['completed'] self.fast_forward_batches = checkpoint['loops'][ 'fit_loop']['epoch_loop.batch_progress'][ 'current']['completed'] def on_save_checkpoint(self, checkpoint): if self.ema: checkpoint['ema'] = self.ema.state_dict() # Copied from: # https://github.com/Dao-AILab/flash-attention/blob/main/training/src/tasks/seq.py # ['epoch_loop.batch_progress']['total']['completed'] # is 1 iteration behind, so we're using the optimizer's progress. checkpoint['loops']['fit_loop'][ 'epoch_loop.batch_progress']['total'][ 'completed'] = checkpoint['loops']['fit_loop'][ 'epoch_loop.automatic_optimization.optim_progress'][ 'optimizer']['step']['total'][ 'completed'] * self.trainer.accumulate_grad_batches checkpoint['loops']['fit_loop'][ 'epoch_loop.batch_progress']['current'][ 'completed'] = checkpoint['loops']['fit_loop'][ 'epoch_loop.automatic_optimization.optim_progress'][ 'optimizer']['step']['current'][ 'completed'] * self.trainer.accumulate_grad_batches # _batches_that_stepped tracks the number of global steps, # not the number of local steps, so we don't multiply with # self.trainer.accumulate_grad_batches here. checkpoint['loops']['fit_loop'][ 'epoch_loop.state_dict'][ '_batches_that_stepped'] = checkpoint['loops']['fit_loop'][ 'epoch_loop.automatic_optimization.optim_progress'][ 'optimizer']['step']['total']['completed'] if 'sampler' not in checkpoint.keys(): checkpoint['sampler'] = {} if hasattr(self.trainer.train_dataloader.sampler, 'state_dict'): sampler_state_dict = self.trainer.\ train_dataloader.sampler.state_dict() checkpoint['sampler'][ 'random_state'] = sampler_state_dict.get( 'random_state', None) else: checkpoint['sampler']['random_state'] = None def on_train_start(self): if self.ema: self.ema.move_shadow_params_to_device(self.device) # Adapted from: # https://github.com/Dao-AILab/flash-attention/blob/main/training/src/datamodules/language_modeling_hf.py distributed = ( self.trainer._accelerator_connector.use_distributed_sampler and self.trainer._accelerator_connector.is_distributed) if distributed: sampler_cls = dataloader.FaultTolerantDistributedSampler else: sampler_cls = dataloader.RandomFaultTolerantSampler updated_dls = [] for dl in self.trainer.fit_loop._combined_loader.flattened: if hasattr(dl.sampler, 'shuffle'): dl_sampler = sampler_cls(dl.dataset, shuffle=dl.sampler.shuffle) else: dl_sampler = sampler_cls(dl.dataset) if (distributed and self.fast_forward_epochs is not None and self.fast_forward_batches is not None): dl_sampler.load_state_dict({'epoch': self.fast_forward_epochs, 'counter': (self.fast_forward_batches * self.config.loader.batch_size)}) updated_dls.append( torch.utils.data.DataLoader( dl.dataset, batch_size=self.config.loader.batch_size, num_workers=self.config.loader.num_workers, pin_memory=self.config.loader.pin_memory, sampler=dl_sampler, shuffle=False, persistent_workers=True)) self.trainer.fit_loop._combined_loader.flattened = updated_dls def optimizer_step(self, *args, **kwargs): super().optimizer_step(*args, **kwargs) if self.ema: self.ema.update(self._get_parameters()) def _process_sigma(self, sigma): raise NotImplementedError def _process_model_output(self, model_output, xt, sigma): raise NotImplementedError def forward(self, xt, sigma, sigma_prime=None, use_jvp_attn=False): sigma = self._process_sigma(sigma) if sigma_prime is not None: sigma_prime = self._process_sigma(sigma_prime) with torch.amp.autocast(device_type=self.device.type, dtype=torch.float32): model_output = self.backbone(xt, sigma, sigma_prime, use_jvp_attn=use_jvp_attn) return self._process_model_output( model_output=model_output, xt=xt, sigma=sigma) def on_train_epoch_start(self): self.metrics.reset() assert self.metrics.train_nlls.nll.mean_value == 0 assert self.metrics.train_nlls.nll.weight == 0 def training_step(self, batch, batch_idx): current_accumulation_step = ( batch_idx % self.trainer.accumulate_grad_batches) losses = self._loss(batch['input_ids'], batch['attention_mask'], current_accumulation_step, train_mode=True, xT=None if 'xT' not in batch else batch['xT'], given_t=batch['given_t'] if 'given_t' in batch else None, not_sampling_t=self.config.training.not_sampling_t ) self.metrics.update_train(losses.nlls, losses.prior_loss, losses.num_tokens) self.log(name='trainer/loss', value=losses.loss.item(), on_step=True, on_epoch=False, sync_dist=True) return losses.loss def on_train_epoch_end(self): # NOTE: # Originally, this method re-logged validation NLL metrics at the end # of every *training* epoch by iterating over `self.metrics.valid_nlls` # and calling `.compute()` again. # # That extra logging turned out to be a non-trivial bottleneck and also # caused `val/*` metrics to appear much more frequently in WandB than # actual validation runs (which already log in `on_validation_epoch_end`). # # We therefore keep this hook but make it a no-op to avoid the # unnecessary per-train-epoch metric computation/logging. All # validation-related metrics are still logged from # `on_validation_epoch_end`, which is called whenever validation runs. return def on_validation_epoch_start(self): self.metrics.reset() self._eval_mode() assert self.metrics.valid_nlls.nll.mean_value == 0 assert self.metrics.valid_nlls.nll.weight == 0 def validation_step(self, batch, batch_idx): del batch_idx losses = self._loss(batch['input_ids'], batch['attention_mask'], xT=None if 'xT' not in batch else batch['xT'] ) self.metrics.update_valid(losses.nlls, losses.prior_loss, losses.num_tokens) return losses.loss def on_validation_epoch_end(self): for k, v in self.metrics.valid_nlls.items(): self.log(name=k, value=v.compute(), on_step=False, on_epoch=True, sync_dist=True) if ((self.config.eval.compute_perplexity_on_sanity or not self.trainer.sanity_checking) and self.config.eval.generate_samples): step_list = self.config.sampling.steps if isinstance(step_list, ListConfig): step_list = list(step_list) elif isinstance(step_list, int): step_list = [step_list] for num_steps in step_list: if hasattr(self.metrics, 'gen_ppl'): self.metrics.gen_ppl.reset() if hasattr(self.metrics, 'sample_entropy'): self.metrics.sample_entropy.reset() current_text_samples = [] for _ in range(self.config.sampling.num_sample_batches): samples = self.generate_samples( num_samples=self.config.loader.eval_batch_size, num_steps=num_steps ) self.metrics.record_entropy(samples) decoded_batch = self.tokenizer.batch_decode(samples) if len(current_text_samples) < self.config.sampling.num_sample_log: current_text_samples.extend(decoded_batch) if self.config.eval.compute_generative_perplexity: self.metrics.record_generative_perplexity( decoded_batch, self.num_tokens, self.device) if self.config.eval.compute_generative_perplexity: self.log(f'val/gen_ppl_T{num_steps}', self.metrics.gen_ppl.compute(), on_epoch=True, on_step=False, sync_dist=True) self.log(f'val/sample_entropy_T{num_steps}', self.metrics.sample_entropy.compute(), on_epoch=True, on_step=False, sync_dist=True) if self.trainer.global_rank == 0 and hasattr(self.trainer.logger, 'log_table'): log_samples = current_text_samples[:self.config.sampling.num_sample_log] self.trainer.logger.log_table( key=f'samples_T{num_steps}@global_step{self.global_step}', columns=['Generated Samples'], data=[[s] for s in log_samples] ) self._train_mode() def on_test_epoch_start(self): self._eval_mode() self.xTx0s = [] def test_step(self, batch, batch_idx): xT = batch x0 = self.generate_samples(xT.shape[0], xT=xT.detach().clone()) pair = torch.stack([xT, x0], dim=0) # 2 B N self.xTx0s.append(pair) return 0. def on_test_epoch_end(self): # gather across all GPUs self.xTx0s = torch.cat(self.xTx0s, dim=1) # 2 B N torch.distributed.barrier() # if multi gpu if torch.distributed.is_initialized(): data_xTx0s_all = [torch.empty_like(self.xTx0s) for _ in range( torch.distributed.get_world_size())] if self.trainer.global_rank == 0 else None torch.distributed.gather(self.xTx0s, data_xTx0s_all, dst=0) if self.trainer.global_rank == 0: xTx0s = torch.cat(data_xTx0s_all, dim=1).cpu()[ :, :self.config.sampling.num_reflow_samples] xTs, x0s = xTx0s[0], xTx0s[1] save_path = self.config.data.cache_dir if not os.path.exists(save_path): os.makedirs(save_path) xTs = xTs.cpu().numpy() x0s = x0s.cpu().numpy() xT_path = os.path.join(save_path, 'xT.npy') x0_path = os.path.join(save_path, 'x0.npy') np.save(xT_path, xTs) np.save(x0_path, x0s) print('xT shape:', xTs.shape) print('x0 shape:', x0s.shape) print('xT saved to:', xT_path) print('x0 saved to:', x0_path) return def configure_optimizers(self): optimizer = torch.optim.AdamW( self._get_parameters(), lr=self.config.optim.lr, betas=(self.config.optim.beta1, self.config.optim.beta2), eps=self.config.optim.eps, weight_decay=self.config.optim.weight_decay) scheduler = hydra.utils.instantiate( self.config.lr_scheduler, optimizer=optimizer) scheduler_dict = {'scheduler': scheduler, 'interval': 'step', 'monitor': 'val/loss', 'name': 'trainer/lr'} return [optimizer], [scheduler_dict] def generate_samples(self, num_samples, num_steps, eps, xT, given_t): raise NotImplementedError def restore_model_and_sample(self, num_steps, eps=1e-5): """Generate samples from the model.""" # Lightning auto-casting is not working in this method for some reason self._eval_mode() step_list = self.config.sampling.steps if isinstance(step_list, ListConfig): step_list = list(step_list) elif isinstance(step_list, int): step_list = [step_list] all_samples = [] for num_steps in step_list: batch_samples = self.generate_samples( num_samples=self.config.loader.eval_batch_size, num_steps=num_steps, eps=eps) # batch_samples is a tensor of shape (B, L) # Convert to list of tensors (one per sample in batch) for extend if isinstance(batch_samples, torch.Tensor): batch_samples = [batch_samples[i] for i in range(batch_samples.shape[0])] all_samples.extend(batch_samples) self._train_mode() return all_samples def _process_model_input(self, x0, valid_tokens): raise NotImplementedError def nll(self, input_tokens, output_tokens, current_accumulation_step=None, train_mode=False): raise NotImplementedError def _loss(self, x0, valid_tokens, current_accumulation_step=None, train_mode=False, xT=None, given_t=None, not_sampling_t=False): (input_tokens, output_tokens, valid_tokens) = self._process_model_input( x0, valid_tokens) loss = self.nll(input_tokens, output_tokens, current_accumulation_step, train_mode) assert loss.ndim == 2 if self.ignore_bos: loss[:, 1:] = loss[:, 1:] valid_tokens[:, 1:] = valid_tokens[:, 1:] nlls = (loss * valid_tokens).sum() num_tokens = valid_tokens.sum() token_nll = nlls / num_tokens return Loss(loss=token_nll, nlls=nlls, prior_loss=0.0, num_tokens=num_tokens) class Diffusion(TrainerBase): def _validate_configuration(self): super()._validate_configuration() assert self.config.sampling.noise_removal in { 'none', 'ancestral', 'greedy', 'flow'} assert self.config.training.loss_type in {'elbo', 'low_var', 'mse', 'adaptive_l2', 'flow'} if self.config.sampling.noise_removal == 'greedy': assert self.sampler != 'analytic' assert self.parameterization in {'mean', 'subs'} def _process_model_input(self, x0, valid_tokens): return x0, None, valid_tokens def _process_sigma(self, sigma): assert sigma.ndim == 2 sigma = sigma.mean(-1).squeeze() if sigma.ndim == 0: sigma = sigma.unsqueeze(0) if not self.time_conditioning: sigma = torch.zeros_like(sigma) assert sigma.ndim == 1, sigma.shape return sigma def _sample_t(self, n, accum_step): if accum_step is not None: batch_dim = n n = self.config.loader.global_batch_size _eps_t = torch.rand(n, device=self.device) if self.antithetic_sampling: offset = torch.arange(n, device=self.device) / n _eps_t = (_eps_t / n + offset) % 1 t = (1 - self.sampling_eps) * _eps_t + self.sampling_eps if accum_step is not None: t = t.chunk(self.trainer.num_nodes)[self.trainer.node_rank] t = t.chunk(self.trainer.num_devices)[self.trainer.local_rank] t = t.chunk(self.trainer.accumulate_grad_batches)[ accum_step] t = t[:batch_dim] return t def _sigma_from_alphat(self, alpha_t): return -torch.log(alpha_t) def _reconstruction_loss(self, x0): t0 = torch.zeros(1, x0.shape[0], dtype=self.dtype, device=self.device) sigma_t0 = self._sigma_from_alphat(self.noise(t0)[1]) model_output_t0 = self.forward(x0, sigma_t0) return - torch.gather(input=model_output_t0, dim=-1, index=x0[:, :, None]).squeeze(-1) def nll_per_token(self, model_output, xt, x0, alpha_t, dalpha_t, low_var): raise NotImplementedError def nll(self, x0, output_tokens, current_accumulation_step=None, train_mode=False): del output_tokens t = self._sample_t(x0.shape[0], current_accumulation_step) assert t.shape[0] == x0.shape[0] if self.T > 0: t = (t * self.T).to(torch.int) t = t / self.T # t \in {1/T, 2/T, ..., 1} t += (1 / self.T) dalpha_t, alpha_t = self.noise(t) alpha_t = alpha_t.unsqueeze(-1) assert alpha_t.ndim == 2 sigma = self._sigma_from_alphat(alpha_t) xt = self.q_xt(x0, alpha_t) log_x_theta = self.forward(xt, sigma=sigma) utils.print_nans(log_x_theta, 'model_output') return self.nll_per_token( log_x_theta=log_x_theta, xt=xt, x0=x0, alpha_t=alpha_t, dalpha_t=dalpha_t, low_var=train_mode and self.loss_type == 'low_var') def _get_score(self, **kwargs): del kwargs raise NotImplementedError def _denoiser_update(self, x, t): raise NotImplementedError def _analytic_update(self, x, t, dt): raise NotImplementedError def _ancestral_update(self, x, t, dt, p_x0, noise_removal_step): raise NotImplementedError @torch.no_grad() def generate_samples(self, num_samples, num_steps=None, eps=1e-5): if num_steps is None: num_steps = self.config.sampling.steps x = self.prior_sample(num_samples, self.num_tokens) timesteps = torch.linspace( 1, eps, num_steps + 1, device=self.device) dt = (1 - eps) / num_steps p_x0_cache = None for i in range(num_steps): t = timesteps[i] * torch.ones( x.shape[0], 1, device=self.device) if self.sampler == 'ancestral': _, x = self._ancestral_update( x=x, t=t, dt=dt, p_x0=None) elif self.sampler == 'ancestral_cache': p_x0_cache, x_next = self._ancestral_update( x=x, t=t, dt=dt, p_x0=p_x0_cache) if (not torch.allclose(x_next, x) or self.time_conditioning): # Disable caching p_x0_cache = None x = x_next else: x = self._analytic_update(x=x,t=t, dt=dt) t0 = timesteps[-1] * torch.ones(x.shape[0], 1, device=self.device) if self.config.sampling.noise_removal == 'ancestral': if self.sampler == 'analytic': x = self._denoiser_update(x=x, t=t0) else: _, x = self._ancestral_update(x=x, t=t0, dt=None, p_x0=p_x0_cache, noise_removal_step=True) elif self.config.sampling.noise_removal == 'greedy': sigma = self._sigma_from_alphat(self.noise(t0)[1]) x = self.forward(xt=x, sigma=sigma).argmax(dim=-1) return x @torch.no_grad def _semi_ar_sampler( self, n_samples, stride_length, num_strides, dt=0.001): # TODO(subham): Test this method after refactoring. ones = torch.ones(n_samples, dtype=self.dtype, device=self.device) num_steps = int(1 / dt) sampling_steps = 0 intermediate_tokens = [] target = None for _ in range(num_strides + 1): p_x0_cache = None x = self.prior_sample(n_samples, self.num_tokens) if target is not None: x[:, : -stride_length] = target for i in range(num_steps + 1): p_x0_cache, x_next = self._ancestral_update( x=x, t=(1 - i * dt) * ones, dt=dt, p_x0=p_x0_cache) if (not torch.allclose(x_next, x) or self.time_conditioning): p_x0_cache = None sampling_steps += 1 x = x_next x = self.forward(x, 0 * ones).argmax(dim=-1) intermediate_tokens.append( x[:, :stride_length].cpu().numpy()) target = x[:, stride_length:] intermediate_tokens.append(target.cpu().numpy()) intermediate_text_samples = [] sequence_lengths = (( np.concatenate(intermediate_tokens, axis=1)[:, 1:] == self.tokenizer.eos_token_id).cumsum(-1) == 0).sum(-1) for i in range(2, len(intermediate_tokens) + 1): intermediate_text_samples.append( self.tokenizer.batch_decode( np.concatenate(intermediate_tokens[:i], axis=1))) return (sampling_steps, intermediate_text_samples, sequence_lengths) def restore_model_and_semi_ar_sample( self, stride_length, num_strides, dt=0.001): """Generate samples from the model.""" # Lightning auto-casting is not working in this method for some reason # TODO(subham): Test this method after refactoring. self._eval_mode() (sampling_steps, samples, sequence_lengths) = self._semi_ar_sampler( n_samples=self.config.loader.eval_batch_size, stride_length=stride_length, num_strides=num_strides, dt=dt) self._train_mode() return sampling_steps, samples, sequence_lengths class AbsorbingState(Diffusion): def __init__(self, config, tokenizer): # NOTE: Ideally, we should do # vocab_size = len(tokenizer), so that we account # for the special tokens added in dataloader.py. # But we use tokenizer.vocab_size so as to to be # consistent with the prior checkpoints. vocab_size = tokenizer.vocab_size if (not hasattr(tokenizer, 'mask_token') or tokenizer.mask_token is None): self.mask_index = vocab_size vocab_size += 1 else: self.mask_index = tokenizer.mask_token_id self.subs_masking = config.algo.subs_masking super().__init__(config, tokenizer, vocab_size=vocab_size) self.save_hyperparameters() def _validate_configuration(self): super()._validate_configuration() if self.parameterization in {'score', 'mean'}: assert self.time_conditioning assert not (self.parameterization == 'mean' and self.T == 0) if self.T > 0: assert self.parameterization in {'mean', 'subs'} if self.subs_masking: assert self.parameterization == 'mean' def q_xt(self, x, alpha_t): """Computes the noisy sample xt. Args: x: int torch.Tensor with shape (batch_size, diffusion_model_input_length), input. alpha_t: float torch.Tensor with shape (batch_size, 1). """ move_indices = torch.rand( * x.shape, device=x.device) < 1 - alpha_t xt = torch.where(move_indices, self.mask_index, x) if self.ignore_bos: xt[:, 0] = x[:, 0] return xt def prior_sample(self, *batch_dims): return self.mask_index * torch.ones( * batch_dims, dtype=torch.int64, device=self.device) def _ancestral_update(self, x, t, dt, p_x0=None, noise_removal_step=False): _, alpha_t = self.noise(t) if noise_removal_step: alpha_s = torch.ones_like(alpha_t) else: _, alpha_s = self.noise(t - dt) assert alpha_t.ndim == 2 if p_x0 is None: p_x0 = self.forward( x, self._sigma_from_alphat(alpha_t)).exp() q_xs = p_x0 * (alpha_s - alpha_t)[:, :, None] q_xs[:, :, self.mask_index] = 1 - alpha_s _x = sample_categorical(q_xs) copy_flag = (x != self.mask_index).to(x.dtype) return p_x0, copy_flag * x + (1 - copy_flag) * _x def _staggered_score(self, score, dsigma): score = score.clone() extra_const = (1 - dsigma.exp()) * score.sum(dim=-1) score *= dsigma.exp()[:, None] score[..., self.mask_index] += extra_const return score def _analytic_update(self, x, t, dt): sigma_t = self._sigma_from_alphat(self.noise(t)[1]) sigma_s = self._sigma_from_alphat(self.noise(t - dt)[1]) dsigma = sigma_t - sigma_s score = self._get_score(x, sigma_t) if self.config.sampling.use_float64: score = score.to(torch.float64) stag_score = self._staggered_score(score, dsigma) probs = stag_score * self._transp_transition(x, dsigma) return sample_categorical(probs) def _denoiser_update(self, x, t): sigma = self._sigma_from_alphat(self.noise(t)[1]) score = self._get_score(x, sigma) if self.config.sampling.use_float64: score = score.to(torch.float64) stag_score = self._staggered_score(score, sigma) probs = stag_score * self._transp_transition(x, sigma) probs[..., self.mask_index] = 0 samples = sample_categorical(probs) return samples def _transp_transition(self, i, sigma): sigma = _unsqueeze(sigma, reference=i[..., None]) edge = torch.exp(-sigma) * F.one_hot( i, num_classes=self.vocab_size) edge += torch.where(i == self.mask_index, 1 - torch.exp(-sigma).squeeze(-1), 0)[..., None] return edge class UniformState(Diffusion): def _validate_configuration(self): super()._validate_configuration() assert self.time_conditioning assert self.parameterization == 'mean' if self.config.algo.name != 'distillation': assert self.T == 0 def q_xt(self, x, alpha_t): """Computes the noisy sample xt. Args: x: int torch.Tensor with shape (batch_size, diffusion_model_input_length), input. move_chance: float torch.Tensor with shape (batch_size, 1). """ move_indices = torch.rand( *x.shape, device=x.device) < 1 - alpha_t uniform_tensor = torch.randint( 0, self.vocab_size, x.shape, device=x.device) xt = torch.where(move_indices, uniform_tensor, x) if self.ignore_bos: xt[:, 0] = x[:, 0] return xt # (B, L) int def prior_sample(self, *batch_dims): return torch.randint( 0, self.vocab_size, batch_dims, dtype=torch.int64, device=self.device)