Flow-Based Generative Models · UFRJ · 2026.2
Every network we train from here on has the same shape:
\[ f_\theta \colon \; \underbrace{(B, C, H, W)}_{x} \; \times \; \underbrace{(B,)}_{t} \; \longrightarrow \; (B, C, H, W) \]
In U3 this object is the learned velocity field \(u_t^\theta(x)\). Today it is just “the network”.
Two questions, and the session is their answer: what goes inside, and how does \(t\) get in?
t is a (B,) tensor of floats in \([0,1]\). \(t=0\) is noise, \(t=1\) is data.
Not an integer index. Not a step count. Code needing another time variable converts outside the network.
The diffusion literature counts time backwards, in discrete steps. The moment a network accepts an integer timestep, that convention is in the codebase — and every later translation becomes a silent sign error.
Three answers, all in the evaluation block at the end:
Question of the day: what are the parts, and what is the ruler?
A convolution mixes a pixel with its neighbours. Its reach grows only by stacking layers.
Sometimes a position needs information from a distant position — and which position depends on the content, not on the geometry.
Attention is the mechanism that lets a position ask for what it needs.
Each of \(n\) positions produces three vectors, by linear maps of its own representation:
Stack them as the rows of \(Q\), \(K\), \(V\).
\[ \operatorname{Attn}(Q, K, V) = \operatorname{softmax}\!\left( \frac{Q K^{\top}}{\sqrt{d_k}} \right) V \] Rows of \(A = \operatorname{softmax}(QK^\top/\sqrt{d_k})\) are non-negative and sum to one.
Why \(\sqrt{d_k}\)? With unit-variance entries, \(q \cdot k\) has variance \(d_k\). Unscaled, the scores grow with the width, the softmax saturates, and the gradient dies.
Same variance-propagation argument as U0.T2’s initialization rule.
Every output is a convex combination of the value vectors. Attention cannot invent content — only redistribute it.
One attention operation expresses one notion of relevance. Run \(h\) of them in parallel, on projections into lower-dimensional subspaces:
\[ \begin{aligned} \operatorname{head}_m &= \operatorname{Attn}(X W^Q_m, X W^K_m, X W^V_m) \\ \operatorname{MHA}(X) &= [\operatorname{head}_1, \dots, \operatorname{head}_h] \, W^O \end{aligned} \]
Each head can specialize. The cost is unchanged: per-head width is the full width divided by \(h\).
For any permutation matrix \(P\): \[ \operatorname{Attn}(PQ, PK, PV) = P \operatorname{Attn}(Q, K, V) \]
Permute the positions, and the outputs permute. Nothing else changes.
Consequence: anything the network must know — position, time — has to be injected into the representation. That is the conditioning block.
\(QK^\top\) has \(n^2\) entries: \(O(n^2 d_k)\) time, \(O(n^2)\) memory.
For an image as a sequence of pixels, \(n = HW\):
| Image | \(n\) | entries per head |
|---|---|---|
| \(32 \times 32\) | \(1\,024\) | \(\approx 10^6\) |
| \(256 \times 256\) | \(65\,536\) | \(\approx 4 \times 10^9\) |
So: attention only where the resolution is low. Or first reduce \(n\) — the transformer route.
The U6 seminar papers are transformer-native and assume all of the above without a word.
The architecture in the preview block is nothing but this block, repeated.
Today is the only time attention is taught. After this, it is vocabulary.
The signature asks for \((B,C,H,W) \to (B,C,H,W)\). That is demanding:
Full resolution throughout: has the detail, needs impractical depth for the context (U0.T2’s receptive-field computation).
Downsample: gets the context, destroys the detail.
Skip connections concatenate on the channel axis. Not addition: the decoder receives the encoder’s features as extra channels and learns what to do with them.
The bottleneck representation is small by construction.
Detail that is needed to reconstruct a pixel, and is not predictable from context, cannot pass through it — not because training failed, but because there are fewer numbers than the detail requires.
The skips route that detail around the bottleneck.
So a U-Net is both ideas from U0.T2 at once: multiscale, and residual.
Nothing new inside a ResBlock — U0.T2’s defaults, unchanged:
This architecture, plus the conditioning of the next block, is unet-skeleton — built in the next lab session, trained for the rest of the course.
We do not train one network per time.
One network, all \(t \in [0,1]\). So \(t\) is an input: one continuous scalar per example, which must influence every layer at every resolution.
Two decisions:
A single input coordinate enters the first layer through a single column of weights.
To make its effect on the computation detailed, every downstream layer must reconstruct that dependence from one nearly-linear signal.
The network can do it. It wastes capacity doing it.
Remedy: expand \(t\) over a geometric ladder of frequencies, so coarse and fine differences in \(t\) are directly available as separate coordinates.
For t a (B,) float tensor in \([0,1]\), width \(d_{\text{emb}}\), \(\mathrm{half} = d_{\text{emb}}/2\): \[
\begin{aligned}
\omega_k &= (10^4)^{-k/\mathrm{half}}, \quad k = 0, \dots, \mathrm{half}-1 \\
\mathrm{emb}(t) &= \big[\sin(\omega_k \cdot 1000\,t) \,;\, \cos(\omega_k \cdot 1000\,t)\big]_k
\end{aligned}
\]
The factor \(1000\) is the only rescale. Reference implementations were calibrated to a thousand-step schedule; matching them means a library cross-check catches real bugs, not conventions.
Outside this line, \(t\) is a float in \([0,1]\). The FM arrow is untouched.
Slowest rung: monotone over the whole interval — “early or late?”. Fastest: \(\approx 159\) cycles — resolves \(\Delta t \sim 10^{-3}\).
Concatenating the embedding is possible and wasteful. Modulate instead:
With \(\hat h\) the output of the block’s normalization layer: \[ h \leftarrow \big(1 + \gamma(t)\big) \odot \hat h + \beta(t) \] \(\gamma(t), \beta(t) \in \mathbb{R}^{C}\): one pair per channel, from a small MLP on \(\mathrm{emb}(t)\).
AdaGN is this, with GroupNorm as the normalization. Same operation, different name.
The model should also depend on a class label \(y\)? Embed \(y\) to the same width and add:
\[ c = \mathrm{emb}(t) + \mathrm{emb}(y) \]
Feed \(c\) through the same projection, to the same \((\gamma, \beta)\). Nothing else changes.
Summing keeps the width fixed, so the modulation path does not grow with the number of signals.
A class-conditional model built this way returns in U3.
Cited by the next lab session, by every U3 lab, and by U5.T1.
Every velocity field, every score network, every diffusion model you will read in this course uses exactly this block.
It is the single most transferable slide of the bootcamp.
Replace the U-Net entirely. Cut the input into \(p \times p\) patches, embed each linearly into a token, add sine–cosine positions:
\[ T = (I/p)^2 \ \text{tokens of width } d \]
Halving \(p\) quadruples \(T\) — and by the cost slide, at least quadruples the compute.
From there: a stack of standard transformer blocks.
The \(\alpha\) gate sits immediately before each residual connection, and its projection is initialized to zero.
So at initialization every block is exactly the identity, and the network starts as a pass-through.
Reported effect: adaLN-zero reaches roughly half the FID of in-context conditioning at 400k iterations, at negligible extra cost (Peebles and Xie 2023).
How the conditioning enters changes model quality substantially.
Across model sizes from 12 to 28 layers, and across patch sizes, larger transformer backbones give better samples — consistently, without special handling.
Why, and at what cost: U5.T1. Preview only.
Three candidates, and the section is organized around what each one misses:
No single number does the job. That is not a gap in the course; it is the state of the field.
\[ \mathrm{BPD}(\theta) = -\frac{1}{d \log 2} \; \mathbb{E}_{x \sim q}\big[\log p_\theta(x)\big] \] Lower is better. It is a compression rate: bits per dimension to encode a test example under a code built from \(p_\theta\).
BPD measures coverage. Assign low density where the data lives and \(-\log p_\theta(x)\) punishes you without limit.
That is a real virtue — and it is why likelihood is still the right score for density estimation.
Take a model that samples from a good generator with probability \(1-\varepsilon\), and from pure noise with probability \(\varepsilon\).
Its log-likelihood is within \(\log\frac{1}{1-\varepsilon}\) of the good model’s. Divided by \(d\), that is invisible for image-sized \(d\).
Its samples are garbage a fraction \(\varepsilon\) of the time.
The damage hides in directions the average does not weight (Theis, Oord, and Bethge 2016).
Re-show the taxonomy from U0.T3 — now read it as an evaluation table:
| Family | Exact likelihood? | Then evaluate with |
|---|---|---|
| Autoregressive | yes | BPD, and samples |
| Normalizing flows | yes | BPD, and samples |
| VAE | bound only (ELBO) | ELBO, FID, panels |
| GAN | no density at all | FID, precision / recall |
Any protocol that requires likelihood cannot compare across the columns.
\[ \begin{aligned} \mathrm{FID} =\;& \lVert \mu_r - \mu_g \rVert^2 \\ &+ \operatorname{Tr}\!\left(\Sigma_r + \Sigma_g - 2(\Sigma_r \Sigma_g)^{1/2}\right) \end{aligned} \] The Gaussian fit is an assumption: only the first two moments are compared.
Comparable only at identical extractor, preprocessing and sample count. An FID is a property of a model and a protocol.
Biased upward; the bias falls like \(1/n\). The FID of a set against its own distribution is not zero.
The extractor decides what “similar” means. Inception features are ImageNet-shaped. This course pins a small MNIST classifier for MNIST, and reserves Inception for CIFAR and beyond.
Left: both sets from one distribution, true FID \(= 0\). Measured \(4.7\) at \(n=50\); \(0.028\) at \(n=10^4\); slope \(-1.03\).
Two models: true FID \(0.12\) and \(0.48\). B is worse, by a gap of \(0.36\).
The bias is systematic, so at equal \(n\) it lands on both and cancels.
The ranking inverts exactly when the difference in bias exceeds the true gap. (\(200\) vs \(2000\): difference \(0.86 > 0.36\), always inverts. \(500\) vs \(5000\): \(0.34 < 0.36\), inverts \(17.5\%\) of the time.)
FID is one number. A model can be bad in two different ways.
Approximate each set’s support by a union of balls — around each point, out to its \(k\)-th nearest neighbour in that set. Then:
Mode collapse (GANs): high precision, low recall. Over-dispersion: the reverse.
U3.L2 measures the number of function evaluations against FID.
U3.L3 sweeps a conditioning strength against FID — where the fidelity-versus-coverage trade becomes something you watch move.
The harness you build in the next lab session is the instrument.
Precision and recall stay concept-only: vocabulary for reading papers, not code in the harness.
1. The time-conditioned U-Net — the anatomy slide assembled, conditioning wired into every block. Interface frozen that session:
2. The evaluation harness — FID and BPD implemented, the caveats enforced by code. Calibrated before it is trusted, then pointed at your U0.L3 VAE.
Both are PS0 components.
| Piece | From | Status |
|---|---|---|
training-loop-template |
U0.L1 | built |
hygiene-stack |
U0.L2 | built |
vae-mnist-scratch |
U0.L3 | built |
unet-skeleton |
U0.L4 | next session |
eval-harness |
U0.L4 | next session |
Exit line: you now know what the parts are, and what the ruler is. Next session you build both.