Module 09 · Part III · Building an LLM

Tokenization

Words, Characters and Subwords, Byte-Pair Encoding, Other Tokenizers, Vocabulary Trade-Offs, and the Quirks Tokens Cause

By the end you can run BPE by hand and predict how the tokenizer changes cost and behaviour.

Builds onModule 8·
Check yourself ▸

Before you start, can you already…

  • ☐choose the right family for a task and explain why decoder-only won for generation. (Module 8)

If any is shaky, the linked module teaches it.

Open in Colab
3 Hours·Core·Intermediate·0/6

A model reads integers, not text. The tokenizer decides how text becomes integers, once, before training, and the choice is frozen into everything the model learns. It sets sequence length, embedding-table size, the cost of each language, and even which simple-looking questions the model finds oddly hard.

9.1 Words, Characters, or Subwords

One token per word needs an enormous vocabulary and still meets unseen words. One token per character keeps the vocabulary small but makes sequences very long. Subword tokenization sits between: common words get one token, and rare words split into a few common pieces.

9.2 Byte-Pair EncodingRun this section

GPT-2 uses byte-level Byte-Pair Encoding (BPE). Its vocabulary has 50,25750{,}257 tokens: the 256 possible byte values, 50,000 learned merges, and one special end-of-text token.

Architecture Flowchart
Diagram: Training words: 'lower', 'lowest', 'newer', 'wider'; Start: 256 base tokens, one per byte value 0 to 255; Count every adjacent pair of tokens in the corpus; Merge the most frequent pair: 'e' + 'r' becomes 'er'; Recount, merge again: 'l' + 'o' becomes 'lo'; Again: 'lo' + 'w' becomes 'low'; Stop at the target size: 50,257 tokens for GPT-2

In words. Start from single bytes and repeatedly merge the most frequent adjacent pair into a new token. Frequent words end up as one token; rare words stay in pieces.

Figure 9.2
Byte-pair encoding, one merge at a time

Each step finds the most frequent adjacent pair in the corpus and merges it everywhere. Watch frequent words collapse into single tokens, and the test sentence get shorter.

Corpus: low ×5, lower ×2, newest ×6, widest ×3. Character-level for readability; GPT-2 starts from the 256 byte values instead. Ties go to the pair seen first.
Predict, then try

The starting corpus repeats low, lower, newest and widest. Before stepping, predict which pair of symbols will be merged first, and which whole word will become a single token soonest.

In words. A small corpus split into symbols, a table of adjacent-pair counts, and the merges learned so far. Each step merges the most frequent pair everywhere. A test sentence below is retokenized after each merge, and its token count falls.

The BPE Merge Algorithm

  1. Split the training text into words, and write each word as a sequence of base symbols (characters, or bytes in GPT-2).
  2. Count how often each pair of adjacent symbols occurs across the whole corpus.
  3. Take the most frequent pair (A,B)(A, B) and replace every occurrence with a single new symbol ABAB.
  4. Add ABAB to the vocabulary and record the merge.
  5. Repeat steps 2 to 4 until the vocabulary reaches the target size.

New text is tokenized by applying the recorded merges in order. The original BPE paper (Sennrich et al., 2016) marks word ends with </w>; GPT-2 instead keeps the leading space as part of the token, so " lower" and "lower" are different tokens.

Since any text is a sequence of UTF-8 bytes, a byte-level vocabulary can tokenize every input and never needs an <UNK> ("unknown") token.

9.3 Other Subword Tokenizers

  • WordPiece (Schuster & Nakajima, 2012), used by BERT with 30,522 tokens, merges the pair that most increases the training text's likelihood rather than the most frequent.
  • Unigram (Kudo, 2018) starts from many candidate pieces and removes those whose loss hurts least, keeping a probability per piece so a word can split more than one way.
  • SentencePiece (Kudo & Richardson, 2018) is a library that runs BPE or Unigram on raw text, treating the space as a symbol (▁), so it needs no language-specific word splitting. LLaMA 1 and 2 use it with 32,000 BPE tokens; LLaMA 3 switched to 128,256 tokens built with OpenAI's tiktoken.

The algorithm matters less than the vocabulary size and the text it was built from.

9.4 Choosing a Vocabulary SizeRun this section

  • Bigger vocabularies make sequences shorter, which is cheaper (attention cost grows with length, section 6.6) and fits more text in a fixed context window.
  • Bigger vocabularies make the model's ends bigger. The embedding table and output layer each hold numbers, and the softmax runs over all ∣V∣|V| at every position. At dmodel=4096d_{\text{model}} = 4096, going from 32,000 to 128,000 tokens adds about 393 million parameters to each.
  • Rare tokens are poorly trained. A token seen a handful of times gets a handful of updates, and its embedding stays near random.

Vocabularies have grown with models, since the end matrices become a smaller share while the length savings apply to every token: GPT-2 used 50,257, LLaMA 2 32,000, LLaMA 3 128,256, and Gemma 256,000.

The text the tokenizer was built from matters as much. One built mostly from English learns few merges for other languages, so the same sentence can cost two to five times as many tokens in a language it saw little of: less text per context window and a higher price per request.

Browser lab

More merges, shorter sequences, but mostly for the training language

Train character-level BPE on a short English paragraph with 0 to 600 merges, then measure characters per token on an unseen English sentence and on a French sentence of similar meaning. Section 9.4 predicts that compression rises with the number of merges, and rises much less for the language the tokenizer never saw.

A tiny training text, so merges run out early (a pair must occur at least twice to be merged); real tokenizers learn tens of thousands of merges from gigabytes. The direction of the effect, not its size, is what carries over.

9.5 Quirks That Tokens CauseRun this section

The model sees token ids, never letters, and several apparent failures of reasoning are really tokenization:

  • Spelling and counting letters. strawberry may be one or two tokens, and the model has no direct view of the letters inside, so "how many r's are in strawberry?" is harder than it looks.
  • Arithmetic. 1234 might be one token and 12345 two (123, 45), so digits do not line up in consistent columns and carrying is hard to learn. Some tokenizers, LLaMA's among them, split numbers into single digits for this reason.
  • Leading spaces. A prompt ending in a trailing space forces the next word to start without its usual leading-space token, a pattern rarely seen in training, and output often degrades.
  • Under-trained tokens. A token merged from text later filtered out of the training data stays near random, and prompting with it can produce erratic output. Such "glitch tokens" exist in several released models.

When a model fails a simple-looking task, check how the input was tokenized first.

9.6 What the Reference Implementation Tokenizes

The reference implementation uses a character vocabulary of 67 tokens: <PAD> (padding), <UNK> (unknown), and every distinct character in the training text, sorted.

python
# src/infra/tokenizer.py
self.stoi = {self.PAD_TOKEN: 0, self.UNK_TOKEN: 1}
for character in sorted(set(text)):
    self.stoi[character] = len(self.stoi)

This keeps the tokenizer out of the results, so the labs' parameter counts and losses reflect the architecture. A 50,257-token vocabulary would not fit anyway: at width 64, its embedding table alone would be about 3.2 million numbers, nearly 30 times the whole 112k-parameter model.

Its costs are exactly what BPE fixes:

  • Unseen characters fail. The Shakespeare text has no accented letters, so "café" gives c, a, f, <UNK>.
  • Sequences are longer. An English word is five to six characters with its space, against about 1.3 BPE tokens, so text takes roughly four times the context.
  • It does not transfer to any other text.

Measured (lab 1a). The vocabulary has 67 tokens, and its embedding table holds 4,288 of the model's 112,323 parameters, under 4%4\%. At that size the vocabulary is a design choice, not a cost worth worrying about.

Exercises

Check

Why does a byte-level BPE tokenizer never need an <UNK> token?

Apply

The corpus is aaab aab ab (three words). Run BPE by hand: what are the first two merges?

Apply

With a character vocabulary, how many tokens does café cost if é was never seen in training? How many does a byte-level tokenizer with no merges need?

Apply

How many parameters does a 256,000-entry embedding table take at dmodel=3584d_{\text{model}} = 3584, in millions?

Extend

Why might a model answer "what is 4831 + 2967?" less reliably than "what is 4831 plus 2967, digit by digit?" Relate your answer to tokenization.

Core Academic References

  • Sennrich, Haddow & Birch (2016): Neural Machine Translation of Rare Words with Subword Units (BPE). ACL 2016. arXiv:1508.07909
  • Schuster & Nakajima (2012): Japanese and Korean Voice Search (WordPiece). ICASSP 2012.
  • Kudo (2018): Subword Regularization (Unigram). ACL 2018. arXiv:1804.10959
  • Kudo & Richardson (2018): SentencePiece. EMNLP 2018. arXiv:1808.06226
  • Petrov et al. (2023): Language Model Tokenizers Introduce Unfairness Between Languages. NeurIPS 2023. arXiv:2305.15425
  • Land & Bartolo (2024): Fishing for Magikarp: Automatically Detecting Under-trained Tokens in Large Language Models. arXiv:2405.05417

Measured labs

These labs test what this module says by running real code. Each card states the idea, what the experiment found, and what it does not prove. The numbers come from experiments/results.json, the output of running experiments/run_all.sh on the tiny-transformers reference code at version 137f7ef. The project checks fail if a measured number in the module text stops matching that file.

Lab 1a — how big the reference model is

What the module says

Module 1 explains that a token embedding is a table lookup: each token id picks one row, a vector of dmodeld_{\text{model}} numbers. It also quotes parameter counts for large production models.

What the experiment found

The reference model has 112,323 parameters, which take 438.8 KiB when each is stored as a 32-bit float (fp32). That is small enough to inspect every weight yourself, which is the reason to study a model this size.

total parameters
112,323

2 blocks, dmodel=64d_{\text{model}}=64, feed-forward width dff=256d_{\text{ff}}=256, 2 attention heads

size on disk in fp32
438.8 KiB
vocabulary size
67

each token is one character, not a word piece

position table parameters
4,096

learned during training, not the fixed sine waves of section 7.1

characters of training text
1,059,620

What this does not prove

The two embedding tables (tokens and positions) hold 8,384 of the 112,323 parameters, about 7.5%. This lab counts parameters; it does not measure how the share changes in larger models, where the vocabulary and width are both far bigger.

Run it yourself

bash
cd tiny-transformers && .venv/bin/python experiments/parameter_count.py

The code being tested