Chapter 3 left us with an uncomfortable fact, which is that the vocabulary of a natural language never closes. Heaps’ law guarantees it, because however much text a system has seen the next document can hold a word it has not, and for a word level system every such word is a small crisis.
The classical remedy is to map everything unknown to a single
<unk> symbol, as in Chapter 10,
which is honest bookkeeping and terrible epistemology. It declares
unfathomable, Vaccination and iPhone17 to be
the same word, so all identity is lost at exactly the moment identity
matters most.
The opposite extreme is available and instructive. Work at the character level and the vocabulary problem vanishes entirely, since a few dozen symbols cover everything anyone will ever type.
The price is that every regularity we care about is now smeared across long sequences, because the model has to rediscover character by character that t-h-e is a unit before it can start learning what the does. Sequences get roughly five times longer, so a dependency that spanned three words now spans fifteen symbols.
Neither words nor characters will do, then, and the resolution sounds like a compromise while being really a synthesis: let the data decide what the units are.
Frequent words should survive as single tokens because statistically they have earned it, while rare words should break into pieces that are themselves frequent, so that unfathomable becomes something like un + fathom + able and a novel surname becomes pronounceable fragments. In the worst case, anything at all becomes single characters.
A vocabulary of such subword units can be closed, modest in size, and still able to represent every string, and every large language model you have used is built on this idea.
The dominant algorithm is byte pair encoding, or BPE, and it is almost embarrassingly simple. It is one of a family of subword methods, and the siblings that share its idea appear later in this chapter. It was borrowed from a 1994 data compression technique and repurposed for translation by Sennrich, Haddow and Birch in 2016.
It learns a vocabulary by merging, greedily and repeatedly:
Split every word in the training corpus into characters. Append
an end of word marker </w> to each word. The starting
vocabulary is the character set.
Count every adjacent pair of current symbols, across the whole corpus. Each occurrence counts with its word’s frequency.
Merge the most frequent pair into one new symbol. Add that symbol to the vocabulary and record the merge as a rule.
Repeat from step 2, for a fixed number of merges. That number is the algorithm’s only real hyperparameter.
One bookkeeping rule matters enough to state precisely, because without it two otherwise correct implementations will disagree. When several pairs tie for the highest count, merge the lexicographically smallest pair.
Ties are not rare, and in small corpora they are the norm, while the tie break decides every merge that follows. This is the convention the course autograder enforces, and once it is stated BPE becomes exactly reproducible.
Take the classic micro corpus. Four words, each with a frequency:
| word | frequency |
|---|---|
low |
5 |
lower |
2 |
newest |
6 |
widest |
3 |
We will run five merges, and at every step we do the same three things: count all adjacent pairs, pick the winner while breaking ties lexicographically, then rewrite the corpus with that pair joined.
Every count below is weighted by the word’s frequency, so a pair
inside newest contributes 6 rather than 1. That weighting
is the single most common mistake in a hand simulation.
Every word becomes its characters plus the end of word marker.
| symbols | frequency |
|---|---|
l o w </w> |
5 |
l o w e r </w> |
2 |
n e w e s t </w> |
6 |
w i d e s t </w> |
3 |
e s.Count every adjacent pair. There are fourteen distinct pairs in all, and the eight highest are these:
| pair | count | where it comes from |
|---|---|---|
e s |
9 | newest 6
widest 3 |
s t |
9 | newest 6
widest 3 |
t </w> |
9 | newest 6
widest 3 |
w e |
8 | lower 2
newest 6 |
l o |
7 | low 5
lower 2 |
o w |
7 | low 5
lower 2 |
e w |
6 | newest 6 |
n e |
6 | newest 6 |
Six more pairs exist, and none of them scores above 5. Note that
w </w> is one of those six, at 5, coming from
low alone.
Three pairs tie at 9, so the tie break earns its keep at the very
first step. Sorted as tuples,
,
so e s wins.
| corpus after merge 1 | frequency |
|---|---|
l o w </w> |
5 |
l o w e r </w> |
2 |
n e w es t </w> |
6 |
w i d es t </w> |
3 |
es t.Recount, because the merge changed which pairs exist, so
e s and s t are both gone and
es t has taken their place.
Now only two pairs tie at 9, namely es t and
t </w>, and since
the winner is es t.
| corpus after merge 2 | frequency |
|---|---|
l o w </w> |
5 |
l o w e r </w> |
2 |
n e w est </w> |
6 |
w i d est </w> |
3 |
est </w>.Recount again, and this time there is no tie, because
est </w> stands alone at 9 while the next best,
l o and o w, are back at 7.
| corpus after merge 3 | frequency |
|---|---|
l o w </w> |
5 |
l o w e r </w> |
2 |
n e w est</w> |
6 |
w i d est</w> |
3 |
This is the interesting moment, because the suffix
est</w> completed in three merges, before the
algorithm had touched the stem of low at all. Nothing told
it that -est is an English suffix, and it won purely because it
was frequent.
l o.With the 9s exhausted the top count drops to 7, and we get a second
tie, since both l o and o w score
.
Sorted,
,
so l o wins.
| corpus after merge 4 | frequency |
|---|---|
lo w </w> |
5 |
lo w e r </w> |
2 |
n e w est</w> |
6 |
w i d est</w> |
3 |
lo w.The previous merge has just made lo w available, and it
inherits the same count of 7, with no tie this time.
| corpus after merge 5 | frequency |
|---|---|
low </w> |
5 |
low e r </w> |
2 |
n e w est</w> |
6 |
w i d est</w> |
3 |
Five merges, in order, and this ordered list is the tokeniser.
| # | merged pair | new symbol | count | tie? |
|---|---|---|---|---|
| 1 | e s |
es |
9 | 3 way |
| 2 | es t |
est |
9 | 2 way |
| 3 | est </w> |
est</w> |
9 | no |
| 4 | l o |
lo |
7 | 2 way |
| 5 | lo w |
low |
7 | no |
Three of the five steps involved a tie, which is why the tie break rule is not a footnote. Without it three of these five merges are undefined, and every merge after the first bad one is wrong as well.
The words now tokenise like this:
| word | tokens after 5 merges |
|---|---|
low |
low
</w> |
lower |
low e
r </w> |
newest |
n e
w est</w> |
widest |
w i
d est</w> |
The algorithm found a productive English suffix in
est</w> and a whole common word in low,
while lower, being rarer, still decomposes into a known
stem plus leftovers.
Nobody supplied a single linguistic fact, and -est simply paid for itself in compression. That is the entire theory of BPE in one sentence.
Applying a trained tokeniser to new text replays the same logic. Split the word into characters, then apply the recorded merge rules in the order they were learned, wherever they match.
Try lowest, a word the training corpus never contained,
and apply the five rules in order to watch it assemble.
| apply | result | |
|---|---|---|
| split | l o w e s t </w> |
|
rule 1, e s |
l o w es t </w> |
matched |
rule 2, es t |
l o w est </w> |
matched |
rule 3, est </w> |
l o w est</w> |
matched |
rule 4, l o |
lo w est</w> |
matched |
rule 5, lo w |
low est</w> |
matched |
An unseen word came out as two meaningful pieces, a stem
low and a suffix est</w>, and that is
the whole point of subwords.
Try it yourself. The script behind every table in this run is
code/worked_examples/bpe.py. Running it with--no-tiebreaktakes the lexicographically last tied pair instead of the first. All five merges come out different, and so does the tokenisation of every word. That is the clearest demonstration of why the rule matters.
The rules must be applied in the order they were learned, because
rule 2 can only fire once rule 1 has already produced es.
Apply them in a different order and you get a different and wrong
answer.
How do we judge a tokeniser? The workhorse diagnostic is fertility, the average number of tokens produced per word:
A fertility of means every word survived whole and a fertility of means words are being cut in half on average, while character level tokenisation of English runs near .
Fertility is a compression gauge, and like all compression it reflects the fit between training data and use. A BPE vocabulary learned on news text has low fertility on news, whereas on medical abstracts, legal boilerplate or another language its fertility climbs, because the merges it learned are the wrong merges for that text.
Rising fertility is therefore often the first measurable symptom of domain shift, and it is cheaper to compute than any accuracy number.
Fertility is also, bluntly, money, because modern LLM APIs price by the token and context windows are budgeted in tokens. Text that tokenises at fertility costs more than the same content at , and fits less into the window.
Multilingual users often notice that their language costs more per sentence than English on the same model, and what they are measuring is the fertility of a tokeniser whose training data under-represented their language. Tokenisation looks like invisible plumbing, and it has an equity dimension.
Three practical notes round out the picture.
First, modern systems usually run BPE over bytes rather than
Unicode characters, which is byte level BPE as used in GPT-2 and its
descendants. All 256 bytes sit in the base vocabulary, so no string in
any script can ever be out of vocabulary and <unk>
disappears from the system entirely.
Second, BPE has siblings. WordPiece uses likelihood driven merges and is the tokeniser behind BERT, while the unigram language model tokeniser prunes a large vocabulary instead of growing a small one and reaches you through SentencePiece. They differ in the selection criterion and share the subword philosophy.
Third, tokenisation is where some of a model’s oddest behaviour is born. Think of a model that cannot reverse a word, count its letters, or do digit arithmetic, where the cause is often the tokeniser having welded those characters into opaque units before the model ever saw them. When an LLM’s behaviour on a string puzzles you, the first thing to inspect is the string as the model sees it, in tokens.
Fertility is a property of text, whereas the merge count is a property of the model, and it is a larger one than it looks. The number of merges is not a tuning knob that can be turned freely, because it sets the size of the largest single matrix the model carries.
Run BPE over bytes and the base vocabulary is all 256 byte values, after which every merge adds exactly one new symbol. GPT-2 makes the arithmetic visible.
| 256 base byte values | 256 |
| merges learned | 50,000 |
| one end of text special token | 1 |
| GPT-2 vocabulary size | 50,257 |
That is the published figure, and it is the merge loop of Section 5.2.1 run fifty thousand times on web text.
A model stores one vector per vocabulary item, so the embedding matrix is where is the model width. GPT-2 small is 768 wide, which means a single merge costs 768 parameters.
| embedding matrix, | |
| share of GPT-2 small’s 124M parameters | 31.1 percent |
| cost of one more merge | 768 parameters |
| cost of ten thousand more merges | 7,680,000 parameters |
Almost a third of that model is the tokeniser’s vocabulary, and where the output projection is not tied to the input embedding it costs the same again.
There is a genuine trade-off here, and it has two sides. More merges give longer subwords and lower fertility, so sequences get shorter, while more merges also give a larger embedding matrix and a wider softmax. Parameters are being traded against sequence length, and the best point depends on the text the model will actually meet, which is the same argument fertility makes.
Self attention compares every token with every other token, so its cost grows with the square of the sequence length, which means a tokeniser that is somewhat worse per word turns out considerably worse per document. Take a document of 1000 words.
| tokens | attention pairs | KV cache (fp16) | |
|---|---|---|---|
| fertility 1.2 | 1200 | 1,440,000 | 42.2 MB |
| fertility 1.9 | 1900 | 3,610,000 | 66.8 MB |
| ratio | 1.58 | 2.51 | 1.58 |
A tokeniser 58 per cent worse per word costs 151 per cent more attention work, and that gap is the strongest practical argument for measuring fertility at all.
A model generating text keeps a key and a value vector per token per layer, and that store is the KV cache, which is usually what limits how many requests one server can hold at once.
| per token, GPT-2 small at fp16 | bytes |
| 36 KB per token | |
| a full 1024 token window | 36.0 MB |
Every token the tokeniser fails to merge costs another 36 KB of cache, and that is for one request on one small model, with the figure far higher on a large one.
The context window is measured in tokens and never in words, so fertility also decides how much of a document fits inside it.
| fertility | words that fit in a 1024 token window |
|---|---|
| 1.2 | 853 |
| 1.9 | 539 |
Two readers send the same document to the same model, and one is truncated while the other is not, with the only difference being how well the tokeniser fits their language. Section 5.3 called that an equity dimension, and these are the numbers underneath it.
Subword tokenisation closes the vocabulary problem that Chapter 3 opened, giving us a fixed and modest vocabulary that can still spell anything, with frequent units kept whole so downstream statistics have something solid to bind to.
From here on, every model in this book operates on tokens, whether the n-gram models of Chapter 10, the embeddings of Part II or the transformers of Part IV, and every perplexity, similarity and attention weight we compute is a statement about token sequences.
Remember that when you compare numbers across systems, because two models with different tokenisers are answering subtly different questions about the same text.
Sennrich et al. (2016) is the paper that made BPE standard for neural
machine translation. Kudo and Richardson (2018) present SentencePiece
and the unigram alternative. Jurafsky and Martin (2025) situate
tokenisation in the broader text normalisation pipeline, in Chapter 2 of
the third edition draft. The trainer you are asked to write in Problem 2
exists as tokenizers, the library behind most current
models, and sentencepiece is the reference implementation
of the unigram alternative.
BPE by hand. Take a small corpus of a dozen words. Run five byte pair encoding merges by hand, writing out the merge rules in order. Apply the deterministic tie break rule of this chapter at every step. This is where hand simulations go wrong, so whenever two pairs are equally frequent, state which pair you merged and why.
Implement the trainer and encoder. Write
train(corpus, num_merges), which returns an ordered list of
merge rules. Write encode(word), which applies them. Run
your encoder on the corpus of Problem 1 and check that it reproduces the
merges you did by hand. A mismatch almost always means the tie break
rule differs.
Measure fertility. Use your trained tokeniser to compute fertility, as defined in Equation (5.1), on two samples. One in domain, one out of domain, say a technical or foreign language passage. Report both numbers. Explain in one paragraph why the out of domain number is higher.
The vocabulary budget. You are given a model width of 768 and a budget of 40 million parameters for the embedding matrix alone. How many merges can you afford, once the 256 byte values and one special token are accounted for? Now halve the width and repeat. Say in one sentence what the width is really buying.
Where the curve flattens. Train BPE on a corpus at several merge counts, from zero upwards, and measure fertility on held-out text each time. Plot it. The curve falls steeply and then flattens, and doubling the merge count eventually buys very little. Say where you would stop, and defend the choice using both sides of the trade-off in Section 5.4.
Tie break sensitivity. Re-run Problem 1 with a different tie break rule, for example preferring the lexicographically later pair. Show that the merge list changes, and with it the tokenisation of at least one word. What does this tell you about reproducibility claims that omit the tie break convention?
Where a real trainer differs. Train
tokenizers.models.BPE on the low, lower, newest,
widest corpus of this chapter, passing
end_of_word_suffix="</w>", and list the merges it
produces in order. Its first merge is e s, exactly as yours
is, and its second is not. Work out what the library does with the end
of word marker that this chapter does not, and then answer the harder
question: is either merge list wrong, and what would you have to know
before you could say?
Each one reproduces the tables above. Open it in Colab, change an input and watch which numbers move.
bpe.pyBPE, five merges
The whole training run with pair counts and ties, then the learned rules applied to words the corpus never held.
standard library only Run it in Colab Notebook SourceFrom Natural Language Processing: From Corpus Statistics to Grounded LLMs, by Ramaseshan Ramachandran.