localaiforum
Log in
Guide โ€บ Fundamentals

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