Support Waterfill with MegaMoE backend (#27350)
This commit is contained in:
@@ -1986,12 +1986,12 @@ def _moe_runner_fusion_disable(view: Any) -> dict:
|
||||
|
||||
def _a2a_fusion_adjustments(view: Any) -> dict:
|
||||
"""A2A-backend-driven shared-experts fusion adjustments, declared at the
|
||||
legacy write slots in _handle_a2a_moe: DeepEP Waterfill requires the
|
||||
legacy write slots in _handle_a2a_moe: Waterfill requires the
|
||||
fusion enabled; FlashInfer A2A requires it disabled."""
|
||||
if view.moe_a2a_backend == "deepep" and view.enable_deepep_waterfill:
|
||||
if view.moe_a2a_backend in ("deepep", "megamoe") and view.enable_waterfill:
|
||||
if view.disable_shared_experts_fusion:
|
||||
logger.warning(
|
||||
"disable_shared_experts_fusion is overridden to False because DeepEP Waterfill requires shared expert fusion."
|
||||
"disable_shared_experts_fusion is overridden to False because Waterfill requires shared expert fusion."
|
||||
)
|
||||
return {"disable_shared_experts_fusion": False}
|
||||
return {}
|
||||
@@ -2027,10 +2027,10 @@ _A2A_EP_SPANNING_BACKENDS = frozenset(
|
||||
def _a2a_backend_overrides(view: Any) -> dict:
|
||||
|
||||
moe_a2a_backend = view.moe_a2a_backend
|
||||
if view.enable_deepep_waterfill and moe_a2a_backend != "deepep":
|
||||
if view.enable_waterfill and moe_a2a_backend not in ("deepep", "megamoe"):
|
||||
logger.warning(
|
||||
"moe_a2a_backend is overridden to 'deepep' because DeepEP "
|
||||
"Waterfill requires the DeepEP backend."
|
||||
"moe_a2a_backend is overridden to 'deepep' because Waterfill "
|
||||
"requires the DeepEP or MegaMOE backend."
|
||||
)
|
||||
moe_a2a_backend = "deepep"
|
||||
if envs.SGLANG_OPT_USE_DEEPGEMM_MEGA_MOE.get() and moe_a2a_backend != "megamoe":
|
||||
|
||||
@@ -645,8 +645,8 @@ class Envs:
|
||||
SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK = EnvInt(128)
|
||||
SGLANG_DEEPEP_LL_COMBINE_SEND_NUM_SMS = EnvInt(32)
|
||||
SGLANG_BLACKWELL_OVERLAP_SHARED_EXPERTS_OUTSIDE_SBO = EnvBool(False)
|
||||
# Force dynamic DeepEP Waterfill with runtime EP all-reduce instead of the
|
||||
# default static local-batch path.
|
||||
# Force dynamic Waterfill with runtime EP all-reduce instead of the default
|
||||
# static local-batch path.
|
||||
SGLANG_DISABLE_STATIC_WATERFILL = EnvBool(False)
|
||||
|
||||
# NIXL-EP
|
||||
|
||||
@@ -46,12 +46,12 @@ class HashTopK(nn.Module):
|
||||
self.layer_id = layer_id
|
||||
from sglang.srt.runtime_context import get_server_args
|
||||
|
||||
self.enable_deepep_waterfill = (
|
||||
num_fused_shared_experts > 0 and get_server_args().enable_deepep_waterfill
|
||||
self.enable_waterfill = (
|
||||
num_fused_shared_experts > 0 and get_server_args().enable_waterfill
|
||||
)
|
||||
self.deepep_waterfill_balancer = None
|
||||
self.waterfill_balancer = None
|
||||
|
||||
if self.enable_deepep_waterfill:
|
||||
if self.enable_waterfill:
|
||||
# Waterfill appends the shared expert after EPLB maps routed IDs.
|
||||
topk -= num_fused_shared_experts
|
||||
num_fused_shared_experts = 0
|
||||
@@ -119,18 +119,18 @@ class HashTopK(nn.Module):
|
||||
(0, topk_output.topk_weights.shape[-1] + n)
|
||||
),
|
||||
)
|
||||
return self._apply_deepep_waterfill(topk_output, num_tokens=0)
|
||||
return self._apply_waterfill(topk_output, num_tokens=0)
|
||||
|
||||
def _apply_deepep_waterfill(
|
||||
def _apply_waterfill(
|
||||
self, topk_output: StandardTopKOutput, num_tokens: int
|
||||
) -> StandardTopKOutput:
|
||||
if self.enable_deepep_waterfill and self.deepep_waterfill_balancer is None:
|
||||
if self.enable_waterfill and self.waterfill_balancer is None:
|
||||
raise RuntimeError(
|
||||
"DeepEP waterfill HashTopK must be prepared by ModelRunner before forward."
|
||||
"Waterfill HashTopK must be prepared by ModelRunner before forward."
|
||||
)
|
||||
if self.deepep_waterfill_balancer is None:
|
||||
if self.waterfill_balancer is None:
|
||||
return topk_output
|
||||
return self.deepep_waterfill_balancer.expand_topk(topk_output, num_tokens)
|
||||
return self.waterfill_balancer.expand_topk(topk_output, num_tokens)
|
||||
|
||||
def _forward_torch(
|
||||
self, router_logits: torch.Tensor, input_ids: torch.Tensor
|
||||
@@ -267,7 +267,7 @@ class HashTopK(nn.Module):
|
||||
topk_output = StandardTopKOutput(
|
||||
topk_weights=topk_weights, topk_ids=topk_ids, router_logits=router_logits
|
||||
)
|
||||
topk_output = self._apply_deepep_waterfill(topk_output, hidden_states.shape[0])
|
||||
topk_output = self._apply_waterfill(topk_output, hidden_states.shape[0])
|
||||
if is_hip():
|
||||
_zero_topk_weights_padded_region(
|
||||
topk_output.topk_weights, num_token_non_padded
|
||||
|
||||
@@ -397,12 +397,12 @@ class TopK(MultiPlatformOp):
|
||||
self.layer_id = layer_id
|
||||
from sglang.srt.runtime_context import get_server_args
|
||||
|
||||
self.enable_deepep_waterfill = (
|
||||
num_fused_shared_experts > 0 and get_server_args().enable_deepep_waterfill
|
||||
self.enable_waterfill = (
|
||||
num_fused_shared_experts > 0 and get_server_args().enable_waterfill
|
||||
)
|
||||
|
||||
self.deepep_waterfill_balancer = None
|
||||
if self.enable_deepep_waterfill:
|
||||
self.waterfill_balancer = None
|
||||
if self.enable_waterfill:
|
||||
# TODO(ch-wan): Refactor shared-expert fusion and routed TopK fusion.
|
||||
top_k -= num_fused_shared_experts
|
||||
num_fused_shared_experts = 0
|
||||
@@ -428,17 +428,15 @@ class TopK(MultiPlatformOp):
|
||||
allow_routed_experts_capture=allow_routed_experts_capture,
|
||||
)
|
||||
|
||||
def _apply_deepep_waterfill(
|
||||
self, topk_output: TopKOutput, num_tokens: int
|
||||
) -> TopKOutput:
|
||||
if self.enable_deepep_waterfill and self.deepep_waterfill_balancer is None:
|
||||
def _apply_waterfill(self, topk_output: TopKOutput, num_tokens: int) -> TopKOutput:
|
||||
if self.enable_waterfill and self.waterfill_balancer is None:
|
||||
raise RuntimeError(
|
||||
"DeepEP waterfill TopK must be prepared by ModelRunner before forward."
|
||||
"Waterfill TopK must be prepared by ModelRunner before forward."
|
||||
)
|
||||
if self.deepep_waterfill_balancer is None:
|
||||
if self.waterfill_balancer is None:
|
||||
return topk_output
|
||||
assert TopKOutputChecker.format_is_standard(topk_output)
|
||||
return self.deepep_waterfill_balancer.expand_topk(topk_output, num_tokens)
|
||||
return self.waterfill_balancer.expand_topk(topk_output, num_tokens)
|
||||
|
||||
def forward_native(
|
||||
self,
|
||||
@@ -457,7 +455,7 @@ class TopK(MultiPlatformOp):
|
||||
num_token_non_padded=num_token_non_padded,
|
||||
expert_location_dispatch_info=expert_location_dispatch_info,
|
||||
)
|
||||
return self._apply_deepep_waterfill(topk_output, hidden_states.shape[0])
|
||||
return self._apply_waterfill(topk_output, hidden_states.shape[0])
|
||||
|
||||
def forward_cuda(
|
||||
self,
|
||||
@@ -521,7 +519,7 @@ class TopK(MultiPlatformOp):
|
||||
num_token_non_padded=num_token_non_padded,
|
||||
expert_location_dispatch_info=expert_location_dispatch_info,
|
||||
)
|
||||
return self._apply_deepep_waterfill(topk_output, hidden_states.shape[0])
|
||||
return self._apply_waterfill(topk_output, hidden_states.shape[0])
|
||||
|
||||
def forward_cpu(
|
||||
self,
|
||||
@@ -539,7 +537,7 @@ class TopK(MultiPlatformOp):
|
||||
num_token_non_padded=num_token_non_padded,
|
||||
expert_location_dispatch_info=expert_location_dispatch_info,
|
||||
)
|
||||
return self._apply_deepep_waterfill(topk_output, hidden_states.shape[0])
|
||||
return self._apply_waterfill(topk_output, hidden_states.shape[0])
|
||||
|
||||
def forward_npu(
|
||||
self,
|
||||
@@ -604,7 +602,7 @@ class TopK(MultiPlatformOp):
|
||||
(0, topk_output.topk_weights.shape[-1] + n)
|
||||
),
|
||||
)
|
||||
return self._apply_deepep_waterfill(topk_output, 0)
|
||||
return self._apply_waterfill(topk_output, 0)
|
||||
|
||||
def forward_xpu(
|
||||
self,
|
||||
|
||||
+6
-6
@@ -11,7 +11,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""DeepEP Waterfill: shared expert as 9th routed expert, dispatched to least-loaded rank."""
|
||||
"""Waterfill: shared expert as 9th routed expert, dispatched to least-loaded rank."""
|
||||
|
||||
from typing import NamedTuple, Optional, Tuple
|
||||
|
||||
@@ -29,7 +29,7 @@ _LOCAL_PREF_DENOM = 10
|
||||
|
||||
|
||||
class WaterfillDispatchPlan(NamedTuple):
|
||||
"""Inputs needed by the fused DeepEP Waterfill expansion path."""
|
||||
"""Inputs needed by the fused Waterfill expansion path."""
|
||||
|
||||
# Effective rank load consumed by the fused kernel.
|
||||
rank_load: Tensor
|
||||
@@ -275,10 +275,10 @@ def materialize_waterfill_dispatch_fused(
|
||||
allow_all_ranks: bool = False,
|
||||
target_total: int = 0,
|
||||
) -> Tuple[Tensor, Tensor]:
|
||||
"""Run fused Waterfill rank selection and DeepEP TopK expansion.
|
||||
"""Run fused Waterfill rank selection and TopK expansion.
|
||||
|
||||
The Triton kernel intentionally selects each token's shared-expert rank and
|
||||
writes the expanded DeepEP TopK layout in one pass.
|
||||
writes the expanded TopK layout in one pass.
|
||||
"""
|
||||
num_tokens = topk_ids.shape[0]
|
||||
topk = topk_ids.shape[1]
|
||||
@@ -358,7 +358,7 @@ def expand_topk_with_shared_expert(
|
||||
return expanded_topk_ids, expanded_topk_weights
|
||||
|
||||
|
||||
class DeepEPWaterfillBalancer:
|
||||
class WaterfillBalancer:
|
||||
"""Waterfill load balancer: shared expert fused as real routed expert (topk 8→9)."""
|
||||
|
||||
MIN_BATCH_FOR_BALANCE = 64
|
||||
@@ -484,7 +484,7 @@ class DeepEPWaterfillBalancer:
|
||||
return self._build_static_dispatch_plan(local_routed_counts)
|
||||
|
||||
global_routed_counts, local_tokens_per_rank = (
|
||||
DeepEPWaterfillBalancer._all_reduce_dynamic_rank_load(
|
||||
WaterfillBalancer._all_reduce_dynamic_rank_load(
|
||||
local_routed_counts, num_tokens
|
||||
)
|
||||
)
|
||||
@@ -1631,10 +1631,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
for module in self.model.modules():
|
||||
if not isinstance(module, (TopK, HashTopK)):
|
||||
continue
|
||||
if (
|
||||
not module.enable_deepep_waterfill
|
||||
or module.deepep_waterfill_balancer is not None
|
||||
):
|
||||
if not module.enable_waterfill or module.waterfill_balancer is not None:
|
||||
continue
|
||||
if num_routed_experts is None:
|
||||
num_routed_experts = getattr(
|
||||
@@ -1642,14 +1639,12 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
)
|
||||
if num_routed_experts is None:
|
||||
raise ValueError(
|
||||
"DeepEP waterfill requires model config n_routed_experts."
|
||||
"Waterfill requires model config n_routed_experts."
|
||||
)
|
||||
if balancer_cls is None:
|
||||
from sglang.srt.layers.moe.deepep_waterfill import (
|
||||
DeepEPWaterfillBalancer,
|
||||
)
|
||||
from sglang.srt.layers.moe.waterfill import WaterfillBalancer
|
||||
|
||||
balancer_cls = DeepEPWaterfillBalancer
|
||||
balancer_cls = WaterfillBalancer
|
||||
# Static EPLB remaps TopK ids to physical expert ids before Waterfill.
|
||||
# Redundant experts therefore need to be included in the per-rank
|
||||
# expert count used for Waterfill's shared-expert slot remapping.
|
||||
@@ -1660,7 +1655,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
routed_scaling_factor = module.topk_config.routed_scaling_factor
|
||||
else:
|
||||
routed_scaling_factor = module.routed_scaling_factor
|
||||
module.deepep_waterfill_balancer = balancer_cls(
|
||||
module.waterfill_balancer = balancer_cls(
|
||||
num_routed_experts=num_physical_routed_experts,
|
||||
world_size=self.moe_ep_size,
|
||||
rank=self.moe_ep_rank,
|
||||
@@ -1672,7 +1667,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
num_prepared += 1
|
||||
if num_prepared:
|
||||
log_info_on_rank0(
|
||||
logger, f"Prepared {num_prepared} DeepEP waterfill TopK modules."
|
||||
logger, f"Prepared {num_prepared} Waterfill TopK modules."
|
||||
)
|
||||
|
||||
def _init_lplb_solvers(self):
|
||||
|
||||
@@ -1977,9 +1977,9 @@ class ServerArgs:
|
||||
Optional[str],
|
||||
"The InfiniBand devices for Mooncake Backend transfer, accepts multiple comma-separated devices (e.g., --mooncake-ib-device mlx5_0,mlx5_1). Default is None, which triggers automatic device detection when Mooncake Backend is enabled.",
|
||||
] = None
|
||||
enable_deepep_waterfill: A[
|
||||
enable_waterfill: A[
|
||||
bool,
|
||||
"Enable DeepEP Waterfill: dispatch the shared expert as the 9th routed expert to the least-loaded EP rank. Automatically sets --moe-a2a-backend deepep, implicitly enables shared-expert fusion, and supports --deepep-mode auto, normal, or low_latency. Use auto or low_latency for production decode so CUDA graph remains enabled. Supported on DeepSeek-V3/R1 with EP >= 2.",
|
||||
"Enable Waterfill: dispatch the fused shared expert as an extra routed expert slot to the least-loaded EP rank. Supports DeepEP and MegaMOE MoE A2A backends, implicitly enables shared-expert fusion, and supports --deepep-mode auto, normal, or low_latency when used with DeepEP. Use auto or low_latency for production DeepEP decode so CUDA graph remains enabled. Supported on DeepSeek-V3/R1 with EP >= 2.",
|
||||
] = False
|
||||
elastic_ep_rejoin: A[
|
||||
bool,
|
||||
@@ -1992,7 +1992,7 @@ class ServerArgs:
|
||||
disable_shared_experts_fusion: A[
|
||||
bool,
|
||||
Arg(
|
||||
help="Disable the built-in shared experts fusion optimization for DeepSeek V3/R1. Note: DeepEP Waterfill (--enable-deepep-waterfill) still routes shared expert through DeepEP as an extra MoE slot, so shared expert is not separated from the MoE path when Waterfill is enabled.",
|
||||
help="Disable the built-in shared experts fusion optimization for DeepSeek V3/R1. Note: Waterfill (--enable-waterfill) routes the shared expert as an extra MoE slot, so the shared expert is not separated from the MoE path when Waterfill is enabled.",
|
||||
resolvable=True,
|
||||
),
|
||||
] = False
|
||||
@@ -5492,6 +5492,10 @@ class ServerArgs:
|
||||
run_post_process_pass(self, _a2a_fusion_adjustments)
|
||||
|
||||
a2a_backend = resolved_view(self).moe_a2a_backend
|
||||
if self.enable_waterfill:
|
||||
self.enforce_shared_experts_fusion = True
|
||||
logger.info(f"Waterfill is enabled with moe_a2a_backend='{a2a_backend}'.")
|
||||
|
||||
if a2a_backend == "megamoe":
|
||||
if not envs.SGLANG_OPT_FIX_MEGA_MOE_MEMORY.is_set():
|
||||
envs.SGLANG_OPT_FIX_MEGA_MOE_MEMORY.set(True)
|
||||
@@ -5508,11 +5512,6 @@ class ServerArgs:
|
||||
logger.warning(
|
||||
f"DeepEP MoE is enabled. The expert parallel size is adjusted to be the same as the tensor parallel size[{self.tp_size}]."
|
||||
)
|
||||
if self.enable_deepep_waterfill:
|
||||
self.enforce_shared_experts_fusion = True
|
||||
logger.info(
|
||||
"DeepEP Waterfill is enabled. Shared expert will be dispatched through DeepEP for load balancing."
|
||||
)
|
||||
|
||||
if a2a_backend == "mooncake":
|
||||
logger.warning(
|
||||
|
||||
Reference in New Issue
Block a user