Decision SOP for serving LLMs with vLLM. Covers PagedAttention mental model, quantization/parallelism/batching tradeoffs, OOM triage, and when NOT to use vLLM. Activates when a coder-agent is choosing or tuning an inference engine, debugging vLLM throughput/latency/OOM, or comparing vLLM against TGI/SGLang/TensorRT-LLM/llama.cpp.
npx skills add https://github.com/agentsope/SkillAlchemy --skill agentsop-vllm
Activate this skill when any of the following hold:
Do NOT activate for: training/fine-tuning (use accelerate/deepspeed/trl), CPU-only edge inference (use llama.cpp/Ollama), Apple Silicon production (vLLM Metal/MPS is experimental, not production-ready as of 2026) [aimadetools.com 2026], API-only consumption of hosted models (just call the OpenAI/Anthropic SDK).
vLLM's defining insight (Kwon et al., SOSP 2023) is that LLM serving's bottleneck was not compute — it was KV-cache memory fragmentation. Pre-vLLM systems pre-allocated a contiguous KV-cache slot per request, sized for the maximum possible output length; in early 2023, inference engines used only 20–40% of available GPU memory because of internal+external fragmentation [arxiv.org/abs/2309.06180; zilliz.com/learn].
PagedAttention applies classic OS paging to KV cache:
Result: near-zero memory waste → larger batch sizes → 2–4× throughput vs FasterTransformer/Orca at equal latency [arxiv.org/abs/2309.06180]; 14–24× vs vanilla HuggingFace Transformers [yottalabs.ai 2026].
vLLM inherits Orca's iteration-level scheduling (OSDI 2022, 36.9× over FasterTransformer [medium.com/byte-sized-ai]). Instead of waiting for a static batch to finish, the scheduler reassigns batch slots every decode step: a request that finishes early frees its slot to a waiting request. Static batching is dead; continuous batching is table stakes.
Takeaway: when tuning, separate TTFT (time-to-first-token, gated by prefill+queue) from ITL (gated by decode bandwidth and batch interference).
Per Red Hat's tuning hierarchy [developers.redhat.com 2026]:
[Step 0] Confirm vLLM is the right tool
├─ Production, GPU-backed, concurrent users? → continue
└─ Else → see §7 (ecosystem) and stop
[Step 1] Pick the model + precision
├─ Model fits in single-GPU VRAM at BF16? → keep BF16, TP=1
├─ Need 50% VRAM cut, ~zero quality loss? → FP8 (Hopper/Ada+) [arxiv 2411.02355]
├─ Need 4× VRAM cut, tolerate ~1.6pt avg drop? → AWQ-4 or GPTQ-4
└─ Reasoning-heavy / coding workload? → favor FP8 > AWQ; verify on eval set
[Step 2] Choose parallelism
├─ Fits 1 GPU → TP=1, PP=1
├─ Fits 1 node, NVLink present → TP=#GPUs/node
├─ Fits 1 node, only PCIe (e.g. L40S) → PP within node (TP-only over PCIe collapses)
├─ Multi-node → TP=GPUs/node, PP=#nodes
└─ MoE model (Mixtral, DSv3) → DP attention + EP/TP for MoE layers
[Step 3] Set memory/batch envelope
├─ --gpu-memory-utilization 0.90 (default; 0.85 if sharing GPU)
├─ --max-model-len = (longest realistic prompt + output) — NOT model max!
├─ --max-num-seqs (start 256; lower if preemption logs appear)
└─ --max-num-batched-tokens (raise for TTFT; lower for ITL)
[Step 4] Turn on the free wins
├─ enable_prefix_caching=True → if any system-prompt/few-shot reuse
├─ enable_chunked_prefill=True (V1: default on) → tame long-prompt HoL blocking
└─ kv_cache_dtype="fp8" → +KV headroom, Ampere+ only
[Step 5] Optional: speculative decoding
├─ Low QPS, latency-bound, have draft/EAGLE weights? → EAGLE-3 or MTP (high gain)
├─ No draft model, zero setup cost? → n-gram (modest gain, ~1.17×)
└─ High QPS / large batch? → skip; gains shrink, complexity rises
[Step 6] Benchmark on YOUR workload
├─ Replay representative ISL/OSL distribution
├─ Watch Prometheus: num_requests_waiting, KV cache occupancy, preemption count
└─ Tune in this order: §3 Step 3 → Step 4 → Step 5 → reconsider Step 2
[Step 7] Scale out
├─ Latency SLA violated under load? → add a replica (data parallelism across pods)
├─ Tail TTFT high? → prefix-aware routing; pin prefix to replica
└─ Cost too high? → revisit quantization, smaller model, draft model
torch.OutOfMemoryError: CUDA out of memory during engine init or warmup.--max-model-len to the realistic max (prompt + output), not the model's architectural max [markaicode.com 2026].--gpu-memory-utilization to 0.85 if other processes share the GPU; raise to 0.95 if vLLM is alone and KV cache is too small.--kv-cache-dtype fp8 (Ampere+) or --enforce-eager (skip CUDA-graph reservation).--max-num-seqs (e.g. 256 → 64 → 16).--tensor-parallel-size to shard the model.--quantization {fp8|awq|gptq|...} flag; documented expected accuracy delta.--tensor-parallel-size=<GPUs in node>, PP=1.--pipeline-parallel-size instead of large TP — all-reduce over PCIe will tank TP throughput [docs.vllm.ai parallelism_scaling].TP=GPUs/node, PP=#nodes. Use InfiniBand if possible.--enable-prefix-caching (in V1, often on by default).num_requests_waiting > 0 sustained → engine queue-bound; check num_requests_running.--max-num-seqs or quantize KV to FP8.--speculative-config JSON, with measured tokens/s delta on representative traffic.Situation: A team serves Llama-3-70B-FP8 on 2×H100. P50 TTFT is fine at 250 ms, but P99 spikes to 4 s when traffic bursts. They consider raising --max-num-seqs from 64 to 256 to handle bursts.
Tension:
max_num_seqs → more concurrency → higher throughput, but also more contention for KV cache → preemption + head-of-line blocking on prefills.max_num_batched_tokens → better TTFT (more prefill per step) → worse ITL for streaming requests already mid-decode.Resolution heuristic:
num_requests_waiting > 0 and KV occupancy < 80%: raise max_num_seqs.max_model_len.max_num_batched_tokens (e.g. 2048) to favor decode/ITL; for batch/offline: raise (≥8192) to favor TTFT/throughput [docs.vllm.ai optimization; anyscale.com].Evidence: [developers.redhat.com 2026 5-steps-triage]; [medium.com/@kaige.yang0110].
Situation: Deploying Qwen-72B on a single H100 (80 GB). At BF16, model is 144 GB → doesn't fit. Options: (a) FP8, 72 GB → fits with tight KV budget; (b) AWQ-4, 36 GB → comfortable KV budget, larger batches.
Tension:
Resolution heuristic:
Evidence: [arxiv.org/abs/2411.02355]; [docs.gpustack.ai vLLM quantization].
Situation: 4×A100-80GB available, NVLink. Choice: one big replica (TP=4, max single-request latency optimized) or two replicas (TP=2, more concurrency).
Tension:
Resolution heuristic:
vllm bench serve on representative ISL/OSL.Evidence: [docs.vllm.ai parallelism_scaling]; [docs.jarvislabs.ai scaling-llm-inference-dp-pp-tp].
Situation: Production RAG service. Each request has a 2 KB system prompt + 4–16 KB context + short user query. They wonder whether enable_prefix_caching helps enough to justify the held KV blocks.
Tension:
Resolution heuristic:
Evidence: [docs.vllm.ai/en/stable/design/prefix_caching/]; [bentoml.com/llm/inference-optimization/prefix-caching].
Situation: Single-user, latency-sensitive coding assistant on Llama-3-70B. Decode dominates total latency.
Tension:
Resolution heuristic:
Evidence: [docs.vllm.ai/en/latest/features/speculative_decoding/]; [developers.redhat.com 2025 fly-eagle3-fly].
max_model_len to the model's architectural max "just in case"A Llama-3.1 model supports 128k context. If you serve 4k-prompt workloads but set --max-model-len 131072, vLLM reserves KV-cache slots for the worst case → smaller batches → throughput collapse. Set it to your realistic max (longest_prompt + longest_output + safety margin) [markaicode.com 2026].
APC accelerates prefill only. If outputs are long and prefixes don't repeat, gain ≈ 0 — and you've consumed KV memory for nothing [docs.vllm.ai automatic_prefix_caching]. Measure shared-prefix fraction before enabling for high-pressure workloads.
Tensor parallelism issues an all-reduce after each layer. NVLink (≈900 GB/s bidirectional) handles it; PCIe Gen4 x16 (≈32 GB/s, ~28× slower) does not [spheron.network 2026]. On L40S / consumer cards / cross-NUMA setups, use pipeline parallelism or independent replicas instead [docs.vllm.ai parallelism_scaling].
Academic benchmarks (MMLU, HellaSwag) show GPTQ ≈ AWQ; real coding workloads can show meaningfully larger gaps between methods, and quality at INT3 collapses (~6-point drop) [arxiv.org/abs/2411.02355]. Always test on your eval set.
vLLM needs minimum 2 + N physical CPU cores for N GPUs; under-provisioning CPUs makes the API server, tokenizer, and detokenizer the bottleneck before the GPU is touched [docs.vllm.ai/en/stable/configuration/optimization/]. Scale CPU and use --api-server-count when needed.
--enforce-eager in production "for stability"--enforce-eager skips CUDA graph capture → slower decode. It's a debugging/memory-OOM workaround, not a steady-state production setting. Fix the underlying OOM (KV dtype, max_model_len, max_num_seqs) and re-enable CUDA graphs.
vLLM is the right tool when:
vLLM is the wrong tool when:
guided_decoding (consider SGLang for complex agent state machines).| Engine | Sweet spot | When to pick over vLLM |
|---|---|---|
| vLLM | Production GPU serving, mixed traffic, open weights, vendor-neutral | Default first choice for throughput-oriented LLM serving in 2026 |
| TGI (HuggingFace) | Was the HF default | Officially in maintenance mode; HF themselves now recommend vLLM or SGLang [yottalabs.ai 2026] |
| SGLang | Heavy prefix sharing, agent/RAG state machines, structured generation | ~29% higher throughput than vLLM when requests share context (chatbots, RAG, agents) thanks to RadixAttention prefix tree [n1n.ai 2026] |
| TensorRT-LLM | Single-vendor NVIDIA, max throughput, willing to invest setup | Up to 30–50% higher throughput than vLLM in high-concurrency NVIDIA-only deployments; 1–2 weeks setup; vendor lock-in [n1n.ai 2026] |
| llama.cpp | CPU, edge, Apple Silicon, single-user, GGUF | No GPU available; ≤1 concurrent user; minimal-deps deploy [aimadetools.com 2026] |
| Ollama | Local dev, prototyping, model switching | Developer ergonomics over throughput; 5-minute setup [contracollective.com 2026] |
Common pattern: develop on Ollama → benchmark with vLLM → consider SGLang if prefix-sharing workload → consider TensorRT-LLM only if NVIDIA-locked and engineering budget is large.
A production-ish vLLM serve for Llama-3.1-70B-Instruct-FP8 on 2×H100 with NVLink, RAG-style workload:
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--quantization fp8 \
--tensor-parallel-size 2 \
--max-model-len 8192 \
--max-num-seqs 256 \
--max-num-batched-tokens 8192 \
--gpu-memory-utilization 0.90 \
--enable-prefix-caching \
--enable-chunked-prefill \
--kv-cache-dtype fp8
Then triage with the §4 OP-5 5-step workflow on real traffic before tuning further.
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
Access NCBI GEO for gene expression/genomics data. Search/download microarray and RNA-seq datasets (GSE, GSM, GPL), retrieve SOFT/Matrix files, for transcriptomics and expression analysis.
Bayesian modeling with PyMC. Build hierarchical models, MCMC (NUTS), variational inference, LOO/WAIC comparison, posterior checks, for probabilistic programming and inference.
Multi-objective optimization framework. NSGA-II, NSGA-III, MOEA/D, Pareto fronts, constraint handling, benchmarks (ZDT, DTLZ), for engineering design and optimization problems.
Statistical modeling toolkit. OLS, GLM, logistic, ARIMA, time series, hypothesis tests, diagnostics, AIC/BIC, for rigorous statistical inference and econometric analysis.
Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.
Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.
Write docstrings for PyTorch functions and methods following PyTorch conventions. Use when writing or updating docstrings in PyTorch code.
Take agentsope/agentsop-vllm from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.