Book Deep Read · Generative AI System Design Interview · Ch 8
Generative AI System Design Interview · Ch 8 · Ali Aminian, Hao Sheng & Alex Xu · 2024
You type "a cabin below a snowy mountain, dusk" into your phone, and a few seconds later out comes something you would use as a wallpaper. This chapter is about the machinery behind that. It answers one very concrete engineering question: how do you get a machine to paint a large, sharp picture without burning through all your compute?
The most effective way to teach a machine to paint turns out to be teaching it to ruin a painting first. Take a real photo, sprinkle a little static onto it, do that a thousand times, and you end up with a screen of pure snow. That ruining step is so simple it needs no learning at all — you just follow a fixed recipe. Then you train a network to do exactly one thing: look at a speckled picture and point out which parts are the speckles. Point them out, subtract them, and the picture gets a little cleaner.
Because it turns one hard job into a thousand easy ones. Asking a machine to paint a whole face from nothing in a single shot is hard — the older approach from the previous chapter, where two networks fight each other, often collapses mid-training. But asking it to "clean up a slightly fuzzy picture a little" is far easier, and you have unlimited practice material: grab any photo, sprinkle your own static on it, and the correct answer is exactly the static you just sprinkled. Nobody has to label anything.
So painting becomes this: start from a screen of pure snow, let the network wipe once, wipe again, wipe again — and after a few dozen wipes, a picture develops out of the noise.
Here is the catch. A 512-by-512 picture has a few hundred thousand dots in it, and each wipe has to push every one of those dots through the network — dozens to hundreds of times. Do all that at full size and your graphics card falls over.
The key move in this chapter is to switch sheets of paper. You separately train a pair of helpers — a compressor and a restorer. The compressor squeezes a photo down to a palm-sized sketch holding roughly one-fiftieth of the information; the restorer blows the sketch back up to full size. Then all those wipes happen on the little sketch, and you restore it just once at the end. The cost per step drops by an order of magnitude, and that is what made "a picture from a home graphics card" possible at all.
The price is real: the squeeze is lossy, and the finest strokes get wiped out — which is exactly why small text, fingers and dense grids come out mangled so often in these pictures.
Wipe more and the detail is steadier but it takes longer; wipe less and it is fast but the composition occasionally falls apart. So in a product this is a dial with a published price: fewer wipes for a quick preview, more wipes for the final take. That beats "swap in a bigger model" by a mile — it needs no retraining and you can turn it live.
Run "paint a picture" backwards into "wipe off the static step by step", and one hard job becomes a thousand easy ones; then move all that wiping onto a small sketch sheet, and the compute finally comes down. How many wipes is the dial you get to turn.
Want the mechanisms, notation and diagrams? → Switch to the deep read
This chapter turns the diffusion model into a system that can ship high-resolution images, and its central claim is counter-intuitive: the bottleneck at high resolution is not "can it paint fine detail" but "how large a canvas are you repeating those tens-to-hundreds of steps on". So the answer comes in two halves — first rewrite one-shot generation as a long chain of easy denoising steps (buying training stability), then move that whole chain onto a 64×64 latent (buying affordable compute). The mental model to walk away with: the design variables of a generative system are not "which model" but "how big the canvas, how many steps".
t means a blurrier image; at t=T it is essentially pure noise.E squeezes an image into a small block of numbers; decoder D restores it. The training goal is simply "restore it faithfully".This is the seventh of the ten case studies in Part II. It follows Ch 7 on face generation with GANs — where a generator and a discriminator fight, training is unstable and mode collapse looms; here the adversarial game is replaced by a plain regression task. It sets up Ch 9 on text-to-image, which bolts text conditioning and classifier-free guidance onto exactly this skeleton. Real systems it maps to: Stable Diffusion, DALL·E 2, Imagen, Midjourney — the whole current generation of image products.
Two pains: one about unstable training, one about unaffordable compute.
The instability is inherited from the previous chapter. A GAN optimises a game between two networks; whenever one pulls ahead the other's gradient goes flat, the balance gets harder as resolution climbs, and mode collapse (it only ever paints the same few faces) is always in reach. The engineering consequence is that training becomes unpredictable — same code, same data, a different random seed, and the run is wasted. For a team that has to schedule and reproduce work, that is fatal.
The compute problem belongs to this chapter. A 512×512 RGB image is 512×512×3 = 786,432 numbers. Diffusion runs the denoiser T times, and the original DDPM used T=1000 — a thousand full-size forward passes per image. Worse, self-attention inside the U-Net grows with the square of the number of positions (O(n²) — double the positions and the cost quadruples): 512×512 is over 262,000 positions, so global attention at that scale simply does not work in practice.
What happens if you do not solve it: you are stuck training 64×64 models and propping them up with a stack of super-resolution stages — each trained separately, deployed separately, each compounding the previous stage's errors. Image generation stays a game only for companies with large datacentres.
A diffusion model defines two processes running in opposite directions. Forward: take a real image x₀ and, following a fixed recipe, mix in Gaussian noise step by step until after T steps it is pure noise x_T. This half has no learnable parameters at all — it is an addition formula. Reverse: start from pure noise and subtract the noise back out, step by step, until an image develops. That half is what the network learns.
Why is this rewrite the bedrock of the chapter? Because it makes the supervision signal carry its own answer: pick a random image, pick a random timestep t, mix in the noise yourself — and what the network ought to output (the blob of noise you just mixed in) is known. No human labels, and no adversary network acting as judge. The reason GANs are hard to train is precisely that their "right answer" comes from another network that is also moving; diffusion swaps that moving target for a fixed one.
The training objective is so plain it hardly looks like a generative model: L = ‖ε − ε_θ(x_t, t)‖². In words — the noise ε that was genuinely mixed in, versus the ε_θ the network guesses from the noisy image x_t; the gap between them is the loss. An ordinary regression problem, with stable gradients, reproducible runs and a loss curve you can schedule against. That is its engineering advantage over a GAN.
The backbone is a U-Net: downsample to capture large structure, upsample to restore detail, with skip connections handing high-frequency information straight across at matching scales. Because input and output share a shape, it fits per-pixel prediction naturally. The timestep t is embedded into a vector and injected into every block, which tells one set of weights which step this is, and therefore how hard to wipe. That matters: all steps share a single set of parameters, or the model size would be multiplied by a thousand.
This is the real turn of the chapter. You train a separate autoencoder first: encoder E compresses 512×512×3 down to 64×64×4, decoder D restores it. The information drops from 786,432 numbers to 16,384 — a ratio of 48:1. Then the entire diffusion process runs only on the latent, and you decode exactly once at the end.
Why does that work? Most of the bits in a natural image are spent on high-frequency texture the eye barely registers, and a deterministic decoder can reproduce that well enough. The diffusion network's capacity should go into semantics and composition — what is in the picture and where it sits — not into reproducing skin pores. It also settles the attention bill: positions drop from over 262,000 to 4,096, and a squared cost falls by three orders of magnitude, which is what makes global attention viable again.
The cost has to be stated plainly: the encode/decode round trip is lossy, and the more aggressive the compression (downsampling factor f=16, f=32), the more small structure dissolves. Small text, human hands and dense regular grids are the worst hit — a fair share of this generation's signature artifacts are not the diffusion network painting wrong, but the decoder failing to bring detail back.
Latent space makes 512×512 practical, but products want 1024×1024 and beyond. Two roads.
Road A · cascaded: generate at low resolution, then chain one or two diffusion super-resolution stages to enlarge (say 64 → 256 → 1024). Each stage is small, can be trained and swapped independently, and the low-resolution stage gets to spend its compute on composition. The cost is that errors propagate down the chain — if stage one paints a bad hand, the next two only render that hand more sharply — total latency is the sum of every stage's steps, and you now operate three models.
Road B · enlarge the latent: keep the pipeline and move the latent from 64×64 to 128×128 (which decodes to 1024×1024). One pipeline, end to end, no seams between stages. The cost is that VRAM and per-step time scale with latent area — double the side and the area quadruples — and the model usually needs another fine-tuning round at the target resolution, or you get duplicated composition (the same subject painted twice).
In practice the two get mixed: the main pipeline takes road B to 1024, with one lightweight super-resolution stage bolted on to reach 2048 and above.
Original DDPM sampling walks all T=1000 steps. DDIM rewrites sampling as a deterministic process that can skip ahead, reaching comparable quality in 20–50 steps — and note that the model is not retrained; only the sampler changes.
What this means for system design is underrated: step count is the only parameter in the whole pipeline you can set per request, live, and trade almost linearly for latency and cost. Same weights: 20 steps for a preview, 50 for the final take, a 2.5× cost difference, and no second model to deploy. Swapping models, by contrast, means reloading weights and re-warming — minutes, not milliseconds.
The other half is batching. Every diffusion step is the same large matrix operation, so packing the same step from several requests into one GPU call saturates utilisation and lifts throughput considerably — but forming a batch means waiting, which pushes single-request p99 up. The usual answer is a batching window of a few tens of milliseconds: take a smaller batch rather than let the tail get away from you.
This chapter really comes down to two choices: which canvas you compute on, and how many steps you take. The first fixes your cost ceiling and your quality ceiling; the second fixes per-request latency.
Table 1 · Four generation routes, and when each one is right
| Route | Training stability | Cost / latency per image | Quality ceiling | When to pick it |
|---|---|---|---|---|
| GAN | Poor: an adversarial game that is hard to balance; mode collapse | Lowest: one forward pass, milliseconds | Very high in a narrow domain (faces); struggles open-domain | Narrow domain, hard latency budget, on-device (live face swap, avatar styling) |
| Pixel-space diffusion | Good: a plain regression objective | Highest: every step over 786k numbers | High, but compute caps the resolution | Largely research and low-res baselines now; superseded in production |
| Latent diffusion | Good: same objective as pixel-space | Middling: roughly 1/48 of pixel space per step | High; fine detail is bounded by the decoder | The default: open-domain text-to-image / image-to-image, deployable on one GPU |
| Cascaded diffusion | Good, but 2–3 models to train | Mid-high: total steps are the sum across stages | Very high; the upscaling stage restores sharpness | You need extreme resolution and accept multi-model ops plus error propagation |
Table 2 · Choosing a step count (same weights, switchable in production)
| Step band | Relative cost | Typical problem | Where it fits |
|---|---|---|---|
| 1–4 steps (distilled) | ~1/25 | Visible loss of detail and composition; needs purpose-distilled weights | Live preview, generate-as-you-type, on-device inference |
| 20–30 steps | ~1/2 | Occasional unstable composition; complex scenes drop elements | The default for interactive generation — the image a user is waiting on |
| 50+ steps | 1× baseline | Returns fall off fast; extra steps are mostly wasted money | Final renders, offline batch generation, detail-sensitive commercial assets |
One more choice that tends to get skipped: the downsampling factor f. At f=4 the decoder is more faithful but the latent is larger (4× the compute); f=8 is the industry's balance point; at f=16 and above you save compute but detail collapses badly. Picking f is drawing a line between "how much the decoder can restore" and "how long diffusion takes" — and once set it is very hard to change, because it fixes the input shape of every diffusion weight you train.
This chapter is not about the internals of one model; it is about the skeleton every image product built after 2022 shares: an autoencoder that fixes the canvas, a U-Net that denoises, a sampler that fixes the step count. Stable Diffusion, DALL·E 2, Imagen and Midjourney differ mainly in data, in how conditioning is injected, and in their resolution strategy — those three bones are the same. Get the skeleton straight and Ch 9 (text-to-image), Ch 10 (personalisation) and Ch 11 (text-to-video) all read as things bolted onto it.
In interviews the follow-up is almost always about cost: given a million image requests a day at 1024×1024, how do you set step count, how do you batch, when do you switch to distilled weights, where in the pipeline does super-resolution belong. Getting "steps × latent area × batch size" right to the correct order of magnitude is worth far more than reciting the DDPM derivation.
guided-diffusion repository opens with "This is the codebase for Diffusion Models Beat GANs on Image Synthesis" and publishes FID at each resolution: 2.07 at 64×64, 4.59 at 256×256, 7.72 at 512×512 (best 3.85 when upsampled). These are the numbers most often cited for diffusion displacing GANs as the mainstream route.OpenAI · guided-diffusion official repository (2021) · github.com/openai/guided-diffusion512×512 images from a subset of LAION-5B, and states plainly that it "runs on a GPU with at least 10GB VRAM". That 10GB figure is this chapter's latent-space design cashed out in the real world.CompVis · Stable Diffusion official repository (2022) · github.com/CompVis/stable-diffusionf or change the decoder.1 · The central claim: the bottleneck at high resolution is not fine detail but how large a canvas you repeat tens-to-hundreds of steps on.
2 · Diffusion rewrites one-shot generation as step-by-step denoising. The forward pass has no learnable parameters, so training answers are free and no adversary is needed — that is the root of its stability over GANs.
3 · The network learns a subtraction (predict the noise that was added); the backbone is a U-Net, the timestep t is injected as an embedding, and all steps share one set of weights.
4 · Latent space is the turn: 512×512×3 compressed to 64×64×4, 48:1; attention positions fall from over 262,000 to 4,096, collapsing a squared cost.
5 · The price is lossiness: small text, fingers and dense texture do not come back — those artifacts usually live in the decoder, not the denoiser.
6 · Two roads to 1024: cascaded super-resolution (divide and conquer, but errors propagate) vs a bigger latent canvas (end-to-end consistent, but VRAM and per-step time scale with area).
7 · Step count is the only dial you can turn live for near-linear latency: 20 steps for preview, 50 for final, same weights; batching lifts throughput but raises p99.
8 · Evidence anchors: OpenAI's ImageNet FID (4.59 at 256×256) showed diffusion beating GANs; Stable Diffusion's f=8 latent plus an 860M U-Net brought generation onto a 10GB VRAM consumer card.