PRODUCTION

Serving a Model

Everything up to here was about one request. Production is about the thousandth concurrent one — where latency splits into two numbers that pull against each other, and the cheapest configuration is rarely the fastest.

Latency Is Two Numbers

Averaging them together hides the thing your users actually feel.

Short prompt
Long prompt
prefill — the whole prompt at once decode — one token per step
MeasureWhat it isDriven byHow to improve it
Time to first tokenSilence before anything appearsPrompt length, queue depth, prefill computeShorter prompts, prefix caching, more compute
Time per output tokenHow fast text then streamsMemory bandwidth, batch size, model sizeSmaller or quantized weights, faster memory
They have different cures, so measure them separately. Prefill is compute-bound and parallel across the prompt; decode is memory-bandwidth-bound and inherently sequential, because each token depends on the one before. Buying a card with more FLOPs fixes the first and barely touches the second. And because streaming hides decode time but not prefill, time to first token is what users report as “slow”.

Throughput Is Bought With Latency

Batching is the single biggest lever in serving, and it works by making individual requests slightly worse.

Decoding one sequence leaves a GPU almost idle: the weights have to be read from memory for every token regardless of how many sequences are using them. Run sixteen sequences together and the same read serves all sixteen, so throughput climbs steeply while per-request speed drops only a little.

Continuous batching is what makes this practical. Rather than waiting for a batch to finish, the scheduler drops sequences as they complete and admits new ones into the free slots on the next step. A short request no longer waits behind a long one, which is the difference between a demo and a service.

Lever

Batch size

More concurrent sequences, better hardware utilisation, more KV cache needed. The cache is what caps it, not compute.

Lever

Prefix caching

A shared system prompt is identical across requests, so its keys and values can be computed once and reused. Frequently the cheapest win available.

Lever

Speculative decoding

A small model drafts several tokens, the large one verifies them in a single pass. Accepted drafts come free; rejected ones cost a little.

Sizing the Machine

Three things compete for the same VRAM, and only one of them is fixed.

Weights
KV cache
Activations
constant, set by parameters and precision grows with context and concurrency scales with batch size

Weights are the easy part: parameters times bytes per parameter. A 7B model is about 13 GB at FP16 and roughly 4 GB at 4-bit. What people underestimate is the cache — at long contexts and real concurrency it can exceed the weights outright, which is what the calculator on the KV cache page is for.

Plan for the load you expect rather than a single request, leave headroom for fragmentation, and remember that the moment you run out, the failure is not slowness but a refused request.

What It Costs

Self-hosting is fixed cost; an API is variable cost. Which wins depends entirely on utilisation.

A rented GPU bills by the hour whether or not anyone is using it. An API bills by the token and nothing when idle. So the comparison is not a rate against a rate — it is a break-even volume.

Take a card at roughly $1/hour, about $720/month. If a served model produces on the order of a few hundred tokens a second under healthy batching, a fully saturated month is on the order of a billion output tokens. Against API pricing in the region of $0.50 to $10 per million output tokens, the same volume costs somewhere between a few hundred and several thousand dollars.

Which means the honest summary is: self-hosting wins at high, steady utilisation and loses badly at low or spiky utilisation — and most workloads are spiky. Idle GPUs are the expense nobody budgets for.

Before optimising cost, check the prompt. A system prompt resent on every call, a retrieved document nobody reads, a conversation history that never gets trimmed — these are usually a larger share of the bill than the model choice, and they cost cache memory as well as money.

Failing Well

Model serving fails in ways ordinary web services do not, and the defaults are wrong for it.

FailureWhat users seeDefence
Queue saturationEverything slows at once, then times outAdmission control — shed load rather than queue it; return a clear busy signal
Runaway generationA response that never endsHard token cap and stop sequences on every call
Cache exhaustionRequests refused despite low GPU useCap context and concurrency together; evict idle sessions
Naive retriesA slow period becomes an outageBackoff with jitter; never retry a streamed response mid-flight
Upstream outageThe feature disappearsA smaller local model behind the same OpenAI-compatible interface
Timeouts need rethinking. A generation legitimately takes thirty seconds, so a standard thirty-second HTTP timeout will cut healthy work in half. Stream, so the connection proves itself alive with every token, and measure time to first token separately — that is the number a timeout should actually watch.

Next in Series

OpenAI Compatible Endpoints

The API shape that local and hosted engines all speak