Repository navigation
Description
Summary
On frank2, DeepSeek V4.1 TP4 on GPUs behind different CPU IO-die quadrants (0,4,8,12) runs ~40% slower per decode round than on switch-local GPUs (12-15). The same package and settings are used on both. Two fabric effects cause it:
- Peer reads across quadrants stall in periodic windows (1-1.6 ms per 80 KiB read). This hits
pull,bulk_pulland every other pull-style transport. - Writes that fan out to several quadrants at once collapse by 8-13x. This hits
full,payload_rsagandbulk_pushabove a few KiB.
Every joint mode in the GB202 ladder is one of these two kinds, so the runtime race can only pick between stalling reads and collapsing writes.
A proof of concept forces write-only, one-destination-at-a-time ("phased") collectives through a ladder with one profile per bucket. The rest of the package is unchanged. It brings host-bridge GPUs to the level of the switches:
| DS4.1 c32 CSF, frank2 | current (Luke's GB202 ladder) on 0,4,8,12 | PoC on 0,4,8,12 | current on switches 12-15 | current on switches 0-3 | PoC on switches 0-3 |
|---|---|---|---|---|---|
| GPU time per b1w8 round | 13.9-14.1 ms | 8.2-8.3 ms | 7.6 ms | 7.6 ms | 8.0-8.1 ms |
| c16 tok/s | 1001-1102 | 1466-1474 | 1524-1559 | 1558-1578 | 1482-1499 |
| c32 tok/s | 1007-1764 | 2155-2204 | 2309-2312 | 2266-2306 | 2162-2239 |
The forced phased modes also run on switches (0-3) and cost 4-6% there. On host bridges the PoC is within 1-2% of the same PoC on switches: once reads and fan-out are gone, the host-bridge fabric adds almost nothing.
PoC branches. The phased kernels are Claude-written, so they are not mergeable under design-review principle 14:
- Flare:
claude/poc-hostbridge-phased(10cceb2): ladder mode names, the forced ladder, tests, export/bench scripts. - FlashInfer:
claude/poc-hostbridge-phased: FI 443d8fad + phased joint submodes (4703658b8, from FI Parse Gemma 4 reasoning channels and tool calls #62), a PoC ladder-tool filter (39cfd638b), and the diagnostic harness (c0cb3d5a5).
1. Hardware and topology
frank2:
- AMD EPYC 9575F, 1 socket, NPS1;
- kernel
pcie_aspm=off pcie_port_pm=off amd_iommu=off iommu=off; - driver 615.71.10;
- 17x RTX PRO 6000 behind c-payne Switchtec Gen5 boards.
Each switch chip is split into two partitions with separate x16 uplinks, and both uplinks land in the same IO-die quadrant (one NBIO):
| quadrant (NBIO) | host bridges | GPUs |
|---|---|---|
| 0 | 00, 20 | 0,1 / 2,3 |
| 1 | 40, 60 | 4,5 / 6,7 |
| 2 | 80, a0 | 8,9 / 10,11 |
| 3 | c0, e0 | 12,13 / 14,15,16 |
GPUs 0-3 are all in quadrant 0, which is why 0-3 performs like frank1. GPUs 0,4,8,12 span four quadrants. Side note: GPU 16 links at Gen5 x4.
2. What we measured
The tool is a standalone harness (benchmarks/experimental/b12x/pcie_fabric_stall/ on the FI branch):
- one process, one persistent kernel per GPU;
- every operation is timestamped with
%globaltimer(start, after a cross-GPU start barrier, end), with clock alignment; - patterns: read/write ring/all/incast/pair, one-shot pull/push, phased push, ring, RSAG push/phased/pull, host read/write;
- a second harness runs NCCL all-reduce inside CUDA graphs with per-operation stamps.
2.1 Cross-quadrant peer reads stall in periodic windows
- Windows: every quadrant has a ~150-200 ms window every ~2.30 s. The quadrants take turns ~300 ms apart (00 -> 40 -> 80 -> c0). The phase is fixed in wall-clock time and does not depend on the measuring process. A 4-quadrant group therefore has a degraded target ~30% of the time.
- Size of the slowdown: inside a window, reads whose target is in that quadrant are 10-50x slower. Single 80 KiB reads take 1.1-1.6 ms, and up to 2.7 ms at 400 KiB.
- Not the GPU: a reader behind the same switch reads the same target at the same moment without any stall.
- Within one quadrant reads are clean: reads between host bridges 00 and 20 (GPU 1 <-> 2,3) had 0 stalls in 700k reads.
- Posted writes never stall: write-only patterns peaked at <= 55 us over 150k+ operations spanning many windows. Host-memory reads and writes are clean too.
2.2 Fan-out writes across quadrants collapse
- A GPU that interleaves writes to destinations in different quadrants is 8x (3 quadrants) to 13x (4 quadrants) slower than one writing one destination at a time.
- Interleaving to one remote quadrant, or within a switch, is fine.
- This looks like the source side ordering posted writes across fabric destinations. Rotating destinations per GPU-wide phase, or a ring, avoids it.
2.3 Same patterns on 0-3 versus 0,4,8,12
80 KiB, 16 CTAs; per-GPU mean / max per operation, including the ~5.5 us start barrier.
| pattern | 0-3 | 0,4,8,12 | ops > 1 ms (0,4,8,12) |
|---|---|---|---|
| read_ring | 4.2 / 5.1 us | 11.1 / 1224 us | 7-11 per GPU |
| pull (one-shot) | 10.8 / 15.0 us | 17.7 / 1622 us | 19-22 per GPU |
| write_all (fan-out) | 10.1 / 11.6 us | 110.8 / 209 us | 0 |
| push (fan-out one-shot) | 12.4 / 13.8 us | 110.7 / 203 us | 0 |
| push_phased | - | 16.1 / 53.7 us | 0 |
| rsag_phased | - | 18.6 / 42.0 us | 0 |
| ring | - | 20.5 / 45.1 us | 0 |
2.4 Transport comparison on 1,5,9,13 (same switches and quadrants as 0,4,8,12)
= 3 s per cell, so every cell spans several windows.
| KiB | transport | mean | p50 | p99 | max | ops > 1 ms |
|---|---|---|---|---|---|---|
| 80 | NCCL P2P (NCCL_P2P_LEVEL=SYS, LL ring writes) |
14.3 | 14.2 | 15.9 | 44 | 0 |
| 80 | phased one-shot push, 8 CTAs | 15.3 | 15.2 | 15.9 | 53 | 0 |
| 80 | phased RSAG, 4 CTAs | 15.5 | 15.5 | 15.7 | 43 | 0 |
| 80 | write ring | 18.5 | 18.5 | 19.1 | 42 | 0 |
| 80 | NCCL default (SHM through host memory) | 22.3 | 22.3 | 23.4 | 53 | 0 |
| 80 | one-shot pull | 23.4 | 11.5 | 402 | 1606 | 24 |
| 80 | fan-out RSAG push | 33.3 | 33.0 | 43.8 | 62 | 0 |
| 80 | fan-out one-shot push | 110.5 | 110.3 | 136.5 | 195 | 0 |
| 400 | phased RSAG | 26.7 | 26.6 | 28.3 | 61 | 0 |
| 400 | write ring | 27.0 | 27.0 | 29.7 | 64 | 0 |
| 400 | NCCL P2P | 34.6 | 34.8 | 40.6 | 99 | 0 |
| 400 | phased one-shot push | 39.5 | 38.9 | 47.2 | 130 | 0 |
| 400 | NCCL default (SHM) | 59.3 | 59.2 | 62.1 | 92 | 0 |
| 400 | one-shot pull | 69.4 | 47.1 | 1431 | 2686 | 1106 |
| 400 | fan-out one-shot push | 653.5 | 651.8 | 722.6 | 772 | 0 |
Pull has the best median and the worst mean and tail. A median-based startup race prefers it, but serving pays the mean.
The phased cost on a fabric where fan-out works is small. On one quadrant (13-16) at 16 KiB: pull 8.2 us, phased push 8.3 us, fan-out push 9.2 us.
2.5 NCCL and vLLM
-
NCCL 2.30.7 is not affected by default. On this topology it picks
SHM/direct(GPU writes to host memory), which gives 22 us at 80 KiB with no stalls.- With
NCCL_P2P_LEVEL=SYSit uses an LL ring of posted writes, one destination per GPU, and is the fastest small transport (14.3 us). - Only
NCCL_P2P_READ_ENABLE=1 NCCL_PROTO=Simplemakes it read across quadrants, and then it shows the same stalls (max 1.63 ms).
- With
-
vLLM (fraserprice DeepSeek-4.1-Flash-1017-BRRR image):
- <= 96 KB: b12x one-shot pull, including the fused add+RMSNorm path;
- 96 KB-1 MB: its own
phased_all_reduce.cu(posted writes, one destination per GPU-wide phase, rotation); -
1 MB: NCCL with
NCCL_P2P_LEVEL=SYS NCCL_P2P_READ_ENABLE=1.
Its small path would hit the same read windows on 0,4,8,12; we did not serve it there.
3. Ruled out
| hypothesis | test | result |
|---|---|---|
| AER / bad link | AER counters, GPU replay counters | 31 correctable events in 2.2 days, all on root port c0:01.1 (~1/hour), GPU replays 0; windows occur every 2.3 s on every quadrant |
| link speed/width changes, retraining | LnkSta sampled every 0.5 ms during stalls | stable Gen5 x16, no training, no bandwidth-management events |
| ASPM / port PM | kernel cmdline | already off |
| CPU C-states | hold /dev/cpu_dma_latency = 0 |
no change |
| activity-driven DMA power management | 200 MB/s background host->GPU copies | no change |
| DF P-states | FCLK via HSMP every 2 ms | constant 1733 MHz |
| LCLK DPM | HSMP pin of LCLK DPM range to 1..1 on all 4 NBIOs, A/B/A | no change (read_ring ops > 1 ms: 60 / 67 / 56; pull: 139 / 134 / 147) |
| power profile | HSMP power efficiency mode 2 (IO performance), A/B/A | no change |
| DF C-states | HSMP DF C-state disable, A/B/A | no change |
| GPU-side tasks, fan control, NVML, Switchtec MRPC polling | GPU 14 has no fan controller; NVML/MRPC calls fired at known times | GPU 14 stalls the same; timed calls cause no windows |
| SMU mailbox busy | HSMP round-trip latency inside and outside windows | identical (p50 106 us) |
All HSMP changes were read back and restored. One side observation: CPU config-space reads of a GPU (our 0.5 ms LnkSta sampler) add extra read stalls targeting that GPU.
Conclusion: the windows come from per-NBIO periodic firmware activity that no OS-visible control reaches. Only BIOS options or AMD can change it. Software can avoid it completely by not reading across quadrants.
4. Why the current runtime selection cannot fix it
- No good candidate on host bridges. Every joint mode is either a peer read (
pull,bulk_pull) or a fan-out write (full,payload_rsag,bulk_push). - The race reads medians. It decides on medians over a few ms, which hides 2.3 s windows. Pull wins on median.
- Choices change per start. Two starts of the same package on 0,4,8,12 picked Pull 8 / Bulk Pull 8 / Full 4 / Payload RSAG 2, then Pull 9 / Bulk Pull 7 / Payload RSAG 4 / Full 2. In the second start, c32 fell to 1007-1026 tok/s because the adaptive scheduler mostly ran b16w8 rounds.
5. Everything we tried
| # | attempt | result | verdict |
|---|---|---|---|
| 1 | Luke's runtime tuning, exported on switches, served on host bridges (DS4.1 c16 package, 2026-10-28) | c16 341-364 tok/s vs 507-521 for a static package exported natively on host bridges | runtime worse than static on host bridges |
| 2 | Select by mean instead of median over the same short window | flips more often (128 rows: RSAG chosen 8/20 vs 3/20), short windows cannot resolve clustered stalls | rejected |
| 3 | Staggered peer order, still concurrent writes | 250 us vs 500 us interleaved (3x320 KiB); one destination at a time 27 us | not enough |
| 4 | Single-CTA profiles (one write stream) | RSAG/1 at 128 rows 395 us vs pull long-run mean 110-220 us | not enough |
| 5 | Phased joint submodes (FI #62) added as startup-race candidates (DS4.1 c16) | 562-576 tok/s c16 vs 507-521 native static | works, but kernels violate principle 14; race still sees reads |
| 6 | Export release deadline (raise it / handshake in #128) | rejected as a workaround; main has since fixed it (release takes 14.7 s) | obsolete |
| 7 | Stall harness on 1,5,9,13, 0,4,8,12 and 0-3 | localized: cross-quadrant reads (periodic windows) and cross-quadrant fan-out writes | root cause found |
| 8 | LCLK DPM pin, IO-performance power mode, DF C-states off | no change | firmware, not OS-controllable |
| 9 | Phased cost on a single quadrant (13-16) | 16 KiB: phased 8.3 us vs pull 8.2 us vs fan-out 9.2 us | phased is cheap where fan-out works |
| 10 | PoC: forced phased ladder (below) | host bridges at switch level | works |
6. The PoC
What changed against the measured reference package. The reference is Flare 4f20ac5 (= main ladder code) with FI 443d8fad, DS4.1 c32 CSF, --collective-tuning runtime with gb202_pcie_tp4.json. The PoC changes:
- FlashInfer: FI 443d8fad plus
full_phased,payload_rsag_phasedandbulk_push_phased. They move the same data as their base modes, but every CTA of a rank writes one peer at a time in rank-staggered order. The joint compile key goes to 211 so stale cache entries are never reused. - Flare: the three names are added to
ignition/preparation/collective_ladder.py. - Ladder:
ignition/preparation/ladders/frank2_hostbridge_tp4_forced.json, 255 buckets from 16 B to 8 MiB, one profile per bucket, so the startup race has one candidate.- It is built from a
b12x.tools.collective_laddersweep on 0,4,8,12 withpullandbulk_pullexcluded: 84,200 configurations, 1200 s. - Write-only modes have no stall tail, so each bucket keeps the lowest mean among profiles within 3% of the best median.
- It is built from a
The resulting ladder:
| payload | forced mode | mean |
|---|---|---|
| <= ~12 KiB | full / payload_rsag (fan-out is harmless when latency-bound) |
3.6-6 us |
| 12-110 KiB | full_phased |
7.4 us at 16 KiB, 10.4 at 64 KiB |
| 115 KiB-1.6 MiB | payload_rsag_phased |
13.9 at 128 KiB, 26 at 512 KiB, 45 at 1 MiB |
| >= 1.7 MiB | bulk_push_phased |
82 at 2 MiB, 294 at 8 MiB |
For comparison, the existing modes on 0,4,8,12 at 32 rows (320 KiB) take 32.5 us (pull, with stalls) and 28.9 us (bulk pull); the phased choice takes ~20 us.
Export and tests.
- Same command and environment as the reference package (frank1 GPUs 8-11), only adding the ladder. The export took ~18 min and resource release 14.7 s.
- Weights are byte-identical to the reference; only 14 deployment/index files differ.
- FI
test_pcie_joint_gpu.pypasses on 0,4,8,12 (13 passed, 12 skipped). - Flare
tests/preparation/test_collective_ladder.pypasses for the new ladder, including b12x accepting every pinned geometry at both ends of every bucket. The one remaining failure is a native-extension mismatch in the test environment, and it fails the same way on main.
Startup:
- the selection is identical on every start: Full 2,
full_phased6,payload_rsag_phased11,bulk_push_phased2, Payload RSAG 1; - no pull of any kind;
- startup tuning takes 1.33 s instead of 2.45 s.
Serving measurement.
- frank2,
--max-concurrency 32 --mtp-draft-tokens 7 --mtp-verification-token-budget 128 --embedding-table-storage ram --prefix-cache-memory 64G. llm_decode_bench.pywith 2 x 30 s cells per concurrency at temperature 1, after a warm-up.- Runs alternated: current, PoC, current, PoC, on 0,4,8,12 and again on the switched GPUs 0,1,2,3 (two PIX pairs in one quadrant). 12-15 has one run of the current package. Ratios use the mean of the cells.
| tok/s | current on 0,4,8,12 | PoC on 0,4,8,12 | current on 12-15 | current on 0-3 | PoC on 0-3 |
|---|---|---|---|---|---|
| c1 | 193-248 | 308-374 | 323-331 | 319-380 | 318-329 |
| c8 | 671-684 | 963-1008 | 1031-1039 | 1035-1042 | 976-986 |
| c16 | 1001-1102 | 1466-1474 | 1524-1559 | 1558-1578 | 1482-1499 |
| c32 | 1007-1764 | 2155-2204 | 2309-2312 | 2266-2306 | 2162-2239 |
| ratio (cell means) | c1 | c8 | c16 | c32 |
|---|---|---|---|---|
| PoC 0,4,8,12 vs current 0,4,8,12 | +61% | +45% | +42% | +24% (vs the good start) |
| PoC 0,4,8,12 vs current 0-3 (best switch result) | +1% | -6% | -6% | -5% |
| PoC 0-3 vs current 0-3 (cost of forcing phased on switches) | -5% | -5% | -5% | -4% |
| PoC 0,4,8,12 vs PoC 0-3 (cost of the host-bridge fabric) | +6% | -1% | -1% | -1% |
c1 differences are within draft-acceptance noise at temperature 1.
GPU time per speculative round, from Flare's flare_speculative_gpu_nanoseconds_total / flare_speculative_rounds_total (slowest rank). This does not depend on draft acceptance.
| graph | current on 0,4,8,12 | PoC on 0,4,8,12 | current on 12-15 | current on 0-3 | PoC on 0-3 | PoC 0,4,8,12 vs PoC 0-3 |
|---|---|---|---|---|---|---|
| b1w8 | 13.91-14.07 ms | 8.20-8.29 ms | 7.62-7.65 ms | 7.58-7.63 ms | 8.03-8.09 ms | +2% |
| b8w1 | 13.37 ms | 8.23-8.25 ms | 7.70 ms | 7.67-7.71 ms | 8.07-8.09 ms | +2% |
| b16w1 | 14.20 ms | 10.80-10.81 ms | 10.28 ms | 10.08-10.16 ms | 10.62-10.66 ms | +2% |
| b32w1 | - | 14.41-14.48 ms | 13.89 ms | 13.84-13.88 ms | 14.36-14.41 ms | +0.5% |
| b8w8 | 28.28-28.42 ms | 19.8-21.8 ms | 19.95 ms | 19.86-19.87 ms | 19.2-19.5 ms | +2-13% (few rounds) |
On 0-3 the current package's startup race picks Bulk Push 7, Full 4, Payload RSAG 11 (no reads), and that is the best result on this host.
Result.
- On host bridges the PoC makes a decode round ~40% faster: 8.2-8.3 ms against 13.9-14.1 ms.
- That is within 4-8% of the best switch rounds (7.6 ms at b1w8), and within 0.5-2% of the same forced phased ladder on switches.
- Throughput lands within 5-6% of the switches at c8-c32.
- It is stable across starts, which the current selection is not.
- The remaining gap is the cost of the phased modes themselves, which are 4-6% slower than the switch-optimal fan-out modes on 0-3. It is not the host-bridge fabric. A per-fabric choice (switch modes on switches, phased modes on host bridges) therefore gives switch-level results on both.
7. Recommended direction
- Across quadrants, never read peers and never fan writes out. Use write-only transports with one destination per GPU at a time:
- <= ~12 KiB: fan-out is fine (latency-bound);
- ~12-110 KiB: phased one-shot push (or an LL-style write ring, which is what NCCL does);
- ~115 KiB-1.6 MiB: phased RSAG (a write ring is within a few %);
-
= 1.7 MiB: phased bulk push.
- Select by fabric class, not by a short median race. Peer reads look best on median and worst on mean. Options:
- ship a ladder per fabric class (switch-local vs host-bridge/quadrant-crossing) and choose it from PCI topology at load;
- or keep the startup race but leave read submodes out of the candidates when ranks span IO-die quadrants.
- Kernels need a permitted author. The phased submodes in the PoC are Claude-written (FI Parse Gemma 4 reasoning channels and tool calls #62), so principle 14 rules them out. The measurements above are the spec; the branches show the shape that works.
- Do not force phased modes on switches. On 0-3 they cost 4-6% against the switch-optimal fan-out modes. On host bridges they are within 0.5-2% of their own switch result. So the remaining gap to switches is the phased modes' own cost (phase barriers), not the fabric. A per-fabric choice gets both fabrics to their best.
8. Caveats
- One host (frank2) and one PoC export. The forced ladder is frank2-specific; on switches (0-3) it costs 4-6%, so it should only be used where ranks span IO-die quadrants.
- Throughput varies with draft acceptance at temperature 1; use the per-round GPU times for the comparison.
- Branches: the Flare branch is based on main 921afd5 (its ladder code is identical to 4f20ac5). The FI branch is based on 443d8fad. Neither carries a design sign-off. Do not merge.
Reproduce
- Harness: FI branch
benchmarks/experimental/b12x/pcie_fabric_stall/README.md. - PoC export, serving and A/B: Flare branch
scripts/poc_hostbridge_collectives/README.md. - frank2 data:
/data/ssd2/claude-ds41/work/p2pstall/(harness runs:dumps-*) and/data/ssd2/claude-ds41/work/poc-phased/(ladder report, forced ladder,bench/{base-1,base-2,poc-1,poc-2,sw-base-1,sw03-base-1,sw03-base-2,sw03-poc-1,sw03-poc-2}). - PoC package:
/data/ssd2/claude-ds41/poc-package/.
Activity