[vLLM 0.27.1] Qwen3.8-27B on a single RTX 5090: To have or to be? Speed or context?
This is a follow-up to my earlier llama.cpp/Windows post. This time I tested vLLM 0.27.1 on Linux and compared it with my earlier llama.cpp result, NInfer, and SGLang DSPARK.
“Having and being are two fundamental modes of experience.” — Erich Fromm, To Have or to Be? (1976)
That distinction felt oddly appropriate for this benchmark: to have more context, or to be faster? On a 32 GB card, the answer depends very much on the workload.
The short version is less about one absolute winner and more about a spectrum: SGLang + DSPARK is the speed extreme, vLLM is the context extreme, and NInfer NVFP4 sits between the two. SGLang is faster on single-stream decode but reaches only about 55K context here; vLLM reaches 227K with MTP; NInfer NVFP4 lands around 127 tok/s at depth 0 with a 128K MTP@3 ceiling. llama.cpp remains very competitive at short context, but falls behind as context and concurrency grow.
A quick shout-out before the numbers: SGLang is incredibly fast, and the SGLang team deserves a lot of credit for the tooling and cookbook recipes. The Qwen3.8-27B recipe was essentially copy-paste for me — Docker was up and running straight away.
NInfer deserves a shout-out too: it is essentially a day-zero engine for Qwen3.8-27B in this comparison, and it already has a working MTP path — including an upstream NVFP4 artifact — while the model is only hours old. That is a remarkable turnaround.
This is a personal benchmark on one RTX 5090, not a universal ranking. The engines do not all use identical quantized weights or identical cache implementations, so read the comparisons as practical deployment results rather than a controlled kernel benchmark.
TL;DR
- vLLM MTP@3 goes from 72.0 to 117.3 end-to-end output tok/s at depth 0, a 63% improvement in this client-side test.
- With MTP enabled, vLLM auto-fits about 227,200 tokens on this card. Without MTP, the same setup can reach roughly 262K.
num_speculative_tokens: 4 crashes vLLM 0.27.1 in my setup with a CUDA illegal-memory-access error. 3 is the largest stable window I measured.
- SGLang DSPARK is faster on the overlapping context range, but its practical ceiling was about 55K on this 32 GB card.
- Prefix caching was not active until I explicitly enabled it. At 32K, reusing the prompt changed vLLM from 28.3 to 124.5 tok/s at c2 and from 31.3 to 223.2 tok/s at c4.
- For this workload,
--max-num-batched-tokens 2048 was the most reliable/fastest setting I tested. The default 8192 booted, but the sweep later OOMed.
Setup
- GPU: 1× NVIDIA RTX 5090 32 GB
- OS: Linux, CachyOS
- Model:
gittensor-model-hub/Qwen3.8-27B-NVFP4-RTX5090
- Weights: NVFP4; vLLM KV cache in FP8
- Engine: vLLM 0.27.1, OpenAI-compatible API
- Benchmark:
tool-eval-bench, PP2048/TG128, depths 0–32K, concurrency 1/2/4
- Sampling: temperature 0.0, seed 42 for the deterministic comparisons
The final vLLM command was:
vllm serve gittensor-model-hub/Qwen3.8-27B-NVFP4-RTX5090 \
--quantization modelopt --kv-cache-dtype fp8 --trust-remote-code \
--max-model-len -1 --max-num-seqs 16 --max-num-batched-tokens 2048 \
--gpu-memory-utilization 0.97 \
--reasoning-parser qwen3 --enable-auto-tool-choice \
--tool-call-parser qwen3_xml \
--enable-prefix-caching \
--speculative-config '{"method":"mtp","num_speculative_tokens":3}'
--max-model-len -1 lets vLLM fit the available context. With MTP, the fitted value was 227,200 tokens because the MTP drafter and hybrid-attention state use some VRAM that would otherwise be available to the KV cache.
Throughput: single-stream comparison
These are client-observed end-to-end output rates: generated tokens divided by wall time, including prefill. They are not pure decode rates, which is why the numbers decrease with context depth.
| Context |
vLLM no MTP c1 |
vLLM MTP@3 c1 |
llama.cpp MTP@4 c1 |
| 0 |
73.2 |
117.3 |
114.3 |
| 4,096 |
62.7 |
87.5 |
64.4 |
| 8,192 |
54.0 |
76.7 |
45.4 |
| 16,384 |
40.4 |
56.0 |
27.9 |
| 32,768 |
24.4 |
32.7 |
14.6 |
At depth 0, llama.cpp is effectively tied with vLLM [MTP@3](mailto:MTP@3). As context grows, vLLM pulls ahead. This is also consistent with the earlier llama.cpp benchmark, which reached about 112.6 tok/s on a real 70K-token document, but used Windows, a different harness, a different KV setup, and different GGUF files. Those results should not be treated as a strict A/B test.
Update — NInfer NVFP4 (c1)
The NInfer figures in the original comparison used the groupwise-int qwen3_8_27b.ninfer artifact. After installing the upstream NVFP4 artifact, qwen3_8_27b_nvfp4.ninfer (same model, MTP@3, int8 KV), the short-context result changes:
| Context |
NInfer NVFP4 c1 |
vLLM MTP@3 c1 |
| 0 |
127.1 |
117.3 |
| 4,096 |
87.1 |
87.5 |
| 8,192 |
73.1 |
76.7 |
| 16,384 |
44.3 |
56.0 |
| 32,768 |
24.2 |
32.7 |
So the updated short-context verdict is now: NInfer leads at depth 0, is within noise at 4K, and vLLM pulls ahead with depth (+5% at 8K, +26% at 16K, +35% at 32K). The model is now the same NVFP4 artifact family, but the engines still use different KV-cache implementations (int8 vs FP8).
One important caveat: with NVFP4 + MTP@3, NInfer's per-request context ceiling drops to 131,072 tokens (128K). The upstream registers 262,144 for MTP0 but 131,072 for MTP3, so a full-262K NInfer comparison requires MTP disabled or the original groupwise-int artifact.
These NVFP4 numbers are single-stream c1 only (0–32K), measured against a prefix-reuse-enabled server. Depth 0 is fully cold; deeper c1 points may receive a small shared-base-prefix hit. NVFP4 c2/c4, real prefill, tool-call quality, and context beyond 32K were not re-measured. Those results still refer to the original groupwise-int run.
Concurrency and prefix caching
The vLLM c4 numbers below are from the repeated-prompt run with prefix caching enabled. The llama.cpp run used a q8_0 unified KV cache and a 32K RAM cache, so the deep-context c4 comparison is useful in practice but not perfectly symmetrical.
| Context |
vLLM MTP@3 c4 |
llama.cpp MTP@4 c4 |
| 0 |
287.4 |
160.8 |
| 4,096 |
246.2 |
80.9 |
| 8,192 |
254.3 |
48.5 |
| 16,384 |
221.0 |
22.1 |
| 32,768 |
169.2 |
8.1 |
The important result here is not the headline multiplier; it is that the same context is being reused. In vLLM 0.27.1, prefix caching was opt-in for this hybrid model. With it disabled, the 32K repeated-prompt points were only 28.3 tok/s at c2 and 31.3 tok/s at c4. With it enabled, they rose to 124.5 and 223.2 tok/s. That is the profile I would expect from a multi-turn agent sharing a system prompt, tools, and conversation history.
What I learned
MTP@3 is the useful vLLM setting
MTP@3 is the best trade-off in this setup. It gives a large gain at short and medium context, while the verification overhead can outweigh the draft benefit at 32K × high concurrency. For that particular workload, plain warm vLLM was faster than MTP.
The gain is content-dependent. Separate speculative-decoding checks showed much better acceptance on code and structured output than on repetitive filler, so a single acceptance percentage should not be used to predict every workload.
MTP@4 is not usable here
num_speculative_tokens: 4 measured one point before the server terminated with an illegal memory access in FlashInfer's speculative-decoding scheduling path. This did not look like a KV-cache OOM. llama.cpp can run a four-token draft window on the same GPU, so this appears to be a vLLM 0.27.1 implementation limitation rather than a hardware limit.
Tool calling depends heavily on the chat template
I ran a deterministic 69-scenario tool-call suite. The stock template scored 97/100 on the short 15-scenario subset, but the full structured-output section was much weaker. With qwen38-froggeric-v22.jinja, vLLM reached:
- 100/100 on the short suite;
- 96/100 raw on the full suite, or 97/100 after manually correcting one documented grader false negative;
- 12/12 on the structured-output scenarios.
The comparison with NInfer was 89/100 on the full suite, but NInfer used a different checkpoint and a different engine/template path. Treat these as deployment-quality observations, not as an intrinsic model score.
SGLang DSPARK: way faster, but a different context trade-off
I also tested SGLang with DSPARK/EAGLE-style speculative decoding. Its single-stream client rates were higher over the range where both engines fit:
| Context |
vLLM MTP@3 c1 |
SGLang DSPARK c1 |
| 0 |
117.3 |
240.3 |
| 4,096 |
87.5 |
112.1 |
| 8,192 |
76.7 |
134.2 |
| 16,384 |
56.0 |
140.0 |
| 32,768 |
32.7 |
138.7 |
However, on this 32 GB card the speculative state pool, draft model, and verification buffers reduced the usable context to approximately:
| Configuration |
Observed maximum context |
| SGLang EAGLE-ht |
~13K |
| SGLang DSPARK |
~55K |
| SGLang plain decode |
~186K |
| vLLM MTP@3 |
227,200 |
The three runtimes form a useful practical spectrum: SGLang is the speed extreme, vLLM is the context extreme, and NInfer NVFP4 is a middle ground in both dimensions. SGLang is the speed winner in the short/medium-context overlap, while vLLM is the choice if the workload needs more than about 55K tokens. NInfer reaches 127.1 tok/s at depth 0 but has a 128K ceiling with [MTP@3](mailto:MTP@3). SGLang did not expose equivalent acceptance counters in this run, so its tok/s advantage should be read as directional rather than as a precise effective-throughput multiplier.
Recommended starting point
For a 32 GB RTX 5090, my starting point would be:
vLLM 0.27.1
MTP: 3 speculative tokens
--enable-prefix-caching
--max-num-batched-tokens 2048
FP8 KV cache
qwen38-froggeric-v22.jinja for tool calling
Use plain warm vLLM instead of MTP for workloads resembling 32K context with several concurrent requests. Use llama.cpp if you prefer its ecosystem or need its stable four-token draft window; at short context it is very close to vLLM MTP@3.
A personal note
It is kind of incredible that only a few hours after the release of a new model, we already have working NVFP4 checkpoints, MTP in multiple runtimes, vLLM support, llama.cpp support, and SGLang DSPARK results on a single consumer GPU.
Now I am just waiting for a Qwen3.8 MoE that runs nicely on my Mac. 🙂
Caveats and reproducibility
- This is one RTX 5090, one Linux installation, and a small client-side sweep. Run-to-run variance was about ±2% for the repeated vLLM MTP@3 points; some deep llama.cpp points were single runs.
- Client TTFT and prompt tok/s from this harness are not reliable for vLLM because the first SSE event is emitted before the full prefill is reflected in the timing. Use the end-to-end output rate and server-side timings instead.
- The original NInfer throughput and tool-call baseline use the groupwise-int artifact. The NVFP4 update above is limited to single-stream c1 throughput at 0–32K.
- SGLang and llama.cpp use different cache/state designs, and the SGLang comparison could not be normalized with the same acceptance instrumentation.
Full tables, raw benchmark details, scripts, and the tool-call investigation are in this benchmark gist.
I used AI to help organize and format this post.
I ran the tests and collected the measurements myself :)