Skip to content

RNN, LSTM & GRU: Attempts to Fix Long Context

Overview

Before attention, the entire field wrestled with one question: how does a model use information from far back in a sequence? Recurrent models (RNN β†’ LSTM β†’ GRU) were a long series of attempts to answer it. Each generation fixed one failure mode β€” vanishing gradients, memory capacity, direction β€” but all of them left two fundamental problems unsolved: sequential computation and compressed memory. Attention succeeded precisely where these attempts failed.

Timeline of attempts:
  1986  RNN        β†’ can in principle, fails in practice (gradients)
  1997  LSTM       β†’ fixes vanishing gradients with gates
  2014  GRU        β†’ simpler LSTM, same limits
  2014  Bi-RNN     β†’ fixes direction (for understanding tasks)
  2015  Bahdanau attention β†’ first "look back directly" fix (inside RNN!)
  2017  Transformer β†’ removes recurrence entirely β†’ this chapter's subject
  • The problem being fixed: Attention Mechanisms β†’ The Problem: Why RNNs Fail at Long Context
  • The two unsolved failures: no parallelism + fixed-capacity memory
  • Key idea to remember: LSTMs made long-range memory possible; attention made it direct, parallel, and lossless

The Baseline: Plain RNN

How it works

h_t = tanh(W_x Β· x_t + W_h Β· h_{t-1} + b)

             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  x₁ β†’ [h₁] β†’ xβ‚‚ β†’ [hβ‚‚] β†’ x₃ β†’ [h₃] β†’ ... β†’ [h_T]
        β”‚             β”‚             β”‚              β”‚
        └── h₁ feeds β”€β”˜β”€β”€ hβ‚‚ feeds β”€β”˜β”€β”€ ...        └── final h_T = "summary"

Each step: take the new input x_t, mix it with the previous
hidden state h_{t-1}, squash through tanh, pass it on.

The strengths (why recurrence was attractive)

βœ… Variable length: one formula for any sequence length
βœ… Parameter sharing: same W_x, W_h for every position (few params)
βœ… Sequential structure: order is built into the computation
βœ… Cheap: O(T) time, O(1) extra state per step

The fatal weaknesses

❌ Failure 1 β€” Vanishing (or exploding) gradients:
   βˆ‚L/βˆ‚W ∝ Ξ _{k} J(h_k)   ← product of many Jacobians
   each step scales the gradient by ~β€–Jβ€– (often < 1)
     10 steps back β†’ Γ—0.9¹⁰ β‰ˆ 0.35
     50 steps back β†’ Γ—0.9⁡⁰ β‰ˆ 0.005
    100 steps back β†’ Γ—0.9¹⁰⁰ β‰ˆ 0.00003
   The network CANNOT learn long-range dependencies:
   the gradient signal is dead before it reaches the distant past.

❌ Failure 2 β€” No parallelism:
   h_t needs h_{t-1}; h_{t-1} needs h_{t-2}...
   T steps = T serial GPU operations (a 1,000-token sentence is
   1,000 sequential matmuls β€” the GPU sits idle between steps)

❌ Failure 3 β€” One-vector memory:
   everything seen so far must fit in ONE fixed-size vector h_t
   β†’ old information gets overwritten by new information
   β†’ capacity is the bottleneck even when gradients survive

πŸ’‘ Key Insight: RNNs in principle can remember arbitrarily long (they're Turing-complete), but gradient math and fixed-size memory make it impractical. The LSTM was the first serious engineering fix.


Attempt 1: LSTM (1997) β€” Fix the Vanishing Gradient

Long Short-Term Memory (Hochreiter & Schmidhuber, 1997) attacked failure #1 head-on with a gated, additive memory path.

The central trick is a separate memory, the cell state C_t, updated by addition instead of multiplication:

Gate formulas (βŠ™ = element-wise multiply, Οƒ = sigmoid):

  f_t = Οƒ(W_f Β· [h_{t-1}; x_t] + b_f)     forget gate   β†’ what to DROP from memory
  i_t = Οƒ(W_i Β· [h_{t-1}; x_t] + b_i)     input gate    β†’ what to WRITE to memory
  C̃_t = tanh(W_c · [h_{t-1}; x_t] + b_c)  candidate      → proposed new content
  C_t = f_t βŠ™ C_{t-1} + i_t βŠ™ CΜƒ_t        cell update   β†’ additive!
  o_t = Οƒ(W_o Β· [h_{t-1}; x_t] + b_o)     output gate   β†’ what to REVEAL
  h_t = o_t βŠ™ tanh(C_t)                   hidden state
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ C_t-1 ─────────────────────────┐
       β”‚                                                        β–Ό
x_t ───┼──► Οƒ (forget f) ─────┐                          β”Œβ”€ multiply ─► C_t
h_t-1 ─┼──► Οƒ (input i) ──────┼──► candidate CΜƒ ──► multiply
       └──► tanh (candidate) β”€β”˜        β–²                    β”‚
            └──► Οƒ (output o) ─────────┴──► tanh ──► multiply ─► h_t

Why this fixes vanishing gradients

Plain RNN path:   h_t = tanh(WΒ·[...])  β†’ every step is a tanh multiply
                  gradient Γ— (tanh' ≀ 1) each step β†’ decays

LSTM path:        C_t = f_t βŠ™ C_{t-1} + i_t βŠ™ CΜƒ_t
                  gradient flows through C_{t-1} with multiplier β‰ˆ f_t
                  if f_t β‰ˆ 1 (gate OPEN) β†’ gradient passes ~unchanged

This is the "Constant Error Carousel": the cell can carry a signal
back hundreds of steps because the path is ADDITIVE, not a chain of
squashing functions. Training a 100-step dependency becomes feasible.

Worked gate example (toy numbers)

Given: h_{t-1} = 0.2,  x_t = 0.8,  C_{t-1} = 1.0
Pre-activations (already computed by the weights):

  forget:    z_f = 0.5   β†’ f = Οƒ(0.5) = 0.62     (keep 62% of old memory)
  input:     z_i = 1.0   β†’ i = Οƒ(1.0) = 0.73     (write 73% of candidate)
  candidate: z_c = βˆ’0.3  β†’ CΜƒ = tanh(βˆ’0.3) = βˆ’0.29
  output:    z_o = 0.2   β†’ o = Οƒ(0.2) = 0.55

Cell update:
  C_t = f·C_{t-1} + i·C̃
      = 0.62Β·1.0 + 0.73Β·(βˆ’0.29)
      = 0.62 βˆ’ 0.21 = 0.41

Hidden state:
  h_t = oΒ·tanh(C_t) = 0.55Β·tanh(0.41) = 0.55Β·0.39 β‰ˆ 0.21

Interpretation: the LSTM kept most of its old memory (0.62 factor),
wrote a little negative candidate, and output a bounded view of it.

What LSTMs still couldn't fix

❌ Sequential computation remains: C_t still depends on C_{t-1}
   β†’ training is T serial steps (slow, GPU-starved)

❌ Fixed-capacity memory remains: one cell vector per position;
   keeping "cat" for 16 steps means it still shares the cell with
   everything else β€” information is mixed, not addressable

❌ Long-range is *possible* but *fragile*:
   - forget gate is rarely fully open: f β‰ˆ 0.6–0.9 in practice
   - old content decays: 0.9¹⁢ β‰ˆ 0.19, 0.8¹⁢ β‰ˆ 0.03 of the signal
   - gates are a *learned* trade-off: keeping everything hurts
     (the LSTM learns to forget to fit new material)

❌ No direct "pointer" to a distant token β€” to use token #5's content
   at token #100, it must survive 95 compressions intact

Attempt 2: GRU (2014) β€” Simplify the LSTM

Gated Recurrent Unit (Cho et al., 2014) merged the forget and input gates into one update gate and dropped the separate cell state.

  z_t = Οƒ(W_zΒ·[h_{t-1}; x_t])              update gate (how much to change)
  r_t = Οƒ(W_rΒ·[h_{t-1}; x_t])              reset gate  (how much old info to use)
  hΜƒ_t = tanh(W_hΒ·[r_t βŠ™ h_{t-1}; x_t])     candidate
  h_t = (1 βˆ’ z_t) βŠ™ h_{t-1} + z_t βŠ™ hΜƒ_t    blend old and new

2 gates, 3 weight matrices (vs LSTM's 3 gates, 4 matrices)
LSTM:  3 gates (f, i, o) + cell state C      β†’ 4 matrices
GRU:   2 gates (z, r), no separate cell      β†’ 3 matrices

Same core idea, fewer parameters:
  - trains faster, needs less data
  - performance β‰ˆ LSTM on most tasks (sometimes better, sometimes worse)
  - still sequential, still fixed-memory, still decay-prone

Attempt 3: Bidirectional & Stacked RNNs β€” Fix Direction and Depth

Bidirectional RNNs (Schuster & Paliwal, 1997)

Forward pass:  h₁ β†’ hβ‚‚ β†’ ... β†’ h_T     (left-to-right context)
Backward pass: h₁ ← hβ‚‚ ← ... ← h_T     (right-to-left context)
Concatenate:   [h_forward; h_backward]  (both directions available)

Fixed: understanding tasks benefit from FUTURE context
  ("bank" needs the word AFTER it to be disambiguated)
Still broken: sequential in both passes (2Γ— slower), still compressed

Stacked (deep) RNNs

hΒΉ_t β†’ hΒ²_t β†’ hΒ³_t β†’ ...   (hierarchical abstractions per layer)

Fixed: more expressive features per position
Worsened: depth multiplies the vanishing-gradient problem;
  needs skip-connections/gating to even train deep stacks

Attempt 4: Attention Inside RNNs (Bahdanau, 2015) β€” the patch that worked

The first attention mechanism was not a transformer β€” it was added to an LSTM encoder–decoder to fix one specific problem: the decoder had to squeeze the whole source sentence into a single final vector.

The fix: let the decoder "look back" at every encoder state

Instead of:  context = ONE vector (the last encoder state)
Now:         c_t = Ξ£β±Ό Ξ±_tj Β· h_j        ← weighted sum of ALL encoder states

  e_tj = vα΅€ tanh(W_aΒ·[s_{t-1}; h_j])    (alignment score)
  Ξ±_tj = softmax(e_tj)                  (attention weight over source)
  c_t  = Ξ£β±Ό Ξ±_tj Β· h_j                  (context vector β€” direct lookup!)
  s_t  = RNN(s_{t-1}, [y_{t-1}; c_t])   (decoder update)
Decoder generating "The cat":
  step 1 ("The")  β†’ attends to: "Die"(0.55) "Katze"(0.10) ...
  step 2 ("cat")  β†’ attends to: "Katze"(0.70) ...

The decoder READS the relevant source words directly β€”
no 95-step compression survival required.

Why this matters for the story

βœ… This is the exact mechanism this chapter studies β€” but inside an RNN
βœ… It proved "direct connection to the past" is the right fix
❌ The RNN core still serialized everything: attention fixed MEMORY,
   not PARALLELISM β€” T steps were still T serial computations

The Transformer (Vaswani 2017) took the next and final step:
remove the RNN entirely β†’ attention is the ONLY token-to-token
mechanism β†’ everything is parallel.

Attempt 5: The Transformer (2017) β€” Remove Recurrence

Recurrent:  h_t = f(h_{t-1}, x_t)            (must finish tβˆ’1 first)
Transformer: out = softmax(QΒ·Kα΅€/√d)Β·V         (all positions at once)

Fixes the two remaining failures in one stroke:
  Parallelism:  one big matmul, not T serial steps
  Memory:       every token is addressable at ANY distance with O(1) steps
                β€” "its" reaches "cat" directly, no compression in between

New costs introduced:
  O(nΒ²) compute/memory  β†’ see [06 Complexity & Cost](/02-llm-modeling/00-fundamentals/01-attention/06-complexity-&-cost/)
  position-blind        β†’ see [05 Position Information](/02-llm-modeling/00-fundamentals/01-attention/05-position-information/)

The Scoreboard

Attempt Year Fixed Left broken
Plain RNN 1986 β€” gradients, parallelism, memory capacity
LSTM 1997 vanishing gradients (gated additive cell) parallelism, capacity, fragile long-range
GRU 2014 simpler/faster training same as LSTM
Bi-RNN 1997/2014 direction (both contexts) sequential Γ—2, capacity
Bahdanau attention 2015 last-vector bottleneck (direct lookup) sequential core remains
Transformer 2017 parallelism + direct memory O(nΒ²) cost, position-blind
The pattern: every recurrent attempt fixed ONE symptom.
Attention was the first fix that addressed the ROOT:
"stop compressing β€” give every token a direct wire to every other token."

Concrete Failure: The "its" Problem Through an LSTM

Sentence: "The cat, which was adopted from the shelter last winter,
           finally settled into its new home."

Distance from "cat" to "its" β‰ˆ 16 tokens.

LSTM path:
  - "cat" is stored in the cell at position 2
  - every step, the forget gate multiplies the cell (f β‰ˆ 0.6–0.9)
  - signal remaining after 16 steps:  0.9¹⁢ β‰ˆ 0.19  (best case)
                                      0.8¹⁢ β‰ˆ 0.03  (typical)
  - AND the cell also holds "shelter", "winter", "settled", "home"...
    β†’ "cat" must survive as one component among many, compressed

Attention path:
  - at "its", the query matches the key of "cat" directly
  - score β†’ softmax weight β†’ value flows in ONE step
  - distance is irrelevant: 16 steps or 16,000, same mechanism

LSTM result:  confused pronoun reference (shelter/home steal attention)
Transformer:  "its" β†’ "cat" with high weight (see [08 Interpretability](/02-llm-modeling/00-fundamentals/01-attention/08-interpretability/))

Why Attention Ultimately Won (Three Reasons)

1. DIRECT ACCESS β€” every token can read any other token with O(1)
   steps; no information has to "survive" compression or decay
2. PARALLELISM β€” the whole sequence is processed as one matmul;
   GPUs (which are massive parallel machines) finally get fed
3. SCALABILITY β€” training data Γ—1000 (the LLM era) is only usable
   because transformers train fast enough to exploit it;
   LSTMs at GPT-3 scale would take decades of wall-clock time

LSTM/GRU are not obsolete β€” they still power:
  - speech processing, some time-series models
  - hybrid architectures, state-space models (Mamba) revisit their ideas
But for text, attention won outright.

Key Takeaways

πŸ” RNN β†’ LSTM β†’ GRU: a history of patching one failure at a time
🧠 LSTM's gift: the gated additive cell (constant error carousel) tamed vanishing gradients
🚧 The wall they all hit: sequential computation + fixed-capacity memory
🎯 Bahdanau (2015): attention was first an RNN patch β€” "look back directly"
πŸ›οΈ Transformer (2017): removed recurrence β†’ parallel + addressable memory
βš–οΈ Trade-off: attention pays O(nΒ²) for what recurrence gave away for free (order, linear cost)
🧭 Not useless today: LSTM ideas live on in Mamba/state-space models and sequence audio models