AI Engineer Roadmap All lessons Interactive Quiz MernStackDev GitHub

Encoder Decoder Models

Play the interactive lesson: Encoder-decoder modelsRead on GitHub
On this page
  1. Explain it like I'm new
  2. Watch it
  3. How it works
  4. Code example
  5. When to use it / when not to
  6. Common mistakes
  7. Recap
  8. Interview questions
  9. Go deeper

Level: Intermediate · Reading time: 8 min · Phase: 3 · LLM Concepts

In one line: An encoder-decoder model first reads the whole input with an encoder, then writes the output with a decoder that looks back at the encoded input through cross-attention.

Before you start: Self Attention and Decoder Only Models.

Words to know:

Explain it like I'm new

Think of a human interpreter at a meeting. First they listen to the full sentence in French (the encoder step: understand everything, in both directions). Then they speak the sentence in English word by word (the decoder step), glancing back at their notes of the French sentence as they go. Those glances are cross-attention.

This was the original transformer design from Attention Is All You Need, built for machine translation. Popular examples are T5 (which frames every task as "text in, text out") and BART. Speech recognition models such as Whisper also use this shape: audio in, text out.

The big difference from decoder-only models: the input gets bidirectional attention (every input token sees every other input token), and the input and output are separate sequences.

Watch it

Encoder-decoder models: One part reads the input, another writes the output.
Encoder-decoder models: One part reads the input, another writes the output.

It plays on a loop. Read the caption in the box, one step at a time. Want to go at your own pace? Step through it with Previous and Next.

How it works

  1. Encoder. The input tokens go through N layers of self-attention without a causal mask, plus feed-forward layers. Output: one context-rich vector per input token.
  2. Decoder. Generates the output one token at a time. Each decoder layer has three parts:
    • Masked self-attention over output tokens produced so far (causal).
    • Cross-attention: queries from the decoder, keys and values from the encoder output.
    • Feed-forward network.
  3. The decoder's final vector is turned into next-token probabilities, a token is chosen, and the loop repeats.
  4. The encoder runs once per input; only the decoder loops.
flowchart LR
    subgraph Encoder
      I[input tokens] --> E[self-attention, no mask<br/>+ feed-forward] --> H[encoded input]
    end
    subgraph Decoder
      O[output so far] --> D1[masked self-attention]
      D1 --> D2[cross-attention]
      H --> D2
      D2 --> D3[feed-forward] --> N[next token]
    end
    N -- append --> O

Worked example

The code example below uses 3 encoded input tokens and 1 output token so far:

  1. The encoder has already turned the input into [1,0], [0,1], [0.5,0.5].
  2. The decoder's current state is [0.9, 0.1], which acts as the query.
  3. The query is compared to each encoded input token. Token 1 is most similar, so the weights are about [0.43, 0.24, 0.32] (they add up to 1, give or take rounding).
  4. The result is the weighted blend of the inputs: [0.59, 0.41]. This blend flows into the decoder's feed-forward layer.
  5. At the next output token the decoder asks again, with a new query, against the same encoder output. The encoder does not run again.

Training is like the decoder-only case but the loss is only on the output tokens: given input X, predict output Y token by token. This is also called sequence-to-sequence (seq2seq) learning.

Code example

Cross-attention is the same attention function with Q from one sequence and K, V from another. Uses llm_attention.py.

from llm_attention import scaled_dot_product_attention

encoder_out = [[1.0, 0.0], [0.0, 1.0], [0.5, 0.5]]   # 3 input tokens, already encoded
decoder_state = [[0.9, 0.1]]                          # 1 output token so far

context, weights = scaled_dot_product_attention(
    q=decoder_state, k=encoder_out, v=encoder_out
)
print([round(w, 2) for w in weights[0]])  # which input tokens the decoder "looks at"
print(context)                            # blended information passed to the next layer

In real projects (Hugging Face Transformers, T5):

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

tok = AutoTokenizer.from_pretrained("t5-small")
model = AutoModelForSeq2SeqLM.from_pretrained("t5-small")
ids = tok("translate English to German: The house is small.", return_tensors="pt")
print(tok.decode(model.generate(**ids, max_new_tokens=20)[0], skip_special_tokens=True))

When to use it / when not to

Encoder-decoderDecoder-only
Input attentionbidirectionalcausal
Input/outputseparate sequencesone shared sequence
Extra layercross-attentionnone
Typical examplesT5, BART, WhisperGPT, Llama, Claude

Common mistakes

Recap

If you remember only three things:

Interview questions

1. What does cross-attention do? <details><summary>Answer</summary> It lets each decoder position query the encoder's outputs. Queries come from the decoder; keys and values come from the encoder. This is how the output stays tied to the input. </details>

2. Which parts use a causal mask in an encoder-decoder model? <details><summary>Answer</summary> Only the decoder's self-attention. The encoder's self-attention and the cross-attention are not causally masked (padding masks may still apply). </details>

3. Why have decoder-only models become more common for general LLMs? <details><summary>Answer</summary> They are simpler (one stack, one sequence), scale well with next-token prediction on raw text, and can handle input→output tasks by putting the input in the prompt. The tooling around them is also more mature. </details>

Go deeper

Prev: Decoder Only Models · Next: KV Cache · Back to phase: Phase 3