Tokenization: BPE and SentencePiece
Tokenization is the process of breaking raw text into discrete units called tokens — the atomic pieces that language models actually read and process. It matters because no neural network can operate directly on characters or words; tokenization is the essential translation layer between human language and machine arithmetic. The counterintuitive fact: a single English word like "unbelievable" might become 3–4 tokens, while a single emoji like 🎉 might become 6 tokens — meaning LLMs are often "charged" more to process symbols than entire common words.
The core insight is that you need a vocabulary size that is Goldilocks: not so small (character-by-character) that sequences become impossibly long, and not so large (whole-word) that rare words go unrepresented. Byte Pair Encoding (BPE) and SentencePiece both solve this by learning a compression scheme from data — they discover which subword chunks appear together so frequently that it makes sense to treat them as one unit. The "aha moment" is realizing that tokenization is not a linguistic decision — it is a statistical compression decision, and the vocabulary a model learns is as much a hyperparameter as learning rate.
How It Works
Tokenization using BPE proceeds in two distinct phases: training (learning which subword merges to make) and encoding (applying those merges to new text).
Phase 1 — Training BPE
You start with a large corpus of text. Initially, every character is its own token. You then repeatedly find the most frequently co-occurring pair of adjacent tokens and merge them into a single new token. You repeat this for a fixed number of steps, which becomes your vocabulary size.
Explanation: The merge score for a pair (A, B) is simply how many times those two tokens appear side-by-side in the current training corpus — the highest-scoring pair gets merged next. After `k` merge steps, your vocabulary contains the initial character set plus `k` learned merge rules.
Explanation: The final vocabulary size |V| equals the number of unique starting characters |C| plus the number of merge operations k you performed — so vocabulary size is a direct, controllable hyperparameter. Here is a minimal Python implementation of BPE training: ```python from collections import Counter, defaultdict def get_pairs(vocab): """Count all adjacent symbol pairs across all words.""" 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 def merge_vocab(pair, vocab): """Merge the best pair in all words.""" new_vocab = {} bigram = ' '.join(pair) replacement = ''.join(pair) for word, freq in vocab.items(): new_word = word.replace(bigram, replacement) new_vocab[new_word] = freq return new_vocab
# Example corpus (words with space-separated characters, marks word end) vocab = { 'l o w ': 5, 'l o w e r ': 2, 'n e w e s t ': 6, 'w i d e s t ': 3, } num_merges = 10 for i in range(num_merges): pairs = get_pairs(vocab) if not pairs: break best = max(pairs, key=pairs.get) vocab = merge_vocab(best, vocab) print(f"Merge {i+1}: {best} → {''.join(best)}") ``` Running this yields merge steps like: ``` Merge 1: ('e', 's') → es Merge 2: ('es', 't') → est Merge 3: ('n', 'e') → ne ... ```
Phase 2 — Encoding New Text
To encode a new word, you apply the learned merge rules in the order they were learned (most frequent first). This is crucial — order matters. ```python def encode_word(word, merges): """Apply BPE merge rules to a single word.""" symbols = list(word) + [''] for merge_pair in merges: # merges ordered by priority i = 0 while i < len(symbols) - 1: if (symbols[i], symbols[i+1]) == merge_pair: symbols = symbols[:i] + [''.join(merge_pair)] + symbols[i+2:] else: i += 1 return symbols
# Usage merges = [('e','s'), ('es','t'), ('n','e')] # learned order print(encode_word("newest", merges))
# → ['ne', 'w', 'est', ''] ```
SentencePiece: BPE Without Pre-Tokenization
SentencePiece (Google, 2018) makes one critical change: it treats the raw byte stream as input, including spaces, rather than requiring you to split on whitespace first. This matters enormously for languages like Japanese, Thai, or Chinese that have no spaces. ```python import sentencepiece as spm
# Train a SentencePiece model spm.SentencePieceTrainer.train( input='my_corpus.txt', model_prefix='my_tokenizer', vocab_size=8000, model_type='bpe', # or 'unigram', 'char', 'word' character_coverage=0.9995, # cover 99.95% of characters pad_id=0, unk_id=1, bos_id=2, eos_id=3, )
# Load and use sp = spm.SentencePieceProcessor() sp.load('my_tokenizer.model') text = "Tokenization is fascinating!" tokens = sp.encode(text, out_type=str) ids = sp.encode(text, out_type=int) print(tokens) # ['▁Token', 'ization', '▁is', '▁fasci', 'nating', '!'] print(ids) # [142, 891, 23, 4421, 1893, 45] ``` Notice the `▁` (underscore-like) prefix — SentencePiece uses this to mark "token begins after a space," encoding word-boundary information directly in the token string.
Philip Gage was a software engineer who published BPE in 1994 as a lossless data compression algorithm in Dr. Dobb's Journal — not for NLP at all, but for compressing binary data. His specific contribution was the elegant greedy pair-merging scheme: always replace the most frequent byte pair with a single unused byte. The surprising fact: Gage's original algorithm was essentially forgotten until Rico Sennrich re-discovered and adapted it for neural machine translation in 2016, giving a 26-year-old compression trick a second life as the backbone of modern LLM tokenization.
Rico Sennrich is a computational linguist and NLP researcher at the University of Edinburgh and later University of Zurich, known for work on neural machine translation. His 2016 paper "Neural Machine Translation of Rare Words with Subword Units" (with Haddow and Birch) directly transplanted Gage's BPE compression idea into NLP, solving the open-vocabulary problem that plagued early seq2seq models. His paper has accumulated over 10,000 citations and is one of the most influential NLP papers of the decade, yet the core algorithm it adapts was written as a C programmer's compression trick in a magazine.
Intuition
Imagine you're an alien who has just landed on Earth and is trying to learn to read English by studying a massive library of books. You notice that the letters `t`, `h`, `e` appear next to each other constantly — so you decide to treat "the" as a single symbol. Then you notice "ing" always clusters together, and "un" often starts words. You're not thinking about grammar or meaning — you're just finding patterns of co-occurrence and creating shorthand symbols for them. That's exactly what BPE does: it looks at raw frequency in a corpus and greedily builds a vocabulary of high-frequency chunks. SentencePiece then extends this idea by saying: "I won't even assume I know what a word is — I'll treat the whole text as one long stream and let the patterns emerge," which is especially powerful when you move to languages where the concept of a space-separated word doesn't exist.
Historical Development
Origins · 1 minhow this was discovered
Why It Matters
Prerequisites
What This Leads To
See Also
Byte-Level BPE · WordPiece Tokenization · Unigram Language Model Tokenization · Vocabulary Size and Scaling · Text Preprocessing and Normalization