Frontend Labs
⋯

Chapter 28 — How BPE builds a vocabulary (by hand)

BPE (Byte-Pair Encoding) is the algorithm behind GPT, Llama, and most other models. It is genuinely simple: repeatedly merge the most frequent adjacent pair.

The training algorithm

1. Split your training text into words. Write each word as a list of characters,
   with a marker for the end of the word.
2. Count every adjacent pair of symbols across the whole corpus.
3. Merge the most frequent pair into one new symbol.
4. Repeat steps 2-3 until you have as many merges as you want.
5. SAVE THE ORDERED LIST OF MERGES. That list IS the tokenizer.

Fully worked example

Corpus (with how often each word appears):

"low"     x5     ->   l o w </w>
"lower"   x2     ->   l o w e r </w>
"newest"  x6     ->   n e w e s t </w>
"widest"  x3     ->   w i d e s t </w>

Round 1 — count all adjacent pairs (weighted by word counts):

(l,o)  = 5+2 = 7          (n,e)   = 6
(o,w)  = 5+2 = 7          (e,w)   = 6
(w,</w>) = 5              (w,e)   = 2+6 = 8
(e,r)  = 2                (e,s)   = 6+3 = 9   <-- tied winner
(r,</w>) = 2              (s,t)   = 6+3 = 9   <-- tied
                          (t,</w>) = 6+3 = 9  <-- tied
(w,i)  = 3   (i,d) = 3    (d,e)   = 3

Winner (break ties by first-seen): merge (e, s) -> "es"

l o w </w>        x5
l o w e r </w>    x2
n e w es t </w>   x6        <- "e s" became "es"
w i d es t </w>   x3

Round 2 — recount:

(es,t) = 6+3 = 9   <-- winner
(t,</w>) = 9
(l,o) = 7, (o,w) = 7, ...

Merge (es, t) -> "est":

l o w </w>       x5
l o w e r </w>   x2
n e w est </w>   x6
w i d est </w>   x3

Round 3: (est, </w>) = 9 wins. Merge -> "est"

Round 4: (l,o) = 7 and (o,w) = 7 tie. Merge (l,o) -> "lo"

Round 5: (lo, w) = 7 wins. Merge -> "low"

The learned merge list (this is the tokenizer):

1. e + s      -> es
2. es + t     -> est
3. est + </w> -> est</w>
4. l + o      -> lo
5. lo + w     -> low

Vocabulary = all individual characters plus es, est, est</w>, lo, low.

Using it on new text (encoding)

Apply the merges in the order they were learned:

Encode "lowest":
  start:   l o w e s t
  merge 1 (e+s):    l o w es t
  merge 2 (es+t):   l o w est
  merge 4 (l+o):    lo w est
  merge 5 (lo+w):   low est
  no more merges apply
  RESULT: ["low", "est"]     -> 2 tokens
Encode "newest":
  start:  n e w e s t
  merge 1: n e w es t
  merge 2: n e w est
  (merges 4,5 don't apply -- no "lo")
  RESULT: ["n", "e", "w", "est"]   -> 4 tokens

Notice: "lowest" costs 2 tokens (it's made of frequent pieces), "newest" costs 4. Frequency in the training corpus determines cost. That's the whole economics of tokenization.

The variants you'll see named

Name Idea Used by
BPE merge the most frequent pair GPT-2, GPT-3, most models
Byte-level BPE start from raw bytes (256 of them) instead of characters, so any Unicode text is representable and there's no <UNK> ever GPT-2 onwards, tiktoken
WordPiece like BPE but merges the pair that most improves the corpus likelihood, not raw frequency BERT
Unigram / SentencePiece start with a big candidate vocabulary and prune the least useful tokens T5, Llama, Mistral, Gemma
tiktoken OpenAI's fast byte-level BPE implementation; encodings are named cl100k_base (~100k tokens, GPT-3.5/4), o200k_base (~200k) OpenAI models

They differ in details; the mental model — build a vocabulary of frequent pieces — is the same for all.

Practice (30 min) — implement BPE

from collections import Counter

def get_pair_counts(words):
    """words: dict of tuple-of-symbols -> count"""
    pairs = Counter()
    for word, freq in words.items():
        for a, b in zip(word, word[1:]):
            pairs[(a, b)] += freq
    return pairs

def merge_pair(words, pair):
    new_words = {}
    a, b = pair
    for word, freq in words.items():
        out, i = [], 0
        while i < len(word):
            if i < len(word) - 1 and word[i] == a and word[i+1] == b:
                out.append(a + b)
                i += 2
            else:
                out.append(word[i])
                i += 1
        new_words[tuple(out)] = freq
    return new_words

# the corpus from the worked example
words = {
    tuple("low") + ("</w>",):     5,
    tuple("lower") + ("</w>",):   2,
    tuple("newest") + ("</w>",):  6,
    tuple("widest") + ("</w>",):  3,
}

merges = []
for step in range(10):
    pairs = get_pair_counts(words)
    if not pairs:
        break
    best = max(pairs, key=pairs.get)
    print(f"step {step+1}: merge {best} (seen {pairs[best]} times)")
    merges.append(best)
    words = merge_pair(words, best)
    for w in words:
        print("      ", " ".join(w))
    print()

print("learned merges:", merges)

Run it and check the first few merges match the hand-worked example above.

Then:

  1. Change the corpus to your own sentences. Which merges appear first? (Answer: whatever is most repeated.)
  2. Run 30 merges instead of 10. What happens to the number of tokens per word?
  3. Add a word that shares no letters with the others. How is it tokenized?

Train a real tokenizer

from tokenizers import Tokenizer, models, trainers, pre_tokenizers, decoders

tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=True)
tok.decoder = decoders.ByteLevel()

trainer = trainers.BpeTrainer(
    vocab_size=2000,
    special_tokens=["<|endoftext|>", "<|pad|>"],
    initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
)
tok.train(["input.txt"], trainer)       # the Shakespeare file from Chapter 26

out = tok.encode("To be or not to be, that is the question")
print(out.tokens)
print(len(out.ids), "tokens")

Compare the token count against tiktoken on the same sentence. A small vocabulary trained on one book will need more tokens than a 100k vocabulary trained on the internet.

Check yourself

  1. Describe the BPE training algorithm in three sentences.
  2. Why must merges be applied in the order they were learned?
  3. Why does "lowest" cost fewer tokens than "newest" in our toy example?

Further reading

  • Video (essential): Karpathy, "Let's build the GPT Tokenizer" (~2 h).
  • Code: github.com/karpathy/minbpe — a clean, minimal, well-commented BPE implementation to read after you write your own.
  • Paper: Sennrich, Haddow, Birch, "Neural Machine Translation of Rare Words with Subword Units" (2016) — the paper that brought BPE to NLP.
  • Docs: huggingface.co/learn/nlp-course/chapter6 — a full chapter on tokenizers with exercises.

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36