Skip to content

Tokens

The path from text to the next token in seven steps: tokens, embeddings, query/key/value, mask and heads, the block, the output and generation. The current step is Tokens. ATTENTION TRANSFORMERS Tokenstext → ids ▲ you are here Embeddingsids → X Q · K · Vone head Masktwo heads BlockLN · MLP Outputlogits → p Generationpick → next token

Step 1 of 7. In: the text "the cat climbed the". Out: five integers, [0, 2, 4, 5, 1]. ← Overview · next step: Embeddings →

A neural network computes with numbers, so before anything else the text has to become numbers. The unit it is cut into is the token: a piece of text taken from a fixed list, the vocabulary, in which every piece has an integer, its id. A token can be a whole word (cat), part of a word (ed), one character or even one byte. The model never sees letters, only the sequence of ids, and everything it predicts is an id too.

Why not words? Why not characters?

The two obvious choices both fail:

  • Words. "climb", "climbs", "climbed" and "climbing" would be four unrelated entries, and so would every name, typo, number and word of every language: the list never ends. Worse, a word that was not in the list when the model was trained has no id at all.
  • Characters (or bytes). Nothing is ever unknown: 256 bytes write any text. But "the cat climbed the" becomes 19 symbols instead of 4 words, attention costs grow with the square of the length (step 3), and each position carries almost no meaning on its own.

Subwords sit in between: frequent words stay whole, rarer words are written with a few frequent pieces, and anything left over falls back to single bytes, so nothing is ever unknown. GPT-2 writes "the cat climbed the" with 4 tokens: the, cat, climbed, the.

Bytes first

Text is stored as bytes, in UTF-8: one byte for each ASCII character, two to four for the others — "a" is the byte 97, "ã" is two bytes, 195 and 163. A byte-level tokenizer such as GPT-2's starts from the 256 possible bytes, so any string at all, in any language, can be encoded. Our toy starts from the characters of a small corpus instead (23 of them), which keeps its tables short; the algorithm is the same.

BPE: merge the most frequent pair, again and again

The vocabulary is learned from a corpus by byte-pair encoding (BPE)1, before the model is trained and separately from it:

  1. Cut the corpus into words, and each word into characters (bytes, in GPT-2).
  2. Count every pair of neighbouring symbols. Merge the most frequent pair into a new symbol, and write that merge down.
  3. Repeat until the vocabulary has the size you want.

To tokenize new text, apply the recorded merges in the same order. GPT-2 learned 50,000 merges from English web text2. Our toy tokenizer learns from eight sentences:

the cat sat on the roof. · the dog sat on the floor. · the cat jumped on the wall. · the dog jumped on the sofa. · the cat likes to climb the wall. · the dog climbs on the sofa. · the cat walked to the roof. · the dog walked to the floor.

Like GPT-2, it first cuts before each space and each punctuation mark, and the space stays at the start of the next word (written ␣ below). Then it counts. The most frequent pair of the corpus is t + h, 16 times (every "the"), so merge 1 is th; then th + e (16), then ␣ + the (8), ␣ + c (6)… After 40 merges every piece the toy model needs exists, and the tokenizer's vocabulary has 23 characters + 40 merges = 63 entries.

The 40 merges, in the order they were learned (in bold, the ones that change our sentence)
# merge count # merge count
1 t + h → th 16 21 ␣s + at → ␣sat 2
2 th + e → the 16 22 ␣ + r → ␣r 2
3 ␣ + the → ␣the 8 23 ␣r + oo → ␣roo 2
4 ␣ + c → ␣c 6 24 ␣roo + f → ␣roof 2
5 a + t → at 6 25 ␣ + f → ␣f 2
6 ␣ + o → ␣o 5 26 ␣f + l → ␣fl 2
7 ␣o + n → ␣on 5 27 ␣fl + oo → ␣floo 2
8 ␣c + at → ␣cat 4 28 ␣floo + r → ␣floor 2
9 ␣ + s → ␣s 4 29 ␣ + j → ␣j 2
10 o + o → oo 4 30 ␣j + u → ␣ju 2
11 ␣ + d → ␣d 4 31 ␣ju + m → ␣jum 2
12 ␣d + o → ␣do 4 32 ␣jum + p → ␣jump 2
13 ␣do + g → ␣dog 4 33 ␣jump + ed → ␣jumped 2
14 e + d → ed 4 34 ␣wal + l → ␣wall 2
15 ␣ + w → ␣w 4 35 ␣s + o → ␣so 2
16 ␣w + a → ␣wa 4 36 ␣so + f → ␣sof 2
17 ␣wa + l → ␣wal 4 37 ␣sof + a → ␣sofa 2
18 l + i → li 3 38 ␣c + li → ␣cli 2
19 ␣ + t → ␣t 3 39 ␣cli + m → ␣clim 2
20 ␣t + o → ␣to 3 40 ␣clim + b → ␣climb 2

The running sentence is not in the corpus. Tokenizing it means starting from its 19 characters and applying the 40 merges in order; only 11 of them find a pair to merge:

after merge pieces how many
start: characters t h e ␣ c a t ␣ c l i m b e d ␣ t h e 19
1: t + h th e ␣ c a t ␣ c l i m b e d ␣ th e 17
2: th + e the ␣ c a t ␣ c l i m b e d ␣ the 15
3: ␣ + the the ␣ c a t ␣ c l i m b e d ␣the 14
4: ␣ + c the ␣c a t ␣c l i m b e d ␣the 12
5: a + t the ␣c at ␣c l i m b e d ␣the 11
8: ␣c + at the ␣cat ␣c l i m b e d ␣the 10
14: e + d the ␣cat ␣c l i m b ed ␣the 9
18: l + i the ␣cat ␣c li m b ed ␣the 8
38: ␣c + li the ␣cat ␣cli m b ed ␣the 7
39: ␣cli + m the ␣cat ␣clim b ed ␣the 6
40: ␣clim + b the ␣cat ␣climb ed ␣the 5

"climbed" never appears in the corpus, and BPE writes it anyway, with two pieces it learned elsewhere: ␣climb (from "climb" and "climbs") and ed (from "jumped" and "walked"). Merges that do not touch the sentence — ␣on, ␣sofa, ␣jumped… — are about words it does not contain. GPT-2, with 50,000 merges learned from far more text, has a single token for climbed (id 19952).

The space belongs to the next word

"the" at the start of the text and " the" after a space are two different tokens: ids 0 and 1 in the toy, 1169 and 262 in GPT-2 ("The", capitalized, is a third one, 464). Neither the tokenizer nor the model knows they are "the same word". That is also why a prompt that ends in a space changes what the model predicts: the space would normally be the start of the next token (the LLMs chapter measures it).

Vocabulary and ids

The toy model keeps 12 tokens: 11 of the 63 pieces (the five of the sentence and six others it can predict) and the special token <|endoftext|> (below).

id piece id piece id piece
0 the 4 ␣climb 8 ␣sofa
1 ␣the 5 ed 9 ␣floor
2 ␣cat 6 ␣roof 10 .
3 ␣dog 7 ␣wall 11 <|endoftext|>

"the cat climbed the" → the ␣cat ␣climb ed ␣the → [0, 2, 4, 5, 1]

A real model has one row for every entry of its tokenizer: GPT-2's vocabulary is 256 bytes + 50,000 merges + 1 special token = 50,257, and it reads the sentence as [1169, 3797, 19952, 262]. The toy keeps 12 so that its tables fit on a page.

The ids are names, not quantities: 4 is not "twice" 2, and nothing in the number says "verb". The next step turns each id into a vector.

Special tokens

Some entries do not come from BPE. They are added by hand, and ordinary text never produces them:

  • <|endoftext|> (GPT-2's id 50256, our 11) separates documents in the training data and lets the model end its own text: when generation draws it, it stops (step 7).
  • Chat models add markers for whose turn it is; BERT uses [CLS], [SEP] and [MASK]. A batch of sentences of different lengths is filled with a padding token, which a mask then hides (step 4).

Portuguese costs more

A tokenizer is fitted to its corpus, so a language it saw little of is written with smaller pieces. GPT-2 was fitted on English:

text words GPT-2 tokens tokens per word
the cat climbed the 4 4 1.00
o gato subiu no 4 6: o g ato sub iu no 1.50
The cat climbed onto the roof of the house because it was scared of the dog. 16 17 1.06
O gato subiu no telhado da casa porque estava com medo do cachorro. 13 25 1.92

The two toy tokenizers of these pages show the same effect from the other side: the English one cuts "o gato subiu no telhado." into 21 pieces, while the Portuguese one, fitted on Portuguese sentences, keeps gato whole — which GPT-2 does not. More tokens means more compute (attention grows with \(n^2\)), less text in the same context window and a bigger bill for the same sentence3.

See it run

The simulator runs the toy tokenizer, merge by merge, on any text you type. Try the cases: the sentence with its answer, a word the corpus never saw ("bird", written letter by letter) and the Portuguese sentence through the English tokenizer.

Check yourself — the toy tokenizer never saw “climbed”. Why does it not fail?

Because BPE never needs a whole word to be in the vocabulary. It starts from characters, which are all there, and merges what it can: "climbed" ends as ␣climb + ed, two pieces learned from "climb", "climbs", "jumped" and "walked". Even a word made of characters it never saw would be written, one character (one byte, in GPT-2) at a time.

Check yourself — “the cat climbed the” has “the” twice. Do both get id 0?

No. The first is the (id 0); the second is ␣the (id 1), because the space belongs to the token. The next step gives them two different vectors, and only training can make those vectors similar, if that helps.


← Overview · next step: Embeddings →


  1. Sennrich, R., Haddow, B., & Birch, A. (2016). Neural Machine Translation of Rare Words with Subword Units — ACL. BPE brought to NLP. ↩

  2. Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., & Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. GPT-2 and its byte-level BPE. ↩

  3. Petrov, A., La Malfa, E., Torr, P. H. S., & Bibi, A. (2023). Language Model Tokenizers Introduce Unfairness Between Languages — NeurIPS. ↩