Tokens & tokenizers
Models never see words or letters. They see token IDs โ integers indexing a fixed vocabulary of subword pieces, built with byte-pair encoding (BPE) or a close variant. Tokenization is the border crossing for everything: your prompt, the reply, the context window, the tokens-per-second meter, API bills. Once you think in tokens, half the folklore about LLM behavior dissolves.
How a vocabulary is built
BPE starts from raw bytes and repeatedly merges the most frequent adjacent pair until the vocabulary hits its budget. The result: common words become single tokens, rare words become 2โ5 pieces, and nothing is unrepresentable โ byte fallback covers every string, including typos and emoji. Vocabularies range from ~30k entries (older models) to ~250k (modern multilingual):
| Tokenizer | Vocab size | Note |
|---|---|---|
| GPT-2 | ~50k | English/web-heavy |
| Llama 2 | ~32k | Korean splits painfully |
| Llama 3 | ~128k | Big multilingual upgrade |
| Qwen 2 / 2.5 / 3 | ~151k | Strong CJK merges |
| Gemma | ~256k | Largest common vocab |
A tokenizer trained mostly on English merges English fragments aggressively; Korean syllable pairs get far fewer useful merges. The vocab decides your Korean tax long before the model itself is good or bad at Korean.
Worked example: English
"Tokenization turns text into pieces."
โ ["Token", "ization", " turns", " text", " into", " pieces", "."]
โ 37 characters โ 7 tokens โ 5.3 chars/token
Note the details: the common word "Token" is one piece; the suffix gets its own; and the leading space belongs to the following token (" turns", not "turns"). That is why "hello", " hello", and "Hello" are different tokens โ prompts that differ by a space or a capital tokenize differently.
Worked example: Korean
Same sentence, worse economics. Hangul is written in syllable blocks, and Korean merges are rarer in English-heavy vocabularies (splits below are plausible, not exact):
"์๋
ํ์ธ์, ๋ง๋์ ๋ฐ๊ฐ์์." # 17 characters
strong multilingual vocab (~150k entries):
["์๋
", "ํ์ธ์", ",", " ๋ง๋", "์", " ๋ฐ", "๊ฐ", "์", "์", "."]
โ ~10 tokens โ 1.7 chars/token
weak vocab (~32k entries, jamo-level splits):
["์", "๋
", "ํ", "์ธ", "์", ",", " ๋ง", "๋", "์", ...]
โ 15+ tokens โ 1.1 chars/token
English averages ~4 chars/token. Korean commonly lands at 1โ1.5. Same meaning, 3โ4ร the tokens. This is why:
- CJK prompts consume context windows alarmingly fast โ an 8k-token window holds roughly 3โ4ร more English than Korean.
- Generation "feels" slower in Korean at identical tok/s: more tokens per sentence, less text per token.
- Korean costs more on per-token APIs, and reasoning traces written in Korean burn budget the same way.
Why letter-counting questions fail
Ask "how many r's in strawberry" and even large models fumble. Reason: the model never sees letters. "strawberry" arrives as roughly ["straw", "berry"] โ two integers. There is no letter count inside an integer, any more than a page number tells you the page's spelling. The model can recite memorized spellings or reason step by step, and both are unreliable. If you need it done: force the model to spell the word one letter per line first, then count the lines.
The vocabulary ships with the model
- Tokenizer files (vocab, merges) are part of the release; GGUF embeds them, safetensors repos carry them alongside the weights.
- Engines (llama.cpp, LM Studio, vLLM, ...) load the right tokenizer automatically. You never pick one.
- Token counts are model-relative: the same text may be 1,000 tokens under one family's vocab and 1,150 under another's. Never hardcode token budgets across models.
- Vocab quality and model quality are separate axes: a 32k-vocab model can handle Korean (it understands its tokens); it just spends more tokens per sentence.
Everything is priced in tokens
| What you care about | Denominated in |
|---|---|
| Context window (4k โ 128k+) | tokens in + tokens out together |
| Generation speed | tokens per second |
| KV cache memory | grows per token in context (KV cache) |
| API pricing | per million tokens |
| Output length caps | max output tokens |
| Reasoning/thinking budgets | tokens the model may spend before answering |
| Embedding documents for RAG | chunked and counted per million tokens |
Special tokens and the chat template
Besides text pieces, the vocab reserves control IDs: beginning-of-sequence, end-of-sequence, and role markers. When your chat UI sends a message, it is not sending raw text โ it fills a chat template that interleaves special tokens like <|im_start|>user ... <|im_end|> around your words. That wrapping is why the token count the engine reports is always a bit higher than your message's text alone, and why mixing templates from different families confuses models.
Estimation rules: English chars รท 4. Korean chars รท 1.0โ1.5 โ assume the worst. The simulator uses the ~4 chars/token approximation and is calibrated for English; mentally inflate for Korean.
Counting tokens yourself
pip install tiktoken
python -c "import tiktoken as t; e = t.get_encoding('o200k_base'); print(len(e.encode('hello world')))"
- tiktoken (https://github.com/openai/tiktoken) covers OpenAI-family encodings; other families differ but land within roughly ยฑ15% for English.
- Hugging Face model pages include a tokenizer viewer; tokenizer playgrounds let you paste text and watch the splits live.
- Local engines print prompt token counts in their logs โ check once against your real prompts, then estimate by ratio.
Related: inside the transformer ยท prefill vs decode ยท KV cache