From Notebook to Production SLA: Running vLLM on Kubernetes with the Production Stack
The Real Business Bottleneck: Reliability Under Load
Suppose you sell an AI service — a copilot, an agent pipeline, or an API wrapper with a contract attached. The bottleneck that kills deals is not model quality. It is what happens at concurrency; many users sending requests at the same time. Your demo runs beautifully for one user. Your customer's procurement team asks what happens when fifty people log in on Monday morning, and your honest answer is "I don't know" because you never measured it.
The plateau boundary is specific to your model, GPU, and version — even a healthy-looking p50 can hide a p95 an order of magnitude worse, so the only number that belongs in an SLA is the one you measured yourself on a concurrency ladder.
This article is about the gap between a notebook demo and an inference service you can back with a service level agreement. That agreement is a written promise to customers about response time and uptime. The vLLM project has published two artifacts that frame this gap well. The first is the production stack release blog from January 2025. It describes a reference deployment architecture; a router that spreads incoming requests across replicas, plus the Kubernetes manifests to run them; built for serving vLLM in production rather than ad hoc. The second is the GLM-5.2 production SLA blog from July 2026. It shows the end state: vLLM serving a production workload across 24 B300 GPUs with a latency SLA attached to it.
Between those two points sits the work most founders skip: measuring their own serving boundary before a customer measures it for them.
One caution before we go further, because it shapes everything below. A publicly documented data point; GitHub issue #42484 on vLLM 0.19.1 on H100; reports a measured throughput plateau as concurrency scales from 4 to 16. The issue documents the shape of the problem: throughput that looked linear in early testing stops growing as concurrent requests increase. It does not, as far as the public record goes, pin down a single root cause. That is precisely why your own benchmark matters: the plateau boundary is specific to your model, your GPU, your version, and your workload. Nobody else's number transfers.
Why Naive In-Prompt Solutions Fail
Founders under load pressure often reach for application-level fixes: shorter prompts, "be concise" instructions, batching at the client, retry loops. None of these address the actual constraint, and each has a concrete failure mode.
Prompt shortening changes quality, not capacity. Telling the model to be terse may cut output tokens somewhat. But under concurrency the bottleneck is how fast the GPU can juggle many conversations at once, not how many tokens you asked for politely. You degrade the product to dodge an infrastructure problem.
Client-side batching creates a queue you don't own. If your frontend batches requests and fires them at a single vLLM replica, you've moved the queue from the server into your own application. Users still wait; now you can't see or tune the wait, because it's hidden in your client code instead of in server-side scheduler metrics.
Retry loops amplify load. When latency spikes and clients time out and retry, your effective concurrency doubles at the worst possible moment. Retries backfire when the underlying problem is saturation; the server is already at its load ceiling; not flakiness.
Prompt caching is real but narrow. Server-side prefix caching (which vLLM supports) helps when many requests share a long prefix; a system prompt, a document. It does nothing for the long stretch while the model is still writing its answer, and it does nothing when your traffic is mixed. If your customers each send different conversations, few requests share a prefix.
The honest framing: none of these are wrong, but all of them depend on the deployment decision you make first. The decision that actually determines whether you can sign an SLA is how you deploy, replicate, route, and measure.
Production Architecture & Code Blueprints
The vLLM production stack, as described in the January 2025 release blog, gives you the structure: multiple vLLM engine replicas behind a router on Kubernetes, with manifests the project maintains. The July 2026 GLM-5.2 blog shows what the mature version looks like at scale. It serves a production SLA on 24 B300 GPUs, handling prompt reading and answer generation as separate stages. You do not need 24 GPUs to adopt the architecture. You need the shape of it.
Here is a minimal, runnable two-replica deployment. It assumes one Kubernetes node with at least one GPU (CPU-mode vLLM with a small model works for learning the plumbing; it will not teach you anything about GPU latency).
# vllm-replica-deployment.yaml
# Two vLLM engine replicas behind a service.
# Swap the model for something that fits your GPU budget;
# the architecture is the point, not the model size.
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-replica
labels:
app: vllm
spec:
replicas: 2
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args:
- "--model"
- "meta-llama/Llama-3.1-8B-Instruct" # pick a model your GPU holds
- "--served-model-name"
- "main"
- "--max-model-len"
- "8192"
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: 1 # one GPU per replica keeps
memory: "24Gi" # benchmarking attribution clean
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: vllm-svc
spec:
selector:
app: vllm
ports:
- port: 8000
targetPort: 8000
type: ClusterIP
# benchmark_concurrency.py
# Measure p50/p95 latency vs concurrency against the service above.
# The metric that matters for an SLA is latency *at your real
# concurrency*, not throughput at concurrency 1.
import asyncio, time, statistics
import httpx
URL = "http://vllm-svc:8000/v1/chat/completions"
PAYLOAD = {
"model": "main",
"messages": [{"role": "user", "content": "Summarize the plot of Moby Dick in 3 sentences."}],
"max_tokens": 128,
}
async def one_request(client, results):
t0 = time.perf_counter()
r = await client.post(URL, json=PAYLOAD, timeout=120)
dt = time.perf_counter() - t0
results.append((dt, r.status_code))
async def run_level(concurrency, n_total):
results = []
async with httpx.AsyncClient() as client:
# hold exactly `concurrency` requests in flight at all times
sem = asyncio.Semaphore(concurrency)
async def guarded():
async with sem:
await one_request(client, results)
await asyncio.gather(*[guarded() for _ in range(n_total)])
lats = sorted(d for d, code in results if code == 200)
if len(lats) < n_total * 0.99:
print(f"c={concurrency}: ERRORS (failed requests: {n_total - len(lats)})")
print(f"c={concurrency}: p50={lats[len(lats)//2]:.2f}s "
f"p95={lats[int(len(lats)*0.95)]:.2f}s")
async def main():
for c in [1, 2, 4, 8, 16]: # same ladder shape as the documented plateau issue
await run_level(c, n_total=c * 10)
asyncio.run(main())
Three things this blueprint gives you that a notebook never will:
Isolation of variables. One GPU per replica means when latency degrades, you know whether it's the engine or the neighbor. Two replicas means you can kill one mid-benchmark and measure how much extra latency customers feel while the surviving replica carries everything. Those are the two questions an SLA conversation will actually be about.
The concurrency ladder. The benchmark walks concurrency up 1 → 2 → 4 → 8 → 16, a ladder that brackets the 4 → 16 concurrency range documented in issue #42484. That issue reports a throughput plateau between 4 and 16 concurrent requests on H100 with vLLM 0.19.1; the measured pattern of a serving boundary. Whether your boundary sits at 8 or 40 depends on your model and hardware, which is exactly why you run the ladder yourself rather than citing someone else's plateau.
p50 and p95, not averages. An SLA is written against percentiles. If your p50 is 900ms and your p95 is 9 seconds, your average is a lie your customers' dashboards will expose within a week.
A note on honest gaps: the upstream production-stack release blog describes the architecture but publishes no latency table that transfers to your hardware. The GLM-5.2 SLA blog reports numbers for a 24x B300 cluster you do not own. Neither gives you your number. The methodology above is what produces yours.
Financial/ROI Impact for Founders
The arithmetic here is less about GPU cost curves and more about contract risk, so let's do the founder-relevant version.
An SLA-bearing AI contract typically prices in a latency commitment and a penalty or clawback for misses. The single most expensive thing you can do is sign a p95 commitment you have never measured. After one week of sustained traffic above your unmeasured saturation point, the load level where throughput stops growing as documented in issue #42484, you are paying penalties. That service was technically "up" the whole time. Reliability incidents that come from saturation look, to your monitoring, like success: requests are returning, CPU is busy, nothing is crashing.
The ROI of the benchmark exercise is therefore straightforward: it converts an unknown liability into a known capacity number. Once you have p50/p95 at each concurrency level, you can compute your per-replica safe concurrency; the highest load where p95 stays inside your SLA. Multiply by replica count and state a supported-user figure in your sales deck. That number is also your scale-out trigger: when sustained traffic approaches replicas × safe concurrency, you add a replica rather than discovering the plateau in production.
Now the cost side: the exercise above runs on hardware you likely already have, uses fully open-source manifests and scripts, and requires no vendor API keys. The marginal cost is hours of engineering time. The alternative; learning your saturation point from a customer's incident ticket; costs churn, and churn on a B2B contract is the most expensive line item you have. This is the same reasoning we apply when clients ask whether they can self-serve their inference at scale. The deployment work is learnable. The measurement discipline is what separates a demo from a service.
Clear CTA
The path from notebook to SLA is: production-stack architecture on Kubernetes, replicas sized to your GPU, a concurrency ladder benchmark, and percentiles you can put in a contract. Everything in the blueprints above is open source and runnable on a single GPU node today.
If you want to pressure-test your own setup; or you're staring at a signed SLA and a throughput curve that went flat; talk to us. Our AI infrastructure services cover exactly this arc, from prototype hardening to SLA-backed serving. Our Proof Studio exists to demonstrate deployment and benchmarking artifacts to your stakeholders before you commit budget. Bring your concurrency number; we'll tell you what it means.
Sell an AI tool with a claim like this?
We run your tool's claim in a sandbox and hand you proof assets your buyers can check — recorded runs, failures included, and a sales-ready claim table.
More in Articles
We installed Google Antigravity 2.0 in our lab and put its Agent Manager and browser-driving agents through a scripted battery of real repo tasks, including a browser-driven E2E test. Here is what worked, what broke, and whether it justifies a seat.
Why sharing your users' OAuth tokens with AI agents breaks audit, least privilege, and enterprise deals — and how to run agent identity with OBO exchange on a local stack.
Sonnet 5 costs a third less per token and counts your text differently. The break-even math, plus the caching and routing architecture that decides it.
Provider invoices bill credentials, not customers. Three LLM cost attribution patterns, a list-price model, and which control to build first.
Tools you can use
Free calculator: compare the monthly cost of an LLM API against self-hosting on your own GPU, and find the token volume where self-hosting starts to win.
Free calculator: estimate the GPU VRAM needed to run or fine-tune any LLM. Choose model size, precision, and mode (inference, LoRA, QLoRA, full fine-tune).