Use this page to look up server behavior, flags, and defaults.
vllm-server is a small HTTP server speaking the OpenAI API. Source:
examples/server/main.cpp and
src/vllm/entrypoints/openai/.
build/examples/vllm-server --model /path/to/Qwen3.6-27B --port 8000 --max-num-seqs 32For build and packaging commands, see Building vllm.cpp. For published artifacts and verification steps, see Binary releases.
The x86_64 CPU library is one adaptive binary: portable, SSE2,
SSE2+F16C, AVX2, and AVX-512 elementwise matmul kernels are isolated in their
own translation units and selected only after CPUID plus the required XCR0 OS
state are checked. Leave VT_CPU_MATMUL_TIER unset for automatic selection, or
set it to portable, sse2, sse2+f16c, avx2, or avx512 for a same-binary
correctness/performance check. A forced tier that the current CPU or OS cannot
execute fails closed instead of silently narrowing or risking an illegal
instruction. Release builds never use -march=native.
On arm64, leave the same variable unset to select between portable and NEON
elementwise matmul, or force portable/neon. DotProd and i8mm kernels are
independently selectable with VT_CPU_Q8_DOT, VT_CPU_QUANT_MMLA, and
VT_CPU_QUANT_REPACK; auto uses Linux HWCAP/HWCAP2 or Darwin feature sysctls,
while an unavailable forced tier fails closed. The exact accepted values are
listed in the environment reference.
NVFP4 optimization defaults and current measurements are listed in the environment reference and the benchmark report.
Registered in
src/vllm/entrypoints/openai/api_server.cpp.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/completions |
Text completion (JSON or text/event-stream) |
| POST | /v1/chat/completions |
Chat completion (JSON or streaming SSE) |
| GET | /v1/models |
List the served model |
| GET | /health |
Process liveness (200) |
| GET, POST | /ping |
Liveness probe (200, mirrors /health) |
| GET | /version |
Engine version |
| GET | /metrics |
Prometheus metrics (vllm:* names, text format 0.0.4), recorded per engine step by the engine that serves your requests. Series and families keep stable addresses as new ones register (#330), so a long-lived scrape target does not read through a reallocated registry |
| POST | /tokenize |
Tokenize a prompt to token ids (optional token_strs) |
| POST | /detokenize |
Detokenize token ids back to text |
| GET | /server_info |
Server info (vllm_config, vllm_env, system_env) |
| POST | /reset_prefix_cache |
Reset the prefix cache; returns {"success": bool} |
| POST | /v1/embeddings |
Embeddings. Registered only when an embedder is attached, so a text server answers 404 at the route table |
| POST | /v1/audio/transcriptions |
Speech to text (multipart: audio as file, response_format as a form field). Registered only when a transcriber is attached |
| POST | /v1/videos |
Start a video generation job, returns {id, status} (MiniMax-H3) |
| POST | /v1/videos/sync |
Same, but runs to completion before answering |
| GET | /v1/videos/{id} |
Job status |
| GET | /v1/videos/{id}/content |
The finished MP4 (video/mp4) |
| POST | /v1/audio/speech |
Text (or lyrics + a music description) to audio; responds with audio/wav bytes. Registered only when a synthesizer is attached (--speech-model) |
/v1/audio/speech is registered only when you start the server with
--speech-model. Without that flag, the route returns 404. MiniMax-Music3
returns a 44.1 kHz stereo WAV. See the
MiniMax-Music3 recipe.
IndexTTS-2.5 loads from --speech-model and returns a 22.05 kHz mono WAV. Each
request must include reference_audio as a data URL for a 16-bit PCM mono WAV.
The clip must have a 16 kHz sample rate. Its CAMPPlus speaker vector conditions
the talker and S2Mel stages. Different clips change the output, but this does
not establish voice-cloning parity. The inferred emotion path is not
implemented, and the vLLM-Omni quality comparison is pending. See the
owning model specification for the owned
limitations and verification evidence.
/v1/chat/completions accepts chat_template_kwargs, an object of extra Jinja
variables handed to the model's chat template, exactly as vLLM does. It is how a
client selects a reasoning mode on a template that gates one:
curl -sS -X POST http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' -d '{
"model": "qwen38-27b",
"messages": [{"role": "user", "content": "hi"}],
"chat_template_kwargs": {"enable_thinking": false}
}'The request keys win over anything --enable-thinking / --no-enable-thinking
set at startup. A key nobody supplies is not a template variable at all, so
{% if enable_thinking is undefined %} answers true and the model's own default
applies; that is what vLLM does and what the Qwen3.8 family's template expects.
/tokenize's chat form accepts the same field and renders through the same
template, so its token ids match what /v1/chat/completions would send.
A key valued null or "auto" means "not set", so it leaves the server-wide
default standing rather than clearing it, and a key that names something the
renderer supplies is refused with HTTP 400 rather than honoured:
messages, tools, chat_template and tokenize. Without that refusal a
request could hand the model a conversation its own messages field never
carried, which the request log, usage and any policy layer would then
describe wrongly. vLLM refuses the same four.
Two further groups of keys are accepted and ignored, again as vLLM ignores
them. add_generation_prompt and continue_final_message are request fields of
their own, and the field always wins over the kwarg. The second group is any key
that names a Jinja built-in: a global such as namespace or range, a filter
such as tojson, upper or join, or a test such as equalto. Such a key is
dropped, because the template needs the built-in and CPython Jinja2 never lets a
render variable replace one. raise_exception is the single name in that group
that does bind, which is also what vLLM does with it.
A chat template can refuse the request itself, through an unknown message role
or a kwarg value the template rejects. That answers HTTP 400, not 500, on
both /v1/chat/completions and /tokenize.
prompt_logprobs is accepted on /v1/completions and /v1/chat/completions
and the engine computes it, every prompt position is scored against the token
that followed it, accumulated across chunked prefill, but the response body
does not carry it yet: emitting it needs the OpenAI echo wiring, which is
not done. Until then it is reachable through the library
(RequestOutput.prompt_logprobs), not over HTTP. logprobs/top_logprobs on
GENERATED tokens are emitted normally.
That computation is gated on the CPU backend only. A step that owes prompt
logits takes the full-logits route, and on that route the sampler is handed a
host-resident logits buffer carrying the accelerator's device label, sound on
unified memory, and not yet verified on CUDA at all, discrete or otherwise.
Treat prompt_logprobs on a GPU build as unverified until that gate runs; the
mechanism and the exact owed invocation are in
.agents/specs/prompt-logprobs.md
(risk 4 and the PENDING CUDA smoke gate). Requests that do NOT set it are
unaffected on every backend, the route is only taken for a step where some
request asked.
The four /v1/videos routes are registered only when the server was started
with --video-dit; without it they are absent (404) and the server is identical
to one built without video support. Use the
MiniMax-H3 recipe for the combined video and audio
workflow.
Some clients (Hermes among them) send max_tokens: -1 to mean "no client-side
limit". A non-positive max_tokens, or max_completion_tokens on
/v1/chat/completions, which takes precedence, is treated as unset, not as
an error and not as a clamp to some constant. Unset then generates up to
max_model_len minus the prompt length, mirroring vLLM.
That distinction is load-bearing for long-context requests: substituting a
constant would cap exactly the request that asked to be left unlimited, and the
client would see finish_reason: length with no way to tell it apart from a
limit it set itself. Use VT_SERVER_MAX_NEW_TOKENS when you want a serving-side
ceiling.
Stop ids come from two files in the checkpoint, not one. config.json's
eos_token_id supplies the primary eos id, and the sibling
generation_config.json supplies secondary stop ids that are usually a
superset of it. Gemma-4-26B is the clearest case:
config.json eos_token_id: [1, 106]
generation_config.json eos_token_id: [1, 106, 50]
Both are read, mirroring vLLM's default --generation-config auto. The
secondary ids are merged into the request's stop_token_ids, so a chat model
stops on its turn-level token rather than running to the length cap. A missing
or malformed generation_config.json is a silent no-op.
ignore_eos: true suppresses all of them, primary and secondary alike, and
generation then runs to the token budget. The ids still count toward
min_tokens masking either way, so min_tokens cannot be satisfied by emitting
a stop token early.
| Flag | Default | Meaning |
|---|---|---|
--model <dir|file.gguf|org/repo|org/repo:QUANT> |
Required except when --speech-model selects a speech/music-only server |
A local directory or .gguf file, opened as before, or a Hugging Face repository, which is fetched into the cache. The local forms are probed first, so a network call can never shadow a path on disk. See Access Hugging Face checkpoints |
--revision <ref> |
repository default branch | A branch, a tag, or a 40 character commit for a --model org/repo. vLLM's own flag, and there is no inline org/repo@rev syntax |
--download-dir <path> |
the resolved Hugging Face cache root | The directory that holds the models--org--repo folders. vLLM's own flag. Fetching a repository needs a build that carries transport layer security. See Access Hugging Face checkpoints for the three build options, the environment variables, and which release lanes carry it |
--host H |
0.0.0.0 |
Bind host |
--port P |
8000 |
Bind port |
--served-model-name N |
model dir basename, or the org/repo you typed |
Model id in /v1/models and responses |
--tokenizer-config F |
<dir>/tokenizer_config.json |
Chat template / tokenizer config |
--block-size N |
32 |
KV block size. Must be a multiple of 16, the attention backends' get_kv_cache_shape refuses anything else, and the server now rejects it at startup rather than throwing during engine init |
--num-blocks N |
0 (auto, resolves to 256) |
KV block count, and vLLM's num_gpu_blocks_override. It wins over every other sizing knob. 0 means auto, which uses --kv-cache-memory when that is set and otherwise falls back to 256 blocks |
--kv-cache-memory BYTES |
0 (unset) |
Absolute KV-pool size in bytes, vLLM's kv_cache_memory_bytes. The block count is this budget divided by the model's own bytes per block, summed across its KV groups, so it is correct on MLA and heterogeneous-KV architectures too. It ignores --gpu-memory-utilization, as vLLM does. A budget smaller than one KV block is refused at startup |
--gpu-memory-utilization F |
0.92 |
Accepted, and it does not size anything yet. See What --gpu-memory-utilization does not do yet |
--max-model-len N |
0 (config default) |
Max sequence length |
--max-num-seqs N |
32 |
Maximum concurrent sequences and HTTP workers. For GDN or Mamba speculative decoding, it also sizes recurrent state as N x (k+1). The loader refuses an unsupported state budget. |
--max-num-batched-tokens N |
0 (per-arch default) |
Per-step token budget |
--enable-prefix-caching / --no-enable-prefix-caching |
model default | Override automatic prefix caching |
--disable-sliding-window / --enable-sliding-window |
enabled | Disable the model-level sliding window for every model that has one, mirroring vLLM's ModelConfig.disable_sliding_window (vllm/config/model.py:248). A model without a window ignores the flag, which is upstream's own stated behaviour. A per-layer window still wins, matching upstream precedence. Covers Gemma-2, Gemma-3, Gemma-4, OLMo-2 and Muse-Glimmer; it replaced VT_GEMMA2_SLIDING and VT_GEMMA3_SLIDING, which reached only the first two. |
--scheduling-policy fcfs|priority|lpm |
fcfs |
Scheduler policy (lpm is the SGLang cache-aware policy, see the SGLang compatibility guide) |
--enable-radix-attention / --disable-radix-attention |
model default | SGLang-named alias for the prefix-cache toggle |
--enable-jump-forward / --disable-jump-forward |
off | Accepted and validated, and nothing decodes with it yet. See What --enable-jump-forward does not do yet |
--enable-force-include-usage |
off | Force the usage block in responses |
--generation-config auto|vllm|<dir> |
auto |
Where the server's default sampling parameters come from. auto takes temperature, top_k, top_p, min_p and repetition_penalty from the checkpoint's own generation_config.json, and a request that OMITS one of those knobs then gets the checkpoint's value; a request that sends one still wins. vllm loads no file and keeps the neutral OpenAI defaults. A directory path reads a generation_config.json from there instead. Qwen/Qwen3.8-27B ships top_k: 20 and top_p: 0.95, so under auto an unparameterised request samples from 20 candidates rather than all 248,320. The server prints what it resolved at startup. --override-generation-config is unavailable (#1985). |
--tool-call-parser <name> |
hermes |
Select one of 42 registered names across 38 dialect families. auto detects the dialect from the chat template, and none disables parsing. Gemma-4 accepts wrapped or bare text calls. Inkling requests require "skip_special_tokens": false. The inkling reasoning parser is unavailable. |
--reasoning-parser <name> |
none |
Select think_auto, deepseek_r1, deepseek_v3, holo2, mistral, minimax_m2, minimax_m2_append_think, step3, olmo3, muse_glimmer, qwen3, or mimo. auto detects from the template. The qwen3 and mimo names share an adapter. |
--kv-transfer-config '<json>' |
(unset) | External KV connector, same JSON as vLLM's flag. See the KV offload guide |
--offload-config '<json>' |
(unset) | Configure vLLM weight-offload fields and the vllm_cpp disk-residency and hybrid-placement tiers. vLLM offload backends currently refuse at startup because loaders do not use them. A vllm_cpp-only config works. Transcription refuses this flag. See Weight offload and placement and Expert streaming. |
--speculative-config '<json>' |
(unset) | Configure mtp, dflash, ngram, or dspark. Unknown fields, unsupported methods, incompatible targets, and invalid depths refuse at startup. Sampling defaults to greedy with standard rejection. The document also accepts a vllm_cpp extension object whose only key is drafter_chain, a preference-ordered list of speculators; it is validated but refused at startup, because nothing resolves a chain yet. See Speculative decoding. |
--language-model-only / --no-language-model-only |
off | Set every multimodal limit to zero. Multimodal requests then return HTTP 400. It also skips loading any tower whose every modality it zeroes, mirroring vLLM's _mark_tower_model (interfaces.py:288-293); the server names what it skipped. Measured on Qwen3-VL-4B-Instruct only: 0.770 GiB of host RSS at load, --device cpu, dgx:gpu0, 2026-08-28 (#607). Read that as one model's tower rather than a general saving, and note it is load-time host RAM, not VRAM. An earlier 2026-08-24 run read 1.542 GiB; about half of that was our own bf16→f32 widening (#1359), whose Qwen3-VL half has since landed, and the 2026-08-28 rerun measured the resulting 0.499x fall — correct, not a regression. Other models are unmeasured. See Multimodal input and Memory benchmarks. |
--limit-mm-per-prompt '<json>' |
999 per modality |
Set lower per-prompt limits with a JSON object such as '{"image": 2, "video": 0}'. Malformed JSON, negative counts, and unknown image, video, or audio options refuse at startup. Dotted flag syntax is unavailable. See Multimodal input. |
--mmproj <mmproj-*.gguf> |
(unset) | Load and validate a clip GGUF projector for a GGUF model. The server refuses incompatible model types, architectures, projector types, and incomplete temporal patch weights. HTTP multimodal inference for GGUF is unavailable, so this option does not produce image answers. See Multimodal input. |
--enable-log-requests / --disable-log-requests |
on | Log each incoming request. Mirrors vLLM's flag of the same name |
--enable-log-outputs |
off | Also log the generated output, not just the request |
--max-log-len N |
256 |
Truncate logged prompts and outputs to N characters |
--enable-metrics / --disable-metrics |
on | Serve the metrics endpoint |
--enable-thinking / --no-enable-thinking |
neither | Set the enable_thinking chat-template variable for templates that gate a reasoning block on it. Our spelling of vLLM's --default-chat-template-kwargs enable_thinking, whose default is also to set nothing. Passing neither is not the same as --no-enable-thinking: it leaves the variable UNSET, so a template asking {% if enable_thinking is undefined %} gets its own default (the Qwen3.8 family reasons; Gemma-4 does not). --no-enable-thinking forces it off for every request |
--verbose, -v |
off | Verbose server logging |
--cuda-profile-graph-replays N |
0 (off) |
Trace-only diagnostic: arm the CUDA-graph-replay profiler and stop after N replays, printing a pid to signal with SIGUSR2. Requires a build with VT_BENCH_PROFILE_CONTROL |
--cuda-profile-graph-batch N |
16 when replays are armed |
Batch size the profiler traces. Must not exceed --max-num-seqs |
-h, --help |
Print usage and exit |
A published vllm serve line has to reach model load. The flags below appear in
most official vllm-project/recipes
commands, mean nothing to this engine, and are therefore accepted and ignored
rather than rejected. Each one prints a notice on startup naming itself and the
reason it does nothing, so a log never implies it took effect.
| Flag | Effect here | Why it is inert |
|---|---|---|
--enable-auto-tool-choice |
none | Tool parsing is already unconditional once --tool-call-parser resolves; there is no second gate to open. Note --tool-call-parser defaults to hermes here, where upstream's defaults to unset, so the two flags do not line up when the parser is omitted. Upstream's validation is still mirrored: combining it with --tool-call-parser none is refused, as in vllm/entrypoints/openai/cli_args.py:395 |
--trust-remote-code |
none | It authorizes executing Python from the checkpoint. This engine has no Python runtime, so there is nothing to authorize, N/A by construction, not unimplemented |
The notice is on stderr at startup, one line per flag actually passed, so what you see in a log matches this table:
server: accepted '--trust-remote-code' for published-recipe compatibility; it has no effect here: no Python runtime, so there is no remote code to trust
The mirrored validation is reported before the parser dialect is checked, so a
contradiction is named as a contradiction rather than passing silently (none is
itself a valid selection):
server: Error: --enable-auto-tool-choice requires --tool-call-parser
server: (--tool-call-parser none selects NO parser; name a parser, or drop --tool-call-parser to keep the hermes default)
This list is enumerated, not a catch-all. Any other unrecognized flag still
aborts with server: unknown argument '<flag>', including flags that are inert
only because the capability is missing (--tensor-parallel-size and the other
parallelism flags), silently accepting those would let you believe you got
tensor parallelism when you did not.
The flag is accepted, keeps vLLM's exact name and fraction semantics, and is
then discarded. It does not size the KV pool. Passing
--gpu-memory-utilization 0.85 gives the same 256-block pool as passing
nothing.
Turning a free-memory fraction into a block count needs a profile run that
measures what the weights and activations cost on the device first. That run is
not implemented. It is ROAD-V1-MEM M3, tracked by
issue #83, and it needs a GPU to
gate.
The flag is accepted rather than refused so that a published vllm serve
command line runs here unchanged. Setting it prints this warning at startup, so
a log never implies it took effect:
vllm.cpp: WARNING --gpu-memory-utilization 0.85 was accepted but did NOT size the KV cache.
vllm.cpp: The profile run that turns a free-memory fraction into a block count is not
vllm.cpp: implemented yet (ROAD-V1-MEM M3, https://github.com/mudler/vllm.cpp/issues/83).
vllm.cpp: The pool fell back to 256 blocks. To size it today, pass
vllm.cpp: --kv-cache-memory <bytes> for an absolute KV budget, or --num-blocks <n> for an
vllm.cpp: exact block count.
To size the pool today, use --kv-cache-memory for an absolute byte budget or
--num-blocks for an exact count. A run that never sets the flag prints
nothing.
Warning. On a unified-memory board such as NVIDIA GB10, a fraction of "device" memory is a fraction of the one pool the host shares, so it reserves host RAM as well. A value of 0.85 has hard-rebooted a GB10 box. When M3 lands and this flag starts to bind, choose the fraction on such a board against the whole 119 GiB pool and leave the host its headroom. Until then the flag reserves nothing, on any board.
The server accepts the flag, refuses it when you pass it twice, and resolves it
into the engine as LoadedEngine::jump_forward_enabled(). Nothing then reads the
resolved value. Passing --enable-jump-forward therefore leaves the decode path
exactly as passing nothing leaves it. --disable-jump-forward turns off
something that was never on.
Everything up to that latch exists, and each part has unit tests:
NativeGrammar::forced_token, the grammar hook that reports the single valid next token at a non-accepting state.vllm::v1::DrainForcedTokens, the driver that drains a run of forced tokens and advances the grammar over them.- The tri-state resolution of the flag against the
VT_ENABLE_JUMP_FORWARDenvironment override. - The C ABI field
vllm_model_params.enable_jump_forward, added in ABI v10.
One call site is missing. Draining forced tokens between model steps means
recomputing KV for the tokens the model never ran, and that scheduler splice is
not implemented. Until it lands, DrainForcedTokens is called only from
tests/vllm/v1/structured_output/test_jump_forward.cpp, and
jump_forward_enabled() is read only from tests/capi/test_capi.cpp.
The work is ENG-STRUCTURED-OUTPUT, tracked by
issue #2387.
The server accepts the flag rather than refusing it because the knob is already published: ABI v10 added the field, and the SGLang compatibility guide documents it. Refusing it now would break a caller that already sets it, and setting it cannot give a wrong answer, because it changes no answer at all.
Note. Unlike --gpu-memory-utilization, this flag prints no startup warning,
so a log tells you nothing either way. This page is the only signal.
The KV pool holds --num-blocks × --block-size tokens, 256 × 32 = 8192 by
default. A request longer than that can never be scheduled, so the engine
refuses it early rather than leaving it in the waiting queue forever. Two checks
do that, mirroring vLLM:
- At startup. If
--max-model-lenis given and the pool cannot hold one sequence that long, the server exits with the sizes and the flags that close the gap (vLLM's_check_enough_kv_cache_memory). If it is not given, the serving length is auto-fitted down to what the pool holds and logged (vLLM's_auto_fit_max_model_len), so raising--num-blocksis what buys a longer context. - At admission. A prompt at or past the resolved
max_model_lenis rejected with HTTP 400 (BadRequestError) naming both lengths, exactly as vLLM's_validate_prompt_lendoes. It is never a finish reason and never a 500.
Set VT_ENGINE_STEP_LOG=1 to print a per-step engine heartbeat if you need to
confirm that a quiet engine is idle rather than stalled.
For a production deployment, use LocalAI, which can embed engines like this behind a model gallery, multi-model serving, the full OpenAI API surface, auth, and metrics.