From 51c5ddbe65c1fe9790e8cba3c52c2b404686ceca Mon Sep 17 00:00:00 2001
From: Lucia Fang <116399278+luccafong@users.noreply.github.com>
Date: Fri, 10 Jul 2026 21:45:17 -0700
Subject: [PATCH] [eplb] chunk expert-weight P2P on CUDA to prevent NCCL
rebalance hang (#30829)
---
.../docs/references/environment_variables.mdx | 4 +-
python/sglang/srt/environ.py | 5 ++
.../srt/eplb/expert_location_updater.py | 47 ++++++++-----------
3 files changed, 26 insertions(+), 30 deletions(-)
diff --git a/docs_new/docs/references/environment_variables.mdx b/docs_new/docs/references/environment_variables.mdx
index 50802c1fb..d3268993d 100644
--- a/docs_new/docs/references/environment_variables.mdx
+++ b/docs_new/docs/references/environment_variables.mdx
@@ -59,8 +59,8 @@ SGLang supports various environment variables that can be used to configure its
0 |
- SGLANG_EPLB_ROCM_P2P_BATCH_CHUNK_SIZE |
- Number of logical expert IDs per batch when submitting P2P ops during EPLB rebalance on ROCm. Smaller values prevent RCCL GPU-side accumulation hangs but increase overhead. |
+ SGLANG_EPLB_P2P_BATCH_CHUNK_SIZE |
+ Number of expert IDs per batch when submitting P2P ops during EPLB rebalance (CUDA and ROCm). Smaller values prevent NCCL/RCCL GPU-side accumulation hangs but increase overhead; set >= num_physical_experts to submit a single batch. Deprecated alias: SGLANG_EPLB_ROCM_P2P_BATCH_CHUNK_SIZE. |
32 |
diff --git a/python/sglang/srt/environ.py b/python/sglang/srt/environ.py
index 78cd951b1..8303280d9 100644
--- a/python/sglang/srt/environ.py
+++ b/python/sglang/srt/environ.py
@@ -599,6 +599,11 @@ class Envs:
SGLANG_EXPERT_DISTRIBUTION_RECORDER_DIR = EnvStr("/tmp")
SGLANG_EPLB_HEATMAP_COLLECTION_INTERVAL = EnvInt(0)
SGLANG_ENABLE_EPLB_BALANCEDNESS_METRIC = EnvBool(False)
+ # Chunk size for the rebalance expert-weight P2P exchange; set
+ # >= num_physical_experts to submit a single batch_isend_irecv.
+ SGLANG_EPLB_P2P_BATCH_CHUNK_SIZE = EnvIntWithAlias(
+ 32, deprecated_name="SGLANG_EPLB_ROCM_P2P_BATCH_CHUNK_SIZE"
+ )
# TBO
SGLANG_TBO_DEBUG = EnvBool(False)
diff --git a/python/sglang/srt/eplb/expert_location_updater.py b/python/sglang/srt/eplb/expert_location_updater.py
index 694e62487..7873223f0 100644
--- a/python/sglang/srt/eplb/expert_location_updater.py
+++ b/python/sglang/srt/eplb/expert_location_updater.py
@@ -21,20 +21,19 @@ import torch.distributed
from torch.distributed import P2POp
from sglang.srt.elastic_ep.elastic_ep import ElasticEPStateManager
+from sglang.srt.environ import envs
from sglang.srt.eplb.expert_location import (
ExpertLocationMetadata,
get_global_expert_location_metadata,
)
from sglang.srt.runtime_context import get_server_args
-from sglang.srt.utils import get_bool_env_var, get_int_env_var, is_hip
+from sglang.srt.utils import get_bool_env_var
logger = logging.getLogger(__name__)
_LOG_INPUT = get_bool_env_var("SGLANG_EXPERT_LOCATION_UPDATER_LOG_INPUT")
-_is_hip = is_hip()
-
class ExpertLocationUpdater:
def __init__(self):
@@ -485,31 +484,23 @@ def update_expert_weights_single_layer(
if len(p2p_ops) == 0:
return
- if _is_hip:
- # Submit P2P ops in batches to prevent RCCL GPU-side
- # accumulation hangs. All ranks use the same expert_id ranges
- # (based on num_physical_experts) to ensure matching send/recv
- # pairs land in the same batch. Setting batch_chunk_size >=
- # num_physical_experts disables batching behavior.
- batch_chunk_size = get_int_env_var(
- "SGLANG_EPLB_ROCM_P2P_BATCH_CHUNK_SIZE", 32
- )
- ops_by_expert = {eid: ops for eid, ops in sorted_infos}
- for start in range(0, num_physical_experts, batch_chunk_size):
- batch_ops = []
- for eid in range(
- start, min(start + batch_chunk_size, num_physical_experts)
- ):
- if eid in ops_by_expert:
- batch_ops.extend(ops_by_expert[eid])
- if batch_ops:
- reqs = torch.distributed.batch_isend_irecv(batch_ops)
- for req in reqs:
- req.wait()
- else:
- reqs = torch.distributed.batch_isend_irecv(p2p_ops)
- for req in reqs:
- req.wait()
+ # Submit P2P ops in batches to prevent NCCL/RCCL GPU-side accumulation
+ # hangs on large rebalances. All ranks use the same expert_id ranges
+ # (based on num_physical_experts) so matching send/recv pairs land in
+ # the same batch. Set batch_chunk_size >= num_physical_experts to disable.
+ batch_chunk_size = envs.SGLANG_EPLB_P2P_BATCH_CHUNK_SIZE.get()
+ ops_by_expert = {eid: ops for eid, ops in sorted_infos}
+ for start in range(0, num_physical_experts, batch_chunk_size):
+ batch_ops = []
+ for eid in range(
+ start, min(start + batch_chunk_size, num_physical_experts)
+ ):
+ if eid in ops_by_expert:
+ batch_ops.extend(ops_by_expert[eid])
+ if batch_ops:
+ reqs = torch.distributed.batch_isend_irecv(batch_ops)
+ for req in reqs:
+ req.wait()
def _execute_buffer2weight_copies(buffer2weight_copy_infos):
for (