DeepSeek-V4.1-Flash at 1M context on four DGX Sparks plus one RTX 5090

Attention-FFN disaggregation (AFD): a 32 GB consumer GPU runs attention, the KV cache and the speculative drafter; four DGX Sparks hold the routed experts. The result serves the full 1,048,576-token context with a 2.5M-token KV pool, prefills long prompts at about 5,400 tokens per second, decodes at 74 to 85 tokens per second on one stream, and turns a 2.8-minute cold 679k-token prefill into a 0.8-second reuse.
DeepSeek-V4.1-Flash (552B MoE, 769B with Engram) RTX 5090 32 GB coordinator 4× DGX Spark expert ranks DS41RT engine · prefill capacity 1024 · dSpark · FP4 KV 1M context 2026-09-14

Headlines

Prefill
5,377 tok/s
Cold prefill of a 170k-token prompt (31.5 s). A 679k-token prompt prefills cold in 169 s at 4,020 tok/s, and reprefills in 0.8 s on exact reuse.
Decode
74 to 85 tok/s
One stream: 74.1 tok/s on a long-form essay prompt, 85 tok/s on a code-and-reasoning prompt. Six streams: 168 tok/s aggregate.
Context
1M context
Full 1,048,576-token context enforced, KV pool 2.5M tokens (2.4× the maximum context). Two operating points, one flag: this prefill-first default, or a pool-first alternate with a 7.79M-token pool that served eight simultaneous 679k-token requests with zero pool refusals.

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.

1 · The architecture

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.

Coordinator · RTX 5090, 32 GB Attention, indexers, routers, embeddings, head KV cache: FP4, 890 bytes per token 2.5M-token pool · 24 retained prefixes dSpark speculative drafter (3 stages) Weights resident: 17.5 GiB Prefill lane capacity 1024 · 26.4 GiB occupied ~2.9 GiB headroom after the memory plan Host tier · 128 GB RAM + NVMe Engram n-gram tables, 188.8 GiB, memory-mapped Rows are read on demand, never held in VRAM Page cache in host RAM · no measured read bottleneck 200G RoCE fabric persistent verbs queue pairs per layer, both ways: activations out expert outputs back Expert rank 0 · DGX Spark, 128 GB unified Routed experts, ~80 GB packed · capacity 1024 Expert rank 1 · DGX Spark Routed experts, ~80 GB packed Expert rank 2 · DGX Spark Routed experts, ~80 GB packed Expert rank 3 · DGX Spark Routed experts, ~80 GB packed Decode floor: each token reads about 4.25 GB of expert weights across the four Sparks
Figure 1. What runs where. The coordinator owns everything that is sequential or context-bound; the Sparks own only the routed experts. The Engram tables sit on the coordinator host's disk and RAM, so nothing large has to be replicated per rank.

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.

2 · Uplift over four Sparks alone ★ pending a true like-for-like run

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.

★ Pending: true like-for-like completion run. The baseline series in this section is the reference author's published numbers, not our measurement. The like-for-like run (the reference TP4 stack on the same four Sparks, same prompt ladder, same benchmark, same day) has not been done yet. This section will be replaced when it is; until then every baseline figure here carries a ★.
★ Baseline: TP4 on 4 Sparks (published, boot 10; pending our like-for-like run)Hybrid: 4 Sparks + RTX 5090 AFD (measured)
Figure 2 (★ baseline pending like-for-like). Prefill and decode are separate panels on purpose. Prefill is the best cold rung each stack published or measured (baseline 46.8k-token prompt; hybrid 85k-token prompt). Single-stream decode is the code prompt on each stack's benchmark. Aggregate decode is six concurrent streams (baseline: mean of eight prompt categories; hybrid: the code-and-reasoning standard bench). KV pool is the token capacity of the cache at the serving configuration.
★ Baseline: TP4 on 4 Sparks (published by the reference author; like-for-like run pending)Hybrid: 4 Sparks + RTX 5090 (measured)
Max context served300K in the serving config; 1M proven on a separate boot1,048,576 enforced (8 × 679k-token requests served concurrently on the pool-first alternate)
KV pool1,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 prompts3,848 to 5,384 across 21k to 170k; 4,020 at 679k
Decode, one stream (tok/s)73.8 code prompt; 92.2 counting74 essay prompt; 85 code-and-reasoning prompt
Decode, six streams aggregate (tok/s)131.9167.9
Exact-prefix reusevLLM prefix caching679k 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.

3 · Prefill

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.

Cold prefill throughput, tokens per second
Figure 3. Prefill throughput reaches about 5,400 tok/s once the prompt fills the 1024-token prefill steps (the 21k rung, 3,848 tok/s, does not) and eases to 4,020 tok/s at 679k as attention over the long context grows. The previous default, capacity 256, was flat at 2,000 tok/s across the same range.
Prompt tokensCold prefillPrefill tok/sCold at capacity 256 (earlier default)Exact reuse (24 retained prefixes)Speed-up
21,2315.5 s3,84813.4 s for 25,634 tokens (1,913)0.2 s (100% hit)
84,84415.8 s5,38452.5 s for 104,890 tokens (1,997)not run
169,65431.5 s5,37784.9 s (2,000)0.3 s (100% hit)105×
678,514168.8 s4,020397.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.

4 · Decode

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.

Decode · essay prompt
74.1 tok/s
200-token outputs. 73.5 tok/s at capacity 256, 74.1 at 1024, 74.3 with the host page cache dropped: no difference, because decode is bound by expert-weight reads on the Sparks, not by the prefill lane or the host tier.
Decode · code-and-reasoning prompt
85.6 tok/s
700-token outputs, the Local Inference Labs standard bench at one stream. The same bench at six streams: 168 tok/s aggregate, 30 tok/s per stream.
Aggregate decode, all streamsPer-stream decode
Figure 4. Decode versus concurrency on the standard bench (700-token outputs, code-and-reasoning prompt, three waves per level). Aggregate doubles from one to six streams while per-stream decode divides; the curve is still rising at six.
StreamsRequestsAggregate decode tok/sPer-stream decode tok/sMean wall sFailures
1385.185.68.20
26112.157.112.50
39133.549.715.30
412148.440.518.70
515161.635.221.40
618167.930.424.90

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.

5 · Concurrency and the pool

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.

6 · Fitting the coordinator into 32 GB

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.

  1. Explicit memory reservation at 97%. The automatic pool planner budgets for the worst case and refuses to start on 32 GB. Reserving explicitly lets the planner size the pool itself (2.5M tokens at capacity 1024, 7.79M at 256) with about 3 GiB of device memory to spare, and the engine reaches readiness in seconds.
  2. Two operating points, one flag. The dSpark drafter costs 7.95 GB resident and stays on (it is worth about 1.8× on decode). Prefill capacity 4096 runs out of memory outright on this card, so the choice is between capacity 1024, which leaves 2.07 GiB for the pool (26.35 GiB occupied, 2.5M tokens), and capacity 256, which leaves 7.4 GiB (7.79M tokens). Capacity 1024 is the default: prefill at 170k tokens is 2.69× faster (5,377 vs 2,000 tok/s) and decode is unchanged. Capacity 256 is the pool-first alternate for many concurrent long sessions. The coordinator's batch flag and the ranks' capacity flag must agree, and a mismatch fails loudly at the first request rather than running slow.
  3. Prefix retention at 24 entries. Free in pool terms, and the difference between a 169 s and a 0.8 s response on a repeated long context.
  4. Explicit RDMA device selection on multi-port hosts. The first bring-up failed every request with a closed control plane. The expert ranks were creating their queue pairs on the first RDMA device the host enumerated, which carried no IPv4 RoCEv2 address; the fabric address lived on the second port, so the queue-pair transition to ready-to-receive failed. Host-to-host bandwidth tests passed because they bind by device name, which isolated the fault to device selection. The engine has an undocumented map for exactly this, 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.

7 · What is and is not established

8 · Reproduce

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.

WhatWhere
The enginetpurtell/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 runbookhughmadden/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 GPUtpurtell/ds41rt#1, from hughmadden/ds41rt branch sm-count-diagnostic. A diagnostic only; not needed to reproduce.
The Spark expert imageghcr.io/tpurtell/ds41rt-spark-expert:v1, published by upstream, used unmodified
The baselinetonyd2wild/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 benchLocal 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.

9 · Credits

The 5090 port, the bring-up diagnostics and every measurement on this page are Turquoise Bay AI's work.