Skip to content

Batching and scheduling chains: concurrency, goodput, queues and budgets

8 chains

A scheduling round tests whether you can turn arrival rates and step times into concurrency, memory and tail latency. Most of these chains use Llama 3.1 8B on one H100 SXM, so the numbers can be compared across cards.

Little's law for an LLM server

inputs   10 requests/s, 0.5 s TTFT, 300 output tokens at 25 ms each
W        TTFT + (output - 1) x TPOT = 0.5 + 299 x 0.025 = 7.975 s
L        lambda x W = 10 x 7.975 = 79.75 requests in the system
decoding 10 x 299 x 0.025 = 74.75 sequences decoding at any moment
sanity   74.75 / 0.025 s = 2,990 tokens/s = 10 requests/s x 299 tokens

The law holds for any stable system, so it is a cross-check on dashboards: a waiting-plus-running gauge that disagrees with rate times time is measuring something else.

Drilled in: Little's law and KV sizing

KV in flight and the naive ceiling

inputs   the 79.75 requests above on Llama 3.3 70B, 1,650 tokens each on average
         320 KiB of KV per token; TP=4 pool of 159.8 GB = 487,600 tokens
in flight  79.75 x 1,650 x 327,680 B = 43.1 GB
ceiling    159.8 GB / (1,650 x 327,680 B x 7.975 s) = 37 requests/s
sanity   the ceiling assumes W stays at 7.975 s, and it does not: more
         concurrency slows every step

Treat 37 requests/s as an upper bound. As concurrency rises, TPOT rises, W grows and the pool fills well before the naive rate.

Drilled in: Little's law and KV sizing

Goodput at a TPOT target

inputs   Llama 3.1 8B BF16 on one H100 SXM, 2,048 tokens of context, 10 ms TPOT target
batch 64   (16.06 + 64 x 2,048 x 131,072 / 1e9) GB / 3.35 TB/s = 9.9 ms, 6,450 tok/s
batch 128  (16.06 + 34.4) GB / 3.35 TB/s = 15.1 ms, 8,505 tok/s
sanity   32% more tokens at batch 128, and every request misses 10 ms:
         goodput goes from all of them to none

Throughput keeps rising past the SLO; goodput falls off a cliff at it. Size the batch from the target, then read the throughput off the curve.

Drilled in: Goodput versus throughput

Swap or recompute a preempted request

inputs   Llama 3.1 8B BF16 on H100 SXM; 128 KiB of KV per token; 4K context
swap     out and back: 2 x 131,072 B / 50 GB/s = 5.2 us per token
recompute 2 x 8.03e9 FLOPs plus attention at 50% MFU = 34.7 us per token
break-even  262,144 B / 34.7 us = 7.6 GB/s of effective bandwidth
sanity   a batched swap wins; per-block copies of 32 KiB that reach
         only 3 to 4 GB/s lose to recompute

The link's rated bandwidth is not the one that matters. Small scattered copies achieve a fraction of it, which is why engines that swap must batch the copies.

Drilled in: Preemption: recompute or swap · Swap versus recompute

Head-of-line blocking under FCFS

inputs   Llama 3.1 8B on one H100 SXM at 50% MFU, token budget 8,192
         a 32,768-token prompt arrives ahead of ten 500-token prompts
steps    32,768 / 8,192 = 4 full steps before a short prompt gets budget
FCFS     short prompts' TTFT 1.80 s in a step-level simulation
by length  short prompts 0.27 s; the long prompt waits one extra step, 0.16 s
sanity   mean TTFT 1.78 s to 0.41 s, and prompt length is known on arrival,
         so this reordering needs no prediction

Shrinking the budget does not fix the order; it only makes each step shorter and less efficient. Capping one prompt's share of each step does.

Drilled in: Head-of-line blocking, FCFS and SJF

The p99 TTFT knee

inputs   M/M/1 queue, mean prefill S = 66.5 ms, p99 TTFT target 1 s
formula  p99 response = 4.6 x S / (1 - rho)
limit    4.6 x 0.0665 / (1 - rho) <= 1  ->  rho <= 0.69
slope    1 / (1 - rho)^2: 11 at 70% load, 4 at 50%
sanity   the "keep it under 70%" rule falls out of these inputs; lognormal
         prompt lengths pull the limit to about 50%

The model is a starting point. A batching engine's service time depends on its load, which bends the curve further.

Drilled in: The p99 TTFT utilization knee

A 100K-token prompt under the budget

inputs   Llama 3.1 8B, vLLM v0.32.0 defaults on H100 (budget 8,192, chunked
         prefill on), 64 decoders; 494.5 TFLOP/s achieved (50% MFU)
chunk 1  2 x 8.03e9 x 8,192 / 494.5e12 = 266 ms linear
         + 524,288 x 8,128^2 / 2 / 494.5e12 = 35 ms causal attention = 301 ms
later    each chunk adds 524,288 x 8,128^2 / 494.5e12 = 70 ms of attention
chunks   ceil(100,000 / 8,128) = 13
sanity   the 64 decoders' inter-token latency goes from 9.9 ms to 301 ms
         and climbs from there for 13 steps

Chunked prefill keeps decoders alive. It does not keep them fast, and the attention term grows with each chunk because later chunks attend to everything before them.

Drilled in: A 100K-token prompt, step by step · Chunked prefill and the token budget

Tenant cost in weighted tokens

inputs   tenant A: 200 input, 100 output tokens per request
         tenant B: 8,000 input, 1,000 output tokens per request
weights  about 1 per input token and 3 per output token, from the roofline
cost     A = 200 + 3 x 100 = 500; B = 8,000 + 3 x 1,000 = 11,000
ratio    22x per request
sanity   round-robin by request gives B 22 / 23 = 96% of the GPU

Fairness has to count what each request costs. A per-tenant counter charged as tokens are processed, with the tenant furthest behind admitted first, gives each tenant its share.

Drilled in: Tenant fairness with token quotas

Next card: Attention Kernel Interview Chains