Phase 19 · Lesson 59 of 85

Cross-Attention Fusion

The projection layer aligns one image vector with one caption vector. A real vision-language decoder needs every text token to attend to every patch token, so the model can ground each word in a region. Cross-attention is how that grounding happens. The text queries; the vision keys and values answer. This lesson builds the cross-attention block, the causal text self-attention, and the mask shapes that keep both legal.

Lesson 59 of 85Build~90 minutesPython

Before this: Lessons 28 to 35 of Phase 19 (Track B foundations)

On this page

Learning Objectives

  • Implement multi-head cross-attention where the query stream is text and the key/value stream is vision.
  • Compose a decoder block: causal self-attention + cross-attention + feed-forward.
  • Get the mask shapes right: causal mask for self-attention, no mask for cross-attention.
  • Run a forward pass with batched text tokens and a fixed pool of image tokens.

The Problem

Concatenating image tokens and text tokens into one sequence is one fusion option (early fusion, the path Chameleon and Emu3 take). Cross-attention is the other (late fusion, the path Flamingo introduced and that every Flamingo-shaped decoder since has copied). In late fusion, the text decoder runs on text-only tokens and reaches over into the image stream through cross-attention at every layer.

Late fusion has two advantages. First, the text stream stays clean and the model preserves text-only capabilities. Second, the image stream is computed once per image and reused for every decode step, so generation is cheap even for long captions. The cost is one extra attention sub-layer per block.

The Concept

Diagram (opens full size)
Diagram (opens full size)

Mask shapes

The two attentions inside a decoder block need different masks:

Attention Query length Key length Mask Why
Self-attention Nt (text) Nt (text) Causal: lower-triangular (Nt, Nt) Text tokens may not look ahead during autoregression
Cross-attention Nt (text) Nv (vision) No mask The whole image is visible to every text position

The lesson includes one shape-validation function so the mistake of mixing them up surfaces as a ValueError instead of a silently broken loss curve.

Why no mask on cross-attention

The image is fully observed before any text is generated. Token t of the caption may attend to any patch of the image; there is no temporal order on image patches. Some Flamingo variants add a per-sample masking pattern when interleaving multiple images and text segments, but for a single image plus a caption, cross-attention sees everything.

Key/value caching

The image keys and values are computed once at the start of the decode and held in a cache. Each new text token uses the cache without recomputation. This is what makes captioning fast at inference: the heavy ViT runs once; the cross-attention reuses its keys and values for every step. The lesson exposes the cache and tests the cache-hit path.

Block composition

A decoder block runs: pre-LN -> self-attention -> residual -> pre-LN -> cross-attention -> residual -> pre-LN -> feed-forward -> residual. Three sub-layers, each with its own LayerNorm. The Flamingo paper added a learned tanh gate on cross-attention, initialised at zero, so the frozen language model starts out unchanged and training stays stable; the canonical baseline (used here) has no gate.

class DecoderBlock:
  def forward(self, text_tokens, image_tokens, text_mask, cross_mask):
      text_tokens = text_tokens + self.self_attn(self.ln1(text_tokens),
                                                 mask=text_mask)
      text_tokens = text_tokens + self.cross_attn(self.ln2(text_tokens),
                                                  image_tokens,
                                                  mask=cross_mask)
      text_tokens = text_tokens + self.ffn(self.ln3(text_tokens))
      return text_tokens
ch-crossattn-fan

Build It

code/main.py implements:

  • CrossAttention(hidden, heads), multi-head cross-attention with separate q and kv projections.
  • CausalSelfAttention(hidden, heads), the masked self-attention from a standard decoder.
  • DecoderBlock, composing the three sub-layers with pre-LN residuals.
  • VisionLanguageDecoder, four-layer decoder fed by a mock vision encoder output and a small text embedding table.
  • causal_mask(length) returning a (length, length) lower-triangular boolean tensor.
  • A demo that feeds a batch of two text sequences of length 10 with image memory of length 197 and prints output shape, the self-attention mask shape, and the cross-attention output norm per position.

Run it:

python3 code/main.py

Output: decoder produces a (2, 10, text_vocab) logits tensor. Mask shape is (10, 10). The KV-cache reuse check confirms identical logits between the cached and uncached paths.

Use It

Cross-attention shows up in two production families:

  • Flamingo and IDEFICS. Insert a cross-attention sub-layer every K language model blocks, with a frozen LM. The vision-language adapter is the cross-attention block plus its gate.
  • BLIP-2. The Q-Former uses cross-attention from a fixed set of 32 query tokens into the image features, then projects the queries into the LM embedding space.

The shape of the block in this lesson maps directly onto both. The mask discipline (causal on self, none on cross) is the same.

Tests

code/test_main.py covers:

  • causal mask is lower-triangular and matches expected boolean shape
  • cross-attention output shape is (B, Nt, hidden) regardless of key length
  • KV-cache path matches uncached path to float tolerance
  • shape mismatch between text and image streams raises a clear ValueError
  • a full decoder forward pass produces the right batch and sequence shape

Run them:

python3 -m unittest code/test_main.py

Exercises

  1. Add a learned tanh gate to the cross-attention residual (the Flamingo trick) and verify training converges from a near-zero initial gate. The gate starts at 0; the model recovers text-only behavior before mixing the image stream in.

  2. Implement interleaved attention where the same decoder consumes multiple images plus multiple text segments. Build the per-sample cross-attention mask that prevents text segment 2 from attending to image 1.

  3. Profile the cross-attention vs the self-attention layer at Nt=64, Nv=576 (a 24x24 grid at higher resolution). The cross-attention cost is Nt * Nv and dominates at high image resolution.

  4. Add a query-side dropout on the cross-attention map and measure caption diversity on the demo (caption sample variance increases with dropout in the cross map).

  5. Swap the cross-attention layer for a Q-Former-style attention block where a fixed 32-token query pool attends to image features once per layer.

Key Terms

Term What it means
Late fusion Text and vision stay in separate streams; cross-attention bridges them at every block
Cross-attention Q comes from one stream, K and V from another
Causal mask Lower-triangular boolean mask that prevents looking ahead during autoregression
KV cache Image keys and values stored once and reused for every decode step
Memory tokens The frozen image tokens that the decoder reaches into

Further Reading

  • Flamingo (2022) for the canonical late-fusion design with gated cross-attention.
  • BLIP-2 (2023) for the Q-Former, which is a cross-attention block dressed as a learned query pool.
  • IDEFICS (2023) for an open-weight reproduction of the Flamingo recipe.

Check yourself

Quick quiz

  1. What is the operational difference between early fusion and late fusion in vision-language modeling?

    1. Late fusion uses more memory
    2. Early fusion is illegal
    3. Early fusion concatenates image and text tokens into one sequence; late fusion keeps them separate and bridges via cross-attention at each block
    4. Early fusion runs first in wall-clock time
    Show the answer

    C. Early fusion concatenates image and text tokens into one sequence; late fusion keeps them separate and bridges via cross-attention at each block Chameleon and Emu3 are early-fusion; Flamingo and BLIP-2 are late-fusion. The architectural choice changes mask shapes and KV caching.

  2. In a decoder block with both self-attention and cross-attention, which one uses a causal mask?

    1. Neither
    2. Both
    3. Cross-attention only
    4. Self-attention only; the image is fully observed and cross-attention has no temporal order
    Show the answer

    D. Self-attention only; the image is fully observed and cross-attention has no temporal order Text generation is autoregressive, so text self-attention is causal. Image tokens are all visible before any text is decoded.

  3. Why is the cross-attention KV cache built once per image?

    1. Image keys and values do not change as text is decoded, so projecting memory once and reusing the K and V across all decode steps is the whole inference speedup
    2. It avoids using the GPU
    3. PyTorch requires caching
    4. Caching saves disk space
    Show the answer

    A. Image keys and values do not change as text is decoded, so projecting memory once and reusing the K and V across all decode steps is the whole inference speedup The vision encoder runs once. Its KV projection is computed once. Each text token reuses the cache for free.

  4. What is the output shape of cross-attention when text length is Nt=10 and image length is Nv=197 with hidden=256?

    1. (B, 10, 256)
    2. (B, 207, 256)
    3. (B, 10, 197)
    4. (B, 197, 256)
    Show the answer

    A. (B, 10, 256) Cross-attention output shape follows the query stream, so output is (B, Nt, hidden) regardless of key length.

  5. Why is no mask used on cross-attention?

    1. Masks always cause NaN
    2. Masks are too expensive
    3. The image is fully observed before text generation begins; every text position may attend to every patch
    4. PyTorch does not support cross masks
    Show the answer

    C. The image is fully observed before text generation begins; every text position may attend to every patch There is no temporal order on image patches and no leakage to prevent, so cross-attention sees the whole image.

  6. Which extension would you add to recover Flamingo-style stability during training?

    1. Remove the FFN
    2. Insert a learned tanh gate on the cross-attention residual so the model can start from text-only behavior and grow into the image stream
    3. Drop the self-attention layer
    4. Use only one attention head
    Show the answer

    B. Insert a learned tanh gate on the cross-attention residual so the model can start from text-only behavior and grow into the image stream Flamingo's tanh gate starts at zero, recovers text-only behavior at init, and gives the model a smooth ramp into cross-attention.

Questions people ask

What is cross-attention?

In cross-attention, queries come from one sequence and keys and values come from another. In vision-language models, text tokens ask questions and image patches answer. This lets each word draw on the most relevant parts of the image.

What is the difference between self-attention and cross-attention?

Self-attention looks within one sequence, such as text attending to earlier text. Cross-attention links two sequences, such as text attending to image patches. Many multimodal decoders use both in each block.

Next lessonVision-Language PretrainingPrevious: Projection Layer for Modality Alignment

Lessons from AI Engineering from Scratch by Rohit Ghumare and contributors, MIT license. Notes for owners by Lozo. License

Next step

Rather have it built for you?

Reading is optional. Tell me what's stuck and I'll build it into the business you already run.

  • A real conversation, not a pitch
  • You keep your accounts
  • Nothing sends without your yes