Tokens, Context Window, Temperature, Top-p

GenAI bills and bugs both start with the same unit: the token. Once you see prompts, context limits, and sampling knobs in token terms, pricing, truncation failures, and “why did it get weird?” stop feeling mystical.

Intuition

The model never reads “words.” A tokenizer chops your string into discrete IDs from a fixed vocabulary. Those IDs fill a context window — a hard budget shared by system instructions, history, retrieved docs, tools, and the reply you hope to get.

After the model scores the next token, temperature and top-p reshape which candidates you are willing to sample. Low and narrow → boring but stable. High and wide → spicy but risky. Production systems treat these as reliability controls, not creativity cosmetics.

How it works

Tokens

Tokenization is model-specific. Rough patterns for English:

Rule-of-thumb for planning (not billing truth): ~4 characters per token for English prose, or ~100 tokens ≈ 75 words. Always measure with the real tokenizer in CI for anything cost-sensitive.

Context window

If the window is W tokens, everything the model can attend to in one call must fit in W (API products sometimes split “input” vs “output” limits — read the docs for your provider). Overflow typically means:

flowchart TB subgraph window [Context window budget] S[System + tools] H[History] R[Retrieved docs] U[User turn] O[Room for output] end S --> H --> R --> U --> O

Long RAG dumps are the classic way to spend the whole budget on noise. Prefer ranked snippets, summaries, and hard caps per source.

Temperature

Let z_i be the logit for vocabulary item i. Softmax turns logits into probabilities. Temperature T > 0 scales logits before softmax:

p_i = softmax(z_i / T)

Temperature does not add knowledge. It only changes how aggressively you explore the model’s uncertainty.

Top-p (nucleus sampling)

Sort tokens by probability descending. Keep the smallest prefix whose cumulative probability is at least p. Sample only inside that nucleus.

Top-p adapts to the shape of the distribution: when the model is peaked, the nucleus is small; when it is flat, the nucleus grows.

Practical presets

Task Temperature Top-p Why
Extraction / JSON 0–0.2 0.1–0.5 Minimize format drift
Support answers 0.2–0.4 0.5–0.8 Stable tone, light variety
Code 0.1–0.3 0.5–0.9 Prefer correctness
Brainstorm 0.7–1.0 0.9–0.95 Explore alternatives

Tune one primary diversity control first (often temperature or top-p), then the other. Extreme values on both compound into chaos.

Cost and latency mental model

Every call roughly costs price_in * prompt_tokens + price_out * completion_tokens. Output tokens are often more expensive and always more latency-sensitive because generation is sequential. That is why short, structured answers beat essay-length chat for automation: you pay less and finish sooner. Caching identical system prompts (when your provider supports it) and trimming history aggressively are usually bigger wins than shaving a few words from a user template.

Interaction with tools and RAG

Retrieved chunks and tool schemas sit in the same window as the question. A 20-tool catalog with verbose descriptions can crowd out the user turn. Prefer concise tool descriptions, retrieve top-k passages not whole manuals, and summarize old turns instead of replaying full transcripts forever.

In code

Approximate English token count without a real tokenizer — good for budgeting demos, bad for invoices:

def estimate_tokens(text: str) -> int:
    # Rough: ~4 chars/token for English prose
    return max(1, (len(text) + 3) // 4)


prompt = "System: be brief.\nUser: Explain tokens."
print(estimate_tokens(prompt))

Simulate a context packer that reserves output room:

def pack(parts: list[str], window: int, reserve_out: int) -> list[str]:
    budget = window - reserve_out
    kept = []
    used = 0
    for part in reversed(parts):  # keep newest first
        cost = estimate_tokens(part)
        if used + cost > budget:
            continue
        kept.append(part)
        used += cost
    return list(reversed(kept))


history = ["old policy...", "mid turn...", "latest user question"]
print(pack(history, window=200, reserve_out=50))

Temperature on a toy logit vector (numpy-free):

import math


def softmax(xs):
    m = max(xs)
    exps = [math.exp(x - m) for x in xs]
    s = sum(exps)
    return [e / s for e in exps]


def with_temperature(logits, T: float):
    return softmax([z / T for z in logits])


logits = [2.0, 1.0, 0.1]  # pretend vocab of 3
print([round(p, 3) for p in with_temperature(logits, 0.5)])  # peakier
print([round(p, 3) for p in with_temperature(logits, 1.5)])  # flatter

Nucleus filter:

def nucleus(probs: list[float], p: float) -> list[int]:
    order = sorted(range(len(probs)), key=lambda i: probs[i], reverse=True)
    kept, cum = [], 0.0
    for i in order:
        kept.append(i)
        cum += probs[i]
        if cum >= p:
            break
    return kept


probs = [0.5, 0.3, 0.15, 0.05]
print(nucleus(probs, 0.8))  # indices covering >= 0.8 mass

What goes wrong

One-line summary

Tokens meter cost and memory; the context window is a shared budget; temperature and top-p reshape sampling — use them deliberately for stability vs diversity.

Key terms

)