Scheduling per token, not per request
A fixed batch runs until its longest sequence finishes. Every sequence that ended earlier leaves its slot idle for the remainder.
Output lengths in real traffic vary by an order of magnitude, so that fraction is often below 0.3, and the wasted capacity is paid for at full price.
Continuous batching makes the scheduling decision at each decoding step instead, so a completed sequence frees its slot immediately and a queued request takes it.
What makes it possible
Decoding is a loop of identical steps, and the batch dimension is just the outer index of that step.
Nothing in the arithmetic requires the members of a batch to have started together, which is the observation the technique rests on.
That is the measured gain over static batching on realistic length distributions, and it is the single largest serving improvement available.
Memory becomes the scheduler's constraint
Each active sequence holds a growing cache, so the limit on concurrency is how many caches fit rather than how much arithmetic a step costs.
Reserving the maximum length for every request wastes most of the memory, because most requests are far shorter.
Paged attention is the fix.
Splitting the cache into small blocks allocated as the sequence grows removes that waste, the same way virtual memory removes it for processes.
It also allows sharing: two requests with the same prompt prefix can point at the same blocks, which is copy-on-write applied to attention.
One scheduling step, in order
- Check which sequences finished on the previous step and free their blocks.
- Admit waiting requests while free blocks remain.
- Run prefill for newly admitted sequences, possibly alongside decode for the rest.
- Run one decode step across every active sequence.
- Stream the produced tokens to their callers and repeat.
Step three is where the latency trade lives. A long prompt arriving mid-flight delays everyone's next token unless prefill is chunked into pieces that interleave.
What it costs
Tail latency rises with throughput.
A fuller batch means each step does more work, so every active sequence waits slightly longer per token.
Under memory pressure the scheduler may preempt a sequence entirely, dropping its cache and recomputing it later, which is invisible to the caller and expensive.
That calls for admission control.
The honest configuration exposes a target concurrency rather than accepting everything, because a server that admits every request degrades all of them equally instead of queueing some.