21. Flow-Matching
Diffusion works, and it is a strange way to have arrived at something that works. There is a stochastic differential equation, a variational bound, a noise schedule with a name, a discrete chain of a thousand steps, and three interchangeable parameterizations. The method is not complicated — the derivation is.
Flow matching1 arrives at the same place from the other direction and the setup fits in three lines. Draw a noise sample and a data sample. Draw the straight line between them. Train a network to predict the direction of travel at a random point on that line. That is the whole method.
No schedule, no bound, no chain. And — this is the part worth being clear about — it is not a different family of model. Diffusion is flow matching with a curved interpolant. Once you see that, the last four chapters collapse into one idea.
Moving particles
You have particles distributed as \(p_0 = \mathcal{N}(0, I)\). You want them distributed like your data by time \(t = 1\). Learn a velocity field \(v_\theta(x, t)\) that says which way each particle should move at each instant, and integrate.
There is an obvious problem with training this, and it is the one the whole field was stuck on. The velocity field you need is a marginal: at position \(x\) and time \(t\), it must average over every data point that could plausibly have produced \(x\). You cannot compute that. You cannot even write it down.
The theorem that makes it work
You do not have to. Regressing the conditional velocity — the one for a single sampled pair \((x_0, x_1)\), which is just \(x_1 - x_0\) — has the same gradient as regressing the intractable marginal one1. So you train on a target you can write down in one line, and the network converges to the field you actually wanted.
This is exactly the move DDPM makes when it regresses \(\epsilon\) instead of the score. Same trick, stated once and in general instead of once per noise schedule.
Diffusion is a special case
Take any pair of functions \(a(t)\), \(b(t)\) and define the interpolant
Every method in this module is a choice of \(a\) and \(b\):
| Method | \(a(t)\) | \(b(t)\) | Path shape |
|---|---|---|---|
| DDPM / diffusion | \(\sqrt{\bar\alpha_t}\) | \(\sqrt{1 - \bar\alpha_t}\) | Curved — a quarter circle |
| Flow matching (OT path) | \(t\) | \(1 - t\) | Straight, per pair |
| Rectified flow3 | \(t\) | \(1 - t\) | Straight, and re-coupled |
The conditional velocity is \(a'(t)x_1 + b'(t)x_0\) in every case, and the marginal is its conditional expectation. One framework, one loss, one code path. The remaining differences are the weighting of the loss across \(t\) and the shape of the trajectory — which is what the next panel measures.
What "straighter paths" actually means
The standard claim is that flow matching gives straight trajectories and therefore needs fewer integration steps. It is half true, and the half that is false is worth knowing.
Everything in that panel is exact — the target is a Gaussian mixture, so both interpolants' marginal velocities have closed forms and nothing is trained. Compare the two and:
- The conditional path — the line between one noise sample and one data sample — is straight by construction with the linear interpolant.
- The marginal path, which is what the model actually learns, is not. It has to average over every data point compatible with the current position, and independently paired noise and data cross each other constantly. On this target the learned linear-interpolant paths come out more curved than the diffusion ones.
- Past about 32 steps the two interpolants converge to the same error. The entire argument is about the few-step regime.
Flow matching's real advantage is not the trajectory
It is the objective: a single unweighted regression with no schedule to tune, uniform loss weighting across \(t\), and no \(\epsilon\)-versus-\(\mathbf{v}\) decision to get wrong at high noise. That is why SD3 and FLUX adopted it. Claims of "2–10× fewer steps" from the interpolant alone do not survive careful measurement; the step-count gains come from reflow and distillation, which is the next section.
Rectified flow: straightening on purpose
If the paths are not straight, straighten them3. Reflow is two steps:
- Sample noise \(z\), integrate the trained ODE accurately, get \(x = \text{ODE}(z)\).
- Retrain the model on the pairs \((z, x)\) that it produced itself, on the straight line between them.
The marginal distribution of \(x\) is unchanged — you are not altering what the model generates, only which noise is paired with which output. But the coupling is now one the model can realize with a straight line, and a straight line is integrated exactly by one Euler step.
Tick after one reflow in the panel and the curvature goes to zero and the error goes to zero at every step count. That is the mechanism behind one-step and few-step generation, and it is why SD3 and FLUX are described as rectified-flow models rather than merely flow-matching ones.
| Where it goes from there | Idea |
|---|---|
| Consistency models4 | Train the map from any point on the trajectory directly to its endpoint. Self-consistency is the loss. |
| Shortcut models5 | Condition the network on the step size itself, so one model serves 1, 4 or 128 steps. |
| MeanFlow6 | Learn the average velocity over an interval rather than the instantaneous one — one-step generation without a separate distillation stage. |
Where it is used
Flow matching is not a niche alternative any more; it is what the current generation of models is built on.
| Model | What it uses it for |
|---|---|
| Stable Diffusion 37 | Rectified flow + MMDiT, with a resolution-dependent timestep shift |
| FLUX.1 | Rectified flow + a 12B DiT, two CLIP encoders and T5-XXL |
| Video and audio models | The same objective, unchanged, on a temporal latent |
| Molecules and proteins | Flow matching on manifolds — the framework does not assume Euclidean data |
| Robot policies | Action chunks as a flow; the same code, a different \(p_1\) |
flowchart LR
A["Text<br/>(prompt)"] --> B["Text encoders<br/>CLIP + T5-XXL"]
N["Noise<br/>z₀ ~ N(0,I)"] --> C
B --> C["Diffusion Transformer<br/>rectified flow"]
C -->|"ODE: 20–50 steps"| D["Latent z₁"]
D --> E["VAE decoder"]
E --> F["Image 1024×1024"] Sampling
The training gives you a velocity field. Turning it into a sample is ODE integration, and you may pick the solver — the same separation as in chapter 20.
import torch
@torch.no_grad()
def sample_euler(model, shape, n_steps=25, device='cuda'):
"""First order. One model evaluation per step."""
x = torch.randn(shape, device=device) # x at t = 0 is pure noise
dt = 1.0 / n_steps
for i in range(n_steps):
t = torch.full((shape[0],), i * dt, device=device)
x = x + dt * model(x, t)
return x
@torch.no_grad()
def sample_heun(model, shape, n_steps=15, device='cuda'):
"""Second order: two evaluations per step, but far less error per step.
At equal cost it beats Euler once the field has any curvature at all."""
x = torch.randn(shape, device=device)
dt = 1.0 / n_steps
for i in range(n_steps):
t = torch.full((shape[0],), i * dt, device=device)
t2 = torch.full((shape[0],), (i + 1) * dt, device=device)
v1 = model(x, t)
v2 = model(x + dt * v1, t2) # predictor, then correct
x = x + dt * (v1 + v2) / 2
return x
Reading a flow-matching codebase
Three conventions differ between papers and will cost you an afternoon if you assume: whether \(t = 0\) is noise or data (this chapter uses noise), whether the model predicts velocity, \(x_1\) or \(\epsilon\) (all interconvertible, given \(t\) and the interpolant), and whether \(t\) is passed as a number in \([0,1]\) or an integer timestep index. Check all three before debugging anything else.
Key takeaways
- Flow matching learns a velocity field and samples by integrating an ODE. The loss is one MSE with no schedule and no bound.
- The marginal velocity is intractable, but regressing the conditional one has the same gradient. That theorem is the whole method.
- Diffusion is flow matching with a curved interpolant. \(a(t) = \sqrt{\bar\alpha_t}\) instead of \(a(t) = t\). One framework covers both.
- "Straight paths" applies to the conditional path. The marginal path — the one the model learns — is not straight, and may be more curved than diffusion's.
- The real advantage is the objective: simpler, unweighted, no schedule to get wrong. That is why SD3 and FLUX adopted it.
- Reflow delivers actual straightness by re-coupling each noise sample with the output it already produces. This is what makes one-step generation possible.
- Consistency models, shortcut models and MeanFlow all attack the same target: collapse the trajectory into fewer evaluations.
- Past ~32 steps the interpolant stops mattering. Every argument here is about the few-step regime.
-
Lipman, Y., Chen, R. T. Q., Ben-Hamu, H., Nickel, M., & Le, M. (2023). Flow Matching for Generative Modeling — ICLR. The conditional-flow-matching theorem is section 3, and it is short. ↩↩
-
Liu, X., Gong, C., & Liu, Q. (2023). Flow Straight and Fast: Learning to Generate and Transfer Data with Rectified Flow — ICLR. Rectified flow and the reflow procedure. ↩↩
-
Song, Y., Dhariwal, P., Chen, M., & Sutskever, I. (2023). Consistency Models — ICML. ↩
-
Frans, K., Hafner, D., Levine, S., & Abbeel, P. (2025). One Step Diffusion via Shortcut Models — ICLR. Condition on the step size and one model serves every step budget. ↩
-
Geng, Z., Deng, M., Bai, X., Kolter, J. Z., & He, K. (2025). Mean Flows for One-step Generative Modeling. Average velocity instead of instantaneous, and one-step generation without a distillation stage. ↩
-
Esser, P., et al. (2024). Scaling Rectified Flow Transformers for High-Resolution Image Synthesis — ICML. SD3; the ablation of interpolants and timestep weightings in section 3 is the most useful published comparison. ↩
-
Albergo, M. S., Boffi, N. M., & Vanden-Eijnden, E. (2023). Stochastic Interpolants: A Unifying Framework for Flows and Diffusions. The general \(a(t), b(t)\) statement used in the table above. ↩