Pixels2GenAI
Path iii Generative
M 12 · 12.7.1 · conceptual

12.7.1 Flow Matching

Train a generative model that learns a velocity field mapping noise to data along straight paths, sample with an Euler ODE integrator in 50 steps, and see why it needs 5-20× fewer steps than DDPM for comparable quality.

Duration35–45 min
Leveladvanced
Load3 core concepts
PrereqsLesson 12.3.1 (DDPM Basics) helpful but not required; PyTorch, basic ODE intuition
Fig. 1 Sixteen African fabric patterns generated from random noise in 50 Euler integration steps. The same dataset DDPM (12.3.1) needs ~250 steps to match in quality.

Overview

DDPM trains a network to undo a noise schedule and samples by reversing that schedule step by step — a curved, stochastic walk through a thousand small denoisings. It works, and the Lesson 12.3.1 lessons go through the details, but the curve is the problem. Curved paths through high-dimensional space need a lot of small steps to follow accurately. Cut the step count and the trajectory drifts off the curve into regions the model can’t recover from.

Flow Matching, formalised by Lipman et al. in 2022 [1] and independently by Liu, Gong and Liu’s Rectified Flow paper [2], asks a deliberately naive question: what if the path from noise to data were a straight line? Define x_t = (1 - t) · x_0 + t · x_1 where x_0 is noise and x_1 is data. The derivative — the velocity along that line — is simply x_1 - x_0. Train a neural network to predict the velocity at every point in the interpolation, sample by integrating that velocity field with Euler steps from t = 0 to t = 1. With straight lines, fifty Euler steps is enough to land cleanly. With DDPM’s curves, fifty steps misses by a country mile.

This shift — same data, same U-Net, fundamentally different objective and sampling procedure — is what powers FLUX.1, Stable Diffusion 3, and most of the current generation of high-end text-to-image systems [3].

Learning objectives

  1. Explain why Flow Matching needs fewer sampling steps than DDPM, in terms of trajectory curvature.
  2. Derive the Conditional Flow Matching training loss in five lines: sample x_0, sample t, interpolate, target velocity = x_1 - x_0, MSE.
  3. Implement an Euler ODE sampler that integrates a learned velocity field from noise to data.
  4. Predict the quality vs. step-count trade-off (5, 10, 20, 50 steps) and recognise the regime where Flow Matching beats DDPM.

Quick start — load the trained model, integrate 50 Euler steps

Download the checkpoint from the GitHub Release. Sampling is a fifteen-line loop: noise in, 50 velocity-field updates, fabric pattern out.

flow_matching_fabrics_latest.pt — pre-trained 54 MB checkpoint (GitHub Release)
python · quick-start.py
import torch
from flow_model import SimpleFlowUNet

model = SimpleFlowUNet(in_channels=3, base_channels=64)
ckpt = torch.load('flow_matching_fabrics_latest.pt', map_location='cpu')
model.load_state_dict(ckpt['model_state_dict'])
model.eval()

x = torch.randn(4, 3, 64, 64)     # pure noise at t = 0
dt = 1.0 / 50

with torch.no_grad():
    for i in range(50):
        t = torch.full((4,), i / 50)
        v = model(x, t)            # predicted velocity at (x, t)
        x = x + dt * v             # Euler step

x = x.clamp(-1, 1)                 # fabric patterns

That’s the whole sampling procedure. No noise schedule, no posterior-variance bookkeeping, no separate clamping at each step. Train one network to predict dx/dt, then numerically integrate.

Core concepts

Concept 1 — Straight paths instead of stochastic curves

DDPM’s forward process adds Gaussian noise in tiny steps; the reverse process learns to undo each step. The trajectory the model has to follow at sampling time is a curve that wanders through high-dimensional space according to a noise schedule β_t. To integrate that curve accurately you need hundreds of small steps. Ho, Jain and Abbeel’s original DDPM paper [4] used 1,000 steps; DDIM [5] brought it down to ~250 without retraining; further accelerated samplers like DPM-Solver get into the tens but at a cost in flexibility.

Fig. 2 Left: DDPM's curved, stochastic trajectory from noise to data — every step is small because the curve bends sharply. Right: Flow Matching's straight-line trajectory — the same start and end with one-tenth the steps.

Flow Matching takes the opposite design stance. Forget the noise schedule entirely. Define the simplest possible path from noise to data — a straight line — and learn to follow it. The interpolation x_t = (1 - t) · x_0 + t · x_1 is parameterised by t ∈ [0, 1]; the derivative is dx/dt = x_1 - x_0, constant along the line. A network trained to predict this constant at every (x_t, t) produces a velocity field whose integral curve is, by construction, that straight line.

This connects to a much older idea. Optimal transport, the mathematical framework Gaspard Monge sketched in 1781 [6], characterises the most efficient way to push one probability distribution onto another. Straight-line interpolation between samples corresponds exactly to one of the canonical optimal-transport paths — the displacement interpolant — which is why Flow Matching’s geometry is the right one in a precise sense, not just a convenient simplification. Tong et al. [7] generalise this with minibatch optimal-transport couplings for further trajectory straightening.

Concept 2 — The training objective in five lines

The Conditional Flow Matching loss is one of the simplest objectives in modern deep learning. Five lines per training step, no schedules, no warm-up tricks.

python · conditional flow matching loss
def flow_matching_loss(model, x_1):
    x_0 = torch.randn_like(x_1)                          # 1. noise
    t   = torch.rand(x_1.shape[0], 1, 1, 1)              # 2. random time per sample
    x_t = (1 - t) * x_0 + t * x_1                        # 3. straight-line interpolant
    v_target = x_1 - x_0                                 # 4. constant velocity along the line
    v_pred   = model(x_t, t.squeeze())                   # 5. network's prediction
    return F.mse_loss(v_pred, v_target)

Compare line-for-line with DDPM’s loss: no β_t schedule, no ᾱ_t cumulative-product computation, no posterior-variance term, no learned variance head. Just a uniform t, a linear interpolation, and an MSE on the velocity.

Training dynamics are markedly different from GANs and noticeably calmer than DDPM. Loss decreases monotonically (no oscillation, no mode collapse), and the curve flattens cleanly when the model is done learning. The model used in this lesson reached final loss 0.198 from an initial 0.29 over 100,000 steps in ~1.7 hours on an RTX 5070Ti.

Concept 3 — Euler integration: turning a velocity field into samples

A trained model gives you v_θ(x, t), an approximation of the velocity field that flows from noise to data. Sampling is solving the ODE dx/dt = v_θ(x, t) with x(0) ~ N(0, I). Euler’s method is the textbook starting point:

x_{t + Δt} ≈ x_t + Δt · v_θ(x_t, t)

With Δt = 1/50, fifty steps cover the interval [0, 1]. The U-Net backbone (the same architecture DDPM uses; Ronneberger, Fischer and Brox’s original 2015 U-Net [8] is the family) handles the per-step velocity prediction; the integration loop wraps around it.

python · Euler ODE sampler
@torch.no_grad()
def sample_flow(model, n_samples, n_steps=50, shape=(3, 64, 64)):
    x = torch.randn(n_samples, *shape)
    dt = 1.0 / n_steps
    for i in range(n_steps):
        t = torch.full((n_samples,), i / n_steps)
        v = model(x, t)
        x = x + dt * v
    return x.clamp(-1, 1)

Higher-order integrators (midpoint, RK4, Heun) need fewer steps for the same numerical accuracy but cost two or four U-Net forward passes per step. For Flow Matching with n ≥ 20 Euler steps the gain is marginal; for n ≤ 10 it’s substantial. The library implementations (torchcfm, Meta’s flow-matching repo) ship with both options.

Fig. 3 A single sample's trajectory through the velocity field. Noise at `t=0` (top-left), fabric pattern at `t=1` (bottom-right), continuous transformation in between.

Exercises

EXECUTE I.

Generate 16 fabric patterns from the pre-trained model

flow_matching_fabrics_latest.pt — pre-trained 54 MB checkpoint flow_model.py — SimpleFlowUNet architecture exercise1_generate.py — 50-step Euler sampler

Place the .pt next to flow_model.py, then run the generator:

bash
python exercise1_generate.py

You should see exercise1_output.png — a 4×4 grid like Figure 1. Generation takes ~30 seconds on CPU, ~3 seconds on GPU.

Reflection

  1. Looking at the DDPM lesson’s generation output on the same fabric dataset, how many steps did that one need for comparable quality, and what’s the speed-up ratio here?
  2. The Quick Start sampler clamps to [-1, 1] only at the end. Why not clamp at every step (like some DDPM implementations do)?
  3. The model has 4.6M parameters — modest by 2024 generative-model standards. Why does a relatively small model work well for Flow Matching?
  4. If you increased n_steps from 50 to 500, would output quality improve noticeably? Why or why not?
MODIFY II.

Three explorations: step count, trajectory visualisation, velocity field

exercise2_explore.py — three explorations in one script
bash
python exercise2_explore.py

Goal 1 — Step count comparison. Sample the same starting noise at 5, 10, 20, 50, and 100 steps. The script saves a side-by-side grid.

Fig. 4 Quality as a function of step count. 5 steps is visibly degraded; 10 is usable; 20-50 is the sweet spot; 100 is wasted compute.

Goal 2 — Trajectory visualisation. Save intermediate x_t at evenly-spaced t values to see the noise → image transformation step by step (Figure 3).

Goal 3 — Velocity field magnitude. Visualise ||v_θ(x_t, t)|| at four different t values to see how the velocity field’s strength changes through the flow.

Fig. 5 Velocity-field magnitude at four `t` values. Large at early `t` (the field is pushing noise hard toward data); smaller at late `t` (the path is converging on a sample, less correction needed).
CREATE III.

Train Flow Matching from scratch (4-8 hours GPU)

exercise3_train.py — full training script (100k steps, ~1.7h on RTX 5070Ti)

This exercise reproduces the checkpoint behind the previous two. Configuration:

HyperparameterValue
Image size64×64 RGB
Batch size32
OptimiserAdamW
Learning rate1e-4
Training steps100,000
BackboneSimpleFlowUNet, 4.66M params
Dataset1,059 African fabric images (from Lesson 12.1.2)

The training run is genuinely long but unattended — start it, leave it, come back to a checkpoint. The dataset comes from Module 12.1.2’s preprocessing pipeline; reuse those preprocessed files directly.

bash
python exercise3_train.py --verify    # confirms 1,059 images found
python exercise3_train.py --train     # starts the 100k-step run

Summary

Common pitfalls

  • Too few sampling steps. Below ~10 Euler steps you’ll see blockiness and washed-out colour. The break-even with DDPM is around 20.
  • Clamping x to [-1, 1] during integration — the velocity field is defined on the entire real line; clamping mid-flow distorts the trajectory.
  • Confusing DDPM’s discrete t ∈ {0, ..., T-1} with Flow Matching’s continuous t ∈ [0, 1]. Feeding integer-valued t to a Flow Matching model produces noise.
  • Forgetting model.eval() before sampling. BatchNorm in training mode is the usual culprit when “the same trained model gives different results.”
  • Using Euler when a higher-order integrator would help — for n_steps < 10, switch to midpoint or RK4 to recover quality without retraining.

References

  1. [1] Lipman, Y., Chen, R. T. Q., Ben-Hamu, H., Nickel, M. & Le, M. (2023). Flow Matching for Generative Modeling. International Conference on Learning Representations (ICLR). arxiv:2210.02747
  2. [2] Liu, X., Gong, C. & Liu, Q. (2023). Flow Straight and Fast: Learning to Generate and Transfer Data with Rectified Flow. International Conference on Learning Representations (ICLR). arxiv:2209.03003
  3. [3] Esser, P., Kulal, S., Blattmann, A., Entezari, R., Müller, J., Saini, H., Levi, Y., Lorenz, D., Sauer, A., Boesel, F., Podell, D., Dockhorn, T., English, Z., Lacey, K., Goodwin, A., Marek, Y. & Rombach, R. (2024). Scaling Rectified Flow Transformers for High-Resolution Image Synthesis. arXiv preprint. arxiv:2403.03206
  4. [4] Ho, J., Jain, A. & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. Advances in Neural Information Processing Systems, 33, 6840–6851. arxiv:2006.11239
  5. [5] Song, J., Meng, C. & Ermon, S. (2021). Denoising Diffusion Implicit Models. International Conference on Learning Representations (ICLR). arxiv:2010.02502
  6. [6] Villani, C. (2009). Optimal Transport: Old and New. Springer. ISBN 978-3-540-71049-3.
  7. [7] Tong, A., Malkin, N., Huguet, G., Zhang, Y., Rector-Brooks, J., Fatras, K. & Bengio, Y. (2023). Improving and Generalizing Flow-Based Generative Models with Minibatch Optimal Transport. arXiv preprint. arxiv:2302.00482
  8. [8] Ronneberger, O., Fischer, P. & Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. Medical Image Computing and Computer-Assisted Intervention (MICCAI), 234–241. arxiv:1505.04597