qwen3.5-122B-A10B-on-spark

Qwen3.5-122B-A10B (hybrid GDN + mamba + 128-expert MoE, ~10B active) running on a single NVIDIA DGX Spark (GB10 / SM121, 128 GiB unified) under vLLM, with DFlash block-diffusion speculative decode and an optional dense-bandwidth patch stack — measured end-to-end, with a per-token bandwidth model that explains every number.

Status: Working end-to-end, one-shot install. On real Hermes-agent tool-call turns, DFlash decode reaches a median ~81 tok/s on GB10~2× the native MTP-2 head (~40 tok/s) on the same workload, and above the best previously published number for this model on Spark (albond's fully-patched MTP stack, 51.58 tok/s end-to-end). DFlash's acceptance is task-dependent (it block-drafts 12 tokens in one parallel forward), so the win is largest on structured/tool-call/code traffic and collapses to parity on open-ended prose.

A separate dense-bandwidth stack (hybrid INT4+FP8 shared experts + int8 lm-head) adds +28 % to no-spec / base decode (28.2 → 36.0 tok/s) but, by the amortization law below, washes out to ~null on high-acceptance agent traffic — so it's a lever for base / low-acceptance serving, not for the agent path.

  • Engine: vLLM 0.23, sm121 build with the DFlash PRs, via the prebuilt image ghcr.io/aeon-7/aeon-vllm-ultimate:2026-06-18-v0.23.0-dflashfix. No host build — the four runtime patches in runtime/ are applied at serve time.
  • Target: Intel/Qwen3.5-122B-A10B-int4-AutoRound — INT4 (AutoRound/GPTQ) routed experts + attention, BF16 shared experts/embeddings/head, ~62 GiB. (Safetensors, not GGUF — vLLM serves HF checkpoints directly.)
  • Drafter: z-lab/Qwen3.5-122B-A10B-DFlash — 0.8B / 6-layer non-causal block-diffusion drafter (block 16), shares the target's embed_tokens + lm_head, ~1.6 GiB.
  • Hardware: NVIDIA DGX Spark, GB10, SM121, 128 GiB LPDDR5X unified, ~273 GB/s.

Quick start

On a DGX Spark with Docker + the NVIDIA container runtime:

curl -sSL https://raw.githubusercontent.com/Entrpi/qwen3.5-122B-A10B-on-spark/main/install.sh | bash -s -- --start

That one command:

  1. Verifies the host (aarch64, GB10/SM121, Docker GPU access, free disk).
  2. Pulls the sm121 vLLM image (~40 GiB, one-time).
  3. Downloads the INT4 target (~62 GiB) + DFlash drafter (~1.6 GiB) into the HF cache.
  4. Starts the dflash profile on :8000, waits until READY, and runs the "capital of France" smoke test (asserts "Paris").

Already have the model? Skip the 62 GiB download:

# point at a checkpoint dir you already have (mounted read-only at /model):
./install.sh --start --model-dir /path/to/Qwen3.5-122B-A10B-int4-AutoRound
# or reuse an existing HF cache (download becomes a no-op if already present):
./install.sh --start --hf-home /mnt/big/hf

Preview without running: ... | bash -s -- --help.

Hardware requirements

Validated on NVIDIA DGX Spark (GB10, SM121, 128 GiB unified)
Likely to work other Blackwell with --force (untested)
Runtime Docker + NVIDIA container runtime (docker run --gpus all)
Disk ≥ 75 GiB free (image + weights); ≥ 150 GiB if --build-hybrid
OS aarch64 Linux (Grace)
Memory 128 GiB unified is enough for the model + DFlash drafter + KV @ 16k

GB10 is detected via nvidia-smi --query-gpu=compute_cap returning 12.1; anything else needs --force.

What you get — profiles

Pick with --profile:

Profile Stack Best for Measured
dflash (default) INT4 + DFlash n=12 agents / tool-calls / code ~81 tok/s Hermes · 53.7 albond-bench
dense hybrid INT4+FP8 + int8 lm-head + DFlash n=12 base / low-accept serving 36.0 base (+28%) · 59.0 albond-bench
base plain INT4, no spec airtight baseline 28.2 tok/s c=1
mtp INT4 + native MTP-2 head comparison ~40 tok/s Hermes

The server is OpenAI-compatible (/v1/chat/completions with tool calls + SSE, /v1/completions, /v1/models) and serves under the model name qwen.

Benchmarks

All single-stream (c=1), temperature 0, GB10. "Hermes" = regenerating the next assistant turn over 10 real conversations from a live agent's state.db (73 % tool-calls); "albond-bench" = albond's own end-to-end harness (completion_tokens / total wallclock incl. prefill, 5 prompts, run-1 discarded — directly comparable to his published 51.58).

DFlash vs MTP, same harness, unpatched

Workload (accept len) base no-spec MTP-2 DFlash n=12
Prose (~2.3) 28.2 33.7 33.2 (use n=4)
Code (~5.4) 28.2 40.5 54.5
Counting (~11) 28.2 43.7 (MTP caps at acc 3) 124.5
Hermes, real turns (8.3) 39.9 ~81
albond-bench e2e (6.5) 53.7

MTP-2 drafts 2 tokens sequentially (acceptance caps at ~3); DFlash block-drafts 12 in one parallel forward, so on predictable/agent traffic it accepts 511 and pulls ~2× ahead. They tie only on low-acceptance prose. 53.7 unpatched already clears albond's fully-patched MTP (51.58) under his own method.

The dense-bandwidth stack (dense profile)

Two independent always-on levers, ported to vLLM 0.23 as runtime patches: hybrid INT4+FP8 (BF16 shared experts → calibrated FP8) and int8 lm-head (the 248 320-row vocab projection → int8 w8a16 GEMV, ~2× the bf16 read).

Config base (acc 1) DFlash spec, albond-bench (acc 6.4) Hermes (acc 8.3)
INT4 baseline 28.2 53.7 ~81
+ hybrid-FP8 30.4 (+7.8%) 57.0 (+6.1%) ~80
+ int8 lm-head 32.7 (+16%)
+ both 36.0 (+28%) 59.0 (+10%) ~8087 (noise)

The amortization law

The dense levers cut always-on weight reads (shared experts + lm-head, read every token). Under speculative decode the verify forward reads those weights once and amortizes them across the accepted block, so the gain shrinks as acceptance rises — monotonically, across the whole curve:

dense stack uplift:   +28%  (base, accept 1)
                  →   +10%  (albond-bench, accept ~6.4)
                  →   ~0%   (Hermes, accept ~8.3)

Consequence: for the agent path (dflash), DFlash's own high acceptance already saturates the dense levers — its remaining bottleneck is routed-expert verify-batch reads, which no dense-weight quant touches. For base / low-accept serving (dense), the stack is a real +28 %. See docs/FINDINGS.md.

Under the hood: the four runtime patches

vLLM is unmodified on disk; runtime/serve.sh edits the installed package in-place before vllm serve (idempotent, sentinel-guarded):

Patch What it does Needed by
patch_unify2.py scale-block KV-cache unify so the hybrid GDN+mamba target absorbs the drafter's attention spec (the original assert can't); + --no-enable-prefix-caching routes to the no-hash-assert coordinator DFlash (any spec profile)
patch_inc_hybrid.py adds an INCConfig.maybe_update_config override that detects FP8 dense layers in the hybrid checkpoint and dispatches Fp8LinearMethod for shared_expert dense
patch_int8_lmhead_v3.py replaces the lm-head matmul in _get_logits with a batched int8 w8a16 Triton GEMV (keeps bf16 weight for the shared drafter) dense
patch_fla_shmem.py lets the FLA GDN chunk kernels use big tiles on sm121's 99 KiB shmem (prefill/TTFT only; harmless) always (free)

Why DFlash needs the unify patch at all, why the drafter must run FLASH_ATTN (non-causal), and the full vLLM-vs-SGLANG dead-end history are in docs/FINDINGS.md.

Repo layout

install.sh                 One-shot installer (curl | bash | --help)
runtime/                   Mounted read-only at /host inside the container:
  serve.sh                   vLLM serve wrapper (applies the patches, then serves)
  patch_unify2.py            DFlash KV-unify fix
  patch_inc_hybrid.py        hybrid INT4+FP8 dispatch
  patch_int8_lmhead_v3.py    int8 lm-head GEMV
  patch_fla_shmem.py         FLA sm121 big-tile (prefill)
  mtp_serve.sh               MTP-2 comparison serve
scripts/                   Host-side helpers:
  monitor.sh                 Container-startup monitor with OOM auto-kill guard
  bench_decode.py            Decode-only tok/s (excludes TTFT)
  bench_albond.py            albond's e2e method (comparable to his 51.58)
  hermes_bench.py            Real agent turns from ~/.hermes/state.db
  run_bank.sh                prose/code/counting/hermes bank on any server
tools/
  build-hybrid-checkpoint.py Build the hybrid INT4+FP8 ckpt (for --build-hybrid)
  inspect_ckpt.py            Which layers are INT4 vs BF16 vs FP8
  validate_*.py              Standalone correctness checks for the patches
docs/
  FINDINGS.md                The full investigation, methodology, and the
                             amortization-law derivation

Reproducing

# default agent path (DFlash) + smoke test:
./install.sh --start

# the dense stack (build the hybrid ckpt once, ~20 min, then serve):
./install.sh --build-hybrid
./install.sh --start --profile dense

# benches (run on the host against the server; need: pip install requests):
python3 scripts/bench_decode.py  --base-url http://127.0.0.1:8000 --model qwen \
        --prompt "Write a detailed essay about the history of tea."
python3 scripts/bench_albond.py  http://127.0.0.1:8000 "dflash"     # e2e, vs 51.58
python3 scripts/hermes_bench.py  --base-url http://127.0.0.1:8000   # real agent turns

# MTP comparison:
./install.sh --start --profile mtp
Piece Role
vLLM the inference engine; this repo serves Qwen3.5 + DFlash on it, unmodified-on-disk
Intel/...int4-AutoRound · z-lab/...DFlash the target + drafter weights
albond/DGX_Spark_Qwen3.5-122B-A10B-AR-INT4 the MTP + hybrid-FP8 + int8-lmhead recipe we benchmarked against and ported the dense levers from
Entrpi/ds4-on-spark sibling repo, same hardware, different model (DeepSeek-V4-Flash via ds4)
Modal: Speculative decoding is all you need the DFlash block-diffusion drafter and the task-dependent-acceptance framing

Acknowledgements

  • z-lab / Modal — the DFlash drafter and block-diffusion speculative decode.
  • Intel/AutoRound — the INT4 target quantization.
  • vLLM and the AEON sm121 image maintainers — the engine and the DFlash-enabled GB10 build.
  • albond — the MTP/hybrid-FP8/int8-lmhead recipe and the end-to-end benchmark methodology.

License

MIT — see LICENSE. The patches are original; vendored third-party files (tools/build-hybrid-checkpoint.py) retain their upstream attribution.

S
Description
Qwen3.5-122B-A10B on a DGX Spark with DFlash speculative decode. One-shot Docker/vLLM installer. 80+ tok/s!
Readme MIT
108 KiB
Languages
Python 70.9%
Shell 29.1%