Attention Mechanism
Instead of reading one fixed summary vector, let the decoder look back at every encoder state and weigh each one by how relevant it is to the current step.
Why Does This Exist?
The sequence-to-sequence architecture has one specific, well-diagnosed wall: every fact about the input sentence has to survive being compressed into a single fixed-size vector before the decoder ever sees it. For a five-word sentence that's rarely a problem. For a 40-word sentence, it is — translation quality on that architecture drops off a cliff past roughly 20 to 30 source tokens, and the drop isn't a training artifact. It's the architecture asking a fixed-size container to hold an unboundedly large amount of information.
Here's the concrete failure researchers were chasing in 2014 and 2015: translating a long sentence, the decoder would nail the first few words, generated while the context vector was still fresh, and then visibly degrade — dropping clauses, repeating phrases, losing track of which source word it was supposed to be rendering next. The bottleneck was measurable in exactly the sentences you'd expect: the long ones.
Think of It Like This
Highlighting a textbook instead of memorizing it first
Recall the earlier analogy for sequence-to-sequence: reading a report once, closing it, and writing a summary purely from memory. Attention is what happens when you're allowed to keep the report open on the desk while you write, glancing back at whichever paragraph is relevant to the sentence you're writing right now.
You don't reread the whole report for every sentence of your summary — you glance at the part that matters for this sentence, weighted by how relevant each paragraph is to what you're currently writing. Write the next sentence, and you might glance somewhere else entirely. Nothing was ever compressed into one fixed memory that had to hold everything; the whole report stays available, and each moment draws on it differently.
How It Actually Works
Score, then weigh, then blend
At each decoder step , attention runs three operations against every encoder hidden state — not just the last one.
Score. Compare the current decoder state against each encoder state, producing one number per encoder position:
(This is one common form — dot-product attention, up to a learned matrix . Additive attention runs the two vectors through a small feedforward layer instead; the scoring function varies, the rest of the mechanism doesn't.)
Weigh. Turn the raw scores into a probability distribution with softmax:
Every is between 0 and 1, and they sum to exactly 1 across all encoder positions — a genuine probability distribution over "how relevant is each source position, right now."
Blend. Use those weights to combine the encoder states into a context vector for this specific step:
That then feeds into the decoder alongside its own hidden state to produce the next output token.
Why this removes the bottleneck
Compare here against the sequence-to-sequence context vector : the old version was one vector, computed once, used at every decoding step regardless of what was actually being generated. This is recomputed at every single decoder step, and it's a weighted combination of all encoder states, not a compression of them into a smaller fixed vector. The encoder's full output — every , for every input position — stays available throughout decoding. Nothing about the input has to survive a bottleneck, because there is no bottleneck: the decoder can, in principle, put nearly all of its attention weight on the one encoder position that matters for the word it's producing right now, and different weight entirely for the next word.
The diagram above shows this concretely: decoding position 2 puts weight 0.7 on encoder state , and smaller weights on and . Decoding position 3 would very plausibly redistribute that weight entirely differently — attention isn't fixed once and reused; it's a fresh computation at every step, conditioned on what the decoder is doing right now.
What attention weights actually reveal
Because is an explicit, inspectable number, attention weights double as a rough diagnostic: plotting them for a translation task typically shows a soft diagonal, with visible off-diagonal spikes exactly where word order differs between the two languages. That interpretability, on top of the accuracy gain, is part of why attention spread so quickly once it worked.
Show Me the Code
Scoring, softmax, and blending, run explicitly rather than fused, against three encoder states and one decoder state.
import numpy as np
def softmax(z: np.ndarray) -> np.ndarray: e = np.exp(z - z.max()) return e / e.sum()
def attend(d_t: np.ndarray, encoder_states: np.ndarray, w_a: np.ndarray) -> tuple[np.ndarray, np.ndarray]: scores = encoder_states @ w_a @ d_t # one score per encoder position weights = softmax(scores) # sums to exactly 1 context = weights @ encoder_states # weighted blend of ALL encoder states return context, weights
rng = np.random.default_rng(0)encoder_states = rng.normal(size=(3, 4)) # 3 source positions, hidden size 4d_t, w_a = rng.normal(size=4), rng.normal(scale=0.3, size=(4, 4))context, weights = attend(d_t, encoder_states, w_a)print(np.round(weights, 3)) # -> [0.319 0.354 0.327] — close, none dominant this stepprint(np.round(weights.sum(), 6)) # -> 1.0weights.sum() is exactly 1.0 by construction — that's the softmax, not a coincidence — and context is a genuine weighted average of all three rows of encoder_states, never a copy of just one.
Watch Out For
Forgetting attention weights change every decoder step
It's tempting to think of attention as computing one alignment between source and target sentences, the way a bilingual dictionary aligns words once. It doesn't — is indexed by , the current decoder step, precisely because the relevant source position usually shifts as generation proceeds. Caching or reusing attention weights across decoder steps, instead of recomputing the score-softmax-blend sequence fresh each time, silently breaks the entire mechanism: the decoder ends up looking at whatever was relevant to an earlier word for every word after it.
Unscaled dot-product scores collapsing the softmax
As the hidden dimension grows, raw dot-product scores tend to grow in magnitude along with it, and a softmax over large, spread-out scores collapses toward a near one-hot spike — all the weight on a single position, gradients near zero everywhere else. This is exactly the instability that motivated dividing scores by in the scaled dot-product form used throughout modern attention. If attention weights are consistently near-one-hot from the very first training step, check whether scores need scaling before assuming the model has genuinely learned a sharp alignment.
The Quick Version
- Attention lets a decoder look back at every encoder state, at every step, instead of relying on one fixed-size summary vector.
- Three operations per step: score each encoder state against the current decoder state, softmax the scores into weights summing to 1, and blend the encoder states by those weights.
- The blended context vector is recomputed fresh at every decoder step — it isn't fixed once and reused.
- This removes the sequence-to-sequence bottleneck because the full encoder output stays available throughout decoding.
- Attention weights are inspectable and typically show a soft, interpretable alignment between output and input positions.
What to Read Next
- Sequence-to-Sequence is the architecture and the fixed-vector bottleneck this page's mechanism exists to remove.
- Bidirectional RNNs commonly produce the encoder states that attention scores against.
- Long Short-Term Memory is the recurrent cell most often paired with attention in this generation of architectures.
- Temporal Convolutional Networks is a non-recurrent alternative worth contrasting against an attention-augmented recurrent decoder.