[Fix] Select custom all-reduce v2 by topology capability (#35061)
Co-authored-by: xingyuliu <xingyuliu@fb.com>
This commit is contained in:
+126
-67
@@ -112,65 +112,137 @@ def _pack_heuristic(*args) -> Heuristic:
|
||||
return Heuristic(*arg_list)
|
||||
|
||||
|
||||
def _sm100_config(world_size: int, num_sm: int) -> AllReduceConfig:
|
||||
# SM100 (Blackwell, B200/B300/GB200). Tuned on B200 (148 SMs); world 16 on GB200.
|
||||
graph_map = {
|
||||
2: (8.000 * MB, 32.00 * MB, 128.0 * MB),
|
||||
3: (4.000 * MB, 4.000 * MB, 128.0 * MB),
|
||||
4: (2.250 * MB, 2.250 * MB, 128.0 * MB),
|
||||
5: (1.500 * MB, 1.500 * MB, 128.0 * MB),
|
||||
6: (1.000 * MB, 1.000 * MB, 128.0 * MB),
|
||||
7: (0.625 * MB, 0.625 * MB, 128.0 * MB),
|
||||
8: (0.500 * MB, 0.500 * MB, 128.0 * MB, Range(8 * MB, 128 * MB)),
|
||||
16: (0.250 * MB, 0.250 * MB, 128.0 * MB, Range(256 * KB, 128 * MB)),
|
||||
# SM100 (Blackwell, B200/B300/GB200). Tuned on B200 (148 SMs); world 16 on GB200.
|
||||
@cache
|
||||
def _sm100_configs(num_sm: int) -> dict[int, AllReduceConfig]:
|
||||
mc_blocks = {5: 64, 6: 48, 7: 48, 8: 32, 16: 32}
|
||||
|
||||
def config(world_size: int, *, graph: tuple, eager: tuple) -> AllReduceConfig:
|
||||
return AllReduceConfig(
|
||||
graph=_pack_heuristic(*graph),
|
||||
eager=_pack_heuristic(*eager),
|
||||
num_push_blocks=num_sm,
|
||||
num_pull_blocks=num_sm if world_size == 2 else 96,
|
||||
num_mc_blocks=mc_blocks.get(world_size),
|
||||
)
|
||||
|
||||
return {
|
||||
2: config(
|
||||
2,
|
||||
graph=(8.000 * MB, 32.00 * MB, 128.0 * MB),
|
||||
eager=(16.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
),
|
||||
3: config(
|
||||
3,
|
||||
graph=(4.000 * MB, 4.000 * MB, 128.0 * MB),
|
||||
eager=(8.000 * MB, 8.000 * MB, 32.00 * MB),
|
||||
),
|
||||
4: config(
|
||||
4,
|
||||
graph=(2.250 * MB, 2.250 * MB, 128.0 * MB),
|
||||
eager=(3.000 * MB, 3.000 * MB, 32.00 * MB),
|
||||
),
|
||||
5: config(
|
||||
5,
|
||||
graph=(1.500 * MB, 1.500 * MB, 128.0 * MB),
|
||||
eager=(2.000 * MB, 2.000 * MB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
),
|
||||
6: config(
|
||||
6,
|
||||
graph=(1.000 * MB, 1.000 * MB, 128.0 * MB),
|
||||
eager=(1.250 * MB, 1.250 * MB, 64.00 * MB, Range(0, 64 * MB)),
|
||||
),
|
||||
7: config(
|
||||
7,
|
||||
graph=(0.625 * MB, 0.625 * MB, 128.0 * MB),
|
||||
eager=(1.000 * MB, 1.000 * MB, 64.00 * MB, Range(0, 64 * MB)),
|
||||
),
|
||||
8: config(
|
||||
8,
|
||||
graph=(0.500 * MB, 0.500 * MB, 128.0 * MB, Range(8 * MB, 128 * MB)),
|
||||
eager=(0.750 * MB, 0.750 * MB, 128.0 * MB, Range(0, 128 * MB)),
|
||||
),
|
||||
16: config(
|
||||
16,
|
||||
graph=(0.250 * MB, 0.250 * MB, 128.0 * MB, Range(256 * KB, 128 * MB)),
|
||||
eager=(0.250 * MB, 0.250 * MB, 128.0 * MB, Range(256 * KB, 128 * MB)),
|
||||
),
|
||||
}
|
||||
eager_map = {
|
||||
2: (16.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
3: (8.000 * MB, 8.000 * MB, 32.00 * MB),
|
||||
4: (3.000 * MB, 3.000 * MB, 32.00 * MB),
|
||||
5: (2.000 * MB, 2.000 * MB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
6: (1.250 * MB, 1.250 * MB, 64.00 * MB, Range(0, 64 * MB)),
|
||||
7: (1.000 * MB, 1.000 * MB, 64.00 * MB, Range(0, 64 * MB)),
|
||||
8: (0.750 * MB, 0.750 * MB, 128.0 * MB, Range(0, 128 * MB)),
|
||||
16: (0.250 * MB, 0.250 * MB, 128.0 * MB, Range(256 * KB, 128 * MB)),
|
||||
}
|
||||
mc_blocks_map = {5: 64, 6: 48, 7: 48, 8: 32, 16: 32}
|
||||
return AllReduceConfig(
|
||||
graph=_pack_heuristic(*graph_map[world_size]),
|
||||
eager=_pack_heuristic(*eager_map[world_size]),
|
||||
num_push_blocks=num_sm,
|
||||
num_pull_blocks=num_sm if world_size == 2 else 96,
|
||||
num_mc_blocks=mc_blocks_map.get(world_size, None),
|
||||
)
|
||||
|
||||
|
||||
def _sm90_config(world_size: int, num_sm: int) -> AllReduceConfig:
|
||||
# SM90 (Hopper, H100/H200). Tuned on H200.
|
||||
graph_map = {
|
||||
2: (16.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
3: (1.250 * MB, 1.250 * MB, 128.0 * MB),
|
||||
4: (384.0 * KB, 384.0 * KB, 128.0 * MB),
|
||||
5: (192.0 * KB, 192.0 * KB, 32.00 * MB),
|
||||
6: (128.0 * KB, 128.0 * KB, 32.00 * MB, Range(8 * MB, 32 * MB)),
|
||||
7: (128.0 * KB, 128.0 * KB, 32.00 * MB, Range(1 * MB, 32 * MB)),
|
||||
8: (128.0 * KB, 128.0 * KB, 32.00 * MB, Range(512 * KB, 128 * MB)),
|
||||
# SM90 (Hopper, H100/H200). Tuned on H200.
|
||||
@cache
|
||||
def _sm90_configs(num_sm: int) -> dict[int, AllReduceConfig]:
|
||||
def config(world_size: int, *, graph: tuple, eager: tuple) -> AllReduceConfig:
|
||||
return AllReduceConfig(
|
||||
graph=_pack_heuristic(*graph),
|
||||
eager=_pack_heuristic(*eager),
|
||||
num_push_blocks=num_sm,
|
||||
num_pull_blocks=64,
|
||||
num_mc_blocks=None if world_size < 4 else 128 // world_size,
|
||||
)
|
||||
|
||||
return {
|
||||
2: config(
|
||||
2,
|
||||
graph=(16.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
eager=(32.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
),
|
||||
3: config(
|
||||
3,
|
||||
graph=(1.250 * MB, 1.250 * MB, 128.0 * MB),
|
||||
eager=(3.000 * MB, 3.000 * MB, 16.00 * MB),
|
||||
),
|
||||
4: config(
|
||||
4,
|
||||
graph=(384.0 * KB, 384.0 * KB, 128.0 * MB),
|
||||
eager=(896.0 * KB, 896.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
),
|
||||
5: config(
|
||||
5,
|
||||
graph=(192.0 * KB, 192.0 * KB, 32.00 * MB),
|
||||
eager=(384.0 * KB, 384.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
),
|
||||
6: config(
|
||||
6,
|
||||
graph=(128.0 * KB, 128.0 * KB, 32.00 * MB, Range(8 * MB, 32 * MB)),
|
||||
eager=(192.0 * KB, 192.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
),
|
||||
7: config(
|
||||
7,
|
||||
graph=(128.0 * KB, 128.0 * KB, 32.00 * MB, Range(1 * MB, 32 * MB)),
|
||||
eager=(128.0 * KB, 128.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
),
|
||||
8: config(
|
||||
8,
|
||||
graph=(128.0 * KB, 128.0 * KB, 32.00 * MB, Range(512 * KB, 128 * MB)),
|
||||
eager=(128.0 * KB, 128.0 * KB, 128.0 * MB, Range(0, 128 * MB)),
|
||||
),
|
||||
}
|
||||
eager_map = {
|
||||
2: (32.00 * MB, 128.0 * MB, 128.0 * MB),
|
||||
3: (3.000 * MB, 3.000 * MB, 16.00 * MB),
|
||||
4: (896.0 * KB, 896.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
5: (384.0 * KB, 384.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
6: (192.0 * KB, 192.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
7: (128.0 * KB, 128.0 * KB, 32.00 * MB, Range(0, 32 * MB)),
|
||||
8: (128.0 * KB, 128.0 * KB, 128.0 * MB, Range(0, 128 * MB)),
|
||||
}
|
||||
return AllReduceConfig(
|
||||
graph=_pack_heuristic(*graph_map[world_size]),
|
||||
eager=_pack_heuristic(*eager_map[world_size]),
|
||||
|
||||
|
||||
@cache
|
||||
def _get_all_reduce_configs() -> dict[int, AllReduceConfig]:
|
||||
cuda_major, _ = torch.cuda.get_device_capability()
|
||||
num_sm = torch.cuda.get_device_properties().multi_processor_count
|
||||
if cuda_major == 9:
|
||||
return _sm90_configs(num_sm)
|
||||
if cuda_major == 10:
|
||||
return _sm100_configs(num_sm)
|
||||
|
||||
default = AllReduceConfig(
|
||||
graph=Heuristic(1 * MB, 1 * MB, 16 * MB),
|
||||
eager=Heuristic(1 * MB, 1 * MB, 16 * MB),
|
||||
num_push_blocks=num_sm,
|
||||
num_pull_blocks=64,
|
||||
num_mc_blocks=None if world_size < 4 else 128 // world_size,
|
||||
num_pull_blocks=num_sm,
|
||||
num_mc_blocks=None,
|
||||
)
|
||||
return {world_size: default for world_size in range(2, 17)}
|
||||
|
||||
|
||||
@cache
|
||||
def get_supported_world_sizes() -> tuple[int, ...]:
|
||||
return tuple(_get_all_reduce_configs())
|
||||
|
||||
|
||||
@cache
|
||||
@@ -180,17 +252,4 @@ def get_all_reduce_config(world_size: int) -> AllReduceConfig:
|
||||
Only SM90 and SM100 are benchmarked so far; other archs get a
|
||||
conservative default (1 MB one-shot crossovers, no multicast).
|
||||
"""
|
||||
cuda_major, _ = torch.cuda.get_device_capability()
|
||||
num_sm = torch.cuda.get_device_properties().multi_processor_count
|
||||
if cuda_major == 9:
|
||||
return _sm90_config(world_size, num_sm)
|
||||
if cuda_major == 10:
|
||||
return _sm100_config(world_size, num_sm)
|
||||
default = Heuristic(1 * MB, 1 * MB, 16 * MB)
|
||||
return AllReduceConfig(
|
||||
graph=default,
|
||||
eager=default,
|
||||
num_push_blocks=num_sm,
|
||||
num_pull_blocks=num_sm,
|
||||
num_mc_blocks=None,
|
||||
)
|
||||
return _get_all_reduce_configs()[world_size]
|
||||
|
||||
@@ -42,7 +42,10 @@ from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph impo
|
||||
is_in_tc_piecewise_cuda_graph,
|
||||
)
|
||||
|
||||
from .configs.custom_all_reduce_v2 import get_all_reduce_config
|
||||
from .configs.custom_all_reduce_v2 import (
|
||||
get_all_reduce_config,
|
||||
get_supported_world_sizes,
|
||||
)
|
||||
from .custom_all_reduce_utils import (
|
||||
can_use_custom_all_reduce_with_nvlink,
|
||||
is_one_nvlink_clique,
|
||||
@@ -447,8 +450,7 @@ class CustomAllReduceV2:
|
||||
|
||||
|
||||
def _is_vmm_backed_allocator(device: torch.device) -> bool:
|
||||
"""True iff the caching allocator is VMM-backed (expandable_segments). Uniform
|
||||
launch, so the local probe reflects every rank."""
|
||||
"""Check whether expandable-segments VMM backs the caching allocator."""
|
||||
probe = torch.empty(1, dtype=torch.uint8, device=device)
|
||||
return is_vmm_pointer(probe.data_ptr())
|
||||
|
||||
@@ -457,34 +459,15 @@ def can_use_custom_all_reduce_v2(
|
||||
group: ProcessGroup,
|
||||
device: torch.device,
|
||||
) -> bool:
|
||||
# Multi-node (MNNVL): the node-local NVLink/P2P topology checks below
|
||||
# are meaningless across nodes; the torch symm-mem rendezvous (fabric
|
||||
# handles) is the real capability gate there.
|
||||
if envs.SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE.get() and not all(
|
||||
in_the_same_node_as(group, source_rank=0)
|
||||
):
|
||||
world_size = dist.get_world_size(group=group)
|
||||
if world_size in range(2, 9):
|
||||
logger.warning(
|
||||
"CustomAllReduceV2 enabled on a multi-node group "
|
||||
"(world_size=%d); graph zero-copy is disabled.",
|
||||
world_size,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
supported = list(range(2, 17))
|
||||
supported = get_supported_world_sizes()
|
||||
if dist.get_world_size(group=group) not in supported:
|
||||
return False
|
||||
# Multi-node needs a single NVLink clique (one NVL72 / MNNVL domain) whose
|
||||
# allocator is VMM-backed: graph inputs cross nodes via FABRIC / POSIX-fd VMM
|
||||
# handles, not cudaIpc (intra-node only). Else use the intra-node nvlink check.
|
||||
if not all(in_the_same_node_as(group, source_rank=0)):
|
||||
return is_one_nvlink_clique(group, device) and _is_vmm_backed_allocator(device)
|
||||
full_nvlink = can_use_custom_all_reduce_with_nvlink(
|
||||
group=group,
|
||||
device=device,
|
||||
supported_world_size=supported,
|
||||
supported_world_size=list(supported),
|
||||
cls_name="CustomAllReduceV2",
|
||||
)
|
||||
return full_nvlink is True
|
||||
|
||||
@@ -724,14 +724,8 @@ class GroupCoordinator:
|
||||
self.pymscclpp_comm is not None
|
||||
and self.pymscclpp_comm.should_mscclpp_allreduce(input_)
|
||||
)
|
||||
# With the MNNVL opt-in, let CustomAllReduceV2 take eligible (small)
|
||||
# inputs ahead of the symm-mem pynccl fast path; otherwise pynccl
|
||||
# would absorb every all-reduce whenever --enable-symm-mem is on and
|
||||
# v2 never runs. Large inputs fail should_custom_ar and still go to
|
||||
# the symm-mem path below.
|
||||
_ca_takes_input = (
|
||||
_CA_V2_MULTINODE
|
||||
and self.ca_comm is not None
|
||||
should_use_custom_allreduce = (
|
||||
self.ca_comm is not None
|
||||
and not self.ca_comm.disabled
|
||||
and self.ca_comm.should_custom_ar(input_)
|
||||
)
|
||||
@@ -739,7 +733,7 @@ class GroupCoordinator:
|
||||
self.pynccl_comm is not None
|
||||
and self.is_symmetric_memory_enabled()
|
||||
and not should_use_pymscclpp_allreduce
|
||||
and not _ca_takes_input
|
||||
and not should_use_custom_allreduce
|
||||
):
|
||||
self.debug_check_symmetric_mempool(self, {"input": input_}, "all_reduce")
|
||||
with self.pynccl_comm.change_state(enable=True):
|
||||
@@ -2104,9 +2098,6 @@ logger = logging.getLogger(__name__)
|
||||
_ENABLE_CUSTOM_ALL_REDUCE = True
|
||||
_ENABLE_MSCCLPP_ALL_REDUCE = False
|
||||
_ENABLE_TORCH_SYMM_MEM_ALL_REDUCE = False
|
||||
# Read once at import: whether CustomAllReduceV2 is opted in on a multi-node
|
||||
# (MNNVL) group. Used on the all_reduce hot path (see GroupCoordinator).
|
||||
_CA_V2_MULTINODE = envs.SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE.get()
|
||||
_ENABLE_FLASHINFER_ALLREDUCE_ONLY = False
|
||||
|
||||
|
||||
|
||||
@@ -1104,14 +1104,6 @@ class Envs:
|
||||
SGLANG_CUSTOM_ALL_REDUCE_V2_MAX_SIZE_KB = EnvInt(16 * 1024)
|
||||
SGLANG_FORCE_CUSTOM_ALL_REDUCE_V2_PULL_SIZE_KB = EnvInt(None)
|
||||
SGLANG_FORCE_CUSTOM_ALL_REDUCE_V2_PUSH_SIZE_KB = EnvInt(None)
|
||||
# Allow CustomAllReduceV2 on a process group that spans nodes (MNNVL
|
||||
# fabric). Requires torch symmetric memory to rendezvous across nodes
|
||||
# (fabric handles + IMEX). Graph zero-copy input registration is not
|
||||
# supported in this mode and is disabled; all-reduce inside CUDA graphs
|
||||
# falls back to eager pull from the symm workspace. Auto-enabled on
|
||||
# MNNVL-fabric devices (GB200/GB300) when nnodes > 1; set 0/1 to
|
||||
# override in either direction.
|
||||
SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE = EnvBool(False)
|
||||
|
||||
# ===================================================================
|
||||
# RoPE cache
|
||||
|
||||
@@ -8026,41 +8026,6 @@ class ServerArgs:
|
||||
"1" if requested_transport == "cuda_ipc" else "0"
|
||||
)
|
||||
|
||||
def _handle_custom_all_reduce_v2_multinode(self):
|
||||
# Custom all-reduce v2's graph zero-copy path uses IPC handles and is
|
||||
# intra-node only. On MNNVL-fabric devices (GB200/GB300) the eager pull
|
||||
# path works across nodes via the symm-mem workspace, so opt into the
|
||||
# multinode mode automatically (a failed fabric rendezvous falls back
|
||||
# to the legacy path at init). Elsewhere force-disable v2 on
|
||||
# multi-node so the dispatch falls back to the legacy CustomAllreduce
|
||||
# path, unless the MNNVL opt-in is set explicitly.
|
||||
if self.nnodes <= 1 or not envs.SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2.get():
|
||||
return
|
||||
if (
|
||||
not envs.SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE.is_set()
|
||||
and is_mnnvl_fabric_device()
|
||||
# CustomAllReduceV2 supports world sizes 2..8 only
|
||||
# (can_use_custom_all_reduce_v2 rejects larger groups); don't
|
||||
# auto-opt-in a TP16+ launch just to fall back downstream.
|
||||
and self.tp_size <= 8
|
||||
):
|
||||
logger.info(
|
||||
"MNNVL fabric device detected with nnodes=%d: enabling "
|
||||
"custom all-reduce v2 multinode mode "
|
||||
"(SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE=1; set it "
|
||||
"to 0 to opt out).",
|
||||
self.nnodes,
|
||||
)
|
||||
envs.SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE.set("1")
|
||||
if not envs.SGLANG_ENABLE_CUSTOM_ALL_REDUCE_V2_MULTINODE.get():
|
||||
if envs.SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2.is_set():
|
||||
logger.warning(
|
||||
"Disabling SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2 because nnodes=%d "
|
||||
"(custom all-reduce v2 is intra-node only).",
|
||||
self.nnodes,
|
||||
)
|
||||
envs.SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2.set("0")
|
||||
|
||||
def _handle_environment_variables(self):
|
||||
self._handle_multimodal_feature_transport()
|
||||
envs.SGLANG_ENABLE_TORCH_COMPILE.set("1" if self.enable_torch_compile else "0")
|
||||
@@ -8072,7 +8037,6 @@ class ServerArgs:
|
||||
envs.SGLANG_ENABLE_DETERMINISTIC_INFERENCE.set(
|
||||
"1" if self.enable_deterministic_inference else "0"
|
||||
)
|
||||
self._handle_custom_all_reduce_v2_multinode()
|
||||
if self.enable_deterministic_inference:
|
||||
envs.SGLANG_FLASHINFER_MOE_FUSED_FINALIZE.set("0")
|
||||
if self.debug_cuda_graph:
|
||||
|
||||
Reference in New Issue
Block a user