DeepSeek-V4.1-Flash is 552B parameters plus a 203 GB Engram n-gram table that stock vLLM keeps in host memory — which on a DGX Spark is the GPU memory. The recipe serves it across four GB10 nodes with Engram-on-disk patches from tonyd2wild and Kai, the day-0 vLLM branch, and a DCP2/DCP4 port.
The seven patch files from tonyd2wild/DeepSeek-V4.1-Flash-vLLM-DGX-Spark (MIT), bind-mounted over the image
For DCP2 + RoCE collectives: aidendle94/sparkrun-vllm-dsv41-gb10:production-1.2 (the 28-file patch set and the b12x RoCEnante runtime baked in; production-1.1 = the 23-file DCP2 set without RoCE), or production-1.0 with the files bind-mounted, plus the launcher profile and DSV41_* / ROCE switches
deepseek-ai/DeepSeek-V4.1-Flash weights (510 GB) on one node, exported read-only over NFS to the other three — or the NVFP4-Engram variant aidendle94/DeepSeek-V4.1-Flash-NVFP4-Engram (418 GB, tables quantized to NVFP4; pick it in the Build tab)
~50 GB of local NVMe per node for a sparse copy of that rank''s Engram rows, plus ~25 GB for the image
Root on every node (RDMA passthrough, memlock, page-cache flushing) and earlyoom tuned to fire before the kernel OOM killer
DeepSeek-V4.1-Flash served on four DGX Spark GB10 nodes with vLLM: TP4, DSpark speculative decoding with the checkpoint's own draft layers, FULL_AND_PIECEWISE CUDA graphs, tool calling and images on, thinking on at reasoning_effort: max by default since 2026-09-12 (1.0 shipped it off, 1.1–1.2 ran high = 50; the levels are low 25 / high 50 / xhigh 75 / max 100, set per request with any integer 1–100 or "thinking": false). The model is 552B parameters of MXFP4 experts and MXFP8 dense layers plus two Engram n-gram tables — 203 GB of FP8 lookup rows that stock vLLM keeps in host memory. On a DGX Spark host memory is the GPU pool, so as shipped it does not fit four of them. It fits with the tables on disk.
02Build
Per-node deployment
One container per node, host networking, RDMA passthrough. The model directory is the local checkpoint on the node that holds it and the NFS mount everywhere else; the patch files are bind-mounted over the image's vLLM tree (production-1.0 + the seven tonyd2wild files, or production-1.0 + the 23-file DCP2 set) — or use production-1.1, which has the DCP2 set baked in and needs no mounts; the node's Engram rows are mounted when present. Workers start first, the head last. The launcher refuses to run eager and refuses to start a rank with less than 100 GiB MemAvailable after dropping caches. Pick the checkpoint below: the original fp8 Engram tables, or the NVFP4-Engram variant we quantized (same everything else, 92 GB smaller).
Tables as shipped: fp8 e4m3 rows with UE8M0 scales per 32, 101 GB per layer. 510 GB checkpoint; ~50 GB of node-local sparse row copies per node.
docker run · one rank (rank 0 shown; workers add --headless and their own VLLM_HOST_IP)
Export the checkpoint's HuggingFace repo directory from the node that holds it (ro,no_subtree_check, 32 nfsd threads) and mount it on the others; the repo directory, not the snapshot, so the blob symlinks resolve.
Either pull production-1.2 (the DCP2 patch set and the RoCE adapter are baked in; production-1.1 lacks the RoCE runtime) or put the patch files and mounts.txt on every node and let the launcher build the bind mounts from the manifest; a rank that lacks a file must abort the launch — Docker binds an empty directory over a missing source and the import fails minutes later.
Boot once without local rows and read each rank's Engram DISK mode: layer L rows [start, end) lines. Run engram_local.py per node with those ranges (about 6 minutes per node at 150 MB/s, cache-dropping, verified against the source), then relaunch with DSV41_ENGRAM_DIR set. Every rank should log read from node-local. Copies are per checkpoint: switching between the fp8 and NVFP4 tables means new copies (28 GB per node for NVFP4); the manifest carries the format and a mismatch falls back to reading the model directory.
Healthy in ~11 minutes: 5.5 min of weight load, the draft pass, graph capture, DSpark speculator capture, the b12x MoE warm-up. Then check three things before trusting it: GPU KV cache size (1,888,800 tokens on DCP2 with 6 GiB; 1,263,118 on DCP1 at 8 GiB), MemAvailable on every node (≥ 5 GB on the head), and Using 'B12X_MXFP4_MXFP8' Mxfp4 MoE backend.
Run something that checks answers, not just tokens: count to 100 (exact), a needle at 32K, an image and a tool call. Then a concurrency soak with unique-tagged prompts so the prefix cache cannot hide anything.
thinking on, per request
curl -s http://<head>:8410/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model": "deepseek-v4.1-flash", "max_tokens": 300, "temperature": 0,
"messages": [{"role": "user", "content": "Is 91 a prime number? Answer briefly."}],
"chat_template_kwargs": {"thinking": true, "reasoning_effort": 25}}'
# reasoning_content: "We need answer briefly. 91 = 7*13, not prime." (19 reasoning tokens)
# content: "No. 91 = 7 × 13, so it is not prime."
03Deep dive
Why it does not fit, and what the disk patch does
At TP4 each rank carries a quarter of the experts (296 GB of MXFP4 split four ways), its share of the dense layers, the DSpark draft layers and the vision encoder: 81.6 GiB per rank, measured. The two Engram tables — 384 million rows of 256 FP8 values plus per-32 E8M0 scales each, one table at layer 1 and one at layer 14 — add 203 GB. Stock vLLM loads them into pinned host memory and reads them over UVA. On a 121 GiB unified-memory node, that cannot fit.
The tables never load. The loader patch skips the two Engram tensors; the model reads rows on demand with preadv from the safetensors shards, 32 threads, and dequantizes on the CPU. Each rank only ever needs the rows of its 6 of 24 hash columns, so each rank's working set is a quarter of each table.
Rows are staged before the forward, not inside it. Eager mode was tonyd2wild's throughput ceiling at ~200 ms per step: a host-side lookup in the middle of the forward defeats CUDA graphs. The patch moves the lookup into prepare_inputs — one GPU hash, one host sync, parallel reads into a persistent buffer — and the whole decode step is then one captured graph. That single change took their decode from 20 to 40 tok/s.
Node-local rows. Reading rows over NFS costs 6–8 ms per step on the workers against 3 on the node that holds the model, and every TP step waits for the slowest rank. tools/engram_local.py writes a sparse copy of one rank's rows to local NVMe at the original byte offsets (47.2 GiB per rank, verified row by row); DSV41_ENGRAM_DIR points the reader at it when the copied range covers the rank and falls back to NFS otherwise.
Memory left resident: a staging buffer sized for the prefill chunk — about 13 MB per layer at 8K tokens per step — and whatever the page cache keeps of the local row files. Nothing else.
What had to be fixed for GB10
SM12x page sizes (Kai). FlashInfer's SM120 sparse-MLA kernels exist for one compressed page size; the sliding-window cache gets a 64-token backend and the indexer cache holds 64 states per page (64 tokens at ratio 1, 128 at ratio 2) because DeepGEMM asserts block_kv == 32 or 64. --block-size 128 is required; vLLM otherwise picks the smallest listed size and the indexer backend refuses it at KV init.
Top-k on GB10. The persistent_topk kernel oversubscribes the 48 SMs on long rows and its fallback wants 128 KB of shared memory per block; GB10 has 99 KB. The patch routes decode through top_k_per_row_decode, which is also 1.6–3.6× faster there. This is the same shared-memory ceiling that stopped our single-GPU attention tests until we cut the head count to 16.
No runtime JIT storms. A FlashInfer MXFP8 GEMM compiling at first use with 22 parallel jobs exhausted host memory on all four nodes at once and the watchdogs reset them. The kernels are prebuilt in the image and the launcher sets MAX_JOBS=2 so anything that still compiles cannot take a node down. One small module (trtllm_utils) still compiles once per fresh cache directory; it takes about a minute.
Exact CUDA-graph batch sizes. Capture sizes are every multiple of k and k+1 up to max_num_seqs × (k+1) so a DSpark decode batch is never padded; padded batches can hang the SM120 sparse-MLA kernel (FlashInfer #5015). Adaptive verification stays off for the same reason.
Three changes of ours
Rebuild vLLM's CUDA extensions for sm_121a — what the reference build does
The V4.1 branch adds C++ ops (fused q-norm/RoPE/KV insert, cooperative top-k) to _C_stable_libtorch, so the nightly merge-base image lacks them and the whole stable extension has to be compiled on a Spark: CUTLASS fetch, cmake, hours of nvcc at -j 20.
Take the extensions from the official day-0 arm64 image — shipped
vLLM published vllm/vllm-openai:deepseekv41-flash-0909-arm64 for the release. Its _C_stable_libtorch and _moe_C_stable_libtorch are built against the stable libtorch ABI, so they are torch-version independent by design. Copied into the nightly-based image next to the branch's python tree they load on GB10, carry every fused_deepseek_v4 op, and silu_and_mul matches the reference exactly. Tested on the GPU before the image was built; the build itself then takes seconds.
B12X MoE kernels. The b12x SM120/SM121 CuTe DSL kernel library is on PyPI as a pure-python wheel and its dependencies already sit in the image, so pip install b12x==1.3.0 is the whole overlay. With --moe-backend b12x vLLM selects B12X_MXFP4_MXFP8 experts (MXFP4 weights, MXFP8 activations, the clamped SwiGLU limit passed through). Decode went from 81–85 to 89–93 tok/s on counting, 64 to 71 on code; prefill at 9K from 1,932 to 2,362 tok/s. The kernels JIT on the first boot (a few minutes) and cache under B12X_CACHE_DIR.
An explicit KV budget, with the host watched.--gpu-memory-utilization 0.80 gave 4.1–6.7 GiB of KV depending on the rank, and the pool follows the smallest. --kv-cache-memory-bytes makes every rank identical. 10 GiB measured 1.58M tokens and passed a 20-minute 8-way soak, but MemAvailable on the head sat at 2 GB, and earlyoom on these nodes fires at 2% of RAM (~2.4 GB). 8 GiB gives 1.26M tokens and 9 GB of headroom. That is the shipped value.
Weights over NFS, rows on NVMe. One node keeps the 510 GB checkpoint and exports the whole HuggingFace repo directory (so the snapshot's ../../blobs symlinks resolve inside the container); the others mount it with _netdev,nofail in fstab. A rank loads its 81.6 GiB in about 7.5 minutes over the 200G fabric (341 s for the target plus a 100 s second pass for the draft layers). Only the Engram rows are copied locally, because only they are read at every step.
The reference implementation, and a GB10 kernel bug
Before the vLLM branch existed we had the model running on the same four nodes from DeepSeek's own inference/ reference (torch + TileLang, MP=4 over NCCL) with an NVMe-backed Engram of our own: O_DIRECT 4 KB block reads, a decoded-row cache and a prefetch hook. It answered correctly one token at a time and produced garbage — often a stray beginning-of-sequence token followed by an unrelated document — whenever it prefilled a prompt in one pass. Sixteen hours of bisection ended in a single-GPU harness that runs every TileLang kernel against a PyTorch reference over row counts from 1 to 3,288.
With that fix the reference matched its stepwise path on every prompt, found needles at 3.3K and 9.8K tokens and answered a two-image question correctly — at 8 tok/s decode and 450–560 tok/s prefill, because its decode loop syncs on a bincount().tolist() per MoE layer and launches about two thousand kernels per token. It stays in the tree as the correctness oracle; the vLLM stack is what serves.
DCP2 — the port (production-1.1, 2026-09-11)
Decode-context parallelism was not implemented for V4.1's attention in this branch: the DCP code lives in the older MLA backends and the indexer, not in the sparse sliding-window plus compressed-KV path the model uses on SM12. We ported it in one night, in four stages, each validated on the CPU with Triton's interpreter before it touched the fleet, then in six 40-minute production windows. Production runs on it now (PROFILE=dcp2 in the launcher).
Stage
What
The subtle part
1 · KV groups
a dcp_replicated flag on attention specs: the 128-token sliding-window caches and the compressor ring stay whole on every rank; the ratio-1/ratio-2 compressed caches and the indexer K cache are sharded over compressed states
the block-table kernel must round-robin only the sharded groups; replicated ones keep DCP1 geometry, block-table width and memory
2 · indexer
compressed slot mapping in state coordinates; decode bounds compressed first, localized second; the layer-20 candidate blocks scored on local blocks of 4, MAX all-reduced, pinned on the global newest block
localize(len // 2) is not localize(len) // 2 (for lengths ≡ 2 mod 4 rank 0 loses a state); the reduce is sliced to a 128 MiB scratch — the 1 GiB version got a rank killed by earlyoom at 128K
3 · attention
query all-gather to 32 heads, FlashInfer's LSE-returning SM120 entry (base-2 LSE, sink folded in, -1e30 for empty rows), sink and window attended on DCP rank 0 only, one all-to-all combine, heads reduce-scattered back
with top-k attention the window would be counted twice unless one rank owns it; the whole path is captured inside the FULL decode graphs, so the collectives cost ≈ 3 ms per 50 ms step, not the CPU
4 · DSpark draft
the draft layers are window-only and bypass DCP — but the draft-input kernel mapped their replicated cache with the process-wide DCP formula
half of the draft's context KV went to PAD: text stayed coherent (row 0 is the target's), acceptance fell from 3.2 to 1.1–1.9 (position-0 acceptance 7–26 %). One per-group parameter fixed it
Correctness gate: teacher-forced scoring of 40 DCP1 completions under DCP2 — mean logprob −0.109, argmax agreement 97.6 % over 8,345 tokens — identical to production scoring its own outputs (−0.112 / 97.5 %). Greedy prefixes are not a valid metric here: b12x MoE is not batch-invariant, so near-tie tokens flip either way.
DCP1 (production-1.0)
DCP2 (production-1.1)
KV pool at 300K context
1,263,118 tokens · 4.21×
1,888,800 tokens · 6.30×
decode, single stream (counting / code / prose)
92.3 / 64.8 / 32.8 tok/s
87.4 / 60.8 / 29.9 tok/s
DSpark acceptance (same bench)
3.2
3.2
prefill 32K / 128K
≈ 2.1K / 1.9K tok/s (16K chunks)
1.5K / 1.5K tok/s (8K chunks)
needles 32K + 128K
found
found
8-way stability, 12 min + bursts
540 req / 0 err (20 min)
283 req + 16 burst / 0 err / 0 hangs
MemAvailable per node
9–12 GB
8–13 GB
Same nodes, same image (production-1.0 plus 21 bind-mounted patch files), 2026-09-11 02:00–05:30. DCP2 runs --decode-context-parallel-size 2 --dcp-comm-backend ag_rs, an all-to-all LSE combine, 8K prefill chunks and an explicit 6 GiB KV budget per rank; the smaller chunk is what lets the replicated window groups reserve fewer blocks per request, which is where most of the pool gain comes from.
04Performance
Measured 2026-09-10 (1.0) · 2026-09-11 (DCP2 1.1 / 1.2). Four-node TP4 · DSpark k=5 · FULL_AND_PIECEWISE graphs · b12x MoE · explicit 8 GiB KV per rank · Engram rows node-local · images and tools on. Decode and TTFT are the tonyd2wild fixed prompt set (streaming, temperature 0, thinking off, tokens from the usage block); prefill is unique source-code prompts with one output token; the KV pool is the engine's own log line. Image: aidendle94/sparkrun-vllm-dsv41-gb10:production-1.1.
RoCE decode (1.2 image)95 / 75 / 36tok/scounting / code / prose after the b12x one-shot RDMA collectives (2026-09-16); same-day NCCL 86 / 50 / 26; a 48 KB TP all-reduce 100 → 21 µs in graph replay
Single-stream decode by prompt categorytok/s · higher is better
counting (ceiling)94.5
formatted output87.9
math81.1
codingshipped77.5
JSON62.8
reasoning60.4
prose34.5
narrative33.2
summary31.5
DSpark acceptance drives the spread: near six draft tokens per step on code and tables, near two on prose.
Throughput by concurrency — mean of eight categoriestok/s · higher is better
per streamaggregate
C158.652.8-10%
C246.682.8+78%
C341.11072.6×
C443.11473.4×
C538.71634.2×
C635.71855.2×
C streams released together, one batch per cell; aggregate over the batch wall time, TTFT included.
Prefill by prompt size — DeepGEMM vs B12X MoE kernelstok/s · higher is better
DeepGEMM_MXFP4 (vLLM default)b12x (shipped)
9.3K tokens1,9322,362+22%
35.6K tokens2,0912,143+2%
131.6K tokens1,9081,940+2%
Same boot settings, unique source-code prompts, one output token. Within ±10% run-to-run swing at the long sizes.
KV pool by configurationtokens · higher is better
gpu-memory-utilization 0.80 (auto)1,039,860
explicit 10 GiB per rank1,578,913
explicit 8 GiB per rank (production-1.0)1,263,118
DCP2, explicit 6 GiB per rank (production-1.1 / 1.2)1,888,800
DCP4, explicit 6 GiB per rank, 300K max-model-len (2026-09-11)2,477,126
DCP4, 6 GiB per rank, 500K max-model-len (production since 2026-09-14)shipped3,418,145
The auto pool follows the rank with the least free memory (883,925 on a later boot); explicit sizes are identical on every rank. Under DCP the compressed and indexer caches are sharded over the group while the 128-token window caches stay replicated, which is why DCP2 gains 50 % and DCP4 a further 31 %.
DCP1 (1.0) vs DCP2 (1.1) — single stream, same promptstok/s · higher is better
Measured 2026-09-11 within an hour of each other; KV pool 1.26M → 1.89M tokens. DSpark acceptance 3.2 on both.
DCP2 1.1 vs 1.2 — the performance stage, same promptstok/s · higher is better
DCP2 1.1 (05:27)DCP2 1.2 (08:00)
counting85.593.6+9%
code61.564.4+5%
prose30.431.5+4%
prefill 32K (÷100)15.420.9+36%
prefill 128K (÷100)15.021.5+43%
Memory-mapped Engram gather with a readahead prepass (decode), next-chunk Engram prefetch and compressed-KV-gather attention (prefill). Teacher-forced agreement −0.112 / 97.4 %, at the DCP1 floor.
Prefill budget per 8K chunk — measured components, 1.1 vs 1.2ms per chunk · lower is better
DCP2 1.1DCP2 1.2
attention q-gather4446-99%
attention kernel325298-8%
attention LSE combine4720
indexer223233+4%
Engram staging (before the step)20094-53%
CUDA-event timers around each phase of the eager prefill step, rank 0, 22–30 chunks of ~8K tokens at 32K–128K context. MoE (0.85 s), TP all-reduce (0.64 s true cost plus waiting for the slowest rank) and other dense work (0.9 s) make up the rest of the 3.6 s chunk.
Head-node MemAvailable at idle, by KV sizeGB · higher is safer; earlyoom fires at ~2.4 GB
explicit 10 GiB2.8
explicit 8 GiB (shipped)shipped9.0
A 20-minute 8-way soak passed at 10 GiB with the head half a gigabyte above the kill line. 8 GiB costs 20% of the pool and buys 4× the margin.
Across recipes
Single-stream decode, by recipetok/s · higher is better
DeepSeek-V4.1-Flash · SGLang (1.2)94.1
DeepSeek-V4.1-Flash (1.1, DCP2)shipped60.8
GLM-5.3-Flash (1.2)89.7
DeepSeek-V4-Flash (3.76.1)71.0
DeepSeek-V4-Flash-Vision (3.73)67.9
Different prompt sets, all single stream at temperature 0 with completion_tokens accounting: exact within a recipe, indicative across them. DeepSeek-V4.1-Flash · SGLang (1.2): code prompts, DSpark block 5, RoCE collectives, early Engram staging; 110 counting, 37 prose · DeepSeek-V4.1-Flash (1.1, DCP2): code prompts, DSpark k=5, b12x MoE, DCP2 (77.5 on 1.0 with a different prompt set) · GLM-5.3-Flash (1.2): structured output, thinking off · DeepSeek-V4-Flash (3.76.1): mixed prompts, 8-rep mean · DeepSeek-V4-Flash-Vision (3.73): code prompts, DCP2 + DSpark
Cold prefill, by recipetok/s · unique content · higher is better
DeepSeek-V4.1-Flash · SGLang (1.5)4,113
DeepSeek-V4.1-Flash (1.0)shipped2,143
GLM-5.3-Flash (1.2)2,070
DeepSeek-V4-Flash (3.76.1)2,200
DeepSeek-V4-Flash-Vision (3.73)2,380
Cold means the prompt has never been seen by the server; with prefix caching on, a repeated prompt is a cache hit and reads 3–25× higher. DeepSeek-V4.1-Flash · SGLang (1.5): 32K depth, unique random words (2,913 at 128K) · DeepSeek-V4.1-Flash (1.0): 36K depth (1,940 at 132K) · GLM-5.3-Flash (1.2): 32K depth · DeepSeek-V4-Flash (3.76.1): 334K depth · DeepSeek-V4-Flash-Vision (3.73): 92K depth (2,088 at 334K)
Four boots on the same nodes on 2026-09-10. The KV pool is one pool across the four ranks and is set by the rank with the least free memory unless the size is given explicitly. Prefill swings ±10% run to run on GB10, so 2,362 vs 2,144 at 9K is the same kernel on a different minute.
DCP2 — the performance stage (production-1.2, 2026-09-11)
With DCP2 correct, we instrumented it: a per-step Engram staging timer, then CUDA-event timers around every phase of an eager prefill step (attention gather / kernel / combine, indexer, MoE, TP all-reduce, Engram forward). Five more 40-minute windows in the same night, each judged by measured components rather than time-to-first-token, because the MoE/dense part of a prefill chunk swings ±10–24 % between boots on GB10. Three changes came out of it; all are opt-in environment variables on the same image, all validated with teacher-forced scoring at the DCP1 floor and needles at 32K and 128K.
Change
What we measured
What it does
Engram row gather · memory map
the disk-staging bubble between two decode steps was 6–8 ms on every rank — 72 rows per step, one NVMe latency each, serialized; a prefill chunk asks for 60–90K unique rows
rows come through a read-only mmap with MADV_RANDOM (the first version without it doubled prefill: every fault pulled a readahead window off the sparse local file), a fancy-index gather per table with the GIL released; small gathers issue posix_fadvise(WILLNEED) for every row first so all reads are in flight together, then gather inline. Decode bubble 6.6 → 1.2–1.9 ms/step; 128K prefill 86.7 → 71.5 s on its own
Next-chunk Engram prefetch
the Engram staging before an 8K prefill chunk cost 0.20 s on the calling thread while the GPU idled
the prompt is known, so each step hashes the next chunk of the longest still-prefilling request with the same kernel and lookback window, maps the hashes to this rank's rows and warms them in the page cache while the current chunk computes. Staging 0.20 → 0.09 s per chunk; misses only cost bandwidth
DCP prefill by compressed-KV gather
per 8K chunk the query all-gather (0.44 s) and LSE combine (0.46 s) cost more than the attention kernel itself (0.33 s): 152 collectives of 38 MB plus 152 of 67 MB saturate the 25 GB/s pair
the compressed KV of a 128K context is only ~7K states, a few MB per layer: each rank gathers the other rank's compressed pages once per kv-source cache and step, the indexer's global top-k ids are remapped into the gathered buffer by inverting the sharding formula, and the DCP1 kernel runs — every rank attends its own 16 heads over the full context with its own sink and the replicated window. Attention per chunk 1.21 → 0.30 s (gather 6 ms, combine 0)
Ruled out
DSpark k=3 / k=4 (61 / 72 tok/s on counting vs 86; prose alone gains), 16K prefill chunks under DCP2 (pool 1.24M, 4 GB free on two nodes during a 128K prefill, needle 92 s vs 87 s)
k stays 5, chunks stay 8K
All measured on 2026-09-11 06:00–08:15 in production-settings windows. The per-row Engram cost was not NFS: the launcher already mounts a node-local sparse copy of each rank's rows; it was per-row latency serialized by a Python-per-row gather.
Per 8K-token prefill chunk (rank 0, 32K–128K context)
DCP2 1.1
DCP2 1.2
forward
4.15 s
3.63 s
attention — q gather / kernel / LSE combine
1.21 s — 0.44 / 0.33 / 0.46
0.30 s — 0.006 / 0.30 / 0
indexer (incl. candidate selection)
0.20 s
0.23 s
MoE (b12x FP4 grouped GEMM)
inside "rest" 2.73 s
0.85 s
TP all-reduce (80 × 84 MB per chunk)
inside "rest"
1.23 s on ranks 0–2, 0.64 s on rank 3 — the difference is waiting for the slowest rank
Engram forward + other dense work
inside "rest"
0.13 + 0.88 s
Engram staging before the step
0.20 s
0.09 s
decode: Engram staging per step
6.1–8.0 ms
1.2–1.9 ms
The split of "rest" only exists in 1.2 (the finer timers came with it). Rank 3 is the unit capped at 2,151 MHz: its MoE and dense phases run ~20 % longer and the other three ranks absorb the gap inside the all-reduce — about 0.5 s of every chunk, i.e. ~15 % of prefill time attributable to one clock cap.
DCP2 1.1 (05:27)
DCP2 1.2 (window 10, 08:00)
decode, single stream (counting / code / prose)
85.5 / 61.5 / 30.4 tok/s @ acceptance 3.34
93.6 / 64.4 / 31.5 tok/s @ 3.24
prefill 32K
21.1 s · 1,537 tok/s
15.5 s · 2,094 tok/s
prefill 128K
86.7 s · 1,502 tok/s
60.5 s · 2,152 tok/s
teacher-forced vs DCP1 outputs (8,345 tokens)
−0.109 / 97.6 %
−0.112 / 97.4 %
needles 32K + 128K
found
found
KV pool
1,888,800 tokens
1,888,800 tokens
DCP2 now beats the DCP1 production-1.0 numbers on decode (92 / 65 / 33) and on prefill (2.1K / 1.9K tok/s) while holding the 50 % larger KV pool. Roll-out: the memory-mapped gather went to production at 07:46; the prefetch and the KV-gather prefill passed the 8-way 12-minute soak (0 errors, 0 failed checks, 0 failed health probes) and went live at 08:58. Production re-verified right after: decode 90.7 / 63.6 / 34.4 tok/s, 32K needle in 14.9 s, KV pool 1,888,800, 9–12 GB free per node, watchdog armed.
DCP4 — measured (2026-09-11 09:06–09:50)
With the port written for any group size, DCP4 is the same code with the sharded caches quartered instead of halved: 16 local heads gather to 64 for the decode kernel, the candidate blocks split four ways, the compressed-KV gather collects three remote page sets instead of one. The replicated 128-token window caches and the compressor ring do not shrink, which is why the pool does not double. One window, same production configuration plus DCP_SIZE=4, full gate including the 8-way 12-minute soak.
DCP2 (production-1.2, 08:58)
DCP4 (production since 09:47)
KV pool (300K max-model-len; 3,418,145 = 6.84× at the 500K setting since 2026-09-14)
1,888,800 tokens · 6.30×
2,477,126 tokens · 8.26×
decode, single stream (counting / code / prose)
90.7 / 63.6 / 34.4 tok/s @ 3.35
90.7 / 63.5 / 31.3 tok/s @ 3.36
prefill 32K / 128K
15.3 s / 61.0 s
17.2 s / 67.6 s
per-chunk attention / indexer (the parts DCP width touches)
0.30 s / 0.23 s
0.29 s / 0.24 s
teacher-forced vs DCP1 outputs
−0.111 / 97.4 %
−0.108 / 97.6 %
8-way 12-min soak
0 err / 0 failed checks / 0 health failures
0 / 0 / 0
MemAvailable during the probe
head 9, workers 12 GB
head 8, workers 12 GB
DCP4 costs nothing on decode and buys 31 % more KV pool. Its 8–11 % slower prefill in this window sits entirely in the "other dense work" and all-reduce-wait buckets, which swung ±10–24 % between two identical DCP2 boots the same night, while the DCP-sensitive components are flat — so it is not established as a real cost. Then the post-window restore booted DCP4 by accident — the window script inherited the dev DCP_SIZE — and a 32K needle on that production boot came in at 15.4 s, level with DCP2's 14.9 s, which settled it: the window's gap was boot variance. DCP4 stays in production (launcher default DCP_SIZE=4; 2 returns to DCP2).
NVFP4 Engram variant — measured (2026-09-16)
The two Engram tables re-quantized to NVFP4 (e2m1 values packed two per byte, fp8 e4m3 scales per 16, one global scale per table — the ModelOpt convention), everything else byte-identical: 203 GB of tables become 111 GB. Rounding is GPTQ with the Engram output projection as the Hessian proxy (rows are consumed only through wkv, so the error that matters is ‖W_c e‖ per hash column) and a per-block MSE scale search. Served on the production configuration with the variant's rows read from the checkpoint over NFS on three nodes (no node-local copies yet).
fp8 Engram (production)
NVFP4, round-to-nearest (first upload)
NVFP4, GPTQ + MSE scale (current)
tables on disk
2 × 101.4 GB
2 × 55.5 GB
2 × 55.5 GB
raw relative RMS error vs fp8 rows (layer 1 / 14)
—
9.49 % / 9.64 %
8.99 % / 9.06 %
error through wkv (sampled rows, layer 1)
—
8.96 %
8.23 %
teacher-forced vs the fp8 model's completions (8,345 tokens)
−0.112 / 97.5 % (self)
−0.110 / 97.5 %
−0.111 / 97.5 %
needles 32K / 128K
found / found
found / found
found / found
tool call · thinking · 4-way soak
pass
pass
pass
decode counting / code / prose
90.7 / 63.5 / 31.3
86.8 / 58.8 / 29.9 (rows over NFS)
87.3 / 66.4 / 27.1 (rows over NFS)
KV pool
3,418,145
3,418,145
3,418,145
Agreement with the fp8 model sits at the fp8 model's own batch-noise floor for both roundings, so the row-level error does not reach the next-token distribution. The scale search took most of the raw-error gain (9.49 → 8.89 % on a sample), GPTQ the rest; the Gram matrices of wkv per hash column are nearly isotropic (median max/min diagonal 1.2), which is why error feedback helps less here than on transformer weights. What remains is the e2m1 grid. Repo: aidendle94/DeepSeek-V4.1-Flash-NVFP4-Engram (public); tools in the recipe repository (tools/engram_nvfp4.py, studies, upload).
RoCE one-shot collectives — measured (production-1.2, 2026-09-16)
Every decode step runs about eighty tensor-parallel all-reduces of 48–400 KB (6–48 tokens × 4096 × bf16) plus the logits all-gather, and on a four-node ring each one is a NCCL kernel with its own launch and rendezvous. The b12x RoCEnante runtime (local-inference-lab, Jason @original-el8 and Luke Alonso; the vLLM adapter from local-inference-lab/vllm#597 as carried by tonyd2wild's 2026-09-14 speed run) replaces those with one RDMA write per peer into pinned host memory over both ConnectX-7 functions, striped, and replays inside the CUDA graph. Only the TP group takes it; the DCP and expert-parallel collectives and the 64 MB prefill all-reduces stay on NCCL. One fleet latency test with vLLM stopped, then one serving window that booted straight onto the production port and kept the boot when the full gate passed.
all-reduce (4 ranks, bf16, graph replay)
NCCL
RoCE, both HCAs
RoCE, one HCA
8 KB
86.8 µs
11.7 µs
12.6 µs
48 KB (one 6-token step)
100.1 µs
21.3 µs
26.8 µs
192 KB
116.4 µs
45.9 µs
63.3 µs
480 KB (eight 6-token streams)
140.8 µs
88.0 µs
132.1 µs
1 MB
196.8 µs
167.9 µs
264.9 µs
2 MB
265.8 µs
316.2 µs
514.6 µs
logits all-gather [6, 32320]
111.2 µs (eager, incl. reshape)
78.7 µs
112.8 µs
Median per rank, then the slowest rank, 300 samples per cell; all rows bit-identical across ranks (the upstream test compared Python hash() digests, which are salted per process — fixed to sha256), bf16 error against an fp32 NCCL reduction ≤ 0.36 %, gathers bit-exact. The crossover sits near 1.2 MB, so the adapter routes up to 1 MB. One rank keeps its RoCE-v2 GID at index 4 where the other three have it at 3; the launcher now reads the index per node, because a fixed index fails every queue-pair connect and the adapter falls back to NCCL without stopping the boot.
61 / 71 / 63 ms per step at 5.97 / 4.27 / 2.06 tokens per step
teacher-forced vs DCP1 outputs
−0.111 / 97.5 %
−0.111 / 97.6 %
needles 32K / 128K
15.3 s / 61–78 s
15.3 s / 78.1 s
8-way 6-min soak
0 err / 0 failed checks / 0 health failures
KV pool
3,418,145
3,418,145
The tok/s gain is partly the collectives and partly DSpark acceptance luck on the bench prompts (3.10 vs 3.56), so the microbenchmark row is the honest number: the decode-sized all-reduces cost 2–5× less per call. Prefill is unchanged by construction. Rollback is one launcher default (ROCE_AR=0); the NCCL path is untouched.
Stability under concurrency
Phase
Load
Result
Ramp
the fixed prompt set at C1 → C6
counting aggregate 87 → 384 tok/s, prose 33 → 99; TTFT 0.21–0.27 s at every level
Sustained
8 workers × 20 min, mixed pool (counting, code, prose, 8K code summaries, 13K needles), every request uniquely tagged
540 requests, 0 errors, 0 hangs, 0 failed output checks; latency p50 16.9 s, p95 32.9 s under full load
Burst
8 × 13.3K-token needles released together
37.6 s wall, all answers correct, ~2.8K tok/s aggregate prefill
Burst
8 × counting released together
3.2 s, all exact
Monitoring
health probe + MemAvailable on all four nodes every 30 s, watchdog log
47/47 healthy, no watchdog events — and the head at 2 GB free for the whole run at the 10 GiB KV setting, which is why 8 GiB ships
Config B (10 GiB KV) under test; the memory finding produced config C. Latencies are end to end for requests that share the engine with seven others, including 13K-token prefills.
Configuration history · production-1.0 → 1.2, DCP4, 500K
Boot
Change
Outcome
1
tonyd2wild boot-10 config: gmu 0.80, DeepGEMM MoE, 8K chunks, Engram over NFS on three ranks
served first try, 704 s to healthy; count 84 tok/s, needles 32K/128K, vision + tools 7/7; KV 1,039,860
2
node-local Engram rows on every rank
same speed on our fabric (NFS from the head's page cache was already fast); no NFS dependency at runtime
A
explicit 10 GiB KV + 16K prefill chunks
KV 1,578,913; prefill +10% at 36K; decode unchanged
B
--moe-backend b12x
decode +9%, prefill at 9K +22%; passes count / corruption / needle / vision / tools; 8-way soak clean — head at 2 GB free
C
8 GiB KV
KV 1,263,118 (4.2× at 300K), head 9 GB free, speed as B — shipped as production-1.0
DCP2 performance stage (2026-09-11): memory-mapped Engram gather with readahead prepass, next-chunk Engram prefetch, prefill attention by compressed-KV gather; image production-1.1 = the 23-file set baked in
decode 94 / 64 / 32; 128K prefill 60.5 s (2,150 tok/s); teacher-forced at the floor; pool unchanged; 8-way soak clean — memmap gather live 07:46, prefetch + KV-gather live 08:58
500K
`--max-model-len 500000` (2026-09-14) on DCP4
KV pool 3,418,145 tokens = 6.84 concurrent 500K contexts (the coordinator gives the sharded caches a larger share as the per-request window reservation stops dominating); 490K needle found in 310 s; 32K needle 14.2 s; head ≥ 6 GB free during the 490K prefill
teacher-forced −0.111 / 97.5 % (floor), needles found, raw row error 8.99 / 9.06 %; tables 203 → 111 GB; published on HuggingFace — validated, production stays on fp8
RoCE
b12x RoCEnante one-shot RDMA all-reduce/all-gather for the TP group (2026-09-16, community port; image production-1.2 = the 28-file set incl. the adapter baked in, `ROCE_AR=1`)
decode 94.5 / 74.5 / 35.9 @ 3.56 (NCCL same day 85.6 / 49.9 / 25.8 @ 3.10); 48 KB all-reduce 100 → 21 µs in graph replay; gates at the floor; 8-way soak clean — in production since 23:21
DCP4
`DCP_SIZE=4` on the 1.2 stack (2026-09-11 09:06)
KV 2,477,126 (8.3× at 300K); decode unchanged; window prefill 17.2 s / 67.6 s at 32K / 128K vs DCP2 15.3 / 61.0, production 32K needle 15.4 s (parity); full gate incl. 8-way soak clean — in production since 09:47
Boots 1–C on 2026-09-10, 1.1 and 1.2 on 2026-09-11, RoCE on 2026-09-16, all on the same nodes. Every figure comes from the live four-node cluster with unique-content prompts and completion_tokens accounting; the Performance tab has the charts.
05Notes
Operational caveats
GB10 has a slow state.Step time on this model swings up to 1.5× between a fast and a slow GPU state that nvidia-smi does not show (tonyd2wild's issue #1, reproducible with a plain PyTorch GEMV). Our first request after a 30-minute idle prefilled at 1,656 tok/s; the next at 2,362. Benchmark warm and back to back, and compare configurations on the same minute, not the same day.
The slowest rank paces every step.One of our four units is clock-capped at 2,151 MHz against ~2,500 on the others, and every TP step waits for it. Check clocks under load on all nodes before chasing kernels.
Watch MemAvailable, not the KV number.The explicit KV size is the right tool, and it is also how you walk into earlyoom without an error message. Read free -g on every node after any change; keep the head above 5 GB.
Weights via NFS need fstab.Two of tonyd2wild's workers lost a manual mount after a watchdog reset and would not have come back. _netdev,nofail in fstab, and a preflight that checks the last shard exists before launching.
Adaptive verification stays off; 1M is untested on this stack.Padded speculative batches can hang the SM120 sparse-MLA kernel. The 1M-token boot upstream was eager, before the top-k fix; on this stack 500K is what has been exercised (a 490K needle: 487,264 prompt tokens, found, 310 s to first token, head at 6–8 GB free throughout). A prefill that long also starves any liveness probe that expects a completion within seconds — our watchdog now treats a slow request probe with a passing /health as busy for up to 10 minutes before counting it.
Copy Engram rows while serving only with a rate limit.The copy tool drops page cache as it goes and we ran it at 150 MB/s under live traffic with a memory guard; MemAvailable never moved. At full disk speed on a unified-memory box it would.
Kill by PID, never by pattern.A pkill -f whose pattern matches the invoking shell kills the shell.