The vector field is a neural network. This lecture builds that network for real images — time conditioning, U-Nets, Diffusion Transformers, and working inside a VAE's latent space, the recipe behind Stable Diffusion 3 and Meta's Movie Gen.
Recall the spine of the whole course. A flow or diffusion model is an ODE (or SDE) whose velocity arrows — the vector field uθt(x) — are produced by a neural network with weights θ. To sample, you start at a Gaussian point and Euler-step along those arrows from time t = 0 to t = 1. Lectures 2 and 3 told us how to train the arrows (flow matching, score matching) and how to steer them with a prompt (guidance).
But all of that was demonstrated on toy 2-D Gaussians, where the vector field was a three-layer MLP. The elephant in the room: a real image is not a 2-D point. A 256×256 RGB photo is a vector with d = 3·256·256 ≈ 196,608 numbers; a 1024×1024 photo has d ≈ 3 million. An MLP that maps a 3-million-dimensional input to a 3-million-dimensional output is hopeless — the first weight matrix alone would have trillions of entries.
Two big ideas carry the lecture, mapping onto the two sections of the original MIT slides:
We have a neural network now (finally), but we are not training it from scratch here — that's the loss from Lectures 2–3. Today is pure plumbing: what goes in, what comes out, what shape, and why each design choice was made. You'll patchify a toy image, build a Fourier time embedding, and round-trip an image through a compressing autoencoder — all in your browser.
Before designing any architecture, pin down the contract the network must satisfy. Get the types and shapes exactly right and the rest is just choosing how to fill the box in the middle.
The "same shape in, same shape out" requirement is the single most important structural fact. The velocity is a vector in the same space as the image, because Euler's update is x ← x + h·velocity — you add the velocity to the image, so they must align coordinate-for-coordinate.
There's a subtle asymmetry worth naming, straight from the slides. In ordinary supervised learning — say image classification — the input is a big image but the output is tiny (one class number). So a classifier can keep narrowing: big image in, small label out, and never pay the cost of producing a full-sized output. Our problem is different.
Two more inputs need embedding before the network can use them, because they don't arrive in image shape:
The vector field changes with time: at t near 0 the input is almost pure noise and the network should predict the rough overall structure; at t near 1 it is almost a finished image and the network should refine fine details. So the network must know what t is. The problem: t is a single number, drowned out by the millions of pixel numbers.
The trick — the same one used for positional encodings in transformers — is to map the scalar t to a vector of sines and cosines at many different frequencies. With embedding dimension d (even), pick d/2 frequencies w1, …, wd/2 and form:
Read it carefully. Each component is a wave; a high-frequency wave wiggles many times as t sweeps 0→1, a low-frequency wave barely moves. Together they form a fingerprint of t that is easy for the network to read. Two nearby times produce nearly identical fingerprints (smoothness); two distant times produce very different fingerprints (distinguishability).
We want both slow waves (to track the coarse phase of generation) and fast waves (to resolve nearby times finely). So we space the frequencies geometrically — equally spaced in log-scale — between a minimum and a maximum:
So w1 = wmin (slowest), wd/2 = wmax (fastest), and each step multiplies the frequency by the same ratio. A geometric ladder covers a huge dynamic range — from waves that complete a fraction of a cycle over [0,1] to waves that complete hundreds — using only d/2 entries.
Worked number. Take a tiny embedding with d = 4, so d/2 = 2 frequencies. Let wmin = 1 and wmax = 4. Then w1 = 1 and w2 = 1·(4/1)(2−1)/(2−1) = 4. At t = 0.25: the angles are 2π·1·0.25 = π/2 and 2π·4·0.25 = 2π. So cos(π/2) = 0, cos(2π) = 1, sin(π/2) = 1, sin(2π) = 0. The raw embedding is [0, 1, 1, 0]; its length is √(0+1+1+0) = √2, so after the √(2/4) = √(1/2) factor it becomes [0, √(1/2), √(1/2), 0], a unit vector. One number, t = 0.25, became a four-dimensional unit signal the network can act on.
A heatmap: each row is one embedding dimension (a fixed frequency); the horizontal axis is time t from 0 to 1; warm/blue is the +1/−1 value of that wave. Low rows are slow waves, high rows are fast waves — the geometric frequency ladder. Drag a slider to add more frequencies or stretch the max frequency; the vertical line marks the current t and the side strip shows the embedding vector at that t (this is what the network actually receives).
Now build the embedding yourself in pure numpy — and prove that distinct times map to distinct, nearly-orthogonal fingerprints while the embedding stays smooth in t.
Now the architecture itself. The earliest and longest-reigning choice for image diffusion is the U-Net — a convolutional neural network whose input and output both have the shape of an image. That property is exactly what we need: feed in a noisy image, get out a velocity image of the same size.
A U-Net is an encoder–decoder with a twist. The encoder repeatedly shrinks the spatial size while growing the number of channels; the decoder does the reverse. Follow one 256×256 RGB image (the slides' example) through it:
Notice: as resolution drops (256 → 32), the channel count rises (3 → 512). Coarse spatial information gets traded for richer per-location feature descriptions. The lowest-resolution block in the middle — the slides whimsically call it the midcoder — processes the most abstract, global representation.
The time embedding from Chapter 2 and the condition embedding are injected into every block — typically by adding them (after a small projection) to the feature maps, so each convolutional stage knows the current time and prompt. This is the same idea we'll see formalized as AdaLN in the DiT next chapter.
The encoder arm (left) shrinks spatial size and grows channels; the bottleneck sits at the bottom; the decoder arm (right) mirrors it back to image size. Dashed teal wires are the skip connections carrying fine detail across the U. Drag the depth slider to add levels — deeper U-Nets reach a smaller, more abstract bottleneck. Each box shows its (channels) × (height × width).
The U-Net's competitor — now the dominant architecture for state-of-the-art models — is the Diffusion Transformer (DiT) (Peebles & Xie, 2023). Its bet: instead of convolutions, treat an image like a sentence of patches and run a standard transformer over it. Transformers scale astonishingly well with data and compute, and DiTs inherit that.
A transformer eats a sequence of vectors. An image is a grid of pixels. The bridge, borrowed from the Vision Transformer (ViT), is patchification: chop the image into a grid of small P×P patches and flatten each patch into one vector ("token").
Here N is the number of patches (the sequence length, often written L), C is the input channels, P is the patch size, and C′ = C·P² is the number of numbers in one flattened patch. Then a learned linear map (a weight matrix W) projects each C′-vector to the transformer's hidden dimension d, giving the patch embeddings PatchEmb(x) ∈ RN×d.
Now run L transformer layers (DiT blocks) over the token sequence. The engine is scaled dot-product attention: every token forms a query, key, and value; the queries and keys decide how much each token attends to every other; the output mixes the values accordingly. Crucially, attention is global — any patch can directly talk to any other patch in one layer, unlike a convolution's small local window.
A class-conditional DiT (like the one in the course lab) is simpler: it drops the cross-attention and folds the class label into the same AdaLN conditioning as time, by adding the class embedding to the time embedding. Text-conditional models keep the cross-attention to attend to the full prompt sequence.
After the L blocks, the token sequence is still N×d. We need a velocity image of shape C×H×W. So a final linear map sends each token back to C·P² numbers, and we reassemble the patches into the full grid — the exact inverse of patchify. This depatchify step restores the image shape, satisfying our contract.
An image is sliced into a grid of patches (left); each becomes a token in a sequence; the time embedding and prompt tokens join in; L transformer blocks mix everything via attention; then depatchify reassembles a velocity image (right). Drag patch P to see the token count change live (N = (H/P)²). Smaller patches → more, finer tokens → more compute.
Build the patchify/depatchify pair yourself and prove the round-trip is exactly lossless — the foundation the whole DiT rests on.
We have a network that takes images and returns images. But running diffusion directly in pixel space is brutally expensive, and the reason is worth feeling in your bones.
The slides name three concrete problems with diffusing in raw pixel space:
Here is the escape. The set of realistic images is a tiny, structured sliver of the full pixel space — almost every random pixel array is meaningless static. So real images plausibly lie near a much lower-dimensional manifold. If we can find a faithful map from the 3-million-dimensional pixel space down to, say, a few thousand numbers and back, then we can run the entire expensive diffusion process in the small space and only pay the pixel cost once to decode at the end.
The next two chapters build the compressor (an autoencoder) and then plug it into the pipeline (latent diffusion).
The orange bar is the per-sample work for pixel-space diffusion (dimension × number of Euler steps); the teal bar is the same after compressing into a latent. Drag the resolution and the compression factor to feel how the latent bar stays manageable while the pixel bar explodes. (Bars are on a log scale.)
The compression tool is an autoencoder: a pair of networks that squeeze an image down and then rebuild it.
How do we train them? Demand that decoding an encoding gives back the original. That's the reconstruction loss — the squared error between the input and its round-trip:
Minimize this and the autoencoder learns to throw away only the information it can afford to lose — the redundant, perceptually unimportant detail — while keeping enough to rebuild a faithful image.
A plain autoencoder compresses, but it has a fatal flaw for our purposes. Remember the plan: after compressing, we want to learn the distribution of latents and sample new ones with a diffusion model. But the reconstruction loss says nothing about what the distribution of latents looks like.
A variational autoencoder (VAE) upgrades the autoencoder so the latent distribution is regularized toward a simple, friendly shape — a standard Gaussian. Two changes:
The full VAE loss balances the two with a weight β ≥ 0:
You'll build the simplest faithful version of this idea next — a linear encoder/decoder using the top principal directions of the data — and prove it both compresses (small latent) and reconstructs (low error vs. a trivial baseline).
Now snap the pieces together. A latent diffusion model (LDM) — the architecture of Stable Diffusion and essentially every modern image/video generator — runs the entire flow/diffusion process inside the VAE's latent space, and only touches pixels at the very start and end.
Two practical notes the lecture flags. First, you must train the autoencoder before training the diffusion model — the latent space has to exist first. Second, at decode time you take the decoder's mean output rather than a random sample, to avoid injecting noise artifacts into the final image. A well-trained autoencoder acts like a perceptual filter: it strips away high-frequency, semantically-meaningless detail so the diffusion model can spend all its capacity on the structure that matters.
"Virtually all AI-generated images or videos that you see," the slides say, "are generated in latent spaces." Memory would blow up otherwise, and the latent lets the model focus on the parts that matter.
Let's see every piece assemble at scale in two state-of-the-art systems. Notice how little is new — they are this lecture's components, made enormous.
Read that list against the course: a flow-matching objective (L2), guidance (L3), a DiT in a VAE latent with a time embedding (L4). The MM-DiT's only real novelty is extending the conditioning from a single class label to a full sequence of text tokens via cross-attention — exactly the "text via cross-attention" branch of the DiT block from Chapter 4.
Stepping back: both flagship systems are the same five Lego bricks — flow matching (L2), guidance (L3), time embedding + DiT + VAE latent (L4) — scaled to billions of parameters and thousands of GPUs. You now understand every box in their architecture diagrams.
A side-by-side of the two systems' shared pipeline (encode → latent diffusion via DiT → decode) and where they differ (image vs. video; text encoders; parameter count). Tap a stage to highlight it across both columns — the shared bricks light up in teal, the video-only additions in orange.
The payoff: a live image studio combining the two structural moves of the lecture. Pick a toy image, then watch it get patchified into tokens (the DiT's first step) and, separately, compressed into a small latent and reconstructed (the VAE's job). Play with the knobs and feel the trade-offs you've learned.
Left: the original image overlaid with the patch grid (the DiT's tokens); the count updates with the patch slider. Middle: the same image compressed to a low-rank latent of size k and decoded back — this is the VAE's compress-and-reconstruct, done here with the top-k principal directions of a simple image family. Right: the per-pixel reconstruction error. Drag latent k: small k = heavy compression = blurrier reconstruction (more error); large k = faithful but larger latent. Drag patch P to change the token count. Change the image to see structure-dependent behavior.
This is the literal substrate every modern generator runs on: an image becomes a small latent (left/middle), a DiT (operating on patch tokens of that latent) runs the flow in the small space, and a single decode at the end returns a full image. The simulation is the test — no quiz here.
You assembled the real machine. The vector field is no longer a formula or a toy MLP — it is a U-Net or a Diffusion Transformer, conditioned on an embedded time and prompt, running inside a VAE's compressed latent space. That is the architecture behind every image and video generator in production.