DFlash for Qwen3.5-122B-A10B = 80+ tok/s on 1x Spark!

Modal just released a new MTP / speculative decoding model for Qwen3.5-122B-A10B:

I set it up so you can run it with albond’s optimizations for the Spark on the latest vLLM, and tested that it enables 80+ tok/s decode on my actual Hermes Agent prompt history.

TLDR: A one-shot Docker/vLLM installer that runs Qwen3.5-122B-A10B on a single Spark with DFlash block-speculative decoding. On agent/tool-call traffic it sustains ~81 tok/s single-stream, and on @albond’s own end-to-end benchmark it clears his published number unpatched (59 vs 51 tok/s).

My repo + installer:
GitHub - Entrpi/qwen3.5-122B-A10B-on-spark: Run Qwen3.5-122B-A10B on a DGX Spark (GB10/sm121) with DFlash speculative decode — one-shot Docker/vLLM installer. Builds on albond's recipe; stacks community patches for latest vLLM + DFlash. · GitHub

This builds directly on @albond’s excellent INT4 + MTP recipe

That work is the foundation. What’s new here is swapping the speculative-decode path to DFlash and re-stacking the community patches to run on current vLLM (0.23) with DFlash support, plus memory/load tuning for the Spark’s unified memory.

Headline results

Single-stream (c=1), temp 0, GB10. The comparison row uses @albond’s exact e2e
harness (completion_tokens / total wall-clock incl. prefill, cross-prompt mean of
5 prompts, run-1 discarded as warmup) so it’s apples-to-apples:

Config tok/s Notes
Plain INT4, no spec (baseline) 28.2 airtight
@albond’s full patched MTP-2 stack (published) 51.58 INT4+FP8 + int8-lmhead + MTP-2
DFlash n=12, unpatched (albond harness) 53.7 accept len 6.5, no extra patches
DFlash n=12 + dense levers (albond harness) 59.0 + hybrid FP8 + int8 lm-head
DFlash n=12, real agent turns (Hermes) ~81 73% tool-calls, accept len ~8.3

Why DFlash wins on agent traffic: it block-drafts ~12 tokens in one parallel
forward
, where MTP-N needs N sequential head passes (so MTP-2 saturates at accept
length ~3). On predictable tool-call/code output, DFlash acceptance climbs to 8–12,
which MTP structurally can’t match without paying N forwards.

What’s in the stack

  • Target: Intel/Qwen3.5-122B-A10B-int4-AutoRound (INT4, bf16 KV)
  • Drafter: z-lab/Qwen3.5-122B-A10B-DFlash (0.8B block-diffusion, non-causal → FA2)
  • Engine: vLLM 0.23 on AEON-7’s sm121 DFlash image
    (ghcr.io/aeon-7/aeon-vllm-ultimate)
  • Optional dense levers (default profile): hybrid INT4 routed + FP8 shared
    experts and an int8 W8A16 lm-head GEMV — prebuilt hybrid checkpoint hosted at
    bleysg/Qwen3.5-122B-A10B-int4-fp8-hybrid so there’s no per-user rebuild.

The key DFlash-on-122B fix is a scale-block KV unify patch + prefix-caching off
(the hybrid GDN/mamba + attention KV page geometry otherwise rejects the drafter’s
attention spec). Details in docs/FINDINGS.md.

Memory & context

Tuned to reserve ~14 GB for the OS even under load:

value
gpu-memory-utilization 0.82 (0.87+ over-subscribes → swaps → stalls)
max context 262,144 (full native), single stream can reach it
KV pool (dense) 426,610 tokens — 1.63× a full 262k context
KV pool (dflash) 456,664 tokens
free under peak 3-stream load ~15 GiB

Comfortably fits a realistic agent layout: one ~100k main thread + up to ~3
subagents <100k each.

Concurrency is additive (dense, decode-only tok/s per stream):

Workload 1 2 3
prose 48.8 38.4 30.8
code 66.9 55.3 43.0
agentic (6k tool-call ctx) 121.8 98.9 66.7

Startup

Time-to-READY is ~3 min (vs ~12). --load-format fastsafetensors reads the
62–71 GiB straight to device in ~32 s instead of ~8 min — the default
mmap+per-tensor read + redundant CPU→GPU copy is pathologically slow on GB10’s
current kernel (and the copy is pointless on unified memory). Auto-falls-back to
nogds; no GPUDirect hardware needed.

Quick start

git clone https://github.com/Entrpi/qwen3.5-122B-A10B-on-spark
cd qwen3.5-122B-A10B-on-spark
./install.sh --start         # dense profile by default; downloads the hosted hybrid

OpenAI-compatible server on :8000 (model name qwen), tool-calls + SSE.
Profiles: dense (default), dflash, base, mtp.

Caveats

  • The always-on dense levers (FP8 shared experts, int8 lm-head) give ~+28% at
    base decode but amortize to ~null on high-acceptance agent traffic (the verify
    forward batches the dense reads across the accepted block). So for a pure agent
    workload, plain dflash is essentially equal and gives a larger KV pool — the
    dense profile is the better default for mixed/low-acceptance use.

Huge thanks to @albond (the recipe this builds on), z-lab (DFlash),
AEON-7 (sm121 DFlash vLLM build), Intel (AutoRound INT4), and Qwen.
Feedback and numbers from other Spark units very welcome.

Thank you for the interesting project.
Let me ask why do not you use nvfp4 instead of int4 in the layers?
Any compare to standard qwen3.6-35b-a3b-fp8 for agentic coding or research tasks?

I’m using the recipe from albond with the top performing Intel AutoRound model used there.

I’ve developed a new quantization method which outperforms AutoRound and works with NVFP4 but haven’t done one for this model yet. Will likely do that soon.

btw. this can be accessible only locally? 127.0.0.1? it cannot be reachable from intranet?

I’m asking because from Hermes agent I have:

⚠️ API call failed (attempt 1/3): BadRequestError [HTTP 400]
🔌 Provider: custom Model: qwen
🌐 Endpoint: http://192.168.50.185:8000/v1
📝 Error: HTTP 400: “auto” tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set
📋 Details: {‘message’: ‘“auto” tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set’, ‘type’: ‘BadRequestError’, ‘param’: None, ‘code’: 400}
⏱️ Elapsed: 0.13s Context: 2 msgs, ~5,622 tokens
⚠️ Non-retryable error (HTTP 400) — trying fallback…
🔄 Primary model failed — switching to fallback:

One more q: that “dense” is the one? (default) or dflash is what you want us to test?

I have this when on hermes tool bench rtun

Thanks for kicking the tires and good catch on the tool-choice error. Fixed in the repo just now (git pull); details below.

1. Tool calling (HTTP 400). The server was shipping without tool-calling enabled, which is silly for an agent recipe. any client sending tools with tool_choice="auto" got that 400. serve.sh now turns it on by default. If you’re on an older clone, either git pull or add these flags to vllm serve:

--enable-auto-tool-choice --tool-call-parser qwen3_xml --reasoning-parser qwen3

2. Intranet access. It’s not local-only: the server binds 0.0.0.0 inside
--net=host, so it’s reachable from the LAN at http://<your-spark-ip>:8000. The
127.0.0.1 in the README is just the local example. Heads-up: there’s no auth, so if you expose it beyond a trusted network, put it behind a reverse proxy or firewall.

3. dense vs dflash. dense is the default profile (good general-purpose pick).
Dense is using some trimmed down precisions in the always on decode pipeline to improve performance. In agent/tool traffic the two are within noise of each other (dense’s always-on FP8/int8 levers amortize away at high drafter acceptance), so either is fine for agentic use; dense is mostly good for improving the speed of prose generation, which has less draft predictability.

So If I understood your post correctly, I can use your method with Albond’s Hybrid Model, correct?

I might give this a go over the weekend to see what I can get. So far my experience with DFlash has been lackluster when it comes to long context workloads (It headlines very well on benchmark but my real world usage performance quickly tanks when the context pressure raises)

Yes, that model works. The hosted hybrid this downloads by default is the same as that (just pointed at a pre-made one to save the local build time). If you have albond’s hybrid on disk, use it with the dense profile here.

Thanks

Before and after applying the changes from this post
1)

Even though the second benchmark took almost 55 seconds longer after applying the changes, the vLLM logs show that generation peaks at 70+ tokens/sec, and in practice it feels like it is actually faster. You can feel the difference.

It is a pity that the Qwen 122B model still does not always seem “smart” enough. Something like Kimi or MiniMax would definitely be smarter.

Could someone please advise how you use Hermes, for example with two DGX Spark machines? How many tokens/sec do smarter models than Qwen 122B usually produce?

On the SparkArena leaderboard, when I select two devices and sort models by generated tokens per second, the numbers do not look impressive at all.

I would really like to run something intelligent on 2× DGX, with at least around 50–80 tokens/sec, because anything lower than that becomes frustrating. Simple tasks — such as website development or finding bugs in the API integration between backend and frontend — that Codex completes in about 20 minutes take local models 2–4 hours. You can clearly see them going in the wrong direction, then slowly trying to reach the correct solution, or sometimes not reaching it at all.


vLLM: 0.23.0+aeon.sm121a.dflash
profile: dense/DFlash
DFlash speculative tokens: 12
served model name: qwen
port: 8001
max_model_len: 262144
gpu_memory_utilization: 0.82
max_num_seqs: 3
max_num_batched_tokens: 8192
attention_backend: flash_attn
tool parser: qwen3_xml
reasoning parser: qwen3
chat template: /home/dgx/qwen-dflash-runtime-codex/unsloth.jinja
enable_thinking: false
prefix caching: disabled
restart policy: unless-stopped

Yeah, Qwen team says they will be open sourcing Qwen 3.7 smaller models soon. Hopefully that includes an update of this one. Just the right size for a single Spark.

That’s a good news, source?

I saw a twitter post where some guy from Qwen said they will open weight Qwen 3.7, that is about it. Nothing official.

You should start a new thread with your question. Your observations regarding the performance belong in this thread but the questions regarding of different model belong in a new thread. This thread is getting many branches.

Scott

I’m ready for a new 122B-A10B from Qwen :)

To be honest, for us in the DGX Spark Community I think an 80B-A8B or something like that would PERFECT

This repo allowed me to finally have Qwen3.5-122B-A10B-heretic-int4-AutoRound running with speculative decoding. Heretic stripped MTP out of it, so the model was too slow for everyday use. I modified install.sh (to point to the heretic model) and ran it with–build-hybrid. And it just worked! So grateful, as I have spent way too many days (over the last few weeks) trying to get this going. Thank you so much.

I can’t get this to run at the full context, even if I set the mem utilization to .87 it says I only have enough for 112000 context.

Hey! Can you share your recipe and hf model link?

Hello. First of all, thanks for your work.

A few minor things (might just be me using this wrong):

  • Your installation command was wrong. This one worked for me (you had the wrong URL):
    curl -sSL https://raw.githubusercontent.com/Entrpi/qwen3.5-122B-A10B-on-spark/refs/heads/master/install.sh | bash -s – --start
  • The “port” variable in your install script isn’t actually wired up? When you set it, it doesn’t do anything. To fix this, I just added this on this line in install.sh
    291 -e PORT=“$PORT” -e HF_HOME=/hf -e VLLM_CACHE_ROOT=/hf/.vllm_cache -e MAX_MODEL_LEN=“$CTX” -e GPU_MEM=“$GPU_MEM” \
  • When you change speculative depth (I tried 6 because 7->12 seemed to be rejected a lot), you still keep the torch compile cache. I didn’t know what type of error it was spitting out at me without running it through a free frontier LLM, but is causes issues during startup. You should delete the torch compile files if the speculative depth changes from the last execution. (found in ~/.cache/huggingface/.vllm_cache/torch_compile_cache by default I believe)

For speed, I just averaged ~181 samples of output from the docker, using the “Average generation throughput.”

As far as speed, this certainly loads a lot faster than the 0.19 vllm albond version,and I am happy that it uses the latest image. For the actual output speed, I have seen it vary from anywhere between 28 tok/s to 75 tok/s on my current varied higher context workload (~100k token payloads, 2k+ token structured prompt). The average was about 45 tok/s.

I think compared to Albond’s script it’s a tiny bit faster or maybe about the same (for me anyway). The main benefit is using a modern vllm container while getting about the same speed, at least to me. The performance is really uneven, too. I could get an average of 25-30 t/s on some dense datasets, and then 50-60 on others. This is just the result of me running the same (rather large) prompt over and over (scripted via Curl, nothing special) on a bunch of documents.

I’m not sure how it takes up so little vram while serving the 262k context, either, though. Is this using a lower quant for something than albond’s version?

I ran NVIDIA AIPERF evals on this setup (with dense and dflash settings) vs the unquantized model (hosted TP=4 across 4x RTX PRO 6000s).

The sample size is small enough the error bars aren’t picking clear winners on any here, but it hints at very minor degradation from quantizing.

Thanks for the detailed report, all now fixed.

Install URL good catch. The default branch was master while the docs pointed at /main/. I’ve renamed the default branch to main, so the documented one-liner now works as-is:

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

--port not propagating confirmed: serve.sh/mtp_serve.sh honored PORT, but install.sh never passed it into the container, so the server stayed on 8000 while the readiness poll watched your --port. Fixed exactly as you suggested (-e PORT="$PORT" on the docker run).

Torch-compile cache on spec-depth change … also fixed. install.sh now clears torch_compile_cache automatically whenever the profile/nspec signature changes, so no more manual rm after changing speculative depth.

On the throughput … your numbers look right and expected. The ~80 tok/s headline is short-context, single-stream; at ~100k-token payloads decode is dominated by KV/attention growth, and DFlash acceptance varies a lot by content (open prose is the worst case I observed; code/structured/tool-calls are much higher), so 28–75 averaging ~45 is plausible. Glad it’s faster than the older build.

Thanks again for digging in.