Skip to content

    AI Architect

    Models, Routing and Inference for AI Architects

    How AI architects choose models, route requests, cache prompts and budget latency, with tokenization, KV cache, batching and quantization explained.

    By Published 40 min read

    Share

    This lesson is part of AI Architect: From AI Engineer to Expert. Enroll for free, then sign in, to read it in full.

    What you'll learn: how to design the model layer of an AI system. You will see what a model gateway does, how to route requests between models with rules, classifiers and cascades, which caches are safe and which are risky, how to read latency (time to first token against total time) and how to budget the context window. Eight "Under the hood" sections explain the machinery behind those choices (tokenization, the KV cache, prefill and decode, batching, quantization, speculative decoding, prompt caching and streaming), so that each design rule follows from how model serving works and not from folklore.

    The real-world problem

    Composite scenario, not a real company. A team ships an assistant that works well in the demo. In the first month of production three things happen. The model bill is larger than the forecast, because every message, including "where is my order", goes to the largest model. Users call the assistant slow, although the provider's status page is green: each message re-sends a long prompt and the answer appears only when it is complete. And after the provider moves the team to a newer model version, the same prompts count as more tokens, so a few long conversations start to fail their limit checks.

    None of these is a bug in the code. Each is a consequence of how models are served: tokens are the unit of cost, limit and speed, output tokens are produced one at a time, and the processed form of a prompt can be reused only when its start is byte-for-byte the same. This page teaches the model layer as an architect needs it: the components, the policies, and the mechanisms that explain why the policies work.

    After this page you will be able to

    • Draw a model gateway and say which of its jobs (budgets, routing, caching, retries, accounting) belong there and not in the Orchestrator.
    • Choose between rule, classifier and cascade routing for a workload, and compute the break-even escalation rate of a cascade.
    • Compute the KV-cache size per token and per request for a model from its published dimensions, and say what that number limits.
    • Split a latency budget into queueing, time to first token and decode time, and decide where streaming and caching help.
    • Lay out a prompt so that a provider prefix cache can hit, and name two risks of a semantic cache.

    Read the AI architect mindset for requirements and constraints, and diagramming AI systems for the figure conventions used here. Background that this page does not repeat:

    Terms used on this page: model gateway, token, byte-pair encoding, context window, KV cache, prefill, time to first token, continuous batching, quantization, speculative decoding, prompt caching, semantic cache, model cascade and server-sent events.

    Core concepts

    What a model gateway does

    The Model gateway is the single path from your system to model providers. Its jobs are the ones that every caller would otherwise implement slightly differently: check the token and deadline budget before spending money, choose the model, look in the caches, call the provider with timeouts, retries and a fallback, pass a stream through, and emit a usage record (tokens, model, latency, route) for every call. It should not hold business logic, build prompts or decide which tool to call; those belong to the Orchestrator.

    A concrete interface keeps the boundary honest: the Orchestrator sends a purpose (for example order_status), the prompt parts, a maximum input size, a maximum output size and a deadline, and the Model gateway returns the answer plus a record of what it did.

    Figure 1. Inside the Model gateway

    flowchart LR accTitle: Inside the Model gateway accDescr: The Orchestrator sends a request with a purpose and a budget to the Model gateway. A budget check rejects or trims oversize requests. The router picks a model, the cache lookup answers repeats, and the provider adapter calls the LLM provider with a timeout, retry and fallback. A usage event goes to the Trace store. Orch["Orchestrator"] subgraph MG["Model gateway"] Budget["Budget check: tokens and deadline"] Router["Router: choose a model"] Cache["Cache lookup: exact and semantic"] Adapter["Provider adapter: timeout, retry, fallback"] end LLM["LLM provider"] Trace[("Trace store")] Orch -->|"request with purpose and budget"| Budget Budget -->|"over budget: reject or trim"| Orch Budget -->|"within budget"| Router Router -->|"model chosen"| Cache Cache -->|"hit: stored answer"| Orch Cache -->|"miss"| Adapter Adapter -->|"HTTPS, prompt text only"| LLM LLM -->|"streamed tokens"| Adapter Adapter -->|"stream passed through"| Orch Adapter -.->|"usage event: tokens, model, route, latency"| Trace

    How to read this: solid arrows are the request path, the dotted arrow is asynchronous, the cylinder is a store, and the large box is the gateway; the cache sits after the router because a cache key must include the chosen model.

    Routing policies: rules, classifier, cascade

    A router decides which model serves a request. Three policies cover most designs.

    A rule routes on facts known before any model call: the endpoint, the UI surface, the customer tier, the language, the length. Rules are free, deterministic and easy to test. They are brittle where the difficulty of a request is not visible in its metadata.

    A classifier (a router model) predicts from the request text which model is needed. RouteLLM is the published example: it learns a binary router between a strong and a weak model from human preference data, and a cost threshold turns the predicted win probability into a decision. The authors report cost savings of over 2 times with minimal impact on response quality on public benchmarks; that is their setting, not a promise for your traffic. A learned router needs data and can drift silently when traffic changes.

    A cascade tries the cheap model first and escalates when its answer is not reliable. FrugalGPT describes a scoring function that returns a score between 0 and 1 for a query and an answer, and a list of models called in sequence until a score passes a threshold; on some datasets it matched the best single model at up to 98% lower cost in the authors' experiments (2023 APIs and datasets). Escalated requests pay for both calls and wait for both, and a cascade is only as good as its scorer.

    Figure 2. Which routing policy fits?

    flowchart TD accTitle: Choosing a routing policy accDescr: A decision tree. If the request type is known before the call, use rules. If not, and labelled data and stable traffic exist, use a classifier. If not, and answers can be scored reliably with tolerable extra latency, use a cascade. Otherwise use one model with caching. Q1{"Is the request type known before the call?"} Q2{"Do you have labelled quality data and stable traffic?"} Q3{"Can you score an answer reliably and accept extra latency on escalations?"} A1["Rules on endpoint, tier or intent"] A2["Classifier router, re-checked on a golden set"] A3["Cascade with a validated scorer and threshold"] A4["One model, plus caching"] Q1 -->|"yes"| A1 Q1 -->|"no"| Q2 Q2 -->|"yes"| A2 Q2 -->|"no"| Q3 Q3 -->|"yes"| A3 Q3 -->|"no"| A4

    How to read this: start at the top, follow yes or no at each diamond, and stop at the first rectangle; real systems often combine a rule layer with a cascade behind it.

    Figure 3. A cascade with its failure paths

    sequenceDiagram accTitle: Cascade routing sequence accDescr: The Orchestrator sends a request to the Model gateway. A rule check passes it to the small model. If the small model answers confidently its answer is returned. If it answers with low confidence or times out, the gateway escalates to the large model within the remaining deadline. Every outcome emits a usage event to the Trace store. autonumber participant O as Orchestrator participant M as Model gateway participant S as LLM provider small model participant L as LLM provider large model participant T as Trace store O->>M: request, intent order_status, deadline 8 s Note over M: Rule check - intent is not on the always-large list M->>S: prompt with 3 s timeout alt Small model replies and is confident S-->>M: answer Note over M: Score at or above threshold, accept M-->>O: answer, route small else Small model replies with low confidence S-->>M: answer Note over M: Score below threshold, escalate with the remaining budget M->>L: prompt with 5 s timeout L-->>M: answer M-->>O: answer, route large else Small model times out Note over M: Decision - escalate, small attempt used 3 s of 8 s M->>L: prompt with 5 s timeout L-->>M: answer M-->>O: answer, route large end M-)T: usage event with route, tokens and latency

    How to read this: the alt block lists three outcomes of the small model, notes mark the gateway's decisions, and the last arrow is asynchronous.

    The diagram shows what the happy path hides: an escalation after a timeout has only the remaining deadline left, so the large model's timeout must be set from the budget and not from a constant. A policy that is not logged cannot be audited, so the usage event records the route and the reason.

    Caching layers

    Three caches differ in what they reuse and in what can go wrong.

    An exact cache returns a stored answer when the whole request is identical: same model, parameters, prompt version and inputs. It is safe for answers that do not depend on the user, the time or changing data, and the key must contain everything the answer depends on (tenant, permissions, policy version) or it leaks.

    A prefix cache (provider prompt caching) reuses the provider's already-processed form of the start of a prompt. It does not change the output: OpenAI's documentation says plainly that prompt caching does not change how the model generates output tokens. It saves input processing time and cost. The mechanism is the subject of one Under the hood section below.

    A semantic cache returns a stored answer when a new query is similar enough, by embedding distance, to an earlier one. FrugalGPT lists a completion cache of this kind among its cost levers. Its risks are specific: "similar" is not "same" ("cancel my order" and "cancel my subscription" are close in meaning space and far apart in consequence), a hit can cross a tenant boundary, and a stored answer goes stale when a policy changes. Scope it by tenant and content version, apply it only to intents whose answers are generic, use a high similarity threshold, and sample hits for review.

    Figure 4. Cache lookup order in the Model gateway

    flowchart TD accTitle: Cache lookup order accDescr: A request first checks the exact cache. On a miss it checks the semantic cache only if the intent allows it and a similar stored query exists in the same tenant and policy version. On a second miss it calls the LLM provider, where the provider prefix cache applies, and stores the answer if it is cacheable. Req["Request from the Orchestrator"] Ex{"Exact cache hit? Key has model, prompt version, tenant"} Sem{"Intent allows semantic cache and a close query exists in this tenant and policy version?"} R1["Return stored answer"] R2["Return stored answer and log the hit for review"] Prov["Call LLM provider, prefix cache applies there"] Store["Store under the exact key if cacheable"] R3["Return the new answer"] Req --> Ex Ex -->|"yes"| R1 Ex -->|"no"| Sem Sem -->|"yes"| R2 Sem -->|"no"| Prov Prov --> Store Store --> R3

    How to read this: diamonds are lookups, rectangles are outcomes, and the safest cache is checked first.

    Latency anatomy: time to first token and total time

    A streamed answer has two latency numbers. Time to first token (TTFT) is the wait until the first output token arrives; the DistServe paper defines it as the duration of the prefill phase, and a client also sees queueing and network time in it. Time per output token (TPOT) is the average time per token after the first. Total latency equals TTFT plus TPOT times the number of tokens after the first. A chatbot cares about TTFT, and TPOT matters only until it beats reading speed, whereas summarisation cares about TPOT. With illustrative numbers (TTFT 0.6 s, TPOT 25 ms, 400 tokens) the total is 0.6 + 399 x 0.025 = 10.575 s: unstreamed, the user stares at nothing for 10.6 s, and streamed, text starts at 0.6 s. The total is the same; the experience is not. The prefill and decode section below explains why the two numbers behave so differently.

    Structured output and context-window budgeting

    Structured output makes the model emit JSON that follows a schema. Anthropic's documentation describes it as constrained decoding with compiled grammar artifacts, and lists where the guarantee does not hold: a refusal, and output cut off by the token limit. The first use of a schema adds compile latency, and compiled grammars are cached for 24 hours from last use (as of 2026-10-22, Anthropic). The architect's rule: check the stop reason before parsing, and validate meaning separately, because valid JSON is not correct JSON.

    Context-window budgeting starts from one fact: everything in a request counts toward the window, and so does the output. Anthropic's context-window page lists the system prompt, every message including tool results, images and documents, the tool definitions, and the generated output including thinking. The same page warns that more context is not automatically better, because accuracy and recall degrade as token count grows. A budget is therefore a table, and the output reservation comes first.

    Part of the request Illustrative tokens Policy when it grows
    Output reservation (max_tokens) 1,000 Fixed; set from the longest legitimate answer
    Tool definitions 1,500 Load only the tools the route needs
    System prompt and reference text 4,000 Keep stable; it is the cacheable prefix
    Retrieved passages 3,000 Cap the count; drop the lowest ranks first
    Conversation history up to 4,000 Summarise or trim the oldest turns
    Current user turn 500 Reject or truncate with a message

    Overflow behaviour is provider-specific. As of 2026-10-22, Anthropic's page says input alone above the window returns a 400 error, and that on newer models input plus max_tokens above the window is accepted and generation stops with a dedicated stop reason, while older models return a validation error. Count before sending, and define the gateway's "too long" error yourself.

    Self-hosting versus API

    At decision level the choice is about who runs the machinery in the sections below. With an API the provider runs batching, KV-cache memory and quantization; you pay per token, accept its model and tokenizer changes, and send prompts across your boundary under contract. Self-hosting gives you data location, version pinning and tuning freedom, and hands you capacity planning, utilisation, on-call and an evaluation run for every model you adopt.

    The economics reduce to utilisation: cost per token = GPU cost per hour / (tokens per hour at full load x utilisation). Practitioners disagree on the volume where the lines cross, so measure with your own traffic and prices. A sensible default is an API behind a Model gateway, with data residency, version pinning or sustained volume as the triggers to revisit.

    Case files

    Three composite case files apply this page. All are illustrative scenarios, not real companies.

    • Support chat costs tripled: routing and caching. Problem: a retail support assistant sent every message to its largest model, and cost ran at three times the forecast. Pattern: a non-AI FAQ deflector first, then rules by risk and a small-first cascade with escalation, plus a stable cacheable prefix.
    • In-product assistant latency: streaming and prefill. Problem: a SaaS assistant felt slow because of a long prompt and unstreamed replies. Pattern: a latency budget split into time to first token and decode time, then streaming, shorter prompts and answers, a cached prefix and a smaller model for simple intents.
    • Classifying 2 million documents: batch and cascade. Problem: a public body priced one real-time model call per document far above its budget. Pattern: rules for coded documents, then batch processing with a cheap tier first and a larger model only for uncertain documents, with human sampling.

    Under the hood: Tokenization and the context window

    Intuition

    A model never sees letters or words. It sees integers, each naming a piece of text from a fixed vocabulary. Common strings are one piece, rare strings are split into several. So the "token" is the unit in which a provider counts cost, enforces limits and measures speed, and because each model family trains its own vocabulary, the same text is a different number of tokens in different models.

    Mechanism

    Sennrich, Haddow and Birch adapted byte pair encoding (BPE) to words: start from characters plus an end-of-word symbol, then repeatedly replace the most frequent adjacent pair with one new symbol. The vocabulary size is the initial symbols plus the number of merges, which the paper calls the only hyperparameter. At use time the merges are applied in learned order, so any word can be encoded. Running the code the paper prints on its toy dictionary (low 5, lower 2, newest 6, widest 3; the figure caption names a slightly different dictionary) gives the first merges (e, s) with count 9, (es, t) 9, (est, end-of-word) 9, (l, o) 7 and (lo, w) 7; the unseen word "lowest" then encodes as the pieces low and est. Production tokenizers build on the same idea: the tiktoken README, for example, describes BPE as reversible and lossless, working on arbitrary text, and compressing text.

    Figure 5. Where token counts come from

    sequenceDiagram accTitle: Learning and applying a tokenizer accDescr: Before the model is trained, a merge learner reads a text corpus and repeatedly stores the most frequent adjacent pair as a new symbol, building the vocabulary and merge list. At request time the Model gateway asks the tokenizer to count a prompt. The tokenizer applies the merges in learned order and returns token ids and a count, and the ids are sent to the model. participant C as Training corpus participant L as Merge learner participant V as Vocabulary and merges participant T as Tokenizer participant G as Model gateway participant M as Model C->>L: text split into symbols loop until the chosen number of merges L->>L: count adjacent symbol pairs L->>V: store the most frequent pair as a new symbol end G->>T: count tokens for this prompt T->>V: apply merges in the learned order V-->>T: token pieces T-->>G: token ids and the count G->>M: ids, if input plus output fit the window Note over G,M: A different model has a different merge list, so a different count

    How to read this: the loop happens once, before training, and the lower half happens on every request.

    Tokenizers differ by model, and the primary sources show by how much. The Llama 3 paper reports a 128K-token vocabulary (100K from the tiktoken tokenizer plus 28K for non-English languages) and an English compression of 3.94 characters per token against 3.17 for the Llama 2 tokenizer. For 4,000 English characters that is 4,000 / 3.17 = 1,262 tokens against 4,000 / 3.94 = 1,015, so the older tokenizer needs 24% more. Anthropic's token-counting page says models from Claude 4.7 on use a newer tokenizer that produces approximately 30% more tokens for the same input than earlier models, depending on content (as of 2026-10-22). The context window limits input plus output together: Anthropic's page defines it as all the text a model can reference when generating a response, including the response itself.

    What this means for architects

    Count tokens with the target model's own tokenizer or the provider's counting endpoint, never with words times a ratio and never with counts measured on another model. The site's token counter uses 4 characters per token and is a planning estimate only. Budgets and cost forecasts are in tokens, so a model upgrade is a budget event even if the prompt text is unchanged: pin model versions, recount, and make the Model gateway enforce the limit it computed. Expect more tokens per character for code, numbers and non-English text, and measure your own content.

    Under the hood: Attention and the KV cache

    Intuition

    To choose the next token, the model lets the current position look back at every earlier position and weigh them by relevance. What it looks up in each earlier position (its key and its value) never changes once computed. So the server keeps them instead of recomputing them for every new token. That store is the KV cache. It grows by a fixed amount per token, and it is memory, not arithmetic, that runs out first.

    Mechanism

    The Transformer paper defines attention as softmax(Q K^T / sqrt(d_k)) V: each query is compared with all keys, the scores are normalised, and they weight the values. In a decoder, masking stops a position from attending to later positions. vLLM's paper describes the consequence at serving time: in the generation phase the key and value vectors of earlier positions were cached at previous iterations, so only the new token's key and value are computed.

    The cache size per token follows from the model's shape: bytes per token = 2 (key and value) x layers x KV heads x head dimension x bytes per value. The PagedAttention paper works the example for the 13B-parameter OPT model: 2 x 5,120 (hidden size) x 40 (layers) x 2 (bytes per FP16 value) = 819,200 bytes, which is the paper's 800 KB per token (reading 1 KB as 1,024 bytes), and up to 1.6 GB for a 2,048-token sequence (1.68 x 10^9 bytes).

    Grouped-query attention (GQA) shrinks the cache by sharing keys and values among groups of query heads; multi-query attention (MQA) is the extreme with one KV head. The Llama 3 paper states that its models use GQA with 8 key-value heads and lists the dimensions below; the head dimension is the model dimension divided by the number of attention heads, which gives 128 in all three.

    Model Layers Model dimension Attention heads KV heads KV bytes per token 8,192 tokens 128,000 tokens
    Llama 3 8B 32 4,096 32 8 131,072 (128 KiB) 1.0 GiB 15.6 GiB
    Llama 3 70B 80 8,192 64 8 327,680 (320 KiB) 2.5 GiB 39.1 GiB

    For the 8B model, 2 x 32 x 8 x 128 x 2 = 131,072 bytes. With 32 KV heads (plain multi-head attention) it would be 4 times larger, 512 KiB per token. These are per sequence: ten concurrent 8,192-token conversations on the 8B model hold 10 GiB of cache on top of the weights.

    Figure 6. What is computed once and what is kept

    sequenceDiagram accTitle: Prefill fills the KV cache and decode appends to it accDescr: During prefill the inference server runs all prompt tokens through the model weights in parallel and stores their keys and values in the KV cache. During decode it runs only the newest token, appends that token's key and value to the cache, reads all earlier keys and values for attention, and samples the next token. participant S as Inference server participant W as Model weights participant K as KV cache Note over S,K: Prefill, once per request S->>W: run all n prompt tokens in parallel W-->>S: keys and values for every layer and position S->>K: store n keys and values loop Decode, one step per output token S->>W: run the newest token only W-->>S: its key, its value and scores for the next token S->>K: append the new key and value K-->>S: all earlier keys and values, read for attention Note over S: Sample the next token from the scores end

    How to read this: the first block happens once, the loop repeats for every output token, and the cache is the only thing that carries information between steps.

    FlashAttention is a separate optimisation: an exact attention algorithm that tiles the computation to cut reads and writes between GPU high-bandwidth memory and on-chip SRAM. It makes attention faster and less memory-hungry; it does not remove the need to store keys and values.

    What this means for architects

    The cache turns context length into memory. Capacity for a self-hosted model is (GPU memory minus weights) / (KV bytes per token x tokens per request). In the vLLM paper's setup (a 13B model on a 40 GB A100) about 65% of memory holds weights and close to 30% is left for the cache: about 12 GB, or 12 x 10^9 / 819,200 = 14,648 tokens in total, roughly seven full 2,048-token sequences. That is why long contexts cut concurrency, and why a model's maximum window is not the context you can afford on every request. Fewer KV heads means cheaper long context, a selection criterion next to price and quality. With an API the same arithmetic appears as the provider's limits and long-input pricing, which are provider-specific.

    Under the hood: Prefill and decode

    Intuition

    Reading the prompt is done for all tokens at once; writing the answer is done one token at a time. The first is a large batch of arithmetic that a GPU does well. The second repeats a small amount of arithmetic per token, but each step must stream the model's weights from memory again, so it is limited by memory bandwidth and not by compute.

    Mechanism

    The vLLM paper describes a prompt phase that takes the whole prompt, parallelises over its tokens, and produces the first new token, then a generation phase that cannot be parallelised because each token depends on the previous one: it uses matrix-vector operations, underuses the GPU, is memory-bound, and accounts for most of a single request's latency. The Sarathi-Serve paper states it as prefill compute-bound, decode memory-bound, and measures on Mistral-7B on an A100 that the linear operations for one decode token cost nearly the same as for 128 prefill tokens. DistServe defines TTFT as the duration of prefill and TPOT as the average time per token after the first.

    Figure 7. Anatomy of one streamed request

    sequenceDiagram accTitle: Prefill then decode for one request accDescr: The Client app sends a request through the Model gateway to the LLM provider. The provider queues the request, processes all prompt tokens in parallel during prefill and returns the first token, which ends the time to first token. It then generates one token per step, each streamed back to the client, and finishes with a stop event and usage. participant C as Client app participant G as Model gateway participant P as LLM provider C->>G: request with 9,000 input tokens G->>P: model request, stream on Note over P: Queue wait, then prefill - all prompt tokens in parallel P-->>G: first token G-->>C: first token Note over C,P: Time to first token = queue + network + prefill loop each remaining output token Note over P: Decode step - one token, weights read again P-->>G: next token G-->>C: next token end P-->>G: stop event with usage Note over C,P: Total = TTFT + (N minus 1) x TPOT

    How to read this: the first block before the loop is paid once and grows with prompt length, while the loop is paid per output token.

    Two bounds make this concrete (all inputs are stated assumptions). The decode floor: if decode reads all weights per token, time per token is at least weight bytes divided by memory bandwidth. An 8B-parameter model in 16-bit weights is about 16 GB; at the 2,039 GB/s NVIDIA lists for the A100 80GB SXM, 16 x 10^9 / 2,039 x 10^9 = 7.85 ms per token, so 400 tokens take at least 3.1 s (ignoring the KV cache and overheads, so real systems are slower). And TTFT: with an assumed prefill rate of 10,000 tokens per second, a 9,000-token prompt prefills in 0.9 s, or 3,000 / 10,000 = 0.3 s if 6,000 tokens come from a prefix cache. With TPOT 25 ms and 400 output tokens the totals are 0.9 + 399 x 0.025 = 10.875 s and 10.275 s: caching moved TTFT and the total by the same 0.6 s, because the 399 decode steps dominate.

    What this means for architects

    An output token costs more latency than an input token: each is a sequential step, while input tokens are processed in parallel. Shortening answers (a tighter format, a schema without prose, a sane max_tokens) is the strongest lever on total time, and thinking tokens are decode time too. Shortening or caching the prompt moves TTFT, which an interactive user feels first. Set separate objectives for TTFT and TPOT and alert on their 95th percentiles: the Sarathi-Serve authors note that under load scheduling delays raise TTFT, and DistServe shows that adding a prefill to a batch of decodes slows both. Batch jobs care about throughput and can ignore TTFT.

    Under the hood: Continuous batching and paged KV memory

    Intuition

    Because decode is memory-bound, running many requests together shares each weight read across all of them, which is why serving systems batch. Two practical problems follow. Requests finish at different times, so a fixed batch wastes slots. And each request's cache grows by an unknown amount, so reserving space for the worst case wastes memory. Continuous batching solves the first, paged memory the second.

    Mechanism

    The Orca paper (OSDI 2022) contrasts request-level scheduling, where the engine keeps a batch fixed until every request in it finishes, with iteration-level scheduling, where the scheduler runs one model iteration at a time. Finished requests return immediately and a new request starts after the current iteration, not after the whole batch. The authors report 36.9 times the throughput of NVIDIA FasterTransformer at the same latency on GPT-3 175B. Toy arithmetic shows the waste removed: four requests with output lengths 10, 50, 200 and 30 in a fixed batch occupy 4 x 200 = 800 token-steps but need 10 + 50 + 200 + 30 = 290, a utilisation of 36.25%.

    The vLLM paper attacks memory. Earlier systems pre-allocated a contiguous chunk per request for its maximum length, and the paper measures that only 20.4% to 38.2% of KV-cache memory held real token states. PagedAttention stores the cache in fixed-size blocks (16 tokens by default), keeps a block table from logical to physical blocks, allocates a block only when the last one fills, and so wastes at most part of one block per request. Blocks can be shared with reference counts and copy-on-write. When memory runs out, vLLM preempts whole requests (earliest arrivals served first) and recovers them by swapping to CPU memory or recomputing. The paper reports 2 to 4 times the throughput of FasterTransformer and Orca at similar latency.

    Worked numbers for the OPT-13B cache at 819,200 bytes per token: a 100-token sequence needs ceil(100 / 16) = 7 blocks, which is 112 slots, 12 of them unused (12 x 819,200 = 9.8 MB). Reserving the 2,048-token maximum instead holds 1.68 GB for a request that uses 100 tokens, 95.1% of it idle.

    Figure 8. Life of a request in a continuous-batching server

    stateDiagram-v2 accTitle: Request states in a continuous-batching server accDescr: A request waits in a queue, is admitted to the running batch when memory blocks are free, and generates one token per iteration, taking a new block when its last block is full. It finishes on an end token or the token limit and its blocks are freed. If memory runs out the newest requests are preempted, their blocks swapped out or dropped for recomputation, and they return to waiting. [*] --> Waiting: request arrives Waiting --> Running: admitted, blocks are free Running --> Running: one iteration, one token, new block when full Running --> Finished: end token or token limit Running --> Preempted: no free blocks, newest request first Preempted --> Waiting: blocks swapped out or dropped for recompute Finished --> [*]: blocks freed

    How to read this: each box is a state, arrows carry the condition, and the loop on Running is the per-iteration step that continuous batching makes possible.

    What this means for architects

    Latency under load is queueing plus a slower iteration as the batch grows, so a provider that is fine at noon can show a long TTFT at peak with no change on your side. For a self-hosted server the KV block pool is the capacity limit: concurrent requests times average tokens times bytes per token must fit, and a long max_tokens is not free because it raises preemption risk. Provider batch endpoints are a separate product feature: Anthropic's Message Batches API processes requests asynchronously, most batches finish within an hour, batches expire if processing does not complete within 24 hours, and its page states a 50% cost reduction (as of 2026-10-22, batch processing). The document-classification case file uses this trade of delay for cost.

    Under the hood: Quantization

    Intuition

    Weights are stored as 16-bit numbers. Store them in 8 or 4 bits and the model needs a half or a quarter of the memory, and since decode is limited by how many bytes it reads per token, it can also run faster. The price is rounding error, which must be small enough not to hurt your task.

    Mechanism

    Weight memory is parameters x bits / 8. For an 8B-parameter model: 16 bits is 16 GB, 8 bits is 8 GB, 4 bits is 4 GB, plus small per-group scales whose size depends on the scheme. At the 2,039 GB/s used above, the decode floor falls from 7.85 ms to 3.92 ms to 1.96 ms per token. That is a ceiling for weight-bound decode, not a forecast.

    LLM.int8() (Dettmers et al.) quantizes the matrix multiplications of feed-forward and attention projection layers to 8 bits, halving inference memory with no loss in their tests; it multiplies the emergent outlier dimensions that ruin naive quantization in 16 bits while more than 99.9% of values are multiplied in 8 bits. GPTQ (Frantar et al.) is a one-shot post-training method using approximate second-order information that compresses weights to 3 or 4 bits, quantizing a 175B-parameter model in about four GPU hours with negligible accuracy degradation in their measurements. They report per-token speedups at batch size 1 for 3-bit OPT-175B of about 3.25 times (one A100, against FP16 on 5 GPUs) and 4.5 times (two A6000, against FP16 on 8 GPUs). They explain that dequantizing costs extra compute but the kernel reads far less memory, and they note that LLM.int8() reduces memory but has the same runtime as the FP16 baseline and that they do not quantize activations.

    Figure 9. Offline quantization and online use

    sequenceDiagram accTitle: Quantizing weights offline and using them online accDescr: Offline, a quantizer uses a small calibration sample to choose low-bit weight values that keep layer outputs close to the original and stores the low-bit weights with their scales. Online, at every decode step the GPU kernel reads the low-bit weights, which is a fraction of the bytes, dequantizes them inside the kernel and multiplies them with 16-bit activations. participant D as Calibration sample participant Q as Quantizer participant S as Weight store participant K as GPU kernel Note over D,S: Offline, once per model D->>Q: a small sample of inputs Q->>Q: choose low-bit values per weight group to keep layer outputs close Q->>S: store low-bit weights and scales Note over S,K: Online, every decode step loop each decode step K->>S: read low-bit weights, a fraction of the bytes Note over K: Dequantize in the kernel, multiply with 16-bit activations end

    How to read this: the top block is paid once and may change quality, the loop is where the memory saving shows up as speed.

    What this means for architects

    A quantized model is a different model. Papers measure perplexity and standard benchmarks, not your task, so a quantized variant needs its own run on your golden set before it replaces anything, and the accuracy cost varies with model size, bit width and scheme. For self-hosting, smaller weights leave more memory for the KV cache, which raises concurrency, so quantization is a capacity lever as well as a latency lever. For API models, whether and how a provider quantizes is generally not documented; treat any silent behaviour change as something your regression gate must catch.

    Under the hood: Speculative decoding

    Intuition

    A small draft model guesses the next few tokens cheaply. The large model then checks all guesses in a single pass, keeps the ones it would have produced itself and replaces the first wrong one. Because decode is memory-bound, checking several positions in one pass costs little more than generating one token, so accepted guesses are nearly free.

    Mechanism

    Leviathan, Kalman and Matias (Google) and Chen et al. (DeepMind) independently published the method. With draft distribution q and target distribution p, a drafted token is kept with probability min(1, p/q); on the first rejection a replacement is sampled from the normalised positive part of p - q. Both papers prove that the output distribution is identical to sampling from the target alone (Leviathan et al., Appendix A.1; Chen et al., Theorem 1), and both state that each target pass yields at least one and at most gamma + 1 tokens (gamma, called K by Chen et al., is the number of guesses).

    Only Leviathan et al. derive the speed formulas below; the one theorem in Chen et al. is the distribution proof, and their speed results are measurements. Let alpha be the average probability that a guess is accepted; Leviathan et al. assume it is the same at each position (independent and identically distributed), which makes the tokens per pass a capped geometric variable with expectation E = (1 - alpha^(gamma+1)) / (1 - alpha) (their Equation 1). Their Theorem 3.8 gives the expected wall-time improvement E / (gamma x c + 1), where c is the time of one draft run divided by that of one target run, assuming gamma + 1 target evaluations can run in parallel without raising wall time. With alpha = 0.8 and gamma = 4, E = (1 - 0.8^5) / 0.2 = 3.36 tokens per pass; at c = 0.05 the improvement is 3.36 / 1.2 = 2.80 times, at c = 0.2 it is 3.36 / 1.8 = 1.87 times. Their Theorem 3.11 gives the cost in arithmetic: operations per token rise by (gamma x c-hat + gamma + 1) / E, 1.55 times here (c-hat = 0.05 is the draft's arithmetic cost ratio). Measured: speedups of 2.3 to 3.4 times on T5-XXL, which the paper summarises as 2X-3X (Leviathan et al.), and 2 to 2.5 times on Chinchilla 70B in a distributed setup at batch size 1 (Chen et al.), who also find that the gain plateaus or regresses as the number of guesses grows.

    Figure 10. One speculative step with four guesses

    sequenceDiagram accTitle: One speculative decoding step accDescr: The draft model guesses four tokens one after another. The target model scores the prefix plus all four guesses in one parallel pass. Guesses one to three are accepted and guess four is rejected, so the target model supplies a corrected token, and four tokens are emitted from a single target pass. participant Q as Draft model participant P as Target model participant O as Output stream Q->>Q: guess tokens 1 to 4, one after another, cheaply Q->>P: prefix plus the four guesses P->>P: one parallel pass scores all four positions plus one more Note over P: Guesses 1 to 3 accepted, guess 4 rejected P->>O: tokens 1 to 3 plus one corrected token Note over O: Four tokens from a single target pass

    How to read this: the target model runs once per step, and the number of tokens emitted depends on how many guesses it accepts.

    What this means for architects

    Speculative decoding is mostly a server-side optimisation, and with an API a provider may or may not use it: that is not visible to you. Where you run the server it reduces latency without changing the output distribution, but it needs spare compute (the paper says it does not help where additional compute is not available), so its gain shrinks as batches grow and it can reduce throughput at high load. It needs a draft model that agrees often with the target and shares its tokenizer; the acceptance rate alpha is workload-dependent and must be measured on your traffic, since the gain collapses as alpha falls.

    Under the hood: Prompt caching and prefix stability

    Intuition

    The cached state of a token depends on every token before it, so two prompts can share cached work only for the part where they are identical from the very first token. The vLLM paper makes the point directly: the same tokens can have different KV cache at different positions. Put the part that never changes first and the part that always changes last, and most of the work is reused. Change one early byte and everything after it is recomputed.

    Mechanism

    How providers implement this is mostly undocumented; the contract is documented. OpenAI's page says the cache stores key-value tensors, not tokens, that reuse requires the entire rendered prefix to match, and that cached state lives on individual machines, where traffic above 15 requests per minute can lead to overflow routing. Anthropic's page says the cache covers the prompt in the order tools, system, messages, up to the block marked with cache_control (up to 4 breakpoints, or one automatic breakpoint that moves forward), that a hit needs 100 percent identical segments, and that the default lifetime is 5 minutes, refreshed on each use, with a 1-hour option at extra cost. A change at one level invalidates it and all later levels, so editing a tool definition invalidates tools, system and messages. An entry becomes available only after the first response begins, so parallel requests that must share a prefix should wait for the first to start. Usage splits input as cache_read_input_tokens + cache_creation_input_tokens + input_tokens. Minimum length is model-specific: as of 2026-10-22 Anthropic lists 512 to 4,096 tokens by model, and OpenAI lists 1,024 for its newest models and says earlier ones vary with request settings. Shorter prompts are processed without caching and without an error.

    Figure 11. A prefix cache across three requests

    sequenceDiagram accTitle: Prefix cache across three requests accDescr: The first request misses the provider prefix cache, which stores the processed prefix. The second request with the same start and a new user turn hits it and reports cache read tokens. The third request has an edited system prompt, so the match breaks at the edit and everything after it is processed again. participant O as Orchestrator participant M as Model gateway participant P as LLM provider participant C as Provider prefix cache, internal O->>M: request 1 - tools, system, reference text, user turn 1 M->>P: model request P->>C: look up the longest matching prefix C-->>P: none P->>C: store the processed prefix P-->>M: answer, usage shows cache creation tokens O->>M: request 2 - same start, user turn 2 M->>P: model request P->>C: look up the longest matching prefix C-->>P: prefix found P-->>M: answer, usage shows cache read tokens O->>M: request 3 - system prompt edited near the top M->>P: model request Note over P,C: Match breaks at the edit, everything after it is a miss P-->>M: answer, usage shows cache creation tokens again

    How to read this: the cache is inside the provider and not directly visible, so the usage fields in each reply are the evidence of a hit or a miss.

    Worked example with the multipliers Anthropic's page states as of 2026-10-22 (writes 1.25 times the base input rate for the 5-minute lifetime, reads 0.1 times; some models use other read multipliers, so check the page for yours). A 6,000-token prefix sent in 10 requests within the lifetime costs 10 x 6,000 = 60,000 token-equivalents uncached, against 1.25 x 6,000 + 9 x 0.1 x 6,000 = 7,500 + 5,400 = 12,900 cached, which is 78.5% less. The break-even is the second request: cached cost for n requests is 1.25 + 0.1 (n - 1) prefix-lengths, which is 1.35 for n = 2 against 2 uncached.

    What this means for architects

    Prefix stability is an architectural property, set by prompt layout and release practice. Order the request from most stable to least: tool definitions, system prompt, shared reference text, retrieved passages, then the user turn. Keep timestamps, request ids and user names out of the start, serialise tool schemas in a fixed order, and do not reorder tools per request. Every prompt release is a one-time cache rewrite, so ship prompt changes in batches. Compare request rate with lifetime: 1,000,000 requests a month is about 23 a minute on average, which keeps a 5-minute entry warm by day and may let it expire at night. Alert on the cache-read share in the usage fields, since a silent layout change shows up only there. Per-tenant data early in the prompt gives each tenant its own prefix and defeats sharing. For privacy, read the provider's data terms: OpenAI says cached key-value tensors may be stored in GPU-local storage.

    Under the hood: Streaming responses

    Intuition

    Instead of holding the answer until the model finishes, the provider sends tokens as they are produced. The total time is unchanged, but the user sees text after the time to first token and can start reading, and the interface can react (stop, cancel) before the end.

    Mechanism

    Provider streams use server-sent events (SSE), defined in the WHATWG HTML Standard. The response has the media type text/event-stream and is always UTF-8: events separated by blank lines, each made of lines with a field name and a value. data carries the payload (several data lines are joined with newlines), event names the type, id sets the last event id and retry the reconnection time. A line starting with a colon is a comment, and the standard suggests one about every 15 seconds so proxies do not drop an idle connection. A browser EventSource reconnects after a drop and sends a Last-Event-ID header; a status other than 200 or a wrong content type fails the connection with no reconnect, and HTTP 204 tells the client to stop. An incomplete trailing event is discarded.

    Anthropic's streaming documentation (as of 2026-10-22) builds on this: a message_start event, content blocks each with a start, deltas and a stop, message_delta events with cumulative usage, a final message_stop, and ping events anywhere. Tool-call arguments stream as partial JSON strings parsed only after the block stops. Errors can arrive inside the stream, for example an overloaded_error that would be HTTP 529 without streaming. The page describes resuming an interrupted stream by capturing the partial response and sending a continuation request, and says tool-use and thinking blocks cannot be partially recovered.

    Figure 12. A stream through the system, with a failure after the first token

    sequenceDiagram accTitle: Streaming path and a mid-stream failure accDescr: The Client app opens an event stream through the API gateway to the Orchestrator and Model gateway, which call the LLM provider with streaming on. Token deltas flow back along the same path and the interface shows text from the first delta. In the normal case the provider ends with a stop event and usage. If the connection fails after the first token, the HTTP status 200 has already been sent, so the Orchestrator keeps the partial text, sends an error event with a partial flag and does not retry silently. participant C as Client app participant G as API gateway participant O as Orchestrator participant M as Model gateway participant L as LLM provider C->>G: open event stream G->>O: forward request O->>M: generate, stream on M->>L: model request L-->>M: message start, then token deltas M-->>O: token deltas O-->>G: token deltas G-->>C: data lines, text appears from the first delta alt Stream completes L-->>M: stop event with usage M-)O: usage record else Failure after the first token L--xM: connection drops or error event Note over O: Status 200 was already sent, keep the partial text, no silent retry O-->>C: error event with partial flag end

    How to read this: the same arrows carry every delta from the provider to the screen, and the else branch shows the case in which the HTTP status can no longer signal the error.

    What this means for architects

    Streaming moves a failure into the middle of a response. Once headers are sent the status is 200, so errors must travel in-band and the client must show a partial answer as incomplete. Do not retry silently after the first token: shown text cannot be unsaid, and a retried tool call may run twice. Output checks that ran on a complete answer must now run on partial text or after it, so decide per intent which replies may stream; the in-product assistant case makes this decision. Every hop (gateway, reverse proxy, CDN) must pass the stream through unbuffered, and idle timeouts must allow gaps between tokens. JSON cannot be parsed until complete. Usage arrives at the end, and whether closing the connection stops generation and billing is provider-specific, so test it.

    Decision framework

    1. If requests have a type that is known before the call (endpoint, UI surface, tier, intent), then route with rules first, because rules are free, deterministic and testable; revisit when a review of misrouted requests shows that difficulty is not visible in the metadata.
    2. If cheap answers can be scored reliably and a slower tail on escalated requests is acceptable, then use a cascade, otherwise a classifier router, because a cascade pays for two calls on every escalation and is only as good as its scorer; revisit when the measured escalation rate approaches the break-even rate (1 minus the small-to-large cost ratio).
    3. If the prompt has a stable prefix above the provider's minimum and traffic keeps it warm, then lay the prompt out for prefix caching before adding routing, because it cuts cost and TTFT without changing any answer; revisit when prompt releases are more frequent than the cache lifetime.
    4. If an answer depends on the user, the tenant, the time or changing data, then do not use a semantic cache for it and key any exact cache on all of those inputs, because a wrong hit is an incident and not a miss; revisit when the content versioning is strong enough to expire entries exactly.
    5. If the user is waiting and the answer is longer than a sentence, then stream, and set separate objectives for time to first token and per-token time; if no one is waiting, do not stream and prefer batch execution, because streaming adds failure modes and buys nothing offline; revisit when the output checks can run on partial text.
    6. If data residency, version pinning or sustained high utilisation is a hard requirement, then evaluate self-hosting with an honest operations cost, otherwise stay on an API behind a Model gateway, because the gateway keeps the exit open; revisit when volume or regulation changes.

    Comparison of the main options

    Option Cost effect Latency effect Main risk Fits when
    One large model for all requests Highest Slowest tail for easy requests Cost grows with traffic Prototype or low volume
    Rule routing Lower, no extra calls None added Brittle when difficulty is hidden Request type is known upfront
    Classifier router Lower, one small extra call Small, one extra step Silent drift, needs data Stable traffic with labelled outcomes
    Cascade Lower if escalation is rare Escalated requests wait twice False accepts by the scorer A reliable scorer exists
    Exact cache Removes repeat calls Near zero on a hit Leaks if the key omits an input Generic, repeatable answers
    Prefix (prompt) cache Lowers input cost Lowers TTFT Layout changes silently kill hits Long stable prompts, steady traffic
    Semantic cache Removes near-repeat calls Near zero on a hit Wrong, stale or cross-tenant hit FAQ-like intents only
    Self-hosted model Depends on utilisation You own batching and queueing Operations and evaluation burden Residency, pinning, steady volume

    Failure scenarios to design for

    1. The router sends a hard request to the small model and nobody notices. Defined behaviour: every call logs route, reason and confidence; a sample of small-model answers per intent is reviewed weekly; tool errors, schema failures and thumbs-down escalate the next turn to the large model; the golden set has a slice per route.
    2. The cascade scorer accepts a wrong answer (false accept). Defined behaviour: the threshold is set on labelled data for a target false-accept rate, intents with costly errors are on the always-large rule list, and a rise in the share of low-confidence answers raises an alert because it signals drift.
    3. A semantic or exact cache returns another tenant's answer or an answer from an old policy. Defined behaviour: tenant and content version are part of the key, entries expire when the content version changes, hits are logged with the matched query, and cross-tenant probes are part of the test suite.
    4. The prefix cache goes cold after a release, a traffic gap or a layout change. Defined behaviour: the gateway tracks the cache-read share per route, a drop below a set share alerts, releases include one warm-up request before traffic, and capacity is sized for the uncached case.
    5. A model upgrade changes the token count and requests exceed their budget. Defined behaviour: model versions are pinned, the gateway counts with the target model's tokenizer, over-budget requests are trimmed by the stated policy or rejected with a defined error, and a version change reruns the budget table.
    6. A stream fails after the first token. Defined behaviour: the partial text is kept and marked incomplete, no tool call is executed from a partial response, the user is offered an explicit retry, and the usage record notes the interruption.
    7. Structured output is truncated by the token limit. Defined behaviour: the stop reason is checked before parsing; on a token-limit stop the request is retried once with a higher limit or sent to review, never parsed as if complete.

    Worked example

    A support assistant sends 1,000,000 requests a month. Cost is measured in large-model input-token equivalents (LTE) so that no price appears; use the LLM cost estimator with real prices.

    Assumptions: this is a composite scenario and every figure is illustrative. Each request has a 6,000-token stable prefix, 300 variable input tokens and 250 output tokens. An output token weighs 4 LTE. A small model costs 0.1 of the large model per token. Prefix-cache writes weigh 1.25 and reads 0.1 (as stated for Anthropic as of 2026-10-22; check your provider), with a 90% hit rate on every model. In the cascade, every request goes to the small model first and 25% escalate, so an escalated request pays for the small attempt and then the large call.

    Design Calculation LTE per request Change from baseline
    Baseline: all large, no cache 6,000 + 300 + 250 x 4 7,300 none
    Add prefix caching input: 0.9 x (6,000 x 0.1 + 300) + 0.1 x (6,000 x 1.25 + 300) = 810 + 780 = 1,590; plus output 1,000 2,590 -64.5%
    Add a cascade (25% escalate) 0.1 x 2,590 + 0.25 x 2,590 = 259 + 647.5 906.5 -87.6%
    Alternative: a perfect classifier router 0.75 x 0.1 x 2,590 + 0.25 x 2,590 = 194.25 + 647.5 841.75 -88.5%

    Three checks keep this honest. The cascade costs 0.1 + f of the large-only cost for escalation rate f, so it stops paying at f = 0.9, and the real saving depends on the measured f and on quality at the threshold. The perfect classifier is an upper bound no real router reaches; a real one adds a small call and makes errors. And the 90% hit rate is an assumption: the large model sees about 5.8 requests a minute on average (0.25 x 23.1), so its prefix can expire in quiet hours. Read the real rate from the usage fields. Output cost is untouched by caching and driven by output length, which is why the weight of 4 per output token matters as much as the cache.

    A routing policy you can run

    The policy below combines a rule layer with a cascade. The scorer (confidence) must be validated on a golden set before its threshold means anything, and the models are passed in as functions so the policy can be tested without a provider. The block was executed with stub models for the four paths: a confident small answer, a low-confidence escalation, a small-model timeout and an always-large intent.

    import logging
    from dataclasses import dataclass
    from typing import Callable
    
    log = logging.getLogger("model-gateway")
    
    @dataclass
    class Answer:
        text: str
        confidence: float
    
    Model = Callable[[str], Answer]
    ALWAYS_LARGE = {"refund_dispute", "complaint", "legal_threat"}
    THRESHOLD = 0.80
    
    def route(intent: str, prompt: str, small: Model, large: Model) -> Answer:
        if intent in ALWAYS_LARGE:
            log.info("route=large reason=rule intent=%s", intent)
            return large(prompt)
        try:
            first = small(prompt)
        except TimeoutError:
            log.warning("route=large reason=small_timeout")
            return large(prompt)
        if first.confidence >= THRESHOLD:
            log.info("route=small confidence=%.2f", first.confidence)
            return first
        log.info("route=large reason=low_confidence confidence=%.2f", first.confidence)
        return large(prompt)

    Read it as three layers. The set ALWAYS_LARGE is the rule layer: intents with costly errors never reach the small model. The try block is the cascade, with the timeout as an escalation trigger. Every branch logs the route and the reason, which is what makes the policy auditable. A production version would also pass the remaining deadline to the large call (Figure 3), count tokens before either call, and emit the usage event of Figure 1.

    Common mistakes

    • Routing on a confidence number nobody validated. A model's self-reported or heuristic score is not calibrated by default; set the threshold on labelled data and re-check it after every model or prompt change.
    • Putting a timestamp, request id or user name at the top of the prompt. It makes every request's prefix unique and the prefix cache never hits.
    • Counting tokens as words times 1.3, or with another model's tokenizer. Counts are model-specific and change with model versions; use the target model's counter.
    • Semantic-caching answers that depend on the user's data. The cache returns a plausible answer to the wrong person or an old answer to the right one.
    • Tracking only total latency. Time to first token and time per token have different causes and different fixes, and one number hides which one regressed.
    • Streaming everything, including batch jobs and tool calls. Streaming adds partial-failure handling and breaks output checks that need the whole answer; use it where a person is reading.

    Try it

    Time: 25 to 40 minutes.

    1. Pick a model whose layers, KV heads and head dimension are published (for example one of the Llama 3 sizes in the table above) and compute its KV bytes per token and per 32,000-token request.
    2. Paste a real prompt (about 500 words) into the site's token counter, note the estimated tokens per word, then count the same prompt with a provider's token-counting endpoint for two different models if you have access, and record the difference.
    3. Write a latency budget for one feature: an assumed prefill rate, TTFT, TPOT and output length, then compute total time with and without streaming and with and without a cached prefix.
    4. List your prompt's parts in cache order (tools, system, reference text, retrieved text, user turn), mark every part that changes per request, and check that the stable part is first and is longer than your provider's minimum cacheable length.

    Done when you have a one-page note with the KV bytes per token, the measured tokens per word and its model-to-model difference, the latency split with its arithmetic, and a prompt order in which no per-request value appears before the stable prefix.

    Key takeaways

    • A model gateway owns budgets, routing, caching, provider retries and usage records; the Orchestrator owns prompts and decisions.
    • Route with rules where the request type is known, a classifier where data supports it, and a cascade only with a validated scorer; the cascade break-even is 1 minus the cost ratio.
    • Tokens are model-specific. Count with the target model's tokenizer, and treat a model upgrade as a budget event.
    • KV-cache bytes per token = 2 x layers x KV heads x head dimension x bytes per value; it turns context length into memory and limits concurrency.
    • Prefill is parallel and compute-bound, decode is sequential and memory-bound, so output tokens cost more latency than input tokens; set separate objectives for TTFT and TPOT.
    • Prefix caching works only on an identical start of the prompt: put stable content first, keep per-request values out of the top, and watch the cache-read share.
    • Streaming improves perceived latency and moves failures into the middle of a response; design for partial output.

    Self-check

    1. A 6,000-token system prompt is followed by a line with the current time and then the user turn. The prefix cache never hits. Why, and what is the fix?

    The cached state of each token depends on every token before it, and the provider matches the prompt from its first token. A changing timestamp early in the prompt makes the prompts differ from that point on, so only the text before it can match. Move per-request values (time, ids, user turn) after the stable content, or remove them.

    2. Compute the KV-cache bytes per token for a model with 40 layers, 8 KV heads, head dimension 128 and 16-bit values, and the size for a 16,000-token request.

    Bytes per token = 2 x 40 x 8 x 128 x 2 = 163,840 (160 KiB). For 16,000 tokens that is 163,840 x 16,000 = 2.62 x 10^9 bytes, about 2.44 GiB for one sequence.

    3. Why does an output token add more latency than an input token?

    Input tokens are processed together in the prefill phase, a parallel, compute-heavy step. Output tokens are generated one at a time, and each step must read the model's weights from memory again, so decode is limited by memory bandwidth and is sequential. Total latency is about TTFT plus TPOT times the remaining tokens.

    4. A cascade sends every request to a small model costing 0.1 of the large one and escalates 30% of them. Does it save money, and at what escalation rate does it stop paying?

    Cost relative to large-only = 0.1 + 0.3 = 0.4, a 60% saving before any scorer overhead. It stops paying when 0.1 + f = 1, which is f = 0.9. Quality matters more than cost here: the saving is real only if answers accepted at the threshold are as good as the large model's on your golden set.

    5. A stream fails after 200 tokens have been shown. Why is retrying silently the wrong default?

    The user has already read the text, so a retry that produces a different answer contradicts it. If the response included a tool call, a retry could run the action a second time. The defined behaviour is to keep the partial text, mark it incomplete and offer an explicit retry, with idempotency keys on any tool call.

    Further reading

    Last verified: 2026-10-22

    Next

    Next is Data and retrieval architecture for AI systems, which covers how documents are ingested, chunked, indexed and retrieved safely.

    Enroll to keep reading

    Lesson 4 of 13 · AI Architect: From AI Engineer to Expert

    How AI architects choose models, route requests, cache prompts and budget latency, with tokenization, KV cache, batching and quantization explained.

    In the rest of this lesson

    • Under the hood: Attention and the KV cache
    • Under the hood: Prefill and decode
    • Under the hood: Continuous batching and paged KV memory
    • Under the hood: Quantization
    • Under the hood: Speculative decoding
    • Under the hood: Prompt caching and prefix stability

    Enrolling is free. It unlocks every lesson in AI Architect: From AI Engineer to Expert, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments