Skip to learning content
Learning StudioCourses and practice
Experiences

Tokenization · Byte-pair encoding

Subword Tokenization

A learned subword vocabulary balances sequence length against vocabulary size while still giving the model a way to represent words it hasn't seen.

Evidence & limits
What the further reading establishes
Subword representations improve open-vocabulary neural translation, especially for rare words.
What this lab runs
You'll run the full BPE training and encoding algorithm on a fixed corpus and see every merge it learns.
What it does not prove
The toy trainer leaves out word-boundary markers, and its compression numbers don't measure translation quality.

Dataset

Morphology Set

Source
Course-authored synthetic corpus
License
Not separately licensed
Size
6 lines · 24 words · fixed

Further reading

Lesson progressRestoring progress…

  1. CodeRestoring
  2. ExperimentRestoring
  3. CheckRestoring

Summary

Pick the right-sized pieces. A word vocabulary maps every unseen word to one unknown id. A character vocabulary can spell any word made from known base symbols, but even common words become long sequences. Subword tokenization lands in the middle, so the language model predicts reusable pieces instead of whole words or single characters.

How BPE trains. BPE starts each training word as a list of symbols: signaling becomes [s, i, g, n, a, l, i, n, g]. It counts every neighboring pair, picks the most common one, replaces every non-overlapping match, and then counts again. You have to recount because each merge creates new possible pairs.

Keep pairs distinct. A pair is two separate symbols, not just the spelling you get when you join them. If you made keys with plain string concatenation, [a, bc] and [ab, c] would both turn into abc. The code uses JSON array keys like ["s","i"] so those pairs stay separate and are easy to see while debugging.

Replay merges in order. What BPE learns is an ordered list of merges. To encode new text, start with the same base symbols and try each learned merge once in its training order. For abc, applying [a,b] before [ab,c] produces [abc]. Reverse the order, and you're left with [ab,c].

Two BPE training roundsRecount the pairs after every corpus-wide merge. When you encode a new word, replay the final merge list once in order.
  1. 1
    Training wordss · i · g · n | s · i · g · n · a · l
  2. 2
    Round 1 counts[s,i]: 2 [i,g]: 2 [n,a]: 1 → select [s,i]
  3. 3
    Merge 1[s,i] → si then recount the modified words
  4. 4
    Round 2 counts[si,g]: 2 → next candidate
Learned order[a,b] → [ab,c]a · b · c → ab · c → abcReversed order[ab,c] → [a,b]a · b · c → a · b · c → ab · c

The system-wide tradeoff. A bigger merge budget usually makes encoded sequences shorter, but it also makes the model's embedding and output matrices larger. Tokenizer design changes context use, parameter count, serving cost, and the exact token ids the trained model expects. It's part of the model contract, not just a harmless preprocessing step.

Knowledge check

Why does a BPE tokenizer need to apply its learned merges in training order?

Implementation

models/bpe-tokenizer.py
0 of 3 exercises verifiedOpen coding workspace →

Count the possible merges across the tokenized vocabulary.

Signature
def count_pairs(words):
Inputs
words list[list[str]]; one symbol list per vocabulary item
Returns
dict[str, int] keyed by JSON pairs
Rule
slide over every adjacent pair and add one to its shared count
Example
[["s","i","g"], ["s","i"]] → {"[\"s\",\"i\"]": 2, …}
Adjacent pair counts progressive practice rounds
Restoring saved code…
Reference solution

Approach Count the possible merges across the tokenized vocabulary.

import json def count_pairs(words):    counts = {}    for symbols in words:        for index in range(len(symbols) - 1):            pair = json.dumps(                [symbols[index], symbols[index + 1]],                separators=(",", ":"),            )            counts[pair] = counts.get(pair, 0) + 1    return counts

Build the tokenizer in three separate Python cells. Pair counts use a visible key that can't confuse symbol boundaries: json.dumps(["s", "i"], separators=(",", ":")) returns the string ["s","i"]. The merge and encoder cells get each pair as a two-item list.

Saved results

Results created after your saved code passes its checks

The replay is course data. The validation result is tied to the code you saved and checked.

Loading…