Foundations

Byte pair encoding

Type anything and watch a working BPE tokeniser build it from characters upward, one merge at a time. Shrink the vocabulary and see the same text cost more tokens.

Text
Or type
Vocabulary 41 merges
MERGES, IN THE ORDER THEY FIRED#0#0#1#1#2#3#5#6#7#10#18#19#22the␣cat␣and␣the␣internetthecatandtheinternet␣ marks a space. lower rank number means the pair was more frequent in training.real algorithm, toy merge table: 41 hand-written rules instead of a trained 100k vocabulary.
24
Characters
11
Tokens
2.18
Chars per token
13
Merges fired

The most common sequences collapsed into single tokens. Anything the merge table never saw stays fragmented, which is why a rare word can cost several times more than a common one of the same length.

A model never sees your text. It sees token ids, and the thing that decides which ids is a tokeniser that was trained before the model was. Understanding it is unusually practical, because tokens are the unit you are billed in, the unit the context window is measured in, and the reason some perfectly ordinary strings behave strangely.

Byte pair encoding starts with individual characters and repeatedly merges the most frequent adjacent pair, recording each merge as a rule. Training produces an ordered list of those rules. Encoding replays them: find the highest-ranked rule that matches anywhere in the sequence, apply it, repeat until nothing matches. The brackets in the diagram are those merges, stacked in the order they fired, so you can read the history of how each token was assembled.

The greedy-by-rank detail matters. The algorithm does not scan left to right, it always applies the best available rule wherever it is, which is why the result depends on the whole merge table rather than on local context. It is also why token boundaries do not line up with what you think of as words: a boundary can fall mid-word, and the same substring can be split differently depending on what surrounds it. The mapping itself is exactly reversible, so decoding recovers your text precisely. It is the segmentation that is unintuitive, not the encoding.

Drag the vocabulary slider down. Rules disappear from the end of the table, common sequences stop collapsing, and the token count rises for identical text. That is the entire tradeoff a tokeniser makes: a larger vocabulary means fewer tokens per document and a larger embedding matrix to pay for it.

Try the rare word and the numbers samples. A long uncommon word fragments into pieces, which is why rare terms cost several times more than common ones of the same length. Numbers are worth watching too, though the story there has moved on: older tokenisers merged digits into arbitrary multi-digit chunks, so "1024" and "1025" could have completely unrelated segmentations, and recent models deliberately split numbers into fixed groups of digits instead. Consistent grouping was the fix, not fewer pieces.

One honest caveat. The algorithm here is the real one, but the merge table is forty-one hand-written rules rather than a trained vocabulary of a hundred thousand. That is why the characters-per-token figure comes out around two, where a production tokeniser averages closer to four on English prose. The behaviour is faithful; the compression is not.

The maths

The merge rule
(x,y)  =  argmax(x,y)  count(xy  adjacent in corpus)(x, y)^* \;=\; \arg\max_{(x,y)} \; \text{count}(x\,y \;\text{adjacent in corpus})

Training repeatedly finds the most frequent adjacent pair, merges it into one symbol, and records the rule. The rank numbers on the brackets are the order those rules were learned, so lower rank means more frequent. This is a greedy heuristic, not an optimisation: nothing guarantees the resulting vocabulary is the best one of its size.

Encoding
while    (x,y)rules:apply    argmin(x,y)rank(x,y)\text{while} \;\; \exists\,(x,y) \in \text{rules}: \quad \text{apply} \;\; \arg\min_{(x,y)} \text{rank}(x, y)

Encoding replays the rules greedily by rank, not left to right: the best-ranked applicable rule fires wherever it sits in the string. That is why the result depends on the whole table rather than on local context.

The vocabulary trade
V  =  alphabet+merges,embedding params  =  V×d|V| \;=\; |\text{alphabet}| + |\text{merges}|, \qquad \text{embedding params} \;=\; |V| \times d

Every merge rule adds one token to the vocabulary and one more row of d parameters to the embedding matrix. The slider removes rules from the end of the table, which is exactly what a smaller trained vocabulary is.

Related terms

More visualisations

Building with language models?

These explainers come out of the work. If you want the same thinking applied to your own system, that is what I do.

See how I can help