| """Noise schedule functions for MDLM diffusion. |
| |
| All functions operate on PyTorch tensors and are pure (no global state). |
| |
| Convention: alpha(t) is the fraction of tokens that remain *unmasked*. |
| - alpha(0) = 1.0 (fully clean) |
| - alpha(1) = 0.0 (fully masked) |
| """ |
|
|
| from __future__ import annotations |
|
|
| import math |
| from collections.abc import Callable |
|
|
| import torch |
| from torch import Tensor |
|
|
|
|
| def linear_schedule(t: Tensor) -> Tensor: |
| """Linear noise schedule: alpha(t) = 1 - t. |
| |
| Args: |
| t: Diffusion time in [0, 1]. Any shape. |
| |
| Returns: |
| Retention probability alpha_t, same shape as *t*. |
| """ |
| return 1.0 - t |
|
|
|
|
| def cosine_schedule(t: Tensor) -> Tensor: |
| """Cosine noise schedule: alpha(t) = cos(pi/2 * t). |
| |
| MDLM Appendix E.1 eq (92) ("Cosine"); the same function the craftax |
| repo names "cosine". |
| |
| Args: |
| t: Diffusion time in [0, 1]. Any shape. |
| |
| Returns: |
| Retention probability alpha_t, same shape as *t*. |
| """ |
| return torch.cos(t * (math.pi / 2.0)) |
|
|
|
|
| def cosine_sq_schedule(t: Tensor) -> Tensor: |
| """Cosine-squared noise schedule: alpha(t) = cos(pi/2 * t)^2. |
| |
| MDLM Appendix E.1 eq (91) ("Cosine Squared", after Nichol & Dhariwal). |
| Previously registered under the name "cosine" in this repo; renamed so |
| the label "cosine" denotes the same function in both repos. |
| |
| Args: |
| t: Diffusion time in [0, 1]. Any shape. |
| |
| Returns: |
| Retention probability alpha_t, same shape as *t*. |
| """ |
| return torch.cos(t * (math.pi / 2.0)) ** 2 |
|
|
|
|
| def linear_schedule_deriv(t: Tensor) -> Tensor: |
| """Analytic d(alpha)/dt for the linear schedule.""" |
| return torch.full_like(t, -1.0) |
|
|
|
|
| def cosine_schedule_deriv(t: Tensor) -> Tensor: |
| """Analytic d(alpha)/dt for the cosine schedule.""" |
| return -(math.pi / 2.0) * torch.sin(t * (math.pi / 2.0)) |
|
|
|
|
| def cosine_sq_schedule_deriv(t: Tensor) -> Tensor: |
| """Analytic d(alpha)/dt for the cosine-squared schedule.""" |
| return -(math.pi / 2.0) * torch.sin(t * math.pi) |
|
|
|
|
| _SCHEDULE_MAP: dict[str, Callable[[Tensor], Tensor]] = { |
| "linear": linear_schedule, |
| "cosine": cosine_schedule, |
| "cosine_sq": cosine_sq_schedule, |
| } |
|
|
| _DERIV_BY_FN: dict[Callable[[Tensor], Tensor], Callable[[Tensor], Tensor]] = { |
| linear_schedule: linear_schedule_deriv, |
| cosine_schedule: cosine_schedule_deriv, |
| cosine_sq_schedule: cosine_sq_schedule_deriv, |
| } |
|
|
|
|
| def get_schedule_deriv_for( |
| schedule_fn: Callable[[Tensor], Tensor], |
| ) -> Callable[[Tensor], Tensor]: |
| """Analytic derivative for a registered schedule function. |
| |
| The NELBO weight uses the analytic d(alpha)/dt as stated in |
| MDLM eq (10) / Shi eq (4). |
| |
| Raises: |
| KeyError: If *schedule_fn* is not a registered schedule. |
| """ |
| if schedule_fn not in _DERIV_BY_FN: |
| raise KeyError( |
| "No analytic derivative registered for " |
| f"{getattr(schedule_fn, '__name__', schedule_fn)!r}" |
| ) |
| return _DERIV_BY_FN[schedule_fn] |
|
|
|
|
| def get_schedule(name: str) -> Callable[[Tensor], Tensor]: |
| """Look up a noise schedule by name. |
| |
| Args: |
| name: One of ``"linear"``, ``"cosine"``, ``"cosine_sq"``. |
| |
| Returns: |
| The schedule function ``alpha(t)``. |
| |
| Raises: |
| KeyError: If *name* is not registered. |
| """ |
| if name not in _SCHEDULE_MAP: |
| raise KeyError( |
| f"Unknown schedule '{name}'. Available: {list(_SCHEDULE_MAP.keys())}" |
| ) |
| return _SCHEDULE_MAP[name] |
|
|