Serving AI Models at Scale

Serving a model to one user is easy. Serving it to 10,000 concurrent users at low latency and reasonable cost is a real engineering challenge. This page covers the tools and patterns that make production LLM serving possible.

📖 Covers: Naive serving problems · vLLM · PagedAttention · Continuous Batching · Triton · FastAPI · Load Testing

The Problem with Naive Serving

🐌
Sequential requests

One request at a time → GPU sits idle between tokens, terrible throughput

💾
KV cache fragmentation

Unpredictable output lengths → memory wasted on over-allocation

⏱️
Fixed batch size

Short requests wait for long ones to finish → high latency spikes

vLLM — The Industry Standard LLM Server

vLLM is the most widely used open-source LLM serving library. Its two key innovations are PagedAttention and continuous batching.

Terminal · Start vLLM Server
pip install vllm

# Serve Llama 3 8B on a single A100
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Meta-Llama-3-8B-Instruct \
    --max-model-len 8192 \
    --tensor-parallel-size 1 \
    --gpu-memory-utilization 0.9

# Now accepts OpenAI-compatible API calls at localhost:8000
Python · Query vLLM with OpenAI SDK
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")

response = client.chat.completions.create(
    model="meta-llama/Meta-Llama-3-8B-Instruct",
    messages=[{"role": "user", "content": "Explain neural networks simply."}],
    max_tokens=256,
    temperature=0.7
)
print(response.choices[0].message.content)

PagedAttention — vLLM's Secret Weapon

The KV cache (which stores attention keys and values for each token) is the main memory consumer during inference. Traditional serving pre-allocates the maximum context size for every request, wasting memory.

PagedAttention manages KV cache memory like an OS manages virtual memory — using fixed-size "pages" that can be scattered across GPU memory and shared between sequences.

❌ Naive KV Cache

Req 1 (used)
Req 1 (wasted)
Req 2 (used)
Req 2 (wasted)
Free

50% memory wasted

✅ PagedAttention

R1
R2
R1
R3
R2

~96% utilisation

Continuous Batching

Traditional batching waits for an entire batch to finish before accepting new requests. Continuous batching inserts new requests mid-generation, as slots free up. This dramatically improves throughput for workloads with mixed request lengths.

Naive (fixed batch)
Dynamic batching
vLLM continuous batching
24×

FastAPI Wrapper (for Custom Models)

Python · Production-ready inference API
from fastapi import FastAPI
from pydantic import BaseModel
from transformers import pipeline
import uvicorn

app = FastAPI()
model = pipeline("text-generation", model="gpt2", device=0)

class Request(BaseModel):
    prompt: str
    max_tokens: int = 200
    temperature: float = 0.7

@app.post("/generate")
async def generate(req: Request):
    result = model(
        req.prompt,
        max_new_tokens=req.max_tokens,
        temperature=req.temperature,
        do_sample=True
    )
    return {"text": result[0]["generated_text"]}

@app.get("/health")
async def health(): return {"status": "ok"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000, workers=1)

Production Checklist

Use vLLM for open-source LLMs — 24× throughput over naive serving
Enable streaming — stream tokens as they're generated for better UX
Set max_model_len — cap context to save VRAM for more concurrent requests
Monitor GPU utilisation — should stay 70–90% during peak
Add rate limiting — protect against abuse with per-user token limits
Cache common queries — exact-match or semantic cache (Redis + vector DB)
Set up auto-scaling — scale GPU instances based on queue depth

Frequently Asked Questions

How many concurrent users can one GPU handle?

It depends heavily on model size, context length, and request rate. A single A100 running Llama 3 8B with vLLM can typically handle 50–200 concurrent streaming sessions. Use load testing (locust or k6) to find your specific saturation point.

What is Triton Inference Server?

NVIDIA Triton Inference Server is a more general inference serving system that supports PyTorch, TensorFlow, ONNX, TensorRT, and Python backends. It handles dynamic batching, model ensembles, and concurrent model execution. More complex to set up than vLLM but more flexible for non-LLM models.

How do I reduce time-to-first-token (TTFT)?

TTFT is latency until the first token appears. Reduce it by: (1) prefill caching — cache the KV cache for common system prompts, (2) speculative decoding — run a small draft model ahead, (3) use a smaller model for latency-critical paths, (4) reduce max context length for faster prefill.

Frequently Asked Questions

What does it mean to 'serve' an LLM?

Serving an LLM means putting a trained model behind an API so apps can send it prompts and get answers in real time. The serving layer handles incoming requests, runs the model on GPUs, and returns generated text quickly to many users at once.

What is continuous batching and why does it help?

Continuous batching groups many user requests together so the GPU stays busy. Instead of waiting for one batch to fully finish, it adds and removes requests on the fly as each one completes, which dramatically increases throughput and lowers cost per request.

What problem does vLLM's PagedAttention solve?

PagedAttention manages the model's memory like an operating system manages RAM — in small reusable pages. This stops memory from being wasted on padding and lets you serve many more concurrent requests on the same GPU.

Should I use vLLM or Triton Inference Server?

Use vLLM when you want the fastest, simplest way to serve text-generation LLMs with high throughput. Use Triton when you need to serve many model types together (vision, audio, custom pipelines) under one standardized server.