Tokenization and Context Windows
Phase 1 · Document 01 · LLM Vocabulary and Mental Models Prev: 00 — Core Mental Model · Next: 02 — Parameters, Weights, and Checkpoints
Table of Contents
- Why This Matters
- Core Concept
- Mental Model
- Hitchhiker's Guide
- Warmup Readings
- Deep Readings and External References
- Key Terms
- Important Facts
- Observations from Real Systems
- Common Misconceptions
- Engineering Decision Framework
- Hands-On Lab
- Verification Questions
- Takeaways
- Artifact Checklist
1. Why This Matters
Every LLM API call begins with tokenization and is bounded by the context window. Together these two facts decide whether your input fits at all, what it costs, what the model can see when generating, and how you must design prompts, chunking, caching, and retrieval. You will use this knowledge dozens of times a week: estimating cost before shipping a feature, sizing a RAG chunk, diagnosing a "context length exceeded" error, or explaining to a PM why a 200-page PDF won't "just fit."
Get tokenization wrong and your cost estimates are off by 2–3× for non-English or JSON-heavy workloads. Get the context window wrong and you ship a feature that fails on real documents in production.
2. Core Concept
Tokenization (plain English, then technical)
Tokenization splits text into tokens — the model's fundamental input/output unit. A tokenizer maps a string to a list of integer token IDs, and back. The full set of tokens a model knows is its vocabulary.
"Hello, world!" → [9906, 11, 1917, 0] (GPT-4 family tokenizer)
Modern tokenizers are subword tokenizers — usually Byte-Pair Encoding (BPE) (GPT, Llama) or SentencePiece/Unigram (Gemma, T5). BPE starts from bytes and repeatedly merges the most frequent adjacent pair into a new token, learning a vocabulary where common words are single tokens and rare words split into pieces. This is why token counts are unintuitive:
| Text | Approx tokens | Why |
|---|---|---|
hello | 1 | Common whole word |
tokenization | 2–3 | Split into subwords |
supercalifragilistic | 4–6 | Rare, fragments |
pneumonoultramicroscopic... | 10+ | Very rare |
user_id_count_123 | 4–6 | Underscores/digits fragment |
| Chinese / Japanese | often ~1 char/token | Less training data per char |
Rules of thumb: English ≈ 0.75 words/token (≈ 4 chars/token). Code ≈ 3–4 chars/token. Non-English prose can use 1.5–3× more tokens per word — a direct, often-overlooked cost multiplier.
Counting tokens:
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
print(len(enc.encode("The context window limits what the model sees."))) # ~9
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
print(len(tok.encode("Hello world")))
Context window
The context window (a.k.a. context length / max_model_len) is the maximum number of tokens the model can process in a single forward pass — typically input + output combined, though some providers expose separate max input and max output limits.
context_window ≈ input_tokens + output_tokens (exact semantics vary by model/provider)
| Window | Roughly holds |
|---|---|
| 4K | a short article / a few pages of code |
| 8K | a chapter / ~10 pages of code |
| 32K | a novella / ~50 pages of code |
| 128K | a short novel / ~150 pages of code |
| 1M | a large codebase / many books |
Critical caveat: the context window is what the model will accept, not what it can reason over reliably. Long-context models often suffer "lost in the middle" — facts placed in the middle of a long context are recalled worse than facts at the start or end. Acceptance ≠ comprehension. Always evaluate long-context recall for your task instead of trusting the headline number.
3. Mental Model
"Context window" = a fixed-size glass window.
• The model sees ONLY what is inside the window.
• Anything outside the window does not exist to the model.
• The window is measured in tokens, not words or characters.
• Everything you want used — system prompt, history, docs, the question —
must fit inside, AND leave room for the answer.
budget = context_window − reserved_output_tokens − system_prompt − history
↑ what's left is your usable space for retrieved docs / the user turn
4. Hitchhiker's Guide
What to look for first: the model's context window and its separate max-output limit; whether pricing is per input/output token; whether cached input is discounted.
What to ignore at first: the exact BPE merge rules. You need to count tokens accurately, not implement a tokenizer.
What misleads beginners:
- "50,000 words fits in 128K" — count tokens, not words; and reserve output space.
- "Long context = better understanding" — measure mid-context recall; don't assume.
- "The context handles memory" — it does not persist across calls; you re-send history.
How experts reason: they treat the context window as a budget to allocate — reserve output, subtract a stable (cacheable) system prompt, then fill the remainder with the highest-value retrieved tokens. They count tokens with the model's own tokenizer, never a word count.
What matters in production: token count drives cost; prompt length drives prefill latency (TTFT); context length drives KV-cache memory (the real concurrency limiter when self-hosting).
Debug/verify: before sending, log the token count of the fully rendered prompt; alert at >80% of the window. On a "context exceeded" error, log per-message token counts to find the bloat.
Questions to ask providers: Is the window input-only or input+output? Is there prefix/prompt caching, and at what discount? How are image/audio tokens counted? What happens on overflow — error or silent truncation?
What silently gets expensive: unbounded chat history; verbose system prompts re-sent each call; pretty-printed JSON (whitespace is tokens); non-English text; high-res images.
5. Warmup Readings
| Title | Why to read it | What to extract | Difficulty | Time |
|---|---|---|---|---|
| OpenAI Tokenizer (interactive tool) | See live how text → tokens | Why words split oddly; count varies by content | Beginner | 10 min |
| OpenAI Help — "How to count tokens with tiktoken" | The canonical counting method | Use the model's encoder, not a word count | Beginner | 10 min |
| "Lost in the Middle" (Liu et al.) — abstract + figures | Evidence long context ≠ reliable recall | Position of relevant info changes accuracy | Intermediate | 20 min |
| Hugging Face — "Summary of the tokenizers" | BPE vs WordPiece vs Unigram | Why different models give different counts | Intermediate | 20 min |
6. Deep Readings and External References
| Title | URL | Why it matters | Read first | Lab connection |
|---|---|---|---|---|
| tiktoken | https://github.com/openai/tiktoken | The tokenizer for OpenAI models | README + encoding names | Used directly in the lab |
| Hugging Face Tokenizers docs | https://huggingface.co/docs/tokenizers/ | Tokenizers for open-weight models | Quicktour | Compare Llama vs GPT counts |
| Neural Machine Translation of Rare Words with Subword Units (BPE) | https://arxiv.org/abs/1508.07909 | Origin of BPE | §3 (the algorithm) | Explains why "token ≠ word" |
| Lost in the Middle | https://arxiv.org/abs/2307.03172 | Long-context recall degrades by position | Figures 1–5 | Motivates the context-budget lab |
vLLM — engine args (max_model_len) | https://docs.vllm.ai/en/latest/serving/engine_args.html | How the window appears in serving | max_model_len, KV cache notes | Phase 7 serving lab |
7. Key Terms
| Term | Simple meaning | Technical meaning | Why it matters | Where it appears | How to use it |
|---|---|---|---|---|---|
| Token | Unit of text | Subword unit from BPE/SentencePiece vocabulary | The unit of all cost and limits | Pricing, usage | Count before sending |
| Tokenizer | Text splitter | Maps string ↔ token IDs via vocabulary | Determines cost and fit | tiktoken, HF | Use the model's own encoder |
| Vocabulary | Token dictionary | The full token↔ID table | Affects token efficiency per language | Model cards | Compare across models |
| Token ID | Integer for a token | Index into the embedding table | How text becomes numbers | Tokenizer output | Rarely needed directly |
| Context window | Max tokens per call | Max input(+output) the model accepts | Hard input cap | Model cards, catalogs | Size prompts to fit |
| Max input tokens | Max prompt length | Cap on input tokens | Limits doc/RAG size | API docs | Set chunk/retrieval size |
| Max output tokens | Max response length | Cap on generated tokens | Limits report/code length | API docs | Set max_tokens |
| BPE | Subword algorithm | Iteratively merges frequent byte pairs | Why tokens ≠ words | NLP papers | Explains odd splits |
| Lost in the middle | Mid-context blind spot | Recall drops for info in the middle | Long context ≠ reliable | Long-context papers | Put key info at edges; rerank |
8. Important Facts
- Tokens are the unit of pricing for all major providers.
- Most providers bill input and output tokens separately; output is often 2–5× the input price.
- The same text costs different token counts on different models (different vocabularies).
- Code tokenizes more efficiently than prose; pretty-printed JSON wastes tokens on whitespace.
- Non-English text can cost 1.5–3× more tokens per word.
- A single image typically costs ~500–2000 tokens depending on resolution and model.
- Cached input tokens may be 75–90% cheaper where prompt caching is supported.
- Reasoning/thinking tokens are billed (usually at output rates) even though hidden.
- Context window is a trained property; exceeding the trained length (e.g. via
--ctx-size) degrades quality.
9. Observations from Real Systems
- OpenRouter / provider pricing pages list input/output $/1M tokens; some rows show a separate cached-input price — your first stop for cost math.
- vLLM rejects or truncates requests over
max_model_len; the KV cache for long context is the practical concurrency limit, not the weights. - llama.cpp
--ctx-sizeand Ollamanum_ctxset the window; setting them beyond the trained length produces degraded, repetitive output. - Cursor / VS Code Copilot continuously trim and prioritize file/repo context to stay under the window — a live demonstration of context budgeting.
- Anthropic / OpenAI prompt caching make a long, stable system prompt cheap on repeat calls, changing the economics of verbose instructions.
10. Common Misconceptions
| Misconception | Reality |
|---|---|
| "1 token = 1 word" | ≈ 0.75 English words; varies wildly by content/language |
| "Bigger context = better output" | Mid-context recall degrades ("lost in the middle") |
| "Context window is free" | More tokens = more cost, prefill latency, and KV memory |
| "The model remembers prior chats" | Only if you re-send them as tokens this call |
| "Counting characters ≈ counting tokens" | Use the model's tokenizer for accuracy |
| "All models tokenize the same" | GPT, Llama, Gemma all use different tokenizers/vocab sizes |
11. Engineering Decision Framework
Sizing a request:
reserved_output = max answer you need (tokens)
fixed_overhead = system prompt + tool schemas (cache these if possible)
usable = context_window − reserved_output − fixed_overhead
fill `usable` with the highest-value retrieved/ history tokens, newest/most-relevant first.
Choosing a context strategy:
Input > window? → chunk + retrieve (RAG), don't widen the model blindly.
Repeated long system prompt? → enable prompt/prefix caching.
Need facts deep in a long doc? → rerank to the edges; verify recall; consider summary-then-detail.
Non-English / heavy JSON? → recount tokens; budgets from English will be wrong.
| Symptom | Likely cause | Fix |
|---|---|---|
| "Context length exceeded" | History/docs too big | Trim history, chunk docs, summarize, raise to a larger-context model only if justified |
| Cost 2–3× estimate | Counted words/chars, or non-English/JSON | Recount with the real tokenizer; compact JSON |
| Good on short docs, bad on long | Lost in the middle | Rerank key info to edges; reduce irrelevant context |
| High TTFT | Long prompt → long prefill | Cache the stable prefix; retrieve less |
12. Hands-On Lab
Goal
Build a token counter + cost estimator + context-fit checker, and observe tokenization differences across content types and models.
Prerequisites
- Python 3.10+; optional Hugging Face access for the Llama tokenizer.
Setup
pip install tiktoken transformers
Steps
import tiktoken
def count_tokens(text: str, model: str = "gpt-4o") -> int:
return len(tiktoken.encoding_for_model(model).encode(text))
def estimate_cost(text, output_tokens, in_price, out_price, model="gpt-4o"):
n_in = count_tokens(text, model)
return {
"input_tokens": n_in,
"output_tokens": output_tokens,
"total_cost": n_in/1e6*in_price + output_tokens/1e6*out_price,
}
samples = {
"question": "What is the capital of France?",
"code": "def fib(n): return n if n<=1 else fib(n-1)+fib(n-2)",
"prose": "The context window bounds what the model can see. " * 100,
"json_pretty": '{\n "name": "Alice",\n "age": 30\n}\n' * 50,
"json_compact": '{"name":"Alice","age":30}' * 50,
}
for name, txt in samples.items():
r = estimate_cost(txt, 200, 2.50, 10.00) # gpt-4o prices
print(f"{name:12} {r['input_tokens']:>6} tok ${r['total_cost']:.6f}")
Expected output
json_prettycosts noticeably more thanjson_compactfor the same data (whitespace tokens).codeis token-efficient relative to its character count.
Debugging tips
- API
prompt_tokenshigher than your count? You omitted role/message formatting overhead. - HF tokenizer download fails? Gate access on the model page or use a public tokenizer.
Extension task
Add check_fits(messages, model, window, reserved_output) that sums all message tokens, subtracts reserved output, and warns above 80% usage.
Production extension
Compare the same 2,000-word document tokenized by gpt-4o (tiktoken) vs Llama-3 (HF). Produce a cost-delta table — this is exactly the analysis you'd run before choosing a model for a high-volume pipeline.
What to measure
Tokens per content type; cost per type; pretty vs compact JSON delta; GPT vs Llama token delta for identical text.
Deliverables
- A token + cost table for 5 content types.
- The
check_fitsfunction. - A one-line finding: which content type is most expensive per word, and why.
13. Verification Questions
Basic
- Roughly how many tokens is "the quick brown fox"?
- What's the difference between max input and max output tokens?
- Why do two models report different token counts for the same string?
Applied 4. A RAG step retrieves 5 chunks of 500 words each. Approximate the context tokens. 5. 10,000 calls/day at ~500 input + ~200 output tokens, $3/1M in, $15/1M out — monthly cost?
Debugging 6. You hit "context length exceeded." Give three distinct fixes. 7. Your French-language feature costs 2× the English estimate. Why, and how do you confirm?
System design 8. Design a pipeline to process a 200-page PDF through a 32K-context model. Address chunking, retrieval, and output assembly.
Startup / product 9. Your product pastes the full user document into every call. Margins are thin. Propose two token-level changes (caching, retrieval) and estimate their effect on gross margin.
14. Takeaways
- Tokens are the atomic unit of LLM pricing and limits.
- English ≈ 0.75 words/token; always count with the model's own tokenizer.
- The context window is a hard budget: reserve output, cache the stable prefix, fill the rest with the best tokens.
- Long context increases cost, latency, and KV memory — and doesn't guarantee good recall ("lost in the middle").
- JSON whitespace and non-English text are silent cost multipliers.
- Cache repeated input where supported to cut cost dramatically.
15. Artifact Checklist
-
Code: token counter + cost estimator +
check_fits. - Benchmark result: token/cost table across 5 content types.
- Comparison report: GPT vs Llama token counts for one real document.
- Cost estimate: monthly cost for a realistic call volume.
- Notes: the context-budget formula in your own words.
- Diagram: the "glass window" budget allocation.