agentsope/agentsop-vllm
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.
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.