Transfusion: Autoregressive Text + Diffusion Image in One Transformer
Chameleon and Emu3 bet on discrete tokens. It works, but the quantization bottleneck shows: image quality plateaus below continuous-space diffusion models. Transfusion (Meta, Zhou et al., August 2024) takes the opposite bet. Keep images continuous, drop the VQ-VAE, and train one transformer with two losses. Text tokens get next-token prediction, image patches get a flow-matching diffusion loss, and both objectives update the same weights. The architecture behind Stable Diffusion 3 (MMDiT) is a close cousin. This lesson walks through the Transfusion thesis, builds a toy two-loss trainer, and traces the attention mask that lets one transformer do both jobs.
Learning Objectives
- Wire a transformer that runs two losses (NTP on text tokens, diffusion MSE on image patches) on one backbone.
- Explain why bidirectional attention across image patches plus causal attention over text tokens is the right mask choice.
- Compare Transfusion-style (continuous images, diffusion loss) to Chameleon-style (discrete images, NTP) on compute, quality, and code complexity.
- Name MMDiT's contribution: modality-specific weights at each block, joint attention at the residual stream.
The Problem
The debate over discrete versus continuous image representations is older than LLMs. Continuous representations (raw pixels, VAE latents) preserve detail. Discrete tokens (VQ indices) fit the transformer's native vocabulary but lose detail at the quantization step.
Chameleon and Emu3 went discrete. You get one loss and one architecture, but image fidelity is capped by the tokenizer.
Diffusion models went continuous. Image quality is excellent, but the model is separate from the LLM, the noise schedule takes real engineering, and there is no clean way to fold in text generation.
Transfusion asks whether you can have both: keep images continuous, still train one model, and combine two losses in one gradient step.
The Concept
The two-loss architecture
A single decoder-only transformer processes a sequence that contains:
- Text tokens (discrete, from a BPE vocab).
- Image patches (continuous, 16x16 pixel blocks projected into the hidden dim with a linear embedding, the same input layer a ViT uses).
<image>and</image>tags marking where the continuous patches live.
The forward pass runs once. Each token then gets one of two heads:
- Text tokens get standard cross-entropy on the vocab-logits head.
- Image patches get a diffusion loss on the continuous patches: predict the noise that was added to each patch.
Gradients flow through the shared transformer body, so both losses improve the same weights at the same time.
Attention mask: causal text + bidirectional image
Text has to stay causal. If a text token can see future text, teacher forcing breaks. An image is different. Its patches are one snapshot, so within the same image block they should attend to each other in both directions.
The mask:
M[i, j] = 1 if:
(i is text and j is text and j <= i) # causal for text
OR (i is image and j is image and same_image_block(i, j)) # bidirectional within image
OR (i is text and j is image and j < i_image_end) # text attends to previous images
OR (i is image and j is text and j < i_image_start) # image attends to preceding text
You build it as a block-triangular mask, and the same mask is used at training and at inference.
Diffusion loss inside the transformer
The diffusion loss is the standard one. Add noise to an image patch and ask the model to undo it, either by predicting the noise or the clean patch (the two are equivalent). Transfusion's version uses flow matching, where the model predicts the velocity field from noisy to clean.
During training:
- For each image patch x0, sample a random timestep t.
- Sample noise ε, compute xt = (1-t) * x0 + t * ε (linear interpolation for flow matching).
- The transformer predicts v_theta(xt, t); loss = MSE(v_theta(xt, t), ε - x0).
- Backprop alongside the text NTP losses from the same sequence.
At inference, text tokens come from standard autoregressive sampling. Image patches come from a diffusion sampling loop (10-30 steps typical), conditioned on the text tokens before them.
MMDiT: Stable Diffusion 3's variant
Stable Diffusion 3 (Esser et al., March 2024) shipped MMDiT (Multimodal Diffusion Transformer) around the same time as Transfusion, and the two architectures are siblings.
MMDiT differs in three ways:
- Modality-specific weights per block. Each transformer block has separate Q, K, V, and MLP weights for text tokens and image patches. Attention is joint across modalities, and everything else is modality-specific.
- Rectified flow training. This is a specific flow-matching variant with known sampling and simpler math than DDPM.
- Scale. MMDiT is the backbone for SD3 (2B and 8B param variants), while the Transfusion paper scales to 7B.
Both land on the same core idea: one transformer runs NTP on text and diffusion on continuous image representations.
Why this beats Chameleon-style
The quality gap between continuous diffusion and discrete NTP for image generation is measurable. The Transfusion paper reports:
- At 7B params, it beats a same-size Chameleon-style model on FID by 3-5 points.
- No tokenizer training. The image encoder is a linear projection to the hidden dim, same as a ViT's input layer.
- Inference can denoise image patches in parallel, which autoregressive image tokens can't do.
The downside is that Transfusion is a dual-loss model, so training dynamics are trickier. Loss weights need tuning, and a schedule mismatch between NTP and diffusion can let one head dominate.
What sits downstream
Janus-Pro (Lesson 12.15) refines Transfusion by decoupling the vision encoder for understanding from the one for generation (SigLIP for one, VQ for the other) while sharing the transformer body. Show-o (Lesson 12.14) swaps diffusion for discrete diffusion (masked prediction). The unified-generation family branches quickly after Transfusion.
Production VLMs that emit images in 2026 (Gemini 3 Pro, GPT-5, Claude Opus 4.7's image generation path) almost certainly use some descendant of this family, but the details are proprietary.
Use It
code/main.py is a toy Transfusion on a tiny synthetic problem:
- Each caption is a four-integer sequence that encodes a class from 0 to 6.
- Each image is a 4x4 grid of floats whose shade depends on the class.
- The "transformer" is a stand-in. A weight dict with two scale counters fakes the model's output, so the numbers it prints come from the loss plumbing, not from a trained network.
- Each step computes a text loss and a flow-matching MSE loss on a noised image, then sums them with separate weights.
- The block-triangular attention mask is built explicitly for a sequence with one image block and printed.
The transformer is fake. The two-loss plumbing and the mask construction are the real artifacts. The code has no generation loop, so the inference procedure above is something to read, not run.
Ship It
This lesson produces outputs/skill-two-loss-trainer-designer.md. Give it a new multimodal training task (text + image, text + audio, text + video) and it designs the two-loss schedule (loss weights, mask shape, shared vs modality-specific blocks) and flags implementation risks.
I'd run main.py and read the mask it prints before touching anything else. That is the one piece of the file that does real work. Check the rows for the <image> and </image> positions: the code treats those two tags as neither text nor image, so they attend to earlier text but not to themselves. Decide whether that is what you want before you answer the mask exercise.
I'd also be skeptical of the loss numbers. The text loss falls because a counter nudges the fake probabilities upward each step, and the image loss falls because a second counter pulls the predicted velocity toward the target. Nothing is learning. What the printout does show is how the two losses combine, with the text weight at 1.0 and the image weight at 0.1, both hardcoded.
My bet is that those two weights are where a real run would hurt. The body says loss weights need tuning and that one head can dominate, and here the ratio is a constant I picked by hand with no feedback from either loss. If you swap in a real model, I'd log the two losses separately from step one so you can see which head is driving the gradient.
Exercises
-
A Transfusion-style model trains 70% text tokens and 30% image patches. The image diffusion loss is ~10x the text NTP loss in magnitude. What loss weights balance them?
-
Implement the block-triangular mask for a sequence:
[T, T, <image>, P, P, P, P, </image>, T]. Mark each entry 0 or 1. -
MMDiT has modality-specific QKV weights. What parameter count overhead does this add vs Transfusion's fully-shared transformer? At 7B params, is it worth it?
-
Generation: given a text prompt, the model runs NTP for 50 tokens, then hits
<image>, then runs diffusion on 256 patches over 20 denoise steps. How many forward passes total? -
Read SD3 paper Section 3. Describe rectified flow and why it converges in fewer inference steps than DDPM.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Two-loss training | "NTP + diffusion" | A single transformer optimizes both cross-entropy on text tokens and MSE on continuous image patches in the same gradient step |
| Flow matching | "Rectified flow" | Diffusion variant that predicts a velocity field from noise to clean data; simpler math than DDPM |
| MMDiT | "Multimodal DiT" | Stable Diffusion 3's architecture: joint attention, modality-specific MLPs and norms |
| Block-triangular mask | "Causal text + bidirectional image" | Attention mask that is causal across text but bidirectional within image regions |
| Continuous image representation | "No VQ" | Image patches as real-valued vectors, not integer codebook indices |
| Velocity prediction | "v-parameterization" | Network output is the velocity field between noise and data, not the noise itself |
Code
- main.py (python, 160 lines)