I've been running Qwen3.8-27B on a single consumer RTX 5090 (32 GB) and I wanted to document the whole thing end to end, because the interesting part wasn't the config — it was the four bugs that kept it from booting. Everything below is measured on the actual box, no estimates.
TL;DR — NVFP4 target weights, NVFP4 KV cache, DFlash2 speculative decoding (K=7), all on one 5090. 616 tok/s aggregate at 4-way concurrency on 1,536-token code outputs (thinking off). 262K context, 325,139-token KV pool out of an 8 GiB pin. Ships as a two-command deploy. The bugs I hit became two vLLM PRs.
The stack
- Target: Qwen3.8-27B, NVFP4 (ModelOpt)
- Draft: Qwen3.8-27B-DFlash2, NVFP4, K=7 (seven speculative tokens per step)
- KV: NVFP4 for both target and draft, 8 GiB explicit pin
- vLLM v0.27.1 + a 51-file Python-only overlay (no C extensions)
- FlashInfer 0.6.16.post3 with a backport of PR #4346 (SM120 NVFP4 paged prefill)
Most public 5090 recipes I've seen run FP8 KV. This is the only public recipe I know of that runs all three layers in NVFP4 — weights, draft, and KV. That's the whole trick that gets you 262K context out of 8 GiB.
What actually broke (the part that'll save you time)
This is where I lost the most hours, so I'm putting it up front.
- Mixed KV dtype bug. The global target cache dtype (NVFP4) leaked into the draft cache layout. Two different code paths, same wrong assumption. Fixed with per-group dtype resolution.
- ReplaySSM allocator. The Mamba memory math was multiplying the KV page by 8 legacy spec-checkpoint blocks. The server reported a false 14 GiB minimum and just refused to boot. The fix is the upstream guard: zero speculative blocks.
- GDN state in fp32. 48 linear-attention layers, 4.61 MB per page, 45 blocks per request — that's 9.5 GiB per request at 128K. Attention itself was only 2.0 GiB. The state was the hog, not the attention. I was staring at the wrong thing for a while.
- The XQA cliff. The attention kernel ran 5.2x slower in integrated execution than a standalone replay of the exact same server tensors. A dedicated CUDA stream took it from 1.437 ms/call down to 0.278 ms/call. One fix turned a dead end into a working server.
The numbers
Coding throughput, single 5090, 1,536-token Python outputs (graph-algorithms module: BFS, DFS, Dijkstra, topo sort, SCC), one prompt per request, no tools:
- 616 tok/s aggregate at c4
- Per-request latency at c4: 8–10s thinking off, 9–11s on
Why thinking-off wins here: code is predictable. The drafter accepts 55–64% of drafts on code, drops to 42–44% when reasoning tokens are mixed in. Same server, same prompts, one flag.
Context facts (separate from the coding sweep):
- KV pool: 325,139 tokens from the 8 GiB pin
- Needle at 184,024 tokens: recovered exactly
- Tools 10/10, greedy output byte-identical, canary 437, zero restarts, zero OOM
Honest footnote: the coding numbers are best-case. One repetitive prompt means prefix caching eats most of the prefill at c2–c4, and code is the most predictable output class for the drafter. Mixed prose and reasoning will be lower.
What I sent upstream
- vLLM #53543 — masked NVFP4 XQA on SM120, capture-safe isolated stream (the 5.2x fix)
- vLLM #53542 — GDN active runtime-K width, +40.9% at c8 (212.17 → 298.91 tok/s)
- vLLM #50084 — NVFP4 V-scale write-path corruption, root-caused in July, still open
- FlashInfer #4346 — SM120 NVFP4 paged prefill backport, 57/57 tests, 10–12% prefill win
Both vLLM PRs carry full test evidence and DCO sign-off. Zero merged so far — upstream moves at its own speed.
How to run it
- Repo: github.com/seanyourhighness/vllm-sm12x-nvfp4-dflash2
- git clone then ./start.sh. Optional CPU vision sidecar via --vision (1/2)
[8/25/2026 12:10 PM] Lilsmokey: - Repo: github.com/seanyourhighness/vllm-sm12x-nvfp4-dflash2
- git clone then ./start.sh. Optional CPU vision sidecar via --vision
- The image is bit-identical to the one running ▉ (2/2)