datarekha

Tokenization & BPE

A model never sees letters or words — it sees token IDs. How byte-pair encoding chops text into subwords, and why that explains cost, context limits, and why LLMs can't count the r's in strawberry.

7 min read Beginner NLP & Transformers Lesson 19 of 44

What you'll learn

  • Why models work on subword tokens, not characters or words
  • How byte-pair encoding (BPE) builds its vocabulary by merging frequent pairs
  • Why tokenization drives cost, context limits, and famous LLM failures

Before you start

Before a transformer can do anything with your text, the text has to become numbers. A model never sees the word “running” — it sees something like token 12973, looks that ID up in an embedding table, and gets a vector. The rule that turns text into those IDs is tokenization, and it quietly explains a surprising amount: why API calls cost what they cost, why your context window fills faster in some languages, and why a frontier model can write an essay but can’t count the letters in “strawberry.”

TryTokenization · what the model sees

Type anything — watch it break into tokens

The model never sees your characters or words. It sees these sub-word pieces. Edit the text or try a preset.

31tokens
85characters
≈2.74chars / token
Tokenization·isn't·always·intuitive:·GPT-4o,·1234567,·and·🤖·split·in·surprising·ways.

≈4 chars/token is the rule of thumb for ordinary English prose. Numbers fragment, code and URLs shatter, and rare or non-English strings explode into many tokens — which is exactly why they cost more and why models are worse at them.

Approximate — a real byte-pair tokenizer (tiktoken) differs in detail. The point is the shape: tokens ≠ words ≠ characters.

Why not just words, or just characters?

Two obvious choices both fail:

  • One token per word → the vocabulary is unbounded (typos, names, new words, every language) and the model has no idea that “run”, “running”, and “runner” are related.
  • One token per character → tiny vocabulary, but sequences become enormous, and attention is quadratic in length — far too expensive.

The winning compromise is subwords: common words stay whole (“the”, “model”), rare words split into reusable pieces (“token” + “ization”). That keeps the vocabulary fixed (~50k–200k tokens) while handling any input — and spaces, digits, and rare words all fragment differently:

One phrase, nine tokens — whole words, split pieces, digit chunksthe·unbelievable·strawberry·2024whole wordrare-word piecenumber chunka leading · marks a space captured inside the token

Byte-pair encoding, the algorithm

The dominant method, BPE, learns its vocabulary from data with one beautifully simple loop: start from individual characters (or bytes), then repeatedly find the most frequent adjacent pair and merge it into a new token. Do that a few thousand times and frequent sequences naturally become single tokens.

start: charactersl o w e s tmerge most frequent pair e+s → esl o w es tmerge again es+t → estl o w est”est” is now ONE token,reused across “lowest”,“fastest”, “newest”…frequent sequences becomesingle tokens; rare onesstay split into pieces.
BPE repeatedly merges the most frequent adjacent pair, growing a subword vocabulary from characters.

Build a tiny BPE trainer and watch merges form on a toy corpus:

from collections import Counter

# A toy corpus, pre-split into words shown as character sequences with a marker.
corpus = ["low", "low", "low", "lowest", "lowest", "newer", "newer", "wider"]
# represent each word as a list of chars + an end-of-word marker
words = [list(w) + ["_"] for w in corpus]

def most_frequent_pair(words):
    pairs = Counter()
    for w in words:
        for i in range(len(w) - 1):
            pairs[(w[i], w[i+1])] += 1
    return pairs.most_common(1)[0] if pairs else (None, 0)

def merge(words, pair):
    a, b = pair
    out = []
    for w in words:
        i, new = 0, []
        while i < len(w):
            if i < len(w)-1 and w[i] == a and w[i+1] == b:
                new.append(a + b); i += 2          # merge into one token
            else:
                new.append(w[i]); i += 1
        out.append(new)
    return out

# Run a few merge steps
for step in range(5):
    pair, count = most_frequent_pair(words)
    words = merge(words, pair)
    print(f"merge {step+1}: {pair[0]!r}+{pair[1]!r} (seen {count}x)  ->  new token {pair[0]+pair[1]!r}")

print("\nfinal tokenization of 'lowest':", words[3])
merge 1: 'l'+'o' (seen 5x)  ->  new token 'lo'
merge 2: 'lo'+'w' (seen 5x)  ->  new token 'low'
merge 3: 'low'+'_' (seen 3x)  ->  new token 'low_'
merge 4: 'e'+'r' (seen 3x)  ->  new token 'er'
merge 5: 'er'+'_' (seen 3x)  ->  new token 'er_'

final tokenization of 'lowest': ['low', 'e', 's', 't', '_']

That’s the whole idea behind GPT’s tiktoken, SentencePiece, and friends — real tokenizers run this merge process over billions of characters and store the result as a vocabulary plus a merge table.

Token → embedding

Once text is a list of token IDs, the model’s embedding table (a big lookup matrix of shape vocab_size × d_model) maps each ID to a vector. That vector is the actual input to self-attention. The input and output embeddings are often tied (shared weights) to save parameters.

import torch.nn as nn
embed = nn.Embedding(num_embeddings=50257, embedding_dim=768)  # GPT-2 sizes
ids = tokenizer.encode("hello world")    # → [15496, 995]
vectors = embed(torch.tensor(ids))       # → shape (2, 768)

In one breath

  • A model never sees letters or words — text becomes token IDs, each looked up in an embedding table to get a vector.
  • Words give an unbounded vocabulary; characters make sequences too long (attention is quadratic). Subwords are the compromise: common words whole, rare ones split into reusable pieces, fixed ~50k–200k vocabulary.
  • BPE builds that vocabulary by repeatedly merging the most frequent adjacent pair, starting from characters/bytes.
  • Tokenization sets cost and context (you pay per token), penalizes non-English text (2–4× more tokens), and hides characters inside tokens — which is why an LLM can’t count the r’s in “strawberry.”
  • Always measure tokens with the model’s own tokenizer, never characters; ~4 chars/token in English is only a rough rule that code and other languages break.

Quick check

Quick check

0/3
Q1Why do modern LLMs use subword tokens instead of whole words or single characters?
Q2What single operation does byte-pair encoding (BPE) repeat to build its vocabulary?
Q3Why can't an LLM reliably count the letters in 'strawberry'?

Next

Now that text is a sequence of embedded tokens, we can do the thing transformers are famous for: let every token look at every other token, with self-attention.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
How does tokenization work, and why do LLMs rely on subword tokenizers like BPE?

Tokenization splits text into integer IDs the model can process; subword tokenizers like Byte-Pair Encoding start from characters or bytes and iteratively merge the most frequent adjacent pairs into a vocabulary. Subwords keep common words intact while decomposing rare or unseen words into known pieces, avoiding out-of-vocabulary problems and balancing vocabulary size against sequence length.

How does Byte-Pair Encoding (BPE) tokenization work?

BPE starts with a character-level vocabulary and iteratively merges the most frequent adjacent pair of symbols until a target vocabulary size is reached. The resulting subword units handle rare and unseen words gracefully without any out-of-vocabulary tokens.

What are tokens in an LLM and why is API pricing per token rather than per word or character?

A token is the smallest unit a language model processes — typically a word, sub-word fragment, or punctuation mark produced by a byte-pair encoding (BPE) or similar algorithm. Pricing is per token because each token requires one forward-pass position in the attention matrix, directly driving compute and memory cost regardless of whether it maps to a full word or a single letter.

What is tokenization in NLP and why does it matter?

Tokenization splits raw text into discrete units — words, subwords, or characters — that a model can process numerically. The strategy chosen controls vocabulary size, out-of-vocabulary rate, and how well the model handles rare or morphologically complex words.

Related lessons

Explore further

Skip to content