Have vLLM operated instead of running it yourself?
On the Managed GPU Server, the model, vLLM and Open WebUI run on a dedicated GPU in Germany, with monitoring and updates, from EUR 699 net per month plus a one-time setup fee. The ongoing review of the application on top (traces, evaluations, costs) is covered by Managed LLMOps.
A vllm serve runs within minutes. Production needs more: a memory calculation, bounded parameters, security, metrics, an update procedure and a load test. This guide is an operations manual for vLLM with Docker, checked against the vLLM documentation and the source code. Status 28 October 2026, current version vLLM 0.32.0. What is vLLM? explains what vLLM is, vLLM vs. Ollama compares it with Ollama.
Table of contents
- Plan memory: weights and KV cache
- Start vLLM with Docker Compose
- The server parameters that matter
- Kubernetes: notes
- Health checks and readiness
- Metrics, LiteLLM and Langfuse
- Securing vLLM
- Updates and version pinning
- Typical errors
- Load testing without invented numbers
- How to evaluate vLLM consulting
- Status and methodology
- Our approach at WZ-IT
- Further guides
Plan memory: weights and KV cache
Production starts with context and concurrency that fit the GPU memory. GPU memory splits into weights, KV cache and a reserve:
- Weights = file size of the checkpoint (parameters × bytes per parameter).
- KV cache per token = 2 × layers × KV heads × head dimension × bytes (classic attention, values from
config.json). With multi-head latent attention, vLLM stores onlykv_lora_rank + qk_rope_head_dimvalues per layer (source code). - Available for the cache = GPUs × GPU memory ×
--gpu-memory-utilization(default 0.92, CLI reference) - weights - reserve. The sum over all GPUs holds as long as tensor parallelism does not exceed the number of KV heads. With MLA models that have one latent KV head, the cache for each token sits on every GPU of the tensor-parallel group, so the budget applies per GPU (remedy:--decode-context-parallel-size, vLLM docs). - Sessions = available ÷ (KV cache per token × context length).
Example Ministral 3 14B in FP8 (model card): 15.73 GB of weights (15,726,706,472 bytes), 40 layers, 8 KV heads, head dimension 128, so 160 KiB per token in BF16.
| GPU | Available for cache | Tokens in cache | Sessions of 32,768 tokens |
|---|---|---|---|
| 96 GB | 96 × 0.92 - 15.73 - 4 = 68.59 GB | about 418,700 | 12.8 |
| 24 GB | 24 × 0.92 - 15.73 - 4 = 2.35 GB | about 14,400 | 0.4 |
Assumptions: 4 GB reserve per GPU for activations and CUDA graphs (planning value, not a measurement), KV cache in BF16, 1 GB = 10^9 bytes. The table uses the default 0.92, the Compose file below sets 0.90 and reduces the budget by 1.9 GB (96 GB) or 0.5 GB (24 GB). On the 24 GB card the cache by calculation cannot hold a single context of 32,768 tokens, which calls for a smaller --max-model-len (or auto, which picks the largest value that fits). At startup vLLM logs the actual values (Available KV cache memory, GPU KV cache size: … tokens, Maximum concurrency for … tokens per request). The derivation is in Sizing GPU and VRAM for LLMs, quantization is covered by LLM quantization.
Start vLLM with Docker Compose
The model is downloaded once beforehand (HF_HOME=/srv/models/huggingface hf download mistralai/Ministral-3-14B-Instruct-2512), which the vLLM troubleshooting guide recommends. After that this compose.yaml is enough (sized for the 96 GB row of the table, on 24 GB --max-model-len has to be smaller):
services:
vllm:
image: vllm/vllm-openai:v0.32.0
restart: unless-stopped
shm_size: "16gb"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
VLLM_API_KEY: ${VLLM_API_KEY}
VLLM_NO_USAGE_STATS: "1"
HF_HUB_DISABLE_TELEMETRY: "1"
HF_HUB_OFFLINE: "1"
volumes:
- /srv/models/huggingface:/root/.cache/huggingface
- vllm-cache:/root/.cache/vllm
ports:
- "127.0.0.1:8000:8000"
command:
- --model=mistralai/Ministral-3-14B-Instruct-2512
- --served-model-name=assistent
- --tokenizer-mode=mistral
- --config-format=mistral
- --load-format=mistral
- --max-model-len=32768
- --gpu-memory-utilization=0.90
- --max-num-seqs=16
- --enable-auto-tool-choice
- --tool-call-parser=mistral
volumes:
vllm-cache:
What differs from the tutorial default:
- Fixed image version instead of
latest. The image startsvllm serve, parameters follow the image name. vLLM needs--ipc=hostor--shm-size(Docker docs). - Compile cache on a volume (
/root/.cache/vllm). Otherwise every new container compiles the model artifacts again. - GPU reservation as in the Compose documentation, with the NVIDIA Container Toolkit installed.
- Port on loopback only. Published Docker ports are otherwise reachable from outside as well (Docker docs).
- Key in
.env(file mode 600), not in the Compose file. - Tokenizer mode and tool-calling flags come from the model card. Other models need other parsers.
- Optionally the container runs without root:
--user 2000:0with mounts under/home/vllm(Docker docs).
The server parameters that matter
Sources: CLI reference, optimization guide and parallelism.
| Parameter | Effect | Rule for operations |
|---|---|---|
--max-model-len |
Context of prompt plus answer. Without a value, the model configuration applies, 256k for current Mistral models. | Set it to the real need, the startup log calculates concurrency with this value. |
--gpu-memory-utilization |
Share of GPU memory per instance, default 0.92. | The Compose example sets 0.90 as a cautious starting value (an assessment, not a value from the docs). Two instances on one GPU: sum at most 1. |
--tensor-parallel-size |
GPUs per model. | As small as possible. The number of attention heads must be divisible by the value (source code), and the value must also divide the number of KV heads or be a multiple of it (source code). A power of two is not required. Without NVLink the docs recommend pipeline parallelism, see multi-GPU inference. |
--max-num-seqs |
Maximum concurrent sequences. | Match it to the cache, lower it when preemptions are frequent. |
--max-num-batched-tokens |
Token budget per step. | 2048 improves time between tokens, above 8192 improves throughput. Choose by load test. |
--enable-prefix-caching |
Reuse of the cache for an identical prompt start. Active by default if the model supports it, switch off with --disable-prefix-caching. |
Helps with a long system prompt or repeated documents, not with long answers (docs). Check hits via metrics. |
--kv-cache-dtype fp8 |
Halves the cache per token. | Check quality with your own questions (docs). |
--enable-auto-tool-choice with --tool-call-parser |
Tool calling for agents. | Parser matching the model (docs). |
--served-model-name |
API name, also the model_name label of the metrics. |
Use a stable name so a model change does not break clients. |
--generation-config |
Source of sampling defaults, default auto reads the model's generation_config.json. |
For quality questions, cross-check with vllm. |
--api-key |
Bearer key for the API. | Only one building block, see securing vLLM. |
Parameters can also be kept in a YAML file (vllm serve --config), values on the command line take precedence (docs).
Kubernetes: notes
Kubernetes pays off with several GPU nodes, replicas or an existing cluster with an operating process, for a few GPUs Compose is the smaller operational surface. The vLLM guide for Kubernetes and the Kubernetes documentation on GPUs name the key points:
- GPU under
limits:nvidia.com/gpubelongs inlimits. Requests may be omitted (Kubernetes then takes the limit) or must be equal, requests without limits are not allowed. - Shared memory: an
emptyDirwithmedium: Memoryat/dev/shm. - Model cache on a PersistentVolumeClaim, a Hugging Face token as a Secret only for gated models.
- Startup probe with a sufficient
failureThreshold. If it is too low, Kubernetes kills the container while it loads (log:KeyboardInterrupt: terminated). The value comes from measuring the startup time without a probe. - Fixed image version. The examples in the docs use
latest.
Several replicas with routing and autoscaling are covered by the production-stack with a Helm chart, maintained by the vLLM project. General cluster requirements are covered by the Kubernetes production checklist.
Health checks and readiness
/health reports the state of the engine, without an API key. The docs use the endpoint for liveness and readiness. Three additions belong in operations:
- Startup probe for the loading time of large models (Kubernetes probes).
- Synthetic request: monitoring regularly sends a short chat request with
max_tokens: 1and the API key to/v1/chat/completionsand measures the time until the answer starts./healthshows that the process is alive, not that the model answers. - Probes bypass the proxy:
/healthdoes not pass the reverse proxy, probes go directly to the container or pod.
Metrics, LiteLLM and Langfuse
vLLM exposes Prometheus metrics at /metrics (metrics docs), without authentication even when --api-key is set (security docs). Counters carry the suffix _total in Prometheus, for example vllm:prompt_tokens_total in the Grafana example of the docs. Check the names with curl http://localhost:8000/metrics before writing alert rules, the table lists the counters with this suffix.
| Question | Metric |
|---|---|
| Is the cache full? | vllm:kv_cache_usage_perc (1 = 100 percent) |
| Is the queue building up? | vllm:num_requests_waiting against vllm:num_requests_running |
| How fast do answers start? | histogram_quantile(0.95, sum by (le) (rate(vllm:time_to_first_token_seconds_bucket[5m]))) |
| How smooth is the output? | vllm:inter_token_latency_seconds |
| Is cache memory short? | vllm:num_preemptions_total, rises when requests are evicted |
| Does prefix caching work? | vllm:prefix_cache_hits_total divided by vllm:prefix_cache_queries_total |
| How do requests end? | vllm:request_success_total |
The service level of the application sets alert thresholds, not a table. Persistently high cache usage with rising preemptions means: less context, less concurrency or more GPU memory. Deprecated metrics are hidden one minor version later and removed one more version after that (metrics docs), so dashboards belong in the update test.
A gateway complements the engine metrics. LiteLLM addresses vLLM via hosted_vllm/<model> with api_base (docs) and writes traces to Langfuse with success_callback: ["langfuse"] (docs). That puts latency and costs per application next to the engine values. The gateway is an attack target itself, see LiteLLM vulnerability and gateway hardening.
Securing vLLM
The vLLM security documentation explicitly warns against relying on --api-key alone.
--api-keyprotects only/v1,/v2,/inferenceand/cohere. Reachable without a key remain, among others,/invocations(the same inference as/v1),/pooling,/classify,/score,/rerank,/metrics,/health,/tokenize,/loadand control paths such as/pause,/abort_requestsand/update_weights.- Reverse proxy with an allowlist. Only the required paths pass, everything else ends with 403. Rate limiting and logging sit at the proxy. Example for nginx (test before use; without buffering the response goes to the client synchronously, nginx docs):
location ~ ^/v1/(chat/completions|completions|models)$ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_buffering off; # streaming without intermediate buffer
proxy_read_timeout 600s; # default 60s, long answers need more
}
location / { return 403; }
- Isolate the network. With several nodes, PyTorch distributed traffic and KV transfer are unencrypted and belong in an isolated network,
VLLM_HOST_IPgets a fixed address. From outside this network only the API port may be reachable. - Dangerous switches off:
VLLM_SERVER_DEV_MODE=1(opens/collective_rpc, among others) never in production,--enable-tokenizer-info-endpointonly when needed. - Multimodal inputs:
--allowed-media-domainslimits fetches against SSRF. Publicly reachable services setVLLM_MAX_N_SEQUENCESlow (the docs give 64 or 128 as an example). - A key is not a permission concept. Keys per team, limits and attribution belong in a gateway.
- Close data paths. By default vLLM collects anonymous usage data, which
VLLM_NO_USAGE_STATS=1orDO_NOT_TRACK=1switches off (docs). Hugging Face telemetry stops withHF_HUB_DISABLE_TELEMETRY=1, calls to the Hub withHF_HUB_OFFLINE=1(docs).--enable-log-requestswrites parameters to the log, and at DEBUG level prompts as well. With personal data it stays off.
Application risks such as prompt injection are covered by Protection against prompt injection, access from outside by Provide secure remote access to local AI.
Updates and version pinning
According to PyPI, 0.28.0 (26 August 2026), 0.29.0, 0.30.0, 0.31.0 and 0.32.0 (18 October 2026) were released in just under eight weeks. The Docker tags carry the version (v0.32.0), alongside latest and nightly.
- Pin the image to a version number or digest, never to
latest. - Test first: update an environment with the same GPU and the same model, then compare startup log (cache size, concurrency), load test and dashboards.
- Keep a fallback: the previous Compose file and previous image stay available.
- Check model requirements: new models often need new vLLM versions, the Mistral Medium 3.5 model card, for example, names a nightly build.
- Plan for caches: changes to the model, configuration, torch or GPU invalidate the compile cache (docs), the first start after an update takes longer.
- Follow security notices: the project coordinates vulnerabilities in a dedicated process (vulnerability management) and publishes security advisories.
Typical errors
| Symptom | Cause | Fix |
|---|---|---|
| Start aborts with out-of-memory | Weights, reserve and cache exceed GPU memory | Smaller or quantized model, more GPUs, lower --max-model-len and --max-num-seqs (docs) |
Answers stall under load, num_preemptions_total rises |
KV cache too small for concurrency and context length | Lower --max-num-seqs or --max-num-batched-tokens, raise --gpu-memory-utilization or add GPUs (docs) |
| Chat requests fail | Model has no chat template in its tokenizer | Pass --chat-template with a template file (docs) |
| Tool calls appear as text | Auto tool choice or the matching parser is missing | --enable-auto-tool-choice with a --tool-call-parser that fits the model |
| Answer style differs from tests | Sampling defaults come from the model's generation_config.json |
Cross-check with --generation-config vllm (docs) |
| Start hangs while loading | Weights come from a network drive or are being downloaded | Store the model locally beforehand |
| Every restart takes very long | Compile cache not persistent | Volume on /root/.cache/vllm |
Load testing without invented numbers
Throughput figures from other setups say nothing about your prompts, your model and your GPU. The vLLM docs position vllm bench serve for feature and regression tests and recommend GuideLLM for production servers (benchmarking). Procedure:
- Your own prompts as
.jsonlwith the fieldprompt, in a realistic length distribution of the inputs (short chat versus RAG with long context). The answer length is set by--custom-output-len(default 256), with--custom-output-len -1the fieldoutput_tokensof each line applies. Do not copy real data with personal references into test tools. - Steps: raise concurrency gradually (
--max-concurrency, optionally--request-rate). - Measure: time to first token, time between tokens and end-to-end latency as percentiles, error rate, together with
kv_cache_usage_perc,num_requests_waitingandnum_preemptions_totalfrom the metrics. - Mind the prefix cache: repeated runs with the same prompts inflate throughput. Vary the seed or restart the server (docs).
- Define acceptance beforehand, with
--goodputas a target per request. The business side sets the target values. - Repeat after every update and model change.
vllm bench serve --backend openai-chat --endpoint /v1/chat/completions \
--model mistralai/Ministral-3-14B-Instruct-2512 --served-model-name assistent \
--host 127.0.0.1 --port 8000 \
--header "Authorization=Bearer ${VLLM_API_KEY}" \
--dataset-name custom --dataset-path prompts.jsonl --num-prompts 200 \
--skip-chat-template --max-concurrency 8 \
--percentile-metrics ttft,itl,e2el --metric-percentiles 50,95,99
The options come from the docs for vllm bench serve. The call runs per step with a different --max-concurrency. --skip-chat-template stops the client from applying the chat template in addition to the server (the /v1/chat/completions endpoint applies it itself). The client loads the model's tokenizer, without Hub access --tokenizer points to a local copy. Read percentiles, not averages.
How to evaluate vLLM consulting
Internal or external: these questions must be answerable with files and measurements.
- Which image version is running, how is it pinned, what does the fallback look like?
- Is the memory calculation available with assumptions (weights, KV cache, reserve)?
- Which endpoints are reachable from outside, and where is the allowlist?
- Which metrics and alerts exist, and who receives them?
- Is there a load test with your own prompts and written acceptance values?
- How does an update run, and which tests belong to it?
- Who owns configuration, keys and access, and how does the handover back work?
- Which data paths leave the environment (telemetry, logs, model downloads)?
Status and methodology
- Status: 28 October 2026. The basis is the vLLM documentation (version latest) and the source code on the main branch, retrieved on 28 October 2026.
- Calculation: weights from the file sizes of the Hugging Face repositories, cache values from
config.json. The reserve of 4 GB per GPU is an assumption. - Not measured: the guide contains no benchmarks and no throughput figures. They result from your own load test.
- Example model: Ministral 3 14B (Apache 2.0), flags for other models are in their model cards.
Our approach at WZ-IT
We run vLLM on the Managed GPU Server: a dedicated NVIDIA GPU in a German data centre with a matched model, vLLM and Open WebUI. Before provisioning we check model, quantization, context and concurrency by calculation against 24 or 96 GB of GPU memory. Operations include monitoring of host, GPU and vLLM, maintenance of operating system, driver and vLLM with CVE assessment, and incident handling. Monitoring runs 24/7, the response to P1 incidents follows the service level. External APIs, telemetry and backup targets are agreed as separate data paths. Those who want to review the application on top of the inference continuously (traces, evaluations, costs, latency) use Managed LLMOps with Langfuse. On your own hardware in your own network, vLLM and Open WebUI run preinstalled on the AI Cube. The vLLM expertise page describes the field, LLM hosting compares the operating options.
Further guides
- What is vLLM? - engine, PagedAttention and continuous batching
- vLLM vs. Ollama - when which engine fits
- Sizing GPU and VRAM for LLMs - derivation of the memory calculation
- Multi-GPU inference without NVLink - tensor and pipeline parallelism
- LLM quantization - FP8, NVFP4 and AWQ compared
- What is LiteLLM? - the gateway in front of vLLM
- What is Langfuse? - traces and evaluations
- vLLM, Ollama or llama.cpp - inference servers compared
- vLLM with Qwen3.5-122B on two RTX PRO 6000 - worked example with Compose and a hybrid model
Putting vLLM into operation? WZ-IT runs vLLM on a dedicated GPU server in Germany or reviews your application continuously. Managed GPU Server · Managed LLMOps · LLM hosting











