Home / Fundamentals / Transformer Architecture
Tokenization and embeddings
How text becomes integer IDs and vectors: BPE from scratch, byte-level fallback, vocabulary-size trade-offs, tied embeddings and tokenizer pathologies.
12 min read
A language model never sees text. It sees a sequence of integer token IDs from a fixed vocabulary, looks each one up in an embedding matrix, and at the end produces a probability distribution over the same vocabulary. Almost every practical property of an LLM, from cost and context limits to odd failures on arithmetic and spelling, is shaped by this step.
Why subwords
There are three natural choices for the unit of text:
| Unit | Vocabulary | Sequence length | Problems |
|---|---|---|---|
| Words | Millions | Short | Unseen words become <unk>; huge, sparsely trained embedding table |
| Characters / bytes | 256 | 4–5× longer | Attention cost grows with length; the model must learn to assemble words |
| Subwords | 32k–256k | Moderate | A good compromise; the standard choice |
Subword tokenizers give frequent strings their own token and compose rare strings from pieces, so nothing is ever out of vocabulary.
Byte-pair encoding from scratch
BPE learns a vocabulary by repeatedly merging the most frequent adjacent pair of symbols:
from collections import Counter
def train_bpe(corpus_words, num_merges):
# Each word starts as a tuple of bytes; counts are word frequencies.
words = Counter(tuple(w.encode("utf-8")) for w in corpus_words)
merges = []
for _ in range(num_merges):
pairs = Counter()
for word, freq in words.items():
for a, b in zip(word, word[1:]):
pairs[(a, b)] += freq
if not pairs:
break
best = max(pairs, key=pairs.get)
merges.append(best)
new_symbol = best # represent the merged pair as a tuple
merged = Counter()
for word, freq in words.items():
out, i = [], 0
while i < len(word):
if i + 1 < len(word) and (word[i], word[i + 1]) == best:
out.append(new_symbol); i += 2
else:
out.append(word[i]); i += 1
merged[tuple(out)] += freq
words = merged
return merges
Encoding new text applies the learned merges in the order they were learned. Real implementations are much faster (priority queues, incremental pair counts), but the logic is the same.
Byte-level BPE starts from the 256 byte values instead of Unicode characters, so any string, in any language or containing emoji or binary junk, can be encoded without an unknown token.
Pre-tokenization splits text before BPE runs, typically with a regular expression that separates words, numbers, punctuation and whitespace. Merges never cross these boundaries. Choices here matter a lot: some tokenizers split numbers into individual digits or groups of up to three digits, which measurably helps arithmetic; others treat leading spaces as part of the word (" the" and "the" are different tokens).
Alternatives:
- WordPiece (BERT) chooses merges that most increase the likelihood of the training data rather than raw frequency.
- Unigram LM (SentencePiece) starts from a large candidate vocabulary and prunes tokens that least reduce likelihood. Because it defines a probability over segmentations, it supports subword regularization: sampling different segmentations during training as data augmentation.
Vocabulary size is a real trade-off
- Larger vocabulary → shorter sequences. Fewer tokens per document means cheaper attention, more text per context window, and fewer decode steps.
- Larger vocabulary → more parameters and compute at the ends. The embedding table and the output projection each hold
V × dparameters. With V = 128k and d = 4,096 that's ~524M parameters per matrix, about 7% of a 7B model if untied. Computing logits costs2·d·VFLOPs per token. - Larger vocabulary → rarer tokens. Tokens that appear rarely in training get poorly trained embeddings. "Glitch tokens", strings that were frequent in the tokenizer's training data but rare in the model's, can produce bizarre behaviour.
Recent models moved from ~32k toward 100k–256k vocabularies, largely to serve multilingual text and code efficiently.
Fertility: not every language costs the same
Fertility is the average number of tokens per word. A tokenizer trained mostly on English may need 1.3 tokens per English word but 3–4× more for languages written in other scripts. Those users pay more, hit context limits sooner, and get slower responses. Multilingual tokenizer training and larger vocabularies narrow the gap.
Embeddings and the output layer
The embedding lookup is equivalent to multiplying a one-hot vector by the embedding matrix E ∈ ℝ^{V×d}. At the output, the final hidden state h ∈ ℝ^d is mapped to logits over the vocabulary:
logits = h · Wᵀ W ∈ ℝ^{V×d}
p(next token) = softmax(logits)
Weight tying sets W = E: the same matrix embeds inputs and scores outputs. It saves V·d parameters and often helps smaller models; many large models leave them untied for extra capacity.
Special tokens and chat templates
Chat models wrap messages in special tokens that mark roles, turns and tool calls. Two practical rules:
- User-supplied text must never be able to produce special tokens. Tokenize user content with special-token parsing disabled, or an attacker can forge a "system" turn.
- Always use the model's own chat template. Small differences in whitespace or role markers measurably change behaviour.
Pathologies worth knowing
- Spelling and counting letters: the model sees
strawberryas a few subword tokens, not ten letters, so character-level questions require recalling spelling indirectly. - Arithmetic: inconsistent digit chunking (
12345as123+45) makes place value hard to learn. - Trailing whitespace: a prompt ending in a space can force an unusual token boundary and degrade the next token, which is why some libraries "heal" the last token.
- Token boundaries in constrained decoding: grammars defined on characters must be mapped carefully onto tokens.
Interview angles
- Explain why subwords beat words and characters, using the vocabulary size vs. sequence length trade-off.
- Walk through BPE merges on a toy corpus.
- Connect tokenization to cost, context limits, multilingual fairness and failure modes.
- Know that changing a trained model's tokenizer requires re-learning embeddings and usually continued training.