Byte Pair Encoding (BPE): How It Works and How to Support Multiple Languages
1. Vocabulary training
- Count every adjacent pair of tokens and select the most frequent pair.
- Join the pair into a new token, add it to the vocabulary, and replace occurrences of the pair from left to right without overlap.
- Save the merge rule and its position in the learning order, then update the pair counts.
1.1. A four-word example
low 5 times, lower 2 times, newest 6 times, and widest 3 times. Start from individual characters and allow merges only within words. To make the example reproducible, ties go to the smaller pair of token IDs.
Sort the initial characters by Unicode code point: d, e, i, l, n, o, r, s, t, w. Give them IDs 0 through 9, and assign new tokens IDs from 10 onward. For equal pair counts, compare the left IDs first, then the right IDs; the smaller pair wins.
For example, e + s has IDs (1, 7) and s + t has (7, 8), so e + s wins. The four words and their frequencies come from Algorithm 1 of Sennrich et al.[1]; this example omits their end-of-word symbol and uses the tie-breaking rule above.
- [1] Rico Sennrich, Barry Haddow, Alexandra Birch, "Neural Machine Translation of Rare Words with Subword Units," ACL, 2016.
e + s and s + t. Both appear in newest and widest, giving each a count of \(6+3=9\). The tie-breaking rule selects e + s → es. Both words now end in es | t, where | marks a token boundary. The new pair es + t also occurs 9 times and wins the next round, creating est.
low, shared by the 5 occurrences of low and the 2 of lower. A rule's rank records when it was learned, starting at 0. Frequencies below are counted immediately before each merge; output IDs identify the new tokens.
| Rank | Merge rule | Frequency | Output ID |
|---|---|---|---|
| 0 | e + s → es | 9 | 10 |
| 1 | es + t → est | 9 | 11 |
| 2 | l + o → lo | 7 | 12 |
| 3 | lo + w → low | 7 | 13 |
low is one token, lower is low | e | r, and both newest and widest end in the token est. The vocabulary contains 14 entries: 10 initial characters and 4 merged tokens. Here est coincides with an English suffix; the selection rule needed only its frequency.
1.2. Pair frequency and sequence length
es removes s + t and introduces es + t. BPE updates the pair counts and makes its next choice from the new sequence. This is a greedy procedure: it selects the current most frequent pair without looking ahead to later merges.
Counts include overlapping positions, while replacements do not. In aaa, a + a occurs twice, but only one occurrence can be replaced. Pair frequency can therefore exceed the number of tokens a merge removes.
Let \(f(s)\) be the number of times the token sequence \(s\) occurs in the training data. The frequency of the pair \((a,b)\) is then
\[ C(a,b)=\sum_s f(s)\sum_{i=1}^{|s|-1} \mathbf{1}[(s_i,s_{i+1})=(a,b)], \]where \(|s|\) is the current length of the sequence, \(s_i\) is its \(i\)-th token, and \(\mathbf{1}\) is the indicator function, equal to 1 when its condition holds and 0 otherwise.
The inner sum counts matching positions in one sequence; the outer sum weights that count by the sequence's frequency. Thus the 5 occurrences of low initially contribute 5 to both l + o and o + w.
2. Encoding with the learned merge ranks
lowest, which was absent from the training data, the initial sequence is l | o | w | e | s | t. Two learned rules apply: e + s at rank 0 and l + o at rank 2. Rank 0 wins, and the resulting es enables es + t at rank 1. The remaining merges create lo and then low. The result is low | est: an unseen word represented by two learned tokens.
[13, 11], the IDs for low and est in text order. Rank controls the order of operations; ID identifies a token in the model's vocabulary. Some implementations use the same number for both roles.
2.1. Merge order and longest match
b + c → bc, a + b → ab, and c + d → cd, in that order of priority. For abcd, BPE first creates bc and stops at a | bc | d. Choosing the longest vocabulary match from left to right would instead give ab | cd. The same vocabulary permits both segmentations; BPE's merge order selects the first, even though it uses an extra token.
2.2. A minimal encoder
The dictionary below contains the four rules from §1.1. Each value is (rank, output ID).
def encode_ids(initial_ids, rules):
sequence = list(initial_ids)
while len(sequence) >= 2:
candidates = [
(rules[pair][0], i, rules[pair][1])
for i, pair in enumerate(zip(sequence, sequence[1:]))
if pair in rules
]
if not candidates:
break
# Pick the smallest rank, then the leftmost position.
_, i, output = min(candidates)
sequence[i:i + 2] = [output]
return sequence
# Assign IDs 0 through 9 to the initial characters.
alphabet = {char: i for i, char in enumerate("deilnorstw")}
rules = {
(1, 7): (0, 10), # e + s → es
(10, 8): (1, 11), # es + t → est
(3, 5): (2, 12), # l + o → lo
(12, 9): (3, 13), # lo + w → low
}
initial_ids = [alphabet[char] for char in "lowest"]
print(encode_ids(initial_ids, rules)) # [13, 11] = low | est
The function also works with byte IDs and their merge rules. The code and data archive includes training, decoding, and the experiments in §4.2; it requires only the Python standard library.
Let \(n\) be the number of initial tokens and assume constant-time ID-pair lookup. If \(m\) merges occur, the Python code performs up to \(O(n)\) work per merge to scan candidates and modify the list. Including the first scan gives \(O(n+mn)\). Since \(m\leq n-1\), the worst case is \(O(n^2)\).
With a linked structure for adjacency and a priority queue, each merge creates only a constant number of new candidates. There are \(O(n)\) candidates over the whole run, each costing \(O(\log n)\) to insert or remove. This gives \(O(n\log n)\), assuming constant-time ID-pair lookup and adjacency updates.
For short inputs, array scans can be faster because they avoid the overhead of these structures. tiktoken 0.12.0 uses arrays and cites cache locality on short pieces as its reason[9]. Runtime comparisons should therefore use the piece lengths that the tokenizer actually processes after pre-tokenization (§3.2).
- [9] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
3. Characters, bytes, and preprocessing
3.1. Byte-level BPE and unseen characters
A occupies 1 byte, 猫 ("cat" in Chinese and Japanese) occupies 3, and 🙂 occupies 4[3]. Before any merges, these require 1, 3, and 4 tokens respectively. Learned merges can combine the bytes of a character or join several characters. They can also leave a token boundary inside a character: BPE operates on adjacent bytes without requiring each token to be valid UTF-8 on its own.
E7 8C and AB together recover 猫. Decoding either token separately can raise an error or insert replacement characters, depending on the decoder's error handling.
SentencePiece enables this behavior with byte_fallback[10]. The 256 byte entries count toward the requested vocabulary size, leaving fewer entries for characters and longer strings. Here bytes cover missing characters; in byte-level BPE, bytes are the starting units for learning every merge.
- [10] Google, "SentencePiece Training Options," SentencePiece Documentation, 2026.
3.2. Normalization and pre-tokenization
é can be one code point or e followed by a combining accent; Unicode NFC maps both to the same sequence. NFKC also applies compatibility mappings, such as converting full-width A to A[5]. This pools the variants' training counts and gives them the same encoding. It also discards their original distinction, so decoding recovers the normalized text.
hello world into hello and world, merges can build tokens within either piece but cannot cross the boundary. Increasing the vocabulary size leaves that restriction in place.
| Implementation | Segmentation components | Settings to check |
|---|---|---|
| tiktoken[9] | A rank table keyed by byte sequences, plus a regular expression for pre-tokenization | The encoding name, special tokens, and the correspondence between ranks and IDs |
| SentencePiece[10] | The segmentation algorithm, normalization, and other settings saved in one model file | Character coverage, byte fallback, normalization, and whitespace handling |
| Hugging Face Tokenizers[4] | A normalizer, a pre-tokenizer, a segmentation model such as BPE, and a decoder | The settings of each component and how added tokens are matched |
SentencePiece defaults to a custom NFKC-based rule set. Its identity option disables character normalization; whitespace has separate controls[11]. Check those too: collapsing consecutive spaces, for example, loses information even when the characters are otherwise preserved.
- [9] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
- [10] Google, "SentencePiece Training Options," SentencePiece Documentation, 2026.
- [4] Hugging Face, "The tokenization pipeline," Tokenizers Documentation, 2026.
- [11] Google, "Text Normalization in SentencePiece," SentencePiece Documentation, 2026.
4. Language mix and token counts
4.1. Training weights and shared strings
For pair \((a,b)\), let \(C_\ell(a,b)\) count its occurrences in language \(\ell\), and let \(w_\ell\) be that language's training weight. Select the pair with the largest weighted count:
\[ C(a,b)=\sum_\ell w_\ell C_\ell(a,b). \]A weight of 2 gives the same counts as including every training sentence in that language twice. The weights stay fixed during training, while the counts change as merges alter the sequences.
4.2. The effect of training weights
The dataset contains 16 training sentences and 8 evaluation sentences per language. The translations have not been professionally checked; the dataset is intended to demonstrate the mechanism.
Let \(B_\ell\) be the total UTF-8 bytes of the training sentences in language \(\ell\), and \(p_\ell\) its target byte share. Each sentence of that language receives the weight \(100000p_\ell/B_\ell\). The shares \(p_\ell\) sum to 1, so the weighted byte total is 100,000 in every condition. The weights are exact Python Fraction values, and no random sampling is involved. The 50% English condition takes the square root of each share in the 80% condition and normalizes the results to sum to 1.
The initial vocabulary is the 256 byte values, and 256 merges are learned. There is no character normalization; merges cannot cross sentence boundaries but can cross spaces within a sentence. Evaluation applies no weights and sums the token counts of the 8 sentences in each language.
Extract the code and data archive and run this command from the extracted directory. It runs the tests and writes the experiment outputs to results/.
python3 code/run_all.py
The inputs are in data/toy_parallel.json, and the table values are in results/multilingual_metrics.csv. The code uses only the Python standard library. Its README is in Japanese; code/bpe_core.py contains the minimal byte-level implementation, and code/minimal_bpe.py also supports characters.
| Language | 80% English | 50% English | 20% English (equal shares) |
|---|---|---|---|
| English | 137 | 140 | 184 |
| Japanese | 310 | 259 | 208 |
| Chinese | 266 | 233 | 177 |
| Arabic | 344 | 229 | 189 |
| Hindi | 452 | 347 | 259 |
5. Vocabulary allocation with PA-BPE
- Select the language with the highest mean token count per sentence on the development set.
- In that language's training data, select the most frequent adjacent pair.
- Apply the merge to the training and development data of every language.
Table 1 reports tokenizers trained on an unbalanced 30-language sample of FineWeb2 and evaluated on the corresponding FLORES+ languages[6]. Both vocabularies have 128,000 entries.
| Method | Gini coefficient | Compression (sentences per token) |
|---|---|---|
| BPE | 0.064 | 0.0275 |
| PA-BPE | 0.007 | 0.0276 |
A Gini coefficient closer to 0 means less disparity in token counts across languages. Compression divides the number of sentences by the total token count, so a larger value means shorter token sequences for this fixed evaluation corpus.
- [6] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026.
Table 2 compares ordinary BPE with two variants: hybrid learns the first half of its merges with ordinary BPE and the second half with PA-BPE; hybrid+window also limits how often the same language can be selected within a window of recent merges[6].
The tokenizers were trained on mC4, and the models on 100 billion FineWeb2 tokens. Evaluation covered 22 languages. These model results use different tokenizer training data and PA-BPE variants from the tokenizer-only comparison in Table 1.
- [6] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026.
6. Extending an existing BPE tokenizer
6.1. Merge conflicts and continued BPE training
| Rank | Merge rule | Source |
|---|---|---|
| 0 | a + b → ab | Original tokenizer |
| 1 | b + c → bc | Second tokenizer |
| 2 | a + bc → abc | Second tokenizer |
abc now gives a | b | c → ab | c and stops. The first merge consumes the b needed to form bc, blocking the path to abc. The rule that would complete this sequence is ab + c → abc.
ab | c supplies the candidate ab + c. The original pre-tokenization boundaries still constrain the new merges.
In Hugging Face Tokenizers 0.22.1, BPE with ignore_merges=True and dropout disabled returns the ID directly if the whole pre-tokenized piece is in the vocabulary[12]. With the three rules above, abc succeeds by direct lookup. Add the initial character x and pass xabc as one piece, absent from the vocabulary: merging yields x | ab | c.
tiktoken 0.12.0 also checks whole pieces first. It then finds merge candidates by looking up the concatenated bytes of neighboring tokens in a rank table[9]. That differs from registering only a specific ID pair such as a + bc; the conflict example depends on the explicit pair rules stated in the body.
The downloadable examples reproduce the pair-rule conflict in results/integration_conflicts.json.
- [12] Hugging Face, "Tokenizers: BPE model (v0.22.1)," GitHub, 2025.
- [9] OpenAI, "tiktoken: BPE implementation (v0.12.0)," GitHub, 2025.
6.2. Model weights for the new IDs
7. Evaluating a tokenizer change
7.1. Token counts across languages
7.2. Vocabulary size and compute cost
The added parameters number \(50000\times4096=204800000\). Stored at 2 bytes per element, as in the 16-bit floating-point format BF16, they take \(409600000\) bytes, or 409.6 MB in decimal megabytes. With separate input and output matrices, the total increase is 819.2 MB.
These figures count weights only. Training also needs gradients, optimizer state, and other memory, and actual memory use depends on the training method.
7.3. Model quality
To compute bits per byte (BPB), sum the negative log-likelihood of the evaluation text's token sequence in bits, then divide by the text's UTF-8 byte count. For the same text, a lower BPB means the model assigns a higher probability to its token sequence.
Perplexity exponentiates mean loss per token, so it retains the tokenizer-dependent unit. BPB instead gives the compared token sequences a shared denominator.
Conclusion
References
- [1] Rico Sennrich, Barry Haddow, Alexandra Birch, "Neural Machine Translation of Rare Words with Subword Units," ACL, 2016. (§3.2 and Algorithm 1; the original application of character-level BPE to neural machine translation)
- [2] Alec Radford, Jeffrey Wu, Rewon Child, David Luan, Dario Amodei, Ilya Sutskever, "Language Models are Unsupervised Multitask Learners," OpenAI Technical Report, 2019. (§2.2 describes GPT-2's byte-level BPE and pre-tokenization)
- [3] François Yergeau, "UTF-8, a transformation format of ISO 10646," RFC 3629, 2003.
- [4] Hugging Face, "The tokenization pipeline," Tokenizers Documentation, 2026.
- [5] Ken Whistler, "Unicode Normalization Forms (Unicode 18.0.0, Revision 58)," Unicode Standard Annex #15, 2026.
- [6] Negar Foroutan, Clara Meister, Debjit Paul, Joel Niklaus, Sina Ahmadi, Antoine Bosselut, Rico Sennrich, "Parity-Aware Byte-Pair Encoding: Improving Cross-lingual Fairness in Tokenization," ACL, 2026. (ACL 2026 final version; the training procedure is in §4, compression and Gini coefficients in Table 1, and model quality in Table 2)
- [7] Taido Purason, Pavel Chizhov, Ivan P. Yamshchikov, Mark Fishel, "Teaching Old Tokenizers New Words: Efficient Tokenizer Adaptation for Pre-trained Models," Findings of EACL, 2026. (§3.1 describes continued BPE training; §2.4 and Appendix E cover embedding initialization)
- [8] Craig W Schmidt, Varshini Reddy, Haoran Zhang, Alec Alameddine, Omri Uzan, Yuval Pinter, Chris Tanner, "Tokenization Is More Than Compression," EMNLP, 2024. (Compares segmentation methods and other factors against model quality using 64 models with 350 million to 2.4 billion parameters)