Flow Matching & Diffusion Transformers (DiT): Continuous-Time Generative Physics
Viewed through statistical mechanics, generative modeling is a boundary-value transport problem: how do we construct a time-dependent velocity field $v_t : \mathbb{R}^d \to \mathbb{R}^d$ that continuously transports a simple Gaussian prior $p_0 = \mathcal{N}(0, I)$ at $t = 0$ into an intricate empirical data distribution $p_1 = p_{\text{data}}$ at $t = 1$?
Classical Denoising Diffusion Probabilistic Models (DDPM; Ho et al., 2020) solved this via discrete-time stochastic differential equations (SDEs) with curved variance-preserving schedules, requiring dozens of solver steps to integrate accurately. Modern frontier visual, audio, and protein generators – including Stable Diffusion 3, Flux, and Sora-class Diffusion Transformers (DiT) – have converged on a cleaner geometric framework: Optimal Transport Conditional Flow Matching (CFM) (Lipman et al., 2023; Liu et al., 2023) paired with adaLN-Zero Transformer backbones (Peebles & Xie, 2023).
0. Fluid Mechanics of Probability: The Continuity Equation & Flow ODE
Suppose a particle starts at $x_0 \sim p_0$ and evolves deterministically along an ordinary differential equation (ODE) governed by a smooth velocity field $u_t(x)$:
The second equation is the exact conservation of probability mass (the continuity equation from fluid dynamics): the rate of density increase $\partial_t p_t$ inside any infinitesimal volume equals the negative divergence of the probability flux $J_t = p_t u_t$.
Why Marginal Flow Matching Seems Intractable – and Why Conditional Flow Matching Works
To train a neural velocity field $v_\theta(x_t, t)$ to match the marginal velocity $u_t(x_t)$, the naive objective $\mathcal{L}_{\text{FM}}(\theta) = \mathbb{E}_{t, x_t \sim p_t}\bigl[\|v_\theta(x_t, t) - u_t(x_t)\|^2\bigr]$ appears intractable because we do not have a closed-form expression for the marginal $u_t(x)$ integrated over all data points $x_1 \sim p_{\text{data}}$.
Lipman et al. (2023) proved a remarkable identity: if we define a simple conditional trajectory $x_t = \psi_t(x_0 \mid x_1)$ conditioned on a single data sample $x_1 \sim p_1$ with known conditional velocity $u_t(x_t \mid x_1) = \frac{d}{dt}\psi_t(x_0 \mid x_1)$, then the conditional loss $\mathcal{L}_{\text{CFM}}(\theta)$ has the exact same parameter gradient as the intractable marginal loss $\mathcal{L}_{\text{FM}}(\theta)$!
Proof sketch: expanding the squared norm and applying the definition of the marginal velocity $u_t(x) = \int u_t(x \mid x_1) \frac{p_t(x \mid x_1) p_1(x_1)}{p_t(x)} dx_1$ causes the cross-term $\langle v_\theta(x_t, t), u_t(x_t) \rangle$ to collapse into the conditional expectation via Bayes' rule, while the $u_t$-only squared terms are independent of $\theta$.
1. Optimal Transport Paths (Rectified Flow) vs. Curved DDPM Schedules
In a general linear Gaussian interpolation $x_t = \alpha_t x_1 + \sigma_t x_0$ (with $x_0 \sim \mathcal{N}(0, I)$), choosing $\alpha_t = \sqrt{\bar{\alpha}_t}$ and $\sigma_t = \sqrt{1 - \bar{\alpha}_t}$ recovers the curved Variance-Preserving (VP-SDE / DDPM) path. Because $\frac{d^2 x_t}{dt^2} \neq 0$, the conditional trajectories curve through space, forcing numerical ODE solvers to take tiny steps ($\Delta t \ll 1$) to avoid truncation error.
Optimal Transport Flow Matching / Rectified Flow (Liu et al., 2023; Lipman et al., 2023) chooses the simplest possible displacement interpolant – a straight line in Euclidean space with constant velocity:
Exact Algebraic Equivalence Between Velocity $v_\theta$ and Score $\nabla_x \log p_t(x)$
Even when training with Flow Matching, we can recover the exact Stein score function $s_\theta(x_t, t) \approx \nabla_{x_t} \log p_t(x_t)$ (needed for stochastic SDE sampling or likelihood evaluation) at any $t \in (0, 1)$ via Tweedie's formula:
Logit-Normal Timestep Sampling & Resolution Shift (SD3)
When training Flow Matching with uniform timesteps $t \sim \mathcal{U}(0, 1)$, the network wastes capacity near $t \to 0$ (where the optimal prediction is simply the dataset mean $\mathbb{E}[x_1] - x_0$) and $t \to 1$ (where reconstructing $x_1$ from $x_{0.99}$ is trivial). Esser et al. (2024) demonstrated that sampling $t$ from a Logit-Normal distribution concentrates training updates at the critical perceptual transition around $t = 0.5$:
where $s_{\text{res}} = \sqrt{M_{\text{tokens}} / M_{\text{base}}}$ shifts timesteps toward higher noise levels as image/video resolution $M_{\text{tokens}}$ increases (since higher spatial resolution has greater redundancy against equal-variance pixel noise).
2. Diffusion Transformers (DiT) & `adaLN-Zero` Conditioning
Replacing the convolutional U-Net with an isotropic Vision Transformer (Peebles & Xie, 2023) unlocks clean scaling laws with compute $\mathcal{C} = N_{\text{params}} \cdot M_{\text{patches}}$. How should timestep $t$ and class/text conditioning $c$ modulate each Transformer block? Peebles & Xie discovered that Adaptive LayerNorm initialized to Zero (adaLN-Zero) significantly outperforms cross-attention and in-context token conditioning at every FLOP budget:
Initializing $W_{\text{ada}} = 0$ and $b_{\text{ada}} = 0$ sets $\alpha_1 = \alpha_2 = 0$ at step $0$, making every DiT block an exact identity function ($h_{\text{out}} = h$) at initialization and stabilizing deep 40-layer training without warmup instabilities.
PyTorch Implementation: DiT Block with adaLN-Zero & Logit-Normal CFM LossPure PyTorch
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
# x: [B, N_tokens, D], shift: [B, D], scale: [B, D]
return x * (1.0 + scale.unsqueeze(1)) + shift.unsqueeze(1)
class DiTBlockAdaLNZero(nn.Module):
"""Diffusion Transformer Block with adaLN-Zero conditioning (Peebles & Xie, 2023)."""
def __init__(self, d_model: int = 128, n_heads: int = 4, mlp_ratio: int = 4):
super().__init__()
self.norm1 = nn.LayerNorm(d_model, elementwise_affine=False, eps=1e-6)
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
self.norm2 = nn.LayerNorm(d_model, elementwise_affine=False, eps=1e-6)
self.mlp = nn.Sequential(
nn.Linear(d_model, d_model * mlp_ratio),
nn.GELU(approximate="tanh"),
nn.Linear(d_model * mlp_ratio, d_model)
)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(),
nn.Linear(d_model, 6 * d_model, bias=True)
)
# Zero-initialize the modulation linear layer so alpha_1 = alpha_2 = 0 at step 0!
nn.init.zeros_(self.adaLN_modulation[1].weight)
nn.init.zeros_(self.adaLN_modulation[1].bias)
def forward(self, x: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
# x: [B, N_tokens, D], cond: [B, D] (sum of timestep and class/text embeddings)
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
self.adaLN_modulation(cond).chunk(6, dim=-1)
)
h = modulate(self.norm1(x), shift_msa, scale_msa)
attn_out, _ = self.attn(h, h, h, need_weights=False) # [B, N_tokens, D]
x = x + gate_msa.unsqueeze(1) * attn_out
x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
return x
def sample_logit_normal_timesteps(
batch_size: int,
mu: float = 0.0,
sigma: float = 1.0,
res_shift: float = 1.0,
device: str = "cpu"
) -> torch.Tensor:
u = torch.randn(batch_size, device=device) * sigma + mu
t = torch.sigmoid(u)
if res_shift != 1.0:
t = (res_shift * t) / (1.0 + (res_shift - 1.0) * t)
return t # [B]
def rectified_flow_loss(model_fn, x_1: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
"""Optimal Transport Conditional Flow Matching loss: E[||v_theta(x_t, t, c) - (x_1 - x_0)||^2]."""
B = x_1.size(0)
x_0 = torch.randn_like(x_1) # [B, ...]
t = sample_logit_normal_timesteps(B, device=x_1.device)
t_broadcast = t.view(B, *([1] * (x_1.ndim - 1))) # [B, 1, ...]
x_t = (1.0 - t_broadcast) * x_0 + t_broadcast * x_1
target_velocity = x_1 - x_0
pred_velocity = model_fn(x_t, t, cond)
return F.mse_loss(pred_velocity, target_velocity)
3. Classifier-Free Guidance (CFG) & 2nd-Order Heun ODE Integration
At inference time, we integrate $\frac{dx_t}{dt} = \hat{v}_\theta(x_t, t, c)$ from $t = 0$ ($x_0 \sim \mathcal{N}(0, I)$) to $t = 1$, where the guided velocity $\hat{v}_\theta$ linearly extrapolates from the unconditional velocity $v_\theta(x_t, t, \varnothing)$ toward the conditional velocity $v_\theta(x_t, t, c)$ with guidance scale $w \ge 1.0$:
While 1st-order Euler integration has global truncation error $\mathcal{O}(\Delta t)$, the 2nd-order Heun predictor-corrector achieves $\mathcal{O}(\Delta t^2)$ accuracy, halving the required Number of Function Evaluations (NFE) for curved regions near $t \to 1$.
PyTorch Implementation: Euler & 2nd-Order Heun Flow ODE Solver with CFGPure PyTorch
import torch
@torch.no_grad()
def solve_flow_ode(
velocity_fn, # Callable(x_t, t_tensor, cond) -> velocity tensor
x_0: torch.Tensor, # Initial Gaussian noise [B, ...]
cond: torch.Tensor, # Conditioning tensor [B, ...]
uncond: torch.Tensor, # Null conditioning tensor [B, ...]
num_steps: int = 20,
cfg_scale: float = 4.5,
solver: str = "heun" # 'euler' (1st order) or 'heun' (2nd order predictor-corrector)
) -> torch.Tensor:
B = x_0.size(0)
ts = torch.linspace(0.0, 1.0, num_steps + 1, device=x_0.device)
x_t = x_0.clone()
def guided_v(x: torch.Tensor, t_scalar: float) -> torch.Tensor:
t_vec = torch.full((B,), t_scalar, device=x.device, dtype=x.dtype)
if cfg_scale == 1.0:
return velocity_fn(x, t_vec, cond)
v_u = velocity_fn(x, t_vec, uncond)
v_c = velocity_fn(x, t_vec, cond)
return v_u + cfg_scale * (v_c - v_u)
for i in range(num_steps):
t_cur = ts[i].item()
t_next = ts[i + 1].item()
dt = t_next - t_cur
v_cur = guided_v(x_t, t_cur)
x_euler = x_t + dt * v_cur
if solver == "heun":
v_next = guided_v(x_euler, t_next)
x_t = x_t + 0.5 * dt * (v_cur + v_next)
else:
x_t = x_euler
return x_t
4. Failure Modes & Generative Diagnostics
Fix: Apply CFG Interval Clamping (Kynkäänniemi et al., 2024) – disable CFG ($w = 1.0$) for early global-layout steps $t \in [0, 0.2]$ and final high-frequency steps $t \in [0.85, 1.0]$, applying $w \gt 1$ strictly inside the middle perceptual window $t \in [0.2, 0.85]$.
Linear(d_model, p * p * C)) is initialized with standard Kaiming weights, early steps suffer massive velocity variance spikes across patch boundaries.Fix: Always zero-initialize the final linear layer and its modulation MLP in the DiT head so the initial predicted velocity field is identically $v_\theta(x_t, t) = 0$ everywhere at step $0$.
- Training Objective
- Rectified Flow velocity prediction $v_\theta \approx x_1 - x_0$ with Logit-Normal $(\mu=0.0, \sigma=1.0)$ timestep sampling.
- Resolution Shift $s_{\text{res}}$
- $s_{\text{res}} = 1.0$ at $256^2$, $s_{\text{res}} = 3.0$ at $1024^2$ ($4096$ latent patches at $16\times$ VAE compression).
- Inference Solver
- 20–28 Euler steps or 12–16 Heun steps with QK-Norm enabled inside all DiT attention layers.
5. Runnable End-to-End Verification: DiT Identity Init & Flow ODE Transport
The self-contained script below verifies: (1) `DiTBlockAdaLNZero` acts as an exact identity mapping at initialization ($\|f(x, c) - x\|_\infty = 0.0$), (2) training a lightweight velocity network with `rectified_flow_loss` for 200 steps transports $\mathcal{N}(0, I)$ to a target bimodal distribution, and (3) `solve_flow_ode` with 2nd-order Heun integration accurately lands on the target modes.
Runnable Verification Script: adaLN-Zero Identity, Rectified Flow Training & Heun ODEVerified PyTorch
import torch
import torch.nn as nn
def verify_flow_matching_dit():
torch.manual_seed(42)
# 1. Verify DiTBlockAdaLNZero exact identity at step 0
B, N, D = 4, 16, 64
x_lat = torch.randn(B, N, D)
cond = torch.randn(B, D)
dit_block = DiTBlockAdaLNZero(d_model=D, n_heads=4)
out_init = dit_block(x_lat, cond)
identity_diff = (out_init - x_lat).abs().max().item()
assert identity_diff == 0.0, f"adaLN-Zero failed identity init: {identity_diff}"
# 2. Train a tiny Conditional Rectified Flow velocity network on 2D conditional target modes
# Class c=+1 -> target mode at (+3.0, +3.0); Class c=-1 -> target mode at (-3.0, -3.0)
net = nn.Sequential(
nn.Linear(2 + 1 + 1, 64),
nn.SiLU(),
nn.Linear(64, 64),
nn.SiLU(),
nn.Linear(64, 2)
)
opt = torch.optim.AdamW(net.parameters(), lr=1e-2)
def model_fn(x_t: torch.Tensor, t: torch.Tensor, c: torch.Tensor) -> torch.Tensor:
inp = torch.cat([x_t, t.unsqueeze(-1), c.unsqueeze(-1)], dim=-1)
return net(inp)
for _ in range(200):
c_batch = torch.randint(0, 2, (128,)).float() * 2.0 - 1.0 # {-1, +1}
x1_batch = c_batch.unsqueeze(-1) * 3.0 + 0.1 * torch.randn(128, 2)
loss = rectified_flow_loss(model_fn, x1_batch, c_batch)
opt.zero_grad()
loss.backward()
opt.step()
# 3. Integrate Probability Flow ODE using 2nd-order Heun solver for class c=+1
x0_test = torch.randn(64, 2)
c_pos = torch.ones(64)
c_null = torch.zeros(64)
x1_gen = solve_flow_ode(model_fn, x0_test, c_pos, c_null, num_steps=16, cfg_scale=1.0, solver="heun")
gen_mean = x1_gen.mean(dim=0)
dist_to_target = (gen_mean - torch.tensor([3.0, 3.0])).norm().item()
assert dist_to_target < 0.35, f"Flow ODE transport error too high: {dist_to_target}"
print(f"[PASS] DiT adaLN-Zero Step-0 Identity Diff : {identity_diff:.1e} (Exact Identity!)")
print(f"[PASS] Final Rectified Flow Training Loss : {loss.item():.4f}")
print(f"[PASS] Heun ODE Generated Mode Mean : ({gen_mean[0]:.2f}, {gen_mean[1]:.2f}) vs Target (3.00, 3.00)")
if __name__ == "__main__":
verify_flow_matching_dit()
6. Generative Transport Frameworks: Head-to-Head
| Dimension | DDPM (VP-SDE) | Score SDE / EDM | OT Flow Matching (Rectified Flow) |
|---|---|---|---|
| Forward Path $x_t$ | $\sqrt{\bar{\alpha}_t} x_1 + \sqrt{1 - \bar{\alpha}_t} x_0$ | $x_1 + \sigma(t) x_0$ | $(1 - t) x_0 + t x_1$ |
| Network Target | Noise $\epsilon_\theta \approx x_0$ | Preconditioned Denoiser $D_\theta \approx x_1$ | Velocity $v_\theta \approx x_1 - x_0$ |
| Trajectory Geometry | Curved (high $\frac{d^2 x_t}{dt^2}$) | Curved variance-exploding | Straight Euclidean line ($\frac{d^2 x_t}{dt^2} = 0$) |
| Timestep Sampling | Discrete uniform $t \in \{1..1000\}$ | Log-Normal $\ln \sigma \sim \mathcal{N}(P_{\text{mean}}, P_{\text{std}}^2)$ | Logit-Normal $t = \sigma(\mathcal{N}(\mu, \sigma^2))$ + Res Shift |
| Typical Sampling NFE | $50$ – $1000$ steps | $25$ – $50$ steps (Heun) | $8$ – $25$ steps (Euler / Heun) |