AI Development

vLLM vs SGLang in 2026: Serve LLMs Faster

September 12, 2026
11 min read
GPU server racks with glowing status lights in a dark data center
Share:

Your chatbot crawls under load, the GPU dashboard shows 60% utilization, and you already bumped the instance size twice. The model is fine — the inference engine is the bottleneck, and you are still running the default setup copied from a two-year-old tutorial.

In this deep-dive I compare vLLM and SGLang with real 2026 H100 numbers, explain RadixAttention vs prefix caching in five minutes, and give you verified serve commands, an OpenAI-compatible client, structured outputs, and a mini-benchmark so you can decide on your workload — not on a headline.

1. The Problem: The Benchmark Said 29%, Your p99 Did Not Move

Every few weeks a new post claims one engine beats the other by 29% — then teams migrate, debug kernel conflicts at midnight, and watch p99 latency sit exactly where it was. Both engines leapfrog each other every release, so a leaderboard delta is noise. What actually decides the winner is your prompt shape: unique prompts behave nothing like RAG loops where every request shares a 4k system prompt.

The Most Common Mistake

Migrating engines because of a headline number. The often-quoted 29% SGLang advantage was measured on Llama 3.1 8B with prefix-heavy traffic — on Llama 3.3 70B the same lab measured a 3–5% gap, and on an RTX 4090 with unique prompts a third lab measured a tie. Benchmark your prompts, not someone else’s.

The goal of this guide: understand the two caching philosophies, serve the same model on both engines with verified commands, hit both with identical code, and walk away with a decision rule you can defend in a design review.

2. Minimal Concepts You Actually Need

Four ideas explain nearly every benchmark you will read this year. Get these and the numbers below read themselves.

🧱

PagedAttention (vLLM origin)

KV cache paging · SOSP 2023

vLLM split the KV cache into paged blocks like OS memory, killing fragmentation and enabling continuous batching. Its automatic prefix caching is hash-based: full 16-token blocks with identical hashes are reused across requests — almost free lunch, no output change.

🌳

RadixAttention (SGLang origin)

Radix tree reuse · NeurIPS 2024

SGLang keeps finished requests’ KV cache in a radix tree and reuses shared prefixes at token granularity — few-shot examples, chat history, agent templates, RAG context. The longer your shared prefix, the bigger the prefill savings and the lower your time-to-first-token.

⏱️

TTFT vs ITL vs Throughput

Latency users feel · GPUs bill

TTFT (time to first token) is what chat users feel; ITL (inter-token latency) is streaming smoothness; throughput (tokens/sec) is what you pay per GPU-hour. Prefix caching mostly attacks prefill cost, so it moves TTFT and throughput on shared-prefix workloads — rarely tail ITL.

✂️

Disaggregated Prefill/Decode

Experimental · both engines

Split prefill and decode onto separate instances to tune TTFT and ITL independently and control tail latency. The official vLLM docs are explicit: disaggregated prefill DOES NOT improve throughput — it is a latency-shaping tool, not a speedup.

What the 2026 H100 Numbers Actually Say

Techsy’s September 2026 H100 roundup (Spheron’s Llama 3.3 70B FP8 runs + PremAI’s Llama 3.1 8B runs) plus ComputingForGeeks’ RTX 4090 tie tell one consistent story: SGLang pulls ahead when prefixes are shared and models are small; the gap collapses on big models and unique prompts.

Llama 3.3 70B FP8 · H100

2,400 vs 2,460 tok/s @ conc 100 · TTFT p50 740 vs 710 ms

≈3% gap — pick either

Llama 3.1 8B · H100 · prefix-heavy

12,500 vs 16,200 tok/s (SGLang +29%)

SGLang wins shared prefixes

Qwen2.5-7B · RTX 4090 · mixed

5,221 vs 5,155 tok/s — inside run-to-run noise

Honest tie

The One-Paragraph Mental Model

vLLM is the safe default: broadest model coverage, pipeline parallelism for very large models, biggest contributor base. SGLang wins prefix-heavy, structured-generation, and MoE workloads by roughly 10–30% end to end. Both expose OpenAI-compatible endpoints, so your client code barely changes — the server flags are what differ.

3. Tutorial: Serve One Model on Both Engines, Step by Step

We will serve Llama 3.1 8B Instruct on vLLM (:8000) and SGLang (:30000) on the same GPU host, smoke-test both, call both with identical Python, force JSON output on both, and run a 5-minute load comparison. Six steps, official commands only.

Step 1

Serve with vLLM (+ prefix caching on)

vLLM’s serve CLI wraps the OpenAI-compatible server. --enable-prefix-caching turns on the hash-based automatic prefix cache (off by default in some builds); --gpu-memory-utilization 0.9 reserves VRAM for weights plus KV cache the way SGLang’s mem fraction does.

pip install vllm openai

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 0.0.0.0 --port 8000 \
  --enable-prefix-caching \
  --gpu-memory-utilization 0.9

Step 2

Serve with SGLang (same model, port 30000)

SGLang’s launcher downloads weights on first run, allocates the KV pool, and prints pool sizing when ready. --mem-fraction-static 0.9 is the VRAM share for weights plus KV cache — the direct analogue of vLLM’s utilization flag above.

pip install sglang openai

python3 -m sglang.launch_server \
  --model-path meta-llama/Llama-3.1-8B-Instruct \
  --host 0.0.0.0 --port 30000 \
  --mem-fraction-static 0.9

Step 3

Smoke-test both endpoints

Both servers expose /v1/models and /v1/chat/completions. If these two curls do not list your model, fix the server before touching client code — nine times out of ten it is a wrong port or an OOM during weight load.

curl http://localhost:8000/v1/models
curl http://localhost:30000/v1/models

Step 4

One Python client, both engines

This is the verified OpenAI-compatible pattern from the SGLang docs — only base_url changes. Note the shared system prompt: that is exactly the prefix shape where RadixAttention earns its keep, so keep prompts identical or the comparison is meaningless.

from openai import OpenAI

SYSTEM = 'You are a concise release-notes writer for developer tools.'
USER = 'Summarize these changes in 3 bullets: faster KV cache, new tokenizer, fixed streaming.'

def ask(base_url):
    client = OpenAI(base_url=base_url, api_key='EMPTY')
    r = client.chat.completions.create(
        model='meta-llama/Llama-3.1-8B-Instruct',
        messages=[{'role': 'system', 'content': SYSTEM},
                  {'role': 'user', 'content': USER}],
        temperature=0, max_tokens=128,
    )
    return r.choices[0].message.content

print('vLLM:  ', ask('http://localhost:8000/v1'))
print('SGLang:', ask('http://localhost:30000/v1'))

Step 5

Structured outputs on both

Agents need JSON that parses. vLLM takes guided_json in extra_body (grammar-constrained decoding); SGLang accepts the OpenAI response_format json_object the same way. Same schema, both engines, zero parsing hacks.

schema = {'type': 'object', 'properties': {
    'title': {'type': 'string'}, 'bullets': {'type': 'array', 'items': {'type': 'string'}}},
    'required': ['title', 'bullets']}

# vLLM: grammar-constrained JSON
vllm = OpenAI(base_url='http://localhost:8000/v1', api_key='EMPTY')
r1 = vllm.chat.completions.create(model='meta-llama/Llama-3.1-8B-Instruct',
    messages=[{'role': 'user', 'content': USER}],
    extra_body={'guided_json': schema}, max_tokens=256)

# SGLang: OpenAI-style JSON mode
sgl = OpenAI(base_url='http://localhost:30000/v1', api_key='EMPTY')
r2 = sgl.chat.completions.create(model='meta-llama/Llama-3.1-8B-Instruct',
    messages=[{'role': 'user', 'content': USER}],
    response_format={'type': 'json_object'}, max_tokens=256)

Step 6

Mini-benchmark: concurrency sweep that fits in 5 minutes

Same prompts, same concurrency ladder, both ports. Measure total tokens over wall time — that is the throughput number that maps to your GPU bill. Expect SGLang to open a lead as shared-prefix share grows; on unique prompts expect a tie.

import asyncio, time
from openai import AsyncOpenAI

PROMPTS = [USER] * 32  # replace with YOUR production prompts

async def bench(base_url, conc=16):
    client = AsyncOpenAI(base_url=base_url, api_key='EMPTY')
    sem = asyncio.Semaphore(conc)
    async def one(p):
        async with sem:
            r = await client.chat.completions.create(
                model='meta-llama/Llama-3.1-8B-Instruct',
                messages=[{'role': 'system', 'content': SYSTEM},
                          {'role': 'user', 'content': p}],
                max_tokens=128)
            return len(r.choices[0].message.content.split())
    t0 = time.time()
    toks = sum(await asyncio.gather(*[one(p) for p in PROMPTS]))
    dt = time.time() - t0
    return toks / dt

for conc in (1, 8, 32):
    v = await bench('http://localhost:8000/v1', conc)
    s = await bench('http://localhost:30000/v1', conc)
    print(f'conc={conc:3d}  vLLM={v:7.1f} tok/s  SGLang={s:7.1f} tok/s')

How You Know It Worked

Both /v1/models curls list Llama 3.1 8B, the Step 4 script prints two sane summaries, both Step 5 responses pass json.loads on the first try, and your Step 6 sweep shows the gap growing with shared-prefix share — that curve, not any single number, is your migration business case.

4. Common Errors That Invalidate Your Comparison

I have reviewed enough engine shootouts to spot the failure modes by smell. Avoid these five and your numbers will survive contact with production.

🙈

Benchmarking with unique prompts, then quoting the prefix-caching gap

Batch evals and data-gen prompts share almost nothing, so RadixAttention has nothing to reuse. If your traffic is unique prompts, expect the RTX 4090 outcome — a tie — and choose on ecosystem, not throughput. Match the benchmark shape to YOUR traffic mix.

🚩

Forgetting --enable-prefix-caching on vLLM

vLLM’s automatic prefix cache is a server flag, not a per-request field — and it only caches full blocks. Comparing stock vLLM against SGLang’s always-on radix tree is rigging the test. Set the flag, and remember partial trailing blocks never hit.

💥

OOM at load: VRAM fractions set by copy-paste

Blindly copying --gpu-memory-utilization 0.9 or --mem-fraction-static 0.9 onto a smaller card (or alongside another tenant) dies during CUDA-graph capture. Size from the pool log SGLang prints at startup, leave headroom for long contexts, and re-tune per GPU — 4090 numbers do not transfer to H100s.

🔀

Deploying disaggregated serving to “go faster”

Prefill/decode split is experimental and, per the official docs, DOES NOT improve throughput — it trades operational complexity for TTFT/ITL control and calmer tail latency. If your problem is tokens-per-dollar, fix batching and caching first; disaggregate only for strict interactive SLOs.

🔌

Assuming the clients are 100% interchangeable

Base chat calls port cleanly, but advanced flags diverge: vLLM’s guided_json / guided_regex / tool-call parsers vs SGLang’s response_format / reasoning parsers / LoRA model:adapter syntax. Audit every extra_body field before swapping servers or your agents break in new and exciting ways.

Golden rule

Start with vLLM for breadth, switch to SGLang when profiling proves shared-prefix or structured-generation throughput is the constraint. Re-run YOUR sweep every quarter — both projects ship every few weeks and today’s 29% is next quarter’s tie.

Conclusion

On 2026 H100s the honest summary fits in one line: SGLang wins prefix-heavy small-model serving by up to ~29%, ties on big models and unique prompts, and both speak OpenAI-compatible APIs so the migration cost is one config change plus a flag audit.

Run the six steps above with your production prompts this week. If the concurrency curve bends toward SGLang, migrate the prefix-heavy services and leave the rest on vLLM — heterogeneous fleets are normal, and the engines coexist behind the same load balancer.

Decision Framework: Summary

Pick SGLang when

  • • RAG / chat / agents share long prefixes
  • • Heavy structured-output pipelines
  • • MoE models (e.g. DeepSeek-class)

Pick vLLM when

  • • Prompts are mostly unique (evals, batch)
  • • You need pipeline parallelism / exotic archs
  • • Widest integrations and docs matter most

Always

  • • Enable prefix caching explicitly
  • • Benchmark your prompts per quarter
  • • Audit extra_body flags before swapping

Sources

Diego Rodriguez

Diego Rodriguez

Senior Full-Stack & AI Engineer

Diego has 10+ years of experience building production-grade AI-powered applications, from LLM orchestration and RAG pipelines to ML-driven risk detection and algorithmic trading systems.

Learn more about Diego