Runbooks/LLM Inference RunbookTrack A · From text to numbersRAG Runbook →0%
  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
LLM Inference Runbook · Document 03 of 15 · Track A — From text to numbers

Track A · Document 03 · From text to numbers

The Journey of a Token, End to End

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.

Reads in about 25 minutes · 6 figures, one a live latency budget · 7 interview questions · prints to clean A4

What is in this document

  1. One request, one clock
  2. The twelve stages
  3. Where the milliseconds go
  4. Why the second token is different
  5. The same request, three contexts
  6. Reading a symptom back to a stage
  7. Interview questions
  8. FAQ
  9. Cheat sheet

1 · One request, one clock

Documents 01 and 02 took the machinery apart. This one puts it back together and runs it once, with a stopwatch on every stage.

It exists because almost every serving question is really the question which stage? — and you cannot answer that from a list of components. You need the shape of the whole thing, with the timings attached, so that “it got slower” becomes “stage 4 or stage 7, and here is how to tell which”.

The single most useful fact in this document

For a typical chat request on the reference stack, 95% of the wall-clock time 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

The kitchen from document 00. A waiter takes the order and writes it up — that is the CPU front end, and it takes seconds out of a meal that takes an hour. 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.

2 · The twelve stages

Step through it. Watch the clock column on the right rather than the stage names — the distribution is the point, and it is not what most people expect.

ONE REQUEST · 2,000-TOKEN PROMPT · 300-TOKEN ANSWER · LLAMA 3.1 8B ON AN IDLE H100 this stage clock, ms 1HTTP request landsCPUJSON parsed into a message list, sampling parameters validated0.20.22Chat template appliedCPUmessages rendered to one flat string with reserved role tokens0.10.33TokenisedCPU9,000 characters become 2,000 integer ids2.02.34Scheduler admits itCPUchecks free KV blocks and batch slots — here is where you queue0.12.45KV blocks allocatedCPU125 blocks of 16 tokens, reserved on demand and not contiguous0.12.56Embedding lookupGPU2,000 rows gathered from the 128,256 × 4,096 table — 16 MB0.12.67PREFILL — 32 layersGPUall 2,000 positions in parallel · 34.2 TFLOP · fills 0.24 GiB of cache86.489.08Final norm and LM headGPUapplied to the last position only — the other 1,999 are already known0.189.19Sample token 1GPU128,256 logits → masks → temperature → top-p → one id0.289.310Detokenise, first chunk outCPUbytes buffered, valid UTF-8 emitted as a server-sent event0.289.511Decode × 299 moreGPUone full pass per token at 6.0 ms, reading all 16 GB each time1794188312End of turn, blocks freedCPUstop token emitted; 125 blocks return to the pool, or are kept0.11883 TIME TO FIRST TOKEN = 89 ms, at stage 10. Stages 1–10 happen once; stage 11 happens 299 more times. 1,883 ms total · of which 1,794 ms — 95% — is the decode loop Prefill was 4.6%. Every CPU stage put together was 2.7 ms, or 0.14% of the request. That single distribution is why almost every technique in tracks C and D of this runbook is aimed at one loop.
  1. HTTP request lands (CPU, 0.2 ms). JSON parsed into a message list; sampling parameters validated.
  2. Chat template applied (CPU, 0.1 ms). Messages rendered to one flat string with reserved role tokens.
  3. Tokenised (CPU, ~2 ms). 9,000 characters become 2,000 integer ids.
  4. Scheduler admits it (CPU, 0.1 ms). Checks free KV blocks and batch slots. On a busy server, this is where you queue.
  5. KV blocks allocated (CPU, 0.1 ms). 125 blocks of 16 tokens, on demand and not contiguous.
  6. Embedding lookup (GPU, 0.1 ms). 2,000 rows gathered from the 128,256 × 4,096 table. No arithmetic.
  7. Prefill (GPU, 86.4 ms). All 2,000 positions through all 32 layers in parallel — 34.2 TFLOP, filling 0.24 GiB of cache.
  8. Final norm and output head (GPU, 0.1 ms). Applied to the last position only; the other 1,999 are already known.
  9. Sample token 1 (GPU, 0.2 ms). 128,256 logits, then masks, temperature, top-p, and one id is drawn.
  10. Detokenise and emit (CPU, 0.2 ms). Bytes buffered, valid UTF-8 sent as a stream chunk. Time to first token: 89 ms.
  11. Decode × 299 more (GPU, 1,794 ms). One full pass per token at 6.0 ms each, reading all 16 GB of weights every time.
  12. End of turn (CPU, 0.1 ms). The stop token is emitted and the 125 blocks return to the pool — or are kept for reuse.

Read the clock column, not the stage names. 1,883 ms total, of which the decode loop is 1,794 — ninety-five per cent. Prefill is under five per cent and every CPU stage put together is 0.14%. This single distribution is why the rest of the runbook looks the way it does.

StageWhereWhat it is bounded byWhat makes it worse
1–5 · front endCPUSingle-core speed, and whether tokenising shares a thread with the schedulerHuge JSON payloads, hand-built templates, high request rate
4 · admissionCPUFree KV blocks and batch slotsLoad. This is a pure wait and it is invisible in every GPU metric
6 · embeddingGPUNothing meaningful — it is a gather of a few megabytesNothing. Ignore it
7 · prefillGPUArithmetic. 2×N×P plus a term in P²Long prompts, no prefix reuse, someone else’s 50k-token paste
8–9 · head and sampleGPUOne matrix multiply and a sort over 128,256 valuesVery large vocabularies; complex grammar constraints
10 · stream outCPUPer-token Python work, 12,000 times a second at peakConcurrency, not prompt size. This is the one that surprises people
11 · the decode loopGPUMemory bandwidth, until the batch gets largeOutput length — linearly. Everything else is secondary
12 · teardownCPUNothingNothing, unless blocks are being kept for prefix reuse, which is a choice

3 · Where the milliseconds go

The same twelve stages as a budget you can drive. The two sliders that matter are output length, which moves the total almost one-for-one, and the number of other sequences decoding alongside you, which does almost nothing until it suddenly does.

WHERE THE MILLISECONDS GO · H100, 40% OF PEAK COMPUTE, 80% OF PEAK BANDWIDTH CPU, both directions 2.7 ms template, tokenise, schedule, allocate, detokenise, stream queue 0 ms invisible in a GPU profile, and frequently the largest term under load prefill 86 ms 2 × 8.03B × 2,000 plus the quadratic attention term, at 40% of peak TIME TO FIRST TOKEN 89 ms against a 1,000 ms budget — 9% used per decode step 6.0 ms memory-bound: 16.06 GB of weights at 2.68 TB/s achieved decode, all 299 1,794 ms 95% of the whole request TOTAL 1,883 ms 160 tokens/second for this user · the batch is 1, so the card is almost idle

Push the batch slider. Per-step time barely moves until about 150 other sequences are decoding alongside you — because up to that point the GPU is waiting on memory and your neighbours are free. Past it, every extra sequence costs everyone real time. That crossover is the ridge point, and document 04 derives it.

Three readings worth doing before you move on

Set output to 20. A classification request. Now prefill is 74% of the total and the decode loop barely matters — which is why a classification workload and a chat workload want opposite configurations, and why “make the LLM faster” has no answer until someone says which one you have.

Set the prompt to 32,768. Prefill goes to 2.8 seconds and blows the one-second budget on its own, because the quadratic attention term is now half the work. No amount of decode tuning saves this; you need prefix reuse, chunking, or a shorter prompt.

Push the batch slider past 150. Per-step time starts climbing. Up to that point your neighbours were free, because the GPU was waiting on memory anyway. That crossover is the ridge point, it is derived in document 04, and knowing roughly where it sits for your model is the difference between a capacity plan and a guess.

4 · Why the second token is a different animal

Token 1 and token 2 run identical code through identical weights. The economics are two thousand times apart.

TOKEN 2 RUNS THE SAME 32 LAYERS AS TOKEN 1. ALMOST NOTHING ELSE IS THE SAME. TOKEN 1 · PREFILL 2,000 positions enter the stack together 34.2 TFLOP of arithmetic against one read of the weights TOKEN 2 · DECODE exactly one position enters the stack 0.015 TFLOP against the same one read of the weights the weights read is identical — 16.06 GB, every step, forever 16.06 GB read ÷ 2,000 tokens produced = 8 MB per token 16.06 GB read ÷ 1 token produced = 16 GB per token the cache: prefill writes 2,000 entries, decode appends exactly one — and then re-reads all of them writes 0.24 GiB of keys and values appends 128 KiB, reads the whole 0.24 GiB back And that is the whole problem. Prefill amortises one read of the weights over 2,000 tokens. Decode amortises it over one. Same read, two thousand times less work extracted from it. The only fix is more tokens per read. Either from other users — that is batching — or from the same user, by guessing ahead, which is speculative decoding in document 13.
  1. Token 1 comes out of prefill: 2,000 positions enter the stack together, 34.2 TFLOP of arithmetic. Token 2 comes out of decode: exactly one position, 0.015 TFLOP.
  2. The weights read is identical in both cases — 16.06 GB. Prefill gets 8 MB of traffic per token produced; decode gets 16 GB.
  3. Prefill writes 2,000 cache entries. Decode appends one, and re-reads all of them.
  4. Prefill amortises one read of the weights over 2,000 tokens; decode amortises it over one. Same read, two thousand times less work extracted.
  5. The only fix is more tokens per read — from other users (batching) or from the same user by guessing ahead (speculative decoding).

If you take one thing from this document, take this figure. Everything in tracks C and D is an attempt to change one of the two numbers in step 2.

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.

WHAT THE DATA ACTUALLY IS, STAGE BY STAGE · 2,000-TOKEN PROMPT text 9,000 chars 9 kB of UTF-8 token ids 2,000 integers 8 kB — smaller than the text it came from embedded 2,000 × 4,096 floats 16.4 MB — a 2,000× expansion, and this is the last time size grows in the stack 2,000 × 4,096, unchanged the residual stream keeps exactly this shape through all 32 layers left behind 0.24 GiB of KV cache 32 layers × 2,000 tokens × 4 KiB — and it stays until the request ends logits 1 × 128,256 floats 0.49 MiB — for the last position only; all 2,000 would be 0.98 GiB out 1 id 4 bytes, which becomes two or three characters of text

Notice where the size goes. Nine kilobytes of text becomes sixteen megabytes of activations and leaves a quarter of a gigabyte of cache behind — and produces four bytes. The KV cache row is the only one that persists after the pass; every other row is scratch space that is freed immediately.

5 · The same request, three contexts

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

COLD REQUEST, IDLE SERVER — THE BASELINE Nothing cached, nothing queued, no neighbours. This is the number a demo shows you and the number production almost never sees. prefill 2,000 tokens — 86 ms decode — 1,794 ms TTFT 89 ms · total 1,883 ms · 160 tok/s for this user The card is almost entirely idle: one sequence decoding means one FLOP per byte read, against hardware that wants 295. You are paying for a whole H100 to move 16 GB back and forth 300 times. Excellent latency, catastrophic economics — and both facts have the same cause. PREFIX CACHE HIT — TURN 8 OF A CHAT 1,900 of the 2,000 prompt tokens were computed on an earlier turn and their keys and values are still in memory. Only 100 tokens are new. prefill 100 tokens — 4 ms decode — 1,794 ms, unchanged TTFT 7 ms · total 1,801 ms · a 13× improvement in TTFT and 4% in total Read those two numbers together, because people quote the wrong one. Prefix caching is a time-to-first-token lever and a throughput lever — it frees prefill capacity for other users. It does almost nothing for the total time of a long generation, because decode was always the 95%. UNDER LOAD — 150 OTHER SEQUENCES, A QUEUE, AND SOMEONE ELSE’S HUGE PROMPT Now three new terms appear, and two of them are invisible in a GPU profile. queue 120 ms prefill 86 chunk waits your prefill is sliced and interleaved with everyone’s decode steps, so it takes longer in wall-clock decode — 1,830 ms TTFT 260 ms · total 2,090 ms · but the card is now doing 24,000 tok/s instead of 160 This is the trade the whole field is built on: your request got 11% slower and the machine got 150 times more productive. Latency and throughput are the same dial, and deciding where to set it is a product question before it is an engineering one — document 15.

The same request, three contexts, three completely different answers. When someone quotes a latency figure, the first question is which of these three they measured — and the honest answer for a production system is always the third.

The number people quote, and the number that is true

Benchmark blog posts, vendor claims and internal demos almost always measure the first panel: one request, empty server, nothing cached. It is the easiest measurement to take and the least useful one to have.

If you are shown a latency figure, the questions are: 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 rather than a measurement of a system. Document 15 turns that into a benchmarking protocol.

6 · Reading a symptom back to a stage

This is what the journey is for. Not to recite, but to localise.

A SYMPTOM IS USELESS UNTIL YOU KNOW WHICH STAGE IT BELONGS TO 1–5 · CPU front end template, tokenise, admit 4 · the queue admission, and waiting for blocks 7 · prefill compute-bound, quadratic at length 11 · the decode loop bandwidth, batch, cache size and the diagnostic is always the same two questions, in this order 1 · IS IT THE FIRST TOKEN OR THE STREAMING? TTFT bad → stages 1 to 7. Inter-token bad → stage 11. Different worlds. 2 · IS IT EVERYONE, OR THIS REQUEST? Everyone → load, queue, a neighbour’s prompt. One → its own length. THE TRAP Two of the four columns above are CPU or scheduling, and neither shows up in GPU utilisation. A server can sit at 55% utilisation with users waiting, and the cause is a saturated core or an admission policy. Always instrument queue time separately from prefill time — a single TTFT number hides which one you have.

This figure is the reason the journey is worth memorising. “It is slow” is not a bug report; “TTFT p95 moved and inter-token latency did not” names four stages and eliminates eight.

SymptomMost likely stageWhat to check first
TTFT p95 climbed, streaming speed unchanged 4 (queue) or 7 (prefill) Queue time and prefill time, instrumented separately. If it is queue, you are over capacity; if it is prefill, prompts got longer or prefix reuse stopped working
Streaming got slower, TTFT unchanged 11 (decode) Batch size and cache utilisation. Either the batch is past the ridge point, or sequences got long enough that cache reads dominate
Both climbed, GPU utilisation is 55% 1–5 or 10 (CPU) Profile the server process, not the GPU. The detokenise-and-stream path runs once per token per user
Throughput fell and latency spiked together 4, via preemption Preemption count. The scheduler is evicting and recomputing; the fix is admission control, not tuning
One tenant is slow, everyone else is fine 7 Their prompt length distribution. A single 50k-token paste stalls every stream unless chunked prefill is on
TTFT is bimodal — fast or very slow, nothing between 7, with prefix caching Cache hit rate. You are seeing hits and misses, and something is invalidating prefixes — often a timestamp at the top of the system prompt
Answers are worse; no metric moved at all 2 (the chat template) Log the rendered prompt and read it. Document 01 — this one produces no error and no log line

7 · Interview questions

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

Twelve stages, but they fall into three groups. First a CPU front end: parse the request, apply the model’s chat template to turn the message list into one flat string, tokenise it, and ask the scheduler for admission and KV cache blocks. That is a couple of milliseconds when the server is idle, and it is where queueing happens when it is not.

Then prefill: the whole prompt through all 32 layers in one parallel pass. It is compute-bound, it fills the cache, and it produces exactly one token. For a 2,000-token prompt on an H100 that is about 86 milliseconds, so time to first token is around 89.

Then the decode loop: one full pass through the model per output token, each one reading all 16 GB of weights plus the whole cache. At 6 milliseconds a step and 300 tokens that is 1.8 seconds — 95% of the request. So when someone says the model is slow, my first question is whether they mean the pause or the streaming, because those are two different subsystems with opposite bottlenecks and opposite fixes.

ArchitectTime to first token is fine but the text streams slowly. Where do you look?

That isolates it to the decode loop, which rules out tokenising, templating, admission and prefill in one move. Inside the loop there are three candidates and I would take them in this order.

First, batch size — are we past the ridge point? Up to roughly 150 concurrent sequences on an H100 the GPU is waiting on memory and extra users are nearly free; past it every extra sequence genuinely slows everyone. If we are past it, the fix is admission control, not optimisation. Second, sequence length: the cache is read every step and grows every step, so long conversations get slower on their own. Third, the CPU — the detokenise-and-stream path runs once per token per user, and at high concurrency a saturated core looks exactly like a slow GPU.

The measurement that separates them is inter-token latency plotted against batch size and against sequence position. If it rises with batch, it is the ridge point; if it rises with position, it is the cache; if it is flat in both and GPU utilisation is low, it is the CPU.

Eng managerA demo showed 89 ms time to first token. Production is seeing 600. Is someone lying?

No, they measured a different thing, and it is worth being precise about what changed rather than treating it as a discrepancy. The demo number is a cold request on an idle server: nothing queued, no neighbours, nothing cached. Production adds three terms. Queue time, which is pure waiting and appears in no GPU metric. Interference, because a chunked prefill is interleaved with everyone’s decode steps and therefore takes longer in wall-clock even though it does the same work. And prompt-length variance — the p95 prompt is four times the p50 on our traffic, and prefill scales with length.

So the useful response is not to chase the 89. It is to instrument queue time and prefill time separately, then decide which one we are actually paying. If it is queue, we are over capacity and the answer is hardware or admission limits. If it is prefill, the answer is prefix caching and chunked prefill. Those are different budgets and different teams, which is exactly why I want them measured apart before anyone commits to a number.

ArchitectWhy does prefill produce only one token when it processes two thousand positions?

Because the other 1,999 positions are the prompt — we already know what those tokens are. Their pass through the model is not there to predict them; it is there to compute and store their keys and values, so that every future token can attend to them without recomputing. The only position whose prediction we do not already have is the last one.

There is a nice consequence: the output head is applied to one position rather than all 2,000. If you computed logits for every position on an 8,192-token prefill the tensor would be 3.9 GiB, because 128,256 scores per position in fp32 is half a megabyte each. That is also why requesting log-probabilities for prompt tokens is genuinely expensive — you are asking the server to materialise the thing it deliberately avoids.

ArchitectPrefix caching gave us a 13× improvement in TTFT but the users say it feels the same. Why?

Because TTFT was never the dominant term. On a 300-token answer, prefill is under 5% of the wall clock and decode is 95%. Cutting an 86-millisecond prefill to 4 milliseconds is a real and worthwhile 13× on that stage, and it moves total request time by about four per cent. Users perceive the total.

That does not make it a bad change, and I would push back on anyone calling it one — it is just that the value showed up somewhere else. Prefix caching frees prefill capacity, which raises how many requests the fleet can accept, which is a throughput and cost win rather than a felt-latency win. If the goal was perceived speed, the levers are in the decode loop: speculative decoding at low batch, a smaller cache, quantised weights. I would want that distinction written into the ticket before the work starts, because “make it feel faster” and “make it cost less” point at different halves of this document.

Eng managerHow would you instrument this so the team can debug it without you?

Four timers per request, emitted as separate metrics rather than one latency number: queue time, prefill time, time to first token, and inter-token latency. That split is the whole game, because it maps a symptom onto a stage — and two of the four are scheduling rather than GPU work, so they are invisible in every hardware dashboard.

Alongside those, four gauges: KV cache utilisation, queue depth, running batch size, and preemption count. Cache utilisation pinned at 100% with a growing queue is the signature of having admitted more than we can hold, and preemption count is the confirmation. And everything reported at p50, p95 and p99, because 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.

ArchitectNine kilobytes of text goes in and four bytes come out. Where does all the memory go?

Two places, and only one of them persists. The transient one is activations: 2,000 tokens × 4,096 dimensions is 16 MB of residual stream, plus workspace, and that is freed as soon as the pass finishes.

The one that matters is the KV cache: 32 layers × 2,000 tokens × 4 KiB per token per layer is 0.24 GiB, and it stays allocated for the entire life of the request, growing by 128 KiB with every token generated. That is the only line 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.

8 · FAQ

Why is the first token slower than the rest?

Because the whole prompt has to be processed and the cache filled before any prediction exists. That is prefill, and it scales with prompt length. After that, each token is a much smaller job. A long prompt means a long wait for the first word and no change at all to the streaming speed afterwards — which is a useful thing to explain to a product owner who wants “faster”.

Can 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 when done with the published acceptance rule. That is document 13, and it works precisely because verification is prefill-shaped: checking four tokens costs almost the same as checking one.

Does a longer conversation get slower?

Gradually, yes. The cache grows with every token and is read at every step alongside the weights. On the reference stack, 16 GB of weights dwarfs a 0.24 GiB cache, so you barely notice early on. At 128k context the cache is 16 GiB against 16 GB of weights — now it is half the read, and per-token latency has roughly doubled.

What is the difference between the context window and the cache?

The window is a limit — the maximum tokens the model will accept. The cache is memory actually in use right now, proportional to tokens actually used. With paging, a user who has sent 500 tokens holds about 32 blocks, not the full 8,192. That gap is why worst-case capacity sizing gives 53 users on the reference stack and observed capacity is 191.

Does 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 very slightly slower in a large batch, and noticeably slower past the ridge point. It buys throughput, not single-user latency. The honest framing is that latency and throughput are the same dial and the product decides where to set it.

What if 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 user’s stream stops dead. With it, the prefill is sliced and the slices are interleaved with decode steps. The big request gets slightly slower and nobody else stalls. It is on by default in modern servers and you should confirm it rather than assume it — document 15.

Where 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 and the decode loop is a minor term. Classification and extraction workloads live here, and they want the opposite configuration to chat: maximise prefill throughput, do not bother with speculative decoding, and push the batch hard because nobody is watching text stream.

Is queue time really invisible?

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. It is visible in the engine’s own metrics if you export them, and it is one of the four timers worth emitting separately. A system where TTFT is bad and prefill time is fine has a queue problem, and no amount of GPU tuning will touch it.

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

They are the same size — 128,256 × 4,096 — and completely different operations. The embedding gathers one row per token, a few kilobytes. The output head is a full matrix multiply against the whole table, about 1.05 GFLOP and 7% of the forward pass, on every single step. Same weights, opposite runtime profiles.

What 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. Then they stay until evicted, so a later request beginning with the same tokens skips that part of prefill entirely. Document 10. It is a straight memory-for-latency trade and worth taking on any workload with a shared system prompt.

9 · Cheat sheet

the twelve stages parse · template · tokenise · admit · allocate · embed · prefill · head · sample · stream · decode × N · free
the split, one chat request CPU 0.14% · prefill 4.6% · decode 95%. For a 20-token answer it inverts: prefill 74%
TTFT queue + CPU front end + prefill. Three terms, and in production the first is often the largest
prefill compute-bound · 2NP + 4LP²d · 86 ms for 2,000 tokens at 40% of peak · produces exactly one token
decode bandwidth-bound · reads all 16 GB of weights plus the whole cache, per token · 6.0 ms a step at 80% achieved bandwidth
the asymmetry prefill amortises one weight read over 2,000 tokens; decode amortises it over one
what persists only the KV cache. 0.24 GiB for a 2,000-token prompt, growing 128 KiB per generated token, held until the request ends
the two diagnostic questions first token or streaming? everyone or this request? Four answers, and they localise to different stages
the four timers to emit queue · prefill · TTFT · inter-token — separately, at p50/p95/p99. Two of them never appear in a GPU metric

The ninety-second version

“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 a typical chat request that loop is ninety-five per cent of the wall clock, which is why almost every technique in serving is aimed at it. And the diagnostic that follows is one question: is the first token slow or is the streaming slow? Those are different subsystems with opposite fixes.”

Where this connects

Thread started herePicked up in
Stages 2 and 3, and the template that fails silently 01 · Tokenisation
What stages 6 to 9 do to the numbers 02 · Inside the model
The ridge point the batch slider crosses at about 150 04 · The GPU and the roofline
Stages 7 and 11 in full, and why they are opposites 05 · Prefill and decode
The 0.24 GiB left behind at stage 7 06 · The KV cache
Stage 5, and why 1,900 of 2,000 tokens can be free 10 · Paging and prefix reuse
Stage 9 in detail, and getting more than one token per pass 13 · The decode loop
Stage 4 under pressure, and the four timers 15 · Production

Questions to ask them