fanout-diffusion / src /r4t /diffusion.py
dejanseo's picture
Release sub-millisecond 1-bit consistency retriever: ONNX, INT4 QAT checkpoint, and native C++ engine
e6824de verified
Raw History Blame Contribute Delete
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)
@torch.no_grad()
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
}
@torch.no_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)
@torch.no_grad()
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()}