TAI BUI
Back to curriculum
BuildPython

Diffusion Transformers & Rectified Flow

The U-Net was never the secret to diffusion. Swap it for a transformer, replace the noise schedule with a straight-line flow, and you get SD3, FLUX, and every 2026 text-to-image model.

TIME: ~75 minutesPREREQUISITES: Phase 4 Lesson 10 (Diffusion DDPM), Phase 4 Lesson 14 (ViT), Phase 7 Lesson 02 (Self-Attention)

Learning Objectives

  • Trace the evolution from U-Net DDPM (Lesson 10) to Diffusion Transformer (DiT), MMDiT (SD3), and single+double-stream DiT (FLUX)
  • Explain rectified flow: why a straight-line trajectory between noise and data lets models sample in 20 steps instead of 1000
  • Implement a tiny DiT block and a rectified-flow training loop, both under 100 lines
  • Distinguish model variants (SD3, FLUX.1-dev, FLUX.1-schnell, Z-Image, Qwen-Image) by architecture, parameter count, and licensing

The Problem

Lesson 10 built a DDPM with a U-Net denoiser. That recipe dominated 2020 to 2023: U-Net, beta schedule, noise-prediction loss. It produced Stable Diffusion 1.5, 2.1, and DALL-E 2.

Every 2026 state-of-the-art text-to-image model has moved past it. Stable Diffusion 3, FLUX, SD4, Z-Image, Qwen-Image, and Hunyuan-Image: none of them use a U-Net. They use Diffusion Transformers (DiT) instead. SD3 and FLUX also swap the DDPM noise schedule for rectified flow, which straightens the path from noise to data and enables 1-4 step inference with consistency or distilled variants.

The shift matters because it's the reason diffusion-based image generation got controllable, prompt-accurate (SD3 and SD4 finally solved text rendering), and fast enough to ship. Understand DiT and rectified flow, and you understand the 2026 generative-image stack.

Warm-up0/3
1. A diffusion denoiser could be built on a U-Net or on a Vision Transformer (ViT). Which pairing correctly describes each one's basic building block?Warm-up
Two boxes labelled U-Net and ViT, each above its own dashed question-mark box reading built from equals question mark.
Only the two names from the stem; what each is built from is left blank.
2. In a Vision Transformer, how does an image become the sequence of tokens that self-attention works on?Warm-up
A plain picture icon, an arrow to a dashed question mark box reading becomes question mark, and an arrow onward to three small boxes each labelled tok.
One image and the fact that a token sequence comes out the other end; the operation between them is left blank.
3. A DDPM trained with the standard noise-prediction loss has a network that outputs what, given a noisy image and a timestep?Warm-up
A box x_t, noisy image, and a box t, timestep, both feeding into a box labelled model of x_t and t, with an arrow out to a small dashed question mark box.
The network's two inputs only; its output is left blank.

The Concept

From U-Net to transformer

Mermaid diagram

Four architectures mark this shift, each building on the one before it:

  • DiT (Peebles & Xie, 2023): replaces the U-Net with a ViT-like transformer running on latent patches, conditioned via adaptive layer norm (AdaLN).
  • MMDiT (SD3, Esser et al., 2024): two streams with separate weights for text and image tokens that share a joint attention layer.
  • FLUX (Black Forest Labs, 2024): the first N blocks run double-stream like SD3, then later blocks concatenate and share weights (single-stream) for efficiency at greater depth.
  • Z-Image (2025): an efficient single-stream DiT at 6B parameters that challenges the "scale at all costs" assumption.

Rectified flow in one paragraph

DDPM defines the forward process as a noisy SDE (stochastic differential equation: a random process defined one small step at a time) where x_t gets progressively corrupted. The learned reverse process is a second SDE, solved by 1000 small steps.

Rectified flow replaces that with a straight-line interpolation between clean data and pure noise:

x_t = (1 - t) * x_0 + t * epsilon,     t in [0, 1]

Train a network to predict the velocity v_theta(x_t, t) = epsilon - x_0: the forward direction along that straight-line path from clean data to noise (dx_t/dt). During sampling, you integrate this velocity backward, stepping from noise toward data. The resulting ODE sits much closer to a straight line than DDPM's curved one, so sampling needs far fewer integration steps.

DDPM's curved denoising path next to rectified flow's straight line between the same two endpoints, with far fewer steps marked along the straight path

Analogy: DDPM is a hiker who only trusts short steps and re-checks the map after every one, so reaching the destination takes a thousand cautious steps along a wandering trail. Rectified flow is a hiker who first draws a straight line to the destination on the map, then just walks it: far fewer steps, because the direction never needs correcting.

SD3 calls this Rectified Flow Matching. FLUX, Z-Image, and most 2026 models use the same objective. Typical inference runs 20-30 Euler steps (deterministic), against 50+ steps of DDIM (DDPM's faster deterministic sampler) in the old DDPM regime. Distilled variants (turbo, schnell, LCM) push that down to 1-4 steps.

AdaLN conditioning

DiTs condition on timestep and class/text through adaptive layer norm: predict scale and shift from the conditioning vector, then apply them after LayerNorm. Cleaner than the FiLM-style modulation U-Nets used, and now the default in every modern DiT.

cond -> MLP -> (scale, shift, gate)
norm(x) * (1 + scale) + shift, then residual add * gate

Text encoders in SD3 and FLUX

  • SD3 uses three text encoders: two CLIP models plus T5-XXL. Their embeddings get concatenated and fed to the image stream as text conditioning.
  • FLUX uses one CLIP-L plus T5-XXL.
  • Qwen-Image and Z-Image variants use their own in-house text encoders, aligned with their base LLMs.

The text encoder explains a lot of why SD3 and FLUX follow prompts so much better than SD1.5 did. It's also why they can render legible words inside an image at all: T5-XXL's language understanding gives the model enough signal to spell a word out correctly, instead of the scrambled glyphs CLIP-only SD1.5 produced. T5-XXL alone runs 4.7B params.

Classifier-free guidance still holds

Rectified flow changes the sampler, not the conditioning. Classifier-free guidance (drop text with 10% probability during training, mix conditional and unconditional predictions at inference) works identically with rectified flow. Most 2026 models use guidance scale 3.5-5, lower than SD1.5's 7.5, because rectified-flow models follow prompts more tightly by default.

Consistency, Turbo, Schnell, LCM

Four names for the same idea: distil a slow many-step model into a fast few-step model.

  • LCM (Latent Consistency Model): trains a student that predicts the final x_0 from any intermediate x_t in one step.
  • SDXL Turbo and FLUX schnell: 1-4 step models trained with adversarial diffusion distillation.
  • SD Turbo: OpenAI-style Consistency Models adapted to latent diffusion.

Production serving of any new model ships both a "full quality" checkpoint and a "turbo / schnell" variant. Schnell ("fast" in German, Black Forest Labs' naming convention) runs in 1-4 steps and fits real-time pipelines.

Model landscape in 2026

ModelSizeArchitectureLicense
Stable Diffusion 3 Medium2BMMDiTSAI Community
Stable Diffusion 3.5 Large8BMMDiTSAI Community
FLUX.1-dev12BDouble + Single Stream DiTnon-commercial
FLUX.1-schnell12Bsame, distilledApache 2.0
FLUX.2—iterated FLUX.1mixed
Z-Image6BS3-DiT (Scalable Single-Stream)permissive
Qwen-Image~20BDiT + Qwen text towerApache 2.0
Hunyuan-Image-3.0~80BDiTresearch
SD4 Turbo3BDiT + distillationSAI Commercial

FLUX.1-schnell is the 2026 open-source default. Z-Image is the efficiency leader. FLUX.2 and SD4 are the current quality tips.

Why this phase shift matters

DDPM + U-Net worked. DiT + rectified flow works better, runs faster, and scales more cleanly. The transition parallels the one from RNNs to transformers in NLP: both architectures solved the same problem, but transformers scaled and now dominate. Every 2026 paper on image, video, or 3D generation uses a DiT-shaped denoiser and usually a rectified flow objective. U-Net DDPM is now primarily pedagogical (Lesson 10).

Check: The Concept0/22
1. What is a Diffusion Transformer (DiT)?Define
A box labelled noisy latent patches feeding into a box labelled DiT, feeding into a box labelled prediction, with a dashed question mark box under DiT reading built from equals question mark.
DiT's place in the pipeline, with its internal design left blank.
2. What is adaptive layer norm (AdaLN) in a DiT?Define
A box labelled conditioning vector, timestep or text, and a box labelled x, block features, both with arrows down to a dashed question mark box reading AdaLN combines them, question mark.
AdaLN's two named inputs; the combination itself is left blank.
3. What is an MMDiT block, as used in Stable Diffusion 3?Define
A box labelled text tokens and a box labelled image tokens, each with a dashed question mark box underneath reading own weights, question mark, both arrows converging on a third dashed question mark box reading interaction, question mark.
The two token types named; whether they share weights and how they interact are left blank.
4. In FLUX, what is the difference between double-stream and single-stream blocks?Define
A box labelled double-stream, first N blocks, with an arrow to a box labelled single-stream, later blocks, no weight handling drawn for either.
The block order as the lesson gives it; how weights are handled at each stage is left blank.
5. What is rectified flow?Define
A box labelled x_0, clean data, a dashed question mark box reading path shape, question mark, and a box labelled epsilon, noise.
The two fixed endpoints only; the path between them and what the network learns along it are left blank.
6. What is the velocity target in rectified flow?Define
A card listing two quantities available at training time, x_0 the clean data and epsilon the noise mixed into it, above a dashed question mark box reading velocity target, question mark.
The two quantities as an unordered list; how they combine into the velocity target is left blank.
7. What is classifier-free guidance (CFG)?Define
A box labelled prompt plus image, an arrow to a dashed question mark box reading CFG recipe, question mark, and an arrow onward to a small box labelled pred.
The input only; the classifier-free guidance recipe itself is left blank.
8. What do schnell, turbo and LCM variants have in common?Define
Three boxes labelled schnell, turbo and LCM, with a dashed rule under all three and a dashed question mark box below it reading common trait, question mark.
Three model names only; what they share is left blank.
9. Rectified flow trains the model to predict velocity `v = epsilon - x_0` along a straight-line path between data and noise. Why does this allow about 20-step sampling instead of 1000?Explain
Two rows, noise on the left and data on the right in both. The top row draws one plain straight line between them labelled rectified flow, given, straight line, with no markers on it. The bottom row has a dashed question mark box between the same two labels reading DDPM, path shape, question mark. Below both, a wide dashed question mark box asks why this lets 20 steps replace 1000.
Rectified flow's straight path is stated by the stem itself; DDPM's path shape and the reason fewer steps suffice are left blank.
10. Why does FLUX switch from double-stream to single-stream blocks after its first N blocks?Explain
A box labelled double-stream, first N blocks, an arrow to a box labelled single-stream, later blocks, and a dashed question mark box below reading why switch there, question mark.
The block order only; the reason for the switch is left blank.
11. How does rectified flow's path between clean data and noise differ from the forward process in DDPM?Explain
A card of shared inputs, x_0 clean data and epsilon noise, with arrows into two dashed question mark boxes reading DDPM route, question mark, and rect. flow route, question mark, and arrows on from both into a box labelled x_t.
The shared endpoints only; the route each process takes between them is left blank.
12. Why can SD3 and FLUX render legible words inside an image when CLIP-only SD1.5 produced scrambled glyphs?Explain
Two stacked cards: CLIP-only SD1.5, where words inside the generated image come out as scrambled glyphs, and SD3 and FLUX, where they come out legible. Below both, a dashed question mark box reading why, question mark.
The two outcomes exactly as the stem gives them; the reason for the difference is left blank.
13. How do the text encoder setups of SD3 and FLUX differ?Explain
A box labelled prompt text with arrows into two dashed question mark boxes, one reading SD3, which encoders, question mark, the other reading FLUX, which encoders, question mark, and arrows on from both into a box labelled text conditioning.
The shared prompt and the shared output only; each model's encoder stack is left blank.
14. Why do most rectified-flow models (SD3, FLUX) use a guidance scale around 3.5-5, lower than SD1.5's 7.5?Explain
Two cards: SD1.5 with guidance scale 7.5, given; and SD3 and FLUX with guidance scale 3.5 to 5, given.
The two guidance-scale numbers as the lesson states them; why they differ is left for the question.
15. FLUX.1-schnell can produce an image in 4 steps. What makes that possible?Explain
A box labelled noise, an arrow chain through four small boxes labelled step 1 to step 4, then a box labelled image, with a note that 4 steps is the whole budget and a dashed question mark box reading what makes 4 steps enough, question mark.
The 4-step budget the stem gives; what makes it enough is left blank.
16. The shift from U-Net DDPM to DiT is often compared with the shift from RNNs to transformers in NLP. What do the two share?Explain
A box labelled U-Net DDPM to DiT and a box labelled RNN to Transformer, above a dashed question mark box reading what they share, question mark.
The two shifts named, with what they have in common left blank.
17. How does FLUX.1-schnell differ from FLUX.1-dev in the model table?Explain
Two cards, FLUX.1-dev and FLUX.1-schnell, each with size, architecture and license left as question marks.
The two model names from the table; every cell is left blank.
18. A team swaps its U-Net for a DiT but keeps the DDPM objective and expects 20-step sampling. What assumption is wrong?Analyze
A card listing the team's plan: backbone changed from U-Net to DiT, training objective kept as DDPM's noise-prediction loss, and an expectation of 20-step sampling, above a dashed question mark box asking whether this expectation will hold.
The team's plan exactly as given; whether the expectation holds is left blank.
19. A team adopts rectified flow and drops text-dropout from training, assuming CFG is now obsolete. What is the flaw?Analyze
A card stating text dropout during training is removed while classifier-free guidance at sampling is kept, above a dashed question mark box reading the flaw, question mark.
The team's change exactly as given; the flaw in it is left blank.
20. A product team needs open weights under Apache 2.0, commercial use allowed, and a model distilled to 1-4 steps for real-time use. Which model in the landscape table fits?Design
A card listing the requirements: open weights under Apache 2.0, commercial use allowed, and distilled to 1 to 4 steps for real-time use, above a dashed question mark box reading model that fits, question mark.
The requirements exactly as given, with no candidate model named.
21. Your GPU budget is tight and you want the efficiency leader among the 2026 models, not the largest one. Which do you pick?Design
A card stating the need for the efficiency leader among the 2026 models, not the largest one, above a dashed question mark box reading model that fits, question mark.
The requirement exactly as given, with no candidate model named.
22. A service needs the best possible quality for batch jobs and instant previews for users. Which setup follows the lesson's guidance?Design
A card listing two needs, best possible quality for batch jobs and instant previews for users, above a dashed question mark box reading serving setup, question mark.
The two needs exactly as given; the serving setup that meets them is left blank.

Build It

Step 1: A DiT block with AdaLN

The block below implements the scale/shift/gate conditioning described above, with one addition: the modulation MLP starts at zero, so the block begins as an identity function and only drifts away from it as training proceeds.

python
import torch
import torch.nn as nn
 
 
class AdaLNZero(nn.Module):
    """
    Adaptive LayerNorm with a gate. Predicts (scale, shift, gate) from the conditioning.
    Init such that the whole block starts as identity ("zero init").
    """
 
    def __init__(self, dim, cond_dim):
        super().__init__()
        self.norm = nn.LayerNorm(dim, elementwise_affine=False)
        self.mlp = nn.Linear(cond_dim, dim * 3)
        nn.init.zeros_(self.mlp.weight)
        nn.init.zeros_(self.mlp.bias)
 
    def forward(self, x, cond):
        scale, shift, gate = self.mlp(cond).chunk(3, dim=-1)
        h = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
        return h, gate.unsqueeze(1)
 
 
class DiTBlock(nn.Module):
    def __init__(self, dim=192, heads=3, mlp_ratio=4, cond_dim=192):
        super().__init__()
        self.adaln1 = AdaLNZero(dim, cond_dim)
        self.attn = nn.MultiheadAttention(dim, heads, batch_first=True)
        self.adaln2 = AdaLNZero(dim, cond_dim)
        self.mlp = nn.Sequential(
            nn.Linear(dim, dim * mlp_ratio),
            nn.GELU(),
            nn.Linear(dim * mlp_ratio, dim),
        )
 
    def forward(self, x, cond):
        h, gate1 = self.adaln1(x, cond)
        a, _ = self.attn(h, h, h, need_weights=False)
        x = x + gate1 * a
        h, gate2 = self.adaln2(x, cond)
        x = x + gate2 * self.mlp(h)
        return x

AdaLNZero starts as an identity mapping because its MLP weights are initialised to zero. Training nudges the block away from identity; this stabilises deep transformer diffusion models dramatically.

Step 2: A tiny DiT

With the block defined, wiring a full model is just plumbing: patchify the image, add a timestep embedding as conditioning, run the result through depth DiT blocks, then unpatchify back to pixels.

python
def timestep_embedding(t, dim):
    import math
    half = dim // 2
    freqs = torch.exp(-math.log(10000) * torch.arange(half, device=t.device) / half)
    args = t[:, None].float() * freqs[None]
    return torch.cat([args.sin(), args.cos()], dim=-1)
 
 
class TinyDiT(nn.Module):
    def __init__(self, image_size=16, patch_size=2, in_channels=3, dim=96, depth=4, heads=3):
        super().__init__()
        self.patch_size = patch_size
        self.num_patches = (image_size // patch_size) ** 2
        self.patch = nn.Conv2d(in_channels, dim, kernel_size=patch_size, stride=patch_size)
        self.pos = nn.Parameter(torch.zeros(1, self.num_patches, dim))
        self.time_mlp = nn.Sequential(
            nn.Linear(dim, dim * 2),
            nn.SiLU(),
            nn.Linear(dim * 2, dim),
        )
        self.blocks = nn.ModuleList([DiTBlock(dim, heads, cond_dim=dim) for _ in range(depth)])
        self.norm_out = nn.LayerNorm(dim, elementwise_affine=False)
        self.head = nn.Linear(dim, patch_size * patch_size * in_channels)
 
    def forward(self, x, t):
        n = x.size(0)
        x = self.patch(x)
        x = x.flatten(2).transpose(1, 2) + self.pos
        t_emb = self.time_mlp(timestep_embedding(t, self.pos.size(-1)))
        for blk in self.blocks:
            x = blk(x, t_emb)
        x = self.norm_out(x)
        x = self.head(x)
        return self._unpatchify(x, n)
 
    def _unpatchify(self, x, n):
        p = self.patch_size
        h = w = int(self.num_patches ** 0.5)
        x = x.view(n, h, w, p, p, -1).permute(0, 5, 1, 3, 2, 4).reshape(n, -1, h * p, w * p)
        return x

Step 3: Rectified flow training

Training needs nothing exotic: sample a random point along the straight line between a real image and noise, ask the model to predict the velocity at that point, and take the mean-squared error against the true velocity.

python
import torch.nn.functional as F
 
def rectified_flow_train_step(model, x0, optimizer, device):
    model.train()
    x0 = x0.to(device)
    n = x0.size(0)
    t = torch.rand(n, device=device)
    epsilon = torch.randn_like(x0)
    x_t = (1 - t[:, None, None, None]) * x0 + t[:, None, None, None] * epsilon
 
    target_velocity = epsilon - x0
    pred_velocity = model(x_t, t)
 
    loss = F.mse_loss(pred_velocity, target_velocity)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    return loss.item()

Compare with DDPM's noise-prediction loss (Lesson 10): same structure, different target. Instead of predicting the noise epsilon, we predict the velocity epsilon - x_0, which points from data to noise along the straight-line interpolation.

Step 4: Euler sampler

Rectified flow is an ODE. Euler's method is the simplest and, for a well-trained rectified-flow model, nearly as accurate as higher-order solvers at 20+ steps.

python
@torch.no_grad()
def rectified_flow_sample(model, shape, steps=20, device="cpu"):
    model.eval()
    x = torch.randn(shape, device=device)
    dt = 1.0 / steps
    t = torch.ones(shape[0], device=device)
    for _ in range(steps):
        v = model(x, t)
        x = x - dt * v
        t = t - dt
    return x

20 steps. On a trained model this produces samples comparable to 1000-step DDPM.

Step 5: End-to-end smoke test

python
import numpy as np
 
def synthetic_blobs(num=200, size=16, seed=0):
    rng = np.random.default_rng(seed)
    out = np.zeros((num, 3, size, size), dtype=np.float32)
    yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
    for i in range(num):
        cx, cy = rng.uniform(4, size - 4, size=2)
        r = rng.uniform(2, 4)
        mask = (xx - cx) ** 2 + (yy - cy) ** 2 < r ** 2
        colour = rng.uniform(-1, 1, size=3)
        for c in range(3):
            out[i, c][mask] = colour[c]
    return torch.from_numpy(out)

Train a TinyDiT on this with rectified flow. After 500 steps, sampled outputs should look like faint blobs of colour.

Check: Build It0/12
1. What does the 'Zero' in AdaLN-Zero mean, and why does it help a DiT?Explain
A code box with the lesson's two AdaLN conditioning lines, conditioning through an MLP to scale, shift and gate, and those applied around LayerNorm with a gated residual add, above a dashed question mark box reading what Zero names here, question mark.
The AdaLN pieces the name sits on, straight from the lesson; which of them the word Zero refers to is left blank.
2. In the lesson's DiTBlock, what does forward(x, cond) return immediately after initialisation, for any cond?Apply
The DiTBlock forward method with both gated residual additions highlighted, a note that both AdaLN modules are AdaLNZero instances, and a dashed question mark box reading return value, question mark.
The lesson's own DiTBlock.forward code with nothing said about what the gates hold; the return value is left blank.
3. In AdaLNZero with dim=192 and cond_dim=192, what is the output width of self.mlp before .chunk(3, dim=-1)?Apply
The AdaLNZero constructor's nn.Linear line, given dim equals 192 and cond_dim equals 192, above a dashed question mark box reading out_features, question mark.
The lesson's own Linear line with the given dimensions; the resulting output width is left blank.
4. TinyDiT(image_size=16, patch_size=2) is given one 16x16 image. How many patch tokens does it produce?Apply
The TinyDiT constructor's num_patches line, given image_size equals 16 and patch_size equals 2, above a dashed question mark box reading num_patches, question mark.
The lesson's own num_patches line with the given sizes; the result is left blank.
5. In rectified_flow_train_step, a sample has x0 = 4, epsilon = 8 and t = 0.25 (treat them as single numbers). What is x_t?Apply
The lesson's own x_t formula, given x0 equals 4, epsilon equals 8 and t equals 0.25, above a dashed question mark box reading x_t, question mark.
The lesson's own formula with the given numbers to substitute into it; the value of x_t is left blank.
6. For the same sample (x0 = 4, epsilon = 8, t = 0.25), what is target_velocity in the training step?Apply
The lesson's own target_velocity formula, given x0 equals 4 and epsilon equals 8, above a dashed question mark box reading target_velocity, question mark.
The lesson's own formula with the given numbers to substitute into it; the value of target_velocity is left blank.
7. rectified_flow_sample is called with steps=4. Which t values does the model see on its four calls, in order?Apply
The lesson's own rectified_flow_sample loop for steps equals 4, with the line that calls the model highlighted, above a dashed question mark box asking for the t values the model sees, in order.
The lesson's own sampling loop exactly as shipped, for steps equals 4; the sequence of t values it visits is left blank.
8. A TinyDiT is called with x of shape (8, 3, 16, 16) and t of shape (8,). What shape does forward return?Apply
The start and end of TinyDiT.forward for an input x of shape 8 by 3 by 16 by 16 and t of shape 8, with the final returned shape left as a question mark.
The lesson's own forward method's input and final return line; the returned shape is left blank.
9. Compared with a DDPM noise-prediction training step, which part of rectified_flow_train_step is different?Apply
Four boxes in a row for one rectified-flow training step: draw t, build x_t, model of x_t and t, and MSE loss of prediction against target. A dashed question mark box under the loss box reads target, question mark, and a wider dashed box asks which part differs from a DDPM step.
The lesson's own training step laid out stage by stage, with the regression target left blank.
10. A colleague edits the sampler's update to `x = x + dt * v` and the outputs stay noise. What went wrong?Analyze
A code box with the lesson's own sampler update line and the colleague's edited version with the sign flipped and highlighted, above a dashed question mark box asking why the outputs stay noise.
Both versions of the line, sign difference only; why the edit fails is left blank.
11. A model trained to predict DDPM noise epsilon is sampled with rectified_flow_sample, and the results are garbage. Why?Analyze
A card stating the model was trained to predict DDPM noise epsilon, above the lesson's own two sampler lines run unchanged, a note that the result is garbage, and a dashed question mark box reading why, question mark.
The model's training target and the sampler code, both exactly as the stem and the lesson give them; the reason for the garbage is left blank.
12. A 24-block DiT diverges in the first few hundred training steps. Its modulation MLPs use default random initialisation instead of zeros. Which lost property is the likely cause?Analyze
A code box with the lesson's own zero-init line and a note that the 24-block DiT uses the default random init instead, above a dashed question mark box reading lost property, question mark.
The lesson's own init line next to the changed one; what property is lost is left blank.

Use It

For real image generation with FLUX, SD3, or Z-Image, diffusers ships all three with a unified API:

python
from diffusers import FluxPipeline, StableDiffusion3Pipeline
import torch
 
pipe = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-schnell",
    torch_dtype=torch.bfloat16,
).to("cuda")
 
out = pipe(
    prompt="a golden retriever surfing a tsunami, hyperrealistic, studio lighting",
    guidance_scale=0.0,           # schnell was trained without CFG
    num_inference_steps=4,
    max_sequence_length=256,
).images[0]
out.save("surf.png")

Three lines. FLUX.1-schnell in four steps. Swap the model id for black-forest-labs/FLUX.1-dev for higher quality at 20-30 steps with CFG.

For SD3:

python
pipe = StableDiffusion3Pipeline.from_pretrained(
    "stabilityai/stable-diffusion-3.5-large",
    torch_dtype=torch.bfloat16,
).to("cuda")
out = pipe(prompt, guidance_scale=3.5, num_inference_steps=28).images[0]
Check: Use It0/2
1. The FLUX.1-schnell diffusers example passes guidance_scale=0.0 and num_inference_steps=4. Why is guidance 0.0 right here?Apply
The lesson's own FLUX.1-schnell diffusers call with guidance_scale=0.0 highlighted, above a dashed question mark box reading why 0.0 is right here, question mark.
The lesson's own code exactly as shipped; why this particular setting is correct is left blank.
2. Starting from the FLUX.1-schnell example, what do you change to get FLUX.1-dev's higher quality?Apply
A card with the schnell example's model id, guidance_scale 0.0 and num_inference_steps 4, above a dashed question mark box asking what to change for FLUX.1-dev's quality.
The schnell settings exactly as given; what changes to reach FLUX.1-dev's quality is left blank.

Ship It

This lesson produces:

  • outputs/prompt-dit-model-picker.md: picks between SD3, FLUX.1-dev, FLUX.1-schnell, Z-Image, SD4 Turbo given quality, latency, and license constraints.
  • outputs/skill-rectified-flow-trainer.md: writes a complete training loop for rectified flow with AdaLN DiT and Euler sampling.
Author's Note

The part I see people skip is the velocity target. They read v = epsilon - x_0 once, nod, and jump straight to the exciting model-zoo table. That's the wrong shortcut. The whole reason rectified flow samples in 20 steps instead of 1000 is that this specific target makes the reverse trajectory close to a straight line. If your mental model is still "the network predicts noise, like DDPM," you'll misread the next flow-matching variant that shows up in a paper, and there will be more of them.

The naming soup here (DiT, MMDiT, double-stream, single-stream, schnell, turbo, LCM) looks worse than it is. It collapses into two questions: architecture (transformer instead of U-Net, one shared weight stream or a separate one per modality) and sampling (straight-line flow, step count, distilled or not). Once you're asking those two questions instead of memorizing names, every new model announcement stops being something new to learn and turns into a point you can place on a grid you already own.

Exercises

  1. (Easy) Train the TinyDiT above on the synthetic blob dataset for 500 steps. Compare samples produced with 10, 20, and 50 Euler steps.
  2. (Medium) Add text conditioning by concatenating a learned class embedding to the time embedding (10 blob "classes" by colour). Sample with class 0, 5, and 9 and verify colours match.
  3. (Hard) Compute the Fréchet distance (FID proxy) between generated samples from rectified-flow and DDPM versions of the same-size network trained on the same data for the same number of steps. Report which converges faster.

Key Terms

TermWhat people sayWhat it actually means
DiT"Diffusion transformer"Transformer that replaces the U-Net as the diffusion denoiser; operates on patchified latents
AdaLN"Adaptive layer norm"Timestep/text conditioning via learned scale, shift, gate applied after LayerNorm; standard in every modern DiT
MMDiT"Multi-modal DiT (SD3)"Separate weight streams for text and image tokens that share a joint self-attention
Single-stream / double-stream"FLUX trick"First N blocks double-stream (separate weights per modality), later blocks single-stream (concat + shared weights) for efficiency
Rectified flow"Straight-line noise-to-data"Linear interpolation between data and noise; network predicts velocity; fewer ODE steps needed at inference
Velocity target"epsilon - x_0"The regression target in rectified flow; points from clean data to noise
CFG guidance"classifier-free guidance"Mix conditional and unconditional predictions; still used in rectified-flow models
Schnell / turbo / LCM"1-4 step distillation"Small-step variants distilled from full-quality models; production real-time

Further Reading