It isn't SGLang: where the 4× on a DGX Spark actually comes from
The engine is not the reason. MiaAI-Lab/Qwen3.8-27B-SGLang-DGX-Spark serves Qwen3.8-27B on one DGX Spark at 38.6–51.5 tok/s on code where vLLM sits near 24.5. Run the same box on vLLM 0.27.1 with the same block-diffusion drafter and it does 31.72 tok/s against its own 7.94 un-drafted — 3.99×. The step change is a drafter that is a drop-in for both engines. What is left over divides into the checkpoint and ten ARM cores.
| Control | Engine | Drafter | Weights | tok/s | Source |
|---|---|---|---|---|---|
| Stock decode | vLLM 0.27.1 | none | FP8, 30.6 GB | 7.94 | 0xBakeer |
| Same engine, block drafter | vLLM 0.27.1 | DFlash2 k=7 | FP8, 30.6 GB | 31.72 | 0xBakeer |
| Baseline the repo is measured against | vLLM | none | NVFP4, 24 GB | 24.5 | HAOyezi |
| The repo | SGLang dev | DFlash2 k=8 | NVFP4, 24 GB | 38.6 | HAOyezi |
| The repo, its own clock | SGLang dev | DFlash2 k=8 | NVFP4, 24 GB | 50.9–54.6 | MiaAI-Lab |
Three independent single-Spark benchmark repos, one model. The engine swap is the one variable that never moves a number on its own.
The box sets the rules: 273 GB/s
| GB10 | value |
|---|---|
| CUDA cores | 6,144 (5th-gen tensor cores) |
| Memory | 128 GB LPDDR5X-9400 unified, 256-bit |
| Bandwidth | 273 GB/s |
| NVFP4 dense / 2:4 sparse | 500 / 1,000 TFLOP/s |
| GPU TDP | 140 W |
| CPU | 10 × Cortex-X925 @ 3.9 GHz + 10 × A725 @ 2.8 GHz |
Decode at batch 1 reads every weight once per token. That makes one number decisive and it is not a FLOP count.
| Checkpoint | Weight bytes | One pass | Ceiling |
|---|---|---|---|
Qwen/Qwen3.8-27B-FP8 | 28.5 GiB = 30.6 GB | 112.1 ms | 8.92 tok/s |
RadixArk/…-NVFP4-BF16-LMHead | ~24.0 GB | 87.9 ms | 11.38 tok/s |
Un-drafted decode is already at 89 % of that ceiling
vLLM with no drafter and no prefix cache: 7.94 of a possible 8.92. There is no engine rewrite, kernel backend or scheduler fix worth 300 % here, because there is only 11 % of the ceiling left on the table. The sole remaining lever is fewer weight passes per token.
Every speedup is tokens per weight pass
One engine, one box, one checkpoint — the clean control. Accepted tokens per step set the prize; the drafter’s own forward passes set the price.
| Drafter | Accepted tok/step | Free-drafting ideal | Measured | Kept | × stock |
|---|---|---|---|---|---|
| none | 1.000 | 7.94 | 7.94 | 100 % | 1.00× |
| MTP k=3 | 3.165 | 25.13 | 17.99 | 72 % | 2.27× |
| DSpark k=7 | 2.875 | 22.83 | 19.12 | 84 % | 2.41× |
| DFlash2 k=7 | 4.607 | 36.58 | 31.72 | 87 % | 3.99× |
Note DSpark: it accepts fewer tokens per step than MTP (2.875 vs 3.165) and is still faster, because it wastes less drafting them.
Why the block drafter keeps more of its prize
An autoregressive drafter (EAGLE, MTP) emits k tokens in k sequential forward passes, so draft cost grows with k. A block-diffusion drafter predicts the whole masked block in one pass and keeps several candidates per position, so draft cost is flat in k. Output is unchanged either way — verification is exact.
| DFlash, published | Hardware | vs autoregressive |
|---|---|---|
| gpt-oss-120b | 8 × DGX B300 | up to 15× throughput |
| Gemma 4 31B (vLLM) | 1 × B300 | 5.8× Math500 · 5.6× HumanEval |
| Qwen3 8B (SGLang) | 1 × B200 | 5.1× Math500 · 4.2× HumanEval |
| vs EAGLE-3, gpt-oss-120b | matched concurrency | 2.6× coding · 1.8× writing |
| vs EAGLE-3, Llama 3.1 8B | matched concurrency | 3.0× coding · 2.7× writing |
Shipped for SGLang, vLLM and TensorRT-LLM. arXiv:2602.06036.
The depth knob has an interior optimum
| Sweep | Values | Peak |
|---|---|---|
| SGLang MTP steps (this box) | 2 → 12.8 · 3 → 17.2 · 4 → 16.8 · 5 → 16.3 · 6 → 15.8 | 3 |
| vLLM MTP k, fresh generation | 3 → 17.70 · 8 → 18.64 · 15 → 13.39 | 8 |
| vLLM MTP k, edit-heavy | 3 → 21.3 · 8 → 32.2 · 15 → 39.0 | 15 |
| DSpark block size | 7 = code peak; 5 = +8 % prose / −16 % code | workload |
A rejected draft token still cost its pass. k has to be set from the acceptance rate of the actual traffic, and near-copyable text (edits, diffs, structured output) accepts almost everything — which is why the same flag sweeps in opposite directions on two workloads.
The residual cross-engine gap is the checkpoint
Decode is bandwidth-bound, so throughput scales with 1 ÷ weight bytes. 30.6 ÷ 24.0 = 1.28×, and the measured SGLang-over-vLLM ratio is 1.22×. Nothing is left over to attribute to the engine; the sign of the residual is negative.
The reason vLLM was stuck on the bigger checkpoint is the interesting part:
| Constraint | |
|---|---|
| DFlash2 requirement | an unquantized LM head |
| Under vLLM | both 4-bit Qwen3.8-27B checkpoints fail the pre-flight → FP8, 30.6 GB |
| The repo’s move | RadixArk/Qwen3.8-27B-NVFP4-BF16-LMHead — NVFP4 body, dense BF16 head |
| Result | 4-bit weights and a block drafter, which vLLM could not have at the same time |
The repo’s real contribution is a checkpoint export, not an engine.
What SGLang does contribute: knobs, each worth single digits
| Choice | Against | Measured delta |
|---|---|---|
--cpuset-cpus 5-9,15-19 (the ten 3.9 GHz X-cores) | no pinning | +2 to +7 % decode |
| GDN state bf16 | cookbook float32 | +3 % |
--mamba-full-memory-ratio 4.21 | stock 0.9 | un-clamps concurrency |
--mem-fraction-static 0.90 · chunk 8192 | 0.85 · 2048 | TTFT, not decode |
extra_buffer_lazy radix | extra_buffer | pinned by measurement |
| NVFP4 checkpoint | FP8 checkpoint | ~+30 % |
| NGRAM drafter | MTP | −30 % · rejected |
--speculative-accept-threshold-acc < 1 | 1.0 | worse · rejected |
| Prefill CUDA graphs | off | auto-disabled (GDN ≠ GQA) |
Without pinning, the tokenizer and scheduler land on the 2.8 GHz efficiency cores roughly half the time. It is real, and it is 5 % against the drafter’s 300 %.
Where the tuned stack loses
| Workload | vLLM NVFP4 | SGLang + DFlash2 | Ratio |
|---|---|---|---|
| Code generation | 24.5 | 38.6 | 1.58× |
| Thinking / math | 24.5 | 32.1 | 1.31× |
| Knowledge QA | 24.5 | 20.9 | 0.85× |
| Short dialogue — the daily workload | 24.5 | 15.9 | 0.65× |
| Long-form writing | 24.5 | 14.5 | 0.59× |
| Long-context agent decode | 24.5 | 13–16 | 0.53–0.65× |
Two wins out of six. The vLLM column is one scenario-blind baseline, not a per-workload measurement — treat it as a line, not six points.
| Context regime | Accept rate | Decode |
|---|---|---|
| short requests | 0.36–0.45 | 27–33 tok/s |
| long reasoning (agent traffic) | 0.10–0.16 | 13–16 tok/s |
Single stream only
| Streams | TTFT | Aggregate | Per stream |
|---|---|---|---|
| 1 | 127 ms | 56.6 | 56.6 |
| 2 | 202 ms | 58.4 | 42.4 |
| 4 | 224 ms | 111.6 | 33.4 |
| 8 | 280 ms | 184.9 | 30.8 |
| 16 | 4.18 s | 227.6 | 28.2 |
The same trade is explicit on vLLM: 4-bit weights with the free in-checkpoint MTP head overtake the block drafter from two streams up and lead by 23 % at sixteen. And one k does not serve both — DSpark k=14 is the single-stream champion at 58.5 tok/s on edit-heavy text and gives up 43 % of aggregate throughput at eight streams (119.7 vs 208.7).
| Also hardware-bound, not a flag | |
|---|---|
| KV per token | ~32.8 KB |
| A full 1M-token sequence | ≈ 33 GB |
| KV pool | ≈ 81 GB (2.48M tokens) |
| Concurrent 1M requests | 2, whatever MAX_CONCURRENT_REQUESTS says |
| Two engines resident at once | 160 GB demanded of 121.6 GiB → OOM, exit 137, hard reboot at mem 0.95 |
The configuration, for reference
python3 -m sglang.launch_server --model-path /models --trust-remote-code --mem-fraction-static 0.90 --attention-backend flashinfer --chunked-prefill-size 8192 --kv-cache-dtype fp8_e4m3 --mamba-ssm-dtype bfloat16 --mamba-full-memory-ratio 4.21 --mamba-radix-cache-strategy extra_buffer --context-length 262144 --speculative-algorithm DFLASH --speculative-draft-model-path /draft --speculative-num-draft-tokens 8 --reasoning-parser qwen3 --tool-call-parser qwen3_coder --port 8888
# docker: --cpuset-cpus 5-9,15-19 --shm-size 32g --ipc host --gpus all
Setup and caveats
| Hardware | one NVIDIA DGX Spark (GB10, SM121, aarch64), 128 GB unified @ 273 GB/s |
| Model | Qwen3.8-27B dense, hybrid attention (48 GDN + 16 full-attention layers), native 262K context, native VLM |
| Engines | SGLang dev-qwen38-27b-dflash2 · vLLM 0.23.0 / 0.27.1 aarch64 |
| Measured by | three separate owners, 2026-08-18 → 09-08, three harnesses |
| Not one experiment | the vLLM-side drafter ladder is a different box, day and harness from the SGLang rows; the cross-engine comparison is normalised arithmetic, not an A/B |
| Clocks differ | the repo’s ndec.py net-decode (50.9–54.6) excludes prefill; the 38.6 figure is a third party’s decode clock; wall-time rows include prefill |
| Noise floors | code deltas < 15 % are noise; the essay probe is ±1 %; two boots of one image differed 6.5 % |
| Thermal drift | essay 19.5 → 18 tok/s over an hour of benching (power cap) |
| Roofline | calculated from nameplate bandwidth; GB10 exposes no memory-traffic counter |
| Not measured | output quality. Every speedup here is output-preserving by construction, but nobody ran an eval |
Sources
| Shorthand | What it measured | Link |
|---|---|---|
MiaAI-Lab | the repo in question — SGLang + three drafters, every flag measured on-device | MiaAI-Lab/Qwen3.8-27B-SGLang-DGX-Spark |
HAOyezi | one Spark, same harness across Ollama / vLLM / SGLang | HAOyezi/qwen38-27b-dgx-spark-benchmarks |
0xBakeer | the vLLM-side drafter ladder and the acceptance figures | 0xBakeer/Qwen3.8-27B-FP8-on-a-single-DGX-Spark |
| DFlash | the drafter itself — block diffusion for speculative decoding | arXiv:2602.06036 · NVIDIA developer blog |
| GB10 | bandwidth, cores, power | NVIDIA DGX Spark hardware docs |
TL;DR: stock decode on a DGX Spark is 7.94 tok/s and 89 % of a 273 GB/s roofline, so the only lever is tokens per weight pass. A block-diffusion drafter accepts 4.607 per step and keeps 87 % of them — 3.99×, and it is a drop-in for vLLM too, where it measures 31.72. The 1.22× SGLang shows on top is 1.28× fewer weight bytes from an NVFP4 checkpoint with a dense BF16 head, which is the only way to run 4-bit weights and a block drafter together. The engine contributes nothing measurable; the ARM core pinning contributes 5 %. On dialogue it is 0.65× plain vLLM, on long-context agents 0.53×, and from two concurrent streams up it loses outright.