Skip to content

[Fix] eplb: Warm default-group P2P transports on multi-node layouts - #46592

Merged
kpham-sgl merged 2 commits into
sgl-project:mainfrom
weireweire:eplb-prewarm-multinode
Oct 29, 2026
Merged

kpham-sgl merged 2 commits into
sgl-project:mainfrom
weireweire:eplb-prewarm-multinode

Conversation

@weireweire

@weireweire weireweire commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

#42026 warms EPLB's default-group NCCL P2P transports before KV-cache sizing, but only on a single node. On multi-node full-EP layouts, the first rebalance still creates the cross-node connections to the peers it exchanges with lazily (in our runs, all of them). By then the KV pool has already been sized to the free memory, so the transport buffers come out of memory the pool was counting on. The first rebalance also stalls while it connects.

Measured on GB300, DeepSeek-V4.1-Flash MegaMoE decode with DSpark and --enable-eplb --ep-num-redundant-experts 32, stock main:

decode layout first rebalance steady rebalance free memory lost at the first rebalance, per rank
2-node DEP8 3.21 s 1.01-1.04 s 4.38 GiB, of which 3.24 GiB is P2P transports
4-node DEP16 6.6-6.7 s 0.85 s 7.0 GiB

That memory comes out after KV sizing, so a high --mem-fraction-static risks an OOM at the first rebalance, the failure #42026 addressed for single-node layouts. We have not hit that OOM in any run at 0.85; on 1-node DEP4 the ranks still had 52-57 GiB free before each rebalance. This is a stability fix, not a throughput change.

Modifications

  • Drop the nnodes == 1 gate from the EPLB P2P warmup. The other gates are unchanged: CUDA with NCCL, --enable-eplb without an elastic EP backend, PP=1, and EP=TP>1.
  • After the per-peer send/recv, run one 4 MiB all-reduce on the default group. Each rebalance all-reduces the recorded expert counts on that group. The warmup only sets up the NCCL algorithm and protocol picked at 4 MiB; the rebalance all-reduces the whole logical_count buffer (over 100 MB with a 3000-pass window), which NCCL may run with another algorithm. On GB300 MNNVL the warmup covered what the rebalance used (the first-rebalance loss drops to the staging cache, below); IB-only fabrics are not validated.

The all-reduce also runs on single-node layouts, so single-node memory accounting changes slightly (numbers below).

The warmup's KV cost grows with world size (~0.45 GiB per peer, measured on MNNVL); stock pays the same transports later, at the first rebalance. The warmup posts all peers in one batch, unlike the rebalance P2P, which is chunked; it has only run at W <= 8.

Accuracy Tests

Not applicable: a startup-only change with no effect on model outputs.

Speed Tests and Profiling

GB300, SGLang main at 6563312, InferenceX AgentX trace, MegaMoE decode with DSpark and dynamic EPLB (+32 redundant experts).

2-node DEP8 decode, rebalance every 3000 passes, c512, 14 rebalances per arm:

stock this PR
first rebalance 3.21 s 1.06 s
later rebalances 1.01-1.04 s 1.01-1.06 s
free memory lost at the first rebalance, per rank 4.38 GiB 1.14 GiB
warmup at startup - 1.65 s, 3.24 GiB per rank (before KV sizing)
KV pool per rank 87.64M tokens 85.87M tokens (-2.0%)
OOM / request errors none none

The remaining 1.14 GiB is the staging cache that every rebalance grows in both arms.

End-to-end throughput was not compared, and none is expected: the PR saves about 2 s once per server lifetime (the first rebalance at DEP8), and later rebalances take the same time in both arms. The 0.5-2.0% smaller KV pool could cost slightly when the KV pool is the limit.

1-node DEP4 decode, where #42026 already warms the P2P transports and this PR adds only the all-reduce. Rebalance every 1000 passes, c256, 5-6 rebalances per arm:

stock this PR
warmup 0.93 s, 1396 MiB 1.08 s, 1780 MiB
KV pool per rank 42.99M tokens 42.79M tokens (-0.5%)
first rebalance 2.06 s 1.80 s
free memory lost at the first rebalance, per rank 2.45 GiB 2.08 GiB

Not validated:

  • The KV cost and startup time with this PR on 4-node DEP16. Only stock DEP16 was measured; the numbers above are from stock.
  • HIP, and multi-node layouts over IB only. All runs used GB300 NVL72 racks with NCCL defaults, so cross-node traffic could use MNNVL.

CI States

Latest PR Test (Base): ❌ Run #40477015885
Latest PR Test (Extra): ❌ Run #40477015876
Latest PR Test (AMD ROCm 10): ❌ Run #40477015774

Root cause: sgl-project#42026 materializes EPLB's default-group NCCL P2P transports
before KV-cache sizing only when nnodes == 1. On multi-node full-EP layouts
the first rebalance still connects the peers it exchanges with lazily (in
our runs, all of them). By then the KV pool has been sized to the free
memory, so the transport buffers land on a full pool and the first
rebalance stalls for the connection setup.

Fix: drop the single-node gate. Also run one 4 MiB all-reduce on the
default group, which the rebalance uses for the recorded expert counts.
This warms only the NCCL algorithm and protocol picked at 4 MiB; the
rebalance all-reduces the whole logical_count buffer (over 100 MB with a
3000-pass window), which may pick another one. On GB300 MNNVL it covered
what the rebalance used; IB-only fabrics are not validated. The all-reduce
runs on single-node layouts too, where it adds 384 MiB to the warmup.

The warmup's KV cost grows with world size (~0.45 GiB per peer on MNNVL),
which stock pays later at the first rebalance. The warmup posts all peers
in one batch, unlike the chunked rebalance P2P; it has only run at W <= 8.

Validation: GB300, DeepSeek-V4.1-Flash MegaMoE decode with DSpark and
--enable-eplb --ep-num-redundant-experts 32, SGLang main at 6563312,
InferenceX AgentX trace.

2-node DEP8 decode (rebalance every 3000 passes, c512, 14 rebalances per
arm), stock -> patched:
- first rebalance 3.21 s -> 1.06 s (later ones 1.01-1.04 s stock,
  1.01-1.06 s patched)
- free memory lost at the first rebalance 4.38 GiB -> 1.14 GiB per rank
  (the 1.14 GiB is the staging cache every rebalance grows in both arms)
- warmup 1.65 s and 3.24 GiB per rank, now taken before KV sizing;
  KV pool -2.0% per rank
- no OOM in either arm at --mem-fraction-static 0.85

1-node DEP4 decode (rebalance every 1000 passes, c256, 5-6 rebalances),
stock (sgl-project#42026 warmup) -> patched:
- warmup 0.93 s / 1396 MiB -> 1.08 s / 1780 MiB per rank
- KV pool 42.99M -> 42.79M tokens per rank (-0.5%)
- first rebalance 2.06 s -> 1.80 s, free memory lost 2.45 -> 2.08 GiB

Stock 4-node DEP16: the first rebalance takes 6.6-6.7 s vs 0.85 s steady,
and loses 7.0 GiB per rank after KV sizing.

Co-authored-by: Po-Han Huang (NVIDIA) <53919306+nvpohanh@users.noreply.github.com>
@weireweire
weireweire force-pushed the eplb-prewarm-multinode branch from 574e7bc to 0ab98a5 Compare October 27, 2026 01:22
@nvpohanh

Copy link
Copy Markdown
Collaborator

/rerun-test test/registered/ep/test_eplb_no_a2a.py test/registered/model_loading/test_weight_cache_daemon.py test/registered/e2e/models_large/test_nvidia_nemotron_3_super_nvfp4.py

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🚀 2-gpu-h100 (2 tests): ✅ View workflow run

cd test/ && python3 registered/ep/test_eplb_no_a2a.py
cd test/ && python3 registered/model_loading/test_weight_cache_daemon.py

🚀 1-gpu-5090 (1 test): ✅ View workflow run

cd test/ && python3 registered/model_loading/test_weight_cache_daemon.py

🚀 4-gpu-b200 (1 test): ✅ View workflow run

cd test/ && python3 registered/e2e/models_large/test_nvidia_nemotron_3_super_nvfp4.py

weireweire added a commit to weireweire/sglang that referenced this pull request Oct 27, 2026
… pass

Root cause: sgl-project#41593 keeps waiting requests' device prefixes warm by
touching them in the radix LRU, but on a PD prefill worker it walks only
`waiting_queue`. Requests still in `disagg_prefill_bootstrap_queue`,
waiting for decode to pre-allocate their slots, are never touched. When
decode is full, the bootstrap queue is the deep one, and those prefixes
age out while the requests wait. With DEP2 prefill (9 workers) + DEP16
decode serving an agentic multi-turn trace at 7480 concurrent sessions,
the bootstrap queue held ~300 requests per rank for ~100 s. Device-radix
hits fell from ~65 % to ~22 % of prompt tokens, and the extra external
(Mooncake L3) reloads slowed prefill further, so prefill collapsed after
the client's turn burst and did not recover.

Fix: `calc_priority` takes the PD prefill bootstrap queue, and on the
same refresh pass `_touch_waiting_prefixes` touches up to
WAITING_PREFIX_REFRESH_BOOTSTRAP_MAX (512, a module constant next to the
existing waiting-queue bounds) bootstrap entries before the waiting
queue, in reverse order. SGLANG_ENABLE_WAITING_PREFIX_REFRESH=0 still
turns the whole refresh off. The
existing gate is unchanged: cache-agnostic policy, LRU eviction, not
decode, no fast match, prefix sharing supported.

Bootstrap entries are refreshed only on passes that reach
`calc_priority`. The caller returns earlier when the waiting queue is
empty or no request rows are free, so the bootstrap queue is not
refreshed on its own. With more than 128 waiting entries, the entries
past the first 128 are still not touched and can end up older than the
refreshed bootstrap entries.

Validation: GB300, DSv4.1-Flash, 9 DEP2 prefill + DEP16 decode, 7480
concurrent sessions, 1200 s window, SGLang main at 6563312 plus open
PRs sgl-project#46497/sgl-project#46593/sgl-project#46592/sgl-project#46404 on both arms:
  without: 361,815 tok/s/GPU @ p90 interactivity 145.0, TTFT p90 135.9 s;
           L3 73 % / device 22 % of prompt tokens in minutes 8-20.
  with:    560,412 tok/s/GPU @ p90 interactivity 161.0, TTFT p90 58.9 s;
           L3 21 % / device 74 %.
test_scheduler_held_row_admission's Scheduler stub gains
`disagg_prefill_bootstrap_queue = None` for the new argument.
@nvpohanh

Copy link
Copy Markdown
Collaborator

@ch-wan could you review this small change? thanks!

@kpham-sgl

Copy link
Copy Markdown
Collaborator

/rerun-test test_eplb_no_a2a.py

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🚀 2-gpu-h100 (1 test): ✅ View workflow run

cd test/ && python3 registered/ep/test_eplb_no_a2a.py

@kpham-sgl
kpham-sgl merged commit 3a3a861 into sgl-project:main Oct 29, 2026
109 of 125 checks passed
Yuxingwang-intel pushed a commit to Yuxingwang-intel/sglang that referenced this pull request Nov 6, 2026
…gl-project#46592)

Co-authored-by: weireweire <20922698+weireweire@users.noreply.github.com>
Co-authored-by: Po-Han Huang (NVIDIA) <53919306+nvpohanh@users.noreply.github.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