T5, BART — Encoder-Decoder Models
Encoders read. Decoders write. Put them together and you get a model built for input-to-output tasks: translate, summarize, rewrite, transcribe.
Learning Objectives
- Describe how an encoder-decoder transformer carries information from the encoder to the decoder through cross-attention.
- Build T5-style span corruption and verify that the corrupted source and the target round-trip to the original sentence.
- Contrast T5's span-corruption objective with BART's five denoising noise functions, including why BART's pretraining costs more compute.
- Decide whether a new task calls for an encoder-decoder or a decoder-only model.
The Problem
An encoder-decoder model is a transformer built from two stacks. A bidirectional encoder reads the whole input and produces a dense representation of it. A causal decoder generates the output one token at a time, cross-attending to that representation as it goes. Training is shift-by-one on the output side: the same loss as GPT, conditioned on the encoder output.
GPT and BERT each strip the 2017 architecture down for a different goal. Plenty of tasks are naturally input-to-output, though:
- Translation: English → French.
- Summarization: 5,000-token article → 200-token summary.
- Speech recognition: audio tokens → text tokens.
- Structured extraction: prose → JSON.
For these, encoder-decoder is the cleanest fit. The source gets its own stack, and the output gets its own.
Two papers set the modern playbook:
- T5 (Raffel et al. 2019), the "Text-to-Text Transfer Transformer." NLP tasks are reframed as text in, text out, with a single architecture, a single vocabulary, and a single loss. Pretraining is masked span prediction: corrupt spans in the input, decode them in the output.
- BART (Lewis et al. 2019), the "Bidirectional and Auto-Regressive Transformer." A denoising autoencoder: corrupt the input in several ways (shuffle, mask, delete, rotate) and ask the decoder to reconstruct the original.
In 2026 the format survives where input structure matters:
- Whisper (speech → text).
- Google's translation stack.
- Some code-completion and repair models that have distinct context-and-edit structures.
- Flan-T5 and variants for structured reasoning tasks.
Decoder-only took the spotlight, but encoder-decoder stayed in use.
The Concept
The forward loop
source tokens ─▶ encoder ─▶ (N_src, d_model) ──┐
│
target tokens ─▶ decoder block │
├─▶ masked self-attention │
├─▶ cross-attention ◀───────────┘
└─▶ FFN
↓
next-token logits
The encoder runs once per input. The decoder runs autoregressively but cross-attends to the same encoder output at each step, so you can cache that output. For long inputs it's a free speedup.
T5 pretraining: span corruption
Span corruption is T5's pretraining objective. Pick random spans of the input and replace each with a unique sentinel token, then train the decoder to output the missing spans. The spans average 3 tokens and cover 15% of the input in total (the T5 paper's rate). The sentinels are <extra_id_0>, <extra_id_1>, and so on. The decoder outputs only the corrupted spans, each with its sentinel prefix:
source: The quick <extra_id_0> fox jumps <extra_id_1> dog
target: <extra_id_0> brown <extra_id_1> over the lazy
That's a cheaper signal than predicting the whole sequence, and it was competitive with MLM (BERT) and prefix-LM (UniLM) in the T5 paper's ablation.
BART pretraining: multi-noise denoising
Denoising is BART's objective. Apply a noise function to the input, then train the decoder to reconstruct the original text. BART tries five noising functions:
- Token masking.
- Token deletion.
- Text infilling (mask a span, decoder inserts the right length).
- Sentence permutation.
- Document rotation.
Text infilling combined with sentence permutation gave the best downstream numbers. The decoder's target is the full original sequence, not just the corrupted spans, so pretraining compute is higher than T5's.
Inference
Generation works the same way as in GPT, and greedy, beam, and top-p sampling all apply. Beam search (width 4–5) is standard for translation and summarization because the output distribution is narrower than chat.
When to pick each variant in 2026
| Task | Encoder-decoder? | Why |
|---|---|---|
| Translation | Yes, usually | Clear source sequence; fixed output distribution; beam search works |
| Speech-to-text | Yes (Whisper) | Input modality differs from output; encoder shapes audio features |
| Chat / reasoning | No, decoder-only | No persistent "input"; the conversation is the sequence |
| Code completion | Usually no | Decoder-only with long context wins; code models like Qwen 2.5 Coder are decoder-only |
| Summarization | Either works | BART, PEGASUS beat earlier decoder-only baselines; modern decoder-only LLMs match them |
| Structured extraction | Either | T5 is clean because "text → text" absorbs any output format |
Since about 2022, decoder-only has taken over tasks that encoder-decoder used to own, for three reasons: (a) instruction-tuned decoder-only LLMs generalize to new tasks through prompting, (b) one architecture scales more easily than two, and (c) RLHF assumes a decoder. Encoder-decoder holds on where the input modality differs (speech, images) or where beam search quality matters.
Build It
See code/main.py. It implements T5-style span corruption on a toy corpus. This is the piece of the lesson worth building yourself, because span corruption is the core of T5's pretraining.
Step 1: span corruption
def corrupt_spans(tokens, mask_rate=0.15, mean_span=3.0, rng=None):
"""Pick spans summing to ~mask_rate of tokens. Return (corrupted_input, target)."""
n = len(tokens)
n_mask = max(1, int(round(n * mask_rate)))
n_spans = max(1, int(round(n_mask / mean_span)))
...The default mask_rate is the paper's 0.15, but the demo in main() passes 0.20. The target format is the T5 convention: each <extra_id_N> sentinel followed by its span, then one closing sentinel after the last span (<extra_id_0> span0 <extra_id_1> span1 <extra_id_2>). The Concept example leaves that closing sentinel out for brevity. The corrupted input interleaves unchanged tokens with the same sentinels at the span locations.
Step 2: verify round-trip
Given the corrupted input and target, reconstruct the original sentence. If the corruption is reversible, the forward pass is well-defined. This is a sanity check. Real training doesn't do it, but it's cheap and catches off-by-one bugs in your span bookkeeping.
Step 3: BART noising
Five functions: token_mask, token_delete, text_infill, sentence_permute, document_rotate. main() demonstrates each one on its own, so run it and compare each output against the original.
Use It
HuggingFace reference:
from transformers import T5ForConditionalGeneration, T5Tokenizer
tok = T5Tokenizer.from_pretrained("google/flan-t5-base")
model = T5ForConditionalGeneration.from_pretrained("google/flan-t5-base")
inputs = tok("translate English to French: Attention is all you need.", return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=32)
print(tok.decode(out[0], skip_special_tokens=True))The T5 trick is to put the task name in the input text. One model handles dozens of tasks because each task is text in, text out. Instruction-tuned decoder-only models have since generalized the pattern, but T5 codified it first.
Ship It
See outputs/skill-seq2seq-picker.md. The skill picks between encoder-decoder and decoder-only for a new task, given its input-output structure, latency, and quality targets.
I'd read corrupt_spans and round_trip in main.py before going back to either paper. They're short, and running them shows what the decoder target actually contains: sentinels and the missing spans, not the whole sentence. Once that's concrete, the compute gap between T5 and BART is easy to see. T5's decoder emits only the spans, and BART's rebuilds the full sequence.
My bet is that the task table matters more than the architecture history. The question I'd ask about a new task is whether the input has a structure the output doesn't share, like audio going in and text coming out. If it does, a separate encoder is earning its place. If the whole job is one running conversation, I'd start with a decoder-only model.
One thing to watch when you run the script: the demo calls corrupt_spans with mask_rate=0.20, not the 0.15 default in Step 1, so the corrupted source will look a bit heavier than the 15% I described above.
Exercises
- Easy. Run
code/main.py, read the span-corruption output for its 25-token demo sentence, and verify that concatenating the non-sentinel source tokens with the decoded target spans reproduces the original. - Medium.
main.pyalready implements BART'stext_infill, which replaces each span with a single<mask>token and draws span lengths from a Gaussian. Extend it to draw lengths from a Poisson distribution with mean 3, as the BART paper does, and show one example. - Hard. Fine-tune
flan-t5-smallon a tiny English → pig-Latin corpus (200 pairs). Measure BLEU on a held-out 50-pair set. Compare against fine-tuningLlama-3.2-1Bon the same data with the same compute.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Encoder-decoder | "Seq2seq transformer" | Two stacks: a bidirectional encoder for the input and a causal decoder with cross-attention for the output. |
| Cross-attention | "Where source talks to target" | The decoder's Q against the encoder's K/V. It's the only path by which encoder information reaches the decoder. |
| Span corruption | "T5's pretraining trick" | Replace random spans with sentinel tokens; the decoder outputs the spans. |
| Denoising objective | "BART's game" | Apply a noise function to the input and train the decoder to reconstruct the clean sequence. |
| Sentinel token | "The <extra_id_N> placeholder" | A special token that tags a corrupted span in the source and re-tags it in the target. |
| Flan | "Instruction-tuned T5" | T5 fine-tuned on >1,800 tasks; made encoder-decoder competitive at instruction-following. |
| Beam search | "Decoding strategy" | Keep the top-k partial sequences at each step; standard for translation and summarization. |
| Teacher forcing | "Training-time input" | During training, feed the decoder the true previous output token instead of the sampled one. |
Further Reading
- Raffel et al. (2019). Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer: T5.
- Lewis et al. (2019). BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension: BART.
- Chung et al. (2022). Scaling Instruction-Finetuned Language Models: Flan-T5.
- Radford et al. (2022). Robust Speech Recognition via Large-Scale Weak Supervision: Whisper, the canonical 2026 encoder-decoder.
- HuggingFace
modeling_t5.py: reference implementation.
Code
- main.py (python, 193 lines)