WordPiece, Unigram and SentencePiece

BPE is not the only way to build a subword vocabulary. WordPiece (BERT) merges by likelihood and splits words greedily, longest piece first. Unigram (T5, ALBERT) gives every piece a probability and picks the most probable split with the Viterbi algorithm. SentencePiece is the library that trains both on raw text, spaces included.

Key idea
WordPiece: merge by count(ab) ÷ (count(a) × count(b)), encode longest-match-first with ## pieces. Unigram: start big, prune; encode by the most probable path through a lattice. SentencePiece: treat text as a raw stream with ▁ for spaces
Real world
Reading a web address with no spaces, like “expertsexchange”. You could read it as “expert sex change” or “experts exchange”. You pick the split that makes the most likely sense.
In an LLM
BERT reads “hedgehogs” as “hedge”, “##hog”, “##s”. T5 has 61 ways to split “sunflowers” into pieces it knows, and picks “▁sunflower” + “s” because that has the highest probability.

Two questions

Every subword tokenizer answers two questions:

  1. Training: which pieces go into the vocabulary?
  2. Encoding: given the vocabulary, how is a new word cut into pieces?

BPE answers both with merges: merge the most frequent pair, and later replay the merges in order. The two other families in wide use answer them differently.

Here are the three families side by side, before the details. Come back to this table after each section.

BPE (incl. byte-level)WordPieceUnigram
Builds the vocabulary bymerging, from small pieces upmerging, from small pieces uppruning, from a huge list down
Chooses bythe most frequent pairthe pair with the best score, count(ab) ÷ (count(a) × count(b))the piece whose removal hurts the corpus likelihood least
What is storedan ordered list of mergesthe vocabulary onlythe vocabulary, with a probability for each piece
Cuts a new word byreplaying the merges in orderlongest match first, from the leftthe highest-probability path (Viterbi)
Same word, always the same cut?yesyesno: it can also sample cuts
Marks word boundaries withĠ or ▁ at the start of a piece## at the start of a continuation▁ at the start of a piece
Text it cannot covernone (byte-level) or byte fallbackthe whole word becomes [UNK]unknown, or byte fallback
Used byGPT-2 to GPT-4, Llama, Qwen, DeepSeekBERT and its relativesT5, ALBERT, XLNet

WordPiece

was invented at Google for Japanese and Korean voice search (Schuster and Nakajima, 2012), used in Google’s translation system in 2016, and made famous by BERT in 2018.

Before the vocabulary: normalising

BERT does not feed raw text to WordPiece. First it cleans it: the uncased model turns everything to lower case, strips accents (“café” becomes “cafe”), puts spaces around punctuation and around every Chinese character, and splits on whitespace. Only then does WordPiece run on each word. That is why the widget below lower-cases whatever you type, and why BERT cannot tell “Apple” from “apple”.

Encoding: longest match first

WordPiece does not store merges. It stores only the vocabulary, and splits a word greedily: from the start of the word, take the longest string that is in the vocabulary, then continue from where it ended. Pieces that continue a word are written with a “##” prefix, and live in the vocabulary as separate entries: “ing” at the start of a word and “##ing” inside one are different tokens. If at some point no piece matches at all, the whole word becomes [UNK].

Here is BERT’s real vocabulary at work. Watch it try “unaffable”, “unaffabl”, “unaffab”… until something matches:

Loading GPT-2 data…
BERT’s WordPiece.

BERT’s own documentation uses “unaffable” → “un”, “##aff”, “##able” as its example. The real uncased vocabulary gives “una”, “##ffa”, “##ble”: greedy longest-match grabs “una” because it is a longer match than “un”, even though “un” is the better linguistic choice. Greedy is fast, but not always sensible.

Training: likelihood, not counts

WordPiece builds its vocabulary like BPE, by merging pairs, but chooses the pair that most increases the likelihood of the training data rather than the most frequent one. In practice that is computed as a score:

score(a,b)=count(ab)count(a)×count(b)\text{score}(a, b) = \frac{\text{count}(ab)}{\text{count}(a) \times \text{count}(b)}

A pair scores high when its two parts are rarely seen apart. Compare the two rules on the same corpus:

0 merges
the corpus, as symbols
5×low_
2×lower_
6×newest_
3×widest_
pair scores (top 6 of 14)
id3 · 0.333
lo7 · 0.143
st9 · 0.111
t_9 · 0.063
ow7 · 0.063
wi3 · 0.063
pair count · score = count ÷ (count of first × count of second)
vocabulary · 11 = 11 starting symbols + 0 merges
low_ernstid
corpus length (95 symbols)vocabulary size (11)
lowest_
Start: every word is split into single characters plus an end-of-word marker. The corpus is 95 symbols long. The highest-scoring pair is “i” + “d” (score 0.333), highlighted in amber: it will be merged first.
The same training loop with WordPiece’s rule: merge the pair with the highest count ÷ (count of first × count of second). Compare which pairs win with the plain count rule.

On the “low, newest, widest” corpus, BPE merges “e” + “s” first (9 times). WordPiece merges “i” + “d” first: it occurs only 3 times, but “i” and “d” never appear anywhere else, so the score is 3/(3×3)=0.333 / (3 \times 3) = 0.33, the highest. WordPiece prefers pieces that “belong together” over pieces that are merely common.

Unigram

The tokenizer (Kudo, 2018) turns the problem around. Instead of building up from characters by merging, it starts with a huge vocabulary and prunes it. And instead of fixed rules for splitting, it treats segmentation as a probability question.

Every piece has a probability

Each piece xx in the vocabulary has a probability p(x)p(x). A segmentation of a word into pieces x1,…,xkx_1, \dots, x_k has probability

P(x1,…,xk)=p(x1)×p(x2)×⋯×p(xk)P(x_1, \dots, x_k) = p(x_1) \times p(x_2) \times \dots \times p(x_k)

and the tokenizer outputs the most probable segmentation. Probabilities multiply, so in practice we add their logarithms; every piece below shows its log-probability, a negative number where closer to zero means more likely.

A word can usually be split in hundreds of ways. Drawing every piece that matches somewhere in the word gives a lattice: a segmentation is any left-to-right path through it. The best path is found with the algorithm: go left to right, and at each position remember only the best way to get there. Then trace back from the end.

Loading GPT-2 data…
The Unigram lattice.

Step through “unbelievable”. At each position Viterbi compares every piece that ends there (in amber) and keeps the one with the best total. At the end, the whole-word piece “▁unbelievable” wins, far ahead of “▁un” + “believable”: it carries 99.9% of the probability of all 170 possible segmentations. Try “Wonderland”: T5 has “▁Wonder” but no “land” piece that fits, so it ends with an odd “l” + “and”.

Training: start big, prune

  1. Start with a very large vocabulary: every character plus the most frequent substrings of the training text (hundreds of thousands).
  2. Estimate each piece’s probability from how often it appears in the best segmentations of the corpus (the EM algorithm: segment, re-count, repeat).
  3. For each piece, measure how much the corpus likelihood would drop if it were removed.
  4. Remove the least useful 20% or so (never single characters, so every word can still be spelled).
  5. Repeat from step 2 until the vocabulary is the target size.

Sampling: subword regularisation

Because Unigram gives every segmentation a probability, it can do something BPE and WordPiece cannot: sample. Press “Sample a segmentation” above a few times. For “unbelievable” with α = 0.1, the best split comes up only 21% of the time; with α = 0.5, 97% of the time. During training, feeds the model a different plausible split of the same word each time. The model then cannot rely on one exact segmentation, which makes it more robust to typos and unusual words. (BPE later got a similar trick, BPE-dropout, which randomly skips merges.)

example · no spaces

The web address “expertsexchange” has two readings: “expert sex change” and “experts exchange”. You choose by probability: “experts exchange” is a far more likely thing for a company to be called. A Unigram tokenizer does the same with every word, except that its pieces are subwords and the probabilities come from counting a large corpus.

example · “sunflowers” in T5

T5’s vocabulary has 61 ways to cover “▁sunflowers”, from 11 single characters up to “▁sunflower” + “s”. The latter is the most probable. The runner-up, “▁sun” + “flower” + “s”, is perfectly sensible but has one more piece, and every extra factor below 1 lowers the product. This is why Unigram tends to prefer fewer, longer pieces when they exist.

SentencePiece

(Kudo and Richardson, 2018) is a library, not a third algorithm. It trains either a BPE or a Unigram model, and its main idea is to treat the input as a raw stream of characters, spaces included:

  • A space is replaced by the visible character ▁ (U+2581) and treated like any other character. Pieces such as “▁the” mean “the at the start of a word”. Decoding is just joining the pieces and turning ▁ back into spaces, so tokenizing never loses information.
  • No language-specific word splitting is needed. That matters for Chinese, Japanese and Thai, which do not put spaces between words.
  • Options control the details: what share of characters to cover (rare characters left out become unknown, or with byte fallback become bytes, see bytes and Unicode), whether to split numbers into single digits, and whether to add a ▁ at the very start of the text.

T5 and ALBERT use SentencePiece Unigram. Llama 1 and 2, Mistral 7B and Gemma use SentencePiece BPE with byte fallback.

One word, three algorithms

Now run one word through all three. Same word, three real tokenizers. The cut can differ because the algorithms differ (merge order, longest match, best path) and because the vocabularies differ (50,257, 30,522 and 32,100 pieces, learned from different text).

Loading GPT-2 data…
One word, three algorithms.

Try “unhappiness” and “Wonderland”. For “unhappiness”, BERT gives “un”, “##ha”, “##pp”, “##iness” and T5 gives “▁un”, “h”, “app”, “iness”: neither finds “happi” or “happy”. Pieces come from counting text, and counting does not know about prefixes and suffixes. “sunflowers” shows how much the answers can differ: GPT-2 cuts “ sun”, “fl”, “owers”, BERT cuts “sun”, “##flower”, “##s”, and T5 cuts “▁sunflower”, “s”. None of the three guarantees pieces a linguist would recognise.

Side by side

Six real tokenizers on the same text, printed exactly as each library stores its pieces:

Loading GPT-2 data…
Six tokenizers.

Which model uses which

ModelYearAlgorithmSpace markerVocabulary
BERT2018WordPiecenone (## marks continuation)30,522
GPT-22019byte-level BPEĠ50,257
T52019SentencePiece Unigram▁32,000 (+100 sentinels)
ALBERT2019SentencePiece Unigram▁30,000
GPT-3.5, GPT-4 (cl100k)2022byte-level BPE (tiktoken)Ġ-style byteabout 100,000
Llama 2, Mistral 7B2023SentencePiece BPE + byte fallback▁32,000
Llama 32024byte-level BPE (tiktoken)Ġ-style byte128,256
GPT-4o (o200k)2024byte-level BPE (tiktoken)Ġ-style byteabout 200,000
Gemma 22024SentencePiece BPE + byte fallback▁256,000
Qwen 2.5, DeepSeek-V32024byte-level BPEĠabout 151,000; 128,000

The pattern: WordPiece and Unigram were popular for the encoder models of 2018 to 2020. Generative LLMs since then have almost all used BPE, either byte-level or SentencePiece with byte fallback, and the vocabularies keep growing; see vocabulary size. The tokens the table does not count, such as end-of-text and chat markers, are on special tokens.

going deeper

Unigram, formally

Given vocabulary VV with probabilities p(x)p(x), ∑x∈Vp(x)=1\sum_{x \in V} p(x) = 1, and a word XX with candidate segmentations S(X)S(X):

x∗=arg⁡max⁡x∈S(X)∑ilog⁡p(xi)x^* = \arg\max_{\mathbf{x} \in S(X)} \sum_{i} \log p(x_i)

found by Viterbi in O(n⋅L)O(n \cdot L) for a word of length nn and maximum piece length LL. Training maximises the marginal likelihood L=∑slog⁡∑x∈S(Xs)P(x)\mathcal{L} = \sum_{s} \log \sum_{\mathbf{x} \in S(X_s)} P(\mathbf{x}) with EM. Pruning removes the pieces whose removal reduces L\mathcal{L} least, keeping the top η\eta (e.g. 80%) each round. Subword regularisation samples x\mathbf{x} with probability ∝P(x)α\propto P(\mathbf{x})^\alpha (forward-filtering, backward-sampling over the lattice).

WordPiece encoding in code

def wordpiece(word, vocab, unk="[UNK]"):
    pieces, start = [], 0
    while start < len(word):
        end = len(word)
        while end > start:
            sub = word[start:end] if start == 0 else "##" + word[start:end]
            if sub in vocab:
                break
            end -= 1
        if end == start:
            return [unk]                 # no piece fits: the whole word is unknown
        pieces.append(sub)
        start = end
    return pieces

In papers

Related

  • bpe - BPE builds a vocabulary by starting from single characters and repeatedly merging the most frequent adjacent pair into a new token. The ordered list of merges is the tokenizer. To encode new text, cut it into chunks with a regular expression, then replay the merges inside each chunk.
  • bytes - Computers store text as Unicode code points packed into UTF-8 bytes, 1 to 4 per character. Byte-level BPE starts from the 256 possible bytes, so no text is ever unknown. The price is that scripts with more bytes per character, and less training data, cost many more tokens.
  • why-tokens - A model needs a fixed list of units to read and write. Characters make the text very long; whole words need a huge list and still meet words they have never seen. Subword tokens sit in between, and every modern LLM uses them.
  • special-tokens - Some tokens are not text at all: they mark the start and end of a text, fill out batches, and separate the turns of a conversation. A chat is turned into one flat stream of tokens by a chat template, and the model only behaves like an assistant if that template matches the one it was trained on.