TAI BUI
Back to curriculum
BuildPython

Flow Matching & Rectified Flows

Diffusion samplers take 20-50 steps because they follow a curved path from noise to data. Flow matching (Lipman et al., 2023) and rectified flow (Liu et al., 2022) train toward straight paths instead, and a straighter path needs fewer Euler steps. Stable Diffusion 3, Flux.1, and AudioCraft 2 moved to flow matching in 2024.

TIME: ~45 minutesPREREQUISITES: Phase 8 · 06 (DDPM), Phase 1 · Calculus

Learning Objectives

  • Explain why a straight noise-to-data path lets an Euler sampler use far fewer steps than a curved DDPM/DDIM path.
  • Derive the conditional flow matching target x_1 - x_0 and write the simulation-free training loss.
  • Describe how reflow straightens learned paths and what it costs.
  • Implement and compare 1, 2, 4, 8 and 20 step Euler sampling on a 1-D two-mode mixture.
  • Choose between flow matching, distillation and both for a given latency target.

The Problem

DDPM's reverse process is a 1000-step stochastic walk from N(0, I) back to the data distribution. DDIM collapsed it to 20-50 deterministic steps. You want fewer, ideally one. The blocker is that the ODE behind the reverse process is stiff, because the path it follows is curved.

Suppose the path from noise to data were a straight line. Then a single Euler step from t=1 to t=0 would land on the data. Flow matching builds exactly that: define a straight-line interpolation from x_1 ∼ N(0, I) to x_0 ∼ data, train a vector field v_θ(x, t) to match its time derivative, and integrate the field at inference.

Rectified flow (Liu 2022) goes a step further. A reflow procedure straightens the learned paths iteratively, so each round gives you an ODE closer to linear than the last. After two reflow iterations, a 2-step sampler matches 50-step DDPM quality.

Warm-up0/3
1. What does DDPM's forward process do to a data sample?Warm-up
A small pixel-grid data sample showing a plus sign, an arrow into a box labelled DDPM forward process, and an arrow to an empty grid marked with a question mark.
Illustrative 7x7 sample, typed, not captured from a model.
2. An ODE solver advances a state x using its derivative. What does a single Euler step of size h do?Warm-up
Plot of a rising curve x against time with the current point x_n marked and short slope segments drawn at the start, middle and end of a step of size h = 2, labelled slope 0.7, 1.2 and 1.7.
Illustrative curve x(t) = 0.25 t^2 + 0.2 t. The slopes are computed from that formula.
3. A network is trained with squared error on targets that differ for the same input. What does its best possible output at that input equal?Warm-up
Four training targets for one and the same network input, plotted as dots at values 3, 8, 2 and 5 in the order seen, next to a box asking what the network output at that input should be.
Illustrative targets, typed.
Check: The Problem0/2
1. What is flow matching?Define
Plot of six curves that start on the left at noise values at t = 1 and run to the right to t = 0, ending on the right at values between about -4 and 4.
Real run of the lesson's main.py: six start values on a grid, each integrated with 50 Euler steps by the trained network. The toy network is imperfect, so the end points are not exactly the data modes.
2. Why can an Euler sampler use far fewer steps on a straight path than on a curved one?Explain
Two plots of a path from noise x_1 at t = 1 to data x_0 at t = 0: on the left a straight line, on the right a curve that bends, both between the same two end points.
Illustrative paths between the same end points, drawn from a formula.

The Concept

Flow matching: straight-line interpolation between noise and data

Straight-line flow

Define:

x_t = t · x_1 + (1 - t) · x_0,   t ∈ [0, 1]

where x_0 ~ data and x_1 ~ N(0, I). The time derivative along this straight line is constant:

dx_t / dt = x_1 - x_0

Define a neural vector field v_θ(x_t, t) and train it to match this derivative:

L = E_{x_0, x_1, t} || v_θ(x_t, t) - (x_1 - x_0) ||²

This is the conditional flow matching loss (Lipman 2023). Training is simulation-free: you never unroll the ODE. Just sample (x_0, x_1, t) and regress.

Sampling

At inference, integrate the learned vector field backwards in time:

x_{t-Δt} = x_t - Δt · v_θ(x_t, t)

Start at x_1 ~ N(0, I), Euler-step down to t=0.

Rectified flow (Liu 2022)

Straight-line flow works, but the learned paths are not actually straight. They curve because many x_0s can map to the same x_1. Rectified flow's reflow step:

  1. Train flow model v_1 with random pairings.
  2. Sample N pairs (x_1, x_0) by integrating v_1 from x_1 to its landing x_0.
  3. Train v_2 on those paired examples. Because the pairs are now "ODE-matched", the straight-line interpolant between them is genuinely flatter.
  4. Repeat.

In practice 2 reflow iterations get you to near-linear, enabling 2-4 step inference. Distilled few-step models like SDXL-Turbo, SD3-Turbo, and LCM attack the same step-count problem from the distillation side.

Why this won for images in 2024

Three reasons made the difference:

  1. Simulation-free training. You never unroll the ODE during training, so the loss is short to implement.
  2. Better loss geometry. Straight paths keep the signal-to-noise ratio steadier, while DDPM's ε-loss has poor SNR at the edges of the schedule.
  3. Faster inference. 4-8 steps at SDXL-Turbo quality, and 1 step with consistency distillation.
Check: The Concept0/13
1. What makes flow matching training 'simulation-free'?Define
The training loop from main.py with its nine lines, next to a table of the real values of x0, x1, t, x_t, target and the network prediction on the loop's first pass.
Code and values captured by running the lesson's main.py (seed 31).
2. What is the purpose of rectified flow's reflow procedure?Define
Two tables over the same five noise samples: on the left each x_1 with a randomly drawn data value x_0, on the right each x_1 with the x_0 it lands on when v_1 is integrated.
Real values from the lesson's main.py network (seed 31, 50 Euler steps for the right table).
3. In flow matching, what is the velocity field v_θ(x, t)?Define
A box for the network v_θ(x, t) with its two inputs x_t and t, and a table of its real outputs for t of 0.9, 0.5 and 0.1 and x of -3, -2, 0, 2 and 3.
Values from the trained network of the lesson's main.py (seed 31).
4. What is the straight-line interpolant in flow matching?Define
Plot of one data sample 1.8 at t = 0 and one noise sample -0.8 at t = 1 joined by a straight line, with the value of x_t written at t = 0.75, 0.5 and 0.25: -0.15, 0.50 and 1.15.
One typed pair of samples. The marked values are computed from the pair.
5. What is an Euler sampler in this lesson?Define
A table with the four sampling steps of one real sample: step index, t from 1.00 to 0.25, the current x, the network output, and dt = 0.25, with the start and final x listed under it.
Captured from the lesson's main.py network; the start value is the first gauss draw of Random(99).
6. After how many reflow iterations do paths typically become near-linear, enabling 2-4 step inference?Define
A chain of models v_1 with random pairings, v_2 with ODE-matched pairs, and v_3 to v_k repeating the recipe, joined by arrows labelled reflow and ending in a box labelled paths with a question about straightness.
Chain as described in the lesson's Rectified flow section.
7. Why are the paths learned from random pairings not actually straight, even though each training interpolant is?Explain
Plot over t from 1 to 0 with eight thin grey straight lines, one per random pair of a data and a noise sample, and six orange curves that bend, the paths followed by the trained network.
Real: pairs drawn with main.py's sampler, and paths integrated with the lesson's trained network.
8. Why does training v_2 on ODE-matched pairs give flatter paths than the random pairings used for v_1?Explain
Plot over t from 1 to 0 of 25 straight lines, each joining a random noise sample at t = 1 to a random data sample at t = 0; many of them cross each other.
Real pairs drawn with main.py's data sampler and Gaussian noise.
9. What target does the conditional flow matching loss regress v_θ(x_t, t) onto, and why is it known without simulating anything?Explain
Plot of one pair: a data sample x_0 = -2 at t = 0 and a noise sample x_1 = 1 at t = 1 joined by a straight line, with the point x_t marked at t = 0.4, and a box saying the loss compares v_θ(x_t, t) with an unknown target.
One typed pair of samples.
10. Why does the lesson say straight paths give flow matching better loss geometry than DDPM's ε-loss?Explain
Two cards side by side: the flow matching straight-line interpolant and loss, and the DDPM ε-loss with its own x_t formula. Below, a plot over t of the straight line's signal weight 1 - t and noise weight t.
DDPM ε-loss is the standard form, typed for comparison. The straight-line weights are the lesson's alpha(t) = 1 - t and sigma(t) = t.
11. A sampler written as x += dt * net_forward(x, t) produces samples that look like pure noise. What is the bug?Analyze
Training lines from the lesson, x_t = t·x1 + (1 - t)·x0 and target = x1 - x0, above the teammate's sampling line x += dt * net_forward(x, t), with the teammate's report, labelled as not measured here, that samples look like pure noise.
Training lines are from the lesson; the sampling line and the report are the ones in the question.
12. How do reflow and distillation (SDXL-Turbo, LCM) differ in how they cut the step count?Analyze
Horizontal bars on a log scale for the sampling step counts quoted in the lesson: DDPM 1000, DDIM 20 to 50, Flux.1-dev 50, SDXL-base 30, Flux.1-schnell 4 and SDXL-Lightning 2.
Step counts from the lesson's Problem section and Production note. The DDIM bar is drawn to 50.
13. A product needs a 1-step sampler and you already have a flow-matched base. Which route fits?Design
Two bars: a flow-matched base at 50 steps, the Flux.1-dev figure from the lesson's table, and the product's requirement of 1 step.
The 50-step figure is the lesson's Flux.1-dev example. The question does not name the base model.

Flow matching vs DDPM — the exact connection

Flow matching with a Gaussian-conditional path is diffusion with a specific noise schedule. Pick the x_t = α(t) x_0 + σ(t) x_1 schedule and flow matching recovers Stratonovich-reformulated diffusion with v = dx_t/dt = α'·x_0 + σ'·x_1. The straight line above is the case α(t) = 1 - t, σ(t) = t, which gives v = -x_0 + x_1 = x_1 - x_0. For Gaussian paths the two are algebraically equivalent.

What flow matching added is a clearer target (a plain velocity), a cleaner loss, and room to experiment with non-Gaussian interpolants.

Check: Flow matching vs DDPM — the exact connection0/1
1. For Gaussian-conditional paths, how are flow matching and diffusion related?Explain
A card with the Gaussian-conditional path x_t = α(t) x_0 + σ(t) x_1 and its velocity α' x_0 + σ' x_1, above a table of α, σ and their derivatives for the straight line α = 1 - t, σ = t at five values of t.
Formulas and the straight-line choice are from the lesson; table values are computed.

Build It

code/main.py implements 1-D flow matching on a two-mode Gaussian mixture. The vector field v_θ(x, t) is a tiny MLP trained with the straight-line target. At inference, integrate 1, 2, 4, 8, and 20 Euler steps and compare sample quality. The snippets below are illustrative; they follow main.py but compress it.

Step 1: training loss

python
def train_step(x0, net, rng, lr):
    x1 = rng.gauss(0, 1)
    t = rng.random()
    x_t = t * x1 + (1 - t) * x0
    target = x1 - x0
    pred = net_forward(x_t, t)
    loss = (pred - target) ** 2
    # backprop + update

Step 2: multi-step inference

python
def sample(net, num_steps):
    x = rng.gauss(0, 1)
    for i in range(num_steps):
        t = 1.0 - i / num_steps
        dt = 1.0 / num_steps
        x -= dt * net_forward(x, t)
    return x

Step 3: compare step counts

main.py prints a histogram for each step count, plus the sample mean and how many samples landed on each side of zero. Compare the 4-step and 20-step histograms by eye. If they look alike, you have cut the sampling cost by 5x for this toy, and latency scales with step count in a real model too.

Check: Build It0/7
1. In train_step, x0 = 2, x1 = -1 and t = 0.25. What are x_t and the target?Apply
The train_step code from the lesson next to the given values x0 = 2, x1 = -1 and t = 0.25, with x_t and target left as question marks.
Code from the lesson's Build It section.
2. In train_step, the net outputs pred = -1 for a sample whose target is -3. What is loss?Apply
Code lines pred = -1, target = -3 and loss = (pred - target) ** 2 above a number line with the target at -3 and the prediction at -1, and a label loss = ?.
Typed values from the question.
3. In sample with num_steps = 4, which t values does net_forward see, and what is dt?Apply
The sample function from the lesson and the call sample(net, 4), with the t values that net_forward sees and dt left as question marks.
Code from the lesson's Build It section.
4. Suppose net_forward always returns 2.0. Starting from x = 1.0, what does sample(net, 4) return?Apply
The sample function with the start set to x = 1.0 and net_forward noted as always returning 2.0, and a question mark for what sample(net, 4) returns.
Code from the lesson with the question's values typed in.
5. On the toy, the 4-step and 20-step histograms look alike. By what factor have you cut the network evaluations per sample?Apply
Two terminal outputs of main.py, one for 4-step and one for 20-step Euler integration, each with a six-row text histogram showing two bumps, the sample mean and the left and right counts.
Real output of the lesson's main.py (seed 31, 500 samples per step count).
6. On the toy, the 1-step sample is a single blob near zero between the two modes, while 20 steps gives two modes. What best explains the blob?Analyze
Two terminal outputs of main.py: the 1-step histogram is a single bump near zero, and the 20-step histogram shows two bumps; each lists the sample mean and the left and right counts.
Real output of the lesson's main.py (seed 31, 500 samples per step count).
7. A teammate sees that the 4-step and 20-step toy histograms match and plans to run an image model at 4 steps unchanged. What is the main flaw?Analyze
Two histograms of toy samples on the range -5 to 5, one for 4 steps and one for 20 steps, each with two bumps of similar shape and a line listing the left and right counts and the mean.
Real: samples from the lesson's main.py network (seed 31, 500 per histogram).

Pitfalls

  • Time parameterization. Flow matching uses t ∈ [0, 1] with t=0 at data, t=1 at noise. DDPM uses t ∈ [0, T] with t=0 at data, t=T at noise. Same direction, different scale. Papers get this wrong constantly.
  • Schedule choice. Rectified flow's straight line is the standard flow-matching schedule, but you can use cosine or logit-normal t-sampling (SD3 does this) for better scale coverage.
  • Reflow cost. Generating the paired dataset for reflow is a full inference pass per sample. Only do reflow when you really need 1-2 step inference.
  • Classifier-free guidance still applies. Swap ε for v in the linear combination: v_cfg = (1+w) v_cond - w v_uncond.
Check: Pitfalls0/8
1. What does logit-normal t sampling do during training?Define
The line t = rng.random() from the training loop, and an axis from t = 0 at data to t = 1 at noise with the first 14 values drawn by that line shown as dots.
The 14 draws are real, from main.py's seed-31 generator.
2. How does classifier-free guidance carry over to flow matching?Define
Two velocity boxes, v_cond with the prompt and v_uncond without it, feeding a guidance weight w, which leads to a box v_cfg with a question mark.
Inputs named in the question; the combination is left blank.
3. What is the time convention difference between flow matching and DDPM?Explain
An arrow labelled as one sample going from data to noise, above two time rulers, one for flow matching and one for DDPM, whose end points are both marked with question marks.
Schematic. The end values of the two rulers are what the question asks about.
4. Which t-sampling alternative to the plain straight-line schedule does SD3 use?Explain
Histogram of 2000 values of t drawn with rng.random() in 10 bins over [0, 1], all of similar height, with a note asking which alternative to this plain schedule SD3 uses.
Real: 2000 draws from main.py's seed-31 generator.
5. What does it cost to generate the paired dataset for one reflow iteration?Explain
A pipeline for one reflow pair: a noise sample, integrating v_1 from t = 1 down to x_0, and the resulting pair, repeated N times, with the cost of building the dataset marked with a question mark.
Schematic of Rectified flow step 2 in the lesson.
6. With v_cond = 2.0, v_uncond = 1.0 and w = 1.0, what is v_cfg?Apply
The formula v_cfg = (1 + w)·v_cond - w·v_uncond above a number line with v_uncond = 1.0 and v_cond = 2.0 marked, the value w = 1.0, and v_cfg left as a question mark.
Formula from the lesson's Pitfalls section, values from the question.
7. A DDPM pipeline passes t in [0, 1000] to a flow matching net trained with t in [0, 1], and outputs are garbage. What is the most likely cause?Analyze
A table of the trained main.py network's outputs for x = 0.0 and t = 1000, 750, 500 and 250, all equal to -0.511, under a heading saying the net was trained with t in [0, 1] while the DDPM pipeline passes t in [0, 1000].
Real forward passes through the lesson's trained network. The four t values are examples from the stated range.
8. Your latency budget fits about 20 Euler steps. Reflow would cost one full inference pass per paired sample. What do you do?Design
A log-scale axis of Euler steps per sample with the 1 to 2 step range marked as reflow's target and a marker at about 20 for the latency budget, and a line noting that reflow pair generation costs one full inference pass per paired sample.
Ranges from the lesson's Pitfalls section and the question.

Use It

Use case2026 stack
Text-to-image, best qualityFlow matching: SD3, Flux.1-dev
Text-to-image, 1-4 stepsDistilled flow matching: Flux.1-schnell, SD3-Turbo, SDXL-Turbo
Real-time inferenceConsistency distillation from a flow-matched base (LCM, PCM)
Audio generationFlow matching: Stable Audio 2.5, AudioCraft 2
Video generationFlow matching mixed with diffusion (Sora, Veo, Stable Video)
Science / physics (particle trajectories, molecules)Flow matching + equivariant vector field

Whenever a paper says "faster than diffusion" in 2025-2026, it is almost always flow matching + distillation.

Check: Use It0/2
1. What is consistency distillation?Define
Plot of one learned path from a noise value at t = 1 to a value above 3 at t = 0, with points marked at t = 1, 0.75, 0.5, 0.25 and 0, and a box for a student model with input x_t and t and an unknown output.
Path from the lesson's main.py network, integrated with 50 Euler steps from x_1 = 1.2. The student box is a schematic.
2. Which stack does the Use It table recommend for real-time inference?Explain
The Use It table with its six use cases listed and every stack replaced by a question mark, with the Real-time inference row highlighted.
Use cases from the lesson's Use It table; the stacks are hidden.

Ship It

Save outputs/skill-fm-tuner.md. Skill takes a diffusion-style model spec and converts it to a flow-matching training config: schedule choice, time sampling distribution (uniform / logit-normal), optimizer, reflow plan, target step count, eval protocol.

Author's Note

I'd run main.py before rereading the derivation. It trains a tiny MLP on a 1-D two-mode mixture with the straight-line target, then samples with 1, 2, 4, 8, and 20 Euler steps. Each run prints a histogram, the sample mean, and a count of samples on each side of zero. The backward pass is hand-written, so nothing sits between you and the regression.

The part I'd reread until it feels boring is the training step. x_t is t * x1 + (1 - t) * x0, the target is x1 - x0, and the loss is a squared error between the net's output and that target. The ODE never gets unrolled during training, which is the whole meaning of simulation-free here.

I'd also read the step-count comparison with some skepticism. The toy is one-dimensional with two tight modes, and the net sees t through a few extra input features (t, t², and a sine and cosine). So the 1-step result, a single blob near zero between the two modes with samples split about evenly across zero, says little about image models. Check the histogram, not just the left/right counts. The closing print statement in main.py compares against DDPM, but the script never runs a DDPM, so treat that line as a hint to test yourself. The reflow exercise is where the straightening claim gets tested properly.

Exercises

  1. Easy. Run code/main.py and compare the 1-step and 20-step histograms against the true two-mode distribution.
  2. Medium. Switch from uniform t sampling to logit-normal (concentrates sampling at mid-t). Does the model quality improve?
  3. Hard. Implement one reflow iteration: generate paired (x_0, x_1) by integrating the first model, train a second model on the pairs, and compare 1-step sample quality.

Key Terms

TermWhat people sayWhat it actually means
Flow matching"Straight-line diffusion"Train v_θ(x, t) to match x_1 - x_0 along an interpolant.
Rectified flow"Reflow"Iterative procedure that straightens learned flows.
Velocity field"v_θ"Output of the model, the direction to move x_t.
Straight-line interpolant"The path"x_t = (1-t)·x_0 + t·x_1; trivial target derivative.
Euler sampler"1st order ODE solver"Simplest integrator; works well when paths are straight.
Logit-normal t"SD3 sampling"Concentrate t sampling toward mid-values where gradients are strongest.
Consistency distillation"1-step sampler"Train a student to map any x_t directly to x_0.
CFG with velocity"v-CFG"v_cfg = (1+w) v_cond - w v_uncond; same trick, new variable.

Production note: Flux.1-schnell is flow matching at its fastest

Flux.1-schnell is the clearest production example: a flow-matched DiT distilled to 1-4 inference steps while keeping Flux-dev-grade quality. Niels' "Run Flux on an 8GB machine" notebook is the reference deployment recipe: T5 + CLIP encode, quantized MMDiT denoise (in 4 steps for schnell vs 50 for dev), VAE decode. The cost accounting:

VariantStepsLatency at 1024² on L4Total FLOPs (relative)
Flux.1-dev (raw)50~15 s1.0×
Flux.1-schnell4~1.2 s0.08× (12× faster)
SDXL-base30~4 s0.25×
SDXL-Lightning 2-step2~0.3 s0.03×

The production rule: a flow-matched base plus distillation is the 2026 default for fast text-to-image. Every major vendor ships this combo: SD3-Turbo (SD3 + flow + distillation), Flux-schnell (Flux-dev + rectified-flow straightening), CogView-4-Flash. Pure diffusion bases exist only for legacy checkpoints.

Check: Production note: Flux.1-schnell is flow matching at its fastest0/1
1. Your team serves Flux.1-dev at 50 steps (about 15 s per 1024² image on an L4) and needs roughly 1-2 s. What should you try first?Design
Horizontal bars on a seconds axis: Flux.1-dev at about 15 seconds with 50 steps, and a dashed band for the required 1 to 2 seconds.
Figures are the ones in the question.

Further Reading

Code

Ships With