Wiki
Advanced13 min read

Serving and latency: the batching tradeoff

Every inference server lives on a curve: bigger batches raise throughput and lower latency quality. Queueing theory, continuous batching and tail latency decide where to sit on it.

Here is the collision at the heart of inference serving. A GPU is a throughput machine, so it wants large batches: one big matrix multiply keeps thousands of lanes busy and amortises the fixed overhead of a kernel launch. But your users are a latency machine: each request wants its answer now, and a large batch means each request waits while the batch fills. Push throughput up and latency degrades; shrink the batch for latency and the GPU starves. The entire job of a serving system is to sit at a good point on that curve — or, better, to change the curve.

The tool you reach for first is batching, because it is simple and effective. Then you discover its limits, and the modern systems — continuous batching, paged attention, speculative decoding — are all attempts to keep the throughput of large batches without paying large-batch latency.

Throughput and latency are different axes

Throughput is how many requests per second the system completes; latency is how long one request takes. They are linked but not the same: a system can have high throughput and terrible latency (big batches), or low latency and terrible throughput (batch size one). An SLO is a target on both — a promise like "p99 under 200 ms at 500 req/s" — and the serving config is the search for a point that satisfies it.

Move the batch size and watch latency and throughput move in opposite directions.

Trade latency for throughput — the batch size is the dial

latency (max 461 ms) throughput (max 323 req/s)

latency

117 ms

throughput

296 /s

unit efficiency

67%

meets SLO

yes

At B = 16: 67% unit efficiency, 117 ms latency — inside the SLO.

Notice the two curves move in opposite directions: throughput is concave and saturates at 1000/t (overhead becomes negligible), while latency rises roughly linearly with B because requests wait longer at the batch. Continuous batching removes most of the fill wait for generative models by admitting and retiring requests mid-step, which is why it beat static batching for LLM serving. The model here is a first-order approximation, not a queueing simulator.

The latency budget

End-to-end latency is a sum of stages, and reasoning about it means naming each one:

L=Lnetwork+Lqueue+Lpreprocess+Lcompute+LpostprocessL = L_{\text{network}} + L_{\text{queue}} + L_{\text{preprocess}} + L_{\text{compute}} + L_{\text{postprocess}}

Batching acts on the middle two. Preprocessing overhead is roughly fixed per call, so it is amortised across the batch; compute scales with batch size but sub-linearly on a GPU, because the same weights serve the whole batch. That sub-linear compute is the only reason batching wins, and it is the reason the throughput curve in the widget flattens instead of running away.

Queueing is where the latency actually comes from under load. Little's law relates the three quantities you cannot independently choose:

L=λWL = \lambda W

where LL is the number of requests in the system, λ\lambda the arrival rate, and WW the average time each spends there. If arrivals exceed service rate, LL grows without bound and so does latency — the queue is unstable. This is why "just add a bigger batch" fails: past the throughput ceiling, extra load becomes queueing delay, not completed work.

The batching arithmetic

For a batch of size BB, with fixed per-call overhead oo and per-item compute tt:

service(B)=o+Bt,throughput(B)=Bo+Bt\text{service}(B) = o + Bt, \qquad \text{throughput}(B) = \frac{B}{o + Bt}

Throughput rises with BB and saturates at 1/t1/t (the overhead vanishes as a fraction). Waiting time to fill a batch grows roughly linearly in BB — under Poisson arrivals the mean wait is about (B−1)/(2λ)(B-1)/(2\lambda) — so latency grows while throughput stops improving. The useful region is the knee, not the asymptote. This is textbook queueing; there is no clever trick that makes latency and throughput improve together for a fixed model and hardware.

Changing the curve: continuous batching

Static batching is wasteful for generative models because requests do not finish together. A batch is limited by its longest sequence; short requests sit idle occupying memory while the long one runs. Continuous batching (iteration-level scheduling, as in Orca and vLLM) fixes this by rebuilding the batch every decoding step: finished sequences leave, queued ones join, and no slot is wasted. Paged attention attacks the memory side, storing the KV cache in non-contiguous pages so sequences of different lengths can share GPU memory without fragmentation. Together they move the curve: higher throughput at the same latency, because the batch no longer waits for its slowest member.

The same idea generalises. Speculative decoding uses a small draft model to propose tokens a large model verifies in parallel, converting idle capacity into lower latency. Every technique here is a way of buying back the gap between the hardware's ideal batch and the user's impatience.

The tail is what users feel

Mean latency is a comfortable lie. Users experience the slow requests, so serving is managed on percentiles — p50, p95, p99, p99.9. At scale the tail dominates experience and, worse, it amplifies. If one page load fans out to 100 backend calls and each has a 1% chance of being slow, the probability that at least one is slow is 1−0.99100≈63%1 - 0.99^{100} \approx 63\% — the page is slow most of the time even though every service looks healthy. The standard defences are hedged requests (send a backup after the p95 and take the winner), tied requests (cancellation so wasted work stops), and simply keeping fan-out low.

SLOs and goodput

An SLO is the contract that turns these numbers into engineering. Goodput is the rate of requests that actually meet the SLO — work that arrives too late is, for the user, work not done. Capacity planning inverts the question: rather than maximising throughput, find the load at which p99 crosses the SLO and provision headroom below it. The load-shedding question ("what do we drop first when the queue grows?") belongs in the design, decided in advance, not at 3 a.m.

Careful

Averages hide the failure. Autoscaling on mean CPU, choosing batch size by average latency, or benchmarking on a quiet machine will all look fine and still produce an SLO violation under real traffic. Measure percentiles under production-like load, and set the batch/beam/scaling policy from the tail you must meet, not the mean you would like. And beware benchmarking cold caches — the first request after a deploy is the one users remember.

Illustrative vs real

The widget uses a first-order model: latency is fill-wait plus service, the arrival process is treated as smooth, and no scheduler effects are modelled. Real serving has bursty arrivals, multiple queues, per-sequence KV-cache limits, and hardware that behaves differently at different batch shapes. The direction of every trade shown is real; the exact milliseconds are not.

Check yourself

Eduspheria wiki · Systems for AI, Serving at scale

0 / 5 answered

  1. 1Fixed overhead is 4 ms and per-item compute is 2 ms. What is the throughput (req/s) of a batch of size 20?
    req/s
    Numeric answer
  2. 2Adding replicas is the only way to improve both latency and throughput at once.
    True / false
  3. 3Which law relates the number of requests in a system, the arrival rate, and the time each spends in it?
    Short answer
  4. 4A page fans out to 100 backend calls, each slow with probability 1%. Rounded to the nearest percent, what is the chance at least one is slow?
    %
    Numeric answer
  5. 5What problem does continuous batching solve relative to static batching?
    Multiple choice

From the exam paper

Modeled on NITJ AI-619, End-Sem June 2025

0 / 6 answered

  1. 1Fixed overhead is 2 ms and per-item compute is 1 ms. What is the throughput of a batch of size 50, in requests per second?
    req/s
    Numeric answer
  2. 2Using Little's law, a system holds 40 requests and receives 500 per second. What is the average time each request spends in the system, in seconds?
    s
    Numeric answer
  3. 3Which quantity counts only the requests that actually meet the latency SLO?
    Multiple choice
  4. 4Why does throughput saturate rather than keep rising as batch size grows?
    Multiple choice
  5. 5Which technique stores the KV cache in non-contiguous pages so sequences of different lengths can share GPU memory?
    Short answer
  6. 6Mean latency is the right target because only a small minority of users ever hit the slowest requests.
    True / false

Where next: with the serving path in hand, the natural continuation is the training side of the same stack — data pipelines and distributed training — the systems that produce the checkpoints this chapter deploys.