goldenfox's picture
Marimo Diffusion 0.6B: checkpoint, sampler, OpenAI server, ledger-needle bench
685e018 verified
Raw
History Blame Contribute Delete
12.5 kB
"""Absorbing-mask forward corruption, objective, and reverse samplers."""
from __future__ import annotations
from dataclasses import dataclass
from collections.abc import Iterator
from typing import Callable, Literal, Protocol
import torch
import torch.nn.functional as F
from torch import Tensor
class Denoiser(Protocol):
config: object
def __call__(
self,
input_ids: Tensor,
attention_mask: Tensor | None = None,
output_positions: Tensor | None = None,
attn_mask: Tensor | None = None,
) -> Tensor: ...
@dataclass(frozen=True)
class CorruptionBatch:
noisy_tokens: Tensor
mask: Tensor
mask_probability: Tensor
valid_mask: Tensor
@dataclass(frozen=True)
class LossOutput:
loss: Tensor
masked_accuracy: Tensor
masked_tokens: int
@dataclass(frozen=True)
class UnmaskStep:
"""One observable state of the reverse diffusion process."""
step: int
total_steps: int
tokens: Tensor
masked_remaining: int
def sample_mask_probabilities(
batch_size: int,
*,
device: torch.device | str,
eps: float = 1e-3,
low_discrepancy: bool = True,
generator: torch.Generator | None = None,
) -> Tensor:
"""Sample linear noise levels in ``[eps, 1]``.
A random cyclic shift of an evenly spaced grid preserves uniform marginals
while covering the complete noise range in every reasonably sized batch.
"""
if batch_size <= 0:
raise ValueError("batch_size must be positive")
if not 0.0 < eps < 1.0:
raise ValueError("eps must be in (0, 1)")
if low_discrepancy:
offset = torch.rand((), device=device, generator=generator)
unit = (offset + torch.arange(batch_size, device=device) / batch_size) % 1.0
else:
unit = torch.rand(batch_size, device=device, generator=generator)
return eps + (1.0 - eps) * unit
def corrupt_tokens(
clean_tokens: Tensor,
mask_token_id: int,
*,
valid_mask: Tensor | None = None,
mask_probability: Tensor | None = None,
eps: float = 1e-3,
low_discrepancy: bool = True,
generator: torch.Generator | None = None,
) -> CorruptionBatch:
"""Apply the absorbing forward process at one random time per sequence."""
if clean_tokens.ndim != 2:
raise ValueError("clean_tokens must have shape [batch, sequence]")
batch_size, _ = clean_tokens.shape
if valid_mask is None:
valid_mask = torch.ones_like(clean_tokens, dtype=torch.bool)
elif valid_mask.shape != clean_tokens.shape:
raise ValueError("valid_mask must match clean_tokens")
else:
valid_mask = valid_mask.bool()
if mask_probability is None:
mask_probability = sample_mask_probabilities(
batch_size,
device=clean_tokens.device,
eps=eps,
low_discrepancy=low_discrepancy,
generator=generator,
)
else:
mask_probability = torch.as_tensor(
mask_probability, device=clean_tokens.device, dtype=torch.float32
)
if mask_probability.ndim == 0:
mask_probability = mask_probability.repeat(batch_size)
if mask_probability.shape != (batch_size,):
raise ValueError("mask_probability must be scalar or have shape [batch]")
if bool(((mask_probability <= 0) | (mask_probability > 1)).any()):
raise ValueError("mask probabilities must be in (0, 1]")
random_values = torch.rand(clean_tokens.shape, device=clean_tokens.device, generator=generator)
mask = (random_values < mask_probability[:, None]) & valid_mask
noisy_tokens = torch.where(mask, mask_token_id, clean_tokens)
return CorruptionBatch(noisy_tokens, mask, mask_probability, valid_mask)
def diffusion_cross_entropy(
logits: Tensor,
clean_tokens: Tensor,
corruption: CorruptionBatch,
) -> LossOutput:
"""Compute the continuous-time masked-diffusion likelihood bound.
``logits`` may contain all positions as ``[B, L, V]`` or only the masked
positions as ``[N_masked, V]``. The latter is substantially more memory
efficient for small models with non-trivial vocabularies.
"""
if clean_tokens.shape != corruption.noisy_tokens.shape:
raise ValueError("clean_tokens must match the corruption batch")
targets = clean_tokens[corruption.mask]
if logits.ndim == 3:
if logits.shape[:2] != clean_tokens.shape:
raise ValueError("full logits must have shape [batch, sequence, vocab]")
selected_logits = logits[corruption.mask]
elif logits.ndim == 2:
selected_logits = logits
else:
raise ValueError("logits must have shape [B, L, V] or [N_masked, V]")
if selected_logits.shape[0] != targets.numel():
raise ValueError("selected logits count does not match the number of masked tokens")
masked_tokens = int(targets.numel())
if masked_tokens == 0:
zero = logits.sum() * 0.0
return LossOutput(zero, zero.detach(), 0)
per_token = F.cross_entropy(selected_logits.float(), targets, reduction="none")
probabilities = corruption.mask_probability[:, None].expand_as(clean_tokens)
weights = probabilities[corruption.mask].reciprocal()
normalizer = corruption.valid_mask.sum().clamp_min(1)
loss = (per_token * weights).sum() / normalizer
accuracy = (selected_logits.argmax(dim=-1) == targets).float().mean()
return LossOutput(loss, accuracy, masked_tokens)
def _sample_categorical(
logits: Tensor,
temperature: float,
generator: torch.Generator | None,
) -> tuple[Tensor, Tensor]:
"""Sample with fp64 Gumbel noise and return token ids plus model confidence."""
if temperature < 0:
raise ValueError("temperature must be non-negative")
log_probs = F.log_softmax(logits.float(), dim=-1)
if temperature == 0:
tokens = logits.argmax(dim=-1)
else:
# MPS has no float64 kernels. Preserve fp64 categorical sampling by
# moving only the sampling calculation to CPU on Apple Silicon.
sampling_device = torch.device("cpu") if logits.device.type == "mps" else logits.device
if logits.device.type == "mps":
logits64 = logits.float().cpu().double() / temperature
else:
logits64 = logits.double() / temperature
sampling_generator = generator
if generator is not None and generator.device != sampling_device:
sampling_generator = None
uniform = torch.rand(
logits64.shape,
device=sampling_device,
dtype=torch.float64,
generator=sampling_generator,
).clamp_(1e-12, 1.0 - 1e-12)
gumbel = -torch.log(-torch.log(uniform))
tokens = (logits64 + gumbel).argmax(dim=-1).to(logits.device)
confidence = log_probs.gather(-1, tokens[:, None]).squeeze(-1).exp()
return tokens, confidence
def iterative_unmask_steps(
model: Denoiser,
input_ids: Tensor,
mask_token_id: int,
*,
steps: int = 64,
temperature: float = 1.0,
strategy: Literal["ancestral", "confidence", "left_to_right"] = "ancestral",
blocked_token_ids: tuple[int, ...] = (),
attn_mask: Tensor | None = None,
generator: torch.Generator | None = None,
logits_fn: Callable[[Tensor, Tensor], Tensor] | None = None,
) -> Iterator[UnmaskStep]:
"""Yield each state while filling masks and clamping visible prompt tokens.
``ancestral`` implements the absorbing reverse transition from mask rate
``t`` to ``s``. ``confidence`` reveals an equal-sized highest-confidence
group on each pass; it is faster-looking and often useful, but is a heuristic.
``left_to_right`` reveals equal-sized position-ordered groups, which keeps
arithmetic left operands visible before their results are committed.
``logits_fn(tokens, masked)`` overrides how predictions are obtained, so a caller
holding a key/value cache can score only the masked window instead of the whole
sequence. The revealing schedule is unchanged either way.
"""
if input_ids.ndim != 2:
raise ValueError("input_ids must have shape [batch, sequence]")
if steps <= 0:
raise ValueError("steps must be positive")
if strategy not in {"ancestral", "confidence", "left_to_right"}:
raise ValueError("strategy must be ancestral, confidence, or left_to_right")
tokens = input_ids.clone()
batch_size, _ = tokens.shape
yield UnmaskStep(0, steps, tokens.detach(), int(tokens.eq(mask_token_id).sum()))
for step in range(steps):
masked = tokens.eq(mask_token_id)
if not bool(masked.any()):
break
# This function is itself a generator, so a decorator would leave the
# inference context before iteration begins. Scope it around each pass.
with torch.inference_mode():
if logits_fn is not None:
logits = logits_fn(tokens, masked)
else:
# Kept as a conditional kwarg so mask-free denoiser doubles stay valid.
extra = {} if attn_mask is None else {"attn_mask": attn_mask}
logits = model(tokens, output_positions=masked, **extra)
if blocked_token_ids:
logits = logits.clone()
for token_id in blocked_token_ids:
logits[:, token_id] = torch.finfo(logits.dtype).min
predictions, confidence = _sample_categorical(logits, temperature, generator)
proposed = tokens.clone()
proposed[masked] = predictions
reveal = torch.zeros_like(masked)
steps_left = steps - step
if strategy == "ancestral":
# Linear t grid: P(unmask from t to s | still masked) = 1 - s/t.
reveal_probability = 1.0 / steps_left
reveal = (
torch.rand(tokens.shape, device=tokens.device, generator=generator)
< reveal_probability
) & masked
elif strategy == "left_to_right":
for row in range(batch_size):
masked_positions = masked[row].nonzero(as_tuple=True)[0]
remaining = int(masked_positions.numel())
count = (remaining + steps_left - 1) // steps_left
if count:
reveal[row, masked_positions[:count]] = True
else:
confidence_grid = torch.full(
tokens.shape,
-torch.inf,
device=tokens.device,
dtype=confidence.dtype,
)
confidence_grid[masked] = confidence
for row in range(batch_size):
remaining = int(masked[row].sum())
count = (remaining + steps_left - 1) // steps_left
if count:
positions = confidence_grid[row].topk(count).indices
reveal[row, positions] = True
tokens = torch.where(reveal, proposed, tokens)
yield UnmaskStep(
step + 1,
steps,
tokens.detach(),
int(tokens.eq(mask_token_id).sum()),
)
if bool(tokens.eq(mask_token_id).any()):
raise RuntimeError(
"sampler finished with masked positions; this indicates an internal error"
)
@torch.no_grad()
def iterative_unmask(
model: Denoiser,
input_ids: Tensor,
mask_token_id: int,
*,
steps: int = 64,
temperature: float = 1.0,
strategy: Literal["ancestral", "confidence"] = "ancestral",
blocked_token_ids: tuple[int, ...] = (),
attn_mask: Tensor | None = None,
generator: torch.Generator | None = None,
logits_fn: Callable[[Tensor, Tensor], Tensor] | None = None,
) -> Tensor:
"""Return the final state from :func:`iterative_unmask_steps`."""
final_state: UnmaskStep | None = None
for state in iterative_unmask_steps(
model,
input_ids,
mask_token_id,
steps=steps,
temperature=temperature,
strategy=strategy,
blocked_token_ids=blocked_token_ids,
attn_mask=attn_mask,
generator=generator,
logits_fn=logits_fn,
):
final_state = state
if final_state is None: # Defensive: the iterator always yields its initial state.
raise RuntimeError("sampler produced no state")
return final_state.tokens