| """Console logger utilities. |
| |
| Copied from https://github.com/HazyResearch/transformers/blob/master/src/utils/utils.py |
| Copied from https://docs.python.org/3/howto/logging-cookbook.html#using-a-context-manager-for-selective-logging |
| """ |
|
|
| import argparse |
| import logging |
| import os |
| import sys |
| import pickle |
| import time |
|
|
| import fsspec |
| import lightning |
| import numpy as np |
| import torch |
| from scipy.integrate import quad |
| from scipy.stats import norm |
| from timm.scheduler import CosineLRScheduler |
| from math import isfinite |
| from typing import Union |
|
|
| from numpy.polynomial.hermite import hermgauss |
| from scipy.stats import norm |
| from scipy.special import log_ndtr |
| from scipy.interpolate import CubicSpline |
|
|
| def count_parameters(model): |
| return sum(p.numel() |
| for p in model.parameters() |
| if p.requires_grad) |
|
|
|
|
| def fsspec_exists(filename): |
| """Check if a file exists using fsspec.""" |
| fs, _ = fsspec.core.url_to_fs(filename) |
| return fs.exists(filename) |
|
|
|
|
| def fsspec_listdir(dirname): |
| """Listdir in manner compatible with fsspec.""" |
| fs, _ = fsspec.core.url_to_fs(dirname) |
| return fs.ls(dirname) |
|
|
|
|
| def fsspec_mkdirs(dirname, exist_ok=True): |
| """Mkdirs in manner compatible with fsspec.""" |
| fs, _ = fsspec.core.url_to_fs(dirname) |
| fs.makedirs(dirname, exist_ok=exist_ok) |
|
|
|
|
| def print_nans(tensor, name): |
| if torch.isnan(tensor).any(): |
| print(name, tensor) |
|
|
|
|
| class LRHalveScheduler: |
| def __init__(self, warmup_steps, n_halve_steps): |
| self.warmup_steps = warmup_steps |
| self.n_halve_steps = n_halve_steps |
|
|
| def __call__(self, current_step): |
| if current_step < self.warmup_steps: |
| return current_step / self.warmup_steps |
| return 0.5 ** ((current_step - self.warmup_steps) |
| // self.n_halve_steps) |
|
|
|
|
| class CosineDecayWarmupLRScheduler( |
| CosineLRScheduler, |
| torch.optim.lr_scheduler._LRScheduler): |
| """Wrap timm.scheduler.CosineLRScheduler |
| Enables calling scheduler.step() without passing in epoch. |
| Supports resuming as well. |
| Adapted from: |
| https://github.com/HazyResearch/hyena-dna/blob/main/src/utils/optim/schedulers.py |
| """ |
|
|
| def __init__(self, *args, **kwargs): |
| super().__init__(*args, **kwargs) |
| self._last_epoch = -1 |
| self.step(epoch=0) |
|
|
| def step(self, epoch=None): |
| if epoch is None: |
| self._last_epoch += 1 |
| else: |
| self._last_epoch = epoch |
| |
| |
| |
| |
| |
| |
| |
| if self.t_in_epochs: |
| super().step(epoch=self._last_epoch) |
| else: |
| super().step_update(num_updates=self._last_epoch) |
|
|
|
|
| class LoggingContext: |
| """Context manager for selective logging.""" |
|
|
| def __init__(self, logger, level=None, handler=None, close=True): |
| self.logger = logger |
| self.level = level |
| self.handler = handler |
| self.close = close |
|
|
| def __enter__(self): |
| if self.level is not None: |
| self.old_level = self.logger.level |
| self.logger.setLevel(self.level) |
| if self.handler: |
| self.logger.addHandler(self.handler) |
|
|
| def __exit__(self, et, ev, tb): |
| if self.level is not None: |
| self.logger.setLevel(self.old_level) |
| if self.handler: |
| self.logger.removeHandler(self.handler) |
| if self.handler and self.close: |
| self.handler.close() |
|
|
|
|
| class GradientInspectionCallback(lightning.Callback): |
| def __init__(self, num_grads_log): |
| self.num_grads_log = 10 |
|
|
| def on_before_optimizer_step(self, trainer, pl_module, optimizer): |
| gradients = [] |
| for name, param in pl_module.backbone.blocks.named_parameters(): |
| gradients.append(param.grad.view(-1)) |
|
|
| if gradients: |
| grads = torch.cat((gradients)) |
| if not hasattr(pl_module, 'grad_accum_buffer'): |
| pl_module.grad_step = torch.tensor( |
| 0, device=pl_module.device) |
| pl_module.grad_accum_buffer = torch.zeros( |
| self.num_grads_log, |
| grads.shape[0], |
| device=pl_module.device) |
| pl_module.grad_accum_buffer[pl_module.grad_step] = grads |
| pl_module.grad_step += 1 |
|
|
| if (hasattr(pl_module, 'grad_accum_buffer') |
| and pl_module.grad_step == self.num_grads_log): |
| grads = pl_module.grad_accum_buffer |
| grad_var = grads.std(0).mean() |
| pl_module.log(name='trainer/grad_var', |
| value=grad_var.item(), |
| on_step=True, |
| on_epoch=False, |
| sync_dist=True) |
| |
| |
| pl_module.grad_accum_buffer.zero_() |
| pl_module.grad_step = 0 |
|
|
|
|
| def get_logger(name=__name__, level=logging.INFO) -> logging.Logger: |
| """Initializes multi-GPU-friendly python logger.""" |
|
|
| logger = logging.getLogger(name) |
| logger.setLevel(level) |
|
|
| |
| |
| for level in ('debug', 'info', 'warning', 'error', |
| 'exception', 'fatal', 'critical'): |
| setattr(logger, |
| level, |
| lightning.pytorch.utilities.rank_zero_only( |
| getattr(logger, level))) |
|
|
| return logger |
|
|
|
|
| |
| def top_k_top_p_filtering( |
| logits, |
| top_k=0, |
| top_p=0.0, |
| filter_value=-float("Inf"), |
| dim=-1): |
| """Filter a distribution of logits using top-k/top-p (nucleus) filtering. |
| Adapted from https://gist.github.com/thomwolf/1a5a29f6962089e871b94cbd09daf317 |
| |
| Args: |
| logits (Tensor): Tensor of logits |
| top_k (int, optional): Number of top values to keep. |
| Deactivated if k is 0. Defaults to 0. |
| top_p (float, optional): Cumulative mass to retain. |
| Deactivated if p = 0. Defaults to 0.0. |
| filter_value (float, optional): Fill value to replace |
| the entries removed by top-k/top-p filtering. |
| Defaults to -float('Inf'). |
| dim (int, optional): Dimension of the filtering. Defaults to -1. |
| |
| Returns: |
| logits: Tensor whose axis `dim` was filtered. |
| """ |
| if dim != -1: |
| logits = torch.transpose(logits, dim, -1) |
|
|
| assert top_k < logits.size(dim) |
| if top_k > 0: |
| |
| |
| values, _ = torch.topk(logits, k=top_k, dim=-1) |
| to_remove_mask = ( |
| logits < torch.min(values, dim=-1, keepdim=True)[0] |
| ) |
| logits[to_remove_mask] = filter_value |
|
|
| if top_p > 0.0: |
| sorted_logits, sorted_indices = torch.sort( |
| logits, descending=True, dim=-1) |
| cum_probs = torch.cumsum( |
| torch.softmax(sorted_logits, dim=-1), dim=-1) |
|
|
| sorted_indices_to_remove = cum_probs > top_p |
| |
| sorted_indices_to_remove[..., 1:] = \ |
| sorted_indices_to_remove[..., :-1].clone() |
| sorted_indices_to_remove[..., 0] = 0 |
|
|
| mask_to_remove = torch.empty_like(sorted_indices_to_remove) |
| mask_to_remove.scatter_(dim=-1, |
| index=sorted_indices, |
| src=sorted_indices_to_remove) |
| logits[mask_to_remove] = filter_value |
|
|
| if dim != -1: |
| logits = torch.transpose(logits, dim, -1) |
|
|
| return logits |
|
|
|
|
| def _discrete_prob_map(gamma_t, N=10): |
| snr_sqrt = np.exp(-gamma_t / 2) |
|
|
| def value(x): |
| cdf = norm.cdf(x, scale=1) ** (N - 1) |
| pdf = norm.pdf(x, loc=snr_sqrt, scale=1) |
| return pdf * cdf |
| return value |
|
|
|
|
| def _discrete_prob_grad(gamma_t, N=10): |
| snr_sqrt = np.exp(-gamma_t / 2) |
|
|
| def value(x): |
| coef = -0.5 * snr_sqrt * (x - snr_sqrt) |
| cdf = norm.cdf(x, scale=1) ** (N - 1) |
| pdf = norm.pdf(x, loc=snr_sqrt, scale=1) |
| return coef * pdf * cdf |
| return value |
|
|
|
|
| def _cache_prob_usdm_in_partition( |
| vocab_size=30522, partition_index=0, num_partitions=1, |
| log10_num_points=5): |
| print(f'Caching partition:{partition_index} / {num_partitions}') |
| path = 'integral' |
| gamma_min = -5 |
| gamma_max = -1 |
| num_points = 10 ** log10_num_points |
| p_cache = [] |
| grad_p_cache = [] |
| start_time = time.time() |
| gammas = np.linspace(gamma_min, gamma_max, num_points) |
| n = num_points // num_partitions |
| for gamma in gammas[partition_index * n: |
| (partition_index + 1) * n]: |
| pt, _ = quad(_discrete_prob_map(gamma, vocab_size), |
| -np.inf, np.inf) |
| p_cache.append(pt) |
| grad_pt, _ = quad(_discrete_prob_grad(gamma, vocab_size), |
| -np.inf, np.inf) |
| grad_p_cache.append(grad_pt) |
| if len(p_cache) % 100 == 0: |
| print('{}% completed. Time elapsed:{:.2f} mins'.format( |
| int(100 * len(p_cache) / num_points), |
| (time.time() - start_time) / 60)) |
|
|
| filename = os.path.join( |
| path, '{}_{}_{}-{}.pkl'.format( |
| vocab_size, log10_num_points, partition_index, |
| num_partitions)) |
| with open(filename, 'wb') as f: |
| pickle.dump({ |
| 'vocab_size': vocab_size, |
| 'gamma_min': gamma_min, |
| 'gamma_max': gamma_max, |
| 'num_points': num_points, |
| 'pt': np.asarray(p_cache), |
| 'grad_pt': np.asarray(grad_p_cache)}, f) |
|
|
|
|
| def test_cache_prob_usdm_in_partition( |
| partition_index=0, num_partitions=1, vocab_size=30522, |
| log10_num_points=5): |
| path = 'integral/{}_{}_{}-{}.pkl'.format( |
| vocab_size, log10_num_points, partition_index, |
| num_partitions) |
| with open(path, 'rb') as f: |
| data = pickle.load(f) |
| num_points = data['num_points'] |
|
|
| def _get_index(x): |
| return round((num_points - 1) * (x - data['gamma_min']) / ( |
| data['gamma_max'] - data['gamma_min'])) |
|
|
| pt_errors = [] |
| grad_pt_errors = [] |
| gammas = np.linspace(data['gamma_min'], |
| data['gamma_max'], |
| num_points) |
| n = num_points // num_partitions |
| for gamma in gammas[partition_index * n: |
| (partition_index + 1) * n]: |
| pt, _ = quad( |
| _discrete_prob_map(gamma, data['vocab_size']), |
| -np.inf, np.inf) |
| grad_pt, _ = quad( |
| _discrete_prob_grad(gamma, data['vocab_size']), |
| -np.inf, np.inf) |
| idx = _get_index(gamma) |
| print(idx) |
| pt_errors.append((pt - data['pt'][idx]) ** 2) |
| grad_pt_errors.append((grad_pt - data['grad_pt'][idx]) ** 2) |
| print('Integral MSE:{} Integral Squared:{:.4f}'.format( |
| np.mean(pt_errors), np.mean(data['pt'] ** 2))) |
| print('Integral Grad MSE:{} Integral Grad Squared:{:.4f}'.format( |
| np.mean(grad_pt_errors), np.mean(data['grad_pt'] ** 2))) |
|
|
|
|
| if __name__ == "__main__": |
| |
| parser = argparse.ArgumentParser( |
| description='Caches the integral appearing in the ' |
| 'Diffusion Transformation operator.') |
| parser.add_argument( |
| '--vocab_size', |
| type=int, |
| default=50257, |
| help='Vocabulary size (default: 50257)') |
| parser.add_argument( |
| '--partition_index', |
| type=int, |
| default=0, |
| help='Helps parallelize caching') |
| parser.add_argument( |
| '--num_partitions', |
| type=int, |
| default=1, |
| help='Helps parallelize caching') |
| parser.add_argument( |
| '--log10_num_points', |
| type=int, |
| default=5, |
| help=('The integral is function that needs to be ' |
| 'evaluated for inputs with a range [-5, 1]. ' |
| 'This argument represents the logarithm base 10 ' |
| 'of number of bins of discretization.')) |
| args = parser.parse_args() |
|
|
| |
| |
| |
| _cache_prob_usdm_in_partition( |
| partition_index=args.partition_index, |
| num_partitions=args.num_partitions, |
| vocab_size=args.vocab_size, |
| log10_num_points=args.log10_num_points) |
|
|
| test_cache_prob_usdm_in_partition( |
| partition_index=args.partition_index, |
| num_partitions=args.num_partitions, |
| vocab_size=args.vocab_size, |
| log10_num_points=args.log10_num_points) |
|
|
| |
| |
| |
| def standardized_means(alpha: float, tau: float, b: float, diffusion=False): |
| """ |
| Returns (m_c, m_u, m_a, sigma), where |
| sigma = b * (1 - alpha), |
| m_c = (alpha - tau) / sigma (label / 'correct'), |
| m_u = -tau / sigma (other data), |
| m_a = 0.0 (absorbing) |
| """ |
| sigma = b * (1.0 - alpha) |
| if diffusion: |
| sigma = sigma ** 0.5 |
| if sigma <= 0.0: |
| sigma = 1e-12 |
| m_c = (alpha - tau) / sigma |
| m_u = (-tau) / sigma |
| m_a = 0.0 |
| return m_c, m_u, m_a, sigma |
|
|
| |
| |
| |
| def compute_qs_fast(alpha: float, tau: float, b: float, K: int, M: int, *, |
| n_gh: int = 100, sigma_floor: float = 1e-12, |
| diffusion=False) -> tuple[float, float, float]: |
| """ |
| Returns (q_c, q_u, q_a) using log-stabilized Gauss–Hermite and |
| only a constant number of log_ndtr calls per evaluation. |
| |
| q_c : probability the label ('correct') class wins (per label) |
| q_u : probability a particular non-label data class wins (per class) |
| q_a : probability a particular absorbing class wins (per class) |
| """ |
| |
| m_c, m_u, m_a, sigma = standardized_means(alpha, tau, b, diffusion) |
| if sigma < sigma_floor: |
| sigma = sigma_floor |
|
|
| |
| x, w = hermgauss(n_gh) |
| w = w / np.sqrt(np.pi) |
| z_nodes = np.sqrt(2.0) * x |
|
|
| |
| |
| L0 = log_ndtr(z_nodes) |
| |
| L_ca = log_ndtr(z_nodes + m_c) |
| L_ac = log_ndtr(z_nodes - m_c) |
| |
| L_ua = log_ndtr(z_nodes + m_u) |
| L_au = log_ndtr(z_nodes - m_u) |
| |
| d_cu = m_c - m_u |
| L_cu = log_ndtr(z_nodes + d_cu) |
| L_uc = log_ndtr(z_nodes - d_cu) |
|
|
| |
| |
| |
| log_prod_c = (K - 1) * L_cu + M * L_ca |
|
|
| |
| |
| if K > 1: |
| log_prod_u = L_uc + max(K - 2, 0) * L0 + M * L_ua |
| else: |
| log_prod_u = None |
|
|
| |
| |
| if M > 0: |
| log_prod_a = L_ac + (K - 1) * L_au + max(M - 1, 0) * L0 |
| else: |
| log_prod_a = None |
|
|
| |
| def weighted_exp_sum(logv): |
| |
| return float(np.sum(w * np.exp(logv))) |
|
|
| q_c = weighted_exp_sum(log_prod_c) |
| q_u = weighted_exp_sum(log_prod_u) if (log_prod_u is not None and K > 1) else 0.0 |
| q_a = weighted_exp_sum(log_prod_a) if (log_prod_a is not None and M > 0) else 0.0 |
|
|
| return q_c, q_u, q_a |
|
|
|
|
| |
| |
| |
| def compute_alpha_exact(gamma: np.ndarray, K: int, n_gh: int = 100, sigma_floor: float = 1e-12, is_diffusion=False) -> np.ndarray: |
| """ |
| Computes q_c (Alpha) from Gamma using Gauss-Hermite integration. |
| This is the ground-truth function mapping Gamma -> Alpha. |
| """ |
| gamma = np.asarray(gamma) |
|
|
| |
| sigma = 1.0 - gamma |
| if is_diffusion: |
| sigma = np.sqrt(sigma) |
| sigma = np.maximum(sigma, sigma_floor) |
| |
| m_c = gamma / sigma |
| |
| |
| x, w = hermgauss(n_gh) |
| w = w / np.sqrt(np.pi) |
| z_nodes = np.sqrt(2.0) * x |
|
|
| |
| m_c_expanded = m_c[:, None] |
| z_expanded = z_nodes[None, :] |
|
|
| |
| |
| L_cu = log_ndtr(z_expanded + m_c_expanded) |
|
|
| |
| |
| log_prod_c = (K - 1) * L_cu |
| q_c = np.sum(w * np.exp(log_prod_c), axis=-1) |
| |
| |
| alpha = K/(K-1.) * (q_c - 1./K) |
|
|
| alpha += (gamma-1) * 1e-10 |
|
|
| alpha = np.clip(alpha, 0.0, 1.0) |
|
|
| return alpha |
|
|
| def compute_alpha_exact_torch(gamma, K: int, x_np, w_np, sigma_floor: float = 1e-12, is_diffusion=False, device=None) -> torch.Tensor: |
| """ |
| Computes q_c (Alpha) from Gamma using Gauss-Hermite integration (PyTorch version). |
| """ |
| |
| dtype = gamma.dtype |
| device = gamma.device |
|
|
| sigma = 1.0 - gamma |
| if is_diffusion: |
| sigma = torch.sqrt(sigma) |
| |
| sigma = torch.maximum(sigma, torch.tensor(sigma_floor, device=device, dtype=dtype)) |
| |
| m_c = gamma / sigma |
| |
| x = torch.tensor(x_np, dtype=dtype, device=device) |
| w = torch.tensor(w_np, dtype=dtype, device=device) |
| |
| w = w / np.sqrt(np.pi) |
| z_nodes = torch.sqrt(torch.tensor(2.0, dtype=dtype, device=device)) * x |
|
|
| m_c_expanded = m_c.unsqueeze(-1) |
| z_expanded = z_nodes.unsqueeze(0) |
|
|
| L_cu = torch.special.log_ndtr(z_expanded + m_c_expanded) |
|
|
| log_prod_c = (K - 1) * L_cu |
| |
| q_c = torch.sum(w.unsqueeze(0) * torch.exp(log_prod_c), dim=-1) |
| |
| alpha = (K / (K - 1.0)) * (q_c - (1.0 / K)) |
|
|
| alpha = alpha + (gamma - 1) * 1e-10 |
|
|
| alpha = torch.clamp(alpha, 0.0, 1.0) |
|
|
| return alpha |
|
|
| |
| |
| |
|
|
| def build_luts(K: int, n_points: int = 10000, is_diffusion=False) -> tuple[CubicSpline, CubicSpline]: |
| """ |
| Builds two lookup tables (Splines): |
| 1. Alpha -> Gamma (Forward) |
| 2. Gamma -> Alpha (Inverse) |
| |
| Reverted to Linear (Uniform) spacing. |
| Chebyshev nodes concentrate points at 0 and 1, but for large K, the curve |
| is often sigmoid-like (flat at ends, steep in middle). |
| Uniform spacing captures the transition region better. |
| """ |
| |
| |
| gamma_vals = np.linspace(0.0, 1.0, n_points) |
| |
| |
| alpha_vals = compute_alpha_exact(gamma_vals, K=K, is_diffusion=is_diffusion) |
| |
| |
| |
| lut_g2a = CubicSpline(gamma_vals, alpha_vals) |
| |
| |
| |
| |
| |
| sorted_indices = np.argsort(alpha_vals) |
| gamma_sorted = gamma_vals[sorted_indices] |
| alpha_sorted = alpha_vals[sorted_indices] |
| |
| |
| |
| unique_alpha, unique_indices = np.unique(alpha_sorted, return_index=True) |
| unique_gamma = gamma_sorted[unique_indices] |
|
|
| |
| lut_a2g = CubicSpline(unique_alpha, unique_gamma) |
| |
| return lut_a2g, lut_g2a |
|
|
| |
| |
| |
|
|
| |
|
|
| def alpha_to_gamma(alpha: Union[np.ndarray, torch.tensor], lut: CubicSpline) -> Union[np.ndarray, torch.tensor]: |
| """ |
| Maps Alpha -> Gamma using the LUT. |
| """ |
| if isinstance(alpha, torch.Tensor): |
| dtype = alpha.dtype |
| gamma = np.clip(lut(alpha.cpu().numpy()), 0.0, 1.0) |
| return torch.from_numpy(gamma).to(alpha.device, dtype=dtype) |
| else: |
| return np.clip(lut(alpha), 0.0, 1.0) |
|
|
| def gamma_to_alpha(gamma: Union[np.ndarray, torch.tensor], lut: CubicSpline) -> Union[np.ndarray, torch.tensor]: |
| """ |
| Maps Gamma -> Alpha using the LUT. |
| """ |
| |
| if isinstance(gamma, torch.Tensor): |
| dtype = gamma.dtype |
| alpha = np.clip(lut(gamma.cpu().numpy()), 0.0, 1.0) |
| return torch.from_numpy(alpha).to(gamma.device, dtype=dtype) |
| else: |
| return np.clip(lut(gamma), 0.0, 1.0) |
| |
| |
| |