← System Design
AI Systems

Design an LLM Inference Serving Platform

In simple words

A shared kitchen with a few very expensive ovens. Instead of baking one pizza at a time, the chef keeps every oven full, sliding a new pizza in the moment one comes out, and sends each order to the oven with room and the right ingredients already warm.

Problem Statement

Design the platform that serves large language models to many internal products and external API customers: requests come in with a prompt, run on GPU servers, and return generated tokens. The goal is high throughput and low latency from very expensive, very scarce hardware, while isolating customers from each other.

Functional Requirements

Non-Functional Requirements

Rough numbers
  a 70B-parameter model in 16-bit weights ~ 140 GB -> spread across 2-4+ GPUs (tensor parallel)
  per request: prefill (read the prompt, compute-bound) then decode (1 token per step, memory-bound)
  KV cache per sequence grows with context length -> GPU memory, not compute, often caps batch size

Core Entities

API Design

POST /v1/generate
  { "model": "chat-large", "messages": [...], "max_tokens": 800, "stream": true }
  headers: Authorization (tenant), Idempotency-Key (optional)
  -> SSE stream of tokens, then usage {input_tokens, output_tokens}
  -> 429 with Retry-After when the tenant's quota is exhausted
  -> 503 when capacity is exhausted (client may retry with backoff)

High-Level Design (HLD)

Serving LLMs across many GPUsTenants /productsAPI gateway(quota: req +tokens)Router(load + prefixaware)Replica A(model v2)Replica B(model v2)Canary(model v3)Autoscaler +model registryMetering5%trafficscale / roll out
Route each request to the replica with the most free capacity (and a warm prefix cache); canary new versions.

Low-Level Design (LLD)

Continuous batching on one replicaWaiting requestsRunning batch[A B C D]Decode step(1 token each)Streamed tokensFinished → leave,free KV blocksjoin when KVblocks free
Sequences join and leave the batch every step, so the GPU never waits for the slowest request.

Continuous batching and the KV cache

Static batching: wait for N requests, run them together until ALL finish
  -> short answers wait for the longest one; GPU idles as sequences end

Continuous (in-flight) batching: every decode step, finished sequences leave
the batch and waiting ones join
  step 1: [A B C D]    step 2: [A B C D]   step 3: [A C D E]  (B done, E joined)

KV cache: the attention keys/values of every token so far, per sequence.
  Paged KV cache (PagedAttention) allocates it in fixed blocks, like OS pages,
  so memory isn't wasted on max-length reservations and prefixes can be shared.

Routing

Scaling and rollout

Follow-up Questions

Why is round-robin load balancing bad here?
Request cost varies by orders of magnitude (a 100-token prompt vs a 100K-token document). Round robin ignores that and piles long jobs onto one replica; route on outstanding tokens and free KV memory instead.
How do you rate-limit fairly?
Limit both requests per minute and tokens per minute per tenant (estimated input + max output on admission, corrected with actual usage after), with priority tiers so batch traffic yields to interactive traffic.
How does quantisation help?
Storing weights in 8 or 4 bits shrinks memory, letting a model fit on fewer GPUs and leaving more memory for KV cache (bigger batches), usually at a small quality cost that must be measured.

Related