› 01 · Tokens › BPE
⚡ Visual Simulator
AI Journey · Understand Language

Byte Pair Encoding (BPE)

Master Byte Pair Encoding from first principles: why it exists, how pairs are merged iteratively step by step, an interactive merge visualizer, a full scratch Python implementation, and real-world token engineering mechanics.

🔴 MUST 15 Study Phases Tokens Core Prerequisite: Subword Tokens
🧙‍♂️ The Wise Scholar's Metaphor Pair Merging Shorthand
⏮️ 00 · Math & Foundations Hub →

BPE: How the Scholar Invents Shorthand Blocks

The scholar starts as a child knowing only 256 individual characters. Every time two adjacent letters appear together thousands of times across the library (like t + h → th, then th + e → the), the scholar welds them into a single durable Lego block. Repeating this $K$ times builds the exact vocabulary $V$, allowing the scholar to read long passages in fewer, richer glance tokens.

01

IntroductionWhat is Byte Pair Encoding?

🤔 Layman Curiosity Hook

"Why is it that a simple computer compression trick designed in 1994 to save space on old floppy disks is now the core algorithm powering the 'eyes' of GPT-4, LLaMA, and Claude?"

Back in 1994, Philip Gage wanted a lossless way to shrink raw text files. He invented Byte Pair Encoding (BPE). Decades later, AI researchers realized Gage's compression trick solved their absolute worst mathematical headache.

💡 This is how it happens: Gage's trick was simple: find the most common pair of adjacent symbols (like 't' and 'h') and replace them with a brand new single symbol (like 'th'). Keep repeating this merge game until you run out of slots. AI researchers realized that by running this exact same merge loop on massive collections of internet text, the computer automatically learns the optimal syllables and roots of human speech—without a single linguist or grammar textbook!

Byte Pair Encoding (BPE) is the dominant subword tokenization algorithm in modern Large Language Models (including GPT-2, GPT-3, GPT-4, LLaMA, Mistral, and Claude). It begins with individual base characters (or raw bytes) and iteratively merges the most frequent adjacent symbol pairs in a training corpus to create a compact, reusable dictionary of subwords.

  • Bottom-Up Construction: Starts with basic alphabet characters and grows larger semantic units through iterative pair merging.
  • Out-of-Vocabulary Immunity: Any word—even typos or made-up terms—can be broken down into familiar base pieces or raw bytes without loss.
  • Dual Artifacts: Training produces two core outputs: a learned vocabulary of tokens and an ordered list of merge rules.

📦 Origin & Data Compression

Invented in 1994 by Philip Gage as a lossless byte-level data compression technique. In 2016, Sennrich et al. adapted it for Neural Machine Translation to solve the vocabulary explosion problem.

🎯 The Modern Result

A balanced vocabulary that keeps frequent words intact as single tokens while decomposing rare or complex compound words into frequent subword fragments.

Raw Characters / Bytes→ Count Adjacent Pairs→ Merge Most Frequent Pair→ Repeat N Steps→ Vocabulary & Merge Rules
Mental Anchor: BPE = "Iteratively merge the most frequent adjacent pair until you hit your vocabulary budget."
02

WhyThe Problem BPE Solves

Before subword tokenization, Natural Language Processing suffered from a severe trade-off between word-level and character-level modeling:

  • Word-level tokenization: Requires an entry for every distinct inflected word, slang term, and misspelling. When vocabulary exceeds 50k–100k, embedding matrices consume gigabytes of VRAM. Unseen words collapse into the catastrophic [UNK] (unknown) token.
  • Character-level tokenization: Solves out-of-vocabulary words but produces extremely long sequence lengths. A 1,000-word essay becomes 6,000 character tokens, exhausting the quadratic $O(N^2)$ self-attention window and weakening long-range dependencies.
  • BPE's Sweet Spot: Common words stay whole ("the" $\rightarrow$ 1 token), rare words decompose into reusable pieces ("tokenization" $\rightarrow$ ["token", "ization"]), and sequences stay short while preventing [UNK] tokens.

❌ Word-Level Failure

Input: "hyperparameterization"
Vocab lookup fails $\rightarrow$ [UNK]. The model loses all semantic meaning and context.

✅ BPE Resolution

Input: "hyperparameterization"
Splits into: hyper + parameter + ization. Every piece is familiar, preserving rich semantics.

03

IntuitionBuilding Vocabulary from LEGO Bricks

Imagine building language with LEGO bricks. Initially, you are given only individual $1\times1$ plastic studs (characters). Every word in your corpus is spelled out stud by stud.

Now, you inspect the floor and ask: "Which two adjacent studs appear together most frequently?" You superglue those two together into a new $1\times2$ brick. You scan the floor again and glue the next most frequent adjacent combo. After thousands of cycles, you have pre-assembled common brick shapes (stems, prefixes, suffixes, and entire words), yet you can always fall back to single studs for rare structures.

Step-by-Step Evolution
Initial state: Every word is segmented into characters + end-of-word marker (_)
  "low"    →  l  o  w  _
  "lower"  →  l  o  w  e  r  _
  "newest" →  n  e  w  e  s  t  _
  "widest" →  w  i  d  e  s  t  _

1. Most frequent pair: ('e', 's') appearing in "newest" and "widest"
   Merge: 'e' + 's' → 'es'

2. Next most frequent: ('es', 't')
   Merge: 'es' + 't' → 'est'

3. Next most frequent: ('l', 'o') and ('lo', 'w')
   Merge: 'l' + 'o' → 'lo' → 'low'

Vocabulary grows organically from individual characters into meaningful subwords!
Key Insight: BPE operates without any linguistic rules or dictionary of English grammar. It discovers stems, prefixes, and morphology strictly through statistical co-occurrence.
04

TheoryTwo Distinct Phases: Training vs. Tokenization

A frequent beginner misconception is confusing BPE Training with BPE Tokenization (Inference). These are two completely distinct operations:

Phase A: BPE Training (Vocabulary Learning)

  • Input: Large training corpus of raw text + target vocabulary budget $V$.
  • Initialization: Pre-tokenize text into words, append boundary markers (like _ or Ġ), and split into characters. Base vocabulary $V_0$ consists of all unique characters.
  • Iteration: At step $k$, count all adjacent symbol pairs across all words. Find the argmax pair $(a, b)$, create token $ab$, add it to vocabulary $V_k = V_{k-1} \cup \{ab\}$, and record merge rule $a + b \rightarrow ab$.
  • Termination: Stops when target merge count $N = V - |V_0|$ is reached or no pairs remain.

Phase B: BPE Tokenization / Inference (Applying Merges)

  • Input: A new unseen sentence or prompt.
  • Initialization: Split word into individual characters.
  • Deterministic Replay: Iterate through the ordered list of learned merge rules exactly in the order they were learned during training. For each rule $(a, b) \rightarrow ab$, if adjacent tokens $a$ and $b$ appear in the word, merge them.
  • Output: The resulting sequence of token IDs mapped from the vocabulary table.
Critical Rule: In BPE tokenization, merge rules must be evaluated strictly in their historical training order (by merge priority rank), not by local frequency in the test sentence.
05

How It WorksThe Merge Loop Algorithm

Here is the formal algorithmic structure of BPE training in pseudocode:

Algorithm Pseudocode
# BPE Training Loop Pseudocode
def train_bpe(corpus_word_freqs, target_merges):
    # 1. Initialize vocabulary with base characters
    vocab = {char for word in corpus_word_freqs for char in word}
    
    # 2. Represent each word as a tuple of symbols + end marker
    splits = {word: list(word) + ['_'] for word in corpus_word_freqs}
    merge_rules = []
    
    # 3. Iterative merge loop
    for step in range(target_merges):
        pair_counts = Counter()
        for word, symbols in splits.items():
            freq = corpus_word_freqs[word]
            for i in range(len(symbols) - 1):
                pair_counts[(symbols[i], symbols[i+1])] += freq
                
        if not pair_counts:
            break
            
        best_pair = max(pair_counts, key=pair_counts.get)
        new_symbol = best_pair[0] + best_pair[1]
        
        # Apply merge across all words in split table
        for word in splits:
            splits[word] = merge_pair_in_sequence(splits[word], best_pair, new_symbol)
            
        vocab.add(new_symbol)
        merge_rules.append(best_pair)
        
    return vocab, merge_rules
Corpus Word Frequencies→ Scan Adjacent Pairs→ Argmax Highest Frequency→ Merge & Update Corpus→ Store Rank in Rules Table
06

CalculateManual BPE Step-by-Step Worked Example

Let's manually execute 3 full merge steps on a controlled toy corpus. Follow every frequency calculation by hand:

CORPUS
Initial Corpus Frequencies (with end marker _):
• "aa_" (count = 5)
• "ab_" (count = 3)
• "ba_" (count = 2)
STEP 0
Initial Sequence Splits:
a a _  ($\times 5$)
a b _  ($\times 3$)
b a _  ($\times 2$)

Pair Frequency Tally:
• $(a, a) = 5$
• $(a, \_) = (3 \text{ from } ab\_) + (2 \text{ from } ba\_) = 5$
• $(a, b) = 3$
• $(b, \_) = 3$
• $(b, a) = 2$
Tie between $(a,a)$ and $(a,\_)$—we select $(a,a)$ by alphabetical rule.
MERGE 1
Execute Rule 1: $(a, a) \rightarrow \mathbf{aa}$
Updated Corpus:
aa _  ($\times 5$)
a b _  ($\times 3$)
b a _  ($\times 2$)

Re-count Pairs:
• $(aa, \_) = 5$  (HIGHEST)
• $(a, b) = 3$
• $(b, \_) = 3$
• $(b, a) = 2$
MERGE 2
Execute Rule 2: $(aa, \_) \rightarrow \mathbf{aa\_}$
Updated Corpus:
aa_  ($\times 5$)  (Now a single atomic word token!)
a b _  ($\times 3$)
b a _  ($\times 2$)

Re-count Pairs:
• $(a, b) = 3$  (HIGHEST)
• $(b, \_) = 3$  (Tie resolved to $(a,b)$)
• $(b, a) = 2$
MERGE 3
Execute Rule 3: $(a, b) \rightarrow \mathbf{ab}$
Updated Corpus:
aa_  ($\times 5$)
ab _  ($\times 3$)
b a _  ($\times 2$)

Final Learned Merge Rules (in strict priority order):
1. $(a, a) \rightarrow aa$
2. $(aa, \_) \rightarrow aa\_$
3. $(a, b) \rightarrow ab$
Tie-breaking note: Frequency ties are broken deterministically by alphabetical order or hash table ordering. In production tokenizers like HuggingFace Tokenizers or tiktoken, ties are indexed by first occurrence.
07

MathematicsVocabulary Sizing & Complexity Formulas

The mathematics of BPE governs vocabulary explosion, token compression rates, and lookup computational complexity.

FORMULA
Exact Vocabulary Size Equation: $$|V| = |V_{\text{initial}}| + N_{\text{merges}} + N_{\text{special}}$$ Where:
  • $|V_{\text{initial}}|$ = size of initial base character alphabet (or 256 for Byte-Level BPE).
  • $N_{\text{merges}}$ = total number of merge iterations trained.
  • $N_{\text{special}}$ = reserved special tokens (e.g. <|endoftext|>, <|pad|>).
GPT-2
GPT-2 Byte-Level Sizing: $$|V_{\text{initial}}| = 256 \text{ (all raw UTF-8 bytes)}$$ $$N_{\text{merges}} = 50{,}000$$ $$N_{\text{special}} = 1 \text{ (\<|endoftext|\>)}$$ $$|V_{\text{total}}| = 256 + 50{,}000 + 1 = 50{,}257$$
COMPLEXITY
Time & Space Complexity:
• Naive Training: $O(N_{\text{merges}} \cdot |C|)$ where $|C|$ is total corpus token length.
• Optimized Training (Min-Heap / Inverted Index): $O(|C| \log |V|)$ by tracking pair locations.
• Tokenization: $O(L^2)$ or $O(L \log L)$ for an input sequence of length $L$.
08

VisualizePipeline Architecture & Live Interactive Demo

Here is the complete state machine representing BPE training and tokenization cycles:

graph TD
    subgraph "Training Phase"
    A[Raw Training Corpus] --> B[Word Freq Dictionary]
    B --> C[Split Words into Chars]
    C --> D[Count All Adjacent Pairs]
    D --> E[Select Most Frequent Pair a, b]
    E --> F[Create Merged Token ab]
    F --> G[Update Corpus Sequences]
    G --> H{Merge Count < N?}
    H -- Yes --> D
    H -- No --> I[Save Merge Rules & Vocab]
    end

    subgraph "Inference / Tokenization Phase"
    J[New Input Word] --> K[Split into Initial Chars]
    K --> L[Replay Learned Merges in Order]
    L --> M[Output Subword Tokens & IDs]
    end

    I -.-> L

    style A fill:#1e2235,stroke:#3b82f6
    style I fill:#1a1e2e,stroke:#34d399,stroke-width:2px
    style M fill:#1a1e2e,stroke:#38bdf8,stroke-width:2px
          
Figure 1.0: End-to-end BPE lifecycle from frequency learning to ordered inference replay.
🔀 Live Animator: Interactive BPE Merge Simulator
Step Through How BPE Discovers & Glues Subwords in Real Time
Merge Step: 0 / 5 Initial Character Splits
Training Corpus State & Word Splits:
📊 Top Adjacent Pair Candidates
📜 Learned Merge Rules (Priority Order)
🔍 Behind-the-Scenes: Mathematical Pipeline of Every BPE Step

In every single merge step $k$, BPE executes these 4 exact mathematical phases:

  1. Frequency Counting: Counts frequency $f(p_1, p_2)$ across all adjacent tokens in the tokenized corpus.
  2. Argmax Selection: Finds $(p_a^*, p_b^*) = \arg\max f(p_a, p_b)$.
  3. Atomic Substitution: Replaces occurrences of $(p_a^*, p_b^*)$ with new compound token $p_{ab}^*$.
  4. Rule Insertion: Appends rule $(p_a^*, p_b^*) \to p_{ab}^*$ at index position $k$ in the lookup rank table.

⚡ Live Interactive BPE Tokenizer Inference

Type any word below (e.g. lowest, newer, widest, tokenize). The visualizer runs an active in-browser BPE engine trained on a live subword corpus (low, lower, lowest, new, newer, newest, widest) and shows every merge step applied.

Resulting Tokens:
Merge Execution Trace:
💡 Corpus Notice: This interactive toy engine learned merge rules on {"low":5, "lower":2, "lowest":3, "new":4, "newer":2, "newest":6, "widest":3}. Notice how est and low merge into single subwords!
09

CodeComplete Python BPE Implementation From Scratch

Here is the full, clean, dependency-free Python implementation of both BPE Training and BPE Tokenization:

Python 3 — Full BPE Scratch Engine
from collections import Counter
import re

# ── 1. Corpus Preparation ──
def get_vocab(corpus_dict):
    """Convert word-frequency dictionary into space-delimited character strings."""
    vocab = Counter()
    for word, freq in corpus_dict.items():
        # Represent word as space-separated characters with end-of-word token '_'
        vocab[' '.join(list(word) + ['_'])] += freq
    return vocab

# ── 2. Frequency Counter for Adjacent Pairs ──
def get_pairs(vocab):
    """Scan vocabulary words and accumulate adjacent pair frequencies."""
    pairs = Counter()
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols) - 1):
            pairs[(symbols[i], symbols[i + 1])] += freq
    return pairs

# ── 3. Merge Selected Pair in Vocabulary ──
def merge_vocab(pair, vocab):
    """Replace occurrences of (pair[0], pair[1]) with concatenated token."""
    new_vocab = {}
    bigram = re.escape(' '.join(pair))
    replacement = ''.join(pair)
    pattern = re.compile(r'(?

🚀 Key Optimization

Production implementations replace regex loops with a Doubly Linked List and Min-Heap to execute merges in $O(N \log V)$ time instead of scanning whole strings.

⚡ Byte-Level Extension

Before running BPE, text is passed to text.encode('utf-8'). Each byte (0–255) is mapped to a unicode string to prevent split UTF-8 character corruption.

10

Hands-on ExperimentCorpus Variation & Domain Shift

To truly understand BPE, observe how changing the training corpus radically changes the learned merge rules:

> python train_bpe.py --corpus "english_news.txt" --merges 5000
[INFO] Learned Top Merges: 't' + 'h' → 'th', 'th' + 'e' → 'the', 'i' + 'n' → 'in', 'in' + 'g' → 'ing'
[INFO] Tokenizing 'def calculate_loss(x):': ['d', 'ef', ' c', 'al', 'cul', 'ate', '_', 'l', 'oss', '(', 'x', ')', ':'] (13 tokens)

> python train_bpe.py --corpus "python_github_repos.txt" --merges 5000
[INFO] Learned Top Merges: 'd' + 'ef' → 'def', 's' + 'elf' → 'self', 'r' + 'eturn' → 'return', '_' + '_' → '__'
[INFO] Tokenizing 'def calculate_loss(x):': ['def', ' calculate', '_', 'loss', '(x):'] (5 tokens - 61% compression gain!)
11

CompareBPE vs. WordPiece vs. SentencePiece vs. Unigram

How does BPE compare to other major subword tokenization algorithms used across modern AI models?

Algorithm Merge / Split Selection Metric Boundary & Prefix Marker Pre-tokenization Need Primary LLMs Using It
BPE (Byte Pair Encoding) Raw adjacent pair co-occurrence frequency Ġ prefix (GPT) or _ suffix Requires whitespace pre-tokenization regex GPT-2, GPT-3.5, GPT-4 (tiktoken), LLaMA 1/2/3, Mistral
WordPiece Corpus likelihood score: $\frac{\text{count}(ab)}{\text{count}(a)\text{count}(b)}$ ## prefix on continuation pieces Requires whitespace and punctuation splitting BERT, DistilBERT, RoBERTa (vocab style), MobileBERT
SentencePiece (Unigram) Top-down entropy / loss reduction pruning (U+2581) explicit space token None (Treats raw byte stream losslessly) T5, mT5, ALBERT, Gemma, LLaMA-based SentencePiece
Byte-Level BPE Frequency on UTF-8 raw byte representations Direct byte-to-unicode character mappings Regex splitting (prevents punctuation merging) GPT-4, Claude 3/3.5, Command-R
Key Distinction: BPE merges bottom-up based strictly on raw frequency, whereas Unigram (SentencePiece) starts with a massive vocabulary and prunes top-down based on statistical language model likelihood.
12

ApplyReal-World Architecture & Token Engineering

In production LLMs, BPE is the critical gateway governing efficiency, cost, and latency across multiple fronts:

  • Context Window Budgeting: LLM pricing ($/1M tokens) and maximum context limits (8k, 32k, 128k, 1M tokens) are strictly counted in BPE tokens. Efficient BPE tokenization directly reduces API costs and allows longer documents to fit in the context window.
  • Byte-Level tiktoken (OpenAI): OpenAI's Rust-based tiktoken optimizes BPE with regex splitting patterns: (?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+. This prevents cross-category merges (e.g. letters and numbers are never glued into the same token).
  • Cross-Lingual Token Inequity (Token Tax): English sentences compress into few tokens (~1.3 tokens/word), whereas non-Latin scripts (Arabic, Hindi, Japanese) may require 3–5 tokens/word if under-represented in the BPE training corpus.
Raw User Prompt→ tiktoken Regex Split→ BPE Merge Rules→ Token IDs [15496, 11, ...]→ Embedding Layer→ Transformer Layers
13

LimitsLimitations & Common Pitfalls

Greedy Myopia: BPE is a greedy algorithm. Because it chooses the single most frequent adjacent pair at each step, it does not guarantee a globally optimal tokenization for whole sentences.

⚠️ Whitespace Sensitivity

In GPT-style BPE, " Apple" (with leading space) and "Apple" (no space) are mapped to completely different token IDs (e.g. 22334 vs 9652). This affects few-shot prompt formatting.

⚠️ Character Arithmetic Failure

Numbers like "18475" might split arbitrarily into ["18", "475"]. The model never sees individual digits cleanly, explaining why LLMs often struggle with multi-digit math without chain-of-thought.

⚠️ Tokenizer Incompatibility

A BPE tokenizer is strictly coupled to its model. You cannot feed token IDs generated by the LLaMA tokenizer into a GPT-4 or Claude model—their vocabulary tables and merge sequences are completely distinct.

⚠️ False Morpheme Assumption

BPE does not split based on linguistic morphology. It splits purely by statistical frequency. Occasional weird subwords (like " th" or "ing" with punctuation attached) are normal artifacts.

14

ExplainThe Feynman Challenge & Mastery Check

The Scrabble Tile Compressor Metaphor

Explain BPE to a non-technical friend in 60 seconds:

"Imagine you are sending a telegram, but you pay $1 for every wooden tile you send. At first, you only have single-letter alphabet tiles (A through Z). To send 'THE NEWEST ROBOT', you need 16 tiles."

"You notice that throughout millions of messages, the letters 'T' and 'H' appear side-by-side constantly. So you carve a brand new double-letter tile 'TH'. Next, you notice 'TH' and 'E' always touch, so you carve 'THE'. Next, you carve 'EST'."

"Now, you can send 'THE' as 1 tile, 'NEW' as 1 tile, and 'EST' as 1 tile. If someone sends a made-up word like 'ZORGLEST', you send 'Z', 'O', 'R', 'G', and your pre-carved 'EST' tile! You never get stuck, you never need an infinite box of words, and your telegram bill drops by 70%."

Mastery Check Quiz

15

BuildHands-On Mini-Project: 4-Milestone BPE Engine

To cement your mastery of Byte Pair Encoding, build this 4-milestone standalone tokenizer project in Python:

🎯 Project Milestones

  1. Milestone 1 — Toy BPE Trainer: Write a pure Python script that takes a Wikipedia paragraph, calculates bigram frequencies, and trains 50 merge rules. Print the top 10 discovered subwords.
  2. Milestone 2 — Deterministic Tokenizer: Implement the inference tokenization function. Verify that applying merges in reverse order or out-of-order breaks the tokenization.
  3. Milestone 3 — Byte-Level Support (UTF-8): Extend your tokenizer to read raw UTF-8 bytes (0–255). Feed it multi-byte emojis (e.g. 🤖, 🌍, 🚀) and verify zero [UNK] fallback.
  4. Milestone 4 — tiktoken Benchmark: Compare your scratch token count vs OpenAI's tiktoken.get_encoding("cl100k_base") on a 500-word essay. Analyze why differences occur.
✓

SummaryKey Takeaways

  • Core Problem: Word-level vocabularies fail on rare words ($[UNK]$); character tokenization produces sequence lengths that are too long.
  • BPE Mechanism: Starts with basic characters/bytes and iteratively merges the most frequent adjacent pairs.
  • Artifacts: BPE produces two objects: a learned vocabulary and an ordered list of merge rules.
  • Inference: Tokenization deterministically replays merge rules in the exact order they were learned during training.
  • Zero [UNK]: Byte-level BPE (GPT-2, GPT-4, LLaMA) starts with all 256 byte values, ensuring every possible digital byte stream can be tokenized.
  • Next Step: Learn how Google's WordPiece (BERT) and SentencePiece (Unigram LM) refine these principles!
🐙
Go Deeper · Code it yourself
ai-engineering-from-scratch
Open-source curriculum with two hands-on BPE lessons — implement the merge algorithm from raw Python (no libraries), then build a production tokenizer in Rust and Python.
This page 0:00
This session 0:00
Total (all pages) 0:00
Today 0:00
▶ Running
📊 View all study stats →
0:00 studying