BertholomusAI recipe: DeepSeek-V4.1-Flash on 2x NVIDIA DGX Spark (GB10), our own TensorFold TP2 engine. Weights: Mia-AiLab EXL3 2.9bpw (MIT).
Python
39
25 commits
updated Oct 17, 2026
Bertholomus AI recipe: DeepSeek-V4.1-Flash served tensor-parallel over two NVIDIA DGX Spark / GB10 nodes by the model family we wrote for TensorFold (by ashhart).
Weights: Mia-AiLab/DeepSeek-V4.1-Flash-EXL3-2.9bpw (MIT), an EXL3 quant by Mia-AiLab of deepseek-ai/DeepSeek-V4.1-Flash (MIT, © 2023 DeepSeek). We did not make or modify these weights; this repository holds no weights. The Engram tables are read at run time from DeepSeek's original shards 47 and 48.
Engine (v0.6.0 on): bertholomus/TensorFold, branch deepseek-v41-zig, Bertholomus AI's dsv41
family on TensorFold 1.0.2's native Zig engine. Attribution: tools/dsv41-zig/ATTRIBUTION.md.
The Python engine of v0.1.0-v0.5.1 stays on branch deepseek-v41-tp2
(design, report and attribution in tools/dsv41/).
Clean-room: the model math is re-implemented from DeepSeek's MIT inference code and tech report; no code from other
DeepSeek-V4.1 recipes or kits was read or copied.
The kit the Zig engine loads is now in this repository's kit/, byte for byte the kit our served lane loads (engine
ce20a545, served 65f295ec), with MANIFEST, KIT-LICENSES.md and kit/tools/verify_kit.sh. The engine branch
gains the builders (5e10970a: tools/dsv41-zig/build_kit.sh, build_inputs.sh); its code is unchanged. Our served
lane started from this kit in a fresh folder gives the same replies as before on every gate (images 9/9 and 18/18,
51 image probes, 14 prompt lengths).
Engine ce20a545 on branch deepseek-v41-zig, on upstream TensorFold v1.0.2. The whole dsv41 family (prompt
and decode paths, DSpark drafting, Engram, the RDMA ring between the two nodes, the vision tower) now runs in TensorFold
1.0's native server, with no Python in the serving path. It is what we serve now: --context 1048576, --parallel 4,
images on.
TF_DS_REPLAY_FLOOR=1 (on in the served configuration). A last prompt chunk of 16 rows or fewer
at the bounded replay's cut (prompts of 2048k+112 to 2048k+127 tokens, or a short last chunk) read part of its
attention window from the previous request's keys, so its reply could depend on what ran before it (v0.5.1 does
this too). Now it reads from the cut as a larger chunk would: 14 such prompt lengths give the same reply on every
pass. 2 of the 51 image probes (both in that class) change their reply.Measured on two GB10 nodes (TP2), engine ce20a545 (code = served commit 65f295ec) at the served settings below, our own
client (kit_bench.py of the deepseek-v41-tp2 branch), greedy, DSpark drafting, median of 3 unless noted. The
speed rows ran before TF_DS_REPLAY_FLOOR was switched on (it changes only prompts of the class above); the gates,
image checks and probes were run again live with it on: all pass.
v0.6.0, --context 1048576 --parallel 4 | tok/s |
|---|---|
| Decode, set a: code / prose / structured (512 tokens) | 89.8 / 59.0 / 106.6 |
| Decode, set b: code / prose / structured (384 tokens) | 103.1 / 64.1 / 143.2 |
| 2 streams, set b, aggregate | 83.1 |
| 4 streams, set b, aggregate (median of 9) | 118.9 |
| 4 streams sustained 90 s, set b, aggregate | 142.0 |
| Prompt 8K / 32K / 64K / 128K (one run each; 8K best of 2) | 2,433 / 2,495 / 2,461 / 2,354 |
| Decode after a 128K prompt (median of 3 seeded prompts) | 100.1 |
v0.5.1's engine (6a6cf68) measured in the same session, same client and prompts, on the same nodes:
v0.5.1, --context 1048576 --parallel 4 | tok/s |
|---|---|
| Decode, set a: code / prose / structured (512 tokens) | 89.4 / 57.7 / 107.2 |
| Decode, set b: code / prose / structured (384 tokens) | 101.5 / 63.4 / 143.4 |
| 2 streams, set b, aggregate | 79.8 |
| 4 streams, set b, aggregate (median of 9) | 113.0 |
| 4 streams sustained 90 s, set b, aggregate | 125.3 |
| Prompt 8K / 32K / 64K / 128K (one run each; 8K best of 2) | 2,387 / 2,421 / 2,363 / 2,242 |
| Decode after a 128K prompt (median of the same 3 seeded prompts) | 49.8 |
The two structured rows are inside v0.5.1's own run-to-run spread in that session (v0.5.1: 106.5-107.4 and 142.2-143.5 tok/s).
Engine 90b68e0 on top of v0.5's 808eb4a. Replies and speed rows are unchanged on our served configuration.
nvcr.io/nvidia/pytorch:26.07-py3 sets
TORCH_ALLOW_TF32_CUBLAS_OVERRIDE=1, which ran the prompt chunks' fp32 router, indexer and mHC GEMMs in TF32. That
gave other bits than our lane and the gates, and the loop in #6 at seed 3001. The engine now sets the switch to 0 at
start (it prints a line when it was 1) and refuses to start if fp32 GEMMs still run in TF32.TF_LOOP_GUARD=1 makes it the
server default; a request sets it with "loop_guard": true / false. Replies that do not loop are untouched. The
signal is from Capicua25x's loop detector (#9).Decode and prefill are what we worked on this time. The same release suite on the served lane for both builds
(v0.4 = engine bd0024d at --context 262144, v0.5 = 808eb4a at --context 1048576), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
--parallel 4. Needles found to 1M on this configuration;
four long streams together give the same replies as each one alone.Where it came from, all bit-identical: RoPE tables at the context length (cos/sin only) and one shared logits buffer
for the round graphs pay for the 1M window; exact pruned top-k for the long-context indexer, candidate-only reindexing
and tile skipping in deep decode; prompt chunks' grouped expert launches walk a work list built on the device; kept
prompts shrink to a boundary instead of being dropped. The engine is now on upstream TensorFold 0.6.6.
Details in the engine commit message and tools/dsv41/REPORT.md.
Engine d5d7bb3 on top of v0.4's bd0024d. The Engram read-ahead now claims its slot by index. Before, list.remove()
compared id arrays of other lengths and could stop the server on long, prefix-cached agent sessions (present since
v0.2). One file changed; replies and speed rows are unchanged. If you run v0.4, update to this.
Context window, v0.1.0–v0.4.1: the serve command below and our served lane run
--context 262144. Earlier text here said "window up to 1,048,576": that is the engine's limit, which one earlier build tested with a single needle, not the configuration these releases were measured and shipped with.
The same release suite, run on the served lane for both builds (v0.3 = engine bbaa6cd, v0.4 = bd0024d), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
Where it came from, all bit-identical and each behind a switch (set it to 0 for the old path):
TF_DS_L2_PREFETCH).tools/dsv41/REPORT.md.The same release suite, run on the served lane for both builds (v0.2 = engine 08cae28, v0.3 = bbaa6cd), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
Where it came from: less per-row overhead in the 1-row forward, all bit-identical. hc_post is fused into the next
hc_pre, the router and indexer row matmuls are smaller, the rotations and RoPE run in neighbouring kernels' epilogues,
and the indexer and compressor glue is fused. Kernels per 1-row forward: 2,387 → ~990. Every switch defaults on
(TF_DS_HC_FUSED, TF_DS_ROWMM2, TF_DS_ROUND_GLUE, TF_DS_ROT_*, TF_DS_INDEXER_FUSED, TF_DS_COMP_FUSED,
TF_DS_TRITON_PDL); set one to 0 to turn it off.
Same nodes, same public client (tools/dsv41/kit_bench.py), same prompts and same method as v0.1.0. Greedy decode,
DSpark drafting, served configuration below. Median of 3 unless noted.
tools/dsv41/quality_eval.py (subset digest 09c2e6cc43e275af).Where it came from: grouped EXL3 decode linears (884 → 350 launches a forward) and a fused grouped-expert decode path,
both bit-identical; Engram reads moved off the critical path; a lighter per-round rank check; NCCL over both RoCE ports
for prompt chunks; a faster weight-cache read and a shorter warm-up. Details in tools/dsv41/REPORT.md.
Single stream 512 tokens code / prose / structured 60.5 / 38.0 / 74.5 tok/s (set b 69.9 / 42.3 / 96.5); 4 streams 73.2–80.5 tok/s; prefill 1,247–1,365 tok/s at 8K–128K; decode after a 128K prompt 61.8 tok/s; served window 262,144 tokens (the engine accepts up to 1,048,576; an earlier build found one needle at 1,039,833 tokens); start to ready 65 s.
Build the deepseek-v41-zig branch with Zig 0.17.0 inside nvcr.io/nvidia/pytorch:26.07-py3 (CUDA nvcc, sm_121):
zig build install native -Dtarget=aarch64-linux-gnu -Doptimize=ReleaseSafe -Dnvcc=/usr/local/cuda/bin/nvcc -Dsm=121 --prefix <OUT>
That gives native/bin/tensorfold-native (the server, rank 0) and bin/tf-dsv41-lanes (rank 1, following rank 0);
bin/tensorfold is a test CLI, not the server. Both ranks run in the same
container image with the RoCE devices, --network host --ipc host --ulimit memlock=-1. They read a kernel kit
<KIT>: the family's Triton kernels as compiled binaries (aot/, cubins/), the RoPE tables, engram.json, and
vision/ (the tower's attention kernel from the container's PyTorch, and the image routing bias from DeepSeek's
original weights).
The kit is in this repository's kit/ folder (plain git, with MANIFEST and KIT-LICENSES.md).
Five of its files are generated, not committed: the four RoPE tables (136 MB each) and the image routing bias (43
small DeepSeek weight tensors). Make them and check every file against MANIFEST, inside the
nvcr.io/nvidia/pytorch:26.07-py3 container with the deepseek-v41-tp2 engine's src/ at hand:
kit/tools/verify_kit.sh --rope <MODEL_DIR> <deepseek-v41-tp2>/src --bias <ORIGINAL_CHECKPOINT_DIR>
Or download the complete kit, generated files included, from folder kit/ of
bertholomus/DeepSeek-V4.1-Flash-TensorFold-TP2-2xGB10
and run kit/tools/verify_kit.sh. Both copies hold the same bytes. To build the kit from source instead, the
engine branch has tools/dsv41-zig/build_kit.sh (the kit) and tools/dsv41-zig/build_inputs.sh (each node's rank
cache, token map and extension build); see their headers.
Start rank 1, then rank 0:
# rank 1
TF_DS_REPLAY_FLOOR=1 NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tf-dsv41-lanes <MODEL_DIR> <CACHE_DIR> 1 2 <HEAD_IP> 29620 <KIT> --engram <ENGRAM_DIR> --token-map <TOKEN_MAP> \
--pool 1048576 --rdma <HCA_PORT_0>,<HCA_PORT_1> --graphs 1 --side 1 --prefetch 1 --aio 1
# rank 0
TF_DS_REPLAY_FLOOR=1 TF_TP_WORLD=2 TF_TP_MASTER=<HEAD_IP> TF_TP_PORT=29620 TF_DS_RANK_CACHE=<CACHE_DIR> \
TF_DS_KIT=<KIT> TENSORFOLD_CUDA_KERNELS=<KIT>/aot TF_DS_ENGRAM=<ENGRAM_DIR> TF_DS_TOKEN_MAP=<TOKEN_MAP> \
TF_RDMA_DEVICES=<HCA_PORT_0>,<HCA_PORT_1> TF_DS_GRAPHS=1 TF_DS_HC_SIDE=1 TF_DS_L2_PREFETCH=1 TF_DS_ENGRAM_AIO=1 \
NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tensorfold-native serve <MODEL_DIR> --host 127.0.0.1 --port 18891 --name DeepSeek-V4.1-Flash-TF --api-key-file <KEY_FILE> \
--context 1048576 --parallel 4 --thinking --max-tokens 32768 --temperature 1.0 --top-p 0.95 --top-k 20 --backend cuda
TF_DS_REPLAY_FLOOR=1 goes to both ranks.TF_DS_STREAMS=N on rank 0 needs --streams N on rank 1, and rank 1's
--pool equals rank 0's --context. With different values the two ranks wait on each other (#25).python3 with Pillow in the container (the NVIDIA image has it).<CACHE_DIR> keeps each rank's weights in one file (written on the first start); <TOKEN_MAP> and the cache are
the ones the deepseek-v41-tp2 engine writes.TF_RDMA_GID_INDEX=<index> picks the RoCEv2 GID index the native TP2 ring opens (default 5, so existing setups
are unchanged; single-rail ASUS GX10 pairs want 3). Find the index with ibv_devinfo -v or show_gids, and set
it on both ranks. Both ranks must run the same engine build: the ring's peer description grew in the same change
(128 to 144 bytes), and a mixed pair cannot talk. At connect the ring logs gid_index= and the negotiated
path_mtu= per rail.Hosts and addresses are placeholders; substitute your own.
Two GB10 nodes with a direct RoCE link. On each node, a throwaway nvcr.io/nvidia/pytorch:26.07-py3 container with
the deepseek-v41-tp2 branch installed (pip install -e). Start rank 0 (head) first — it owns the TCPStore master
(comm.py: is_master = rank == 0) — then wait for its store port to LISTEN (default 29551) before starting rank 1
(worker). A fixed sleep races on a cold start: rank 0 dies with DistStoreError: Timed out … 1/2 clients joined and
rank 1 with NCCL error 6 at nccl.barrier():
TF_DS_REPLAY=1 TF_DS_PREFILL_CHUNK=2048 TF_DS_RANK_CACHE=<CACHE_DIR> TF_DS_RANK_CACHE_READERS=32 \
TF_DS_WARM_LENGTHS=1,17,33,131,514,1024,2113 \
NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tensorfold serve <MODEL_DIR> --tp 2 --rank R --master <HEAD_IP> --host 127.0.0.1 --port 18891 \
--context 1048576 --vision --parallel 4 --mtp-drafts 5
<MODEL_DIR>: the Mia-AiLab EXL3 checkpoint. Engram tables: TF_DS_ENGRAM=<ENGRAM_DIR> (DeepSeek's original shards
47/48), or a folder next to <MODEL_DIR> whose name contains "Engram".NCCL_IB_HCA lists both RoCE ports when both are cabled (prompt-chunk gathers 153 → 106 ms); one port works too.TF_DS_RANK_CACHE keeps each rank's weights in one file (~106 GB a rank, written on the first start).--context 1048576 is the measured, served window from v0.5 (v0.1.0–v0.4.1 ran 262144).TF_RDMA_DEVICES=<HCA_PORT_0>,<HCA_PORT_1> names the RoCE ports of the link between the two nodes when a node has
more active ports than that link (the RDMA gather needs the same device count on every rank). TF_API_KEY_FILE=<file> makes every route but /health require a key.--temperature sets the server-side default a client inherits when it omits the field. Greedy (0) makes long
agentic turns loop in reasoning_content and return empty content (finish_reason: "length"); omit it to keep the
engine default (1.0), and pin 0 only for the deterministic MMLU/GSM8K runs.python3 tools/dsv41/kit_bench.py --base http://127.0.0.1:18891 --model <name> decode|concurrent|sustained|prefill|depthpython3 tools/dsv41/quality_eval.py --base http://127.0.0.1:18891 --model <name> --mmlu <MMLU_TEST_PARQUET> \ --gsm8k <GSM8K_TEST_JSONL> --max-tokens-on 4096 (MMLU "all" test from cais/mmlu, GSM8K test from openai/grade-school-math)Hosts and addresses are placeholders; substitute your own.
deepseek_v41 TP2 family and
its Zig port dsv41, their kernels, the deployment and the measurements.Not affiliated with or endorsed by DeepSeek, NVIDIA, the TensorFold authors, Mia-AiLab or MiaAI-Lab.
Recipe documentation: Apache-2.0 (LICENSE). The weights stay under their own MIT license.
BertholomusAI recipe: DeepSeek-V4.1-Flash on 2x NVIDIA DGX Spark (GB10), our own TensorFold TP2 engine. Weights: Mia-AiLab EXL3 2.9bpw (MIT).
Python
39
25 commits
updated Oct 17, 2026
Bertholomus AI recipe: DeepSeek-V4.1-Flash served tensor-parallel over two NVIDIA DGX Spark / GB10 nodes by the model family we wrote for TensorFold (by ashhart).
Weights: Mia-AiLab/DeepSeek-V4.1-Flash-EXL3-2.9bpw (MIT), an EXL3 quant by Mia-AiLab of deepseek-ai/DeepSeek-V4.1-Flash (MIT, © 2023 DeepSeek). We did not make or modify these weights; this repository holds no weights. The Engram tables are read at run time from DeepSeek's original shards 47 and 48.
Engine (v0.6.0 on): bertholomus/TensorFold, branch deepseek-v41-zig, Bertholomus AI's dsv41
family on TensorFold 1.0.2's native Zig engine. Attribution: tools/dsv41-zig/ATTRIBUTION.md.
The Python engine of v0.1.0-v0.5.1 stays on branch deepseek-v41-tp2
(design, report and attribution in tools/dsv41/).
Clean-room: the model math is re-implemented from DeepSeek's MIT inference code and tech report; no code from other
DeepSeek-V4.1 recipes or kits was read or copied.
The kit the Zig engine loads is now in this repository's kit/, byte for byte the kit our served lane loads (engine
ce20a545, served 65f295ec), with MANIFEST, KIT-LICENSES.md and kit/tools/verify_kit.sh. The engine branch
gains the builders (5e10970a: tools/dsv41-zig/build_kit.sh, build_inputs.sh); its code is unchanged. Our served
lane started from this kit in a fresh folder gives the same replies as before on every gate (images 9/9 and 18/18,
51 image probes, 14 prompt lengths).
Engine ce20a545 on branch deepseek-v41-zig, on upstream TensorFold v1.0.2. The whole dsv41 family (prompt
and decode paths, DSpark drafting, Engram, the RDMA ring between the two nodes, the vision tower) now runs in TensorFold
1.0's native server, with no Python in the serving path. It is what we serve now: --context 1048576, --parallel 4,
images on.
TF_DS_REPLAY_FLOOR=1 (on in the served configuration). A last prompt chunk of 16 rows or fewer
at the bounded replay's cut (prompts of 2048k+112 to 2048k+127 tokens, or a short last chunk) read part of its
attention window from the previous request's keys, so its reply could depend on what ran before it (v0.5.1 does
this too). Now it reads from the cut as a larger chunk would: 14 such prompt lengths give the same reply on every
pass. 2 of the 51 image probes (both in that class) change their reply.Measured on two GB10 nodes (TP2), engine ce20a545 (code = served commit 65f295ec) at the served settings below, our own
client (kit_bench.py of the deepseek-v41-tp2 branch), greedy, DSpark drafting, median of 3 unless noted. The
speed rows ran before TF_DS_REPLAY_FLOOR was switched on (it changes only prompts of the class above); the gates,
image checks and probes were run again live with it on: all pass.
v0.6.0, --context 1048576 --parallel 4 | tok/s |
|---|---|
| Decode, set a: code / prose / structured (512 tokens) | 89.8 / 59.0 / 106.6 |
| Decode, set b: code / prose / structured (384 tokens) | 103.1 / 64.1 / 143.2 |
| 2 streams, set b, aggregate | 83.1 |
| 4 streams, set b, aggregate (median of 9) | 118.9 |
| 4 streams sustained 90 s, set b, aggregate | 142.0 |
| Prompt 8K / 32K / 64K / 128K (one run each; 8K best of 2) | 2,433 / 2,495 / 2,461 / 2,354 |
| Decode after a 128K prompt (median of 3 seeded prompts) | 100.1 |
v0.5.1's engine (6a6cf68) measured in the same session, same client and prompts, on the same nodes:
v0.5.1, --context 1048576 --parallel 4 | tok/s |
|---|---|
| Decode, set a: code / prose / structured (512 tokens) | 89.4 / 57.7 / 107.2 |
| Decode, set b: code / prose / structured (384 tokens) | 101.5 / 63.4 / 143.4 |
| 2 streams, set b, aggregate | 79.8 |
| 4 streams, set b, aggregate (median of 9) | 113.0 |
| 4 streams sustained 90 s, set b, aggregate | 125.3 |
| Prompt 8K / 32K / 64K / 128K (one run each; 8K best of 2) | 2,387 / 2,421 / 2,363 / 2,242 |
| Decode after a 128K prompt (median of the same 3 seeded prompts) | 49.8 |
The two structured rows are inside v0.5.1's own run-to-run spread in that session (v0.5.1: 106.5-107.4 and 142.2-143.5 tok/s).
Engine 90b68e0 on top of v0.5's 808eb4a. Replies and speed rows are unchanged on our served configuration.
nvcr.io/nvidia/pytorch:26.07-py3 sets
TORCH_ALLOW_TF32_CUBLAS_OVERRIDE=1, which ran the prompt chunks' fp32 router, indexer and mHC GEMMs in TF32. That
gave other bits than our lane and the gates, and the loop in #6 at seed 3001. The engine now sets the switch to 0 at
start (it prints a line when it was 1) and refuses to start if fp32 GEMMs still run in TF32.TF_LOOP_GUARD=1 makes it the
server default; a request sets it with "loop_guard": true / false. Replies that do not loop are untouched. The
signal is from Capicua25x's loop detector (#9).Decode and prefill are what we worked on this time. The same release suite on the served lane for both builds
(v0.4 = engine bd0024d at --context 262144, v0.5 = 808eb4a at --context 1048576), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
--parallel 4. Needles found to 1M on this configuration;
four long streams together give the same replies as each one alone.Where it came from, all bit-identical: RoPE tables at the context length (cos/sin only) and one shared logits buffer
for the round graphs pay for the 1M window; exact pruned top-k for the long-context indexer, candidate-only reindexing
and tile skipping in deep decode; prompt chunks' grouped expert launches walk a work list built on the device; kept
prompts shrink to a boundary instead of being dropped. The engine is now on upstream TensorFold 0.6.6.
Details in the engine commit message and tools/dsv41/REPORT.md.
Engine d5d7bb3 on top of v0.4's bd0024d. The Engram read-ahead now claims its slot by index. Before, list.remove()
compared id arrays of other lengths and could stop the server on long, prefix-cached agent sessions (present since
v0.2). One file changed; replies and speed rows are unchanged. If you run v0.4, update to this.
Context window, v0.1.0–v0.4.1: the serve command below and our served lane run
--context 262144. Earlier text here said "window up to 1,048,576": that is the engine's limit, which one earlier build tested with a single needle, not the configuration these releases were measured and shipped with.
The same release suite, run on the served lane for both builds (v0.3 = engine bbaa6cd, v0.4 = bd0024d), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
Where it came from, all bit-identical and each behind a switch (set it to 0 for the old path):
TF_DS_L2_PREFETCH).tools/dsv41/REPORT.md.The same release suite, run on the served lane for both builds (v0.2 = engine 08cae28, v0.3 = bbaa6cd), same client
(tools/dsv41/kit_bench.py), same prompts, greedy, DSpark drafting. Median of 3 unless noted.
Where it came from: less per-row overhead in the 1-row forward, all bit-identical. hc_post is fused into the next
hc_pre, the router and indexer row matmuls are smaller, the rotations and RoPE run in neighbouring kernels' epilogues,
and the indexer and compressor glue is fused. Kernels per 1-row forward: 2,387 → ~990. Every switch defaults on
(TF_DS_HC_FUSED, TF_DS_ROWMM2, TF_DS_ROUND_GLUE, TF_DS_ROT_*, TF_DS_INDEXER_FUSED, TF_DS_COMP_FUSED,
TF_DS_TRITON_PDL); set one to 0 to turn it off.
Same nodes, same public client (tools/dsv41/kit_bench.py), same prompts and same method as v0.1.0. Greedy decode,
DSpark drafting, served configuration below. Median of 3 unless noted.
tools/dsv41/quality_eval.py (subset digest 09c2e6cc43e275af).Where it came from: grouped EXL3 decode linears (884 → 350 launches a forward) and a fused grouped-expert decode path,
both bit-identical; Engram reads moved off the critical path; a lighter per-round rank check; NCCL over both RoCE ports
for prompt chunks; a faster weight-cache read and a shorter warm-up. Details in tools/dsv41/REPORT.md.
Single stream 512 tokens code / prose / structured 60.5 / 38.0 / 74.5 tok/s (set b 69.9 / 42.3 / 96.5); 4 streams 73.2–80.5 tok/s; prefill 1,247–1,365 tok/s at 8K–128K; decode after a 128K prompt 61.8 tok/s; served window 262,144 tokens (the engine accepts up to 1,048,576; an earlier build found one needle at 1,039,833 tokens); start to ready 65 s.
Build the deepseek-v41-zig branch with Zig 0.17.0 inside nvcr.io/nvidia/pytorch:26.07-py3 (CUDA nvcc, sm_121):
zig build install native -Dtarget=aarch64-linux-gnu -Doptimize=ReleaseSafe -Dnvcc=/usr/local/cuda/bin/nvcc -Dsm=121 --prefix <OUT>
That gives native/bin/tensorfold-native (the server, rank 0) and bin/tf-dsv41-lanes (rank 1, following rank 0);
bin/tensorfold is a test CLI, not the server. Both ranks run in the same
container image with the RoCE devices, --network host --ipc host --ulimit memlock=-1. They read a kernel kit
<KIT>: the family's Triton kernels as compiled binaries (aot/, cubins/), the RoPE tables, engram.json, and
vision/ (the tower's attention kernel from the container's PyTorch, and the image routing bias from DeepSeek's
original weights).
The kit is in this repository's kit/ folder (plain git, with MANIFEST and KIT-LICENSES.md).
Five of its files are generated, not committed: the four RoPE tables (136 MB each) and the image routing bias (43
small DeepSeek weight tensors). Make them and check every file against MANIFEST, inside the
nvcr.io/nvidia/pytorch:26.07-py3 container with the deepseek-v41-tp2 engine's src/ at hand:
kit/tools/verify_kit.sh --rope <MODEL_DIR> <deepseek-v41-tp2>/src --bias <ORIGINAL_CHECKPOINT_DIR>
Or download the complete kit, generated files included, from folder kit/ of
bertholomus/DeepSeek-V4.1-Flash-TensorFold-TP2-2xGB10
and run kit/tools/verify_kit.sh. Both copies hold the same bytes. To build the kit from source instead, the
engine branch has tools/dsv41-zig/build_kit.sh (the kit) and tools/dsv41-zig/build_inputs.sh (each node's rank
cache, token map and extension build); see their headers.
Start rank 1, then rank 0:
# rank 1
TF_DS_REPLAY_FLOOR=1 NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tf-dsv41-lanes <MODEL_DIR> <CACHE_DIR> 1 2 <HEAD_IP> 29620 <KIT> --engram <ENGRAM_DIR> --token-map <TOKEN_MAP> \
--pool 1048576 --rdma <HCA_PORT_0>,<HCA_PORT_1> --graphs 1 --side 1 --prefetch 1 --aio 1
# rank 0
TF_DS_REPLAY_FLOOR=1 TF_TP_WORLD=2 TF_TP_MASTER=<HEAD_IP> TF_TP_PORT=29620 TF_DS_RANK_CACHE=<CACHE_DIR> \
TF_DS_KIT=<KIT> TENSORFOLD_CUDA_KERNELS=<KIT>/aot TF_DS_ENGRAM=<ENGRAM_DIR> TF_DS_TOKEN_MAP=<TOKEN_MAP> \
TF_RDMA_DEVICES=<HCA_PORT_0>,<HCA_PORT_1> TF_DS_GRAPHS=1 TF_DS_HC_SIDE=1 TF_DS_L2_PREFETCH=1 TF_DS_ENGRAM_AIO=1 \
NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tensorfold-native serve <MODEL_DIR> --host 127.0.0.1 --port 18891 --name DeepSeek-V4.1-Flash-TF --api-key-file <KEY_FILE> \
--context 1048576 --parallel 4 --thinking --max-tokens 32768 --temperature 1.0 --top-p 0.95 --top-k 20 --backend cuda
TF_DS_REPLAY_FLOOR=1 goes to both ranks.TF_DS_STREAMS=N on rank 0 needs --streams N on rank 1, and rank 1's
--pool equals rank 0's --context. With different values the two ranks wait on each other (#25).python3 with Pillow in the container (the NVIDIA image has it).<CACHE_DIR> keeps each rank's weights in one file (written on the first start); <TOKEN_MAP> and the cache are
the ones the deepseek-v41-tp2 engine writes.TF_RDMA_GID_INDEX=<index> picks the RoCEv2 GID index the native TP2 ring opens (default 5, so existing setups
are unchanged; single-rail ASUS GX10 pairs want 3). Find the index with ibv_devinfo -v or show_gids, and set
it on both ranks. Both ranks must run the same engine build: the ring's peer description grew in the same change
(128 to 144 bytes), and a mixed pair cannot talk. At connect the ring logs gid_index= and the negotiated
path_mtu= per rail.Hosts and addresses are placeholders; substitute your own.
Two GB10 nodes with a direct RoCE link. On each node, a throwaway nvcr.io/nvidia/pytorch:26.07-py3 container with
the deepseek-v41-tp2 branch installed (pip install -e). Start rank 0 (head) first — it owns the TCPStore master
(comm.py: is_master = rank == 0) — then wait for its store port to LISTEN (default 29551) before starting rank 1
(worker). A fixed sleep races on a cold start: rank 0 dies with DistStoreError: Timed out … 1/2 clients joined and
rank 1 with NCCL error 6 at nccl.barrier():
TF_DS_REPLAY=1 TF_DS_PREFILL_CHUNK=2048 TF_DS_RANK_CACHE=<CACHE_DIR> TF_DS_RANK_CACHE_READERS=32 \
TF_DS_WARM_LENGTHS=1,17,33,131,514,1024,2113 \
NCCL_IB_HCA=<HCA_PORT_0>,<HCA_PORT_1> NCCL_IB_GID_INDEX=5 NCCL_SOCKET_IFNAME=<IFACE> \
tensorfold serve <MODEL_DIR> --tp 2 --rank R --master <HEAD_IP> --host 127.0.0.1 --port 18891 \
--context 1048576 --vision --parallel 4 --mtp-drafts 5
<MODEL_DIR>: the Mia-AiLab EXL3 checkpoint. Engram tables: TF_DS_ENGRAM=<ENGRAM_DIR> (DeepSeek's original shards
47/48), or a folder next to <MODEL_DIR> whose name contains "Engram".NCCL_IB_HCA lists both RoCE ports when both are cabled (prompt-chunk gathers 153 → 106 ms); one port works too.TF_DS_RANK_CACHE keeps each rank's weights in one file (~106 GB a rank, written on the first start).--context 1048576 is the measured, served window from v0.5 (v0.1.0–v0.4.1 ran 262144).TF_RDMA_DEVICES=<HCA_PORT_0>,<HCA_PORT_1> names the RoCE ports of the link between the two nodes when a node has
more active ports than that link (the RDMA gather needs the same device count on every rank). TF_API_KEY_FILE=<file> makes every route but /health require a key.--temperature sets the server-side default a client inherits when it omits the field. Greedy (0) makes long
agentic turns loop in reasoning_content and return empty content (finish_reason: "length"); omit it to keep the
engine default (1.0), and pin 0 only for the deterministic MMLU/GSM8K runs.python3 tools/dsv41/kit_bench.py --base http://127.0.0.1:18891 --model <name> decode|concurrent|sustained|prefill|depthpython3 tools/dsv41/quality_eval.py --base http://127.0.0.1:18891 --model <name> --mmlu <MMLU_TEST_PARQUET> \ --gsm8k <GSM8K_TEST_JSONL> --max-tokens-on 4096 (MMLU "all" test from cais/mmlu, GSM8K test from openai/grade-school-math)Hosts and addresses are placeholders; substitute your own.
deepseek_v41 TP2 family and
its Zig port dsv41, their kernels, the deployment and the measurements.Not affiliated with or endorsed by DeepSeek, NVIDIA, the TensorFold authors, Mia-AiLab or MiaAI-Lab.
Recipe documentation: Apache-2.0 (LICENSE). The weights stay under their own MIT license.