Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions python/sglang/srt/eplb/expert_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ def logical_to_all_physical(
logical_expert_id: int,
require_global_experts: bool = False,
) -> List[int]:
# Use CPU copy to avoid GPU→CPU sync on every call, which is expensive in update weights scenario
cpu_map = self.logical_to_all_physical_map_cpu
# Use CPU copy to avoid GPU-to-CPU sync on every call, which is expensive in update weights scenario
cpu_map = self.physical_to_logical_map_cpu
# Draft workers can query MoE layers whose layer_id lies beyond the
# target-sized expert map; fall back to the identity mapping (no EPLB
# rebalancing for those layers) instead of indexing out of range.
Expand All @@ -367,11 +367,14 @@ def logical_to_all_physical(
return list(
range(logical_expert_id, num_physical_experts, self.num_logical_experts)
)
return [
physical_expert_id
for physical_expert_id in cpu_map[layer_id, logical_expert_id].tolist()
if physical_expert_id != -1
]
if not 0 <= logical_expert_id < self.num_logical_experts:
raise IndexError(
f"{logical_expert_id=} is not one of the "
f"{self.num_logical_experts} logical experts"
)
# Every replica on every rank: logical_to_all_physical_map may narrow the
# dispatch candidates to one replica, but the loaders must fill every slot.
return (cpu_map[layer_id] == logical_expert_id).nonzero().flatten().tolist()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks to cost the same as before.

[Agent check]
A CPU microbench at DSV3 shape (58 layers × 256 logical × 288 physical, 89k calls ≈ one reload's worth of w1/w2/w3 + scales) gives 3.66 µs/call here vs 3.38 µs for the old l2p[l, e].tolist() + filter, ~0.33 s vs ~0.30 s per full reload, per rank.

My suggestion:
a follow-up could build a List[List[List[int]]] from physical_to_logical_map_cpu.tolist() once in _init_raw/update (~1 ms). That makes this lookup 0.08 µs/call, so ~0.3 s → ~8 ms. The old code had the same cost, so don't really feel u need to add pr scope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for measuring it. Agreed, caching the inverse map once per update can be a separate follow-up; I'll keep this PR to the loading fix.



def format_expert_location_layout(
Expand Down
9 changes: 9 additions & 0 deletions python/sglang/srt/layers/moe/fused_moe_triton/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,11 @@ def _map_global_expert_id_to_local_expert_id(self, expert_id: int) -> int:
else:
return -1

def _local_routed_expert_ids(self, physical_expert_ids: List[int]) -> List[int]:
start_idx = self._expert_storage_rank * self._num_local_routed
end_idx = start_idx + self._num_local_routed
return [i for i in physical_expert_ids if start_idx <= i < end_idx]

def weight_loader(
self,
param: torch.nn.Parameter,
Expand Down Expand Up @@ -1227,6 +1232,10 @@ def weight_loader(
self.layer_id, expert_id, require_global_experts
)
)
if not require_global_experts:
# Before the local mapping: after an elastic EP scale-up, another rank's
# routed slot can lie past _num_global_routed and map to the shared slot.
physical_expert_ids = self._local_routed_expert_ids(physical_expert_ids)

for physical_expert_id in physical_expert_ids:
self._weight_loader_physical(
Expand Down
11 changes: 6 additions & 5 deletions python/sglang/srt/layers/quantization/moe_wna16.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,12 @@ def _local_expert_ids(layer, expert_id: int) -> List[int]:
expert-parallel slice this rank stores.
"""
metadata = get_global_expert_location_metadata()
physical = (
[expert_id]
if metadata is None or not layer.uses_expert_location
else metadata.logical_to_all_physical(layer.layer_id, expert_id)
)
if metadata is None or not layer.uses_expert_location:
physical = [expert_id]
else:
physical = metadata.logical_to_all_physical(layer.layer_id, expert_id)
if expert_id < metadata.num_logical_experts:
physical = layer._local_routed_expert_ids(physical)
local = (layer._map_global_expert_id_to_local_expert_id(p) for p in physical)
return [i for i in local if 0 <= i < layer.num_local_experts]

Expand Down
Loading
Loading