Simbaremuteuro explains: Prefill vs. Decode and GPU Serving Bottlenecks

Every language model answer combines a compute-bound prefill pass and a memory-bound, token-by-token decode. Simba explains why mixing them on one GPU stalls serving and compares two fixes: chunked prefill and disaggregated GPU pools.

4:02 video3 min readWatch on YouTube

Every time a language model answers a question, it does two completely different jobs. It reads the whole prompt at once, and then it writes the reply one token at a time. Simba explains that these jobs load a GPU in opposite ways, and that mixing them badly is one of the biggest reasons a model server ends up slow.

Prefill: reading the whole prompt in one pass

Prefill looks at every token in the prompt at the same time. Whether the prompt is 100 tokens or 1,000, it is a single forward pass, computed as one large matrix multiplication. Every input token feeds into that one pass, and the pass produces two things: a key-value cache entry for each token, and the first token of the reply.

Because prefill multiplies a big batch of tokens against the same weights, it does a lot of math for each byte of memory it touches. The GPU's compute cores stay busy, which is why prefill is called compute-bound.

Decode: writing the reply one token at a time

Decode does the opposite. Each step involves one token's worth of math, but the GPU still has to pull in every model weight and the entire key-value cache from memory to do it. There is almost nothing to compute and a lot to read, so decode is memory-bound.

The decode loop never changes shape. It takes the last token and the cache built so far, runs one forward pass, gets the next token, appends to the cache, and repeats. Every step depends on the one before it, so there is no way to skip ahead or run two steps at once for the same reply. The cache grows by one block each time around.

Two clocks servers measure

Servers measure the two phases separately. Time to first token is how long prefill takes, and it grows with prompt length. Time per output token is how long each decode step takes. It barely changes with prompt length but is set by how much must be read from memory at every step.

When the two collide

Put both phases on the same GPU without care and they fight. A large prefill batch can hold the GPU until it finishes, and every decode step waiting behind it stalls. A new prompt arriving mid-conversation can freeze everyone else's reply. The video stresses that this is not a bug in one phase. It is the two bottlenecks colliding.

Two architectural fixes

One fix keeps both phases on the same GPU but schedules around the collision. Sarathi-Serve splits a big prefill into small chunks and slots each chunk between ongoing decode steps, so no decode waits for an entire prefill to clear. The video reports that this bought 2.6 to 5.6 times the serving capacity of vLLM, depending on the model.

The other fix does not share the GPU at all. Disaggregated serving runs prefill and decode on separate pools of GPUs, each tuned to its own bottleneck. Against the same latency bar, the video says it served 7.4 times more requests while meeting a 12.6 times tighter deadline on over 90 percent of them.

Key takeaways

  • Every reply is two jobs: a parallel prompt read and a sequential token write.
  • Prefill is compute-bound; decode is memory-bound.
  • Time to first token reflects prefill and grows with prompt length.
  • Time per output token reflects decode and depends on memory reads per step.
  • Mixing the phases on one GPU makes decode steps wait behind large prefills.
  • Chunked prefill and separate GPU pools both turn that collision into serving capacity.
  • The fix for one slow clock is not the fix for the other.

Try it yourself

If your own serving stack feels slow, work out which clock is actually stuck: time to first token or time per output token. Measure both separately before choosing between chunked scheduling and splitting the work across separate pools.

Chapters

  1. 0:00Two Jobs in One LLM Response
  2. 0:25Prefill: Compute-Bound Parallel Prompt Reading
  3. 0:50Decode: Memory-Bound Sequential Token Generation
  4. 1:15Workload Collision: Time to First Token vs. Time per Output Token
  5. 1:40Architectural Fixes: Chunking vs. Disaggregated GPU Pools
  6. 2:05Your Turn: Diagnosing Which Clock is Stuck on Your Stack
Full transcript(auto-generated, with timestamps)

Two Jobs in One LLM Response

[0:00]Hi, I am Simba. Every time a language model answers you, it's actually doing two completely different jobs. First, it reads your entire prompt all at once. Then it writes the reply one word at a time. Those two jobs load the GPU in opposite ways, and mixing them badly is one of the biggest reasons a server ends up slow. Prefill reads your whole prompt in a single parallel pass across every

Prefill: Compute-Bound Parallel Prompt Reading

[0:25]Input token. Decode writes the reply one token at a time, each one waiting on the last. Same model, same GPU, two completely different kinds of work. Prefill looks at every token in your prompt at the same time. 100 tokens or 1,000, it's one forward pass either way, computed as one big matrix multiplication. Every input token feeds into that single

Decode: Memory-Bound Sequential Token Generation

[0:50]Pass at once, and it fans back out into two things: a KV cache entry for every token, and the first token of the reply. That shape is what decides the bottleneck. Prefill multiplies a big batch of tokens against the same weights at once. Lots of math per byte of memory it touches, so the GPU's compute cores stay busy. Decode does the opposite. One token's worth of math, but it still has

Workload Collision: Time to First Token vs. Time per Output Token

[1:16]To pull in every model weight and the entire key value cache from memory to do it. Almost nothing to compute, a lot to read. That's why prefill is called compute bound and decode is called memory bound. Decode's loop never changes shape. Take the last token and the cache built so far, run one forward pass, get the next token out, append it to the cache, and do it again.

Architectural Fixes: Chunking vs. Disaggregated GPU Pools

[1:40]Every step depends on the step before it. There's no way to skip ahead or run two steps at once for the same reply. And the cache it reads from grows by exactly one block every single time around. Servers measure these two phases separately. Time to first token is how long prefill takes. It grows with how long your prompt is. Time per output token is how long each decode step takes. It barely changes with prompt

Your Turn: Diagnosing Which Clock is Stuck on Your Stack

[2:06]Length, but it's set by how much has to be read from memory every single step. Put both phases on the same GPU without care, and they fight. A big prefill batch can hog the GPU for the time it takes to finish, and every decode step waiting behind it in the queue stalls until it's done. A new prompt showing up mid-conversation can freeze everyone else's reply. That's not a bug in one phase, it's the two bottlenecks colliding. One fix keeps them on the same GPU, but schedules around the collision. Sarath serves splits a big prefill into small chunks, and slots each chunk between ongoing decode steps instead of running it all at once. No decode ever waits for a whole prefill to clear, and that alone bought 2.6 to 5.6 times the serving capacity of vLLM, depending on the model. The other fix doesn't share the GPU at all. Dist

Serve runs prefill and decode on separate pools of GPUs entirely. Each one tuned to its own bottleneck, instead of splitting one GPU's time between two different jobs. Against the same latency bar, that served 7.4 times more requests while hitting a 12.6 times tighter deadline on over 90% of them. So, every reply is two jobs, not one. Prefill reads your prompt in one compute-bound pass. Decode writes the reply memory-bound, one token at a time. Left alone on shared hardware, they collide, and two different fixes, chunking the prefill or splitting the GPUs entirely, both turn that collision into serving capacity because they stopped treating one bottleneck like the other. Your turn. If your own serving stack feels slow, check which clock is actually stuck, time to first token or time per output token? The fix for one is not the fix for the other. Prefill versus decode. Simba for humanitarian's AI.

More from Humanitarians AI Fellows

Humanitarians AI Lyrical Literacy Project