Skip to content
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.

Written and reviewed by Last reviewed

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. It quietly explains a surprising amount:

  • why API calls cost what they cost
  • why your context window fills faster in some languages
  • 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. It handles input in three ways:

  • Common words stay whole (“the”, “model”).
  • Rare words split into reusable pieces (“token” + “ization”).
  • Spaces and digits fragment differently too.

That keeps the vocabulary fixed (~50k–200k tokens) while handling any input:

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. It starts from individual characters (or bytes), then repeatedly finds the most frequent adjacent pair and merges 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).
  • It penalizes non-English text (2–4× more tokens).
  • It 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 converts text into model-specific integer IDs, which are mapped to vectors before the transformer processes them. Subword methods such as Byte-Pair Encoding balance vocabulary size and sequence length: common fragments become single tokens, while rare or new strings can still be represented as smaller known pieces.

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

BPE builds a fixed subword vocabulary by repeatedly merging the most frequent adjacent base-symbol pair in training data, then applies the learned merge ranking to new text. Character-level BPE can still need an unknown token, while byte-level BPE can represent ordinary UTF-8 input; the exact tokenizer is always model-specific.

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 converts text into model-specific tokens and integer IDs. Its design controls unknown-token behavior, context length, memory, latency, cost, and how well a model reuses patterns across rare words and languages.

Related lessons

Explore further