Netflix-style LLM gateway: batching, KV cache, and one API
How to put a stable OpenAI-compatible gateway in front of heterogeneous models — continuous batching, prefix KV reuse, and routing without rewriting every client.
Framed from public engineering talks, blogs, and OSS patterns. Not confidential internals or invented quotes.
The product problem
Product teams want one chat/completions contract. Platform teams need to swap GPUs, swap open-weight vs vendor APIs, A/B post-trained checkpoints, and enforce budgets — without forking every client.
Public engineering discussions from large streaming / consumer platforms (Netflix-class serving) converge on the same shape: an LLM gateway that looks like a stable HTTP API while inference engines underneath handle continuous batching and KV-cache reuse.
This article frames that pattern from public knowledge — not confidential internals.
Architecture at a glance
flowchart LR
Clients[Clients / services] --> Gateway[LLM Gateway]
Gateway --> Router[Router + policy]
Router --> EngA[vLLM / TensorRT-LLM]
Router --> EngB[Vendor API]
Router --> EngC[Canary checkpoint]
Gateway --> Obs[Traces + cost meters]
EngA --> KV[KV cache / prefix store]
Gateway responsibilities (ship these first):
- Authn/authz and per-tenant quotas
- Model alias → concrete deployment mapping
- Request validation (max tokens, tools schema, content filters)
- Streaming SSE/WebSocket fan-out with backpressure
- Idempotency keys for non-stream completions where retries matter
- Unified tracing:
request_id, model id, token counts, TTFT, TPOT
Inference engine responsibilities:
- Continuous batching of decode steps
- Paged / block KV cache
- Prefix caching for shared system prompts
- Speculative decoding (optional later)
Continuous batching (why throughput jumps)
Naive “one request = one GPU reservation until done” wastes compute during decode. Continuous batching interleaves tokens from many sequences on each step:
sequenceDiagram
participant G as Gateway
participant E as Engine
participant GPU as GPU
G->>E: req A (prefill)
G->>E: req B (prefill)
E->>GPU: batch prefill A+B
loop Decode steps
E->>GPU: step tokens for active seqs
GPU-->>E: next tokens
E-->>G: stream deltas
end
Tradeoffs
| Choice | Wins | Pays |
|---|---|---|
| Continuous batching | High GPU util, better $/token | Tail latency under burst; harder SLO math |
| Static batching | Simpler latency model | Idle slots; worse cost |
| Separate prefill/decode pools | Protects TTFT | More fleet complexity |
Failure modes: head-of-line blocking when a few huge-context jobs dominate; OOMs when max concurrent sequences × max context exceeds KV budget.
What to ship: expose max_num_seqs, max_model_len, and a queue depth metric. Reject or shed load before the GPU OOMs.
KV cache and prefix reuse
Autoregressive decoding stores key/value tensors per layer per token. Memory is the scarce resource, not FLOPs alone.
Prefix caching (common in vLLM-class engines): if many requests share the same system prompt / RAG preamble, cache the KV for that prefix and skip recompute.
flowchart TD
Prompt[System + tools schema] --> Hash[Prefix hash]
Hash --> Hit{KV hit?}
Hit -->|yes| Decode[Decode only]
Hit -->|no| Prefill[Prefill + store blocks]
Prefill --> Decode
Tradeoffs
- Aggressive prefix sharing → big TTFT wins for “same template, different user turn”
- Too-coarse hashing → cache pollution; too-fine → low hit rate
- Multi-tenant prefixes need tenant-scoped cache keys or you leak attention state across customers (isolation bug class)
What to ship: measure prefix hit rate; document which prompt segments are stable enough to pin.
Gateway routing patterns
alias: "assistant-default"
→ primary: llama-3.1-70b-awq @ cluster-a
→ fallback: vendor-gpt-class @ api
→ canary: 5% → internal-sft-v12Routing should be data-driven, not hardcoded in clients:
- Feature flags / percentage canaries
- Cost ceilings (route cheap model unless complexity score high)
- Latency class (interactive vs batch)
- Capability tags (
tools,vision,json_schema)
Observability that matches serving
Track at least:
- TTFT (time to first token) — user-perceived snappiness
- TPOT (time per output token) — stream smoothness
- Queue wait — gateway vs engine
- KV memory pressure — leading indicator of OOM
- $/1k tokens by alias and tenant
Without these, “the model feels slow” is unactionable.
Checklist: minimal gateway you can defend in review
- OpenAI-compatible
/v1/chat/completionswith streaming - Model aliases + canary map in config (not code)
- Per-key RPM/TPM limits
- Structured logs with token + latency fields
- Load shed on queue depth / KV pressure
- One load test: shared system prompt → measure prefix-cache benefit
Interview / design cues
Be ready to explain why batching improves throughput but can hurt P99, how KV memory scales with concurrent context, and why a gateway exists even when you “only call one vendor API” today (policy, metering, swap-out).
Further reading in ShipAI
Curriculum modules on serving SLMs, quantization tradeoffs, and industry case labs rebuild a miniature gateway — use this article as the architecture brief before you code.