Every number on this page was measured on this stack on 2026-09-14 (Sydney) unless it is explicitly labelled as published by someone else. Prefill and decode are always reported separately: prefill is prompt tokens divided by time to first token; decode is output tokens per second after the first token, on one stream unless stated.
A mixture-of-experts model spends its decode time on two very different jobs. Attention over the context wants a fast GPU with the KV cache close by. The routed experts want a lot of memory, because every token reads a slice of hundreds of gigabytes of expert weights. Attention-FFN disaggregation splits the model along that seam: one GPU owns attention and everything sequential, and a pool of memory-rich nodes owns the experts. Per layer, the coordinator sends a small activation vector out over the fabric and gets the experts' output back.
The engine is DS41RT, whose published reference pairs a 96 GB RTX PRO 6000 coordinator with four Sparks. This deployment ports the coordinator to a 32 GB RTX 5090. The published coordinator image will not start on a 5090: its ahead-of-time kernels are exported for the build GPU's streaming-multiprocessor count (188 on the 96 GB card) and the engine rejects any other. Rebuilding at the published revision with the export targeting the 5090's 170 SMs produced an image that is label-identical to the vendor's apart from that constant.
The comparison that matters is the same model on the same four Sparks without the fifth GPU: the tensor-parallel vLLM deployment published by tonyd2wild. The baseline bars below are that author's published boot-10 figures, measured with his prompt set and method; the hybrid bars are ours. The two prompt sets differ, so treat the decode panels as indicative until the same ladder has been run on both stacks.
| ★ Baseline: TP4 on 4 Sparks (published by the reference author; like-for-like run pending) | Hybrid: 4 Sparks + RTX 5090 (measured) | |
|---|---|---|
| Max context served | 300K in the serving config; 1M proven on a separate boot | 1,048,576 enforced (8 × 679k-token requests served concurrently on the pool-first alternate) |
| KV pool | 1,070,168 tokens (3.57× at 300K) | 2,490,000 tokens (2.4× at 1M); the pool-first alternate holds 7,790,000 (7.4×) |
| Prefill, cold (tok/s) | 902 to 1,539 across 3k to 93k-token prompts | 3,848 to 5,384 across 21k to 170k; 4,020 at 679k |
| Decode, one stream (tok/s) | 73.8 code prompt; 92.2 counting | 74 essay prompt; 85 code-and-reasoning prompt |
| Decode, six streams aggregate (tok/s) | 131.9 | 167.9 |
| Exact-prefix reuse | vLLM prefix caching | 679k tokens in 0.8 s (211× vs cold) |
Read it this way. Single-stream decode is about the same on both, because on both stacks a decode step is bound by streaming expert weights out of the Sparks' unified memory. What the fifth GPU buys is everything that was starved on the Sparks: the prefill runs on a card with far more compute per watt of memory traffic and a prefill lane four times wider, the KV pool is 2.3 times larger at this configuration and seven times larger at the pool-first alternate, because the coordinator's memory is spent on cache rather than expert weights, and concurrent decode has more room to batch.
Cold prefill, unique prompts, temperature 0, speculative drafter on. Prefill throughput is prompt tokens divided by time to first token. Each rung is one request.
| Prompt tokens | Cold prefill | Prefill tok/s | Cold at capacity 256 (earlier default) | Exact reuse (24 retained prefixes) | Speed-up |
|---|---|---|---|---|---|
| 21,231 | 5.5 s | 3,848 | 13.4 s for 25,634 tokens (1,913) | 0.2 s (100% hit) | — |
| 84,844 | 15.8 s | 5,384 | 52.5 s for 104,890 tokens (1,997) | not run | — |
| 169,654 | 31.5 s | 5,377 | 84.9 s (2,000) | 0.3 s (100% hit) | 105× |
| 678,514 | 168.8 s | 4,020 | 397.1 s (1,709) | 0.8 s (100% hit) | 211× |
Prefix retention is what makes 1M usable. With retention off, a repeated 170k-token prompt costs the full cold prefill again (83 s when this was measured at capacity 256). With 24 retained prefixes it returns in 0.3 s, and the engine's memory plan is byte-identical either way: the retention banks live inside the fixed 2 GiB runtime headroom, so retention costs nothing from the KV pool.
Concurrent prefill. Measured on the pool-first configuration: sixteen 105k-token requests released together all completed at 407.6 s wall each, perfectly batched, for about 3,340 tok/s of aggregate prefill against 2,000 solo at that capacity. The single prefill lane serialises admission, so beyond two to four prefill-heavy streams extra concurrency buys queueing rather than throughput. The same shapes have not been re-run at the current default.
For scale: the vendor's 96 GB reference coordinator at prefill capacity 4096 cold-prefills its 1M prompt in 322 s (their figure, about 3,300 tok/s); this card does the 679k-token rung at 4,020 tok/s with a quarter of the prefill lane.
Decode is output tokens per second after the first token. It depends on the prompt, because the speculative drafter accepts more draft tokens on code and structured text than on prose. Both numbers below are one stream, temperature 0, drafter on.
| Streams | Requests | Aggregate decode tok/s | Per-stream decode tok/s | Mean wall s | Failures |
|---|---|---|---|---|---|
| 1 | 3 | 85.1 | 85.6 | 8.2 | 0 |
| 2 | 6 | 112.1 | 57.1 | 12.5 | 0 |
| 3 | 9 | 133.5 | 49.7 | 15.3 | 0 |
| 4 | 12 | 148.4 | 40.5 | 18.7 | 0 |
| 5 | 15 | 161.6 | 35.2 | 21.4 | 0 |
| 6 | 18 | 167.9 | 30.4 | 24.9 | 0 |
The engine requires temperature 0 for its native sampling path, so the bench ran with that one sampling change; acceptance length is not reported because DS41RT does not expose vLLM-style speculative-decoding metrics.
The admission cap is 16 streams at either operating point, and a lane costs 6 MB, so what bounds concurrency is the pool. At the default (2.5M tokens, about 2.4 full contexts) many simultaneous long sessions are pool-bound by construction. The two large shapes below were measured on the pool-first alternate (capacity 256, 7.79M-token pool) and have not been re-run at the default; decode-side concurrency (section 4) is independent of the prefill capacity.
The practical limiter is the prefill lane rather than KV memory: at long context the fleet is happiest with a few prefill-heavy streams and many decode-heavy ones. If a workload needs many concurrent long sessions, the pool-first alternate is the right configuration.
The coordinator's weights are modest at 17.5 GiB. What made the port hard is that the engine's two execution lanes, their arenas and the default KV plan were sized for a 96 GB card. Four settings decided the outcome, each measured before it was adopted.
DS41RT_PROTOCOL_V2_VERBS_HOST_DEVICE_MAP (local-ip=device entries),
consumed for both client and server endpoints. Setting it on every rank and the coordinator
fixed it, and the launch scripts now refuse to start without a fabric address.Provenance. This page was first published at capacity 256. The widening to 1024 was suggested by tj, DS41RT's author, on the grounds that for most agent workloads the pool was oversized and the prefill lane was what mattered; it was measured the same day (679k-token cold prefill 397 s to 169 s, decode 73.5 to 74.1 tok/s) and became the default.
Everything needed is public. The engine is unmodified upstream; our contribution is how to build it for a 5090 and how to launch and gate the fleet.
| What | Where |
|---|---|
| The engine | tpurtell/ds41rt at the v1 revision 9ea5c964 (the revision the published Spark expert image carries; upstream has since released v2, untested here) |
| The 5090 port: build, probe, preflight and launch scripts, the gates, settings and runbook | hughmadden/ds41rt-rtx5090, MIT. No engine source is vendored; the coordinator is rebuilt from upstream on the 5090 so the AOT export reads 170 SMs. |
| The one upstreamable change: name the expected vs observed SM count when the AOT device gate rejects a GPU | tpurtell/ds41rt#1, from hughmadden/ds41rt branch sm-count-diagnostic. A diagnostic only; not needed to reproduce. |
| The Spark expert image | ghcr.io/tpurtell/ds41rt-spark-expert:v1, published by upstream, used unmodified |
| The baseline | tonyd2wild/DeepSeek-V4.1-Flash-vLLM-DGX-Spark (TP4 vLLM on four Sparks; its published boot-10 numbers are the starred series in §2) |
| The standard bench | Local Inference Labs bench_c1c6.py, run with temperature forced to 0 (the engine's native sampling path requires it) |
# build (on the 5090 host; the AOT export reads this GPU's SM count): REPO=/path/to/ds41rt scripts/afd-build-coordinator.sh # -> ds41rt-coordinator-rtx5090:v1 # prove it fits and boots with no Sparks: MODEL_DIR=/path/to/DeepSeek-V4.1-Flash scripts/afd-ready-probe.sh # READY in ~4 s # operating point (scripts/afd-launch-coordinator.sh): # prefill capacity 1024 (batch 1024; BATCH=80 selects the pool-first capacity 256), # dSpark on, concurrency 16, prefix-cache entries 24, max context 1,048,576, # memory reservation 97% # experts (scripts/afd-launch-experts.sh, per Spark, worker-first 3,2,1,0): # published arm64 image, capacity 1024 (must match the coordinator), ~100 GiB device budget # fabric: on every rank and the coordinator, # DS41RT_PROTOCOL_V2_VERBS_HOST_DEVICE_MAP=<fabric-ip>=<rdma-device> (see docs/GATES.md) # measure: unique salted prompts, temperature 0, streaming; # prefill = prompt tokens / TTFT; decode = output tokens / time after first token
Sydney timestamps throughout.
The 5090 port, the bring-up diagnostics and every measurement on this page are Turquoise Bay AI's work.