Design an LLM inference/serving platform¶
At a glance
Priority: P1 · Complexity: 5/5 · Est. time: 4 h · Phase: 5 · Prereqs: Framework, Inference serving, LLM fundamentals, Capacity & cost planning, Load balancing You're done when: you can design a self-hosted multi-model serving platform on GPUs — explaining prefill vs decode, KV cache memory math, continuous batching, prefix caching, quantisation, parallelism, disaggregated serving, KV-aware routing, autoscaling and multi-LoRA — and size GPUs for a stated token throughput and latency SLO.
Problem¶
"We want to self-host open-weight LLMs (8B–70B+ class, plus embedding and reranker models) for internal teams because of data residency and cost. Design the inference platform."
Logistics context: document extraction (bills of lading, customs declarations) runs at high steady volume — a good fit for a fine-tuned 8B model with LoRA adapters per document family — while interactive assistants need low TTFT.
Clarifying questions¶
| Question | Why |
|---|---|
| Which models and sizes; how many fine-tuned variants (LoRA)? | Memory planning, multi-LoRA serving |
| Workload mix: interactive chat vs batch extraction vs embeddings? | Separate pools, SLOs, batching policy |
| SLOs: TTFT, TPOT (inter-token latency), throughput? | Batch sizes, parallelism, disaggregation |
| Traffic shape: steady vs spiky; peak/avg ratio? | Autoscaling and utilisation → economics |
| Hardware: which GPUs available (H100/H200/B200/MI300-class), on-prem or cloud? | Memory per GPU, interconnect |
| Context lengths? | KV cache size dominates memory |
| Multi-tenancy and isolation requirements? | Shared vs dedicated pools, quotas |
Requirements¶
Functional: OpenAI-compatible endpoints (chat, completions, embeddings, rerank); model registry and deployment (versions, quantisation variants, LoRA adapters); routing; autoscaling; quotas per tenant; batch API; streaming; structured output/constrained decoding; metrics and tracing.
| NFR | Target (assumed) |
|---|---|
| Latency (interactive pool) | TTFT p95 < 800 ms for ≤ 4k-token prompts; TPOT p95 < 50 ms (≥ 20 tok/s per stream) |
| Throughput (batch pool) | Max tokens/s per GPU; job completion within 4 h window |
| Utilisation | > 60% average GPU utilisation (economics) |
| Availability | 99.9% interactive; graceful overflow to API provider via gateway |
| Quality | Quantised/fine-tuned variants must pass eval parity with baseline (≤ 1 pt drop on key metrics) |
| Cost | Cost per M tokens below equivalent API price at expected utilisation |
Estimation¶
KV cache math (the number to know):
KV bytes per token = 2 (K and V) × layers × kv_heads × head_dim × bytes_per_elem
Example 70B-class with GQA: 80 layers, 8 KV heads, head_dim 128, FP16 (2 bytes)
= 2 × 80 × 8 × 128 × 2 = 327,680 B ≈ 0.31 MB per token
8k-token sequence → ~2.5 GB of KV cache per concurrent sequence
Weights: 70B × 2 bytes (FP16) = 140 GB; FP8 = 70 GB; INT4 ≈ 35–40 GB
On 8 × 80 GB GPUs (640 GB) with FP8 weights (70 GB) and runtime overhead (~10%):
~500 GB for KV → ~200 concurrent 8k sequences (FP16 KV), ~400 with FP8 KV cache
Example 8B-class (32 layers, 8 KV heads, 128 dim, FP16): 2×32×8×128×2 = 131 KB/token
→ 8k seq ≈ 1 GB; on one 80 GB GPU with 16 GB weights → ~55 concurrent 8k seqs
Throughput and GPUs:
Decode is memory-bandwidth-bound: each step reads all weights once for the whole batch.
Single-stream upper bound ≈ HBM bandwidth / weight bytes:
8B FP16 (16 GB) on ~3.3 TB/s HBM → ~200 tok/s per stream at batch 1
Batching amortises weight reads: aggregate throughput scales near-linearly with batch until compute-bound
or KV memory runs out → thousands of tok/s per GPU for 8B-class under load (benchmark to confirm!)
Target: interactive peak 400 concurrent streams × 25 tok/s = 10k output tok/s on 70B-class
Suppose benchmark shows one 8-GPU FP8 node sustains ~3k output tok/s at TPOT < 50 ms
→ 4 nodes at peak + 1 N+1 spare = 5 nodes (40 GPUs)
Cost: 40 GPUs × assumed $3/GPU-h × 730 h ≈ $88k/month
Tokens served (at 50% avg utilisation): 10k × 0.5 × 2.6M s/month ≈ 13B output tokens
→ ~$6.7/M output tokens (+ input prefill cost, which is cheaper per token) → compare with API pricing
Say explicitly: these are order-of-magnitude estimates; the real sizing comes from load-testing the exact model, engine version, quantisation and prompt length distribution.
Architecture¶
flowchart TB
C[Clients via LLM gateway] --> IR[Inference router<br/>model-aware, KV/prefix-aware, queue-aware]
IR --> P1[Interactive pool<br/>70B FP8, TP=8]
IR --> P2[Batch pool<br/>8B + LoRA adapters, large batches]
IR --> P3[Embedding / rerank pool]
subgraph DIS[Optional disaggregated pool]
PF[Prefill workers] -->|KV transfer| DC[Decode workers]
end
IR --> DIS
P1 --- ENG[vLLM / SGLang engines<br/>continuous batching, paged KV, prefix cache]
REG[(Model registry<br/>weights, quant variants, adapters)] --> P1
REG --> P2
AS[Autoscaler<br/>queue depth, KV utilisation, SLO] --> P1
AS --> P2
OBJ[(Object store / model cache<br/>fast weight loading)] --> P1
P1 -. metrics .-> MON[(Prometheus / OTel)]
C -.overflow.-> API[(External API fallback)]
Component deep dives¶
1. Engines¶
| Engine | Strengths | Notes |
|---|---|---|
| vLLM | PagedAttention, continuous batching, automatic prefix caching, broad model/hardware support, multi-LoRA, OpenAI-compatible server | Default choice for most platforms |
| SGLang | RadixAttention prefix sharing, fast structured generation, strong for agentic/multi-call workloads | Excellent when prompts share long prefixes |
| TensorRT-LLM / NVIDIA Dynamo | Peak NVIDIA performance, disaggregated serving orchestration | More build complexity |
| Ollama / llama.cpp | Local/dev, edge, CPU/Apple silicon | Not for multi-tenant production scale |
| Hugging Face TGI | Historically common | Check current maintenance status before choosing |
2. Core mechanisms (explain in interviews)¶
- Prefill vs decode: prefill processes the prompt in parallel (compute-bound; drives TTFT); decode generates one token per step per sequence (memory-bandwidth-bound; drives TPOT). Mixing them causes interference — long prefills stall decodes.
- Continuous (in-flight) batching: sequences join/leave the batch every step instead of waiting for the whole batch — huge utilisation gain.
- PagedAttention: KV cache in fixed-size blocks, eliminating fragmentation, enabling sharing (vLLM paper).
- Chunked prefill (Sarathi-Serve): split long prefills into chunks interleaved with decodes to bound TPOT.
- Prefix caching: reuse KV for shared prefixes (system prompts, few-shot, documents) — cuts TTFT and compute for RAG/agents.
- Speculative decoding: small draft model (or n-gram/EAGLE-style heads) proposes tokens verified by the big model in one pass — lowers latency at low batch sizes; gains shrink at high load.
- Quantisation: FP8 weights (near-lossless on modern GPUs), INT4 AWQ/GPTQ (memory savings, some quality loss), FP8 KV cache (doubles concurrency). Always eval.
3. Parallelism¶
| Type | What | When |
|---|---|---|
| Tensor parallel (TP) | Split each layer across GPUs in a node | Model doesn't fit one GPU; needs fast NVLink |
| Pipeline parallel (PP) | Split layers across nodes | Very large models across nodes; adds bubbles |
| Data parallel (replicas) | Multiple copies | Scale throughput; simplest |
| Expert parallel (EP) | Spread MoE experts | MoE models (many 2025–26 open models are MoE) |
Rule of thumb: smallest TP that fits weights + adequate KV; scale out with replicas.
4. Disaggregated prefill/decode¶
Run prefill and decode on separate GPU pools and transfer KV (DistServe, and production stacks like llm-d / NVIDIA Dynamo). Benefits: tune each pool independently for TTFT vs TPOT, eliminate interference, better goodput under SLOs. Costs: KV transfer bandwidth (needs fast interconnect), orchestration complexity. Worth it at large scale with long prompts (RAG, agents); overkill for small fleets.
5. Routing¶
Round-robin is wrong for LLMs. The router should consider: which replica already has the prefix in KV cache (cache affinity), queue depth and KV utilisation per replica, LoRA adapter loaded, and request priority. Kubernetes Gateway API Inference Extension and llm-d implement model-aware and KV-cache-aware routing on K8s.
6. Multi-LoRA¶
Serve one base model with many adapters (per document family, per tenant) loaded dynamically; batch requests across adapters (vLLM supports multi-LoRA). Dramatically better utilisation than one deployment per fine-tune. Limits: adapter count in GPU memory, rank sizes, cold-load latency.
7. Autoscaling and cold starts¶
- Scale signals: queue depth, pending requests, KV cache utilisation, TTFT SLO burn — not CPU/GPU utilisation alone.
- Cold start = node provisioning (minutes) + weight load (70–140 GB) + engine warmup/graph capture. Mitigations: warm spare capacity, local NVMe model cache, fast streaming loaders, pre-pulled images, snapshotting.
- Overflow: gateway routes excess to external APIs (if data policy allows) rather than violating SLOs; batch pool absorbs slack during off-peak (run batch jobs when interactive load is low → utilisation up).
Evaluation strategy¶
- Performance benchmarks: load tests with production-like prompt/output length distributions; measure TTFT, TPOT, throughput, goodput (requests meeting SLO) vs concurrency; track per engine version.
- Quality parity: every quantised variant, engine upgrade and speculative decoding config runs the consuming apps' evals (engine/kernel changes have caused real quality bugs — see Anthropic's September 2025 postmortem on infrastructure issues affecting output quality).
- Numerical canaries: fixed prompts with greedy decoding compared across deployments to catch silent corruption.
- Online: error rates, truncations, JSON validity rates per deployment.
Observability¶
Engine metrics (vLLM/SGLang expose Prometheus): running/waiting requests, KV cache usage %, prefix-cache hit rate, TTFT/TPOT histograms, tokens/s, preemptions. GPU metrics (DCGM): utilisation, memory, SM occupancy, power, XID errors. Traces from gateway → router → engine with OTel GenAI attributes. SLO dashboards per pool; cost per M tokens per model computed from GPU-hours and tokens served.
Failure modes¶
| Failure | Mitigation |
|---|---|
| KV cache exhaustion → preemptions, latency spikes | Admission control, max concurrent seqs, FP8 KV, route by KV utilisation, cap context length per tier |
| Long prompts starving decodes | Chunked prefill, separate pools or disaggregation |
| GPU hardware faults (XID errors, ECC) | Health checks, automatic node drain, N+1 capacity |
| Silent quality regression from quantisation/engine upgrade | Eval parity gates, numerical canaries, staged rollout |
| Cold-start storms on scale-up | Warm pool, model caches, predictive scaling |
| Noisy tenant saturating shared pool | Per-tenant token quotas at gateway, priority queues, dedicated pools for critical tenants |
| Low utilisation → economics worse than APIs | Consolidate models, multi-LoRA, fill with batch work, right-size |
Scaling & cost optimization¶
- Maximise goodput: continuous batching, prefix caching, chunked prefill, speculative decoding at low load.
- Right-size models: distil/fine-tune 8B-class for narrow tasks instead of serving 70B+.
- FP8 everywhere it passes evals; FP8 KV for concurrency.
- Mix workloads: interactive by day, batch backfill by night; spot/preemptible GPUs for batch.
- Provisioned API capacity as overflow instead of over-provisioning GPUs for rare peaks.
What a Staff-level answer adds¶
- Economic model: break-even utilisation vs API prices, including team cost (on-call, upgrades) — self-hosting is a capability investment, not a default.
- Portfolio view: which workloads self-host (high-volume narrow tasks, residency-bound data) vs stay on APIs (frontier-quality needs).
- Upgrade cadence: open models change quarterly; a model onboarding pipeline (download → quantise → benchmark → eval → canary) is the real product.
- Hardware strategy: reserved vs on-demand vs on-prem; GPU generation mix; supply risk.
- Interfaces: OpenAI-compatible API behind the same gateway so apps don't know whether a model is self-hosted.
Resources¶
| Resource | Type | Why this one | Level | Cost |
|---|---|---|---|---|
| Efficient Memory Management … PagedAttention (vLLM paper) | paper | The foundational KV-cache idea | advanced | free |
| Transformer Inference Arithmetic (kipply) |
article | Best first-principles memory/latency math | advanced | free |
| How to Scale Your Model (JAX scaling book) |
book | Rigorous roofline and parallelism reasoning | advanced | free |
| Mastering LLM Techniques: Inference Optimization (NVIDIA) | article | Clear overview of batching, KV, parallelism, quantisation | intermediate | free |
| LLM Inference Performance Engineering (Databricks) |
article | TTFT/TPOT metrics and hardware trade-offs with numbers | intermediate | free |
| vLLM docs | docs | Engine features: prefix caching, multi-LoRA, quantisation | intermediate | free |
| SGLang docs | docs | RadixAttention and structured generation | intermediate | free |
| DistServe paper | paper | Disaggregated prefill/decode and goodput | advanced | free |
| Sarathi-Serve paper | paper | Chunked prefill for throughput-latency trade-off | advanced | free |
| llm-d | docs | K8s-native distributed inference with KV-aware routing | advanced | free |
Follow-up questions¶
L2 — Apply¶
Q1. Compute KV cache per token for a model with 32 layers, 8 KV heads, head_dim 128, FP16. How many 32k-token sequences fit in 40 GB of free GPU memory?
Answer
2 × 32 × 8 × 128 × 2 B = 131,072 B = 128 KB/token. 32k tokens → 4 GB per sequence. 40 GB / 4 GB = 10 concurrent 32k sequences (FP16 KV); ~20 with FP8 KV. This is why long-context tiers need separate pools and quotas.
Q2. TTFT is fine but TPOT spikes whenever long RAG prompts arrive. Why and what do you change?
Answer
Long prefills monopolise GPU compute in the same iteration as decodes, stalling in-flight streams (prefill-decode interference). Fix: enable chunked prefill with a token budget per step; prefix caching for shared document context; route long-prompt requests to a dedicated pool; at larger scale, disaggregate prefill and decode.
Q3. Estimate batch time: 2M bills of lading/month, ~3k input + 500 output tokens each, on an 8B model where a GPU sustains an assumed 2,500 output tok/s and 20k prefill tok/s.
Answer
Output: 2M × 500 = 1B tokens / 2,500 = 400k GPU-s ≈ 111 GPU-hours. Prefill: 6B tokens / 20k = 300k GPU-s ≈ 83 GPU-hours. Total ≈ 195 GPU-hours/month (prefill and decode overlap partially, so this is conservative) → ~$600/month at $3/GPU-h, vs API costs of thousands. Batch jobs can run on off-peak or spot capacity. Validate with a benchmark on real documents.
Q4. Which autoscaling signal would you use for the interactive pool and why not GPU utilisation?
Answer
Queue depth/waiting requests and KV cache utilisation, plus TTFT SLO burn rate. GPU "utilisation" (from nvidia-smi) reports kernel activity, not headroom — a memory-bound decode workload can show high utilisation while serving well, or low while queue grows due to KV limits. Scale out ahead via predictive schedules because cold starts take minutes.
L3 — Design & trade-offs¶
Q5. One 70B model for everything vs a fleet of fine-tuned 8B models with LoRA?
Answer
For narrow, high-volume tasks (extraction, classification, routing), fine-tuned 8B + multi-LoRA usually matches quality at ~5–10× lower cost and latency; one base deployment serves many adapters. For open-ended reasoning and chat, the larger model (or an API frontier model) wins. Decide per workload by evals; the platform should support both, with the gateway routing by alias.
Q6. When is disaggregated prefill/decode worth the complexity?
Answer
When prompts are long relative to outputs (RAG, agents, document processing), fleets are large (dozens+ of GPUs), strict TTFT and TPOT SLOs conflict, and you have fast interconnect for KV transfer. For small fleets or short prompts, chunked prefill + prefix caching gets most of the benefit. Measure goodput under SLO before and after.
Q7. INT4 quantisation halves your GPU count. The product team is excited. What do you require?
Answer
Eval parity on each consuming workload's golden sets (with attention to structured output validity, multilingual, numeric accuracy — often where INT4 degrades), long-context tests, and a canary with online metrics. Consider FP8 first (near-lossless on supported GPUs). Keep FP16/FP8 variant available for workloads that fail parity. Document the decision per workload.
Q8. Self-host vs API for an internal assistant with spiky daytime traffic (peak/avg 6×)?
Answer
Spiky traffic kills self-hosting economics: you'd provision for peak and sit idle most of the day (low utilisation). Better: API (or provisioned API capacity) for interactive peaks, and self-hosted capacity sized near baseline with batch backfill off-peak. Or keep self-hosting only if residency requires it and accept the cost, using overflow to in-region managed endpoints.
L4 — Staff-level ambiguity¶
Q9. Leadership asks: 'Should we build our own GPU inference platform?' How do you answer?
Answer
Frame as a portfolio decision with numbers: current and projected API spend by workload, data residency constraints, quality needs, traffic shape. Build a TCO model: GPUs (reserved), engineering team (e.g., 4–6 engineers), on-call, model onboarding pipeline, versus API costs with provisioned throughput discounts. Recommend a staged path: start with managed open-model endpoints (Foundry/Bedrock) for residency-bound workloads, self-host one high-volume narrow workload to build capability, and expand only when utilisation and savings are proven. Define exit criteria.
Q10. An engine upgrade improved throughput 30% but a downstream team reports subtle extraction errors. Handle it.
Answer
Roll back affected pools (keep the upgrade where evals pass). Reproduce with numerical canaries and the team's eval set; bisect config differences (kernel, sampling defaults, tokenizer, quantisation path). Add the failing cases to the platform's upgrade gate; require consuming teams' eval suites to be runnable by the platform. Communicate a clear upgrade policy: staged, eval-gated, with a pinned-version option.
Q11. Five teams each want dedicated GPU nodes 'for isolation'. Utilisation is 25%. What do you propose?
Answer
Shared pools with strong multi-tenancy: per-tenant token quotas and priority at the gateway/router, multi-LoRA for team-specific fine-tunes, SLO-based scheduling, and reserved capacity guarantees (minimum share) rather than dedicated hardware. Dedicated only for hard compliance needs. Show the cost of 25% utilisation, offer SLO commitments in writing, and charge back by usage to align incentives.
Real-world use cases¶
- Document extraction at a carrier: fine-tuned 8B + LoRA per document family, batch pool at night, residency-compliant.
- Internal coding assistant autocomplete: small model, extreme TTFT requirements, speculative decoding, prefix caching.
- Embedding/rerank service for enterprise RAG: GPU pool for rerankers, batching of queries, high QPS.
- Sovereign AI deployments: on-prem serving for regulated data with API overflow disabled.
Checklist¶
- I can compute KV cache per token and concurrency for a given GPU budget
- I can explain prefill vs decode, continuous batching, prefix caching, chunked prefill, speculative decoding
- I can choose TP/PP/replicas and explain disaggregation trade-offs
- I can design autoscaling signals and cold-start mitigations
- I can build a self-host vs API break-even argument
- I answered all L3/L4 questions out loud in < 3 min each