Repository navigation
perf(dsv41): opt-in neighbor RDMA for TP4 physical rings - #30
bertholomus merged 5 commits into
Conversation
|
Thank you @swbiggart for this, and for the tests and the hardware receipts. The owner will decide on it. |
|
what an honor it would be to get credited in tensorfold! |
|
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. |
54cc9bf to
d080285
Compare
|
Done, let me know if you need anything else. Thanks for putting this together and your consideration of my changes. |
|
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! |
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>
…, 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>
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>
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: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-tp4at59402086381a42c74d53053443224eec2790b4c5.Hardware-tested engine commit:
a9225d72ec63b19867c72eba1cca48a49648805c. Later commits only add qualification receipts/documentation. This integrates the DeepSeek release branch, not canonicalashhart/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/srcwere verified unchanged. Native serving used the unmodified production code.SO.BUSY_POLLcompile 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 policysolo. The context argument is not full-context qualification.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.