Batching is free throughput until it isn’t, the crossover from memory-bound to compute-bound tells you exactly where “free” ends.

The two regimes

= total bytes of modal weights = params x bytes per param B = batch size (number of sequences decoding concurrently in this step) S = sequence length; tokens of context per sequence (how much KV each one has accumulated) = KV bytes per token per sequence

= total KV cache bytes that must be read this step Add W for the # of bytes moved per decode step.

= achieved HBM bandwidth

= param count 2N flops per token 2NB flops per step (assuming dense matmuls) = achieved compute throughput for the relevant dtype.

  • Small (): TPOT is flat, throughput grows linearly. Batching is nearly free.
  • Mid : KV traffic starts to matter; throughput bends over toward its ceiling .
  • Large : compute-bound. Throughput is pinned at and TPOT grows linearly. Pure latency loss, zero throughput gain.

The crossover batch

A decode GEMM does FLOPs while moving weight bytes. Arithmetic intensity . Set it equal to the device ops:byte ratio : The precision cancels: halving doubles on tensor-core hardware, so on essentially every modern accelerator, in BF16, FP8, or FP4. Count tokens in flight, not requests, chunked-prefill tokens count too.

Picking for an SLO

Invert TPOT in the memory-bound regime: Example: ms, TB/s, GB, , KiB: Then take , SLO, memory, and diminishing returns.

Closing the loop with load

Little’s Law ties offered load to the batch you will actually see: If the queue grows without bound and P99 diverges, that is an admission-control problem, not a batching one. See Scheduling and Admission Control and Latency Decomposition.

ChangesEffect
GQA/MLAshrinks → raises the throughput ceiling
KV quantizationsame, ~2×
Weight quantizationshrinks → flat region extends further
Speculative Decodingtrades spare FLOPs for latency; only helps below
Disaggregated Inferencelets prefill and decode sit at different batch sizes