DDPM noise schedule + per-step input plumbing.
A DDPM trainer:
- Precomputes the cumulative-α table once via
cosineSchedule, stored as a[T]f32 ByteArray. - Per training step, calls
stepInputsto:- sample a timestep
t_b ∈ [0, T)per image - sample Gaussian noise
ε ~ N(0, I) - compute
x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · εand returns(x_t, ε, t)as three ByteArrays.
- sample a timestep
- Trains the model to predict
εfromx_t(per-pixel MSE).
The loss + backward live in the useDdpm codegen branch of
MlirCodegen.generateTrainStep.
The schedule is the cosine variant from Nichol & Dhariwal 2021, which trains more stably than Ho et al.'s original linear schedule.
Build the cosine noise schedule (Nichol & Dhariwal 2021) as a
[T] f32 LE ByteArray of ᾱ_t values. With s = 0.008 the
schedule keeps log-SNR roughly linear in t.
Sample n f32 from N(0, 1) via Box–Muller. Used for sampling-time
noise (training noise comes from stepInputs).
Prepend a timestep-encoding channel to each image. Output is
[B, C+1, H, W] (flat) where channels 0..C-1 are the input image
and channel C is filled with t[i] / T_max. Lets the UNet
condition on the diffusion timestep without a new codegen
primitive — it just sees a (C+1)-channel input. t is a [B]
int32 array (one timestep per image).
Scalar variant of prependTChannel for the sampler — broadcasts
a single timestep to all images in the batch.
Sinusoidal time embedding: prepend 2 * nFreq channels of
[sin(t · ω_k), cos(t · ω_k)] at log-spaced frequencies
(Vaswani / NeRF convention). Replaces the cruder single-channel
t/T_max tile with multi-frequency information.
Output: [B, C + 2·nFreq, H, W] flat.
Scalar variant of prependSinCosT for the sampler.
Per training step: sample t_b ∈ [0, T) per image, sample ε,
compute x_t. Returns (x_t, ε, t) where:
x_tis[B, npixels]f32εis[B, npixels]f32 (the loss target — what the model should learn to predict)tis[B]int32 LE (the per-image timesteps; useful for future time-conditioning, currently unused by codegen).
The continuous-time (VP-SDE) view of the same schedule #
`cosineSchedule` tabulates ᾱ at the integers `t = 0 … T-1`. Score-SDE
(Song et al. 2021) treats the same schedule as a function of continuous
`t ∈ [0,1]`, which is what lets an ODE or SDE solver choose its own steps.
⭐ **No retraining is needed to get there.** Under the VP SDE the score is
`∇ log p_t(x) = -ε̂(x,t)/σ_t`, so an ε-predicting network *is* a score model
up to that factor. Everything here is sampler-side arithmetic, shared by the
2-D and MNIST drivers so the two cannot drift.
ᾱ(t) = cos²θ(t) / cos²θ(0) — the closed form of the tabulated schedule.
Equations
- Ddpm.abarC t = (Ddpm.theta✝ t).cos * (Ddpm.theta✝ t).cos / ((Ddpm.theta✝ 0.0).cos * (Ddpm.theta✝ 0.0).cos)
Instances For
β(t) = -d/dt log ᾱ(t) = π·tan θ(t)/(1+s), differentiated in closed form
rather than differenced — the schedule is analytic, so approximating its
derivative would be inventing error. ⚠ β diverges as t → 1. That stiffness
is why an explicit solver in x-space struggles here and why DDIM, which
integrates the linear part exactly, does not.
Equations
- Ddpm.betaC t = Ddpm.piF / (1.0 + Ddpm.sBias) * (Ddpm.theta✝ t).tan
Instances For
Inverse of abarC: t = (2(1+s)/π)·arccos(cos θ₀·√ᾱ) - s. Lets a solver
lay its grid out in ᾱ (or σ, or log-SNR) and land on exact times.
Equations
- Ddpm.tOfAbar ab = (min 1.0 (max (-1.0) ((Ddpm.theta✝ 0.0).cos * (max ab 0.0).sqrt))).acos * 2.0 * (1.0 + Ddpm.sBias) / Ddpm.piF - Ddpm.sBias
Instances For
The samplers the drivers can run, and what one step of each costs in network evaluations. ⚠ Comparisons are made at matched NFE, not matched steps: Heun is second-order and pays two evaluations per step, so on a fixed budget it takes half as many. Reporting steps instead would flatter it.