Download src/r4t/diffusion.py from dejanseo/fanout-diffusion: direct link, hf CLI and curl.
- Browser
- Download file 9.9 kB
-
https://huggingface.co/dejanseo/fanout-diffusion/resolve/main/src/r4t/diffusion.py
- Command line
-
hf download hf://dejanseo/fanout-diffusion/src/r4t/diffusion.py
-
curl -L -o diffusion.py https://huggingface.co/dejanseo/fanout-diffusion/resolve/main/src/r4t/diffusion.py
9.9 kB
| from __future__ import annotations | |
| import math | |
| import torch | |
| from torch import Tensor, nn | |
| from .config import DiffusionConfig | |
| def _broadcast(values: Tensor, target: Tensor) -> Tensor: | |
| return values.reshape(values.shape[0], *([1] * (target.ndim - 1))) | |
| def sinusoidal_embedding(values: Tensor, dimension: int) -> Tensor: | |
| half = dimension // 2 | |
| if half == 0: | |
| return values[:, None] | |
| frequencies = torch.exp( | |
| -math.log(10_000) * torch.arange(half, device=values.device) / max(half - 1, 1) | |
| ) | |
| angles = values[:, None] * frequencies[None] | |
| embedding = torch.cat((angles.sin(), angles.cos()), dim=-1) | |
| if dimension % 2: | |
| embedding = torch.nn.functional.pad(embedding, (0, 1)) | |
| return embedding | |
| class CoherentTransformer(nn.Module): | |
| """Continuous-input transformer with query cross-attention conditioning.""" | |
| def __init__(self, config: DiffusionConfig) -> None: | |
| super().__init__() | |
| if config.hidden_dim % config.heads: | |
| raise ValueError("hidden_dim must be divisible by heads") | |
| self.config = config | |
| self.input_projection = nn.Linear(config.embedding_dim, config.hidden_dim) | |
| self.output_projection = nn.Linear(config.hidden_dim, config.embedding_dim) | |
| self.query_projection = nn.Linear(config.embedding_dim, config.hidden_dim) | |
| self.position = nn.Parameter(torch.zeros(1, config.sequence_length, config.hidden_dim)) | |
| self.time_mlp = nn.Sequential( | |
| nn.Linear(config.hidden_dim, config.hidden_dim), | |
| nn.SiLU(), | |
| nn.Linear(config.hidden_dim, config.hidden_dim), | |
| ) | |
| layer = nn.TransformerDecoderLayer( | |
| d_model=config.hidden_dim, | |
| nhead=config.heads, | |
| dim_feedforward=config.mlp_dim, | |
| dropout=config.dropout, | |
| activation="gelu", | |
| batch_first=True, | |
| norm_first=True, | |
| ) | |
| # PyTorch exposes one dropout value for both attention and residual paths. | |
| # Keep the separately reported paper value in config for provenance. | |
| self.decoder = nn.TransformerDecoder(layer, num_layers=config.layers) | |
| self.final_norm = nn.LayerNorm(config.hidden_dim) | |
| nn.init.normal_(self.position, std=0.02) | |
| def forward( | |
| self, | |
| noisy_targets: Tensor, | |
| noise_embedding: Tensor, | |
| query_embedding: Tensor, | |
| condition_drop_mask: Tensor | None = None, | |
| ) -> Tensor: | |
| batch, length, _ = noisy_targets.shape | |
| if length > self.config.sequence_length: | |
| raise ValueError("input sequence exceeds configured sequence_length") | |
| if condition_drop_mask is not None: | |
| query_embedding = query_embedding.masked_fill(condition_drop_mask[:, None], 0.0) | |
| tokens = self.input_projection(noisy_targets) + self.position[:, :length] | |
| tokens = tokens + self.time_mlp(noise_embedding)[:, None] | |
| memory = self.query_projection(query_embedding)[:, None] | |
| decoded = self.decoder(tokens, memory) | |
| return self.output_projection(self.final_norm(decoded)) | |
| class EDMDenoiser(nn.Module): | |
| """EDM-preconditioned denoiser predicting clean set-valued targets.""" | |
| def __init__(self, config: DiffusionConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.backbone = CoherentTransformer(config) | |
| def forward( | |
| self, | |
| noisy_targets: Tensor, | |
| sigma: Tensor, | |
| query_embedding: Tensor, | |
| condition_drop_mask: Tensor | None = None, | |
| ) -> Tensor: | |
| sigma_data = self.config.sigma_data | |
| sigma_sq = sigma.square() | |
| denominator = sigma_sq + sigma_data**2 | |
| c_skip = sigma_data**2 / denominator | |
| c_out = sigma * sigma_data / denominator.sqrt() | |
| c_in = denominator.rsqrt() | |
| c_noise = sigma.clamp_min(1e-12).log() / 4.0 | |
| network_output = self.backbone( | |
| _broadcast(c_in, noisy_targets) * noisy_targets, | |
| sinusoidal_embedding(c_noise, self.config.hidden_dim), | |
| query_embedding, | |
| condition_drop_mask=condition_drop_mask, | |
| ) | |
| return ( | |
| _broadcast(c_skip, noisy_targets) * noisy_targets | |
| + _broadcast(c_out, noisy_targets) * network_output | |
| ) | |
| def sample_tangent_sigmas( | |
| batch_size: int, | |
| sigma_min: float, | |
| sigma_max: float, | |
| device: torch.device, | |
| generator: torch.Generator | None = None, | |
| ) -> Tensor: | |
| theta_min = math.atan(sigma_min) | |
| theta_max = math.atan(sigma_max) | |
| uniform = torch.rand(batch_size, device=device, generator=generator) | |
| return torch.tan(theta_min + uniform * (theta_max - theta_min)) | |
| def sample_lognormal_sigmas( | |
| batch_size: int, | |
| sigma_data: float = 0.0361, | |
| p_mean: float | None = None, | |
| p_std: float = 1.2, | |
| device: torch.device = torch.device("cpu"), | |
| generator: torch.Generator | None = None, | |
| ) -> Tensor: | |
| """Sample noise levels sigma from log-normal distribution (Karras et al. 2022 EDM).""" | |
| mean = math.log(sigma_data) if p_mean is None else p_mean | |
| normal = torch.randn(batch_size, device=device, generator=generator) | |
| return (mean + normal * p_std).exp() | |
| def diffusion_loss( | |
| model: EDMDenoiser, | |
| targets: Tensor, | |
| query_embeddings: Tensor, | |
| generator: torch.Generator | None = None, | |
| ) -> Tensor: | |
| config = model.config | |
| sigma = sample_lognormal_sigmas( | |
| len(targets), | |
| sigma_data=config.sigma_data, | |
| p_std=1.2, | |
| device=targets.device, | |
| generator=generator, | |
| ) | |
| noise = torch.randn(targets.shape, device=targets.device, dtype=targets.dtype, generator=generator) | |
| noisy = targets + _broadcast(sigma, targets) * noise | |
| drop_mask = torch.rand(len(targets), device=targets.device, generator=generator) < config.condition_drop_probability | |
| prediction = model(noisy, sigma, query_embeddings, condition_drop_mask=drop_mask).float() | |
| targets_f = targets.float() | |
| sigma_f = sigma.float() | |
| weight = ((sigma_f.square() + config.sigma_data**2) / ( | |
| sigma_f * config.sigma_data | |
| ).square().clamp_min(1e-12)).clamp_max(25_000.0) | |
| per_example = (prediction - targets_f).square().flatten(1).mean(dim=-1) | |
| return (weight * per_example).mean() | |
| def _guided_denoise( | |
| model: EDMDenoiser, | |
| values: Tensor, | |
| sigma: Tensor, | |
| query_embeddings: Tensor, | |
| cfg_strength: float, | |
| ) -> Tensor: | |
| conditional = model(values, sigma, query_embeddings) | |
| if cfg_strength == 0: | |
| return conditional | |
| unconditional = model( | |
| values, | |
| sigma, | |
| query_embeddings, | |
| condition_drop_mask=torch.ones(len(values), dtype=torch.bool, device=values.device), | |
| ) | |
| return conditional + cfg_strength * (conditional - unconditional) | |
| def sample_edm( | |
| model: EDMDenoiser, | |
| query_embeddings: Tensor, | |
| sampling_steps: int | None = None, | |
| cfg_strength: float | None = None, | |
| generator: torch.Generator | None = None, | |
| rho: float = 7.0, | |
| ) -> Tensor: | |
| """Deterministic Heun solve of the EDM probability-flow ODE using Karras polynomial schedule.""" | |
| config = model.config | |
| steps = sampling_steps or config.sampling_steps | |
| guidance = config.cfg_strength if cfg_strength is None else cfg_strength | |
| # Karras polynomial schedule (Karras et al. 2022) | |
| ramp = torch.linspace(0, 1, steps, device=query_embeddings.device) | |
| min_inv_rho = config.sigma_min ** (1 / rho) | |
| max_inv_rho = config.sigma_max ** (1 / rho) | |
| sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho | |
| schedule = torch.cat([sigmas, sigmas.new_zeros(1)]) | |
| shape = ( | |
| len(query_embeddings), | |
| config.sequence_length, | |
| config.embedding_dim, | |
| ) | |
| values = torch.randn( | |
| shape, | |
| device=query_embeddings.device, | |
| dtype=query_embeddings.dtype, | |
| generator=generator, | |
| ) * config.sigma_max | |
| for index in range(len(schedule) - 1): | |
| current = schedule[index] | |
| following = schedule[index + 1] | |
| sigma = current.expand(len(values)) | |
| denoised = _guided_denoise(model, values, sigma, query_embeddings, guidance) | |
| derivative = (values - denoised) / current.clamp_min(1e-12) | |
| proposal = values + (following - current) * derivative | |
| if following > 0: | |
| next_sigma = following.expand(len(values)) | |
| next_denoised = _guided_denoise( | |
| model, proposal, next_sigma, query_embeddings, guidance | |
| ) | |
| next_derivative = (proposal - next_denoised) / following | |
| values = values + (following - current) * 0.5 * ( | |
| derivative + next_derivative | |
| ) | |
| else: | |
| values = proposal | |
| return values | |
| class ExponentialMovingAverage: | |
| def __init__(self, model: nn.Module, decay: float) -> None: | |
| self.decay = decay | |
| self.shadow = { | |
| name: parameter.detach().clone() | |
| for name, parameter in model.named_parameters() | |
| if parameter.requires_grad | |
| } | |
| def update(self, model: nn.Module) -> None: | |
| for name, parameter in model.named_parameters(): | |
| if name in self.shadow: | |
| self.shadow[name].lerp_(parameter.detach(), 1.0 - self.decay) | |
| def copy_to(self, model: nn.Module) -> None: | |
| for name, parameter in model.named_parameters(): | |
| if name in self.shadow: | |
| parameter.copy_(self.shadow[name]) | |
| def state_dict(self) -> dict[str, object]: | |
| return {"decay": self.decay, "shadow": self.shadow} | |
| def load_state_dict(self, state_dict: dict[str, object]) -> None: | |
| self.decay = float(state_dict.get("decay", self.decay)) | |
| shadow = state_dict.get("shadow", {}) | |
| self.shadow = {name: tensor.clone() for name, tensor in shadow.items()} | |