Area 05 // AI System DesignLesson 80 / 14016 min read
Serving models with vLLM
vLLM is a popular open-source inference server that runs open models fast and cheaply, mainly thanks to continuous batching (new requests join the batch at every step) and PagedAttention (the KV cache is stored in small pages, like computer memory), and it exposes an OpenAI-compatible API.
Think of it like this
Analogy
Continuous batching is a sushi conveyor belt instead of a set-menu restaurant. At a set-menu place, a table of four waits until the slowest eater finishes before the next group sits down. On a conveyor belt, the moment one diner leaves, a new one takes the seat: no seat sits empty.
PagedAttention is a hotel that hands out rooms one at a time as guests arrive, instead of reserving a whole floor for every booking "just in case they bring friends". Far more guests fit in the same building.
The problem it solves
Serving an open model with a naive loop wastes most of an expensive GPU: requests are processed one at a time or in rigid batches, short requests wait behind long ones, and each request reserves KV-cache memory for the maximum possible context, so only a few fit. Throughput is low and cost per token high. Purpose-built inference servers fix this; vLLM is one of the most widely used.
How it works: inference serving with vLLM
- Install and serve: on a Linux machine with a GPU,
pip install vllm, thenvllm serve <model-id>(e.g. a Hugging Face model). It starts an inference server on port 8000 by default. - OpenAI-compatible API: endpoints such as
/v1/chat/completions(and an Anthropic Messages-compatible API and gRPC as of November 2026), so existing clients just changebase_url. - Core techniques: continuous batching and PagedAttention (below), plus prefix caching, chunked prefill and speculative decoding.
- Fit bigger models: quantized weights (FP8, INT8, INT4, GPTQ/AWQ, GGUF) and parallelism across GPUs (tensor, pipeline, expert, data, context parallelism).
- Serve many adapters: multiple LoRA adapters on one base model (LoRA).
- Tune: max model length, GPU memory fraction, max concurrent sequences, quantization, and parallel size, then benchmark throughput and latency on your workload.
- Operate: run in containers (Docker) on GPU instances or Kubernetes, with metrics (Prometheus) for queue length, KV usage, tokens per second.
How it works: continuous batching
- Static batching groups N requests, runs them together until all finish, then starts the next batch. Short requests wait for the longest one; slots sit idle.
- Continuous batching (iteration-level scheduling) re-forms the batch at every decode step: finished sequences leave immediately and waiting requests join.
- Result: the GPU stays full, waiting time drops, and throughput rises dramatically for workloads with mixed output lengths.
- Chunked prefill splits long prompts into pieces so a big prefill doesn't stall everyone else's decoding.
How it works: PagedAttention
- The problem: each request's KV cache grows token by token. Naive servers reserve a contiguous block big enough for the maximum context per request, wasting most of it.
- PagedAttention splits the KV cache into small fixed-size blocks (pages, e.g. 16 tokens) and allocates them on demand, with a block table per request mapping logical positions to physical blocks, like an operating system's virtual memory.
- Almost no waste: only the last block of each request is partly empty.
- More concurrency: the saved memory holds more requests at once, enabling bigger batches.
- Sharing: requests with the same prefix (system prompt, documents) can point at the same physical blocks, which is how prefix caching works.
See it in code
- Needs
- Nothing for the simulation (plain Python 3). Serving real models needs a Linux machine with an NVIDIA GPU and pip install vllm.
How many decode steps static vs continuous batching need for the same eight requests, and how much KV memory paging saves.
# Requests that need different numbers of output tokens (decode steps).
lengths = [5, 40, 8, 6, 35, 7, 9, 4]
SLOTS = 4 # how many sequences fit on the GPU at once
def static_batching(lengths):
"""Old way: start a batch, wait until its LONGEST request finishes, then start the next."""
steps = 0
for i in range(0, len(lengths), SLOTS):
steps += max(lengths[i:i + SLOTS])
return steps
def continuous_batching(lengths):
"""vLLM way: every step, finished requests leave and waiting ones take their slot."""
waiting, running, steps = list(lengths), [], 0
while waiting or running:
while waiting and len(running) < SLOTS:
running.append(waiting.pop(0))
running = [r - 1 for r in running if r > 1] # one decode step for everyone
steps += 1
return steps
print("static batching :", static_batching(lengths), "steps")
print("continuous batching:", continuous_batching(lengths), "steps")
# PagedAttention: KV cache in fixed-size blocks instead of one big reserved region per request.
BLOCK = 16
reserved_naive = len(lengths) * 2048 # reserve max context for every request
used_paged = sum(-(-(100 + n) // BLOCK) * BLOCK for n in lengths) # 100 prompt tokens + output
print(f"KV slots reserved: naive {reserved_naive:,} vs paged {used_paged:,}")static batching : 75 steps
continuous batching: 40 steps
KV slots reserved: naive 16,384 vs paged 960Serving a real model and calling it with the OpenAI SDK:
pip install vllm
vllm serve Qwen/Qwen2.5-7B-Instruct --max-model-len 16384 # OpenAI-compatible API on :8000from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed") # your vLLM server
r = client.chat.completions.create(model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": "Say hello in French."}])
print(r.choices[0].message.content)A real example
Example
Ten times the throughput on the same GPUs
A company served a fine-tuned 8B model with a simple Python server using a Transformers loop: about 5 requests per second per GPU, with long queues at peak. Switching to vLLM with the same model and GPUs (continuous batching, PagedAttention, prefix caching for their 2,000-token system prompt) raised throughput to roughly 50 requests per second per GPU at similar p95 latency. They cut their GPU fleet from twelve to two, and their client code only changed base_url.
The easy way
Start here
On a laptop, use Ollama instead. When you have a Linux GPU server, vllm serve <model> gives you an OpenAI-compatible endpoint in one command; point your existing OpenAI-SDK code at it and benchmark.
When you grow: the production setup
Production deployments run vLLM in containers on Kubernetes or managed GPU platforms, with quantized models, prefix caching, tuned memory and concurrency limits, autoscaling on queue length, health checks, Prometheus metrics, a gateway in front (routing and fallbacks), and load tests before each model or version change. Alternatives worth benchmarking: SGLang, TensorRT-LLM / NVIDIA NIM and Hugging Face TGI.
Trade-offs
What you gain
- Much higher throughput and lower cost per token than naive serving.
- OpenAI-compatible API makes integration trivial.
- Broad model, quantization and parallelism support; active open-source project.
What it costs you
- Needs GPUs (mainly NVIDIA, Linux) and operations skills.
- Many tuning knobs; defaults may not match your workload.
- Fast-moving project: upgrades need testing.
When not to use it
- You only use closed API models, or traffic is too small to keep a GPU busy.
- Running on laptops or CPUs: Ollama or llama.cpp fit better.
Common mistakes
- Serving models with a hand-written loop and blaming the GPU for low throughput.
- Setting max model length far above what you need, starving the KV cache.
- Not enabling prefix caching for workloads with long shared prompts.
- Load-testing with uniform short requests that don't match real traffic.
- Upgrading the server version without re-benchmarking.
Interview questions
Try answering out loud first, then open the model answer.
Explain continuous batching and why it matters.
Model answer
Static batching runs a fixed group of requests until all finish, so short requests wait on long ones and GPU slots idle. Continuous (iteration-level) batching re-schedules at every decode step: completed sequences exit and queued ones join immediately, keeping the batch full. This raises GPU utilisation and throughput substantially and reduces queueing latency, especially with variable output lengths.
What is PagedAttention?
Model answer
A KV-cache memory manager that stores each sequence's keys and values in fixed-size blocks allocated on demand and tracked by a block table, analogous to virtual memory paging. It eliminates most internal and external fragmentation from reserving contiguous max-length buffers, enabling more concurrent sequences and larger batches, and allows sharing blocks between sequences with common prefixes for prefix caching.
How would you deploy an open model for production traffic?
Model answer
Choose a model and quantization that fits target GPUs with KV headroom; serve with vLLM (or SGLang/TGI) in containers; configure max length, concurrency and prefix caching; benchmark throughput and latency with realistic traffic; run multiple replicas behind a gateway with health checks, autoscaling on queue depth or KV usage, metrics and logging; and roll out new models or versions with canaries and evals.
Quiz
Pick an answer to see whether it’s right and why.
0 of 5 answered
Key terms
- vLLM
- A popular open-source engine for serving LLMs on GPUs fast, using PagedAttention, continuous batching and prefix caching, with an OpenAI-compatible API.
- Continuous batching
- A batching method where new requests join the running batch as soon as others finish, step by step, instead of waiting for the whole batch to end. Keeps GPUs busy with mixed-length replies.
- PagedAttention
- vLLM's technique of storing the KV cache in small fixed-size blocks (like pages in an operating system), so memory isn't wasted and many more requests fit on a GPU.
- KV cache
- Stored keys and values of already-processed tokens, so each new token can be generated without recomputing everything before it.
- Inference server
- Software that loads a model onto hardware and answers requests over an API, handling batching, memory and streaming. Examples include vLLM, SGLang, TGI and Ollama.
Before and after
Progress is saved in this browser only.