From BPE to 1M-Token Contexts -- How LLMs Read, Remember, and Bill You
The Fundamental Units of LLM Communication -- And the Memory That Binds Them
Every interaction you have with a large language model begins and ends with tokens. A token is not a word, not a character, and not a byte -- it is a sub-word unit that sits between the human-readable text and the model's internal numerical representations. The context window is the maximum number of tokens a model can process in a single forward pass, and it determines how much text the model can see at once. This guide covers tokenization algorithms (BPE, WordPiece, SentencePiece, Tiktoken), per-token pricing economics, context window mechanics across model generations, KV cache memory implications, the "lost in the middle" quality degradation phenomenon, and practical strategies -- including CLI and Python tooling -- for working within context limits.
A token is a sub-word unit of text -- a chunk that a language model reads as a single piece. Tokens are not words, not characters, and not bytes. They are the result of running text through a tokenizer, which splits text according to a vocabulary learned during model training. The tokenizer maps each token to a unique integer ID, and the model operates exclusively on those IDs.
The key insight is that token boundaries do not align with word boundaries. Common words like "the" or "cat" might be single tokens, while rare or complex words like "tokenization" might split into multiple tokens: "token" + "ization". This means the number of tokens in a text is not the same as the number of words -- it is usually less for common English, but can be dramatically more for non-Latin scripts, code, or unusual text.
For English text processed by modern tokenizers (GPT-4's cl100k_base, Llama's tokenizer, etc.), the approximate conversion ratios are:
Consider the word "unbelievable". A character-based approach would see 11 characters. A word-based approach would see 1 word. A sub-word tokenizer might see: "un" + "believe" + "able" = 3 tokens. The advantage of sub-word tokenization is that it handles rare words by decomposing them into meaningful parts the model has seen before, while keeping common words as single tokens for efficiency.
Here is how different texts tokenize with a typical BPE tokenizer:
Every model has a fixed vocabulary -- a table mapping token strings to integer IDs. The vocabulary size varies by model:
| Model Family | Tokenizer | Vocab Size | Notes |
|---|---|---|---|
| GPT-3 | p50k_base | ~50,257 | Original GPT-3 and text-davinci models |
| GPT-4 / GPT-3.5-turbo | cl100k_base | ~100,277 | Improved CJK and code tokenization |
| GPT-4o | o200k_base | ~200,000 | Major multilingual improvements |
| Llama 2 / Llama 3 | SentencePiece BPE | 32,000 / 128,000 | Llama 3 doubled vocab for better multilingual support |
| Mistral / Mixtral | BPE | 32,000 | Standard SentencePiece-based |
| Gemini | SentencePiece | ~256,000 | Large vocab for multilingual efficiency |
| Claude (Anthropic) | BPE | ~100,000 | Custom BPE variant |
| BERT / RoBERTa | WordPiece | 30,522 / 50,265 | Encoder-only models, smaller vocab |
Tokenization is the process of converting raw text into a sequence of token IDs. It happens in three stages:
The four major sub-word tokenization algorithms used in modern LLMs are:
Before sub-word splitting, text is divided into chunks using a regex pattern. GPT-4's cl100k_base uses this pattern (simplified):
淵 prefix (GPT-2) or 'Ġ' character internally to represent leading spaces.
Byte-Pair Encoding (BPE) is the dominant tokenization algorithm for modern LLMs. It was originally developed in 1994 as a data compression technique and adapted for neural language models by Sennrich et al. in 2015. GPT-2 popularized it, and today it powers GPT-4, Llama 3, Mistral, Claude, and most other major models.
BPE training builds a vocabulary by iteratively merging the most frequent adjacent byte/character pairs:
At inference time, the tokenizer applies the same merge rules in order. Text is first split into characters/bytes, then merge rules are applied greedily in the order they were learned:
GPT-2 and later models use byte-level BPE, which operates on raw bytes (256 possible values) rather than Unicode characters. This means the base vocabulary is always 256 bytes, and every possible text input can be tokenized -- there are no "unknown" tokens. This solves the out-of-vocabulary problem that plagued earlier word-level and character-level tokenizers. The trade-off is that non-ASCII characters (which require multiple bytes in UTF-8) consume more tokens.
WordPiece is the tokenization algorithm used by BERT, DistilBERT, and Electra. It is similar to BPE but uses a different merge criterion. Instead of merging the most frequent pair, WordPiece merges the pair that maximizes the likelihood of the training corpus when added to the vocabulary. The result is a vocabulary that tends to favor sub-words that appear in more diverse contexts.
WordPiece uses a "##" prefix to denote continuation sub-words. For example, "playing" might tokenize as ["play", "##ing"], where "##ing" indicates that "ing" is a continuation of the previous piece, not a standalone word. This helps the model distinguish word-internal sub-words from standalone words.
SentencePiece is not a single algorithm -- it is a tokenization library and framework developed by Google that can implement either BPE or Unigram language model tokenization. Its key innovation is that it treats the input as a raw stream of Unicode characters without assuming word boundaries (no whitespace pre-splitting). This makes it language-agnostic and particularly well-suited for languages that do not use spaces between words (Chinese, Japanese, Thai).
SentencePiece uses a special unicode character ░ (U+2581, "LIGHT SHADE") internally to represent spaces, so that whitespace is part of the tokenization rather than a delimiter. This means "Hello world" and "Helloworld" tokenize differently even though there are no word boundaries -- the space is encoded into the tokens themselves.
The Unigram approach (used by T5 via SentencePiece) works differently from BPE. Instead of building up by merging, it starts with a large vocabulary and prunes tokens that least reduce the likelihood of the corpus. The final vocabulary is the set of sub-words that best explains the training data. At inference time, multiple segmentations are possible and the tokenizer picks the one with the highest probability (using a Viterbi-like search). This makes Unigram tokenization probabilistic -- the same text always tokenizes the same way given the vocabulary, but the algorithm considers multiple possible segmentations.
Tiktoken is OpenAI's open-source, high-performance BPE tokenizer library, written in Rust with Python bindings. It is the tokenizer used for all OpenAI API models (GPT-3.5-turbo, GPT-4, GPT-4o, o1, etc.). Tiktoken is significantly faster than pure-Python BPE implementations -- typically 3-6x faster than HuggingFace's tokenizers library for the same vocabulary.
Tiktoken uses different encoding files (called "encodings") for different model generations:
| Encoding | Models | Vocab Size | Key Improvements |
|---|---|---|---|
| r50k_base | GPT-3 (text-davinci-002 and earlier) | ~50,257 | Original GPT-3 tokenizer |
| p50k_base | GPT-3 (text-davinci-003, davinci), Codex | ~50,281 | Added code-specific tokens |
| cl100k_base | GPT-3.5-turbo, GPT-4, GPT-4-turbo | ~100,277 | 2x vocab, better CJK and code |
| o200k_base | GPT-4o, GPT-4o-mini, o1, o3 | ~200,000 | 2x vocab again, major multilingual gains |
Despite the name, Tiktoken does not introduce a new tokenization algorithm -- it is a byte-level BPE implementation with a specific set of merge rules and regex pre-tokenization. The "tik" refers to its speed (tick-tock, fast). The real innovation is in the vocabulary design (which merges were learned) and the engineering (Rust core, regex-based pre-tokenization, cached merge lookups).
Most LLM APIs charge per token, not per request, per word, or per character. This means understanding token counts is essential for cost estimation. Pricing is typically split into input tokens (the prompt you send) and output tokens (the response the model generates). Output tokens are usually 3-5x more expensive than input tokens because generation requires autoregressive decoding (one forward pass per token) while input processing is a single forward pass.
API providers price per 1,000 or per 1,000,000 tokens. Here is a comparison of major API pricing (as of mid-2026, approximate):
| Model | Input ($/1M tokens) | Output ($/1M tokens) | Context Window | Output/Input Ratio |
|---|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | 128K | 4.0x |
| GPT-4o-mini | $0.15 | $0.60 | 128K | 4.0x |
| GPT-4-turbo | $10.00 | $30.00 | 128K | 3.0x |
| Claude 3.5 Sonnet | $3.00 | $15.00 | 200K | 5.0x |
| Claude 3.5 Haiku | $0.80 | $4.00 | 200K | 5.0x |
| Gemini 1.5 Pro | $1.25 | $5.00 | 2M | 4.0x |
| Gemini 1.5 Flash | $0.075 | $0.30 | 1M | 4.0x |
| Llama 3.1 405B (via API) | $3.00-$5.00 | $15.00 | 128K | 3-5x |
| DeepSeek V3 | $0.27 | $1.10 | 128K | 4.1x |
To estimate the cost of an API call, you need to count both input and output tokens:
Several factors inflate token costs beyond the obvious prompt + response:
The context window is the maximum number of tokens a model can process in a single inference call. This includes all tokens: the system prompt, the user's input, any conversation history, tool definitions, few-shot examples, retrieved documents (in RAG), and the space reserved for the model's response. If the total exceeds the context window, something must be cut.
Practical estimates of what different context window sizes can hold (assuming ~1.3 tokens per English word, ~500 words per page):
| Context Window | Approx. Words | Approx. Pages | What Fits |
|---|---|---|---|
| 4K | ~3,000 | ~6 pages | A short email chain, a few pages of a document, a brief conversation |
| 8K | ~6,000 | ~12 pages | A full academic paper abstract + body, a medium-length article |
| 16K | ~12,000 | ~24 pages | A full research paper, a short book chapter, a substantial code file |
| 32K | ~24,000 | ~48 pages | Multiple papers, a novella, a large codebase section |
| 128K | ~98,000 | ~196 pages | An entire book, a full code repository, hours of transcripts |
| 200K | ~150,000 | ~300 pages | A large novel, a full legal case file, a complete API documentation set |
| 1M | ~750,000 | ~1,500 pages | Multiple books, an entire codebase, days of video transcripts |
| 2M | ~1,500,000 | ~3,000 pages | An entire encyclopedia volume, a massive codebase, weeks of transcripts |
max_tokens to control output length, but you must ensure: prompt_tokens + max_tokens ≤ context_window. Some APIs (OpenAI, Anthropic) handle this automatically by reducing available output space, but others will error if you exceed the total.
Even with a 128K context window, you cannot fit:
For these use cases, retrieval-augmented generation (RAG) is the standard approach: store the full corpus in a vector database, retrieve only the relevant chunks, and include just those in the context window. This is both more cost-effective and often higher quality than stuffing everything into context.
Context windows have grown dramatically. The original GPT-3 (2020) had a 2K context window. By 2024, Gemini 1.5 Pro reached 2M tokens. This 1,000x increase has transformed what LLMs can do -- from reading a single paragraph to reading an entire codebase. Here is a comparison of popular models and their context windows:
| Model | Provider | Context Window | Max Output | Tokenizer | Released |
|---|---|---|---|---|---|
| GPT-3 (davinci) | OpenAI | 2,048 | ~2,048 | r50k_base | 2020 |
| GPT-3.5-turbo | OpenAI | 16,384 | 4,096 | cl100k_base | 2023 |
| GPT-4 | OpenAI | 8,192 | 4,096 | cl100k_base | 2023 |
| GPT-4-turbo | OpenAI | 128,000 | 4,096 | cl100k_base | 2023 |
| GPT-4o | OpenAI | 128,000 | 16,384 | o200k_base | 2024 |
| o1 / o3 | OpenAI | 200,000 | 100,000 | o200k_base | 2024-25 |
| Claude 2 | Anthropic | 100,000 | 8,192 | Custom BPE | 2023 |
| Claude 3 Opus/Sonnet | Anthropic | 200,000 | 4,096 | Custom BPE | 2024 |
| Claude 3.5 Sonnet | Anthropic | 200,000 | 8,192 | Custom BPE | 2024 |
| Gemini 1.0 Pro | 32,768 | 8,192 | SentencePiece | 2023 | |
| Gemini 1.5 Pro | 2,097,152 (2M) | 8,192 | SentencePiece | 2024 | |
| Gemini 1.5 Flash | 1,048,576 (1M) | 8,192 | SentencePiece | 2024 | |
| Llama 2 (7B-70B) | Meta | 4,096 | 4,096 | SentencePiece BPE | 2023 |
| Llama 3 (8B-70B) | Meta | 8,192 | 8,192 | BPE (128K vocab) | 2024 |
| Llama 3.1 (8B-405B) | Meta | 131,072 (128K) | 8,192 | BPE (128K vocab) | 2024 |
| Mistral 7B / Mixtral | Mistral AI | 32,768 | varies | BPE (32K vocab) | 2023-24 |
| DeepSeek V3 | DeepSeek | 131,072 (128K) | 8,192 | BPE | 2024 |
| Qwen 2.5 | Alibaba | 131,072 (128K) | 8,192 | BPE | 2024 |
| Phi-3 | Microsoft | 128,000 | 4,096 | Custom | 2024 |
When the total token count (input + requested output) exceeds the model's context window, something must give. Different APIs and inference engines handle this differently:
Most commercial APIs (OpenAI, Anthropic, Google) return an HTTP 400 error if you exceed the context window. The request never reaches the model. You get a message like:
Some inference engines (llama.cpp, Ollama, certain API wrappers) silently truncate the input. They drop tokens from the beginning of the conversation (the oldest messages) until the input fits within the context window. This is common in chat applications built on local models. The user sees a response, but the model has "forgotten" the earliest parts of the conversation.
A sliding window approach keeps only the most recent N tokens of context. As new messages arrive, old messages fall off the end. This is how many chat UIs maintain a continuous conversation with a fixed context model. The model always sees a window of recent context, with older context discarded:
Instead of dropping old context, you summarize it. When the conversation approaches the context limit, the system takes the oldest messages, asks the model to summarize them, and replaces the originals with the summary. This preserves the key information while reducing token count:
For large knowledge bases, RAG is the standard solution. Instead of putting all documents in context, you store them in a vector database, retrieve only the most relevant chunks for the current query, and include just those in the prompt. This scales to unlimited knowledge while keeping context window usage bounded:
The KV cache (Key-Value cache) is the primary memory mechanism that makes autoregressive LLM inference feasible. Without it, generating a 1,000-token response would require re-processing all preceding tokens for every single new token -- a quadratic operation. The KV cache stores the intermediate attention computations (keys and values) for all processed tokens so they do not need to be recomputed.
In a transformer's self-attention layer, each token produces a query, key, and value vector. During generation, the model needs the keys and values of all previous tokens to compute attention for the current token. The KV cache stores these keys and values so only the current token's Q, K, V need to be computed:
The KV cache size grows linearly with context length. The formula is:
| Technique | Reduction | Quality Impact | Used By |
|---|---|---|---|
| Grouped Query Attention (GQA) | 4-8x fewer KV heads | Negligible | Llama 3, Mistral, Gemma |
| Multi-Query Attention (MQA) | Single KV head | Slight | PaLM, Falcon |
| KV Cache Quantization (INT8) | 2x reduction | Minimal | vLLM, llama.cpp |
| KV Cache Quantization (INT4) | 4x reduction | Moderate | vLLM (experimental) |
| PagedAttention | Reduces fragmentation | None | vLLM |
| Sliding Window Attention | Fixed cache size | Limits long-range deps | Mistral, Gemma 2 |
| Ring/Block Attention | Distributes across GPUs | None | Internally by providers |
| KV Cache Offloading | Uses CPU RAM | Latency increase | llama.cpp, Ollama |
A larger context window does not guarantee better retrieval or understanding. The "lost in the middle" phenomenon, documented by Liu et al. (2023), shows that models perform best at retrieving information from the beginning and end of the context, with significantly degraded performance for information placed in the middle.
In the original study, researchers placed a relevant fact at different positions within a long context and measured the model's ability to retrieve it. The results showed a U-shaped curve:
This means that even with a 128K context window, placing a critical document in the middle of the context can result in a 25-30% drop in retrieval accuracy compared to placing it at the beginning or end.
Several factors contribute to lost-in-the-middle:
Before sending any request, calculate: system_prompt_tokens + user_input_tokens + conversation_history_tokens + max_output_tokens. This must be ≤ the model's context window. Use a token counter (see the CLI and Python sections below) to measure each component.
Both OpenAI and Anthropic now offer prompt caching. If you send the same prompt prefix (system prompt, documents, examples) across multiple calls, the provider caches the KV cache for that prefix. Subsequent calls with the same prefix are charged at 50-90% less and are significantly faster. This is essential for applications with large static contexts.
For chat applications, implement a memory manager that:
For any knowledge base larger than the context window, use RAG. The standard pipeline:
Do not always use the maximum context. Longer context costs more (both compute and money), is slower, and may suffer quality degradation. Match context to the task:
| Task | Recommended Context | Strategy |
|---|---|---|
| Simple Q&A, chat | 4K-8K | Use a smaller/cheaper model. Minimal context needed. |
| Document summarization (1 doc) | 16K-32K | Fit the full document. One-pass summary. |
| Code analysis (single file) | 8K-32K | Most files fit in 32K. Large files may need chunking. |
| Multi-document RAG | 16K-32K | Retrieve 5-10 chunks, include in context. Not all docs. |
| Full codebase analysis | 128K+ | Or use agentic RAG with multiple retrieval steps. |
| Long document Q&A (book-length) | 128K-200K | Fit the full document. Beware lost-in-the-middle. |
| Entire codebase / massive corpus | 1M-2M (Gemini) or agentic | Use Gemini 1.5 Pro for in-context, or multi-step RAG. |
The most common tool for counting OpenAI-model tokens is tiktoken. Install it and use it from the command line:
For non-OpenAI models (Llama, Mistral, BERT, etc.), use the HuggingFace transformers library:
If you run models locally with Ollama, you can count tokens using the Ollama API:
Source: Avondale.AI Technical Documentation Library
Category: Core AI Concepts
Type: Technical Reference Guide
License: CC BY 4.0
Suggested Citation:
Tokens and Context Windows Explained. Avondale.AI Technical Documentation Library. Accessed June 2026. https://avondale.ai/technical/tokens-context-windows.html