Byte-Pair Encoding from Scratch Byte-Pair Encoding (BPE), originally a data compression algorithm published by Philip Gage in 1994, was adapted in 2016 by Sennrich, Haddow, and Birch for subword tokenization in neural machine translation. The algorithm builds a subword vocabulary by iteratively merging the most frequent adjacent byte pairs in a corpus, enabling models to handle rare and unseen words without a fixed vocabulary. BPE remains a foundational technique in modern NLP tokenizers. Byte-Pair Encoding from Scratch Unicode, Bytes, and What Text Actually Is /blog/unicode-and-bytes How do you turn a sentence into numbers? A neural network operates on tensors of floating-point values. You can't feed it the string "The cat sat on the mat" directly. At some point there has to be a mapping from text to integers, and from integers to vectors. We covered the raw byte representation of text in the previous post /blog/unicode-and-bytes . Now we need the next piece: how to chop that text into tokens , and build the vocabulary that maps each token to an ID. The obvious approach is to split on whitespace and punctuation. Every unique word gets an ID. This blows up immediately. English alone has hundreds of thousands of word forms once you count conjugations, plurals, possessives, and compound words. German is worse. Agglutinative languages like Turkish or Finnish are catastrophic. Any word not seen during training becomes an UNK token, a black hole in the model's understanding. What about going the other direction? Characters. The English alphabet is 26 letters plus some punctuation. Tiny vocabulary, no UNK problem. But now the model has to learn everything from scratch: that c-a-t means something, that -ing is a suffix, that spaces separate words. Sequences get absurdly long. A single paragraph might be 500 characters, and the model has to attend over all of them with quadratic cost. Subword tokenization is the compromise that won. Split common words into single tokens, break rare words into pieces, and give the model a vocabulary that's big enough to be useful but small enough to be manageable. Byte-Pair Encoding is the algorithm that kicked off this whole line of work, and it's what we're going to build from scratch. BPE as compression The algorithm actually comes from data compression, not NLP. Philip Gage published it in the C Users Journal in 1994. The idea is almost comically simple: - Start with a sequence of bytes. - Find the most frequent adjacent pair of bytes. - Replace every occurrence of that pair with a new byte a symbol that didn't exist before . - Repeat until you've done enough replacements or no pair appears more than once. Each step reduces the total length of the sequence by replacing two symbols with one. The "merge table" you accumulate along the way is the compression dictionary. To decompress, you just apply the merges in reverse. Gage's original application was compressing files. But in 2016, Sennrich, Haddow, and Birch realized that the same algorithm could solve the vocabulary problem for neural machine translation. Instead of compressing bytes, you compress a corpus of words. Instead of a compression dictionary, you get a tokenizer. From compression to tokenization The key insight of Sennrich et al. is that BPE's merge table is a subword vocabulary. Here's how it works: Training phase: You take your training corpus, split every word into individual characters plus a special end-of-word marker , and then run the BPE algorithm. Count all adjacent pairs across the entire corpus, merge the most frequent one, update the corpus, repeat. After merges you have a vocabulary of base characters + merged tokens. Encoding phase: Given a new word at inference time, you start with its character sequence and apply the learned merges greedily, in the order they were learned during training. Merge 1 first, then merge 2, and so on. When no more applicable merges remain, whatever tokens you're left with are the segmentation. The order of the merges matters at encoding time. You don't just look for the longest possible token; you replay the merge sequence. We'll come back to why. Let me make this concrete. Here's the BPE algorithm running on a small corpus. Click through the steps and watch how the vocabulary grows. BPE merge algorithm on a toy corpus. Each step merges the most frequent adjacent pair. Notice how the first merges tend to grab common character pairs like e, r or l, o . These aren't linguistically meaningful morphemes. The algorithm doesn't know what a morpheme is. It's just counting frequencies. The token er appears because newer and wider both end in -er , and those words are common enough that the pair e, r rises to the top. The algorithm also merges lo, w into low pretty quickly, because the word "low" appears five times. By the end, frequent whole words like low and new are single tokens, while rarer words stay partially segmented. Building a BPE trainer Let's implement this properly. The trainer needs to: - Take a corpus and split it into character sequences with word frequencies for efficiency . - Count pair frequencies across the whole corpus, weighted by word frequency. - Merge the top pair everywhere. - Repeat to a target vocabulary size. The core data structure is a dictionary that maps each word as a tuple of tokens to its frequency in the corpus. We never re-scan the raw text after the initial pass. Let me walk through a concrete run. We'll train on a small corpus where word frequencies are realistic enough to see interesting behavior: Look at what happens. Merges 1-2 collapse e + r +