Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

585 lines
21 KiB
Python

# Copyright 2023-2026 SGLang Team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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."""
from typing import NamedTuple, Optional, Tuple
import torch
import triton
import triton.language as tl
from torch import Tensor
from sglang.srt.environ import envs
from sglang.srt.layers.moe.topk import StandardTopKOutput
LOCAL_SHARED_MARKER = -1 # Invalid expert ID; DeepEP ignores expert_id < 0.
_LOCAL_PREF_NUMER = 11 # local-rank preference = 11/10
_LOCAL_PREF_DENOM = 10
class WaterfillDispatchPlan(NamedTuple):
"""Inputs needed by the fused DeepEP Waterfill expansion path."""
# Effective rank load consumed by the fused kernel.
rank_load: Tensor
allow_all_ranks: bool
target_total: int
def _empty_expanded(topk_ids: Tensor, topk_weights: Tensor):
"""Return empty expanded tensors for zero-token batches."""
topk, d = topk_ids.shape[1], topk_ids.device
return (
torch.empty(0, topk + 1, dtype=topk_ids.dtype, device=d),
torch.empty(0, topk + 1, dtype=topk_weights.dtype, device=d),
)
@triton.jit
def _count_routed_per_rank_kernel(
topk_ids_ptr, # [num_tokens, topk]
counts_ptr, # [world_size] output (atomic add)
num_tokens,
topk: tl.constexpr,
experts_per_rank,
world_size: tl.constexpr,
BLOCK_SIZE: tl.constexpr,
):
"""Count routed tokens per rank using block-level histogram."""
pid = tl.program_id(0)
token_idx = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = token_idx < num_tokens
for r in range(world_size):
rank_count = tl.zeros([BLOCK_SIZE], dtype=tl.int64)
for k in range(topk):
expert_id = tl.load(
topk_ids_ptr + token_idx * topk + k, mask=mask, other=-1
).to(tl.int64)
valid = expert_id >= 0
target_rank = expert_id // experts_per_rank
target_rank = tl.minimum(tl.maximum(target_rank, 0), world_size - 1)
rank_count += tl.where(
mask & valid & (target_rank == r),
tl.full([BLOCK_SIZE], 1, dtype=tl.int64),
tl.zeros([BLOCK_SIZE], dtype=tl.int64),
)
block_total = tl.sum(rank_count)
if block_total > 0:
tl.atomic_add(counts_ptr + r, block_total)
@triton.jit
def _waterfill_expand_kernel(
topk_ids_ptr,
topk_weights_ptr,
rank_load_ptr,
expanded_ids_ptr,
expanded_weights_ptr,
num_tokens,
topk: tl.constexpr,
old_experts_per_rank,
new_experts_per_rank,
world_size: tl.constexpr,
source_rank,
shared_weight,
local_marker,
local_pref_numer,
local_pref_denom,
precomputed_target_total,
ALLOW_ALL_RANKS: tl.constexpr,
BLOCK_SIZE: tl.constexpr,
):
"""Fused waterfill + expand. ID remap: old_id -> old_id + old_id // old_epr."""
pid = tl.program_id(0)
token_idx = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = token_idx < num_tokens
r_idx = tl.arange(0, world_size)
rank_load_vec = tl.load(rank_load_ptr + r_idx, mask=r_idx < world_size, other=0).to(
tl.int64
)
total_effective_k = tl.sum(rank_load_vec)
total_tokens_global_k = total_effective_k // topk
derived_target_total = (
total_effective_k + total_tokens_global_k + world_size - 1
) // world_size
target_total = tl.where(
precomputed_target_total > 0,
precomputed_target_total,
derived_target_total,
)
# Step 1: Select destination rank for shared expert (waterfill sampling).
source_count = tl.load(rank_load_ptr + source_rank)
best_count = tl.where(mask, source_count, 2**30)
best_rank = tl.full([BLOCK_SIZE], source_rank, dtype=tl.int64)
has_valid = tl.zeros([BLOCK_SIZE], dtype=tl.int1)
src_rank_i32 = tl.full([BLOCK_SIZE], source_rank, dtype=tl.int32)
if ALLOW_ALL_RANKS:
candidate_mask = tl.full([BLOCK_SIZE], (1 << world_size) - 1, dtype=tl.int32)
for r in range(world_size):
target_count = tl.load(rank_load_ptr + r).to(tl.int64)
better = (
target_count * local_pref_numer < best_count * local_pref_denom
) & mask
best_count = tl.where(better, target_count, best_count)
best_rank = tl.where(
better, tl.full([BLOCK_SIZE], r, dtype=tl.int64), best_rank
)
else:
candidate_mask = (tl.full([BLOCK_SIZE], 1, dtype=tl.int32) << src_rank_i32).to(
tl.int32
)
for k in range(topk):
expert_id = tl.load(
topk_ids_ptr + token_idx * topk + k, mask=mask, other=-1
).to(tl.int64)
valid = expert_id >= 0
has_valid = has_valid | valid
if not ALLOW_ALL_RANKS:
target_rank = expert_id // old_experts_per_rank
target_rank = tl.minimum(tl.maximum(target_rank, 0), world_size - 1)
target_rank_i32 = target_rank.to(tl.int32)
shift_amt = tl.where(valid, target_rank_i32, 0)
bit = tl.full([BLOCK_SIZE], 1, dtype=tl.int32) << shift_amt
candidate_mask = tl.where(
valid & mask, candidate_mask | bit, candidate_mask
)
target_count = tl.load(
rank_load_ptr + target_rank, mask=mask & valid, other=2**30
)
better = (
(target_count * local_pref_numer < best_count * local_pref_denom)
& valid
& mask
)
best_count = tl.where(better, target_count, best_count)
best_rank = tl.where(better, target_rank, best_rank)
total_w = tl.zeros([BLOCK_SIZE], dtype=tl.int32)
for r in range(world_size):
present = ((candidate_mask >> r) & 1) == 1
rank_load_r = tl.load(rank_load_ptr + r).to(tl.int64)
w = tl.where(target_total > rank_load_r, target_total - rank_load_r, 0).to(
tl.int32
)
w_vec = tl.full([BLOCK_SIZE], w, dtype=tl.int32)
w_vec = tl.where(
src_rank_i32 == r,
w_vec,
(w_vec * local_pref_denom) // local_pref_numer,
)
total_w += tl.where(present, w_vec, 0)
token_seed = token_idx.to(tl.uint32) ^ (
src_rank_i32.to(tl.uint32) * tl.full([BLOCK_SIZE], 0x9E3779B9, dtype=tl.uint32)
)
token_seed = token_seed * tl.full([BLOCK_SIZE], 1664525, dtype=tl.uint32) + tl.full(
[BLOCK_SIZE], 1013904223, dtype=tl.uint32
)
u = tl.where(total_w > 0, token_seed % total_w.to(tl.uint32), 0).to(tl.int32)
chosen = src_rank_i32
cum = tl.zeros([BLOCK_SIZE], dtype=tl.int32)
for r in range(world_size):
present = ((candidate_mask >> r) & 1) == 1
rank_load_r = tl.load(rank_load_ptr + r).to(tl.int64)
w = tl.where(target_total > rank_load_r, target_total - rank_load_r, 0).to(
tl.int32
)
w_vec = tl.full([BLOCK_SIZE], w, dtype=tl.int32)
w_vec = tl.where(
src_rank_i32 == r,
w_vec,
(w_vec * local_pref_denom) // local_pref_numer,
)
w_vec = tl.where(present, w_vec, 0)
pick = (total_w > 0) & present & (u >= cum) & (u < (cum + w_vec))
chosen = tl.where(pick, r, chosen)
cum += w_vec
best_rank = tl.where(total_w > 0, chosen.to(tl.int64), best_rank)
# Step 2: Compute shared expert ID and local mask.
is_local = best_rank == source_rank
local_shared_id = source_rank * new_experts_per_rank + old_experts_per_rank
remote_shared_id = best_rank * new_experts_per_rank + old_experts_per_rank
shared_expert_id = tl.where(
is_local,
tl.full([BLOCK_SIZE], local_shared_id, dtype=tl.int64),
remote_shared_id,
).to(tl.int64)
shared_expert_id = tl.where(
has_valid,
shared_expert_id,
tl.full([BLOCK_SIZE], local_marker, dtype=tl.int64),
)
# Step 3: Copy and remap topk_ids, copy weights.
for k in range(topk):
old_id = tl.load(topk_ids_ptr + token_idx * topk + k, mask=mask, other=-1).to(
tl.int64
)
valid_id = old_id >= 0
new_id = tl.where(valid_id, old_id + (old_id // old_experts_per_rank), old_id)
tl.store(expanded_ids_ptr + token_idx * (topk + 1) + k, new_id, mask=mask)
for k in range(topk):
val = tl.load(topk_weights_ptr + token_idx * topk + k, mask=mask, other=0.0)
expert_id = tl.load(
topk_ids_ptr + token_idx * topk + k, mask=mask, other=-1
).to(tl.int64)
val = tl.where(expert_id >= 0, val, 0.0)
tl.store(expanded_weights_ptr + token_idx * (topk + 1) + k, val, mask=mask)
# Step 4: Write shared expert column.
tl.store(
expanded_ids_ptr + token_idx * (topk + 1) + topk,
shared_expert_id,
mask=mask,
)
tl.store(
expanded_weights_ptr + token_idx * (topk + 1) + topk,
tl.where(has_valid, shared_weight, 0.0),
mask=mask,
)
def materialize_waterfill_dispatch_fused(
topk_ids: Tensor,
topk_weights: Tensor,
rank_load: Tensor,
num_routed_experts: int,
world_size: int,
source_rank: int,
shared_weight: float,
allow_all_ranks: bool = False,
target_total: int = 0,
) -> Tuple[Tensor, Tensor]:
"""Run fused Waterfill rank selection and DeepEP TopK expansion.
The Triton kernel intentionally selects each token's shared-expert rank and
writes the expanded DeepEP TopK layout in one pass.
"""
num_tokens = topk_ids.shape[0]
topk = topk_ids.shape[1]
old_experts_per_rank = num_routed_experts // world_size
new_experts_per_rank = old_experts_per_rank + 1
device = topk_ids.device
if num_tokens == 0:
return _empty_expanded(topk_ids, topk_weights)
expanded_topk_ids = torch.empty(
num_tokens, topk + 1, dtype=topk_ids.dtype, device=device
)
expanded_topk_weights = torch.empty(
num_tokens, topk + 1, dtype=topk_weights.dtype, device=device
)
BLOCK_SIZE = 256
grid = ((num_tokens + BLOCK_SIZE - 1) // BLOCK_SIZE,)
_waterfill_expand_kernel[grid](
topk_ids,
topk_weights,
rank_load,
expanded_topk_ids,
expanded_topk_weights,
num_tokens,
topk,
old_experts_per_rank,
new_experts_per_rank,
world_size,
source_rank,
shared_weight,
LOCAL_SHARED_MARKER,
_LOCAL_PREF_NUMER,
_LOCAL_PREF_DENOM,
target_total,
allow_all_ranks,
BLOCK_SIZE,
)
return expanded_topk_ids, expanded_topk_weights
@torch.compile(dynamic=True)
def expand_topk_with_shared_expert(
topk_ids: Tensor,
topk_weights: Tensor,
num_routed_experts: int,
world_size: int,
source_rank: int,
shared_weight: float,
) -> Tuple[Tensor, Tensor]:
"""Expand topk [N, 8] → [N, 9] with ID remap; shared expert always local."""
num_tokens = topk_ids.shape[0]
topk = topk_ids.shape[1]
device = topk_ids.device
old_epr = num_routed_experts // world_size
new_epr = old_epr + 1
has_valid = (topk_ids >= 0).any(dim=1)
valid_mask = topk_ids >= 0
old_ranks = torch.where(valid_mask, topk_ids // old_epr, torch.zeros_like(topk_ids))
expanded_topk_ids = torch.empty(
num_tokens, topk + 1, dtype=topk_ids.dtype, device=device
)
expanded_topk_ids[:, :topk] = torch.where(
valid_mask, topk_ids + old_ranks, topk_ids
)
shared_id = source_rank * new_epr + old_epr
expanded_topk_ids[:, topk] = torch.where(has_valid, shared_id, LOCAL_SHARED_MARKER)
expanded_topk_weights = torch.empty(
num_tokens, topk + 1, dtype=topk_weights.dtype, device=device
)
expanded_topk_weights[:, :topk] = torch.where(valid_mask, topk_weights, 0.0)
expanded_topk_weights[:, topk] = torch.where(has_valid, shared_weight, 0.0).to(
topk_weights.dtype
)
return expanded_topk_ids, expanded_topk_weights
class DeepEPWaterfillBalancer:
"""Waterfill load balancer: shared expert fused as real routed expert (topk 8→9)."""
MIN_BATCH_FOR_BALANCE = 64
def __init__(
self,
num_routed_experts: int,
world_size: int,
rank: int,
layer_id: int,
routed_scaling_factor: float = 1.0,
):
self.num_routed_experts = num_routed_experts
self.world_size = world_size
self.rank = rank
self.layer_id = layer_id
self.old_experts_per_rank = num_routed_experts // world_size
self.shared_weight = (
1.0 / routed_scaling_factor if routed_scaling_factor != 0 else 1.0
)
self._counts_buf: Optional[Tensor] = None
self.use_static_waterfill = not envs.SGLANG_DISABLE_STATIC_WATERFILL.get()
def count_local_routed(self, topk_ids: Tensor) -> Tensor:
"""Count routed tokens per rank via Triton kernel (uses original expert IDs)."""
if self._counts_buf is None:
self._counts_buf = torch.zeros(
self.world_size, dtype=torch.int64, device=topk_ids.device
)
buf = self._counts_buf
buf.zero_()
num_tokens = topk_ids.shape[0]
if num_tokens == 0:
return buf
topk = topk_ids.shape[1]
BLOCK_SIZE = 256
grid = ((num_tokens + BLOCK_SIZE - 1) // BLOCK_SIZE,)
_count_routed_per_rank_kernel[grid](
topk_ids,
buf,
num_tokens,
topk,
self.old_experts_per_rank,
self.world_size,
BLOCK_SIZE=BLOCK_SIZE,
)
return buf
def _is_low_batch(self, num_tokens: int) -> bool:
"""Return whether waterfill should skip balancing for small batches."""
return num_tokens < self.MIN_BATCH_FOR_BALANCE
def _can_skip_dispatch_plan_for_low_batch(self, num_tokens: int) -> bool:
"""Return whether static mode can skip dispatch-plan setup entirely."""
return self.use_static_waterfill and self._is_low_batch(num_tokens)
def _build_static_dispatch_plan(
self, routed_counts: Tensor
) -> WaterfillDispatchPlan:
"""Build static-mode Waterfill inputs from current local routed counts."""
return WaterfillDispatchPlan(
rank_load=routed_counts,
allow_all_ranks=True,
target_total=0,
)
def _build_dynamic_dispatch_plan(
self,
routed_counts: Tensor,
local_tokens_per_rank: Optional[Tensor],
topk: int,
) -> WaterfillDispatchPlan:
"""Build dynamic waterfill inputs from globally reduced routed counts."""
# Dynamic Waterfill balances against effective rank load: globally
# reduced routed counts plus each rank's active token count.
rank_load = (
routed_counts + local_tokens_per_rank
if local_tokens_per_rank is not None
else routed_counts
)
total_routed_t = routed_counts.sum()
total_tokens_global_t = total_routed_t // topk
total_effective_t = rank_load.sum()
max_effective_t = rank_load.max()
target_total = int(
(total_effective_t + total_tokens_global_t + self.world_size - 1)
// self.world_size
)
allow_all_ranks = bool(max_effective_t <= target_total)
return WaterfillDispatchPlan(
rank_load=rank_load,
allow_all_ranks=allow_all_ranks,
target_total=target_total,
)
@staticmethod
def _all_reduce_dynamic_rank_load(
local_routed_counts: Tensor, num_tokens: int
) -> Tuple[Tensor, Tensor]:
"""Aggregate dynamic load with SGLang EP communication."""
from sglang.srt.distributed import get_moe_ep_group
from sglang.srt.distributed.communication_op import (
moe_expert_parallel_all_reduce,
)
group = get_moe_ep_group()
world = group.world_size
buf = torch.zeros(
world * 2, dtype=torch.int64, device=local_routed_counts.device
)
buf[:world] = local_routed_counts
rank = group.rank_in_group
buf[world + rank : world + rank + 1].fill_(num_tokens)
buf = moe_expert_parallel_all_reduce(buf)
return buf[:world], buf[world:]
def _build_dispatch_plan(
self, topk_ids: Tensor, num_tokens: int
) -> Optional[WaterfillDispatchPlan]:
"""Prepare dispatch state for the waterfill selection boundary."""
local_routed_counts = self.count_local_routed(topk_ids)
if self.use_static_waterfill:
return self._build_static_dispatch_plan(local_routed_counts)
global_routed_counts, local_tokens_per_rank = (
DeepEPWaterfillBalancer._all_reduce_dynamic_rank_load(
local_routed_counts, num_tokens
)
)
if self._is_low_batch(num_tokens):
return None
return self._build_dynamic_dispatch_plan(
global_routed_counts,
local_tokens_per_rank=local_tokens_per_rank,
topk=topk_ids.shape[1],
)
def _materialize_dispatch(
self,
topk_ids: Tensor,
topk_weights: Tensor,
dispatch_plan: WaterfillDispatchPlan,
) -> Tuple[Tensor, Tensor]:
"""Expand TopK using local expansion or fused Waterfill."""
num_tokens = topk_ids.shape[0]
if num_tokens == 0:
return _empty_expanded(topk_ids, topk_weights)
if self._is_low_batch(num_tokens):
return expand_topk_with_shared_expert(
topk_ids,
topk_weights,
self.num_routed_experts,
self.world_size,
self.rank,
self.shared_weight,
)
return materialize_waterfill_dispatch_fused(
topk_ids,
topk_weights,
dispatch_plan.rank_load,
self.num_routed_experts,
self.world_size,
self.rank,
self.shared_weight,
allow_all_ranks=dispatch_plan.allow_all_ranks,
target_total=dispatch_plan.target_total,
)
@staticmethod
def _with_expanded_topk(
topk_output: StandardTopKOutput,
expanded_ids: Tensor,
expanded_weights: Tensor,
) -> StandardTopKOutput:
"""Wrap expanded tensors back into SGLang's StandardTopKOutput."""
return StandardTopKOutput(
topk_weights=expanded_weights,
topk_ids=expanded_ids,
router_logits=topk_output.router_logits,
)
def _expand_local_shared(
self, topk_output: StandardTopKOutput
) -> StandardTopKOutput:
expanded_ids, expanded_weights = expand_topk_with_shared_expert(
topk_output.topk_ids,
topk_output.topk_weights,
self.num_routed_experts,
self.world_size,
self.rank,
self.shared_weight,
)
return self._with_expanded_topk(topk_output, expanded_ids, expanded_weights)
def expand_topk(
self, topk_output: StandardTopKOutput, num_tokens: int
) -> StandardTopKOutput:
"""Expand topk [N, 8] -> [N, 9] with waterfill-assigned shared expert."""
if self._can_skip_dispatch_plan_for_low_batch(num_tokens):
# Static mode can use local expansion without communication for small
# decode-sized batches. Dynamic mode still all-reduces before local
# expansion so all ranks participate consistently.
return self._expand_local_shared(topk_output)
dispatch_plan = self._build_dispatch_plan(topk_output.topk_ids, num_tokens)
if dispatch_plan is None:
if num_tokens == 0:
expanded_ids, expanded_weights = _empty_expanded(
topk_output.topk_ids, topk_output.topk_weights
)
return self._with_expanded_topk(
topk_output, expanded_ids, expanded_weights
)
else:
return self._expand_local_shared(topk_output)
expanded_ids, expanded_weights = self._materialize_dispatch(
topk_output.topk_ids,
topk_output.topk_weights,
dispatch_plan,
)
return self._with_expanded_topk(topk_output, expanded_ids, expanded_weights)