Neural Language Models & Sequence Modelling
A language model does one thing: predict the next token. This chapter is how that idea became neural — from counting n-grams, to RNNs that carry a memory across a sequence, to the bottleneck that eventually forced the invention of attention. It's the direct run-up to the transformer.
Learning objectives
- Define a language model as next-token prediction — the objective behind LLMs.
- Explain n-gram models and why they can't scale to real context.
- Understand RNNs/LSTMs: a hidden state that carries memory through a sequence.
- Name the two fatal limits — vanishing gradients and sequential (un-parallelizable) computation.
- See how the RNN bottleneck motivated attention (K5).
claude-opus-4-8 is trained on. Understanding RNNs and why they hit a wall is what makes the transformer's design (K5) feel inevitable rather than arbitrary.What a language model is advanced
Here's a claim that sounds too small to be true, so hold onto it. The thing that writes your emails, debugs your code, and drafts your legal memos is, underneath, doing exactly one thing on a loop: guessing the next word. Not "understanding," not "reasoning about your intent" — predicting the next token, then predicting the next one given that, then the next. If you believe nothing else from this chapter, believe that, because every strange behaviour you'll ever see from an LLM — its fluency, its confident wrong answers, its sensitivity to how you phrase things — falls straight out of that one fact.
The reason to open the whole sequence-models chapter here, rather than with an RNN diagram, is that the goal never changed — only the machinery did. N-grams, RNNs, LSTMs, transformers: every architecture in this chapter is a different attempt at the same job. So if you fix the job firmly in your head first, the rest of the chapter reads as a story of engineers building better and better next-token predictors, each one fixing the last one's weakness. Miss this framing and the models look like an arbitrary parade of acronyms; keep it and they line up as one clear arc.
An analogy that makes it click: think of the predictive-text bar above your phone keyboard. You type "I'll be there in five" and it offers "minutes." That little suggester is a language model — a weak one, looking back only a word or two. Now imagine that same suggester having read most of the public internet, tracking not two words but two thousand, and offering not one guess but a full ranked probability over every word it knows. That's an LLM. Same idea, wildly different scale — and scale, as you'll see by the end, is the entire plot.
The common mistake is to assume that because the output is so capable, the objective must be something grander than "predict the next token" — that there's a hidden reasoning module, or a fact database, bolted on. There isn't. Capability is an emergent property of doing next-token prediction extremely well over a staggering amount of text; the objective stays humble even as the behaviour grows sophisticated. Keep the humble objective in view and you'll never be surprised that the model can hallucinate a citation as fluently as it quotes a real one — to a next-token predictor, both are just plausible continuations.
Strip away the mystique: a language model assigns probabilities to sequences of tokens, which reduces to one repeated question — given the tokens so far, what's the next one? Everything an LLM does (chat, code, summaries) is this next-token prediction, run over and over, one token at a time.
This is the one idea the whole chapter is built on: a language model reads the words so far and guesses what word comes next. Read it strictly left to right.
- The left box is your input — the sentence so far, with a blank at the end:
"the cat sat on the ___". That trailing blank is what the model has to fill in. - The arrow feeds that text into the middle box labelled
LM(the language model). An arrow always means "this goes into here". - The right side is the model's answer — but notice it is not a single word. It is a ranked list of candidates with numbers:
mat 0.4,floor 0.2,roof 0.05. Those numbers are probabilities (they add up to 1 across the whole vocabulary); a higher number means the model thinks that word is more likely to come next. - To actually generate text you pick one word from that list, stick it onto the end of the sentence, and run the whole thing through again — over and over, one word at a time.
In short: Reading the model = text in, list of likely next words out. Repeat that loop and you get sentences. Everything a modern LLM does is this one step, done extremely well.
Where it started: n-gram models advanced
Before there were neural networks, there was counting — and counting got you surprisingly far. If you wanted a machine to finish the sentence "peanut butter and ___," you didn't need it to understand sandwiches. You just needed a giant tally of every phrase people had ever written, so you could look up what word most often followed "peanut butter and" and answer "jelly." That's the whole idea behind n-gram models, and it's worth meeting them first because their failures are precisely what motivated everything that came after. You can't appreciate why an RNN's memory was a breakthrough until you've felt how little memory an n-gram has.
Here's the mental model. An n-gram model estimates the probability of the next word by looking back at a fixed number of previous words — and only that many. A bigram remembers one word back; a trigram, two. It's like trying to follow a conversation through a keyhole that only ever shows you the last two words spoken: you can guess the next word plausibly, but you have no idea what the paragraph is about. "The sat cat" and "the cat sat" are equally invisible to a model whose entire universe is two-word windows.
The natural reaction — and it's the common mistake — is to think, "fine, just use a bigger window, count 5-grams or 10-grams instead." This is where the wall is, and it's a wall of arithmetic, not of effort. Every word you add to the window multiplies the number of possible phrases by the size of the vocabulary. With a 50,000-word vocabulary, 5-grams have more possible combinations than there are atoms in your body, and the overwhelming majority of them never appear even once in any corpus you could collect. So the counts you'd need are almost all zero — the model has no data for nearly every real phrase, a problem called sparsity. Widening the window doesn't buy you memory; it buys you an ocean of zeros.
And there's a second, subtler flaw that no amount of data fixes: n-grams have no sense that words mean similar things. If the model has seen "black cat" a thousand times but never "dark cat," it treats "dark cat" as impossible — probability zero — even though you and I know the two phrases are near-identical in meaning. The model has no notion of similarity because it only ever counted exact strings. That's the generalization gap from K2, and it's structural: counting can't share evidence between related words. The table below lays out these three limits — window, sparsity, no generalization — and each one is a door that neural networks (embeddings for meaning, a hidden state for memory) were built to walk through.
The earliest statistical language models were n-grams: estimate the next word's probability by counting how often each word followed the previous n−1 words in a big corpus. A bigram model looks back one word; a trigram, two.
| n-gram limit | Consequence |
|---|---|
| Fixed, tiny window | A trigram sees 2 words back — no real context or long-range meaning |
| Combinatorial explosion | Counting all 5-grams needs astronomically much data; most never appear (sparsity) |
| No generalization | "black cat" seen, "dark cat" unseen → zero probability, despite similar meaning (the K2 problem) |
The RNN: a memory that walks the sequence advanced
The n-gram's fatal flaw was amnesia: a window two words wide. So the obvious question researchers asked was: what if the model kept a running summary — a little notebook it updated with every word it read, carrying meaning forward from any distance? That notebook is the single idea behind the Recurrent Neural Network, and it was a genuine breakthrough. For the first time a model could, in principle, connect a pronoun on line ten back to the name that introduced it on line one. Fixed windows were gone.
Picture a person reading a novel aloud with a sticky note in hand. Each word they read, they glance at the note, update it with a terse summary of the story so far, and read on. They never re-read the whole book — the note is their memory of everything before this point. An RNN works exactly like that: the sticky note is called the hidden state (a vector of numbers), and at every token the network does one small computation — blend the current word with the current note to produce an updated note plus a prediction for what comes next. Read a word, update the state, predict, repeat. The state is a compressed memory of the entire history, refreshed one step at a time.
The word "recurrent" is worth pausing on, because it names the whole trick: the network's output at each step is fed back in as an input to the next step. It's the same handful of weights applied over and over down the sequence — one small machine, reused at every position, threading its memory through. That reuse is elegant and it's why the diagram below draws the box once per word: it really is the same box each time, just shown at successive moments as the hidden state flows left to right.
But hold two things in mind as you read on, because they're the seeds of the RNN's downfall. First, that sticky note is small and gets overwritten every step — early words fade as newer ones pile on, which becomes the memory problem in the LSTM and limits sections. Second, and more quietly lethal: because each note depends on the previous note, the model cannot skip ahead. Word five's computation literally cannot begin until word four's is finished. That strict left-to-right chain, so natural for a human reader, is exactly what will make RNNs impossible to train fast on parallel hardware — the wall that ultimately ended them. The breakthrough and the fatal flaw are the same feature, seen from two sides.
A Recurrent Neural Network processes tokens one at a time, maintaining a hidden state — a vector that summarizes everything seen so far. At each step it combines the current token with the previous hidden state to produce a new hidden state and a prediction. In principle, that state can carry information from any distance.
h passes from each step to the next, so token 4's prediction can (in theory) depend on token 1. That was the breakthrough over n-grams: variable-length memory. But two things flow from that left-to-right chain — a memory problem and a speed problem — and both proved fatal.
This shows an RNN reading a sentence one word at a time. The trick to reading it: the same little machine is drawn once per word, so you can watch its memory get handed forward.
- Along the bottom are the input words in order —
the,cat,sat,on. Time flows left to right, one word per step. - The short upward arrows (from
x1…x4) feed each word up into the box above it. So box 1 gets "the", box 2 gets "cat", and so on. - The green boxes
h1 h2 h3 h4are the hidden state — think of it as the model's running memory, a scratchpad summarising everything read so far. - The key part is the horizontal arrows between the boxes: each step passes its memory to the next step. That is why the box reading "on" can still know about "the cat" from the start — the memory was carried along the chain.
- The catch (see the caption): the memory can only move one step at a time, in order. Nothing can jump ahead or be computed in parallel — and that single fact becomes the fatal flaw two sections later.
In short: An RNN = a reader with a notepad. It reads one word, updates the notepad, passes it on. Great for carrying memory; slow because it can never skip ahead.
LSTMs: patching the memory problem expert
A plain RNN's sticky note has a cruel property: it's rewritten in full at every single step. Read enough words and whatever was on it at the start is smeared into oblivion — the model that could in principle remember token one has, in practice, forgotten it by token twenty. This isn't just hand-waving; it has a precise mechanical cause called the vanishing gradient problem, and it's worth understanding because it explains why the fix took the shape it did.
Here's the intuition without the calculus. To learn, the network sends an error signal backward through every step of the chain, adjusting weights. But that backward signal gets multiplied by a factor at each step, and those factors are usually less than one. Multiply a number below one by itself twenty times and it collapses toward zero — so by the time the signal has travelled back twenty words, it's a rounding error. The network literally cannot feel that word one mattered to word twenty; the lesson never reaches that far. Long-range learning dies not because the idea is impossible but because the training signal evaporates on the way.
The LSTM (Long Short-Term Memory) and its lighter cousin the GRU fix this with a beautiful piece of engineering: a second memory track — a "cell state" you can picture as a conveyor belt running straight through the sequence, largely untouched. Instead of overwriting everything each step, the network learns small gates — valves, really — that decide what to add to the belt, what to erase from it, and what to read off it for the current prediction. Because information can ride the belt across many steps without being repeatedly mangled, both the memory and the gradient survive far longer. The gates are learned, so the model discovers on its own that "the subject of this sentence" is worth keeping while "that comma five words ago" is not.
This is the same deliberate keep-versus-discard logic you'll meet again in long-term agent memory (M4) and LangGraph checkpointers (L5) — deciding what deserves to persist is a recurring theme in this course, and the LSTM is where it first appears in a neural net. But note the honest framing in the callout below, because it's the point of this whole section: LSTMs patched the memory problem, they didn't cure it. Distant information still fades, just more slowly, and — crucially — the conveyor belt did nothing at all about the other flaw, the strict left-to-right chain that blocks parallelism. LSTMs bought the RNN era years of dominance in translation and speech, but they left the deeper wall standing.
Plain RNNs forget fast — over a long sequence, early information washes out of the hidden state (the vanishing gradient problem: the training signal shrinks toward zero as it propagates back through many steps). LSTMs (and GRUs) added gates — learned mechanisms that decide what to keep, forget, and output — letting the network hold information much longer. LSTMs powered the best NLP for years: translation, speech, text generation.
The two walls that killed RNNs expert
This is the section that explains why the entire field pivoted — so it's the one to read slowly. The stakes are enormous: had these two walls not existed, there might have been no transformer, no GPT, no Claude, and this course would be about tuning LSTMs. The reason we have LLMs at all is that engineers eventually decided these two limits couldn't be patched away and needed a fundamentally different architecture. Understanding them is what turns the transformer from "a clever thing that appeared in 2017" into "the obvious answer to two specific, painful problems."
The two walls are easy to name and easy to confuse, so let's be precise. Wall one is memory: even with the LSTM's conveyor belt, information from far back still fades over a long enough sequence. Better than a plain RNN, but not solved — ask an LSTM about something a paragraph ago and it often can't reach it. This one people expect. Wall two is speed, and it's the one that actually mattered: because each hidden state depends on the previous one, the model must compute the sequence strictly in order, one token after another. It physically cannot work on token five while it's still figuring out token four.
Why is that second wall the killer? Here's the analogy that makes it land. Imagine you have a hundred cooks and a thousand meals to prepare. If each meal is independent, you hand them out and all hundred cooks work at once — done in a flash. But if every meal must be cooked in sequence because each one uses the previous meal's leftover sauce, then 99 of your cooks stand idle while one works down the line. That's an RNN on a modern GPU. GPUs are those hundred cooks — thousands of processors built to do enormous numbers of calculations simultaneously. An architecture chained to strict left-to-right order can't hand them parallel work, so it wastes the very hardware that makes large-scale training possible. You can't train on trillions of tokens across thousands of GPUs if your model insists on going one token at a time.
The common mistake here is to remember only wall one — to think RNNs failed because they "forgot things." Memory was the famous problem, but it was partly fixable and had been partly fixed. The parallelization wall was the structural dead end: no amount of gating changes the fact that recurrence is inherently sequential. So as you read the diagram and table below, keep the emphasis where it belongs — the transformer's headline victory in the next chapter is not "it remembers better" (though it does), it's "it computes every position at once," and that is what unlocked the scale that made everything else possible.
Even LSTMs hit hard ceilings. Two limitations — one about memory, one about hardware — are the exact problems the transformer (K5) was designed to eliminate.
This diagram is just two labelled boxes side by side — the two problems that eventually killed RNNs. There are no arrows; each box is one independent problem to read on its own.
- Box 1 · Long-range fade: as the sentence gets long, the memory of the earliest words gets fainter and fainter until it is effectively gone. Even the improved LSTM version (next section) only softens this — it doesn't cure it.
- Box 2 · Sequential compute: because each step needs the previous step's memory before it can run, the model must go word-by-word in order. It cannot do many words at once.
- Why box 2 hurts so much: modern GPUs (the chips used to train these models) are fast precisely because they do thousands of things in parallel. A model forced to go strictly one step at a time can't use that power, so it is painfully slow to train at large scale.
- Read the two boxes as "memory problem" (box 1) and "speed problem" (box 2). The next chapter's transformer is designed specifically to fix both.
In short: Two walls: RNNs forget distant words, and they can't be sped up by parallel hardware. The second wall is the one that made RNNs a dead end for big models.
| Limit | Why it's fatal | What fixes it (K5) |
|---|---|---|
| Long-range dependency | Meaning that spans a paragraph gets lost — the K1 context problem, still unsolved | Attention: every token can look directly at every other, at any distance |
| Sequential computation | Token-by-token processing can't use parallel GPUs → can't scale training | Attention processes all positions at once — fully parallelizable |
Sequence-to-sequence & the birth of attention expert
This section is where the hero of the whole LLM story quietly walks on stage — not as a headline act, but as a bug fix. Attention, the mechanism that would go on to define the transformer and every model built on it, wasn't invented to revolutionize AI. It was invented to patch an annoying weakness in machine translation. That's a genuinely useful thing to know, because it tells you how real breakthroughs happen: someone fixes a concrete, unglamorous problem, and only later does everyone realize the fix was the actual point.
First, the setup. Translating a sentence is a "sequence in, sequence out" problem, so the RNN-era solution was the seq2seq architecture: one RNN, the encoder, reads the entire source sentence and boils it down to a single fixed-length vector; a second RNN, the decoder, reads that vector and generates the translation word by word. Clean, symmetrical, and — for short sentences — it worked.
The flaw is one you can feel physically. Imagine being asked to read a full paragraph, then repeat it perfectly to someone else using only a single index card to carry between them. For one sentence, fine. For a dense paragraph, the card can't hold it all — you write frantically, run out of room, and the far end reconstructs a blurry, lossy version. That index card is the fixed encoder vector, and this is the bottleneck: no matter how long or rich the input, everything must squeeze through one same-sized vector. Translation quality was observed to fall off a cliff as sentences got longer, for exactly this reason.
The fix, introduced around 2014–2015, was attention, and the idea is intuitive once you drop the single-card constraint. What if the decoder, instead of relying only on that one squeezed summary, could look back at the encoder's notes for every input word — and, for each word it's about to produce, decide which input words are most relevant right now? Translating the verb of the sentence? Pay attention to the source verb. Translating a noun? Look back at that noun. It's the difference between reciting from a blurry index card and being allowed to glance back at the original paragraph, focusing on the line that matters for the word you're writing.
Attention worked so well as a bolt-on that it raised a radical question, and that question is the entire bridge to the next chapter: if attention is doing the real work of connecting relevant words across any distance — and it does so by looking at all positions at once, with no left-to-right chain — then why keep the RNN at all? What if you threw out the recurrence entirely and built the whole model from attention? That thought, published in 2017 as "Attention Is All You Need," is the transformer. Notice it demolishes both walls at once: attention reaches any distance directly (memory), and it processes every position simultaneously (parallelism). The rest of this course lives downstream of that single idea.
Before transformers, the standard architecture for translation was seq2seq: an RNN encoder compresses the whole input sentence into a single fixed vector, and an RNN decoder generates the output from it. The flaw is obvious once you see it — everything the input said must squeeze through one vector.
This shows the old translation setup (seq2seq) and the fix that led to modern models. Follow the solid arrows left to right first, then look at the curved dashed arrow on top.
- Encoder RNN (left) reads the whole input sentence and squeezes it down into the tiny middle box marked
1 vec— a single fixed-size summary ("vector") of the entire input. - Decoder RNN (right) takes that one summary and writes out the translation, word by word.
- The problem (the "bottleneck"): everything the input said has to fit through that one small box. For a long sentence, detail gets lost — like summarising a paragraph in five words and then trying to rebuild the paragraph from just those five words.
- The curved dashed arrow on top (highlighted in amber) is attention, the fix: it lets the decoder reach back and look at all of the encoder's per-word states, not just the single squeezed summary — and focus on the words that matter for each output word.
- That amber arrow is the hinge of the whole module: attention worked so well as an add-on that researchers dropped the RNN entirely and built a model out of attention alone — the transformer (K5).
In short: Encoder squeezes the sentence into one box, decoder unpacks it — but the squeeze loses detail. Attention lets the decoder peek back at the full input, and that idea becomes the transformer.
What survived into the LLM era expert
It's tempting to read this chapter as "RNNs lost, transformers won, forget the losers" — and that would be the wrong lesson. Revolutions in engineering almost never throw everything away; they keep the parts that were right and replace only the part that was broken. The transformer is a perfect case study: it inherited far more from the RNN era than it discarded. Sorting out exactly what carried over and what got swapped is worth doing deliberately, because it's how you build an accurate mental model of a modern LLM instead of a superstitious one.
Start with what survived unchanged, because it's the most important and the most surprising. The next-token prediction objective — the very first idea in this chapter — is exactly how today's largest models are trained and how they generate. Nothing about that goal changed from the n-gram counting table to claude-opus-4-8; only the predictor got better. Embeddings survived too: transformers still turn tokens into vectors first, the same trick from K2. And the encoder/decoder shape survived and generalized — transformers come as encoder-only, decoder-only, or both, depending on the task.
Then there's the idea that got promoted: attention went from a humble seq2seq add-on to the load-bearing pillar of the entire architecture. And exactly one big idea got dropped — recurrence, the hidden state passed step to step. That's the whole substitution in a sentence: transformers kept the RNN era's goal (predict the next token) and its inputs (embeddings) and replaced its mechanism (sequential recurrence) with parallel self-attention. The table makes this survived-versus-replaced split concrete, and the callout that follows it is the honest coda: recurrence isn't dead everywhere — it still has niches, and modern state-space models are quietly reviving the idea with fixes for its old flaws — but for general language modeling at scale, the swap was decisive.
| RNN-era idea | Where it lives now |
|---|---|
| Next-token prediction objective | Unchanged — it's exactly how LLMs are trained & generate |
| Embeddings as input | Kept — transformers embed tokens too (K2) |
| Attention | Promoted from add-on to the core mechanism (K5) |
| Hidden-state recurrence | Dropped — replaced by parallel self-attention |
| Encoder/decoder structure | Kept & generalized — transformers come in encoder, decoder, or both |
Common pitfalls expert
| Pitfall | Fix |
|---|---|
| Thinking LLMs are more than next-token predictors at core | That objective is the core; capability emerges from scale |
| Assuming n-grams "just needed more data" | Sparsity & no-generalization are structural, not data-size |
| Believing RNNs failed only on memory | The parallelization limit is what really blocked scale |
| Thinking attention was invented with transformers | It began as a seq2seq add-on; transformers made it everything |
| Dismissing the lineage as irrelevant | It explains context windows, token costs, and why transformers won |
Exercises expert
Exercise K4.1 — Next-token by hand
Context: Building and sampling a bigram model by hand makes the fixed-window limitation tangible: locally fine, globally incoherent.
Your task: Build a tiny bigram model over a paragraph, then generate by repeatedly sampling the next word — and explain the incoherence in terms of the fixed window.
Requirements:
- Count word pairs across the paragraph
- Generate by sampling the next word from those counts
- Observe that each adjacent pair looks plausible
- Explain the global drift as the one-token memory window
💡 Hint: Each pair is locally fine, but with memory of only one token there is no topic or long-range agreement — the gap RNNs and attention were built to close.
Show what to look for
Each pair looks fine ("the cat", "cat sat") but the output drifts with no memory beyond one word — no topic, no long-range agreement. That incoherence is exactly the context limitation that RNNs, then attention, were built to fix.
Exercise K4.2 — Why can't we parallelize an RNN?
Context: The single architectural difference between an RNN and a transformer — sequential state versus parallel attention — is why transformers could scale.
Your task: Explain to a peer, in two sentences, why an RNN must process tokens in order while a transformer need not, then state why that difference mattered for training LLMs at scale.
Requirements:
- State that an RNN's hidden state forces strictly sequential processing
- State that a transformer attends over all positions in parallel
- Connect the parallelism to feasible large-scale training
- Keep it to a peer-level, two-sentence explanation
💡 Hint: The recurrent dependency chains each step to the last; attention removes that chain, and parallel compute is what unlocked scale.
Exercise K4.3 — Trace the lineage
Context: Tracing the progression from n-grams to transformers — one problem solved and one left open at each step — is the map that ties the whole module together.
Your task: Draw the progression n-gram → RNN → LSTM → seq2seq+attention → transformer, and for each step write the one problem it solved and the one it left open.
Requirements:
- Lay out the five stages in order
- For each stage, name the one problem it solved
- For each stage, name the one problem it left open
- Show how each open problem motivates the next stage
💡 Hint: Each arrow should read as "solved X, but left Y" — and Y is exactly what the next model was invented to address.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: A language model, at heart, assigns a probability to the next token. A bigram counter is that idea in its simplest form — counting instead of a neural net.
Your task: Build a bigram counter over a corpus and turn the counts into next-token probabilities given a previous token.
Requirements:
- Count
(prev → next)token pairs across the corpus - Add sentence-boundary markers so starts and ends are modeled
- Normalize the counts per previous token into a probability distribution
- Report the next-token distribution for a given previous token
💡 Hint: The essence of a language model is a distribution over the next token; here it comes straight from normalized counts.
Show solution
Count (prev→next) pairs, normalize per prev. Runnable:
from collections import Counter, defaultdict
def train_bigram(corpus):
counts = defaultdict(Counter)
for sent in corpus:
toks = ["<s>"] + sent.split() + ["</s>"]
for a, b in zip(toks, toks[1:]):
counts[a][b] += 1
return counts
def next_probs(counts, prev):
c = counts[prev]; total = sum(c.values())
return {w: round(n / total, 3) for w, n in c.most_common()}
corpus = ["the cat sat", "the cat ran", "the dog sat"]
counts = train_bigram(corpus)
print("after 'the':", next_probs(counts, "the")) # cat 0.667, dog 0.333
print("after 'cat':", next_probs(counts, "cat")) # sat 0.5, ran 0.5
This is the essence of a language model — a distribution over the next token — just with counting instead of a neural net.
Context: Sampling from the bigram model reveals its limit: it remembers only one token back, so generated text stays locally plausible but wanders globally — the context limitation RNNs were built to fix.
Your task: Generate a sentence by repeatedly sampling the next token from the bigram distribution until the end-of-sentence marker.
Requirements:
- Start from the sentence-start marker
- Sample the next token proportional to its counts
- Stop at the end-of-sentence token or a max length
- Show that the output drifts, having memory of only one prior token
💡 Hint: Because each step conditions on a single previous token, the text has no long-range memory — that wandering is the motivation for real recurrence.
Show solution
Sample proportionally to counts; stop at the end token. Runnable:
import random
from collections import Counter, defaultdict
def train_bigram(corpus):
counts = defaultdict(Counter)
for sent in corpus:
toks = ["<s>"] + sent.split() + ["</s>"]
for a, b in zip(toks, toks[1:]):
counts[a][b] += 1
return counts
def generate(counts, seed=0, max_len=12):
rng = random.Random(seed)
cur, out = "<s>", []
for _ in range(max_len):
choices, weights = zip(*counts[cur].items())
cur = rng.choices(choices, weights=weights)[0]
if cur == "</s>":
break
out.append(cur)
return " ".join(out)
counts = train_bigram(["the cat sat", "the cat ran fast", "the dog sat"])
print(generate(counts, seed=1))
print(generate(counts, seed=7))
Sampling reveals the model's limits: a bigram only remembers one token back, so it wanders — motivating models with real memory (RNNs).
Context: An RNN carries a hidden state through the sequence; that state is its memory, blending each new input with a running summary of everything before it.
Your task: Implement one RNN cell's forward pass — h_t = tanh(W_x·x + W_h·h_<t-1> + b) — over a short sequence in pure Python.
Requirements:
- Step a hidden state through the sequence one timestep at a time
- At each step combine the input projection and the previous-state projection
- Apply a
tanhsquashing so the state stays bounded - Return the hidden state at every timestep
💡 Hint: The hidden state is the whole idea: each step folds the new input into a running summary — the mechanism, minus the learned weights.
Show solution
h_t = tanh(W_x·x + W_h·h_{t-1} + b), stepped over the sequence. Runnable, stdlib math:
import math
def matvec(M, v):
return [sum(mij * vj for mij, vj in zip(row, v)) for row in M]
def add(a, b):
return [x + y for x, y in zip(a, b)]
def rnn_forward(inputs, Wx, Wh, b, h0):
h = h0
states = []
for x in inputs:
pre = add(add(matvec(Wx, x), matvec(Wh, h)), b)
h = [math.tanh(z) for z in pre] # squashing keeps state bounded
states.append(h)
return states
Wx = [[0.5, 0.0], [0.0, 0.5]]
Wh = [[0.1, 0.0], [0.0, 0.1]]
b = [0.0, 0.0]
seq = [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]] # 3 timesteps, 2 features
for t, h in enumerate(rnn_forward(seq, Wx, Wh, b, [0.0, 0.0])):
print(f"t={t}: h = {[round(x, 3) for x in h]}")
The hidden state h is the RNN's memory: each step blends the new input with the running summary — the mechanism, minus the learned weights.
Context: Plain RNNs hit a wall: gradients backpropagated through many tanh steps multiply per-step factors below 1 and decay geometrically, so signal from far-back tokens vanishes.
Your task: Model gradient magnitude across timesteps to show why long-range dependencies vanish in a deep RNN.
Requirements:
- Multiply a per-step factor down the chain, one factor per timestep
- Use a factor below 1 to mimic
tanhsaturation - Trace how the gradient scale shrinks as the step count grows
- Show it becomes negligible after enough steps, killing long dependencies
💡 Hint: A per-step factor under 1 compounds exponentially with distance; LSTMs add a gated cell state to keep a near-1 path for the gradient.
Show solution
Backprop multiplies per-step factors (<1 for tanh saturation); the product decays geometrically. Runnable:
def gradient_flow(steps, per_step_factor):
g = 1.0
trace = []
for t in range(1, steps + 1):
g *= per_step_factor # chain rule multiplies each step
trace.append((t, g))
return trace
print("factor 0.6 (typical tanh region):")
for t, g in gradient_flow(20, 0.6):
if t % 4 == 0:
print(f" after {t:>2} steps: gradient scale = {g:.2e}")
# by step 20 the gradient is ~10^-5: the signal from far-back tokens is gone
With per-step factors below 1, the gradient decays exponentially with distance — so plain RNNs cannot learn dependencies more than a handful of tokens back. LSTMs add a gated cell state to keep a near-1 path.
Context: Seq2seq crammed an entire input into one fixed vector — the bottleneck attention removed by letting the decoder read every input position directly.
Your task: Model the information loss of a fixed encoder vector versus attention's per-token access as input length grows.
Requirements:
- Treat the fixed vector as holding only about a capacity's worth of tokens
- Show its faithful recall degrades as the input grows past that capacity
- Model attention as retaining full recall regardless of length
- Contrast the two across several input lengths
💡 Hint: The fixed vector overflows on long inputs; attention keeps full recall by re-reading every position — the idea transformers took to its conclusion.
Show solution
Model 'capacity' as a fixed budget for the encoder vector vs attention re-reading each token. Runnable:
def fixed_vector_recall(input_len, vector_capacity=8):
# a fixed vector can faithfully hold ~capacity tokens; the rest degrade
kept = min(input_len, vector_capacity)
return kept / input_len
def attention_recall(input_len):
return 1.0 # attention can look back at every token, no fixed bottleneck
for n in (5, 8, 20, 100):
print(f"len={n:>3}: fixed-vector recall={fixed_vector_recall(n):.2f} "
f"attention recall={attention_recall(n):.2f}")
# long inputs overflow the fixed vector; attention keeps full recall
The fixed encoder vector degrades as inputs grow; attention removes the bottleneck by letting the decoder read every input position — the idea transformers took to its conclusion.
Context: Several RNN-era ideas live on in transformers while others were replaced. A concept map of what survived versus what was swapped out is the spine of the whole module.
Your task: Build a lookup that, for each RNN-era idea, reports whether it survived into the LLM era or was replaced.
Requirements:
- Encode the survival map as data keyed by idea
- Mark the next-token objective, embeddings, teacher forcing, and attention as survivors
- Mark recurrent hidden state, the fixed context vector, and sequential-only compute as replaced
- Look up and report the status for a given idea
💡 Hint: The through-line: the objective and embeddings carried over, but recurrence was replaced by attention so compute could parallelize across tokens.
Show solution
Encode the lesson's survival map as data. Runnable:
LEGACY = {
"next-token objective": "SURVIVED -- LLMs still predict the next token",
"word embeddings": "SURVIVED -- input/output embedding tables",
"teacher forcing": "SURVIVED -- used in pretraining",
"attention": "SURVIVED & CENTRAL -- transformers are attention",
"recurrent hidden state": "REPLACED -- self-attention, no sequential state",
"fixed context vector": "REPLACED -- attention over all positions",
"sequential-only compute": "REPLACED -- transformers parallelize over tokens",
}
def status(idea):
return LEGACY.get(idea, "unknown -- check the lesson")
for idea in ["next-token objective", "recurrent hidden state", "attention"]:
print(f"{idea:>24}: {status(idea)}")
The through-line: the objective and embeddings carried over; the recurrence was replaced by attention so compute could parallelize across tokens.
✓ Checkpoint — you can move on when you can…
- Define a language model as next-token prediction and connect it to LLMs.
- Explain why n-grams can't scale (window, sparsity, no generalization).
- Describe an RNN's hidden state and what LSTMs' gates fixed.
- Name the two RNN walls — long-range fade and sequential compute.
- Explain how the seq2seq bottleneck gave birth to attention.
Knowledge check check yourself
RNNs hit two 'walls'. Which one does the lesson say really mattered for scaling LLMs, and why?
Show answer
How did attention originally arise in seq2seq, and what was the pivotal insight that turned it into the transformer?