Latency
TTFT vs total time, streaming UX, parallel retrieval, semantic caching, model routing.
Prerequisites
- LLM APIs — streaming, caching headers, and the request shape that TTFT measures.
- Tokens & Context Windows — prefill cost scales with prompt length; output length dominates decode.
- The RAG Pipeline — a typical multi-stage request path whose latency ledger you will measure.
- Cost — most latency levers also change spend; the two are coupled.
The intuition
Waiting for an LLM feels like waiting for a courier who first has to read your entire letter before they write a single word of the reply. That reading phase is prefill: the model must process every input token before it can emit anything. The writing phase is decode: one word (token) at a time, at a steady pace, until the answer is finished.
If the courier starts speaking as soon as the first sentence is ready, you feel the conversation is underway even though the full reply is still minutes away. That is streaming. If they wait until the whole letter is done, you stare at a blank screen for six seconds and abandon the product.
Two numbers therefore matter, not one: time to first token (TTFT) — when the courier starts talking — and total generation time — when they finish. Optimizing the wrong number is the classic self-inflicted latency bug.
Key insight
For a streaming chat product, perceived latency ≈ TTFT. Total time still matters for cost and for machine-consumed pipelines, but users who read as tokens arrive judge the system by when the first useful token appears — not by when the last one does.
Why it exists
LLM products fail the user's patience budget in ways classical APIs do not. The hard constraints:
- Generation is serial by nature. Decode emits one token per forward pass. A 500-token answer at 100 tok/s is five seconds of wall clock no matter how fast the rest of your stack is.
- Prefill grows with everything you stuff in the prompt. System instructions, tool schemas, retrieved chunks, and chat history all lengthen TTFT. A 100k-token context is not free real estate.
- A RAG or agent request is a pipeline, not a single call. Embed → retrieve → optional rerank → generate. Sequential awaits on independent stages add up before the model even starts.
- Users abandon blank UIs. Hundreds of milliseconds of silence after a click feels broken; progressive tokens feel alive even when total time is long.
The alternatives lose in practice. Ignoring latency until launch produces a product people try once. Optimizing only total time with no streaming still feels slow. Throwing a faster frontier model at everything costs 5–25× more than routing easy work to a small model. Serializing every retrieval and tool call is the most common self-inflicted multi-second tax.
Latency engineering exists so you measure the ledger, optimize the dominant stage, and make waiting feel shorter — not so you micro-tune the vector index while decode still owns 80% of the request.
The core idea
LLM latency has two distinct numbers, and conflating them is the classic junior mistake. Time to first token (TTFT) is how long until the model starts responding — it covers network, queueing, and prefill (processing the entire input prompt). Total generation time adds decode: producing output tokens one at a time, typically 50–200 tokens/second on hosted APIs. A 500-token answer at 100 tok/s takes 5 seconds of decode no matter how fast prefill was — so output length, not input length, usually dominates total time.
Streaming changes perceived latency without changing actual latency: if TTFT is 800ms and users read as tokens arrive, the app feels sub-second even though full completion takes 6 seconds. That's why chat UIs stream by default, and why the latency SLO for a streaming product should be on TTFT (and time-to-first-useful-token), not on total time.
For a RAG or agent request, the LLM call is only one line in a latency ledger: embed the query, retrieve, optionally rerank, then prefill and decode. Each stage is a lever. The big four optimizations: parallelize everything without a data dependency (multi-index retrieval, independent tool calls), cache (prompt caching for repeated prefixes, semantic caching for repeated questions), route easy queries to a smaller, faster model, and generate fewer tokens (concise-output instructions, lower max_tokens, structured output instead of prose).
How it actually works
Prefill and decode have different physics. Prefill processes all input tokens in parallel and is compute-bound — TTFT grows roughly linearly with prompt length (another reason 100k-token contexts hurt). Decode generates autoregressively, one forward pass per token, and is memory-bandwidth-bound: the bottleneck is loading model weights and KV cache from GPU memory each step. This is why serving stacks like vLLM (PagedAttention, continuous batching) target decode throughput, and why output tokens cost more than input tokens on every provider.
The latency ledger of a typical RAG request:
| Stage | Typical latency | Optimization |
|---|---|---|
| Query embedding | 20–100 ms | Cache embeddings of repeated queries |
| Vector search | 10–50 ms | ANN index tuning; usually not the bottleneck |
| Reranking (cross-encoder) | 100–500 ms | Rerank fewer candidates; smaller reranker |
| LLM prefill (→ TTFT) | 200 ms–2 s | Prompt caching; shorter context; smaller model |
| LLM decode | 2–15 s | Fewer output tokens; faster model; streaming |
Decode is usually 60–90% of the total — measure your own ledger before optimizing; teams routinely tune the vector DB while decode dominates. See the RAG pipeline for how those stages compose end to end, and cost for why the same levers often cut spend.
Key insight
Measure before you optimize. Instrument per-stage spans, build the ledger, and spend engineering time on the stage that owns the majority of wall clock. Guessing the bottleneck is how weeks disappear into ANN tuning that buys 20 ms on an 8-second request.
Parallel retrieval. Fan out concurrently: dense + keyword search in hybrid retrieval, multiple collections, query expansion variants — then merge. Same for agents: independent tool calls should execute concurrently (most SDKs support parallel tool use). Sequential awaits in a pipeline are the most common self-inflicted latency bug. Mechanics of hybrid retrieval: hybrid search; tool concurrency: tool calling.
Prompt caching (provider-side) reuses the computed KV cache for a repeated prompt prefix — system prompt, tool definitions, large documents. It cuts both TTFT and input cost: Anthropic cache reads bill at 0.1x input price; OpenAI caches automatically for prompts ≥1024 tokens. Requires prefix stability — put static content first, variable content last. Full economics: cost.
Semantic caching (application-side) is different: it caches final responses, keyed by query embedding. On a new query, embed it, search cached entries, and if similarity clears a threshold, return the stored answer with zero LLM calls — milliseconds instead of seconds. The threshold is the whole game: ~0.92–0.95 works for FAQ-style traffic; too permissive and "cancel my order" matches "track my order" — a wrong answer served confidently. Invalidation risks: underlying data changes (stale answers need TTLs tied to source freshness), and personalized or time-sensitive queries must bypass the cache entirely (scope cache keys by user/tenant where answers differ).
Model routing. A small model (Haiku-class) answers easy queries at ~5x lower cost and noticeably lower TTFT and higher tok/s; a cheap classifier (or the small model itself with an escalation instruction) routes hard queries to the large model. Cascade variant: try small first, escalate on low confidence or failed validation — trades a second call on hard queries for savings on easy ones. Landscape context: choosing models.
Speculative decoding (mention-level): a small draft model proposes several tokens; the large model verifies them in one parallel pass, accepting matches. Output is identical to the large model alone, but decode gets 2–3x faster. It's how providers ship "fast modes" — as an API consumer you select it, not build it.
Common misconception
"We optimized retrieval from 80 ms to 40 ms" is not a latency win if decode is still 6 seconds. Streaming, shorter outputs, prompt caching, and model routing move the number users feel. ANN micro-tuning rarely does.
The flows
| Flow | Sequence | When it applies | What breaks it |
|---|---|---|---|
| Streaming chat path | Request → prefill → stream tokens → client renders progressively | Interactive chat UIs | Buffering proxies that wait for the full body; measuring only total time as the SLO |
| Full RAG ledger | Embed → parallel retrieve → optional rerank → prefill → decode | Grounded Q&A over documents | Sequential hybrid search; oversized rerank sets; stuffing too many chunks into the prompt |
| Prompt-cache hit | Stable prefix already in provider KV cache → cheap prefill → decode | Repeated system/tools/docs across requests | Dynamic content at the top of the prompt; prefix reordering on deploy |
| Semantic-cache hit | Embed query → similarity search over cached answers → return if above threshold | FAQ-like, high-repeat traffic | Threshold too loose (wrong answer); personalized/time-sensitive queries without bypass; stale TTLs |
| Model routing / cascade | Classify or try small model → escalate only if needed → large model | Mixed easy/hard traffic | Router that sends everything to frontier; cascade without a latency budget for the second hop |
| Parallel tool / multi-index | Fan out independent I/O → merge → continue | Hybrid retrieval; multi-tool agents | await in a loop over independent calls |
A worked example
Trace one request through the governed enterprise platform. An employee asks:
"What is the approval threshold for international travel under the current expense policy?"
Without optimizations (naive serial path):
| Stage | What happens | Latency |
|---|---|---|
| Embed query | One encoder call | 40 ms |
| Dense search | Sequential | 35 ms |
| BM25 search | After dense finishes | 30 ms |
| Rerank top-50 | Cross-encoder | 200 ms |
| Prefill | ~8k tokens: system + tools + 8 chunks + question | 900 ms TTFT |
| Decode | ~400-token answer at ~100 tok/s | 4 000 ms |
| Total | User sees blank UI until full answer | ~5.2 s, first token at ~1.2 s |
With the latency levers applied:
| Stage | Change | Latency |
|---|---|---|
| Embed | Unchanged | 40 ms |
| Dense + BM25 | Concurrent fan-out | max(35, 30) = 35 ms |
| Rerank | Top-20 only, then keep 5 | 90 ms |
| Prefill | Prompt cache hit on system + tools + policy block; only query + small dynamic tail is cold | TTFT ≈ 350 ms |
| Decode | Concise instruction + max_tokens 200; ~180 tokens at 120 tok/s on a mid-tier model for this FAQ-shaped query | ~1 500 ms |
| Stream | Tokens paint as they arrive | Perceived start ≈ 350–500 ms |
| Total | Full answer | ~2.0 s, feels sub-second once streaming |
If the same question was asked earlier in the session and semantic cache similarity is 0.96 against a stored answer (threshold 0.93), the path becomes: embed (~40 ms) → cache hit → return in under 100 ms with zero generation.
What each omitted stage looks like in production
- No streaming → correct answer in 2 s still feels like a hung UI; abandonment spikes even when p50 is "fine."
- No parallel retrieval → hybrid search serializes; you add tens of milliseconds for free on every request, and agents with three tools wait for the sum instead of the max.
- No prompt caching → every request re-prefills the same 6k-token policy prefix; TTFT stays near a second and input cost multiplies (see cost).
- Semantic cache threshold too loose → "cancel my order" hits "track my order"; users get a confidently wrong answer in 50 ms — a correctness incident, not a latency win.
- No
max_tokens/ concise instruction → verbose 600-token answers; decode alone is 6 s and the SLO is impossible. - Buffering gateway → SSE never reaches the browser; you paid for streaming infrastructure and still show a blank page until complete.
Common drill-downs
Your RAG endpoint takes 8 seconds. Walk me through diagnosing it. Instrument per-stage spans, get the ledger. Typical finding: decode dominates (long answers) → cap/condense output, stream, smaller model. If TTFT dominates: huge prompt → prompt caching, trim retrieved context, fewer chunks. If retrieval dominates: sequential calls that should be parallel, or an oversized rerank set.
What's the difference between TTFT and total latency, and which do you optimize for a chatbot? TTFT = prefill + queueing until first token; total adds decode at ~50–200 tok/s. For a streaming chat UI, optimize TTFT — users read along, so perceived latency ≈ TTFT. For machine-consumed output (JSON into a pipeline), only total time matters.
How does semantic caching differ from prompt caching? Prompt caching is provider-side KV-cache reuse of an identical prompt prefix — the model still generates fresh output; saves prefill time and input cost. Semantic caching is app-side reuse of a full response for a semantically similar query — skips the LLM entirely; risks wrong-answer hits and staleness, so it needs threshold tuning, TTLs, and bypass rules.
When does a semantic cache hurt you? Personalized answers (same question, different user, different truth), time-sensitive data, low-traffic long-tail queries (hit rate too low to justify complexity), and thresholds tuned too loose — a wrong cached answer is worse than a slow correct one.
How would you cut latency without changing models?
Parallelize independent I/O; prompt-cache static prefix; trim retrieved context to what's needed; instruct concise output and cap max_tokens; stream; semantic-cache the head of the query distribution; move non-blocking work (logging, judge scoring, summarization) off the request path.
What is speculative decoding, one level deep? Draft model emits k cheap tokens; target model validates all k in one parallel forward pass, accepting the longest matching prefix. Acceptance rate determines speedup (2–3x typical); output distribution is provably unchanged. Consumers get it via provider fast modes rather than implementing it.
Why are output tokens slower (and pricier) than input tokens? Input is processed in one parallel, compute-bound prefill pass. Output is sequential — one memory-bandwidth-bound forward pass per token. Serial generation consumes far more accelerator time per token, which pricing reflects (output commonly ~5x input).
Production concerns
- Tail latency. Report p95/p99, not averages — LLM APIs have fat tails (queueing, long generations, provider load). Cap the tail with per-stage timeouts and a
max_tokensceiling; an unbounded generation is an unbounded SLO. Pair with reliability timeouts and circuit breakers. - Streaming infrastructure. SSE/streamed responses must flow through every hop — a buffering proxy or serverless gateway that waits for the full body silently destroys the TTFT win. Structured output (JSON) can't be shown token-by-token; stream field-by-field with partial parsing, or show progress states. See structured output and LLM APIs.
- Semantic cache failure mode is correctness, not staleness only: a false-positive cache hit returns the wrong answer with full confidence. Log cache hits with similarity scores, sample-audit them, and start with a conservative threshold. Join hits to traces in observability.
- Provider variance. TTFT and tok/s vary by provider load and region and shift with model updates — track them as first-class metrics per model, and route or fall back when a provider degrades (reliability).
- Cost coupling. Most latency levers (routing, caching, shorter outputs) also cut cost; the exception is redundancy tricks like hedged/parallel duplicate requests, which buy tail latency with money (cost).
- Perceived latency beyond streaming: optimistic UI states ("Searching your documents…"), retrieval results shown before generation, skeleton states. Users tolerate seconds when something visibly progresses.
Test yourself
p50 latency is 1.2 s and users still complain the app feels slow. What are you probably measuring wrong?
Your team wants to spend a sprint tuning HNSW parameters because vector search is 45 ms. Decode is 5 s. How do you respond?
Semantic cache hit rate is 40% after launch, then quality complaints spike. Diagnose without looking at the model.
Why does prompt caching improve TTFT, not only cost?
You must cut end-to-end latency 2× without changing the frontier model for hard queries. What stack of changes do you ship first?
Go deeper
- Latency optimization — OpenAI docs — the seven principles: fewer tokens, fewer requests, parallelize, make waiting feel shorter.
- Prompt caching — Anthropic docs — mechanics, minimum cacheable lengths, TTLs, cache-warming with
max_tokens: 0. - Redis LangCache docs — managed semantic caching: thresholds, TTLs, eviction, hit-rate monitoring.
- LLM Inference Performance Engineering: Best Practices — Databricks — TTFT/TPOT, batching, why decode is memory-bandwidth-bound.
- Fast LLM Serving with vLLM and PagedAttention — Anyscale — the canonical talk on PagedAttention and continuous batching, by vLLM's authors.
Where this connects
- Cost — the same levers (caching, routing, shorter outputs) usually cut spend; learn the unit economics next.
- Reliability — timeouts, retries, and fallbacks are how you cap the latency tail when a provider stalls.
- Observability — per-stage spans and TTFT metrics are how the ledger becomes visible in production.
- Choosing models — routing and cascade only work if you know which models are fast enough for each tier.