Concept units

Diffusion transformers (DiT)

Advanced

Read these first if they are new to you

The idea in one paragraph

A diffusion transformer, or DiT, is the network inside many diffusion and flow-matching models. It reads a noisy example as a set of tokens, such as the points of a point cloud. A stack of transformer blocks lets every token gather information from the others. The time, and any condition, reach every block through adaptive layer normalization. A last layer turns each token back into an output of its own; in flow matching, that output is the velocity of each point.

The network's job: noisy tokens and a time in, one velocity per token out

Picture a cloud of 1,024 points, part way from noise to a shape, at time t. In flow matching, the network must return 1,024 arrows: the velocity of each point. Each arrow depends on where the other points are. A point at the tip of a half-formed branch should move differently from one in the middle of a cluster.

A DiT handles this in three stages. First, a linear layer turns each point's 3 coordinates into a vector of D numbers, its token (see embeddings). For an image, a token is a small square patch instead of a point. Second, a stack of blocks updates all the tokens. Third, a final layer turns each token into that token's output.

From noisy points to one velocity per point
noisy points xt 1,024 points × 3 coordinates embed each point linear layer, 3 → D DiT blocks, × 12 tokens: 1,024 × D final layer adaLN, then linear D → 3 a velocity for each point 1,024 × 3 time t and any condition embedding c D numbers into every block and the final layer
What to notice. The tokens keep their count all the way through: 1,024 in, 1,024 out. The time enters from the side, into every block, rather than as one more token.

Inside a block: attention and an MLP, each steered by the time

A DiT block is a transformer block with two changes, both from time conditioning. First, each layer normalization takes its scale and shift from the conditioning vector c. Second, each branch's output is multiplied by a gate before it is added back. A small layer (a SiLU activation, then a linear layer) maps c to six vectors of D numbers: γ1, β1, α1 for the attention branch and γ2, β2, α2 for the MLP branch. With LN for layer normalization and ⊙ for feature-by-feature products, the block computes:

z = LN(h) ⊙ (1 + γ1) + β1
h ← h + α1 ⊙ Attention(z)

z = LN(h) ⊙ (1 + γ2) + β2
h ← h + α2 ⊙ MLP(z)

One DiT block: six vectors from the time steer two branches
tokens h layer norm × (1 + γ₁), then + β₁ self-attention × α₁ + layer norm × (1 + γ₂), then + β₂ MLP × α₂ + conditioning c SiLU, then linear γ₁ β₁ α₁ γ₂ β₂ α₂ to the next block
What to notice. The orange boxes are where the time acts: two scale-and-shift steps and two gates. The lines on the right are residual paths, which add each branch's input back to its output.

The final layer is one more adaLN step (a scale and a shift, no gate), then a linear layer from D numbers to the output size of each token: 3 for a velocity in 3D. In the DiT paper, the layers that make the six vectors and the final linear layer all start at zero. So the untrained network passes tokens through every block unchanged and outputs zero for every token.

Worked example: the sizes for a point cloud

Worked example: following the shapes of the data

Take a batch of 2 point clouds of 1,024 points each, D = 384 features, 6 attention heads and 12 blocks. This is the size of the smallest model in the DiT paper, DiT-S.

  1. Input: 2 × 1,024 × 3 numbers. After embedding: 2 × 1,024 × 384.
  2. Time: 2 numbers, one per cloud. After the time embedding: c is 2 × 384.
  3. In each block, the conditioning layer maps 384 numbers to 6 × 384 = 2,304. It has 384 × 2,304 weights plus 2,304 biases: 887,040 parameters.
  4. Self-attention compares every token with every token: 1,024 × 1,024 ≈ 1.05 million pairs, per head and per block.
  5. Output: 2 × 1,024 × 384, and after the final layer, 2 × 1,024 × 3: one velocity per point.

Your turn. With 2,048 points per cloud, how many token pairs does attention compare per head and per block? And how many parameters does each block's conditioning layer have when D = 768?

Answer

2,048 × 2,048 = 4,194,304 pairs, four times as many: doubling the points quadruples the pairs. The conditioning layer maps 768 to 6 × 768 = 4,608 numbers: 768 × 4,608 + 4,608 = 3,543,552 parameters.

dit_block.py (PyTorch)

import torch
import torch.nn as nn

class DiTBlock(nn.Module):
    # One transformer block whose normalization is set by the time (adaLN-Zero).
    def __init__(self, d, heads):
        super().__init__()
        self.norm1 = nn.LayerNorm(d, elementwise_affine=False)
        # Attention with several heads. batch_first=True puts the batch first, as in h.
        self.attn = nn.MultiheadAttention(d, heads, batch_first=True)
        self.norm2 = nn.LayerNorm(d, elementwise_affine=False)
        self.mlp = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))
        self.ada = nn.Sequential(nn.SiLU(), nn.Linear(d, 6 * d))
        nn.init.zeros_(self.ada[1].weight)    # "Zero": every gate starts at 0
        nn.init.zeros_(self.ada[1].bias)

    def forward(self, h, c):                  # h: (batch, tokens, d); c: (batch, d)
        # [:, None] adds an axis of length 1, so each of the 6 parts acts on all tokens.
        g1, b1, a1, g2, b2, a2 = self.ada(c)[:, None].chunk(6, dim=-1)
        z = self.norm1(h) * (1 + g1) + b1
        # z, z, z: the queries, keys and values all come from z. [0] keeps the output.
        h = h + a1 * self.attn(z, z, z, need_weights=False)[0]
        z = self.norm2(h) * (1 + g2) + b2
        return h + a2 * self.mlp(z)

torch.manual_seed(0)
block = DiTBlock(d=384, heads=6)
h = torch.randn(2, 1024, 384)                 # 2 examples, 1024 tokens each
c = torch.randn(2, 384)                       # time (plus condition) embedding
out = block(h, c)
print(out.shape, "unchanged at the start:", torch.equal(out, h))
print("parameters in the time layer:", sum([p.numel() for p in block.ada.parameters()]))
torch.Size([2, 1024, 384]) unchanged at the start: True
parameters in the time layer: 887040

Without position labels, a DiT treats points as a set

An image DiT adds a position embedding to each patch token, because a patch's place in the grid matters. A point token already carries its position: its coordinates. If nothing else marks an order, attention and the per-token MLP treat all tokens alike. Shuffle the input points, and the output velocities come out shuffled the same way. This property is called permutation equivariance, and it suits point clouds, which have no order (see permutation invariance).

A condition can enter in two ways. A short one, such as a class label, is added to c. A long one, such as the known points of a shape to complete or a sentence, becomes its own set of tokens that the blocks read with cross-attention, or joins the data tokens in self-attention.

The name says diffusion, but the network fits flow matching too

The DiT paper (Peebles and Xie, 2023) trained the network as a diffusion model that predicts the added noise, on image patches in a compressed form of the image. Nothing in the network depends on that choice. To use it for flow matching, change only the training target, to the velocity x1 − x0.

This is the original block; later models vary it

This page shows the block as the DiT paper defines it. Later models change details. Some feed conditions through cross-attention or joint attention, and some use other normalizations or position encodings. The pattern stays the same: tokens in, blocks steered by the time, one output per token.

Check yourself

  1. You switch a DiT from predicting noise to flow matching. What changes in the network?

    Answer

    Nothing in the network. Only the training target in the loss changes, from the added noise to the velocity x1 − x0.

  2. All six vectors from a block's conditioning layer are zero. What does the block output?

    Answer

    Its input, unchanged. With α1 = α2 = 0, both branches add nothing.

  3. A point-cloud DiT without position embeddings gets the same 1,024 points in a new order. What happens to its output?

    Answer

    It returns the same 1,024 velocities, in the new order. Each point still gets the velocity it had before.

  4. Why can the same DiT handle clouds of 500 points and of 2,000 points?

    Answer

    The same weights apply to every token, and attention works for any number of tokens. The cost of attention grows with the square of the number of points.

Go further

Optional background, for readers with more time.

Where this shows up

No unit or explanation builds on this one yet.