Capstone

Design the full inference stack

Ninety-seven days of components. Today we assemble them into a complete production inference system: from client request to GPU kernel and back, every layer justified by what we have learned.

This is the architecture post. After 97 days of individual components, attention mechanisms, quantization schemes, scheduling algorithms, deployment patterns, it is time to draw the full picture. Not a theoretical reference architecture, but the system I would actually build if someone handed me a set of GPUs and said "serve this model in production."

Every choice here traces back to a specific post in this series. If you have been following along, this should feel like a puzzle where all the pieces finally click together.

The request lifecycle

A user sends a request. Here is every layer it touches:

Client
  → API Gateway (auth, rate limiting, routing)
    → Load Balancer (least-connections, KV-cache-aware)
      → Inference Server (vLLM / SGLang)
        → Scheduler (continuous batching + chunked prefill)
          → Model (quantized weights on GPU)
            → Attention (FlashAttention + PagedAttention)
              → KV Cache (paged blocks in HBM)
            → Output sampling
          → Response tokens (streamed via SSE)
        → Scheduler releases KV blocks
      → Load Balancer records latency
    → API Gateway logs usage
  → Client receives stream

Each layer has a job, and each job maps to a post. Let me walk through the design decisions at every layer.

Layer 1: API gateway

The gateway sits in front of everything and handles concerns that should never touch the GPU:

Layer 2: Load balancer

The load balancer distributes requests across inference server replicas. This is where routing and queueing decisions happen:

Layer 3: Inference server

This is the core. I would use vLLM or SGLang as the inference engine, wrapped in a thin API layer. The inference server handles:

For the model itself, I would use INT8 or INT4 quantization depending on the quality/cost tradeoff. On Hopper GPUs, FP8 with vLLM's built-in support is the sweet spot: nearly lossless and 2x faster than FP16 for compute-bound operations.

Layer 4: GPU and memory

The GPU layer is where the roofline model determines everything:

For multi-GPU serving, tensor parallelism splits the model across GPUs within a node. For very large models (400B+), pipeline parallelism across nodes. For MoE models, expert parallelism.

Architecture decision

Should you disaggregate prefill and decode onto separate GPU pools? For most workloads, no. The complexity is not worth it. Disaggregation (day 18) makes sense when your traffic has a bimodal distribution: lots of long-context prefills mixed with latency-sensitive decode. Chunked prefill handles most of these cases without the operational overhead.

The complete stack diagram

┌─────────────────────────────────────────────┐
│  Clients (SDK / curl / browser)             │
└────────────────────┬────────────────────────┘
                     │ HTTPS
┌────────────────────▼────────────────────────┐
│  API Gateway                                │
│  - Auth, rate limits, request validation    │
│  - Model routing, usage logging             │
└────────────────────┬────────────────────────┘
                     │
┌────────────────────▼────────────────────────┐
│  Load Balancer                              │
│  - Least-connections + prefix-hash routing  │
│  - Health checks (liveness + readiness)     │
└──────┬─────────────┬──────────────┬─────────┘
       │             │              │
┌──────▼──────┐ ┌────▼────┐  ┌─────▼─────┐
│  vLLM       │ │  vLLM   │  │  vLLM     │
│  Replica 1  │ │  Rep. 2 │  │  Rep. 3   │
│             │ │         │  │           │
│  ┌────────┐ │ │ ┌─────┐ │  │ ┌───────┐ │
│  │Scheduler│ │ │ │Sched│ │  │ │ Sched │ │
│  │Chunked │ │ │ │     │ │  │ │       │ │
│  │Prefill │ │ │ │     │ │  │ │       │ │
│  └────────┘ │ │ └─────┘ │  │ └───────┘ │
│  ┌────────┐ │ │ ┌─────┐ │  │ ┌───────┐ │
│  │Model   │ │ │ │Model│ │  │ │ Model │ │
│  │INT4/FP8│ │ │ │     │ │  │ │       │ │
│  └────────┘ │ │ └─────┘ │  │ └───────┘ │
│  GPU 0..N   │ │ GPU 0..N│  │ GPU 0..N  │
└─────────────┘ └─────────┘  └───────────┘

Sizing the system

How do you decide how many replicas, how many GPUs per replica, and what quantization level? Start from the SLOs and work backward:

  1. Define your SLOs: P99 TTFT under 2 seconds. P99 inter-token latency under 50ms. Throughput of 1000 requests per minute. These numbers come from product requirements.
  2. Benchmark a single replica: Deploy one instance with your chosen model and quantization. Run a load test to find the saturation point: the request rate at which P99 latency exceeds your SLO.
  3. Compute replicas needed: Target request rate divided by per-replica capacity, with 30% headroom for traffic spikes and maintenance. If one replica saturates at 50 req/min and you need 1000 req/min, you need 26 replicas (1000 / 50 * 1.3).
  4. Check the cost: Multiply replicas by GPU cost. If it is too high, consider a smaller model, more aggressive quantization, or distillation.

Operational concerns

A production stack is not just the serving path. You also need:

What I would actually deploy today

If I were starting a production inference stack today for a general-purpose LLM application, here is the specific stack:

The best inference stack is not the one with the most features. It is the one where every component earns its complexity. Start simple, measure everything, and add complexity only when measurements justify it.

Tomorrow: we build it. FastAPI, vLLM, health checks, and a Dockerfile. Real code, real configs, ready to deploy.