Skip to content

perf(dsv41): opt-in neighbor RDMA for TP4 physical rings - #30

Merged
bertholomus merged 5 commits into
bertholomus:deepseek-v41-zig-tp4from
swbiggart:perf/tp4-neighbor-transport
Oct 20, 2026
Merged

bertholomus merged 5 commits into
bertholomus:deepseek-v41-zig-tp4from
swbiggart:perf/tp4-neighbor-transport

Conversation

@swbiggart

@swbiggart swbiggart commented Oct 20, 2026 •

Copy link
Copy Markdown

Update: The branch has been updated following review, and the existing hardware and host receipts have been updated. The original description and measurements below are retained for reference; the current code and linked receipts supersede them.

Problem and change

We do not have a network switch: our four GB10 hosts form a physical ring with dual-PCI-domain RoCE links between adjacent ranks. This was the fastest ring implementation we measured. We are not claiming a direct performance comparison against the target branch or a switched/full-mesh setup.

Add one default-off opt-in, TF_DS_NEIGHBOR_RDMA=1, that selects the optimized paths together:

  • Pair gathers use xor1 only.
  • Full/Markov gathers use xor1 then xor3, preserving global rank order.
  • Equal-width quarter gathers collect directly into the final layout.
  • Parity diagonal exchanges use remote-only xor1 then xor3 to reach rank xor2.

These paths fit the available links and remove unnecessary stages/copies. They reuse existing RDMA kernels and retain the 512 KiB cutoff. Larger diagonal traffic uses two-edge NCCL forwarding; other size/layout fallbacks remain. No arithmetic or drafting-policy changes and no separate experimental switches.

Unset/0 retains upstream inference paths and creates no neighbor RDMA resources. One TP4 startup byte all-gather runs in either mode to reject mixed enablement. All ranks must use the same binary. The opt-in requires two adjacent-edge rail lists and GID 3; it cannot be combined with TF_RDMA_DEVICES. Local failures propagate to both stages; this does not add a distributed recovery guarantee.

Base and tested code

Target: bertholomus/TensorFold:deepseek-v41-zig-tp4 at 59402086381a42c74d53053443224eec2790b4c5.

Hardware-tested engine commit: a9225d72ec63b19867c72eba1cca48a49648805c. Later commits only add qualification receipts/documentation. This integrates the DeepSeek release branch, not canonical ashhart/TensorFold:main's newer transport layer. No unrelated model/server/forced-tool patches are included.

Tests on the consolidated implementation

The exact commit was built for Linux/aarch64 with Zig 0.17.0 and CUDA fatbins, then tested on all four hosts. A weight-free C-ABI adapter and build targets were added externally; all 423 tracked production files under zig/src were verified unchanged. Native serving used the unmodified production code.

  • 56 rank/shape cases passed: independent byte oracle, guards, changing-input CUDA graph replay, mixed-operation graphs, cutoff and alignment fallbacks. Four-rank startup agreement ran successfully.
  • 9 model replies matched exact token IDs and UTF-8: three prompts, serial/drafted/concurrent, 64 tokens each.
  • 24 timed burst replies matched UTF-8 across six repetitions of four 256-token requests.
  • 11 approximately 8K-prompt replies passed: 5/5 burst/solo cases matched token IDs, drafting matched serial, all four recall markers were found, and cached replays matched cold runs. Solo replays reported 6144 cached tokens.
  • Cancellation passed: disconnect an active generation, then compare fresh serial/drafted replies exactly.
  • 90-second four-stream run: 62 requests, no worker errors.
  • Fresh host checks: 14/14 focused tests passed. Full host/golden command retains the inherited macOS SO.BUSY_POLL compile blocker; corpus remains 312/319 equal, seven known differences, zero unexpected. This full command is not reported as a pass. Existing lean findings remain documented separately.

Measurements on this commit

DeepSeek-V4.1-Flash-1017 EXL3 a2f91e27…, DSpark, four GB10 hosts, patched NCCL 2.30.7, --context 1048576 --parallel 16 --max-tokens 32768, graphs enabled, served-depth policy solo. The context argument is not full-context qualification.

Workload Aggregate tokens/s Detail
Four-stream set-a bursts 132.63 One warmup, six measured runs; range 131.94–133.31
Set-a steady admission 148.83 30 seconds, 19 requests
Set-b sustained 169.77 90 seconds, 62 requests; TTFT p50 0.159 s / p95 0.283 s

All use greedy decoding and 256-token maximum outputs. Set-a and set-b are different workloads; their rates are not directly comparable. Complete diagonal-operation latencies were 17.81 / 27.77 / 42.89 / 87.10 µs for 8.75 / 52.5 / 140 / 420 KiB, respectively (slowest-rank means).

The hardware receipt includes machine-readable results, rank/shape results, request bodies, binary digests, runtime versions and qualification limits. See also the host receipt and configuration contract.

Acknowledgments

This work was informed by @christopherowen’s DGX Spark networking experiments and @therealarnold666’s GLM bidirectional owner-ring experiments. These projects informed the design investigation. The implementation adapts TensorFold’s existing RDMA transport and kernels to DeepSeek’s specific exchanges on a four-host physical ring; the existing transport’s provenance is recorded in ATTRIBUTION.md.

@bertholomus

Copy link
Copy Markdown
Owner

Thank you @swbiggart for this, and for the tests and the hardware receipts. The owner will decide on it.

@christopherowen

Copy link
Copy Markdown

what an honor it would be to get credited in tensorfold!

@bertholomus

Copy link
Copy Markdown
Owner

Thank you again @swbiggart, and thank you @christopherowen and @therealarnold666 for the experiments this builds on. The v0.2.0 engine is now on deepseek-v41-zig-tp4 (b65c8b9), so this PR has two small conflicts (README.md and zig/build/cuda.zig). Could you please rebase onto it? Three small asks while you are there: (1) carry the neighbor mode in the engine start-up settings check (prompt2d.zig Settings, the same way TF_DS_IDX_SPLIT is) instead of the extra all-gather, so a run with the mode unset exchanges nothing new; (2) make the ring GID index a setting (it is fixed at 3; our rings use 5); (3) keep it off by default. We will merge after the host tests and a node build pass.

@swbiggart
swbiggart force-pushed the perf/tp4-neighbor-transport branch from 54cc9bf to d080285 Compare October 20, 2026 17:13
@swbiggart

Copy link
Copy Markdown
Author

Done, let me know if you need anything else. Thanks for putting this together and your consideration of my changes.

@bertholomus
bertholomus merged commit b8b115e into bertholomus:deepseek-v41-zig-tp4 Oct 20, 2026
@bertholomus

Copy link
Copy Markdown
Owner

Merged, thank you @swbiggart! All three asks are in: the mode is part of the start-up settings check, the ring GID index is a setting (TF_DS_NEIGHBOR_GID_INDEX, default 5), and the feature stays off unless TF_DS_NEIGHBOR_RDMA=1, so a run without it takes the old paths. Here the host tests (your 10 new ones too), the server tests and a node build pass, also together with #31. Thanks again to @christopherowen and @therealarnold666 for the experiments it builds on!

bertholomus added a commit that referenced this pull request Oct 21, 2026
The four-node line that serves DeepSeek-V4.1-Flash-1017 on four GB10 nodes comes in as its own commits since v0.2.0
(22, six of them merges). This merge keeps the branch a fast-forward of its head with pull requests #30 and #31
(b8b115e).

Against v0.2.0:
- A replay cut uploads its rows' positions into the chunk's aligned buffer (a prompt resumed after a kept text prefix
  no longer stops with MissingTritonVariant).
- Kept prompt states: pull request #26 (@hashd1ve), a prefix two conversations share no longer stales the first one's
  kept states; prompts with lane images keep and resume their states, matched by the images' digests (after #28 by
  @hashd1ve), and a resumed prompt skips the vision tower for the images its kept state holds; a kept state the lanes
  would refuse is passed over for an earlier one.
- TENSORFOLD_PLAIN_SPECIALS: a sender's special-token text stays text, and a lane-vision engine's image placeholders
  stay its image tokens.
- Structured output (response_format json_object and json_schema) through libtfgrammar; TENSORFOLD_GRAMMAR_LIB names
  its file.
- tool_choice "required" and a named function write DeepSeek's DSML calls block, and a forced call's marker is always
  its token.
- The served sampler draws top_k off (0 and -1) and any top_k.
- The draft head's chains counted by depth for /metrics, with an optional stats line (TENSORFOLD_STATS_EVERY).
- Off unless set: TF_FILL_CHUNKS (#29 by @hashd1ve, on TP4: a prompt pass in pieces between the other streams'
  rounds) and TENSORFOLD_REPLY_IDS.

Both sides changed zig/src/families/dsv41/model.zig and prompt.zig. model.zig takes both: #30's neighbor gather beside
the served line's changes. prompt.zig keeps the served line's replayCut, which uploads every cut's surviving positions
into the chunk's own buffer; #31 uploads them for small odd cuts and keeps the view otherwise, and the positions are
the same. #31's CPU test follows the served replayCut (one upload for every cut) and passes on it, and
replay_positions.md says so; the v0.2.0 baseline still fails it as the document says. Every other file is one side's:
40 the served line's, 17 this branch's.

With TF_DS_NEIGHBOR_RDMA unset, #30 opens no neighbor gather and every exchange takes the path it took before. Rank
0's startup settings now carry the neighbor mode (48 bytes, 40 before), so every rank runs the same build.

Thanks @swbiggart for #30 and @mrsergiopascal for #31.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bertholomus added a commit that referenced this pull request Oct 21, 2026
…, forced tool calls, five contributions

The README's fork note adds what the branch serves since v0.3.0: kept prompt states for image turns too, a shared
4,194,304-position window, structured output and forced tool calls in DeepSeek's DSML. NOTICE lists the new files (the
prompt shield's, and the neighbor transport's document and receipts), every upstream file the branch changes against
1.0.12 (16 more since v0.2.0) and the contributions: Manrique's pull requests #26, #28 and #29, Scott Woolsey
Biggart's #30 and mrsergiopascal's #31. ATTRIBUTION.md adds them, the ideas taken from DeepSeek's DeepInfer and from
xgrammar's structural tags, and @den13501's report behind the stream keepalive. The family's section of
THIRD_PARTY_NOTICES.md names libtfgrammar in a deployment's kit.

The fork note also thanks the branch's contributors by name. ATTRIBUTION.md says that the EXL3 kernel code copied
from TensorFold's Python line was written before 0.6.0 and keeps its MIT notice (LICENSES/MIT.txt), and names the
co-authors the commits' trailers name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bertholomus added a commit that referenced this pull request Oct 23, 2026
The four-node line that serves DeepSeek-V4.1-Flash-1017 comes in as its own commit since the v0.3.2 merge (one):
the prompt's cross-rank gathers carry the pair-summed bf16 y the posting kernels take, instead of the four fp32
quarters of the sublayer partials (TF_DS_2D_YX, off unless set: with the switch on the pair partners swap their fp32
partials of each half of the rows, each sums its half in rank order, and the four nodes all-gather the bf16 halves,
so a node receives 5 n·w bytes instead of 12 n·w and the quarters copies become one widen; the gathered values are
the same bits, so the GPU kernels are unchanged). The merge keeps the branch a fast-forward of its head (51b0c63).
model.zig changed on both sides since the merge base (the public side's #30 neighbor lines are in it); the merge
keeps both.

Against v0.3.2:
- TF_DS_2D_YX=1 with NCCL_NCHANNELS_PER_NET_PEER=8 (both now on as served): prefill gains with the prompt's
  length — the gate's 8,192 / 32,768 / 131,072-token rows read 2,974 / 3,107 / 3,140 tok/s (+7.5 / +6.6 / +7.5 % in
  the same session), the 93,335-token prompt 2,949 tok/s median of 3 (+15.6 % since v0.3.1's 2,552, the same
  cmp1021_bench.py bench), and the 300K needle 98.65 s median of 3 (106.59 s on v0.3.2's build, the same check).
  Decode is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants