DL S033 · LSTM and GRU — Gating the Gradient Highway
The 1997 architecture that fixed Hochreiter's vanishing-gradient headache — plus its 2014 streamlined cousin the GRU. Cell state as a gradient highway, four gates, one worked example, and the exact structural insight that later became the transformer's residual connection.
🎯 Understand exactly WHY the LSTM's cell state solves Hochreiter's vanishing-gradient problem, implement an LSTM cell in ~30 lines, compare it to the leaner GRU, and see how the same 'gated additive update' idea reappears as ResNets, Highway Networks, and transformer residuals.
Series: Deep Learning & LLMs From Scratch — 80 sessions · Session 33 / 80 · Module M06 · Enriched pass 2026-07-27
The story
0 · The paper that took ten years to be believed
Vienna, 1997. Sepp Hochreiter and Jürgen Schmidhuber publish Long Short-Term Memory in Neural Computation, volume 9, issue 8. The paper is 32 dense pages. It contains a strange-looking cell diagram with something called a "constant error carousel", three multiplicative gates, and an argument (in section 3.2) that gradient flow through this cell is uniform across time — no vanishing, no exploding.
Almost nobody believed them. The paper accumulated a slow trickle of citations through the late 1990s and early 2000s. Neural nets themselves were in a research winter; the ImageNet moment was still fifteen years away. Even people who were doing sequence modelling preferred simpler RNNs, or Hidden Markov Models, or n-grams.
Then two things happened at once. In 2013 Alex Graves used stacked LSTMs to smash the state of the art on handwriting recognition and TIMIT speech, then wrote a landmark technical report generating handwriting with an LSTM one stroke at a time — the outputs went viral. In 2014 Google Translate switched to an encoder-decoder LSTM (Sutskever, Vinyals, Le Sequence to Sequence Learning) and delivered a jump in translation quality that stunned the industry. By 2015, LSTMs were the default sequence model in every deep-learning textbook. Hochreiter's 1997 paper — snubbed for over a decade — had, at long last, taken over.
The trick is beautiful and, once you see it, obvious. The plain RNN destroys gradient by multiplying the hidden state by at every step. The LSTM has a separate cell state that is added to rather than multiplied by the previous value. Addition preserves gradient. Multiplication (usually) does not. Everything else — the three gates, the tanh activations, the peephole connections — is engineering around that one idea.
Today you build the LSTM. You feel why the cell state is a gradient highway. You meet its 2014 cousin the GRU (simpler, similar performance, one fewer gate). And you'll see the same architectural motif — additive skip path guarded by a sigmoid gate — repeated verbatim in Highway Networks (2015), ResNets (2015), and every transformer residual connection ever written. Once you spot the pattern, you spot it everywhere.
1 · What you'll build today
One sentence: You will implement an LSTM cell in ~30 lines of PyTorch, compare its gradient flow to the plain RNN from S032, and internalise why "additive cell state + sigmoid gates" is the architectural motif that makes deep sequence models trainable.
By the end you will:
- Draw the LSTM cell diagram from memory (input, forget, output, candidate — four learned linear maps).
- Explain, with equations and pictures, why is (approximately) the identity matrix scaled by the forget gate, and why that makes gradient flow across hundreds of timesteps.
- Implement
LSTMCellandGRUCellfrom scratch in PyTorch. - Trace the intellectual lineage: LSTM (1997) → forget-gate improvement (Gers et al. 2000) → Highway Networks (2015) → ResNets (2015) → transformer residual (2017) → modern xLSTM revival (2024).
- Choose sensibly between LSTM, GRU, and transformer for a given task.
- The plain RNN forward pass and its vanishing-gradient failure mode (S032).
- Sigmoid vs tanh: outputs (0,1) vs (-1,1), both saturate (S007).
- Backprop through elementwise multiplication (Hadamard product) (S008).
- Cross-entropy for token classification (S018).
- PyTorch autograd on custom modules (S014-S015).
2 · The core idea — a gradient highway
The plain RNN update was:
Gradient flowing backward through this update gets multiplied by at every step. That product goes to zero (or infinity) fast.
The LSTM's central trick: introduce a second hidden vector called the cell state , and update it via addition rather than multiplication.
where is elementwise product, (forget gate) and (input gate) are sigmoid vectors in , and is a candidate update from the current input. The Jacobian of this update:
That is a diagonal matrix, with entries in . If the forget gate is close to 1 (the model wants to remember), the Jacobian is approximately the identity matrix. Multiply the identity by itself a hundred times and you get the identity — no vanishing, no exploding. Gradient flows across time as if along a highway.
2.1 Why this simple observation was a breakthrough
Hochreiter's 1991 thesis had already identified the disease. Several groups in the mid-1990s tried architectural workarounds — Bengio et al. 1994, Lin et al. 1995 — that limited how much gradient could vanish, but none preserved it fully. The LSTM's insight was structural, not incremental: do not try to prevent multiplication from destroying gradient; add an additive path where gradient does not go through multiplication at all. That reframing — "route around the problem instead of shrinking it" — is a recurring pattern in deep learning. It later reappeared as the ResNet trick (2015): don't try to make deep networks trainable by clever initialisation; add a skip connection so gradient bypasses the depth entirely.
3 · The four gates — with pictures then equations
An LSTM cell computes four things at each timestep, all from the same inputs (x_t, h_{t-1}):
- Forget gate — "what fraction of each cell-state dimension should we erase?"
- Input gate — "what fraction of each candidate dimension should we admit?"
- Candidate cell state — "if we did write to the cell, what would we write?"
- Output gate — "what fraction of each cell-state dimension should be exposed as the hidden state right now?"
Then the state updates:
The gate values are computed from both the current input and the previous hidden state, so the model can decide "given what I just saw and what I already know, forget dimensions 3–7 of my memory and write new stuff to dimensions 12–20".
3.1 A worked example on 2-dimensional state
Set so we can inspect every number. Suppose at timestep :
- (some accumulated memory)
- (keep dim 0 mostly, aggressively forget dim 1)
- (write a little to dim 0, a lot to dim 1)
Then:
Notice how dim 1 of has been rewritten (-0.3 → 0.71) while dim 0 has been slightly updated (0.8 → 0.81). The gates gave the model fine-grained control over which memory slots to preserve vs overwrite. Try this on paper for one or two more timesteps until it clicks.
3.2 Peephole connections (a footnote)
Gers, Schraudolph, Schmidhuber (2002) proposed peephole connections — let the gates see directly, not just via :
Marginal improvement, mostly on very precise timing tasks. Rarely used in modern PyTorch code. Mention it for completeness; skip in your own implementations.
4 · Implement LSTMCell from scratch
The four-gate LSTM in PyTorch, written out explicitly:
import torch
import torch.nn as nn
import torch.nn.functional as F
class MyLSTMCell(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
# One big linear map from [x, h] to [f, i, ĉ, o] concatenated.
# This is exactly what PyTorch's built-in LSTMCell does — bundling the four
# gate projections into a single (4H) matmul is a huge GPU efficiency win.
self.linear = nn.Linear(input_dim + hidden_dim, 4 * hidden_dim)
self.H = hidden_dim
# Initialise forget-gate bias to 1 — Jozefowicz et al. 2015 trick.
with torch.no_grad():
self.linear.bias[hidden_dim:2*hidden_dim].fill_(1.0)
def forward(self, x, state):
h_prev, c_prev = state # (B, H) each
cat = torch.cat([x, h_prev], dim=-1) # (B, D+H)
gates = self.linear(cat) # (B, 4H)
f, i, g, o = gates.chunk(4, dim=-1) # each (B, H)
f = torch.sigmoid(f) # forget
i = torch.sigmoid(i) # input
g = torch.tanh(g) # candidate (ĉ)
o = torch.sigmoid(o) # output
c = f * c_prev + i * g # additive cell update
h = o * torch.tanh(c)
return h, (h, c)Three implementation points that matter:
- Bundled 4H projection. Instead of four separate
nn.Linear(D+H, H)layers, we do onenn.Linear(D+H, 4H)andchunkthe output. This is 4× fewer matmul launches on GPU. PyTorch's built-innn.LSTMCelldoes the same. Roughly 2× faster in practice. - Forget-gate bias init to 1. Jozefowicz, Zaremba, Sutskever 2015, An Empirical Exploration of RNN Architectures showed that initialising the forget-gate bias to 1 (so the initial forget gate is , closer to "keep") speeds up training substantially and often improves final performance. This is now standard.
torch.sigmoidandtorch.tanhare numerically safer thanF.sigmoid/F.tanh(deprecated aliases). Use the tensor-methodsx.sigmoid()/x.tanh()if you prefer.
4.1 Unrolling over a sequence
def lstm_forward(cell, xs, h0=None, c0=None):
B, T, D = xs.shape
H = cell.H
if h0 is None: h0 = xs.new_zeros(B, H)
if c0 is None: c0 = xs.new_zeros(B, H)
state = (h0, c0)
outputs = []
for t in range(T):
h, state = cell(xs[:, t], state)
outputs.append(h)
return torch.stack(outputs, dim=1), stateNow swap this into the character-RNN training loop from S032 and retrain. You'll see the loss drop from ~2.0 (plain RNN final) to ~1.5 in the same number of steps — a large improvement for a small code change. Generations become noticeably more coherent, with fewer of the "Wille the his stall" gibberish patterns.
5 · Why the gradient really flows — a detailed derivation
Let's verify Hochreiter's claim that gradient through the LSTM cell state does not vanish. Compute from the update:
Take the total derivative w.r.t. :
Without peephole connections, depend on only through , and depends on through . So the indirect terms exist but are heavily attenuated by the tanh (which saturates) and by the previous o gate.
The direct path — the highway — is . When the forget gate is close to 1 (the model is keeping memory), this is essentially the identity. Multiply the identity by itself times, you get the identity. Signal survives.
By contrast, in a plain RNN the only path is:
There is no highway. Every gradient signal must traverse this multiplicative Jacobian, and its spectral norm is almost never 1.
5.1 Measure it yourself
Take the gradient-norm experiment from S032 §5.1 and swap nn.RNN for nn.LSTM. Same T=100, same hidden dim, same seed. You'll see the gradient norm at decay from ~1e-11 (plain RNN) to something like ~1e-2 (LSTM) — nine orders of magnitude improvement, entirely from the architectural change. This is the empirical validation of Section 2's theory.
6 · The GRU — same idea, one fewer gate
Cho, Merriënboer, Bahdanau, Bengio (2014), Learning Phrase Representations using RNN Encoder-Decoder, introduced the Gated Recurrent Unit as a leaner LSTM. Two gates instead of four. No separate cell state.
The update rule is a convex combination of the previous hidden state and a candidate — no separate cell state, no output gate. The reset gate controls how much of is exposed to the candidate computation; the update gate controls how much of is preserved in the new hidden state.
Notice the Jacobian has a direct-path term . Same trick as the LSTM's cell state, just merged into the hidden state itself.
6.1 GRU in code
class MyGRUCell(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
self.linear_rz = nn.Linear(input_dim + hidden_dim, 2 * hidden_dim)
self.linear_h = nn.Linear(input_dim + hidden_dim, hidden_dim)
self.H = hidden_dim
def forward(self, x, h_prev):
cat = torch.cat([x, h_prev], dim=-1)
r, z = self.linear_rz(cat).chunk(2, dim=-1)
r, z = torch.sigmoid(r), torch.sigmoid(z)
cat2 = torch.cat([x, r * h_prev], dim=-1)
h_tilde = torch.tanh(self.linear_h(cat2))
h = (1 - z) * h_prev + z * h_tilde
return hParameter count. For hidden dim and input dim :
- LSTM: parameters per cell.
- GRU: parameters per cell.
Roughly 25% fewer parameters, so faster to train and inference for the same .
6.2 LSTM vs GRU — the empirical answer
Chung et al. 2014 benchmarked them on polyphonic music, speech signal modelling, and machine translation. Result: neither consistently wins. GRUs slightly ahead on smaller datasets and smaller models; LSTMs slightly ahead on larger datasets when you have more training compute. On language modelling in particular the gap is essentially noise.
Practical guidance for 2025 if you're actually using recurrent layers:
- Default to GRU if you don't have strong reasons to choose. Fewer params, easier to tune, essentially same quality.
- Use LSTM if you're building on top of pretrained models (many older speech and OCR systems ship LSTM weights).
- Prefer transformers or SSMs for anything language-model-shaped. GRUs and LSTMs are still useful for streaming inference and edge deployment.
7 · The pattern — additive skip + sigmoid gate — is everywhere
Once you see "additive update + sigmoid gate", you spot it in every modern architecture.
7.1 Highway Networks (Srivastava, Greff, Schmidhuber 2015)
For a feedforward deep network, Highway Networks proposed:
where is a sigmoid "transform gate". Compare to the GRU update rule — it is literally the same equation, applied across depth instead of across time. Highway Networks allowed training of 100+ layer feedforward networks in mid-2015, months before ResNets.
7.2 ResNets (He, Zhang, Ren, Sun 2015, ImageNet)
The most cited computer vision paper of the decade did something simpler:
No gate. Just addition. ResNet-152 hit human-level accuracy on ImageNet and won ILSVRC 2015. This is the LSTM's cell-state highway, stripped down to its essence. Every deep network you train today has residual connections, and every one of them exists because Hochreiter figured out in 1997 that addition preserves gradient and multiplication destroys it.
7.3 Transformer residual connections (Vaswani et al. 2017)
Every transformer block is:
Two additive skip connections per block, one around attention and one around the MLP. Without them, transformers of depth > 6 or so fail to train. With them, we scale to 100+ layers routinely. Same insight, third generation.
8 · When to use LSTM/GRU in 2025
Use them when:
- Streaming or low-latency inference matters (real-time speech, keystroke prediction, sensor fusion). One recurrent step is compute and memory — constant regardless of history length. Transformers require caching all prior tokens.
- Sequence lengths are enormous (100k+) and attention is unaffordable. Though SSMs (Mamba-2, RWKV-7) are now usually a better choice here.
- You're deploying to edge devices (phones, watches, MCUs) where the model must fit in tens of KB. A 1-layer GRU with has ~25k parameters. A 1-layer transformer of comparable expressiveness is roughly 10× larger.
- You need strong inductive bias for temporal ordering. Vanilla transformers rely on positional encodings; RNNs have order baked into the architecture.
Avoid them when:
- You need to train on lots of parallel hardware. RNNs are sequential-by-construction and leave 99% of GPU/TPU cores idle during training. Transformers and structured SSMs parallelise across sequence length.
- You need long-range dense information access (few-shot in-context learning, long-form reasoning). Attention lets any position directly reference any other; the recurrent bottleneck of a fixed-size vector cannot compete.
- Your dataset is large (>10B tokens) and quality is paramount. Attention scales better with data at this regime.
9 · War stories
I once tried to add layer norm to a stacked LSTM to stabilise training. Naively I put it inside the cell (before the tanh) and training got slower and worse. Turns out layer norm on the recurrent state changes the effective forget-gate dynamics in ways that break the cell-state highway property. The right place is Ba, Kiros, Hinton 2016's Layer Normalization formulation, which normalises the pre-activation of the gates only — not the cell state. Modern nn.LSTM doesn't include layer norm by default; you have to build it yourself. Read the paper before adding norm to a recurrent net.
Every graduate student's first temptation is to reach for bidirectional=True on their LSTM. It doubles the parameter count and the compute, and on many tasks it just overfits your small dataset. Rules of thumb: bi-LSTM helps a lot for sequence labelling (NER, POS tagging) where both left and right context matter for each token, marginally for classification with pooling, and not at all for autoregressive generation (where using the future would be cheating). Don't turn it on out of habit.
An old PyTorch tutorial I followed had forget-gate bias initialised to 0 (so the initial forget gate is , close to "erase half of memory every step"). My translation model trained painfully slowly and plateaued at a mediocre BLEU score. Switching to bias_forget = 1.0 (initial gate , "keep most of memory") knocked 3 days off training and improved BLEU by 1.5 points. This is a free fix that nobody remembers to apply. Jozefowicz, Zaremba, Sutskever 2015 documented it a decade ago and it still isn't the PyTorch default.
nn.LSTM in PyTorch dispatches to a highly optimised cuDNN kernel when your inputs are contiguous, on GPU, and have a compatible dtype. Any single one of those conditions failing — for example, packing variable-length sequences with a non-default enforce_sorted=False — silently falls back to the pure-Python unrolled implementation, which is 20–50× slower. I once "optimised" a training loop by adding proper padding masks and made it 30× slower without noticing. Use torch.utils.benchmark on your model to sanity-check throughput after any change.
10 · Try it yourself
- Take the character-RNN training script from S032 §6.
- Train three models:
nn.RNN,nn.LSTM,nn.GRU. Same hidden size (128), same optimiser, same 3000 steps. - Plot the loss curves on one chart. You should see: plain RNN plateaus around 2.0; GRU and LSTM both drop to ~1.5, with GRU converging slightly faster in early training.
- Sample 500 characters from each after training. Rank the coherence subjectively.
- Rerun the gradient-flow experiment from S032 §5.1 (one-shot backward, measure ||∂L/∂x_t|| at each t) on the trained LSTM. You should see roughly – times more gradient reaching than in the plain RNN.
- Stretch: implement a bidirectional GRU (two GRUs, one on the forward sequence and one on the reversed sequence, concatenate the hidden states) and try it on a small sentiment classification task like SST-2. See how much accuracy the bidirectionality buys you.
11 · Recap in five sentences
The LSTM's cell state — updated as — creates an additive gradient highway whose Jacobian is , close to identity whenever the forget gate is near 1, which is how it survives hundreds of timesteps without vanishing. The four gates (forget, input, candidate, output) give the model fine-grained control over what to erase, what to write, and what to expose. GRUs merge cell state and hidden state and use two gates instead of four; empirically the two architectures perform comparably. The "additive skip + sigmoid gate" pattern reappears verbatim in Highway Networks, ResNets, and transformer residual connections — Hochreiter's 1997 insight is quietly present in every modern deep network. In 2024 xLSTM brought Hochreiter himself back with an evolved recurrent architecture competitive with billion-scale transformers, closing a thirty-three-year arc that started with his diploma thesis.
🧠 Retention scaffold
One-line summary (write it in your own words): ________________________________________________
Spaced review: re-read §2 (the highway idea) and §5 (Jacobian derivation) in 24 hours. Revisit the full session on day 7.
Next session (S034): we stack two LSTMs into an encoder–decoder (seq2seq) and translate short English sentences to French, reproducing the Sutskever 2014 result that put LSTMs on the map. You'll also feel the bottleneck (a single fixed-size context vector) that motivates attention in S035.
Sticky note (keep on your desk): Addition preserves gradient, multiplication destroys it. Every skip connection you've ever seen — LSTM cell state, ResNet, transformer residual — is one line of the same solution to Hochreiter's 1991 problem.
Further reading
- Original. Hochreiter & Schmidhuber 1997, Long Short-Term Memory.
- Forget gate. Gers, Schmidhuber, Cummins 2000, Learning to Forget: Continual Prediction with LSTM.
- Empirical comparison. Chung et al. 2014, arXiv:1412.3555; Jozefowicz, Zaremba, Sutskever 2015, An Empirical Exploration of RNN Architectures.
- The definitive pedagogy. Olah 2015, Understanding LSTM Networks.
- Skip-connection family. Srivastava et al. 2015 Highway Networks; He et al. 2015 ResNets; Vaswani et al. 2017 Transformers.
- 2024 revival. Beck et al. 2024, xLSTM; Peng et al. 2023, RWKV; Gu & Dao 2023, Mamba.