Principal engineer building production AI agents. I write about running models on my own GPU, AI dev tools, and what I'm building.
Opinions here are my own and don't represent my employer.
Principal engineer building production AI agents. I write about running models on my own GPU, AI dev tools, and what I'm building.
Opinions here are my own and don't represent my employer.
Can Qwen3.8-27B, served for chat through SGLang, double as a decision model? Does that hold for Swift 1.5’s fine-tune, and at NVFP4?
All seven configs ran on Typed Decisions: 400 test cases from four workflows (agent traces, customer service, invoices, security incidents), five questions each. Its answers are the average of three samples from a teacher model that the dataset card doesn’t name beyond “roughly 4B-class”, so its scores measure agreement with that teacher, not correctness.
Three more datasets have answers that don’t come from a model. Typed Decision Bench uses labels from 21 public datasets and one generator. Procedural Typed Decisions computes answers from rules stated in the prompt. Calibrated Typed Decisions has human labels, plus a PubMedQA split with expert labels. Only the two NVFP4 configs ran on these three, so they compare Swift with stock, not the quantizations.
On the two NVFP4 configs, both models come out of the server overconfident: every fitted temperature is above 1. One temperature per question type, fitted per dataset and applied in the client, improves the aggregate probability scores on all four datasets without changing a top answer. On Calibrated Typed Decisions’ human labels, log loss drops from 0.60–0.67 to 0.44–0.46 and ECE from 0.095–0.099 to 0.010–0.011; the same fit lowers log loss on the unseen PubMedQA task from 0.90–0.95 to 0.68–0.69. On Typed Decisions, NVFP4 and BF16 agree with the teacher at similar rates, though about 9–10% of their top answers differ. Observed Swift–stock gaps are under 2 points, in both directions.
SGLang answers a decision from a chat model by reading the probabilities of the answer labels, with thinking off (docs). Typed Decisions test: 400 cases, 2,000 decisions, one request per case through /v1/systemone, the dataset’s own scorer.
| Config | Agreement | KL from gold | Brier | p50 per case |
|---|---|---|---|---|
| Stock BF16 | 0.661 | 0.590 | 0.214 | 381 ms |
| Stock FP8 | 0.663 | 0.558 | 0.208 | 241 ms |
| Stock NVFP4 | 0.666 | 0.542 | 0.205 | 167 ms |
| Swift BF16 | 0.674 | 0.709 | 0.229 | 386 ms |
| Swift FP8 | 0.669 | 0.664 | 0.221 | 240 ms |
| Swift NVFP4 | 0.683 | 0.676 | 0.216 | 166 ms |
| Swift NVFP4 + DFlash2 8 | 0.683 | 0.676 | 0.216 | 170 ms |
Agreement means the model’s top answer matches the gold’s top answer. Swift is the more confident one; it doesn’t agree much more.
Calibration on the two NVFP4 configs, fitted on train (1,200 cases) and scored once on test:
| Config | Calibration | Agreement | KL from gold | Brier |
|---|---|---|---|---|
| Stock NVFP4 | none | 0.666 | 0.542 | 0.205 |
| Stock NVFP4 | T per type | 0.666 | 0.203 | 0.105 |
| Stock NVFP4 | T + priors | 0.743 | 0.133 | 0.069 |
| Swift NVFP4 | none | 0.683 | 0.676 | 0.216 |
| Swift NVFP4 | T per type | 0.683 | 0.198 | 0.100 |
| Swift NVFP4 | T + priors | 0.748 | 0.132 | 0.068 |
The Typed Decisions gold above is a teacher’s opinion; these three don’t come from a model (second column). Same calibration method, fitted separately for each dataset; no item is scored with a fit that used its answer.
| Dataset (cases) | Answers from | Metric | Stock NVFP4: none / T / T + priors | Swift NVFP4: none / T / T + priors |
|---|---|---|---|---|
| Typed Decision Bench (5,387) | labels of 21 public datasets, 1 generator | DecisionScore | 77.18 / 80.28 / 83.97 | 76.63 / 80.58 / 84.17 |
| Procedural Typed Decisions (874) | rules in the prompt | proper score | 74.28 / 76.46 / 79.64 | 74.38 / 77.41 / 80.30 |
Calibrated Typed Decisions test (1,797) | human labels | accuracy | 0.823 / 0.823 / 0.878 | 0.838 / 0.838 / 0.874 |
| same | log loss | 0.602 / 0.457 / 0.316 | 0.668 / 0.438 / 0.318 | |
same, heldout PubMedQA (1,000) | expert labels | accuracy | 0.716 / 0.716 / 0.716 | 0.731 / 0.731 / 0.731 |
| same | log loss | 0.902 / 0.692 / 0.719 | 0.948 / 0.678 / 0.703 |
DecisionScore and proper score are 100 × (1 − normalised Brier), averaged per item, so they reward probabilities, not just the top answer. Lower log loss is better.
test (human labels), ECE drops from 0.095 to 0.010 (stock) and 0.099 to 0.011 (Swift), log loss from 0.60 to 0.46 and 0.67 to 0.44. That is the aggregate: log loss improves in 10 of its 12 source and type groups for each model, gets worse on CLINC and Banking77 choice questions (CLINC 0.06 → 0.10), and GoEmotions stays poorly calibrated (ECE 0.18–0.23 after). Typed Decision Bench gains 3.1 (stock) and 4.0 (Swift) points of score, procedural 2.2 and 3.0 (all intervals above 0). A temperature can’t change the top answer, so accuracy doesn’t move.choice questions, so only the Calibrated Typed Decisions choice temperature acts on it. It still cuts log loss from 0.90 to 0.69 (stock) and 0.95 to 0.68 (Swift), and ECE from 0.15 to 0.06. The temperatures fitted together with priors do a little worse there (0.72 and 0.70). It is one task, so this is one data point for transfer./v1/decisions accepts a per-request temperature; /v1/systemone refuses it, and neither has a bias field. So both calibrations run on the probabilities the client gets back, and chat on the same server is untouched.test, interval [+0.6, +2.3]; +1.5 on PubMedQA, [+0.1, +2.9]) and on agreement with the Typed Decisions teacher (+1.3 at BF16 and +1.75 at NVFP4, intervals above 0; +0.55 at FP8, includes 0). It trails by 0.55 points of score on Typed Decision Bench ([−0.86, −0.25]) and is level on procedural (+0.10, [−0.63, +0.84]). After temperatures and priors, only procedural (+0.66, [+0.04, +1.28]) and PubMedQA, which calibration doesn’t change, exclude 0. That is about ten comparisons with no correction for multiple testing, so the gaps that barely exclude 0 are weak evidence. Swift is also more confident: its Typed Decisions KL is 0.66–0.71, stock’s 0.54–0.59.Cloudflare/clef) and 0.753 (perplexity-ai/pplx-decider-v1.1-27b); both list Qwen/Qwen3.8-27B as base in the Hugging Face API, checked 2026-10-30. Raw Swift NVFP4’s 0.683 would be 27th of the table’s 39 rows (roughly; see Caveats). The calibrated rows are fitted on train, so they belong in the Typed Decisions card’s second table, where Amos 1 large’s priors go from 0.761 to 0.8025 (+0.0415). A similar fit on Swift (priors plus temperatures) is +0.065.Serving stack:
lmsysorg/sglang:nightly-dev-cu13-20261010-961bcf48.Qwen/Qwen3.8-27B @ 1d4bf0f2, Qwen/Qwen3.8-27B-FP8 @ 017b9c7a, RadixArk/Qwen3.8-27B-NVFP4 @ 319f741c, ukisai/Swift-1.5-Qwen3.8-27b @ b4c84d42, ukisai/Swift-1.5-Qwen3.8-27b-NVFP4 @ 25482027. Swift FP8 is my own block-FP8 conversion of Swift BF16 (128×128 blocks, stock FP8’s layout); realderpz/Swift-1.5-Qwen3.8-27B-FP8 is a public equivalent (llmcompressor FP8_BLOCK).z-lab/Qwen3.8-27B-DFlash2 @ 50307d4c, 8 draft tokens.--mem-fraction-static 0.76 for BF16, 0.50 for FP8, 0.41 for NVFP4.python3 -m sglang.launch_server --model-path <checkpoint>
--mem-fraction-static <0.76|0.50|0.41> --max-running-requests 4 --context-length 153600
--dtype auto --attention-backend flashinfer
--reasoning-parser qwen3 --tool-call-parser qwen3_coder
--mamba-full-memory-ratio 0.9 --mamba-ssm-dtype bfloat16
--mamba-radix-cache-strategy extra_buffer_lazy
--chunked-prefill-size 2048 --page-size 64
--enable-hierarchical-cache --hicache-size 64 --hicache-write-policy write_through
--kv-cache-dtype fp8_e4m3 --schedule-policy lpm --enable-session-radix-cache
--sleep-on-idle --trust-remote-code
Benchmark:
1415d375, all config. Four workflows (agent trace observability, customer service, invoice processing, security incidents), five questions per case, three types: yes/no (noul), one of N labels (choice), ordered levels (score). test has 400 cases; train has 1,200 cases from a separate generation run./v1/systemone and one on native /v1/decisions. The headline numbers are pass 1. All three passes ran with an almost empty prefix cache (about 5% of prompt tokens cached, server log)./v1/systemone pass: Typed Decision Bench v0.3.0 @ 79ec2b9b, all 5,387 items, the 359 it withholds rebuilt with its own builder (blobfishai/jevfish @ 80262b5) and checked against its SHA256SUMS; its DecisionScore and accuracy. Procedural Typed Decisions all @ 05ca1d03, a fixed sample of up to 10 states per task and level (874 test, 880 validation for the fit); it has no scorer, so Typed Decision Bench’s per-item proper score. Calibrated Typed Decisions v2.1 @ 34189779, its human labels (not its soft teacher labels), validation for the fit; accuracy, log loss, Brier, ECE. Typed Decision Bench is calibrated by 5-fold cross-fitting within its items.calibrate.py fits by soft cross-entropy against its train gold. “T per type” scales each distribution as p1/T, with one T for each question type. “T + priors” also multiplies each option by eb, with one b per workflow, question and option (L2-penalised), and the three temperatures are refit with them (stock 2.00/2.05/2.43, Swift 2.25/3.30/2.71 for choice/noul/score). It is scored once on test.test. This ECE compares confidence with agreement on the teacher’s top label, not with its full distribution, so matching the teacher exactly doesn’t minimise it. KL and Brier are the calibration numbers there. ECE for every row is in the repo.choice and 2.9–4.6 for noul on procedural and Calibrated Typed Decisions), so a fit from one dataset isn’t shown to carry to another. The one transfer test is PubMedQA, a single task the fit never saw, scored with the Calibrated Typed Decisions fit.test_deterministic --test-mode radix_cache) fails on these flags, with logprobs 0.49 apart (0.20 with deterministic inference, Triton attention and BF16 KV). Open SGLang PRs #43110 and #46707 fix a hierarchical-cache Mamba restore that leaves an active request’s state stale; whether that is the cause here isn’t tested. Details in the repo README, “Prefix cache ablation”.run.sh there reruns all seven configs in about an hour. The README has per-type tables, the noise checks, the real-answer and cache tables with paired intervals, and the rerun commands.main keep changing. To rerun with them exactly as these runs had them, check out d445be4 for Typed Decisions, 4a91a4c for the real-answer and cache runs, and e8e2a51 for the cache ablation.results/<config>/decisions/: every raw answer (pass1.jsonl, pass2.jsonl, native1.jsonl) and summary.json. results-train/ holds the train-split answers for the two NVFP4 configs.results/{stock,swift}-nvfp4/calibration.json: the fitted temperatures and priors, and the test metrics. Code: scripts/decisions/calibrate.py.paired_ci.py and results/paired-agreement.txt: the paired gaps and bootstrap intervals.results-real/: raw answers per dataset and split, fitted calibrations, and tables.md (every row with paired intervals, from real_answers.py). results-cache/: the 50-case warm-cache run; results-cache-ablation/: the cache, precision and determinism follow-up (cache-ablation.sh, cache_check.py). Dataset adapters and scorers: scripts/decisions/sets.py.nvidia-smi reports Gen 4 as this system’s maximum).On one RTX PRO 6000 with SGLang and DFlash2 (8 draft tokens), ukisai’s Swift 1.5 NVFP4 scores the same as stock Qwen3.8-27B NVFP4 on GSM8K and a 205-case BFCL subset (97.04% vs 96.82%, 168 vs 168) and decodes at about the same speed per token. At reasoning_effort=medium it spends 14–19% fewer thinking tokens on GSM8K and SPEED-Bench, so a response is 8–10% shorter and finishes about 10% sooner.
Stock RadixArk/Qwen3.8-27B-NVFP4 vs Swift ukisai/Swift-1.5-Qwen3.8-27b-NVFP4, both with DFlash2 8, reasoning_effort=medium.
| Benchmark | Stock NVFP4 | Swift NVFP4 | Δ |
|---|---|---|---|
| GSM8K, all 1,319, correct | 1,277 (96.82%) | 1,280 (97.04%) | +3 |
| GSM8K, mean output tokens | 348 | 320 | −8% |
| GSM8K, mean thinking tokens | 214 | 184 | −14% |
| GSM8K, mean request time (32 in flight) | 11.4 s | 10.2 s | −10.5% |
| BFCL subset, correct of 205 | 168 | 168 | 0 |
| BFCL, mean output tokens per call | 146 | 134 | −9% |
| SPEED-Bench qualitative, mean output tokens | 1,455 | 1,308 | −10% |
| SPEED-Bench qualitative, mean thinking tokens¹ | 622 | 502 | −19% |
| SPEED-Bench qualitative, out tok/s | 203.3 | 203.5 | 0% |
| SPEED-Bench qualitative, accept length | 4.35 | 4.36 | 0% |
| Chat, 1 user (12 prompts), out tok/s (fixed length) | 177.8 | 170.4 | −4% |
| Chat, 4 users (32 prompts), out tok/s (fixed length) | 591.2 | 593.7 | 0% |
| 20k prompt, 1 user, out tok/s (fixed length) | 98.4 | 99.2 | +1% |
¹ The 125 of 142 turns where both models stopped on their own; the output-token row counts all 142.
Accept length on the five sets of the DFlash2 model card, with its protocol (xhigh, temperature 1.0, top-p 0.95, top-k 20, max 4096 new tokens), 32 prompts per set, 1 user. “8 draft tokens” is SGLang’s --speculative-num-draft-tokens 8, DFlash2’s block size 8 (7 drafted per step):
| Set | DFlash2 card (H200) | Stock NVFP4 | Swift NVFP4 |
|---|---|---|---|
| GSM8K | 5.46 | 5.29 ± 0.11 | 5.57 ± 0.11 |
| MATH-500 | 5.28 | 5.18 ± 0.14 | 5.22 ± 0.11 |
| HumanEval | 4.39 | 4.24 ± 0.13 | 4.45 ± 0.11 |
| MBPP | 4.79 | 4.61 ± 0.10 | 4.79 ± 0.12 |
| MT-Bench | 4.10 | 4.13 ± 0.18 | 4.33 ± 0.21 |
Mean thinking tokens per answer on the same sets, on the prompts where both models stopped on their own. At medium that’s 30–32 of 32 prompts; at xhigh 31 (MATH-500), 26 (MBPP), 24 (MT-Bench) and 21 (HumanEval), with GSM8K at 32.
| Set | Stock, xhigh | Swift, xhigh | Stock, medium | Swift, medium |
|---|---|---|---|---|
| GSM8K | 312 | 167 (−47%) | 204 | 186 (−9%) |
| MATH-500 | 759 | 517 (−32%) | 768 | 478 (−38%) |
| HumanEval | 845 | 700 (−17%) | 685 | 652 (−5%) |
| MBPP | 762 | 545 (−28%) | 342 | 306 (−10%) |
| MT-Bench | 416 | 243 (−42%) | 499 | 425 (−15%) |
Mean tokens per answer, thinking plus answer, all 32 prompts (answers cut off at 4096 count as 4096):
| Set | Stock, xhigh | Swift, xhigh | Stock, medium | Swift, medium |
|---|---|---|---|---|
| GSM8K | 438 | 296 (−32%) | 371 | 368 (−1%) |
| MATH-500 | 1,052 | 864 (−18%) | 1,276 | 833 (−35%) |
| HumanEval | 2,042 | 1,454 (−29%) | 1,196 | 1,131 (−5%) |
| MBPP | 1,551 | 899 (−42%) | 977 | 895 (−8%) |
| MT-Bench | 1,475 | 1,163 (−21%) | 1,113 | 1,002 (−10%) |
medium Swift thinks 5–38% less on the DFlash2 card’s sets and 14–19% less on GSM8K and SPEED-Bench; at xhigh, 17–47% less. Total minus thinking (an estimate: completion_tokens against re-tokenized reasoning) is about the same on full GSM8K (134 vs 136) and at xhigh on GSM8K and MATH-500, but it moves elsewhere: MT-Bench at xhigh −29%, HumanEval at xhigh +26%, and MBPP at medium saves 82 tokens of which only 36 are thinking. Swift’s model card claims 24.8% fewer thinking tokens at medium and 41.9% at xhigh (card @ b4c84d42, checked 2026-10-30). That is BF16 on vLLM; the xhigh figure is its GPQA-Diamond row and the table doesn’t name a benchmark for medium.xhigh costs Swift about what medium does. Whole responses at xhigh vs medium: GSM8K 296 vs 368 tokens, MATH-500 864 vs 833, MBPP 899 vs 895. MT-Bench (1,163 vs 1,002) and HumanEval (1,454 vs 1,131) cost 16–29% more. Stock pays more for xhigh on four of five (HumanEval 2,042 vs 1,196). I didn’t measure accuracy at xhigh, and the 4,096 cap hides the longest answers.xhigh. So the fine-tune didn’t hurt the stock drafter. I didn’t test a drafter tuned on Swift.medium. Prompts, sampling, effort and max length all differ from the DFlash2 card’s runs, so the gap is most likely the workload.multi_turn_base (14 → 16 of 20) and loses irrelevance (15 → 11); with 20 cases each, neither is outside noise.medium, on a benchmark it doesn’t name. I ran nothing harder.Serving stack:
lmsysorg/sglang:nightly-dev-cu13-20261010-961bcf48.RadixArk/Qwen3.8-27B-NVFP4 @ 319f741c, ukisai/Swift-1.5-Qwen3.8-27b-NVFP4 @ 25482027 (ukisai’s own NVFP4 build).z-lab/Qwen3.8-27B-DFlash2 @ 50307d4c, 8 draft tokens, on every run.scripts/args/qwen38-27b.sh) with --mem-fraction-static 0.41. Both get a 236,864-token KV pool and 43.8 GB of GPU memory after load.Benchmarks, all at reasoning_effort=medium unless noted:
\boxed{}), the model’s own sampling, max 32,768 tokens, 32 requests in flight. No answer was cut off.bfcl-eval 2026.3.23, the Qwen/Qwen3-32B-FC handler, the same fixed 205-case subset as the earlier posts (case ids).z-lab/dflash at 07ebd93, 32 prompts per set, 1 user. Once with the DFlash2 card’s protocol (xhigh), once at medium with the same sampling.python3 -m sglang.benchmark.serving --backend sglang-oai-chat --flush-cache --warmup-requests 2 --seed 1, the same chat and 20k scenarios as the first post.completion_tokens, i.e. thinking plus answer; answers cut off at 4096 count as 4096. “Thinking tokens” is the saved reasoning text of each response, tokenized with the model’s tokenizer, counted only on prompts where both models stopped on their own (thinking_tokens.py in the repo).Qwen/Qwen3.8-27B. Mine is NVFP4 on an RTX PRO 6000 at 400 W.ignore_eos), so they measure speed only and can’t show the token savings. The 20k prompt is SGLang’s random dataset (one ShareGPT prompt repeated to length).run.sh there reruns all four configs, about two and a half hours.main keep changing. To rerun with them exactly as these runs had them, check out 50fa2ce.results/stock-nvfp4-quality/ and results/swift-nvfp4-quality/: GSM8K and BFCL, every response included.results/dflash8/ (stock) and results/swift-dflash8/ (Swift): accept-sets/, speed-bench/ (one line per request, generated text included), bench-serving.jsonl and server.log.thinking_tokens.py: the thinking-token tables, from the saved responses.nvidia-smi reports Gen 4 as this system’s maximum).On one RTX PRO 6000, Qwen3.8-Flash-Next NVFP4 on SGLang beats Qwen3.8-27B NVFP4 without a drafter in every scenario I ran. Against the 27B with DFlash2 (8 draft tokens), it is about half as fast at chat, ties at 40k, and wins only from 80k: 1.9x at 120k. It takes the whole card for that, 88 GB against 44 GB. Capped at 44 GB with llama.cpp, it runs at 6–24% of that 27B’s speed (17% at chat with 1 user).
Whole card, output tok/s, median time to first token (TTFT) in parentheses, one run per config. Bold is the fastest; at 40k the two bold cells are a tie.
| Scenario | 27B NVFP4, no drafter | 27B NVFP4 + DFlash2 8 | Flash-Next NVFP4, no drafter |
|---|---|---|---|
| Chat, 1 user | 72.0 (44 ms) | 178.0 (48 ms) | 83.9 (81 ms) |
| Chat, 4 users | 233.7 (183 ms) | 592.5 (73 ms) | 305.2 (168 ms) |
| 20k prompt, 1 user | 52.9 (2.33 s) | 98.4 (2.35 s) | 72.6 (1.69 s) |
| 20k prompt, 4 users | 94.7 (5.77 s) | 137.5 (5.73 s) | 134.2 (4.35 s) |
| 40k prompt, 1 user | 39.1 (5.53 s) | 58.7 (5.57 s) | 58.3 (3.41 s) |
| 80k prompt, 1 user | 22.5 (14.7 s) | 28.5 (14.8 s) | 41.4 (6.98 s) |
| 120k prompt, 1 user | 14.2 (27.6 s) | 16.5 (27.8 s) | 31.9 (10.6 s) |
| GPU memory after load | 43.8 GB (0.41) | 43.8 GB (0.41) | 87.7 GB (0.88) |
The 27B numbers are the runs from the spec-decode post, not rerun. Its GPU memory row (43.8 GB) comes from resources.json of the same server config in the Swift vs stock data.
At the 27B’s 44 GB budget (llama.cpp, Q4 GGUF, experts of 30 of 48 layers in host RAM):
| Scenario | 27B NVFP4 + DFlash2 8 | Flash-Next UD-Q4_K_XL, 44.3 GB |
|---|---|---|
| Chat, 1 user | 178.0 (48 ms) | 30.2 (1.11 s) |
| Chat, 4 users | 592.5 (73 ms) | 37.2 (4.40 s) |
| 20k prompt, 1 user | 98.4 (2.35 s) | 19.3 (9.81 s) |
| 20k prompt, 4 users | 137.5 (5.73 s) | 21.6 (15.9 s) |
| 40k prompt, 1 user | 58.7 (5.57 s) | 14.2 (19.7 s) |
| 80k prompt, 1 user | 28.5 (14.8 s) | failed |
This run is partial: 80k failed (see caveats) and 120k didn’t run.
--ple-offload-embedding together with --cpu-offload-gb and --offload-group-size. KTransformers’ expert offload doesn’t support the model yet (ktransformers#2179, open on 2026-10-29).Flash-Next only wins past 40k, and it takes the whole card to do it.
Model: Qwen/Qwen3.8-Flash-Next, 125B parameters with 6B active per token, plus a 51B n-gram embedding and a 4B MTP head. 48 layers: 36 Gated DeltaNet, 12 Qwen Sparse Attention.
Flash-Next, whole card:
lmsysorg/sglang:nightly-dev-cu13-20261029-c10e0e17, with PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True and SGLANG_OPT_MAMBA_SKIP_DECODE_LOCK=1.RadixArk/Qwen3.8-Flash-Next-NVFP4 @ 7b719225.python3 -m sglang.launch_server --model-path <checkpoint>
--mem-fraction-static 0.88 --max-running-requests 4 --max-mamba-cache-size 12
--context-length 262144 --dtype bfloat16 --quantization modelopt_fp4 --tp 1
--reasoning-parser qwen3 --tool-call-parser qwen3_coder
--cuda-graph-backend-prefill disabled
--ple-offload-embedding --mamba-ssm-dtype bfloat16
--mamba-radix-cache-strategy extra_buffer_lazy --mamba-track-interval 64
--chunked-prefill-size 2048 --page-size 64 --kv-cache-dtype fp8_e4m3 --sleep-on-idle
Chunks of 2048 and no prefill CUDA graph came after a first attempt (native MTP, --mem-fraction-static 0.96, 4096-token chunks) ran out of GPU memory on the first 20k-token prefill. I didn’t test the two changes separately.
Flash-Next, 44 GB:
ghcr.io/ggml-org/llama.cpp@sha256:0283744a (b11977).unsloth/Qwen3.8-Flash-Next-GGUF @ 766911a6, UD-Q4_K_XL (103.7 GiB).--n-cpu-moe 30 sets the GPU memory: 44.3 GB after load, 44.5 GB peak. Host RAM 47–64 GiB.llama-server -m <UD-Q4_K_XL shard 1>
-ngl 999 -ot per_layer_token_embd=CPU --n-cpu-moe 30 -fit off
-c 153600 -kvu --parallel 4 -fa on --jinja -b 4096 -ub 4096 -t 16 -lm none
27B: stock RadixArk/Qwen3.8-27B-NVFP4 @ 319f741c, with and without z-lab/Qwen3.8-27B-DFlash2 (8 draft tokens), on lmsysorg/sglang:nightly-dev-cu13-20261010-961bcf48, --mem-fraction-static 0.41, context 153,600, 64 GiB HiCache in host RAM. Flags in the spec-decode post.
Serving: python3 -m sglang.benchmark.serving --warmup-requests 2 --seed 1, reasoning_effort=medium. Backend sglang-oai-chat with --flush-cache on SGLang; vllm-chat on llama.cpp, with cache_prompt: false on every request because llama.cpp has no cache flush endpoint.
random dataset (one ShareGPT prompt repeated to 20,000 tokens), 512 output tokens; 4 prompts at 1 user, 8 at 4 users.random dataset, which repeats one ShareGPT prompt to length. It’s the same input for every config, but it isn’t a real long document.--mem-fraction-static 0.96 and ran out of GPU memory on the first 20k prefill (chunk 4096). I didn’t try it at 0.88, and I didn’t run incoai/Qwen3.8-Flash-Next-DFlash2. Stock SGLang also caps the Flash-Next draft block at 4 tokens. A drafter might raise the whole-card chat numbers; I have no measurement either way.parallel (4/20 vs 18–19 for the 27B) and parallel_multiple (10/20 vs 18–19). None of the 16 failed parallel responses has two tool calls: the model makes one call or answers in prose. But BFCL’s Qwen/Qwen3-32B-FC handler builds its own prompt instead of using the model’s chat template, so I don’t trust that as a model comparison. The scores and every response are in the repo.ignore_eos), so this measures speed only.main keep changing. To rerun with them exactly as these runs had them, check out 45d1fd2.results/fn-nospec/ (whole card) and results/fn-gguf-q4/ (44 GB): bench-serving.jsonl (one line per scenario, server args included), server.log, resources.json (GPU and host memory, startup time), and bfcl/ with every model response.nvidia-smi reports Gen 4 as this system’s maximum).On one RTX PRO 6000 with SGLang, DFlash2 with 8 draft tokens is the fastest drafter for stock Qwen3.8-27B NVFP4 in every scenario I ran, from short chat to a 120k-token prompt. It beats the model’s own MTP head at 3 and 7 steps, DFlash2 with 4 draft tokens, and RadixArk’s DSpark. Tool calling (BFCL) stays within noise.
The end-to-end gain drops from 2.5x on chat to 1.16x at 120k. That’s prefill, not the drafter: at 1 user, time per output token stays 2.5–2.7x faster at every length. From 40k up the lead over the nearest drafter is only 3–4%, which is inside the noise (see Caveats).
Output tok/s, one run per config. Bold is the fastest.
| Scenario | No drafter | MTP 3 | MTP 7 | DFlash2 4 | DFlash2 8 | DSpark |
|---|---|---|---|---|---|---|
| Chat, 1 user | 72.0 | 143.8 | 143.0 | 143.3 | 178.0 | 148.1 |
| Chat, 4 users | 233.7 | 522.0 | 506.5 | 489.5 | 592.5 | 503.8 |
| 20k prompt, 1 user | 52.9 | 85.1 | 84.3 | 84.9 | 98.4 | 88.8 |
| 20k prompt, 4 users | 94.7 | 129.9 | 127.9 | 131.5 | 137.5 | 131.2 |
| 40k prompt, 1 user | 39.1 | 52.9 | 55.0 | 54.5 | 58.7 | 56.9 |
| 80k prompt, 1 user | 22.5 | 26.4 | 26.8 | 27.1 | 28.5 | 27.5 |
| 120k prompt, 1 user | 14.2 | 15.3 | 15.4 | 15.9 | 16.5 | 15.9 |
| BFCL subset, correct of 205 | 171 | 173 | 173 | 176 | 176 | 171 |
Mean accept length (tokens per verify step, 1 user):
| Config | Chat | 20k | 40k | 80k | 120k |
|---|---|---|---|---|---|
| MTP 3 | 2.80 | 2.87 | 2.74 | 2.90 | 2.84 |
| MTP 7 | 3.36 | 3.41 | 3.65 | 3.79 | 3.55 |
| DFlash2 4 | 2.81 | 2.79 | 2.76 | 2.70 | 2.66 |
| DFlash2 8 | 3.43 | 3.54 | 3.28 | 3.43 | 3.46 |
| DSpark | 2.84 | 2.95 | 3.07 | 2.88 | 2.70 |
Same --mem-fraction-static 0.41 for every config. The draft model and verify buffers come out of the same budget:
| Config | KV pool (tokens) | Mamba slots |
|---|---|---|
| No drafter | 318,592 | 117 |
| MTP 3 | 276,224 | 39 |
| MTP 7 | 264,256 | 24 |
| DFlash2 4 | 244,160 | 44 |
| DFlash2 8 | 236,864 | 28 |
| DSpark | 241,216 | 28 |
DFlash2 8 gives up 26% of the KV pool and 76% of the mamba slots (max_total_num_tokens and mamba_cache_size from each server.log). The largest scenarios above (4 × 20k, 1 × 120k) fit in the 237k-token pool. I haven’t measured what it costs in multi-turn prefix cache hits.
The DFlash2 model card reports 4.10 on MT-Bench (H200, xhigh reasoning, block size 8 with 7 draft tokens per step, the setting I call DFlash2 8). I get 3.43 on ShareGPT at medium. So I reran DFlash2 8 with thinking off and with temperature 0:
| Scenario | DFlash2 8 | Thinking off | Temperature 0 |
|---|---|---|---|
| Chat, 1 user, out tok/s | 178.0 | 184.7 | 177.2 |
| Chat, 4 users, out tok/s | 592.5 | 566.0 | 593.2 |
| 20k prompt, 1 user, out tok/s | 98.4 | 101.2 | 98.5 |
| 20k prompt, 4 users, out tok/s | 137.5 | 140.0 | 138.5 |
| Chat, 1 user, accept length | 3.43 | 3.57 | 3.41 |
Neither one closes the gap. The workload does (see the update below).
Update (2026-10-30): On the DFlash2 card’s own five sets and protocol, the same stock NVFP4 setup lands within 0.18 of its accept length on all five sets (MT-Bench 4.13 vs 4.10), so the gap here is most likely the workload, not the hardware or the quant. SPEED-Bench’s real prompts from 1k to 32k tokens accept 3.9–4.3 (12 prompts per length, standard error 0.2–0.3), so acceptance staying roughly flat with context holds on real prompts up to 32k too. The 20k–120k rows above repeat one ShareGPT prompt to length, so their accept lengths aren’t representative. The 80k and 120k rows, including the ~2.5x decode speedup there, are still repeated-prompt measurements; I haven’t rerun them on real prompts. Details: Swift 1.5 vs stock.
Serving stack:
lmsysorg/sglang:nightly-dev-cu13-20261010-961bcf48 for every config (it has both DFLASH and DSPARK).RadixArk/Qwen3.8-27B-NVFP4 @ 319f741c.z-lab/Qwen3.8-27B-DFlash2 @ 50307d4c, RadixArk/Qwen3.8-27B-DSpark @ b9a5dbdf. MTP uses the head built into the target checkpoint.reasoning_effort=medium.Shared flags, the same as the first post:
python3 -m sglang.launch_server --model-path <checkpoint>
--mem-fraction-static 0.41 --max-running-requests 4 --context-length 153600
--dtype auto --attention-backend flashinfer
--reasoning-parser qwen3 --tool-call-parser qwen3_coder
--mamba-full-memory-ratio 0.9 --mamba-ssm-dtype bfloat16
--mamba-radix-cache-strategy extra_buffer_lazy
--chunked-prefill-size 2048 --page-size 64
--enable-hierarchical-cache --hicache-size 64 --hicache-write-policy write_through
--kv-cache-dtype fp8_e4m3 --schedule-policy lpm --enable-session-radix-cache
--sleep-on-idle --trust-remote-code
Then the speculative flags, appended after the shared ones. The number in a name is the verify width: --speculative-num-draft-tokens for DFlash2 (4, 8) and steps for MTP (3, 7), so MTP 3 and DFlash2 4 both verify 4 tokens per step, and MTP 7 and DFlash2 8 both verify 8.
| Config | Flags |
|---|---|
| MTP 3 | --speculative-algorithm EAGLE --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4 |
| MTP 7 | same with --speculative-num-steps 7 --speculative-num-draft-tokens 8 |
| DFlash2 4 | --speculative-algorithm DFLASH --speculative-draft-model-path <DFlash2> --speculative-num-draft-tokens 4 |
| DFlash2 8 | same with --speculative-num-draft-tokens 8 |
| DSpark | --speculative-algorithm DSPARK --speculative-draft-model-path <DSpark> --speculative-draft-model-quantization unquant --speculative-draft-attention-backend flashinfer --speculative-dspark-block-size 7 --speculative-num-steps 1 --speculative-eagle-topk 1 |
Serving: python3 -m sglang.benchmark.serving --backend sglang-oai-chat --flush-cache --warmup-requests 2 --seed 1.
random dataset (one ShareGPT prompt repeated to length), 20,000-token input, 512 output tokens; 4 prompts at 1 user, 8 at 4 users.random dataset at that length, 512 output tokens, 4 prompts at 1 user.BFCL: bfcl-eval 2026.3.23, the Qwen/Qwen3-32B-FC handler, the same fixed 205-case subset as the first post (case ids).
--mamba-radix-cache-strategy extra_buffer, --mamba-full-memory-ratio 11.93 and 32k prefill chunks. With those it might do better than here.ignore_eos), so this measures speed only.unsloth/Qwen3.8-27B-NVFP4 and went to ~177k.run.sh reruns every config, about two hours.main keep changing. To rerun with them exactly as these runs had them, check out 4b5cf81.results/<label>/ per config: bench-serving.jsonl (one line per scenario, full server args included), server.log, and bfcl/ with every model response.nvidia-smi reports Gen 4 as this system’s maximum).On one RTX PRO 6000 with SGLang and DFlash2 speculative decoding, NVFP4 is 32–37% faster than FP8 for stock Qwen3.8-27B, on short chat and on 20k-token prompts. BFCL tool calling stays within noise (174 vs 167 of 205).
Stock Qwen3.8-27B: Qwen/Qwen3.8-27B-FP8 vs RadixArk/Qwen3.8-27B-NVFP4.
| Scenario | FP8 | NVFP4 | Δ |
|---|---|---|---|
| Chat, 1 user, out tok/s | 133.8 | 176.3 | +32% |
| Chat, 4 users, out tok/s | 449.9 | 596.5 | +33% |
| 20k prompt, 1 user, out tok/s | 73.3 | 98.5 | +34% |
| 20k prompt, 4 users, out tok/s | 104.1 | 142.3 | +37% |
| 20k prompt, 1 user, TTFT p50 | 3.20 s | 2.34 s | −27% |
| 20k prompt, 4 users, TTFT p50 | 8.48 s | 5.78 s | −32% |
| DFlash2 accept length, chat 1 user | 3.49 | 3.41 | −2% |
| BFCL subset, correct | 167/205 | 174/205 | +7 |
| BFCL mean latency per call | 1.22 s | 0.92 s | −25% |
irrelevance (12 → 18) and multi_turn_base (14 → 16), 20 cases each. Everything else nets −1.| Category | FP8 | NVFP4 |
|---|---|---|
| simple_python | 25/25 | 25/25 |
| multiple | 24/25 | 23/25 |
| parallel | 18/20 | 18/20 |
| parallel_multiple | 19/20 | 19/20 |
| live_simple | 18/20 | 18/20 |
| live_multiple | 20/25 | 19/25 |
| multi_turn_base | 14/20 | 16/20 |
| multi_turn_miss_param | 3/10 | 4/10 |
| irrelevance | 12/20 | 18/20 |
| live_irrelevance | 14/20 | 14/20 |
| Total | 167 | 174 |
Serving stack:
lmsysorg/sglang:nightly-dev-cu13-20261010-961bcf48.Qwen/Qwen3.8-27B-FP8 @ 017b9c7a, RadixArk/Qwen3.8-27B-NVFP4 @ 319f741c.z-lab/Qwen3.8-27B-DFlash2), 8 draft tokens, for every run.--mem-fraction-static 0.50 for FP8, 0.41 for NVFP4, so both get about the same KV pool (240,512 vs 236,864 tokens, 28 mamba slots each).reasoning_effort=medium.Launch flags:
python3 -m sglang.launch_server --model-path <checkpoint>
--mem-fraction-static <0.50|0.41> --max-running-requests 4 --context-length 153600
--dtype auto --attention-backend flashinfer
--reasoning-parser qwen3 --tool-call-parser qwen3_coder
--speculative-algorithm DFLASH --speculative-draft-model-path <DFlash2>
--speculative-num-draft-tokens 8
--mamba-full-memory-ratio 0.9 --mamba-ssm-dtype bfloat16
--mamba-radix-cache-strategy extra_buffer_lazy
--chunked-prefill-size 2048 --page-size 64
--enable-hierarchical-cache --hicache-size 64 --hicache-write-policy write_through
--kv-cache-dtype fp8_e4m3 --schedule-policy lpm --enable-session-radix-cache
--sleep-on-idle --trust-remote-code
Serving: python3 -m sglang.benchmark.serving --backend sglang-oai-chat --flush-cache --warmup-requests 2 --seed 1.
random dataset (one ShareGPT prompt repeated to length), 20,000-token input, 512 output tokens; 4 prompts at 1 user, 8 at 4 users.BFCL: bfcl-eval 2026.3.23, the Qwen/Qwen3-32B-FC handler, a fixed 205-case subset across 10 categories (case ids).
ignore_eos), so tok/s compares speed only, not how much each model thinks.unsloth/Qwen3.8-27B-NVFP4: 50.9 → 56.4 tok/s without MTP (+11%), 115.8 → 132.3 with MTP k=2 (+14%), single-stream decode on a ~1.4K-token prompt (repo). The engine, checkpoint and speculative decoding all differ from mine, and I haven’t tested which of them explains the difference.run.sh there reruns both configs.main keep changing. To rerun with them exactly as these runs had them, check out d4d7869.results/<label>/bfcl/ in the same folder.nvidia-smi reports Gen 4 as this system’s maximum).