
sparkrun recipe: Qwen3.8-Flash-Next (NVFP4 / MXFP8, MTP ×4) on 2x DGX Spark, TP=2 over CX-7.
vLLM with b12x GB10 kernels, OpenAI-compatible endpoint, quality-gated releases.
86 / 113 tok/sTG c1defaults (ISL/OSL 2048/512), v2.0.0 / T=0, thinking off (coding), v2.0.0 | 195 / 317 tok/sTG aggregate c8defaults (ISL/OSL 2048/512), v2.0.0 / T=0, thinking off (coding), v2.0.0 | 90/100Hardmodepass ≥ 88, T=0, thinking on | 5.5 sTTFT, ISL 16Kc1, cold prefix |
262K context, max_num_seqs 16, MTP ×4, OpenAI-compatible API, quality-gated releases
GPU power, GPU only (nvidia-smi), not wall power: idle 19.5 W, c1 49.8 W, c8 61.4 W; TG per GPU watt 1.73 (c1) and 3.18 (c8) tok/s/W at server defaults.
TG cells: server defaults (ISL/OSL 2048/512, T=1.0, thinking on) first, T=0 coding (36 prompts, thinking off) second, both aggregate output tok/s. Means over 3 boots where measured; sd between boots in the details. Release v2.0.0.
| Metric | Mean | sd between boots | Boots | Workload | Sampling | Release (date) | Source |
|---|---|---|---|---|---|---|---|
| TG c1, defaults | 86.3 tok/s | 2.37 | 3 | llama-benchy task mode, ISL/OSL 2048/512 | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG c1, coding | 112.8 tok/s | 0.02 | 3 | 36 prompts, OSL ≤768, aggregate output tok/s per pass | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TG c1, coding, per-prompt median | 118.7 tok/s | – | 1 | 36 prompts (Python, C++, Rust, Go), OSL ≤768, median over prompts | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TG c1, coding, defaults, per-prompt median | 92.0 tok/s | – | 1 | 36 prompts, OSL ≤768; 100/108 requests hit the 768-token cap inside thinking | server defaults, thinking on | v2.0.0 (2026-10-10) | results |
| TG aggregate c8, defaults | 195.3 tok/s | 5.12 | 3 | llama-benchy task mode, ISL/OSL 2048/512 | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG aggregate c8, coding | 317.4 tok/s | 1.34 | 3 | 36 prompts, OSL ≤768, aggregate output tok/s per pass | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TTFT, ISL 16K | 5.49 s | 0.04 | 4 | c1, cold prefix | – | v2.2.0 (2026-10-15) | results |
| Hardmode | 90/100 | – | 1 | 88 multi-step tool-use scenarios; pass ≥ 88 | T=0, thinking on | v2.2.0 (2026-10-16) | results |
| GPU power, idle | 19.5 W | – | 3 | server up, no requests, 120 s | – | v2.0.0 (2026-10-10) | results |
| GPU power, TG c1 defaults | 49.8 W | – | 3 | power.draw, GPU only (nvidia-smi), not wall power, mean over the cell | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG per GPU watt, c1 defaults | 1.73 tok/s/W | – | 3 | TG aggregate / GPU W | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| GPU power, TG c8 defaults | 61.4 W | – | 3 | power.draw, GPU only (nvidia-smi), not wall power, mean over the cell | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG per GPU watt, c8 defaults | 3.18 tok/s/W | – | 3 | TG aggregate / GPU W | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
Method and full tables: docs/BENCHMARKS.md.
TP=2 or DP=2? On the same two Sparks, DP=2 (two 1x replicas behind a router) has higher aggregate TG at c8 (203.6 vs 172.2 tok/s) and c16 (240.9 vs 231.3 tok/s), and finished every agent-replay workload 29-37% sooner. TP=2 (this recipe) is faster at c1-c4 and holds more long contexts: 13.46 concurrent 262K requests in one KV cache against 2 × 3.79 for DP=2. k76, 2026-10-10, ISL/OSL 2048/512, server defaults; agent replay 2026-10-08. Details: 1x vs 2x TP=2 vs 2x DP=2.
Requirements:
6.17.0-1032-nvidia on both nodes; 7.0.0-1019-nvidia breaks NCCL (ibv_reg_mr fails past ~85 GB
GPU-resident).loginctl enable-linger nvidia on both nodes (otherwise logind removes the shared-memory ring buffer).sparkrun registry add https://github.com/ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2
sparkrun run qwen3.8-flash-next-2x-dgx-spark
First run downloads ~140 GB per node (time depends on the link; a 98 GB checkpoint download took 3 h 15 min at
~8.6 MB/s on mine). Boot with model and image on disk: ~5 min (279 s on the v2.2.0 image check, with the plan file and the compile
cache coming from the image). A runtime cache that has never seen your GPUs also builds ~50 b12x kernels, which are
keyed by GPU and not shipped (348 s on the v2.1.0 image check); a cold-page-cache boot was not timed on v2.2.0. sparkrun run returns before the
engine is ready: poll http://<head>:8000/health (<head> = first node), then use http://<head>:8000/v1,
model qwen3.8-flash-next:
curl http://<head>:8000/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model": "qwen3.8-flash-next", "messages": [{"role": "user", "content": "Hello"}]}'
No API key: keep it on a trusted network or put an authenticating proxy in front.
Multi-user setups. I set this recipe up for a single user. --enable-prompt-tokens-details (since v2.1.0) makes the usage field report prompt_tokens_details.cached_tokens exactly, so on a server shared by several users or tenants one user can tell whether someone else already sent the same prompt prefix. Prefix caching already leaks this through time to first token; the flag makes it exact. With several users or tenants, give each one its own random, secret cache_salt in the request body (the pinned vLLM accepts it on /v1/chat/completions, /v1/completions and /v1/responses; a gateway can set it per API key), so their prefix-cache entries never match each other. Otherwise, remove --enable-prompt-tokens-details from the recipe.
sparkrun run returns before the engine is ready; wait
for health, then check that a reply has both reasoning and an answer:
H=http://<head>:8000
until [ "$(curl -s -o /dev/null -w '%{http_code}' $H/health)" = 200 ]; do sleep 10; done
curl -s $H/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model":"qwen3.8-flash-next","max_tokens":1024,
"messages":[{"role":"user","content":"Write a Python function that reverses a string."}]}' \
| python3 -c "import json,sys; m=json.load(sys.stdin)['choices'][0]['message']
print('reasoning:', len(m.get('reasoning') or m.get('reasoning_content') or ''), 'chars')
print('content :', (m.get('content') or '')[:300])"
Empty content with long reasoning means max_tokens ran out inside thinking; garbled text means a checkpoint
mismatch (see Troubleshooting under Details below). Optional long-context check (stdlib Python), expect exact 20 at
both depths:
git clone https://github.com/ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2 && cd qwen3.8-flash-next-dgx-spark-tp-2
python3 scripts/fidelity_probe.py --base $H --model qwen3.8-flash-next --depths 8000,32000
The server binds 0.0.0.0 with no API key: keep it on a trusted network or put a proxy with auth in front. Stop with
sparkrun stop --all. To upgrade, run sparkrun registry update qwen38-flashnext first: sparkrun caches registries
and does not refresh them on run.
This repo still has copies of the single-Spark recipes
qwen3.8-flash-next-1x-dgx-sparkand-previous, so existing setups keep working; new one-Spark releases ship only in the one-Spark repo. With both registries added, use@qwen38-flashnext-1x/qwen3.8-flash-next-1x-dgx-spark.
| Model | ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057: NVFP4 experts, MXFP8 dense and attention, GDN projections NVFP4 for TG and MXFP8 for PP |
| MTP | MTP ×4, probabilistic drafts, rejection sampling; drafter D3 refit on the served model's outputs (since v2.1.0) |
| Hardware | 2x DGX Spark (GB10, 128 GB unified each), CX-7 back to back |
| Parallelism | TP=2 over RoCE, one rank per GB10 |
| Engine | vLLM + b12x (NVFP4 MoE, MXFP8 linears, GDN, QSA), GDN prefill without token gathers (v2.2.0), plan and compile caches baked into the image |
| Context | 262,144 tokens (max_model_len) |
| Concurrent requests | up to 16 (max_num_seqs) |
| KV cache | 3,527,297 tokens, 13.46 concurrent 262K requests (v2.0.0 boot log) |
| API | OpenAI-compatible, tool calling, reasoning on (reasoning_effort medium); usage.prompt_tokens_details.cached_tokens reports prefix-cache hits |
| Disk / start time | ~140 GB per node / ~5 min warm (279 s on the v2.2.0 check boot; up to ~6 min when the b12x kernels for your GPUs are not built yet) |
| License | Recipe Apache-2.0; weights Qwen Community License 1.0 |
| Task | Section |
|---|---|
| TG, PP, TTFT, context depth | Performance |
| Sizing: 1x vs TP=2 vs DP=2 | 1x vs 2x TP=2 vs 2x DP=2 |
| Quality gate results | Quality gate |
| Raw data and provenance per number | Every number and where it comes from |
| Pin or roll back a release | Recipes, VERSIONS.md |
Aggregate TG scales to c16 while per-request TG drops. TTFT grows with ISL on a cold prefix; multi-turn requests hit the prefix cache and run PP only on the new tokens.
Left: 1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode. Right: 1x Spark v2.1.0, 2x Spark TP=2 v2.2.0/v1.4.0; c1, cold prefix.
Left: llama-benchy task mode (agent coding turn), ISL/OSL 2048/512, T=1.0, top-p 0.95, top-k 20, thinking on, prefix caching. Aggregate = all output tokens / wall time, PP included; per request = TG rate of one stream after its first token.
1x Spark: release v2.1.0, 2026-10-08, llama-benchy task mode, mean ± sd over runs, one boot
2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
2x Spark DP=2: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
Versions are numbered per setup. Data: docs/data/capability.csv, with the source file of every point.
Right: c1, cold prefix (no cache hit). Each point: mean of 1 to 4 samples of one run (n per point in the CSV).
1x Spark: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271
2x Spark TP=2: release v2.2.0, 2026-10-15, cold.py (k101); v1.4.0, 2026-10-07, fidelity_probe.py
1x Spark: not measured above 128K yet.
Data: docs/data/capability.csv, with the source file of every point.
Left: 1x Spark v2.1.0, 2x Spark TP=2 v2.2.0; c1, cold prefix. Right: 2x Spark TP=2 v2.0.0; llama-benchy, ISL/OSL 2048/512 at depth.
Left: c1, cold prefix (no cache hit). Each point: mean of 1 to 4 samples of one run (n per point in the CSV).
1x Spark: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271
2x Spark TP=2: release v2.2.0, 2026-10-15, cold.py (k101)
Data: docs/data/capability.csv, with the source file of every point.
Right: 2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot (older release; shipped v2.2.0 not measured on this test yet). Server default sampling. Each point: ISL/OSL 2048/512 on top of the cached context, same harness as the concurrency chart. Concurrency levels measured at one depth only are left out.
Data: docs/data/capability.csv, with the source file of every point.
1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode.
llama-benchy task mode (agent coding turn), ISL/OSL 2048/512, T=1.0, top-p 0.95, top-k 20, thinking on, prefix caching. Aggregate = all output tokens / wall time, PP included; per request = TG rate of one stream after its first token.
1x Spark: release v2.1.0, 2026-10-08, llama-benchy task mode, mean ± sd over runs, one boot
2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
2x Spark DP=2: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
Versions are numbered per setup. Data: docs/data/capability.csv, with the source file of every point.
| Concurrency | TG tok/s, aggregate | TG tok/s, per request | Release, run |
|---|---|---|---|
| 1 | 86.3 ± 2.4 | 88.7 ± 2.6 | v2.0.0, 2026-10-10 |
| 2 | 114.9 ± 7.9 | 62.4 ± 5.3 | v2.0.0, 2026-10-10 |
| 4 | 156.6 ± 4.6 | 48.6 ± 1.5 | v2.0.0, 2026-10-10 |
| 8 | 195.3 ± 5.1 | 33.2 ± 1.0 | v2.0.0, 2026-10-10 |
| 16 | 231.3 ± 4.9 | 22.1 ± 0.4 | v2.0.0, 2026-10-10 |
Release v2.0.0. llama-benchy task mode, ISL/OSL 2048/512, T=1.0, thinking on; mean ± sd, where sd is between runs or between boots as the CSV stat column says for each run.
| ISL | PP tok/s | TTFT s | Release, run |
|---|---|---|---|
| 512 | 1,718 ± 19 | 0.3 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 2K | 2,892 ± 11 | 0.7 ± 0.0 | v2.2.0, 2026-10-15, llama-benchy task mode |
| 8K | 2,865 ± 14 | 2.9 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 16K | 2,986 ± 25 | 5.5 ± 0.0 | v2.2.0, 2026-10-15, cold.py (k101) |
| 32K | 2,810 ± 5 | 11.7 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 64K | 2,726 ± 6 | 24.0 ± 0.1 | v2.2.0, 2026-10-15, cold.py (k101) |
| 128K | 2,426 ± 12 | 54.0 ± 0.3 | v2.2.0, 2026-10-15, cold.py (k101) |
c1, cold prefix.
Shipped release of each setup where measured; the bracket names an older release.
TG, coding c1 (median, n=36, T=0): 1x v2.1.0, 2026-10-10, coding_probe.py, up to 768 tokens out; TP=2 v2.0.0, 2026-10-10, coding_probe.py, up to 768 tokens out; not measured: 2x Spark DP=2.
TG c1, defaults: 1x v2.2.0, 2026-10-10, llama-benchy task mode; TP=2 v2.0.0, 2026-10-10, llama-benchy task mode; DP=2 v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271.
TG, aggregate c8, defaults: 1x v2.2.0, 2026-10-10, llama-benchy task mode; TP=2 v2.0.0, 2026-10-10, llama-benchy task mode; DP=2 v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271.
TTFT, ISL 16K, cold prefix: 1x v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271; TP=2 v2.2.0, 2026-10-15, cold.py (k101); not measured: 2x Spark DP=2.
KV cache: concurrent 262K-token requests: 1x v2.1.0, 2026-10-08, serve log; TP=2 v2.0.0, 2026-10-09, serve log; DP=2 v2.1.0, 2026-10-08, serve log.
TG c1/c8: llama-benchy task mode, ISL/OSL 2048/512, server defaults. KV row: vLLM's 'Maximum concurrency for 262,144 tokens per request' at boot (DP=2: two replicas, one pool each).
Data: docs/data/capability.csv, with the source file of every point.
Agent replay, one boot per layout: two-Spark v1.5.0 (old name b1.6) (TP=2); DP=2 on one-Spark v2.1.0 (old name v3e) (DP=2).
All sessions start at once; each turn resends the full conversation (prefix cache on), tools on, T=0.6, thinking off.
2x Spark TP=2: two-Spark v1.5.0 (old name b1.6), 2026-10-08, agent replay (drive.py), one boot
2x Spark DP=2: DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, agent replay (drive.py), one boot
Data: docs/data/capability.csv, with the source file of every point.
| Check | Result | Criterion |
|---|---|---|
| Hardmode | 90/100 | score /100, pass ≥ 88 (88 scenarios), T=0, thinking on |
| TC-45 | 100/100 | regression test (1 scenario, 2 pts, 5 trials) |
| Fidelity | 20/20 | 20 needles retrieved via tool calls, ISL 8K to ~245K |
| Stragglers | none | no stalled request at c7-c16 |
Gate run: release v2.2.0, 2026-10-16. Every release passes this gate before promotion.
Full gate tables: docs/BENCHMARKS.md.
Each release's own promotion run and gate; shaded: reasoning_effort xhigh.
TG: llama-benchy task mode, ISL/OSL 2048/512, T=1.0, thinking on, from each release's own first run (usually its promotion A/B; the runs line below names each), unpaired across releases, so day-to-day drift is included; paired A/B per release in VERSIONS.md.
Shaded (b0 to v1.4.0): chat template default reasoning_effort xhigh; later releases at the recipe default, medium. Different reasoning lengths, so not like-for-like across the shade.
TG runs: b0 2026-09-23 (llama-benchy task mode, mean ± sd over runs, one boot); v1.0.0 2026-09-25, v1.1.0 2026-09-26, v1.2.0 2026-09-27, v1.3.0 2026-09-29 (llama-benchy task mode, mean of 2 candidate boots x 3 runs; sd not recorded); v1.4.0 2026-10-01 (llama-benchy task mode, mean of 2 boots x 3 runs, sd between boots); v1.5.0 2026-10-08, v2.0.0 2026-10-09 (llama-benchy task mode, mean of 2 boots x 4 runs, sd between boots).
Hardmode: promotion gate of each release (b0: gate on the pinned checkpoint), b0 2026-09-23, v1.0.0 2026-09-25, v1.1.0 2026-09-26, v1.2.0 2026-09-27, v1.3.0 2026-09-29, v1.4.0 2026-10-01, v1.5.0 2026-10-08, v2.0.0 2026-10-09, v2.1.0 2026-10-12, v2.2.0 2026-10-16.
Data: docs/data/capability.csv, with the source file of every point.
reasoning_effort medium, tool calling, max_model_len 262,144.The shipped releases were measured as a full grid in k76 on 2026-10-10
(#128); where a cell has no k76 value,
the table shows the newest release that has one.
TG rows are aggregate unless marked "per request"; sampling is the server default (temperature 1.0,
thinking on) unless the run says otherwise. The charts and tables are written by
uv run scripts/make_charts.py from docs/data/capability.csv, where every point lists
its raw file. Full grids for every build: docs/BENCHMARKS.md.
| 1x Spark | 2x Spark TP=2 | 2x Spark DP=2 | |
|---|---|---|---|
| TG c1, tok/s | 60.4 a | 88.7 b | 57.5 c |
| TG c4, tok/s: per request / aggregate | 32.4 d / 111.4 d | 48.6 b / 156.6 b | 43.4 c / 137.5 c |
| TG c8, tok/s: per request / aggregate | 22.0 d / 136.7 a | 33.2 b / 172.2 b | 32.5 c / 203.6 c |
| TG c16, tok/s: per request / aggregate | > max_num_seqs 8 | 22.1 b / 231.3 b | 21.7 c / 240.9 c |
| TG, coding c1 (36 prompts), tok/s median: T=0 / defaults | 77 e / 61 e | 119 f / 92 f | not measured |
| Copy-heavy (MTP acceptance near 1), aggregate tok/s c1 / c4 / c8, mean | 80.8 g / 183.5 g / 274.1 g | 120.5 h / 303.5 h / 453.4 h | not measured |
| PP tok/s, ISL 2K / 16K / 64K / 128K, c1 | 1,760 d / 1,866 d / 1,993 d / 1,812 d | 2,892 i / 2,986 j / 2,726 j / 2,426 j | not measured |
| TTFT s, ISL 2K / 16K / 64K / 128K, cold prefix | 1.2 d / 9.1 d / 32.9 d / 72.5 d | 0.7 i / 5.5 j / 24.0 j / 54.0 j | not measured |
| ITL p50, ms, c1 / c8 (MTP emits several tokens per step) | 19 k / 47 k | 13 l / 31 l | 19 m / 48 m |
| Stream chunk gap p50, ms, c1 / c8 | 56 n / 143 n | 41 o / 90 o | not measured |
| TG aggregate tok/s, depth 0 → 64K, c1 / c4 | 56.9 d → 55.6 d / 111.4 d → 98.4 d | 88.7 b → 82.8 b / 156.6 b → 104.3 b | 57.5 c → – / 137.5 c → – |
| max_model_len | 262,144 (recipe) | 262,144 (recipe) | 262,144 (recipe) |
| KV cache, tokens | 993,754 p | 3,525,633 q | 1,987,508 r |
| Concurrent 262K requests in KV (vLLM count) | 3.79 s | 13.46 t | 2 × 3.79 u |
| Requests that fit KV at 16K / 64K / 128K | 39.7 v / 14.1 v / 7.5 v | not measured | not measured |
| Gate: hardmode / TC-45 / fidelity to ~245K / stragglers | 91 w / 100 w / 20/20 w / none, c8-c16 w | 90 x / 100 x / 20/20 x / none, c7-c16 x | 93 y / 100 y / 20/20 y / none, c5-c16 y |
Releases and runs behind the numbers:
| Hardware | Two DGX Sparks (GB10, 128 GB unified), CX-7 ports cabled back to back |
| Launcher | sparkrun ≥ 0.3.6 with a two-node cluster defined |
| Disk | ~140 GB per node (98 GiB checkpoint + 2.8 GB MXFP8 shard of 7c4f1bc1 + ~31 GB image) |
| Kernel | 6.17.0-1032-nvidia. 7.0.0-1019-nvidia breaks NCCL ibv_reg_mr past ~85 GB GPU-resident (forum) |
| Host setting | loginctl enable-linger nvidia on both nodes (otherwise logind RemoveIPC kills the shm ring buffer) |
| Boot | ~3 min warm (170 s on the v2.0.0 image check); a cold-page-cache boot was not timed on v2.0.0 (v1.5.0: ~9.5 min) |
| Concurrency | max_num_seqs 16, KV pool 3,527,297 tokens on the v2.0.0 check boot (vLLM sizes it at each boot; v1.x boots logged 3.57M to 3.76M, and the per-rank MXFP8 copies of v2.0.0 take about 4%) |
Checkpoint: ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE
@ 16c9bd54 (v2.0.0): local-inference-lab/Qwen3.8-Flash-Next-NVFP4
@ 7c4f1bc1 with the GDN projections requantized to weight-only NVFP4 and the retrained drafter D1 (NVFP4 experts;
MXFP8 dense and attention; 98 GiB). PP-sized GDN calls (41+ rows) use the MXFP8 GDN weights of 7c4f1bc1, read
from its shard 35, which the recipe downloads on the first boot. Image and video input are not tested.
max_num_seqs 32 run (quality gate not run at that cap) reached 285.5 tok/s aggregate TG at c32
(llama-benchy task mode, mean of 3 runs, v1.4.0; high concurrency).| Recipe | Release | Image | Use |
|---|---|---|---|
qwen3.8-flash-next-2x-dgx-spark | v2.2.0 | k104s-20261016-21e0b201-d21d7ade-10d0acc8-warm (also 2x-v2.2.0) | Default |
qwen3.8-flash-next-2x-dgx-spark-previous | v2.1.0 | k94-20261013-21e0b201-d21d7ade-warm (also 2x-v2.1.0) | Rollback, same checkpoint without GDN prefill views |
qwen3.8-flash-next-1x-dgx-spark, -previous | one-Spark v2.3.0 / v2.2.0 | Compatibility copies; the one-Spark recipes live in qwen3.8-flash-next-1x-dgx-spark |
Rolling back to -previous (v2.1.0) needs no download: it serves the same checkpoint (ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057 plus shard 35 of 7c4f1bc1) on the v2.1.0 image. v2.2.0 keeps its torch compile cache in a root of its own (VLLM_CACHE_ROOT), so switching does not recompile: in the v2.2.0 check the v2.2.0 boot loaded its cache from its own root, and the v2.1.0 boot after it left the shared root unchanged. The v2.0.0 recipe is archived in archive/recipes/. Renames: recipes/RENAMES.md.
Releases use semantic versions per repo; VERSIONS.md lists every shipped release with its old build name, date, what changed and its manifest (image digest, vLLM and b12x commits, checkpoint, drafter, plan seed). Each release's own A/B against the one before it is in the release history and in docs/BENCHMARKS.md. The single-Spark releases have their own list in the one-Spark repo.
scripts/arm_verdict.py).Index of every run and verdict: results/README.md.
Flags, environment variables and the reason for each: docs/REFERENCE.md. Kernel and engine notes, rejected experiments: docs/ENGINEERING.md.
The recipes set the server default to reasoning_effort: medium. On my DevOps task set, two-Spark v1.2.0 (old name b1.2) at the template
default xhigh passed 42.5% of checks with 23/42 runaway-thinking runs and a 246 s median per task; v1.4.0 (old name b1.4) at medium
passed 95.9% with 0/42 runaway and 33 s. A request can still ask for xhigh or low; "reasoning_effort": "none"
turns thinking off. For hard reasoning problems xhigh per request helps: on the hotel-lights check, the one-Spark v3c (old name)
went from 21/32 at medium to 31/31 completed runs
(#88); allow a long client timeout,
since most of those answers took over 30 minutes at 8 concurrent requests. Override table:
docs/REFERENCE.md.
| Symptom | Fix |
|---|---|
/health silent for minutes | Expected on a cold boot. sparkrun logs <recipe> -f |
| OOM / earlyoom at start | Unified memory: gpu_memory_utilization ≥ 0.84 starves host RAM. Keep 0.80 and clear other containers |
NCCL ibv_reg_mr_iova2 ... Cannot allocate memory | Kernel 7.0.0-1019-nvidia; hold 6.17.0-1032-nvidia |
ShmRingBuffer ... shared_memory crash | loginctl enable-linger nvidia on both nodes |
| Garbled output | Rank checkpoint mismatch: both serve logs must show snapshots/16c9bd54 (7c4f1bc1 on -previous) (why) |
Empty content, long reasoning | max_tokens ran out during thinking; raise it or send "reasoning_effort": "low" |
| Old release boots after an upgrade | sparkrun registry update qwen38-flashnext |
| Anything else | Try the -previous recipe, then open an issue with sparkrun logs <recipe> -a |
| VERSIONS.md | Release names, old build names, manifests |
| docs/BENCHMARKS.md | Full benchmark and quality tables for every build, method, history |
| docs/REFERENCE.md | Configuration, image provenance, thinking effort, tuning |
| docs/ENGINEERING.md | Known issues and fixes, profiling, rejected experiments |
| tools/dp2/ | DP=2 router: how to run it, flags, limits |
| results/ | Every raw measurement and verdict |
| archive/ | Experimental and superseded recipes (not listed by sparkrun) |
This build stands on these projects:
Earlier experiments drew on other projects too; docs/ENGINEERING.md keeps that full history with exact pins.
Apache-2.0, see LICENSE. The vLLM overlays under archive/mods/ keep their upstream Apache-2.0 headers.
Model weights are not part of this repo and keep their own license (Qwen Community License 1.0; see each checkpoint's
model card).

sparkrun recipe: Qwen3.8-Flash-Next (NVFP4 / MXFP8, MTP ×4) on 2x DGX Spark, TP=2 over CX-7.
vLLM with b12x GB10 kernels, OpenAI-compatible endpoint, quality-gated releases.
86 / 113 tok/sTG c1defaults (ISL/OSL 2048/512), v2.0.0 / T=0, thinking off (coding), v2.0.0 | 195 / 317 tok/sTG aggregate c8defaults (ISL/OSL 2048/512), v2.0.0 / T=0, thinking off (coding), v2.0.0 | 90/100Hardmodepass ≥ 88, T=0, thinking on | 5.5 sTTFT, ISL 16Kc1, cold prefix |
262K context, max_num_seqs 16, MTP ×4, OpenAI-compatible API, quality-gated releases
GPU power, GPU only (nvidia-smi), not wall power: idle 19.5 W, c1 49.8 W, c8 61.4 W; TG per GPU watt 1.73 (c1) and 3.18 (c8) tok/s/W at server defaults.
TG cells: server defaults (ISL/OSL 2048/512, T=1.0, thinking on) first, T=0 coding (36 prompts, thinking off) second, both aggregate output tok/s. Means over 3 boots where measured; sd between boots in the details. Release v2.0.0.
| Metric | Mean | sd between boots | Boots | Workload | Sampling | Release (date) | Source |
|---|---|---|---|---|---|---|---|
| TG c1, defaults | 86.3 tok/s | 2.37 | 3 | llama-benchy task mode, ISL/OSL 2048/512 | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG c1, coding | 112.8 tok/s | 0.02 | 3 | 36 prompts, OSL ≤768, aggregate output tok/s per pass | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TG c1, coding, per-prompt median | 118.7 tok/s | – | 1 | 36 prompts (Python, C++, Rust, Go), OSL ≤768, median over prompts | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TG c1, coding, defaults, per-prompt median | 92.0 tok/s | – | 1 | 36 prompts, OSL ≤768; 100/108 requests hit the 768-token cap inside thinking | server defaults, thinking on | v2.0.0 (2026-10-10) | results |
| TG aggregate c8, defaults | 195.3 tok/s | 5.12 | 3 | llama-benchy task mode, ISL/OSL 2048/512 | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG aggregate c8, coding | 317.4 tok/s | 1.34 | 3 | 36 prompts, OSL ≤768, aggregate output tok/s per pass | T=0, thinking off | v2.0.0 (2026-10-10) | results |
| TTFT, ISL 16K | 5.49 s | 0.04 | 4 | c1, cold prefix | – | v2.2.0 (2026-10-15) | results |
| Hardmode | 90/100 | – | 1 | 88 multi-step tool-use scenarios; pass ≥ 88 | T=0, thinking on | v2.2.0 (2026-10-16) | results |
| GPU power, idle | 19.5 W | – | 3 | server up, no requests, 120 s | – | v2.0.0 (2026-10-10) | results |
| GPU power, TG c1 defaults | 49.8 W | – | 3 | power.draw, GPU only (nvidia-smi), not wall power, mean over the cell | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG per GPU watt, c1 defaults | 1.73 tok/s/W | – | 3 | TG aggregate / GPU W | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| GPU power, TG c8 defaults | 61.4 W | – | 3 | power.draw, GPU only (nvidia-smi), not wall power, mean over the cell | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
| TG per GPU watt, c8 defaults | 3.18 tok/s/W | – | 3 | TG aggregate / GPU W | T=1.0, top-p 0.95, top-k 20, thinking on | v2.0.0 (2026-10-10) | results |
Method and full tables: docs/BENCHMARKS.md.
TP=2 or DP=2? On the same two Sparks, DP=2 (two 1x replicas behind a router) has higher aggregate TG at c8 (203.6 vs 172.2 tok/s) and c16 (240.9 vs 231.3 tok/s), and finished every agent-replay workload 29-37% sooner. TP=2 (this recipe) is faster at c1-c4 and holds more long contexts: 13.46 concurrent 262K requests in one KV cache against 2 × 3.79 for DP=2. k76, 2026-10-10, ISL/OSL 2048/512, server defaults; agent replay 2026-10-08. Details: 1x vs 2x TP=2 vs 2x DP=2.
Requirements:
6.17.0-1032-nvidia on both nodes; 7.0.0-1019-nvidia breaks NCCL (ibv_reg_mr fails past ~85 GB
GPU-resident).loginctl enable-linger nvidia on both nodes (otherwise logind removes the shared-memory ring buffer).sparkrun registry add https://github.com/ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2
sparkrun run qwen3.8-flash-next-2x-dgx-spark
First run downloads ~140 GB per node (time depends on the link; a 98 GB checkpoint download took 3 h 15 min at
~8.6 MB/s on mine). Boot with model and image on disk: ~5 min (279 s on the v2.2.0 image check, with the plan file and the compile
cache coming from the image). A runtime cache that has never seen your GPUs also builds ~50 b12x kernels, which are
keyed by GPU and not shipped (348 s on the v2.1.0 image check); a cold-page-cache boot was not timed on v2.2.0. sparkrun run returns before the
engine is ready: poll http://<head>:8000/health (<head> = first node), then use http://<head>:8000/v1,
model qwen3.8-flash-next:
curl http://<head>:8000/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model": "qwen3.8-flash-next", "messages": [{"role": "user", "content": "Hello"}]}'
No API key: keep it on a trusted network or put an authenticating proxy in front.
Multi-user setups. I set this recipe up for a single user. --enable-prompt-tokens-details (since v2.1.0) makes the usage field report prompt_tokens_details.cached_tokens exactly, so on a server shared by several users or tenants one user can tell whether someone else already sent the same prompt prefix. Prefix caching already leaks this through time to first token; the flag makes it exact. With several users or tenants, give each one its own random, secret cache_salt in the request body (the pinned vLLM accepts it on /v1/chat/completions, /v1/completions and /v1/responses; a gateway can set it per API key), so their prefix-cache entries never match each other. Otherwise, remove --enable-prompt-tokens-details from the recipe.
sparkrun run returns before the engine is ready; wait
for health, then check that a reply has both reasoning and an answer:
H=http://<head>:8000
until [ "$(curl -s -o /dev/null -w '%{http_code}' $H/health)" = 200 ]; do sleep 10; done
curl -s $H/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model":"qwen3.8-flash-next","max_tokens":1024,
"messages":[{"role":"user","content":"Write a Python function that reverses a string."}]}' \
| python3 -c "import json,sys; m=json.load(sys.stdin)['choices'][0]['message']
print('reasoning:', len(m.get('reasoning') or m.get('reasoning_content') or ''), 'chars')
print('content :', (m.get('content') or '')[:300])"
Empty content with long reasoning means max_tokens ran out inside thinking; garbled text means a checkpoint
mismatch (see Troubleshooting under Details below). Optional long-context check (stdlib Python), expect exact 20 at
both depths:
git clone https://github.com/ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2 && cd qwen3.8-flash-next-dgx-spark-tp-2
python3 scripts/fidelity_probe.py --base $H --model qwen3.8-flash-next --depths 8000,32000
The server binds 0.0.0.0 with no API key: keep it on a trusted network or put a proxy with auth in front. Stop with
sparkrun stop --all. To upgrade, run sparkrun registry update qwen38-flashnext first: sparkrun caches registries
and does not refresh them on run.
This repo still has copies of the single-Spark recipes
qwen3.8-flash-next-1x-dgx-sparkand-previous, so existing setups keep working; new one-Spark releases ship only in the one-Spark repo. With both registries added, use@qwen38-flashnext-1x/qwen3.8-flash-next-1x-dgx-spark.
| Model | ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057: NVFP4 experts, MXFP8 dense and attention, GDN projections NVFP4 for TG and MXFP8 for PP |
| MTP | MTP ×4, probabilistic drafts, rejection sampling; drafter D3 refit on the served model's outputs (since v2.1.0) |
| Hardware | 2x DGX Spark (GB10, 128 GB unified each), CX-7 back to back |
| Parallelism | TP=2 over RoCE, one rank per GB10 |
| Engine | vLLM + b12x (NVFP4 MoE, MXFP8 linears, GDN, QSA), GDN prefill without token gathers (v2.2.0), plan and compile caches baked into the image |
| Context | 262,144 tokens (max_model_len) |
| Concurrent requests | up to 16 (max_num_seqs) |
| KV cache | 3,527,297 tokens, 13.46 concurrent 262K requests (v2.0.0 boot log) |
| API | OpenAI-compatible, tool calling, reasoning on (reasoning_effort medium); usage.prompt_tokens_details.cached_tokens reports prefix-cache hits |
| Disk / start time | ~140 GB per node / ~5 min warm (279 s on the v2.2.0 check boot; up to ~6 min when the b12x kernels for your GPUs are not built yet) |
| License | Recipe Apache-2.0; weights Qwen Community License 1.0 |
| Task | Section |
|---|---|
| TG, PP, TTFT, context depth | Performance |
| Sizing: 1x vs TP=2 vs DP=2 | 1x vs 2x TP=2 vs 2x DP=2 |
| Quality gate results | Quality gate |
| Raw data and provenance per number | Every number and where it comes from |
| Pin or roll back a release | Recipes, VERSIONS.md |
Aggregate TG scales to c16 while per-request TG drops. TTFT grows with ISL on a cold prefix; multi-turn requests hit the prefix cache and run PP only on the new tokens.
Left: 1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode. Right: 1x Spark v2.1.0, 2x Spark TP=2 v2.2.0/v1.4.0; c1, cold prefix.
Left: llama-benchy task mode (agent coding turn), ISL/OSL 2048/512, T=1.0, top-p 0.95, top-k 20, thinking on, prefix caching. Aggregate = all output tokens / wall time, PP included; per request = TG rate of one stream after its first token.
1x Spark: release v2.1.0, 2026-10-08, llama-benchy task mode, mean ± sd over runs, one boot
2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
2x Spark DP=2: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
Versions are numbered per setup. Data: docs/data/capability.csv, with the source file of every point.
Right: c1, cold prefix (no cache hit). Each point: mean of 1 to 4 samples of one run (n per point in the CSV).
1x Spark: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271
2x Spark TP=2: release v2.2.0, 2026-10-15, cold.py (k101); v1.4.0, 2026-10-07, fidelity_probe.py
1x Spark: not measured above 128K yet.
Data: docs/data/capability.csv, with the source file of every point.
Left: 1x Spark v2.1.0, 2x Spark TP=2 v2.2.0; c1, cold prefix. Right: 2x Spark TP=2 v2.0.0; llama-benchy, ISL/OSL 2048/512 at depth.
Left: c1, cold prefix (no cache hit). Each point: mean of 1 to 4 samples of one run (n per point in the CSV).
1x Spark: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271
2x Spark TP=2: release v2.2.0, 2026-10-15, cold.py (k101)
Data: docs/data/capability.csv, with the source file of every point.
Right: 2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot (older release; shipped v2.2.0 not measured on this test yet). Server default sampling. Each point: ISL/OSL 2048/512 on top of the cached context, same harness as the concurrency chart. Concurrency levels measured at one depth only are left out.
Data: docs/data/capability.csv, with the source file of every point.
1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode.
llama-benchy task mode (agent coding turn), ISL/OSL 2048/512, T=1.0, top-p 0.95, top-k 20, thinking on, prefix caching. Aggregate = all output tokens / wall time, PP included; per request = TG rate of one stream after its first token.
1x Spark: release v2.1.0, 2026-10-08, llama-benchy task mode, mean ± sd over runs, one boot
2x Spark TP=2: release v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
2x Spark DP=2: release v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271, mean ± sd of 3 runs, one boot
Versions are numbered per setup. Data: docs/data/capability.csv, with the source file of every point.
| Concurrency | TG tok/s, aggregate | TG tok/s, per request | Release, run |
|---|---|---|---|
| 1 | 86.3 ± 2.4 | 88.7 ± 2.6 | v2.0.0, 2026-10-10 |
| 2 | 114.9 ± 7.9 | 62.4 ± 5.3 | v2.0.0, 2026-10-10 |
| 4 | 156.6 ± 4.6 | 48.6 ± 1.5 | v2.0.0, 2026-10-10 |
| 8 | 195.3 ± 5.1 | 33.2 ± 1.0 | v2.0.0, 2026-10-10 |
| 16 | 231.3 ± 4.9 | 22.1 ± 0.4 | v2.0.0, 2026-10-10 |
Release v2.0.0. llama-benchy task mode, ISL/OSL 2048/512, T=1.0, thinking on; mean ± sd, where sd is between runs or between boots as the CSV stat column says for each run.
| ISL | PP tok/s | TTFT s | Release, run |
|---|---|---|---|
| 512 | 1,718 ± 19 | 0.3 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 2K | 2,892 ± 11 | 0.7 ± 0.0 | v2.2.0, 2026-10-15, llama-benchy task mode |
| 8K | 2,865 ± 14 | 2.9 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 16K | 2,986 ± 25 | 5.5 ± 0.0 | v2.2.0, 2026-10-15, cold.py (k101) |
| 32K | 2,810 ± 5 | 11.7 ± 0.0 | v2.0.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271 |
| 64K | 2,726 ± 6 | 24.0 ± 0.1 | v2.2.0, 2026-10-15, cold.py (k101) |
| 128K | 2,426 ± 12 | 54.0 ± 0.3 | v2.2.0, 2026-10-15, cold.py (k101) |
c1, cold prefix.
Shipped release of each setup where measured; the bracket names an older release.
TG, coding c1 (median, n=36, T=0): 1x v2.1.0, 2026-10-10, coding_probe.py, up to 768 tokens out; TP=2 v2.0.0, 2026-10-10, coding_probe.py, up to 768 tokens out; not measured: 2x Spark DP=2.
TG c1, defaults: 1x v2.2.0, 2026-10-10, llama-benchy task mode; TP=2 v2.0.0, 2026-10-10, llama-benchy task mode; DP=2 v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271.
TG, aggregate c8, defaults: 1x v2.2.0, 2026-10-10, llama-benchy task mode; TP=2 v2.0.0, 2026-10-10, llama-benchy task mode; DP=2 v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271.
TTFT, ISL 16K, cold prefix: 1x v2.1.0, 2026-10-10, llama-benchy 0.4.1.dev4+g0d4de4271; TP=2 v2.2.0, 2026-10-15, cold.py (k101); not measured: 2x Spark DP=2.
KV cache: concurrent 262K-token requests: 1x v2.1.0, 2026-10-08, serve log; TP=2 v2.0.0, 2026-10-09, serve log; DP=2 v2.1.0, 2026-10-08, serve log.
TG c1/c8: llama-benchy task mode, ISL/OSL 2048/512, server defaults. KV row: vLLM's 'Maximum concurrency for 262,144 tokens per request' at boot (DP=2: two replicas, one pool each).
Data: docs/data/capability.csv, with the source file of every point.
Agent replay, one boot per layout: two-Spark v1.5.0 (old name b1.6) (TP=2); DP=2 on one-Spark v2.1.0 (old name v3e) (DP=2).
All sessions start at once; each turn resends the full conversation (prefix cache on), tools on, T=0.6, thinking off.
2x Spark TP=2: two-Spark v1.5.0 (old name b1.6), 2026-10-08, agent replay (drive.py), one boot
2x Spark DP=2: DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, agent replay (drive.py), one boot
Data: docs/data/capability.csv, with the source file of every point.
| Check | Result | Criterion |
|---|---|---|
| Hardmode | 90/100 | score /100, pass ≥ 88 (88 scenarios), T=0, thinking on |
| TC-45 | 100/100 | regression test (1 scenario, 2 pts, 5 trials) |
| Fidelity | 20/20 | 20 needles retrieved via tool calls, ISL 8K to ~245K |
| Stragglers | none | no stalled request at c7-c16 |
Gate run: release v2.2.0, 2026-10-16. Every release passes this gate before promotion.
Full gate tables: docs/BENCHMARKS.md.
Each release's own promotion run and gate; shaded: reasoning_effort xhigh.
TG: llama-benchy task mode, ISL/OSL 2048/512, T=1.0, thinking on, from each release's own first run (usually its promotion A/B; the runs line below names each), unpaired across releases, so day-to-day drift is included; paired A/B per release in VERSIONS.md.
Shaded (b0 to v1.4.0): chat template default reasoning_effort xhigh; later releases at the recipe default, medium. Different reasoning lengths, so not like-for-like across the shade.
TG runs: b0 2026-09-23 (llama-benchy task mode, mean ± sd over runs, one boot); v1.0.0 2026-09-25, v1.1.0 2026-09-26, v1.2.0 2026-09-27, v1.3.0 2026-09-29 (llama-benchy task mode, mean of 2 candidate boots x 3 runs; sd not recorded); v1.4.0 2026-10-01 (llama-benchy task mode, mean of 2 boots x 3 runs, sd between boots); v1.5.0 2026-10-08, v2.0.0 2026-10-09 (llama-benchy task mode, mean of 2 boots x 4 runs, sd between boots).
Hardmode: promotion gate of each release (b0: gate on the pinned checkpoint), b0 2026-09-23, v1.0.0 2026-09-25, v1.1.0 2026-09-26, v1.2.0 2026-09-27, v1.3.0 2026-09-29, v1.4.0 2026-10-01, v1.5.0 2026-10-08, v2.0.0 2026-10-09, v2.1.0 2026-10-12, v2.2.0 2026-10-16.
Data: docs/data/capability.csv, with the source file of every point.
reasoning_effort medium, tool calling, max_model_len 262,144.The shipped releases were measured as a full grid in k76 on 2026-10-10
(#128); where a cell has no k76 value,
the table shows the newest release that has one.
TG rows are aggregate unless marked "per request"; sampling is the server default (temperature 1.0,
thinking on) unless the run says otherwise. The charts and tables are written by
uv run scripts/make_charts.py from docs/data/capability.csv, where every point lists
its raw file. Full grids for every build: docs/BENCHMARKS.md.
| 1x Spark | 2x Spark TP=2 | 2x Spark DP=2 | |
|---|---|---|---|
| TG c1, tok/s | 60.4 a | 88.7 b | 57.5 c |
| TG c4, tok/s: per request / aggregate | 32.4 d / 111.4 d | 48.6 b / 156.6 b | 43.4 c / 137.5 c |
| TG c8, tok/s: per request / aggregate | 22.0 d / 136.7 a | 33.2 b / 172.2 b | 32.5 c / 203.6 c |
| TG c16, tok/s: per request / aggregate | > max_num_seqs 8 | 22.1 b / 231.3 b | 21.7 c / 240.9 c |
| TG, coding c1 (36 prompts), tok/s median: T=0 / defaults | 77 e / 61 e | 119 f / 92 f | not measured |
| Copy-heavy (MTP acceptance near 1), aggregate tok/s c1 / c4 / c8, mean | 80.8 g / 183.5 g / 274.1 g | 120.5 h / 303.5 h / 453.4 h | not measured |
| PP tok/s, ISL 2K / 16K / 64K / 128K, c1 | 1,760 d / 1,866 d / 1,993 d / 1,812 d | 2,892 i / 2,986 j / 2,726 j / 2,426 j | not measured |
| TTFT s, ISL 2K / 16K / 64K / 128K, cold prefix | 1.2 d / 9.1 d / 32.9 d / 72.5 d | 0.7 i / 5.5 j / 24.0 j / 54.0 j | not measured |
| ITL p50, ms, c1 / c8 (MTP emits several tokens per step) | 19 k / 47 k | 13 l / 31 l | 19 m / 48 m |
| Stream chunk gap p50, ms, c1 / c8 | 56 n / 143 n | 41 o / 90 o | not measured |
| TG aggregate tok/s, depth 0 → 64K, c1 / c4 | 56.9 d → 55.6 d / 111.4 d → 98.4 d | 88.7 b → 82.8 b / 156.6 b → 104.3 b | 57.5 c → – / 137.5 c → – |
| max_model_len | 262,144 (recipe) | 262,144 (recipe) | 262,144 (recipe) |
| KV cache, tokens | 993,754 p | 3,525,633 q | 1,987,508 r |
| Concurrent 262K requests in KV (vLLM count) | 3.79 s | 13.46 t | 2 × 3.79 u |
| Requests that fit KV at 16K / 64K / 128K | 39.7 v / 14.1 v / 7.5 v | not measured | not measured |
| Gate: hardmode / TC-45 / fidelity to ~245K / stragglers | 91 w / 100 w / 20/20 w / none, c8-c16 w | 90 x / 100 x / 20/20 x / none, c7-c16 x | 93 y / 100 y / 20/20 y / none, c5-c16 y |
Releases and runs behind the numbers:
| Hardware | Two DGX Sparks (GB10, 128 GB unified), CX-7 ports cabled back to back |
| Launcher | sparkrun ≥ 0.3.6 with a two-node cluster defined |
| Disk | ~140 GB per node (98 GiB checkpoint + 2.8 GB MXFP8 shard of 7c4f1bc1 + ~31 GB image) |
| Kernel | 6.17.0-1032-nvidia. 7.0.0-1019-nvidia breaks NCCL ibv_reg_mr past ~85 GB GPU-resident (forum) |
| Host setting | loginctl enable-linger nvidia on both nodes (otherwise logind RemoveIPC kills the shm ring buffer) |
| Boot | ~3 min warm (170 s on the v2.0.0 image check); a cold-page-cache boot was not timed on v2.0.0 (v1.5.0: ~9.5 min) |
| Concurrency | max_num_seqs 16, KV pool 3,527,297 tokens on the v2.0.0 check boot (vLLM sizes it at each boot; v1.x boots logged 3.57M to 3.76M, and the per-rank MXFP8 copies of v2.0.0 take about 4%) |
Checkpoint: ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE
@ 16c9bd54 (v2.0.0): local-inference-lab/Qwen3.8-Flash-Next-NVFP4
@ 7c4f1bc1 with the GDN projections requantized to weight-only NVFP4 and the retrained drafter D1 (NVFP4 experts;
MXFP8 dense and attention; 98 GiB). PP-sized GDN calls (41+ rows) use the MXFP8 GDN weights of 7c4f1bc1, read
from its shard 35, which the recipe downloads on the first boot. Image and video input are not tested.
max_num_seqs 32 run (quality gate not run at that cap) reached 285.5 tok/s aggregate TG at c32
(llama-benchy task mode, mean of 3 runs, v1.4.0; high concurrency).| Recipe | Release | Image | Use |
|---|---|---|---|
qwen3.8-flash-next-2x-dgx-spark | v2.2.0 | k104s-20261016-21e0b201-d21d7ade-10d0acc8-warm (also 2x-v2.2.0) | Default |
qwen3.8-flash-next-2x-dgx-spark-previous | v2.1.0 | k94-20261013-21e0b201-d21d7ade-warm (also 2x-v2.1.0) | Rollback, same checkpoint without GDN prefill views |
qwen3.8-flash-next-1x-dgx-spark, -previous | one-Spark v2.3.0 / v2.2.0 | Compatibility copies; the one-Spark recipes live in qwen3.8-flash-next-1x-dgx-spark |
Rolling back to -previous (v2.1.0) needs no download: it serves the same checkpoint (ursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057 plus shard 35 of 7c4f1bc1) on the v2.1.0 image. v2.2.0 keeps its torch compile cache in a root of its own (VLLM_CACHE_ROOT), so switching does not recompile: in the v2.2.0 check the v2.2.0 boot loaded its cache from its own root, and the v2.1.0 boot after it left the shared root unchanged. The v2.0.0 recipe is archived in archive/recipes/. Renames: recipes/RENAMES.md.
Releases use semantic versions per repo; VERSIONS.md lists every shipped release with its old build name, date, what changed and its manifest (image digest, vLLM and b12x commits, checkpoint, drafter, plan seed). Each release's own A/B against the one before it is in the release history and in docs/BENCHMARKS.md. The single-Spark releases have their own list in the one-Spark repo.
scripts/arm_verdict.py).Index of every run and verdict: results/README.md.
Flags, environment variables and the reason for each: docs/REFERENCE.md. Kernel and engine notes, rejected experiments: docs/ENGINEERING.md.
The recipes set the server default to reasoning_effort: medium. On my DevOps task set, two-Spark v1.2.0 (old name b1.2) at the template
default xhigh passed 42.5% of checks with 23/42 runaway-thinking runs and a 246 s median per task; v1.4.0 (old name b1.4) at medium
passed 95.9% with 0/42 runaway and 33 s. A request can still ask for xhigh or low; "reasoning_effort": "none"
turns thinking off. For hard reasoning problems xhigh per request helps: on the hotel-lights check, the one-Spark v3c (old name)
went from 21/32 at medium to 31/31 completed runs
(#88); allow a long client timeout,
since most of those answers took over 30 minutes at 8 concurrent requests. Override table:
docs/REFERENCE.md.
| Symptom | Fix |
|---|---|
/health silent for minutes | Expected on a cold boot. sparkrun logs <recipe> -f |
| OOM / earlyoom at start | Unified memory: gpu_memory_utilization ≥ 0.84 starves host RAM. Keep 0.80 and clear other containers |
NCCL ibv_reg_mr_iova2 ... Cannot allocate memory | Kernel 7.0.0-1019-nvidia; hold 6.17.0-1032-nvidia |
ShmRingBuffer ... shared_memory crash | loginctl enable-linger nvidia on both nodes |
| Garbled output | Rank checkpoint mismatch: both serve logs must show snapshots/16c9bd54 (7c4f1bc1 on -previous) (why) |
Empty content, long reasoning | max_tokens ran out during thinking; raise it or send "reasoning_effort": "low" |
| Old release boots after an upgrade | sparkrun registry update qwen38-flashnext |
| Anything else | Try the -previous recipe, then open an issue with sparkrun logs <recipe> -a |
| VERSIONS.md | Release names, old build names, manifests |
| docs/BENCHMARKS.md | Full benchmark and quality tables for every build, method, history |
| docs/REFERENCE.md | Configuration, image provenance, thinking effort, tuning |
| docs/ENGINEERING.md | Known issues and fixes, profiling, rejected experiments |
| tools/dp2/ | DP=2 router: how to run it, flags, limits |
| results/ | Every raw measurement and verdict |
| archive/ | Experimental and superseded recipes (not listed by sparkrun) |
This build stands on these projects:
Earlier experiments drew on other projects too; docs/ENGINEERING.md keeps that full history with exact pins.
Apache-2.0, see LICENSE. The vLLM overlays under archive/mods/ keep their upstream Apache-2.0 headers.
Model weights are not part of this repo and keep their own license (Qwen Community License 1.0; see each checkpoint's
model card).