All insights

Engineering

Serving Large Language Models Affordably: Quantization, Distillation, and Inference Optimization

Inference, not training, is where most enterprises spend their model budget. The techniques that cut that cost without wrecking quality are well understood but rarely applied systematically. A practitioner overview.

Root Digit Platform · ML Platform Engineering7 min read

Serving a large language model efficiently begins with a fact that determines nearly every subsequent decision: the two phases of inference have opposite performance characteristics. Prefill is compute-bound. Decode is memory-bandwidth-bound. A serving stack that treats them identically will be inefficient at both.

Why decoding is slow, and why more FLOPS will not fix it

During prefill the whole prompt is processed at once, so weights loaded from memory are amortised across many tokens and the GPU's arithmetic units are the constraint. During decode a single token is generated per step, which requires reading every weight in the model to produce one token. Arithmetic intensity collapses and memory bandwidth becomes the hard limit.

ttoken  ≥  Sweights  /  BWHBM    →    140 GB / 3.35 TB/s  ≈  42 µs  ≈  24 tok/s

A 70B model in FP16 occupies ~140 GB. On an accelerator with 3.35 TB/s of HBM bandwidth, single-stream decode cannot exceed roughly 24 tokens/s regardless of available compute — the weights must cross the memory bus once per token.

Two consequences follow, and they are the whole strategy. First, batching is nearly free during decode: the weights are read once for the entire batch, so serving thirty-two concurrent sequences costs little more per step than serving one. Throughput scales close to linearly with batch size until something else binds. Second, reducing model bytes — through quantisation — improves decode latency proportionally, because it directly shrinks the numerator.

The KV cache is what actually limits batch size

Batching would be unboundedly good if it were free, and the thing that stops it is the attention cache. Every sequence retains key and value tensors for every token it has processed, at every layer, and that memory grows linearly with both context length and batch size.

SKV  =  2 · L · Hkv · dhead · Sseq · B · b

2 for K and V · L — layers · H_kv — key/value heads (far fewer than query heads under grouped-query attention) · d_head — head dimension · b — bytes per element

Concretely, for an 80-layer model with 8 KV heads and a head dimension of 128 in FP16: 2 × 80 × 8 × 128 × 2 = 320 KB per token. A single 8,192-token sequence therefore holds about 2.6 GB of cache. Thirty-two such sequences need 84 GB — for cache alone, on top of the 140 GB of weights.

Grouped-query attention is the architectural response, and its impact is often underappreciated. Sharing key and value projections across groups of query heads reduces H<sub>kv</sub> by the group factor, cutting cache memory by the same factor with minimal quality cost. A model with 64 query heads and 8 KV heads uses one eighth the cache of full multi-head attention — which translates directly into eight times the batch size at the same memory budget.

Continuous batching, because sequences finish at different times

Static batching holds the whole batch until its longest member completes. With generation lengths varying from twenty tokens to two thousand, that leaves most of the batch idle for most of its life while finished sequences occupy slots doing nothing.

Continuous batching evicts each sequence the moment it emits its stop token and admits a waiting request into the freed slot at the next step. Reported throughput gains on realistic mixed workloads are large — commonly several times static batching — and the gain grows with the variance of output length, which in production is high.

MetricDetermined byImproved by
Time to first tokenPrefill compute, queue depthChunked prefill, more parallelism, prefix caching
Inter-token latencyMemory bandwidth, batch sizeQuantisation, speculative decoding, smaller batch
Throughput (tok/s total)Batch size, KV memoryPaged cache, GQA, continuous batching, larger batch
The three metrics that matter, and what moves each. They trade against one another, which is why a single 'performance' target is not a specification.

The tension between the second and third rows is the central scheduling problem. Larger batches raise aggregate throughput and worsen per-user latency, because each decode step does more work. A serving stack must be told which it is optimising — an interactive assistant and an overnight batch job want opposite settings, and running them on the same pool at the same settings serves neither well.

Prefill and decode also interfere. A long prompt arriving mid-flight monopolises the GPU for a compute-heavy prefill while every decoding sequence stalls, producing a latency spike for users who did nothing. Chunked prefill splits long prompts into pieces interleaved with decode steps, converting one large stall into several small ones — a clear improvement in tail latency at a small cost in prefill efficiency.

Speculative decoding: buy tokens with spare compute

Since decode is bandwidth-bound, arithmetic capacity is sitting idle. Speculative decoding spends it: a small draft model proposes several tokens cheaply, and the target model verifies all of them in a single forward pass — a compute-heavy operation that costs barely more than generating one token, precisely because compute was not the constraint.

𝔼[ tokens per step ]  =  ( 1 − αk+1 )  /  ( 1 − α )

α — probability the target accepts a drafted token · k — draft length. At α = 0.8 and k = 4, roughly 3.4 tokens per verification step — a 3.4× speedup if drafting is cheap enough to ignore.

Everything depends on α, which depends on how well the draft model matches the target's distribution on the actual workload. A draft from the same family and training data achieves high acceptance; a generic small model does not. Measure α on production traffic rather than assuming it, because below roughly 0.6 the drafting overhead starts eating the gain.

Quantisation, where the gain is bandwidth rather than compute

Because decode is bandwidth-bound, halving weight precision roughly halves decode latency. FP8 on hardware with native support is close to free in quality terms for most models. Weight-only INT4 methods that preserve the outlier channels responsible for most quantisation error push the memory saving further, at a quality cost that must be measured on your evaluation set — not on a public benchmark, which will not exercise your domain.

Quantise the KV cache as well. It is frequently overlooked despite being, at long context and high batch, the larger consumer. FP8 cache halves that footprint and buys back batch size, which is throughput.

The order to do this in

Paged KV cache and continuous batching first — they are pure wins with no quality cost and typically the largest gains. Then quantisation of weights and cache, measured against your own evaluation set. Then chunked prefill if tail latency is the complaint. Then speculative decoding, if you have a draft model that earns its acceptance rate. And separate pools for interactive and batch traffic before tuning any of it, because a single pool cannot be optimal for both and most of the tuning arguments we are asked to arbitrate turn out to be that problem wearing a different hat.

Explore how Root Digit can support your team

From discovery workshops to production deployment, our engineers and consultants partner with you across the lifecycle of your AI, robotics, and IoT initiatives.

Cookie Policy

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies. You can also choose "Necessary Only" to limit cookies to essential website functions only. Learn more