Runbooks/ LLM Inference Runbook Track A · From text to numbers Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Tokens
  4. 02 Anatomy
  5. 03 Journey
  6. /
  7. 04 Hardware
  8. 05 Phases
  9. 06 KV cache
  10. /
  11. 07 Attention
  12. 08 Position
  13. 09 Precision
  14. 10 Cache ops
  15. /
  16. 11 Many GPUs
  17. 12 Engines
  18. 13 Decode loop
  19. /
  20. 14 Planning
  21. 15 Production
Document 03 · Quick learn A stopwatch on each · 95% is one loop · Symptom → stage

One requesttwelve stages

One request, twelve stages, a stopwatch on each. Ninety-five per cent of the time is in one loop — and knowing which stage a symptom belongs to is most of the answer to most serving questions.

“The walking is the job. Everything else is rounding.”
1,883 ms
total, 2k prompt → 300 tokens
89 ms
time to first token
95%
of the wall clock is decode
0.14%
is every CPU stage put together
One request, one clocktwelve stages, a stopwatch on each

Documents 01 and 02 took the machinery apart. This one puts it back together and runs it once. Almost every serving question is really the question which stage? — and you cannot answer that from a list of components.

CPU front end

Parse, template, tokenise, admit, allocate.

2.5 ms · 0.14% of the request
Prefill

2,000 positions through 32 layers in one pass. 34.2 TFLOP.

86.4 ms · 4.6% of the request
The decode loop

299 more passes, each reading all 16 GB of weights.

1,794 ms · 95% of the request
The single most useful fact in this document

For a typical chat request on the reference stack, 95% of the wall clock is the decode loop. Prefill is under 5%. Every CPU stage put together is 0.14%.

Once you have internalised that distribution, the rest of the runbook stops looking like a grab-bag of techniques and starts looking like what it is: a sustained assault on one loop
The analogy, carried forward

A waiter takes the order and writes it up — the CPU front end, seconds out of an hour-long meal. The chef reads the whole order at once and starts the dish: that is prefill, one big efficient burst. Then, for every single mouthful, the chef walks the length of the restaurant to fetch the entire recipe book, reads one line, and walks back.

That is decode, three hundred times. The walking is the job. Everything else is rounding
The twelve stageswatch the right-hand column, not the stage names
1
HTTP request landsCPU · JSON parsed, sampling parameters validated
0.2 ms
2
Chat template appliedCPU · messages rendered to one flat string
0.1 ms
3
TokenisedCPU · 9,000 characters become 2,000 integer ids
~2 ms
4
Scheduler admits itCPU · free KV blocks and batch slots. On a busy server you queue here
0.1 ms
5
KV blocks allocatedCPU · 125 blocks of 16 tokens, on demand, not contiguous
0.1 ms
6
Embedding lookupGPU · 2,000 rows gathered. No arithmetic at all
0.1 ms
7
PrefillGPU · all 2,000 positions, all 32 layers, 34.2 TFLOP, 0.24 GiB of cache written
86.4 ms
8
Final norm and headGPU · last position only — the other 1,999 are already known
0.1 ms
9
Sample token 1GPU · 128,256 logits, masks, temperature, top-p, one id drawn
0.2 ms
10
Detokenise and emitCPU · bytes buffered, valid UTF-8 streamed. TTFT = 89 ms
0.2 ms
11
Decode × 299GPU · one full pass per token at 6.0 ms, all 16 GB read every time
1,794 ms
12
End of turnCPU · stop token emitted, 125 blocks returned to the pool
0.1 ms
Worked2.5 ms CPU + 86.6 ms prefill + 1,794 ms decode = 1,883 ms total · TTFT 89 ms2,000-token prompt, 300 output tokens

What bounds each stage, and what makes it worse

1–5 · Front end

Bounded by single-core speed, and whether tokenising shares a thread with the scheduler.

Worse with: huge JSON payloads, hand-built templates, high request rate
4 · Admission

Bounded by free KV blocks and batch slots.

A pure wait, and it is invisible in every GPU metric
7 · Prefill

Bounded by arithmetic — 2NP plus a term in P².

Worse with: long prompts, no prefix reuse, someone’s 50k-token paste
8–9 · Head and sample

One matrix multiply and a sort over 128,256 values.

Worse with: very large vocabularies, complex grammar constraints
10 · Stream out

Per-token Python work — 12,000 times a second at peak.

Driven by concurrency, not prompt size. This one surprises people
11 · The decode loop

Memory bandwidth, until the batch gets large.

Worse with output length, linearly. Everything else is secondary
6 · Embedding

Nothing meaningful — a gather of a few megabytes.

Ignore it
12 · Teardown

Nothing.

Unless blocks are kept for prefix reuse, which is a choice
Why the second token is a different animalidentical code, identical weights, two thousand times apart
Token 1

Out of prefill: 2,000 positions, 34.2 TFLOP.

Token 2

Out of decode: exactly one position, 0.015 TFLOP.

Same weights read

16.06 GB, both times.

The ratio

Prefill: 8 MB of traffic per token. Decode: 16 GB.

The only fix

More tokens per read — batching, or guessing ahead.

The chain to be able to say in one breath

“Decode reads every weight to produce one token, so it is bandwidth-bound rather than compute-bound. The fix is a bigger batch — read the weights once, serve many people with them. The batch is capped by KV cache memory. So shrink the cache. Grouped-query attention, paged memory, fp8 caches, prefix reuse and latent attention are five different answers to that one last step.”

Forty words, and it correctly frames almost any serving question before you have answered it
The same request, three contextsand only one of them is what you measured

The journey above was a cold request on an idle machine. Neither condition holds in production, and both change the answer substantially.

Cold, idle server

Nothing queued, no neighbours, nothing cached.

TTFT 89 ms · the number in the blog post
Warm, prefix cached

1,900 of 2,000 tokens already computed.

Prefill 86 → 4 ms · but total moves ~4%
Loaded, real traffic

Queue time, interference from chunked prefill, a longer batch.

TTFT 600 ms · and nobody lied
The number people quote, and the number that is true

Benchmark posts, vendor claims and internal demos almost always measure the first panel. It is the easiest measurement to take and the least useful one to have.

If you are shown a latency figure, ask four things: at what request rate, with what concurrency, at which percentile, and with prefix caching on or off? Any figure missing those four is a description of a configuration, not a measurement of a system
Reading a symptom back to a stagewhat the journey is actually for

“The first word takes forever”

That is TTFT — queue + CPU front end + prefill. Three terms.

  • Is prefill time fine but TTFT bad? Then it is queue — you are over capacity
  • Is prefill itself slow? Long prompts, no prefix reuse, no chunking
  • Is it only some requests? Someone is pasting a very large document
  • Fixes: admission limits and hardware · or prefix caching and chunked prefill
Two questions.
First token, or streaming?
Everyone, or this request?

“The text streams slowly”

That is the decode loop — which rules out four stages in one move.

  • Rises with batch size? You are past the ridge point (~150). Fix admission, not code
  • Rises with sequence position? It is the cache read — fp8 cache, fewer KV heads, lower limit
  • Flat in both, and GPU utilisation low? It is the CPU stream path
  • The measurement: plot ITL against batch, and against position

The four timers that turn “the model is slow” into a lookup

Queue time

Pure waiting. Invisible in every GPU dashboard.

Prefill time

Separate from TTFT, or you cannot tell queue from prefill.

Time to first token

What the user feels as the pause.

Inter-token latency

What the user feels as streaming speed.

Plus four gauges

KV cache utilisation · queue depth · running batch size · preemption count.

The signature to alarm on

Cache utilisation pinned at 100% with a growing queue means you admitted more than you can hold. Preemption count confirms it.

All at p50, p95 and p99

In LLM serving the tail is much worse than the middle — one person pasting a large document delays everyone behind them.

That is nine numbers

It fits on one dashboard, it is cheap to emit, and it turns “the model is slow” from an opinion into a lookup.

Answer it out loudthe ninety-second version, then seven questions
“A request has a CPU front end, a prefill, and a decode loop. The front end parses, applies the chat template, tokenises and asks for admission — a couple of milliseconds when idle, and where queueing shows up when not. Prefill pushes the whole prompt through every layer in one compute-bound pass, fills the cache and produces exactly one token; that plus the queue is time to first token. Then the decode loop runs once per output token, each pass reading every weight in the model plus the entire cache, which makes it bandwidth-bound. On the reference stack that is 2.5 ms of CPU, 86 ms of prefill and 1,794 ms of decode — 95% of the request is the loop.”

Seven questions, and the spine of each answer

Architect

Walk me through what happens when a user sends a prompt.

  • Twelve stages in three groups: CPU front end, prefill, decode loop
  • Front end: parse, template, tokenise, admit, allocate — ~2.5 ms idle, and where queueing lives
  • Prefill: whole prompt, one parallel pass, compute-bound, ~86 ms, produces one token
  • Decode: one pass per token, 6 ms each, 300 of them = 1.8 s
So when someone says the model is slow, ask whether they mean the pause or the streaming — two subsystems, opposite bottlenecks, opposite fixes.
Architect

TTFT is fine but the text streams slowly.

  • That isolates it to the decode loop — rules out tokenising, templating, admission and prefill in one move
  • Batch size: past the ridge point (~150) every extra sequence genuinely slows everyone. Fix admission, not code
  • Sequence length: the cache is read every step and grows every step
  • The CPU: the stream path runs once per token per user
The measurement that separates them: plot ITL against batch size and against sequence position.
Eng manager

A demo showed 89 ms TTFT. Production sees 600. Is someone lying?

  • No — they measured a different thing. The demo is a cold request on an idle server
  • Production adds queue time, interference from interleaved chunked prefill, and a longer batch
  • Queue time appears in no GPU metric
Do not chase the 89. Instrument queue and prefill separately, then decide which you are paying. Queue → capacity or admission limits. Prefill → prefix caching and chunking.
Architect

Why does prefill produce only one token from two thousand positions?

  • Because the other 1,999 are the prompt — we already know what those tokens are
  • Their pass exists to compute and store keys and values, not to predict them
  • Only the last position has a prediction we do not already have
Nice consequence: the head runs on one position. All 8,192 would be a 3.9 GiB tensor — which is why log-probs on prompt tokens are genuinely expensive.
Architect

Prefix caching gave 13× on TTFT but users say it feels the same.

  • Because TTFT was never the dominant term — prefill is under 5% of the wall clock
  • 86 ms → 4 ms is a real 13× on that stage, and moves total request time by ~4%
  • Users perceive the total
It is not a bad change — the value showed up as freed prefill capacity, which is a throughput and cost win. If the goal was felt speed, the levers are in the decode loop.
Eng manager

How would you instrument this so the team can debug without you?

  • Four timers, emitted separately: queue, prefill, TTFT, inter-token latency
  • Four gauges: cache utilisation, queue depth, running batch size, preemption count
  • Everything at p50, p95 and p99
Nine numbers. One dashboard. Two of the four timers are scheduling rather than GPU work, so they are invisible in every hardware dashboard — which is exactly why they must be emitted.
Architect

Nine kilobytes goes in and four bytes come out. Where does the memory go?

  • Two places, and only one persists
  • Transient: activations — 2,000 × 4,096 is 16 MB of residual stream, freed when the pass ends
  • Persistent: the KV cache — 0.24 GiB, held for the life of the request, growing 128 KiB per token
That cache line is the only thing in the whole journey that is per-user, persistent and growing — which is why it caps concurrency and why the rest of the runbook is about shrinking it.
Fast answersthe ten that come up most

QWhy is the first token slower than the rest?

The whole prompt has to be processed and the cache filled before any prediction exists. That is prefill, and it scales with prompt length. A long prompt means a long wait for the first word and no change at all to streaming speed afterwards.

QCan the model generate several tokens at once?

Not honestly — each token depends on the one before it. But you can guess several and verify them in one pass, which is speculative decoding and is genuinely lossless. It works because verification is prefill-shaped: checking four costs almost the same as checking one.

QDoes a longer conversation get slower?

Gradually. The cache grows every token and is read every step alongside the weights. On the reference stack, 16 GB of weights dwarfs a 0.24 GiB cache. At 128k the cache is 16 GiB against 16 GB of weights — now it is half the read, and per-token latency has roughly doubled.

QContext window vs cache?

The window is a limit; the cache is memory in use right now. With paging, a user who has sent 500 tokens holds about 32 blocks, not 8,192. That gap is why worst-case sizing gives 53 users and observed capacity is 191.

QDoes batching make my individual answer arrive faster?

No, and saying so plainly is the right answer. Batching serves far more people at roughly the same speed; your own tokens arrive slightly slower in a large batch. It buys throughput, not single-user latency — the same dial, and the product decides where to set it.

QIf someone pastes an enormous document, does that block everyone?

Without chunked prefill, yes — a 50,000-token prefill occupies the GPU for seconds and every other stream stops dead. With it, the prefill is sliced and interleaved with decode steps. On by default in modern servers; confirm it rather than assume it.

QWhere does the time go for a very short answer?

It inverts. At 20 output tokens on a 2,000-token prompt, prefill is about three-quarters of the request. Classification and extraction live here and want the opposite configuration to chat: maximise prefill throughput, skip speculative decoding, push the batch hard.

QIs queue time really invisible?

In GPU metrics, yes — the card is busy doing other people’s work, so utilisation looks healthy while your request sits in a list. A system where TTFT is bad and prefill time is fine has a queue problem, and no amount of GPU tuning will touch it.

QWhy is the embedding lookup free but the output head expensive?

Same size — 128,256 × 4,096 — completely different operations. The embedding gathers one row per token, a few kilobytes. The head is a full matrix multiply against the whole table, ~1.05 GFLOP, on every single step.

QWhat happens to the cache when the request finishes?

The blocks return to the pool — unless the engine keeps them for prefix reuse, which is what SGLang’s radix tree and vLLM’s prefix caching do. A straight memory-for-latency trade, worth taking on any workload with a shared system prompt.

Ninety-five per cent of a chat request is one loop. Prefill is under 5%; every CPU stage together is 0.14%. Everything in tracks C and D — grouped-query attention, quantisation, paging, prefix reuse, speculation — is a sustained assault on that one loop. And for a 20-token answer the whole distribution inverts, which is why “make the LLM faster” has no answer until someone says which workload.

Decode loop · 95%
Prefill · 4.6%
CPU
Where this goes next

Questions to ask them

Are queue time and prefill time measured separately, or only as one TTFT number?

What fraction of a typical request is decode, on your actual traffic?

Is chunked prefill on? What happens today when someone pastes a very large document?

What is the p99 prompt length, and has anyone looked at what it does to the tail?

Was the latency figure in the design doc measured on an idle server?

← 02 · Anatomy Read the full chapter → 04 · Hardware →