ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2

Python

10

182 commits

updated Oct 16, 2026

See the code

See what people are saying

SourceMessageScoreDate

Qwen3.8-Flash-Next on DGX Spark update: faster prefill, new 1x and 2x releases (r/LocalLLM)

Follow-up to last week's post https://www.reddit.com/r/LocalLLM/comments/1x3d0wj/qwen38flashnext\_coding\_on\_dgx\_spark\_1\_tp1\_at\_75/. Prefill was the weak side, so this week I worked on that. New releases today: 1x v2.4.0 and 2x v2.2.0. What changed: * GDN prefill reads its inputs as views…

1

Oct 16, 2026

README

Qwen3.8 Flash Next on two DGX Sparks: Qwen emblem, gold NVIDIA hardware and violet token trails.

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/s

TG c1
defaults (ISL/OSL 2048/512), v2.0.0
/ T=0, thinking off (coding), v2.0.0

195 / 317 tok/s

TG aggregate c8
defaults (ISL/OSL 2048/512), v2.0.0
/ T=0, thinking off (coding), v2.0.0

90/100

Hardmode
pass ≥ 88, T=0, thinking on

5.5 s

TTFT, ISL 16K
c1, 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.

Measurement details
MetricMeansd between bootsBootsWorkloadSamplingRelease (date)Source
TG c1, defaults86.3 tok/s2.373llama-benchy task mode, ISL/OSL 2048/512T=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG c1, coding112.8 tok/s0.02336 prompts, OSL ≤768, aggregate output tok/s per passT=0, thinking offv2.0.0 (2026-10-10)results
TG c1, coding, per-prompt median118.7 tok/s–136 prompts (Python, C++, Rust, Go), OSL ≤768, median over promptsT=0, thinking offv2.0.0 (2026-10-10)results
TG c1, coding, defaults, per-prompt median92.0 tok/s–136 prompts, OSL ≤768; 100/108 requests hit the 768-token cap inside thinkingserver defaults, thinking onv2.0.0 (2026-10-10)results
TG aggregate c8, defaults195.3 tok/s5.123llama-benchy task mode, ISL/OSL 2048/512T=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG aggregate c8, coding317.4 tok/s1.34336 prompts, OSL ≤768, aggregate output tok/s per passT=0, thinking offv2.0.0 (2026-10-10)results
TTFT, ISL 16K5.49 s0.044c1, cold prefix–v2.2.0 (2026-10-15)results
Hardmode90/100–188 multi-step tool-use scenarios; pass ≥ 88T=0, thinking onv2.2.0 (2026-10-16)results
GPU power, idle19.5 W–3server up, no requests, 120 s–v2.0.0 (2026-10-10)results
GPU power, TG c1 defaults49.8 W–3power.draw, GPU only (nvidia-smi), not wall power, mean over the cellT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG per GPU watt, c1 defaults1.73 tok/s/W–3TG aggregate / GPU WT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
GPU power, TG c8 defaults61.4 W–3power.draw, GPU only (nvidia-smi), not wall power, mean over the cellT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG per GPU watt, c8 defaults3.18 tok/s/W–3TG aggregate / GPU WT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results

Method and full tables: docs/BENCHMARKS.md.

release: v2.2.0 hardware: 2× DGX Spark quality gate: passed license: Apache-2.0

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.

Quick start

Requirements:

  • 2x DGX Spark with the ConnectX-7 ports cabled back to back, sparkrun ≥ 0.3.6 with a two-node cluster defined.
  • Kernel 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).
  • ~140 GB free per node: 98 GiB checkpoint + 2.8 GB MXFP8 shard + 30.9 GB image.
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.

Check that it works, stop, upgrade, secure

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-spark and -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.

At a glance

Modelursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057: NVFP4 experts, MXFP8 dense and attention, GDN projections NVFP4 for TG and MXFP8 for PP
MTPMTP ×4, probabilistic drafts, rejection sampling; drafter D3 refit on the served model's outputs (since v2.1.0)
Hardware2x DGX Spark (GB10, 128 GB unified each), CX-7 back to back
ParallelismTP=2 over RoCE, one rank per GB10
EnginevLLM + b12x (NVFP4 MoE, MXFP8 linears, GDN, QSA), GDN prefill without token gathers (v2.2.0), plan and compile caches baked into the image
Context262,144 tokens (max_model_len)
Concurrent requestsup to 16 (max_num_seqs)
KV cache3,527,297 tokens, 13.46 concurrent 262K requests (v2.0.0 boot log)
APIOpenAI-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)
LicenseRecipe Apache-2.0; weights Qwen Community License 1.0
TaskSection
TG, PP, TTFT, context depthPerformance
Sizing: 1x vs TP=2 vs DP=21x vs 2x TP=2 vs 2x DP=2
Quality gate resultsQuality gate
Raw data and provenance per numberEvery number and where it comes from
Pin or roll back a releaseRecipes, VERSIONS.md

Performance

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.

TG aggregate tok/s vs concurrency c1-c16 per setup, ISL/OSL 2048/512, server defaults. End values labelled. TTFT in seconds vs ISL, c1, cold prefix, per setup. End values labelled.

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.

Runs, method and raw data

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.

PP tok/s vs ISL, c1, cold prefix, per setup. End values labelled. TG aggregate tok/s at context depth 0, 16K and 64K, c1/c4/c8. End values labelled.

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.

Runs, method and raw data

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.

Per-request TG and the numbers behind the charts

TG per-request tok/s vs concurrency c1-c16 per setup, ISL/OSL 2048/512, server defaults. End values labelled.

1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode.

Runs, method and raw data

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.

ConcurrencyTG tok/s, aggregateTG tok/s, per requestRelease, run
186.3 ± 2.488.7 ± 2.6v2.0.0, 2026-10-10
2114.9 ± 7.962.4 ± 5.3v2.0.0, 2026-10-10
4156.6 ± 4.648.6 ± 1.5v2.0.0, 2026-10-10
8195.3 ± 5.133.2 ± 1.0v2.0.0, 2026-10-10
16231.3 ± 4.922.1 ± 0.4v2.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.

c1, cold prefix.

1x vs 2x TP=2 vs 2x DP=2

1x vs 2x TP=2 vs 2x DP=2: TG coding c1, TG c1, TG aggregate c8, TTFT at ISL 16K, concurrent 262K requests in KV. Values labelled on the bars.

Shipped release of each setup where measured; the bracket names an older release.

Runs, method and raw data

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.

  • 1x: 1x recipe, max_num_seqs 8.
  • 2x TP=2 (this recipe): higher per-request TG and a larger KV cache; fits low concurrency and long contexts.
  • 2x DP=2: two 1x replicas behind the router. Lower wall time than TP=2 on all six agent-replay workloads:

Agent replay wall time in seconds, TP=2 vs DP=2, six workloads. Values labelled on the bars.

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).

Runs, method and raw data

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.

Quality gate

CheckResultCriterion
Hardmode90/100score /100, pass ≥ 88 (88 scenarios), T=0, thinking on
TC-45100/100regression test (1 scenario, 2 pts, 5 trials)
Fidelity20/2020 needles retrieved via tool calls, ISL 8K to ~245K
Stragglersnoneno 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.

Release history

Release history: TG aggregate c8 and TG c1 tok/s, and hardmode score, per shipped release. Values labelled.

Each release's own promotion run and gate; shaded: reasoning_effort xhigh.

Runs, method and raw data

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.

Stack

  • Weights: NVFP4 experts, MXFP8 dense and attention. GDN projections in weight-only NVFP4 for calls under 41 rows (TG up to c8), MXFP8 copy for 41+ rows (PP; TG at c9-c16).
  • MTP: 4 probabilistic drafts per step over a 131k-id draft vocabulary, rejection sampling (output distribution unchanged); drafter D1 refit on the served model's outputs.
  • Parallelism: TP=2 over RoCE (CX-7), one rank per GB10; all-reduce ~4% of a c1 TG step.
  • Kernels: b12x for GB10 (NVFP4 MoE, MXFP8 linears, 36 GDN layers, 12 QSA sparse-attention layers), autotuned plan cache baked into the image.
  • Serving defaults: reasoning on at reasoning_effort medium, tool calling, max_model_len 262,144.

Details

Every number and where it comes from: Capability table for one Spark, TP=2 and DP=2, with the run behind each number

Every number and where it comes from

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 Spark2x Spark TP=22x Spark DP=2
TG c1, tok/s60.4 a88.7 b57.5 c
TG c4, tok/s: per request / aggregate32.4 d / 111.4 d48.6 b / 156.6 b43.4 c / 137.5 c
TG c8, tok/s: per request / aggregate22.0 d / 136.7 a33.2 b / 172.2 b32.5 c / 203.6 c
TG c16, tok/s: per request / aggregate> max_num_seqs 822.1 b / 231.3 b21.7 c / 240.9 c
TG, coding c1 (36 prompts), tok/s median: T=0 / defaults77 e / 61 e119 f / 92 fnot measured
Copy-heavy (MTP acceptance near 1), aggregate tok/s c1 / c4 / c8, mean80.8 g / 183.5 g / 274.1 g120.5 h / 303.5 h / 453.4 hnot measured
PP tok/s, ISL 2K / 16K / 64K / 128K, c11,760 d / 1,866 d / 1,993 d / 1,812 d2,892 i / 2,986 j / 2,726 j / 2,426 jnot measured
TTFT s, ISL 2K / 16K / 64K / 128K, cold prefix1.2 d / 9.1 d / 32.9 d / 72.5 d0.7 i / 5.5 j / 24.0 j / 54.0 jnot measured
ITL p50, ms, c1 / c8 (MTP emits several tokens per step)19 k / 47 k13 l / 31 l19 m / 48 m
Stream chunk gap p50, ms, c1 / c856 n / 143 n41 o / 90 onot measured
TG aggregate tok/s, depth 0 → 64K, c1 / c456.9 d → 55.6 d / 111.4 d → 98.4 d88.7 b → 82.8 b / 156.6 b → 104.3 b57.5 c → – / 137.5 c → –
max_model_len262,144 (recipe)262,144 (recipe)262,144 (recipe)
KV cache, tokens993,754 p3,525,633 q1,987,508 r
Concurrent 262K requests in KV (vLLM count)3.79 s13.46 t2 × 3.79 u
Requests that fit KV at 16K / 64K / 128K39.7 v / 14.1 v / 7.5 vnot measurednot measured
Gate: hardmode / TC-45 / fidelity to ~245K / stragglers91 w / 100 w / 20/20 w / none, c8-c16 w90 x / 100 x / 20/20 x / none, c7-c16 x93 y / 100 y / 20/20 y / none, c5-c16 y

Releases and runs behind the numbers:

  • a one-Spark v2.2.0, 2026-10-10, k86-boot-noise-power, llama-benchy task mode (files)
  • b two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • c DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • d one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • e one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, coding_probe.py, up to 768 tokens out (files)
  • f two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, coding_probe.py, up to 768 tokens out (files)
  • g one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, copy-heavy benchmark, 1,500 tokens out (files)
  • h two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, copy-heavy benchmark, 1,500 tokens out (files)
  • i two-Spark v2.2.0, 2026-10-15, promotion A/B, llama-benchy task mode (files)
  • j two-Spark v2.2.0, 2026-10-15, promotion A/B, cold.py (k101) (files)
  • k one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • l two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • m DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • n one-Spark v2.0.0 (old name v3d), 2026-10-05, depth and PP sweep, llm-inference-bench 0.7.6 (files)
  • o two-Spark v1.4.0 (old name b1.4), 2026-10-05, depth and PP sweep, llm-inference-bench 0.7.6 (files)
  • p one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • q two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • r DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • s one-Spark v2.1.0 (old name v3e), 2026-10-08, serve log (files)
  • t two-Spark v2.0.0, 2026-10-09, serve log (files)
  • u DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, serve log (files)
  • v one-Spark v1.3.0 (old name v3c), 2026-10-05, kv-capacity page accounting, same 14 GiB pool in 1× v2.0.0 and v2.1.0 (files)
  • w one-Spark v2.3.0, 2026-10-13, promotion gate (1x repo)
  • x two-Spark v2.2.0, 2026-10-16, promotion gate (files)
  • y DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, quality gate through the router (files)
Requirements: Hardware, disk, kernel, host settings, boot time, checkpoint
HardwareTwo DGX Sparks (GB10, 128 GB unified), CX-7 ports cabled back to back
Launchersparkrun ≥ 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)
Kernel6.17.0-1032-nvidia. 7.0.0-1019-nvidia breaks NCCL ibv_reg_mr past ~85 GB GPU-resident (forum)
Host settingloginctl 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)
Concurrencymax_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.

Known limits: What is not measured or does not work yet
  • By vLLM's count the KV pool fits about 13.5 requests at 262,144 tokens, under the 16-request cap. Four different ~256K contexts at once used 28% of the pool; more than four at once have not been run (long contexts at once).
  • v2.0.0 reads the MXFP8 PP weights from shard 35 of the 7c4f1bc1 snapshot in the HF cache by its full path. Deleting the HF cache means downloading that shard (2.8 GB) again on the next boot, which the recipe does by itself.
  • The v2.0.0 A/B measured c1, c4 and c8 against v1.5.0; c16 was not part of the A/B. k76 measured the shipped v2.0.0 at c16: 231.3 ± 4.9 tok/s aggregate (v1.4.0 A/B: 242.8). At c9-c16 the GDN layers run the same MXFP8 weights as v1.5.0.
  • c1 TG varies between boots (earlier builds showed two levels, ~95 to 100 and ~85 to 88 tok/s on counting).
  • Hardmode still fails a few multi-step scenarios (e.g. TC-30, TC-68, TC-74, TC-88) on every release.
  • Above 16 requests: a 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).
Recipes: The recipes in this repo and how to roll back
RecipeReleaseImageUse
qwen3.8-flash-next-2x-dgx-sparkv2.2.0k104s-20261016-21e0b201-d21d7ade-10d0acc8-warm (also 2x-v2.2.0)Default
qwen3.8-flash-next-2x-dgx-spark-previousv2.1.0k94-20261013-21e0b201-d21d7ade-warm (also 2x-v2.1.0)Rollback, same checkpoint without GDN prefill views
qwen3.8-flash-next-1x-dgx-spark, -previousone-Spark v2.3.0 / v2.2.0Compatibility 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: Version names and what each release changed

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.

How I measure: How builds are screened, compared and promoted
  • Candidate builds are screened with Thunderdome: control and candidate booted alternately (control, candidate, candidate, control), T=0 TG probes at c1/c4/c8 and at 16K context, plus llama-benchy at temperature 1.0. Noise band = the control's boot-to-boot spread, at least 1%.
  • To be promoted, a build must pass the gate, be faster beyond noise in at least one cell, and be slower beyond noise in no cell at 1 to 4 requests; a loss at 5 to 16 requests is published as a caveat (scripts/arm_verdict.py).
  • MTP acceptance per draft position is compared cell by cell as a numerics canary.
  • Copy-heavy and counting workloads accept nearly every draft and show the TG ceiling of MTP; the coding grid (llama-benchy task mode, thinking on) is the rate an agent sees. Every table states its workload.

Index of every run and verdict: results/README.md.

How it works: Parallelism, kernels, MTP, where the time goes
  • TP=2 over RoCE, one rank per GB10; all-reduces over the CX-7 link take ~4% of a c1 TG step.
  • b12x kernels cover NVFP4 MoE, MXFP8 linears, GDN (36 layers) and QSA sparse attention (12 layers), with an autotuned plan cache baked into each image.
  • MTP ×4 uses probabilistic drafts over a 131k-id draft vocabulary; rejection sampling keeps the output distribution unchanged.
  • The 1-request step (~43 ms) splits into MoE 30%, dense MXFP8 31%, MTP draft + head 19%, idle 6%, all-reduce 4%, GDN 3%. MoE reads ~175 GB/s of the ~250 GB/s the GB10 reaches.

Flags, environment variables and the reason for each: docs/REFERENCE.md. Kernel and engine notes, rejected experiments: docs/ENGINEERING.md.

Thinking effort: The default reasoning effort and how to change it per request

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.

Troubleshooting: Common errors and fixes
SymptomFix
/health silent for minutesExpected on a cold boot. sparkrun logs <recipe> -f
OOM / earlyoom at startUnified memory: gpu_memory_utilization ≥ 0.84 starves host RAM. Keep 0.80 and clear other containers
NCCL ibv_reg_mr_iova2 ... Cannot allocate memoryKernel 7.0.0-1019-nvidia; hold 6.17.0-1032-nvidia
ShmRingBuffer ... shared_memory crashloginctl enable-linger nvidia on both nodes
Garbled outputRank checkpoint mismatch: both serve logs must show snapshots/16c9bd54 (7c4f1bc1 on -previous) (why)
Empty content, long reasoningmax_tokens ran out during thinking; raise it or send "reasoning_effort": "low"
Old release boots after an upgradesparkrun registry update qwen38-flashnext
Anything elseTry the -previous recipe, then open an issue with sparkrun logs <recipe> -a
Docs: Where the rest of the documentation is
VERSIONS.mdRelease names, old build names, manifests
docs/BENCHMARKS.mdFull benchmark and quality tables for every build, method, history
docs/REFERENCE.mdConfiguration, image provenance, thinking effort, tuning
docs/ENGINEERING.mdKnown 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)

Credits

This build stands on these projects:

  • local-inference-lab: the vLLM fork my branches start from, the b12x kernels (NVFP4 MoE, GDN, QSA) and the NVFP4 checkpoint (the one-Spark checkpoint is derived from it).
  • Qwen: the base model, under the Qwen Community License 1.0.
  • eugr: spark-vllm-docker (my image base), sparkrun (the launcher), and llama-benchy (the base of my benchmark fork).
  • tonyd2wild: the bench_sweep counting harness behind the counting numbers.
  • SeraphimSerapis: tool-eval-bench, which runs the hardmode and TC-45 quality gate.

Earlier experiments drew on other projects too; docs/ENGINEERING.md keeps that full history with exact pins.

License

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).

ursuciprian/qwen3.8-flash-next-dgx-spark-tp-2

Python

10

182 commits

updated Oct 16, 2026

See the code

See what people are saying

SourceMessageScoreDate

Qwen3.8-Flash-Next on DGX Spark update: faster prefill, new 1x and 2x releases (r/LocalLLM)

Follow-up to last week's post https://www.reddit.com/r/LocalLLM/comments/1x3d0wj/qwen38flashnext\_coding\_on\_dgx\_spark\_1\_tp1\_at\_75/. Prefill was the weak side, so this week I worked on that. New releases today: 1x v2.4.0 and 2x v2.2.0. What changed: * GDN prefill reads its inputs as views…

1

Oct 16, 2026

README

Qwen3.8 Flash Next on two DGX Sparks: Qwen emblem, gold NVIDIA hardware and violet token trails.

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/s

TG c1
defaults (ISL/OSL 2048/512), v2.0.0
/ T=0, thinking off (coding), v2.0.0

195 / 317 tok/s

TG aggregate c8
defaults (ISL/OSL 2048/512), v2.0.0
/ T=0, thinking off (coding), v2.0.0

90/100

Hardmode
pass ≥ 88, T=0, thinking on

5.5 s

TTFT, ISL 16K
c1, 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.

Measurement details
MetricMeansd between bootsBootsWorkloadSamplingRelease (date)Source
TG c1, defaults86.3 tok/s2.373llama-benchy task mode, ISL/OSL 2048/512T=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG c1, coding112.8 tok/s0.02336 prompts, OSL ≤768, aggregate output tok/s per passT=0, thinking offv2.0.0 (2026-10-10)results
TG c1, coding, per-prompt median118.7 tok/s–136 prompts (Python, C++, Rust, Go), OSL ≤768, median over promptsT=0, thinking offv2.0.0 (2026-10-10)results
TG c1, coding, defaults, per-prompt median92.0 tok/s–136 prompts, OSL ≤768; 100/108 requests hit the 768-token cap inside thinkingserver defaults, thinking onv2.0.0 (2026-10-10)results
TG aggregate c8, defaults195.3 tok/s5.123llama-benchy task mode, ISL/OSL 2048/512T=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG aggregate c8, coding317.4 tok/s1.34336 prompts, OSL ≤768, aggregate output tok/s per passT=0, thinking offv2.0.0 (2026-10-10)results
TTFT, ISL 16K5.49 s0.044c1, cold prefix–v2.2.0 (2026-10-15)results
Hardmode90/100–188 multi-step tool-use scenarios; pass ≥ 88T=0, thinking onv2.2.0 (2026-10-16)results
GPU power, idle19.5 W–3server up, no requests, 120 s–v2.0.0 (2026-10-10)results
GPU power, TG c1 defaults49.8 W–3power.draw, GPU only (nvidia-smi), not wall power, mean over the cellT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG per GPU watt, c1 defaults1.73 tok/s/W–3TG aggregate / GPU WT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
GPU power, TG c8 defaults61.4 W–3power.draw, GPU only (nvidia-smi), not wall power, mean over the cellT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results
TG per GPU watt, c8 defaults3.18 tok/s/W–3TG aggregate / GPU WT=1.0, top-p 0.95, top-k 20, thinking onv2.0.0 (2026-10-10)results

Method and full tables: docs/BENCHMARKS.md.

release: v2.2.0 hardware: 2× DGX Spark quality gate: passed license: Apache-2.0

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.

Quick start

Requirements:

  • 2x DGX Spark with the ConnectX-7 ports cabled back to back, sparkrun ≥ 0.3.6 with a two-node cluster defined.
  • Kernel 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).
  • ~140 GB free per node: 98 GiB checkpoint + 2.8 GB MXFP8 shard + 30.9 GB image.
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.

Check that it works, stop, upgrade, secure

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-spark and -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.

At a glance

Modelursuciprian/Qwen3.8-Flash-Next-NVFP4-GDN-MSE @ 03f4a057: NVFP4 experts, MXFP8 dense and attention, GDN projections NVFP4 for TG and MXFP8 for PP
MTPMTP ×4, probabilistic drafts, rejection sampling; drafter D3 refit on the served model's outputs (since v2.1.0)
Hardware2x DGX Spark (GB10, 128 GB unified each), CX-7 back to back
ParallelismTP=2 over RoCE, one rank per GB10
EnginevLLM + b12x (NVFP4 MoE, MXFP8 linears, GDN, QSA), GDN prefill without token gathers (v2.2.0), plan and compile caches baked into the image
Context262,144 tokens (max_model_len)
Concurrent requestsup to 16 (max_num_seqs)
KV cache3,527,297 tokens, 13.46 concurrent 262K requests (v2.0.0 boot log)
APIOpenAI-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)
LicenseRecipe Apache-2.0; weights Qwen Community License 1.0
TaskSection
TG, PP, TTFT, context depthPerformance
Sizing: 1x vs TP=2 vs DP=21x vs 2x TP=2 vs 2x DP=2
Quality gate resultsQuality gate
Raw data and provenance per numberEvery number and where it comes from
Pin or roll back a releaseRecipes, VERSIONS.md

Performance

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.

TG aggregate tok/s vs concurrency c1-c16 per setup, ISL/OSL 2048/512, server defaults. End values labelled. TTFT in seconds vs ISL, c1, cold prefix, per setup. End values labelled.

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.

Runs, method and raw data

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.

PP tok/s vs ISL, c1, cold prefix, per setup. End values labelled. TG aggregate tok/s at context depth 0, 16K and 64K, c1/c4/c8. End values labelled.

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.

Runs, method and raw data

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.

Per-request TG and the numbers behind the charts

TG per-request tok/s vs concurrency c1-c16 per setup, ISL/OSL 2048/512, server defaults. End values labelled.

1x Spark v2.1.0, 2x Spark TP=2 v2.0.0, 2x Spark DP=2 v2.1.0; llama-benchy task mode.

Runs, method and raw data

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.

ConcurrencyTG tok/s, aggregateTG tok/s, per requestRelease, run
186.3 ± 2.488.7 ± 2.6v2.0.0, 2026-10-10
2114.9 ± 7.962.4 ± 5.3v2.0.0, 2026-10-10
4156.6 ± 4.648.6 ± 1.5v2.0.0, 2026-10-10
8195.3 ± 5.133.2 ± 1.0v2.0.0, 2026-10-10
16231.3 ± 4.922.1 ± 0.4v2.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.

c1, cold prefix.

1x vs 2x TP=2 vs 2x DP=2

1x vs 2x TP=2 vs 2x DP=2: TG coding c1, TG c1, TG aggregate c8, TTFT at ISL 16K, concurrent 262K requests in KV. Values labelled on the bars.

Shipped release of each setup where measured; the bracket names an older release.

Runs, method and raw data

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.

  • 1x: 1x recipe, max_num_seqs 8.
  • 2x TP=2 (this recipe): higher per-request TG and a larger KV cache; fits low concurrency and long contexts.
  • 2x DP=2: two 1x replicas behind the router. Lower wall time than TP=2 on all six agent-replay workloads:

Agent replay wall time in seconds, TP=2 vs DP=2, six workloads. Values labelled on the bars.

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).

Runs, method and raw data

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.

Quality gate

CheckResultCriterion
Hardmode90/100score /100, pass ≥ 88 (88 scenarios), T=0, thinking on
TC-45100/100regression test (1 scenario, 2 pts, 5 trials)
Fidelity20/2020 needles retrieved via tool calls, ISL 8K to ~245K
Stragglersnoneno 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.

Release history

Release history: TG aggregate c8 and TG c1 tok/s, and hardmode score, per shipped release. Values labelled.

Each release's own promotion run and gate; shaded: reasoning_effort xhigh.

Runs, method and raw data

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.

Stack

  • Weights: NVFP4 experts, MXFP8 dense and attention. GDN projections in weight-only NVFP4 for calls under 41 rows (TG up to c8), MXFP8 copy for 41+ rows (PP; TG at c9-c16).
  • MTP: 4 probabilistic drafts per step over a 131k-id draft vocabulary, rejection sampling (output distribution unchanged); drafter D1 refit on the served model's outputs.
  • Parallelism: TP=2 over RoCE (CX-7), one rank per GB10; all-reduce ~4% of a c1 TG step.
  • Kernels: b12x for GB10 (NVFP4 MoE, MXFP8 linears, 36 GDN layers, 12 QSA sparse-attention layers), autotuned plan cache baked into the image.
  • Serving defaults: reasoning on at reasoning_effort medium, tool calling, max_model_len 262,144.

Details

Every number and where it comes from: Capability table for one Spark, TP=2 and DP=2, with the run behind each number

Every number and where it comes from

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 Spark2x Spark TP=22x Spark DP=2
TG c1, tok/s60.4 a88.7 b57.5 c
TG c4, tok/s: per request / aggregate32.4 d / 111.4 d48.6 b / 156.6 b43.4 c / 137.5 c
TG c8, tok/s: per request / aggregate22.0 d / 136.7 a33.2 b / 172.2 b32.5 c / 203.6 c
TG c16, tok/s: per request / aggregate> max_num_seqs 822.1 b / 231.3 b21.7 c / 240.9 c
TG, coding c1 (36 prompts), tok/s median: T=0 / defaults77 e / 61 e119 f / 92 fnot measured
Copy-heavy (MTP acceptance near 1), aggregate tok/s c1 / c4 / c8, mean80.8 g / 183.5 g / 274.1 g120.5 h / 303.5 h / 453.4 hnot measured
PP tok/s, ISL 2K / 16K / 64K / 128K, c11,760 d / 1,866 d / 1,993 d / 1,812 d2,892 i / 2,986 j / 2,726 j / 2,426 jnot measured
TTFT s, ISL 2K / 16K / 64K / 128K, cold prefix1.2 d / 9.1 d / 32.9 d / 72.5 d0.7 i / 5.5 j / 24.0 j / 54.0 jnot measured
ITL p50, ms, c1 / c8 (MTP emits several tokens per step)19 k / 47 k13 l / 31 l19 m / 48 m
Stream chunk gap p50, ms, c1 / c856 n / 143 n41 o / 90 onot measured
TG aggregate tok/s, depth 0 → 64K, c1 / c456.9 d → 55.6 d / 111.4 d → 98.4 d88.7 b → 82.8 b / 156.6 b → 104.3 b57.5 c → – / 137.5 c → –
max_model_len262,144 (recipe)262,144 (recipe)262,144 (recipe)
KV cache, tokens993,754 p3,525,633 q1,987,508 r
Concurrent 262K requests in KV (vLLM count)3.79 s13.46 t2 × 3.79 u
Requests that fit KV at 16K / 64K / 128K39.7 v / 14.1 v / 7.5 vnot measurednot measured
Gate: hardmode / TC-45 / fidelity to ~245K / stragglers91 w / 100 w / 20/20 w / none, c8-c16 w90 x / 100 x / 20/20 x / none, c7-c16 x93 y / 100 y / 20/20 y / none, c5-c16 y

Releases and runs behind the numbers:

  • a one-Spark v2.2.0, 2026-10-10, k86-boot-noise-power, llama-benchy task mode (files)
  • b two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • c DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • d one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llama-benchy 0.4.1.dev4+g0d4de4271 (files)
  • e one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, coding_probe.py, up to 768 tokens out (files)
  • f two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, coding_probe.py, up to 768 tokens out (files)
  • g one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, copy-heavy benchmark, 1,500 tokens out (files)
  • h two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, copy-heavy benchmark, 1,500 tokens out (files)
  • i two-Spark v2.2.0, 2026-10-15, promotion A/B, llama-benchy task mode (files)
  • j two-Spark v2.2.0, 2026-10-15, promotion A/B, cold.py (k101) (files)
  • k one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • l two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • m DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, llm-inference-bench 0.7.6 (files)
  • n one-Spark v2.0.0 (old name v3d), 2026-10-05, depth and PP sweep, llm-inference-bench 0.7.6 (files)
  • o two-Spark v1.4.0 (old name b1.4), 2026-10-05, depth and PP sweep, llm-inference-bench 0.7.6 (files)
  • p one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • q two-Spark v2.0.0, 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • r DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-10, k76-capability-matrix, vLLM serve log (files)
  • s one-Spark v2.1.0 (old name v3e), 2026-10-08, serve log (files)
  • t two-Spark v2.0.0, 2026-10-09, serve log (files)
  • u DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, serve log (files)
  • v one-Spark v1.3.0 (old name v3c), 2026-10-05, kv-capacity page accounting, same 14 GiB pool in 1× v2.0.0 and v2.1.0 (files)
  • w one-Spark v2.3.0, 2026-10-13, promotion gate (1x repo)
  • x two-Spark v2.2.0, 2026-10-16, promotion gate (files)
  • y DP=2 on one-Spark v2.1.0 (old name v3e), 2026-10-08, quality gate through the router (files)
Requirements: Hardware, disk, kernel, host settings, boot time, checkpoint
HardwareTwo DGX Sparks (GB10, 128 GB unified), CX-7 ports cabled back to back
Launchersparkrun ≥ 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)
Kernel6.17.0-1032-nvidia. 7.0.0-1019-nvidia breaks NCCL ibv_reg_mr past ~85 GB GPU-resident (forum)
Host settingloginctl 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)
Concurrencymax_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.

Known limits: What is not measured or does not work yet
  • By vLLM's count the KV pool fits about 13.5 requests at 262,144 tokens, under the 16-request cap. Four different ~256K contexts at once used 28% of the pool; more than four at once have not been run (long contexts at once).
  • v2.0.0 reads the MXFP8 PP weights from shard 35 of the 7c4f1bc1 snapshot in the HF cache by its full path. Deleting the HF cache means downloading that shard (2.8 GB) again on the next boot, which the recipe does by itself.
  • The v2.0.0 A/B measured c1, c4 and c8 against v1.5.0; c16 was not part of the A/B. k76 measured the shipped v2.0.0 at c16: 231.3 ± 4.9 tok/s aggregate (v1.4.0 A/B: 242.8). At c9-c16 the GDN layers run the same MXFP8 weights as v1.5.0.
  • c1 TG varies between boots (earlier builds showed two levels, ~95 to 100 and ~85 to 88 tok/s on counting).
  • Hardmode still fails a few multi-step scenarios (e.g. TC-30, TC-68, TC-74, TC-88) on every release.
  • Above 16 requests: a 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).
Recipes: The recipes in this repo and how to roll back
RecipeReleaseImageUse
qwen3.8-flash-next-2x-dgx-sparkv2.2.0k104s-20261016-21e0b201-d21d7ade-10d0acc8-warm (also 2x-v2.2.0)Default
qwen3.8-flash-next-2x-dgx-spark-previousv2.1.0k94-20261013-21e0b201-d21d7ade-warm (also 2x-v2.1.0)Rollback, same checkpoint without GDN prefill views
qwen3.8-flash-next-1x-dgx-spark, -previousone-Spark v2.3.0 / v2.2.0Compatibility 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: Version names and what each release changed

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.

How I measure: How builds are screened, compared and promoted
  • Candidate builds are screened with Thunderdome: control and candidate booted alternately (control, candidate, candidate, control), T=0 TG probes at c1/c4/c8 and at 16K context, plus llama-benchy at temperature 1.0. Noise band = the control's boot-to-boot spread, at least 1%.
  • To be promoted, a build must pass the gate, be faster beyond noise in at least one cell, and be slower beyond noise in no cell at 1 to 4 requests; a loss at 5 to 16 requests is published as a caveat (scripts/arm_verdict.py).
  • MTP acceptance per draft position is compared cell by cell as a numerics canary.
  • Copy-heavy and counting workloads accept nearly every draft and show the TG ceiling of MTP; the coding grid (llama-benchy task mode, thinking on) is the rate an agent sees. Every table states its workload.

Index of every run and verdict: results/README.md.

How it works: Parallelism, kernels, MTP, where the time goes
  • TP=2 over RoCE, one rank per GB10; all-reduces over the CX-7 link take ~4% of a c1 TG step.
  • b12x kernels cover NVFP4 MoE, MXFP8 linears, GDN (36 layers) and QSA sparse attention (12 layers), with an autotuned plan cache baked into each image.
  • MTP ×4 uses probabilistic drafts over a 131k-id draft vocabulary; rejection sampling keeps the output distribution unchanged.
  • The 1-request step (~43 ms) splits into MoE 30%, dense MXFP8 31%, MTP draft + head 19%, idle 6%, all-reduce 4%, GDN 3%. MoE reads ~175 GB/s of the ~250 GB/s the GB10 reaches.

Flags, environment variables and the reason for each: docs/REFERENCE.md. Kernel and engine notes, rejected experiments: docs/ENGINEERING.md.

Thinking effort: The default reasoning effort and how to change it per request

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.

Troubleshooting: Common errors and fixes
SymptomFix
/health silent for minutesExpected on a cold boot. sparkrun logs <recipe> -f
OOM / earlyoom at startUnified memory: gpu_memory_utilization ≥ 0.84 starves host RAM. Keep 0.80 and clear other containers
NCCL ibv_reg_mr_iova2 ... Cannot allocate memoryKernel 7.0.0-1019-nvidia; hold 6.17.0-1032-nvidia
ShmRingBuffer ... shared_memory crashloginctl enable-linger nvidia on both nodes
Garbled outputRank checkpoint mismatch: both serve logs must show snapshots/16c9bd54 (7c4f1bc1 on -previous) (why)
Empty content, long reasoningmax_tokens ran out during thinking; raise it or send "reasoning_effort": "low"
Old release boots after an upgradesparkrun registry update qwen38-flashnext
Anything elseTry the -previous recipe, then open an issue with sparkrun logs <recipe> -a
Docs: Where the rest of the documentation is
VERSIONS.mdRelease names, old build names, manifests
docs/BENCHMARKS.mdFull benchmark and quality tables for every build, method, history
docs/REFERENCE.mdConfiguration, image provenance, thinking effort, tuning
docs/ENGINEERING.mdKnown 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)

Credits

This build stands on these projects:

  • local-inference-lab: the vLLM fork my branches start from, the b12x kernels (NVFP4 MoE, GDN, QSA) and the NVFP4 checkpoint (the one-Spark checkpoint is derived from it).
  • Qwen: the base model, under the Qwen Community License 1.0.
  • eugr: spark-vllm-docker (my image base), sparkrun (the launcher), and llama-benchy (the base of my benchmark fork).
  • tonyd2wild: the bench_sweep counting harness behind the counting numbers.
  • SeraphimSerapis: tool-eval-bench, which runs the hardmode and TC-45 quality gate.

Earlier experiments drew on other projects too; docs/ENGINEERING.md keeps that full history with exact pins.

License

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).