Cheat sheets

Serving Engines Compared

vLLM, SGLang, TRT-LLM and llama.cpp side by side. Schedulers, KV reuse, precision, offload and the pick.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Comparisons
  • Troubleshooting
  • Questions and tips

Source

Engines side by side#

FactorvLLMSGLangTRT-LLMllama.cpp
Core ideaPagedAttention, continuous batching, chunked prefillRadixAttention, cache-aware schedulerNVFP4 weights, FP8 KV, MTPFastest on CPU, widest model support
SchedulingV1 scheduler, FCFSDepth-first dispatch, branch-level LRUDisaggregated prefill/decodeFull control over threading
HardwareNVIDIA, AMD ROCmNVIDIA, AMDNVIDIA only, BlackwellCPU, Apple Silicon Metal
Headline number2,200-2,400 tok/s, 70B FP8, H100~16,200 vs ~12,500 tok/s on 8B$0.012/M tokens on GB200 NVL72Ollama is 15-30% slower
Weight formatHF safetensorsHF safetensorsCompiled per GPU SKUGGUF
Weak spotPrefix-heavy trafficUnique prompts, back to vLLM levelNo AMD, no Intel, no ARMNot the prod stage of the pipeline

From Lesson 17.04, Lesson 17.06, Lesson 17.07, Lesson 17.28

Use each engine when#

vLLM
general chat, 100+ users, AMD GPUs or mixed hardware.
SGLang
agents, RAG and chat that share long prefixes.
TRT-LLM
NVIDIA-only infra and cost-dominant work on Blackwell.
llama.cpp
CPU, edge or Apple Silicon.
Ollama
one user on a dev laptop. Not shared production.
TGI
existing deployments only. Maintenance mode since Dec 11, 2025.
Native CPU offload
one engine has HBM pressure.
LMCache
several engines share prefixes, or preemption is heavy.

From Lesson 17.28, Lesson 17.06, Lesson 17.07, Lesson 17.18

The vLLM scheduler loop#

while True:
finished = [s for s in RUNNING if s.is_done()]
for s in finished: release_blocks(s); RUNNING.remove(s)free blocks
while WAITING and have_free_blocks_for(WAITING[0]):admit on free KV
s = WAITING.pop(0)
allocate_initial_blocks(s)
RUNNING.append(s)
batch = []
for s in RUNNING:
if s.in_prefill:
batch.append(next_prefill_chunk(s))e.g. 512 tokens
else:
batch.append(decode_one_token(s))1 token
run_forward(batch)one fused GPU call

From Lesson 17.04

Production stack#

HTTProuteoffloadstorespillscrapeclientClientrequestsprocessRoutercache-aware, KV eventsgpuvLLM enginesone per GPU or TP/PP groupprocessConnector APIv0.9.0+, async in 0.11.0storeLMCache serverCPU DRAM, sharedstoreCeph/S3 tierdisk below DRAMprocessObservabilityPrometheus, Grafana, OTelHTTProuteoffloadstorespillscrapeclientClientrequestsprocessRoutercache-aware,KV eventsgpuvLLMenginesone perGPU orTP/PPgroupprocessConnectorAPIv0.9.0+,async in0.11.0storeLMCacheserverCPU DRAM,sharedstoreCeph/S3tierdisk belowDRAMprocessObservabilityPrometheus,Grafana,OTel

From Lesson 17.18

One request in the radix tree#

1Requestsystem prompt plus "Context: <doc A>" plus"Question: Carol".2Schedulerwalks the tree. The system prefix matches, 124blocks reused.3Schedulerthe doc A branch matches, 31 blocks reused.4Allocatorgives fresh blocks only to "Question: Carol", 4blocks.5Resultprefill of 4 blocks, not 160. About 40x lessprefill.6Evictionremoves whole branches from the shortest-usedleaves.

From Lesson 17.06

Precision and stack economics in main.py#

FactorH100 + BF16 + vLLMH100 + FP8 + vLLMB200 + NVFP4 + FP8 + TRT-LLMGB200 NVL72 + TRT-LLM + Dyn
HBM per GPU, GB8080192192
HBM bandwidth, TB/s3.353.358.008.00
Weight bits16844
KV cache bits16888
MTP factor1.01.01.81.8
Disaggregation factor1.01.01.62.5
Price per GPU hour2.502.504.806.20

From Lesson 17.07

Run the simulators#

TaskCode
Static batchingsimulate_static(reqs, batch=16)
Continuous plus chunkedsimulate_continuous(reqs, chunked=True)
Cache-aware radix ordersimulate(requests, "CACHE_AWARE")
Scrambled prefix orderworkload_scrambled(n=80, docs=4)
HBM footprint per stackhbm_footprint_gb(params_b, active_b, seq_len, stack)
Cost per million tokenscost_per_million_tokens(active_b, stack)
LMCache under HBM pressuresimulate("LMCACHE", reqs)
Engine for your constraintspick_engine(hardware, scale, workload)

From 17.04 code/main.py, 17.06 code/main.py, 17.07 code/main.py, 17.18 code/main.py, 17.28 code/main.py

Troubleshooting#

SymptomCauseFix
Decode stalls during a long promptPrefill freezes decodeChunked prefill. P99 ITL ~50 ms to ~15 ms
Hit rate 7% on shared promptsDynamic content in the prefixImmutable parts first. 7% to 74%
6.4x gain goneTemplate order changes per requestFix one component order
HBM 100%, same prompt prefilled againPreemption evicts KVNative CPU offload or LMCache
3-5% slower with LMCacheSmall HBM pressureTurn LMCache off
Accuracy drops after NVFP4FP4 weights on reasoning tasksFP8 weights plus FP4 activations
Attention collapses at FP4 KVKV needs dynamic rangeKeep KV cache in FP8
Features break togetherCombination not in the matrixCheck the matrix for your version

From Lesson 17.04, Lesson 17.06, Lesson 17.07, Lesson 17.18

Q&A#

Why is FCFS wrong for prefix traffic?
It evicts the hot branch to admit a short-prefix request.
What does chunked prefill protect?
The TTFT tail and ITL, not mean throughput.
Where does the 7x TRT-LLM gap come from?
HBM bandwidth, NVFP4 weights, MTP and disaggregation.
LMCache for prompts under 1K tokens?
No. Transfer time is more than re-prefill.
Do you enable PagedAttention?
No flag. It is the only allocator vLLM ships.
Does vLLM reuse prefixes?
Yes, with --enable-prefix-caching. SGLang is radix-first.

From Lesson 17.04, Lesson 17.06, Lesson 17.07, Lesson 17.18

Glossary#

ITL
inter-token latency. Time between two decode tokens.
TTFT
first token time. Prefill plus queue plus network.
FCFS
first-come first-served. Breaks prefix locality.
NVFP4
NVIDIA 4-bit microscaling format for weights and activations.
MTP
multi-token prediction. TRT-LLM integrated draft.
MXFP8
microscaling FP8 variant on Blackwell Tensor Cores.
Connector API
vLLM 0.9.0+ pluggable KV store interface.
Preemption
KV cache shuffle when HBM is full.

From Lesson 17.04, Lesson 17.06, Lesson 17.07, Lesson 17.18