Support layerwise rebalancing experts (#6851)

This commit is contained in:
fzyzcjy
2025-06-05 15:05:52 +08:00
committed by GitHub
parent 72a110f664
commit 0de5e7d40f
6 changed files with 115 additions and 38 deletions

View File

@@ -1,6 +1,6 @@
import logging
import time
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List
import torch.cuda
@@ -20,6 +20,10 @@ class EPLBManager:
super().__init__()
self._model_runner = model_runner
self._server_args = model_runner.server_args
self._rebalance_layers_per_chunk = (
self._server_args.eplb_rebalance_layers_per_chunk
)
self._rebalance_num_iterations = self._server_args.eplb_rebalance_num_iterations
# Otherwise, the circular buffer will contain stale data. If the case is needed, it can be implemented.
assert (
@@ -31,17 +35,30 @@ class EPLBManager:
get_global_expert_distribution_recorder().start_record()
logger.info(
f"[EPLBManager] system started, will rebalance per {self._server_args.eplb_rebalance_num_iterations} iterations."
f"[EPLBManager] system started, will rebalance per {self._rebalance_num_iterations} iterations."
)
def on_forward_pass_end(self, forward_pass_id: int):
if forward_pass_id % self._server_args.eplb_rebalance_num_iterations == 0:
self.rebalance()
self._main_generator = self._entrypoint()
def on_forward_pass_end(self):
next(self._main_generator)
# can be more complex if needed
def _entrypoint(self):
while True:
for _ in range(self._rebalance_num_iterations):
yield
yield from self.rebalance()
def rebalance(self):
logger.info("[EPLBManager] rebalance start")
torch.cuda.synchronize()
time_start = time.time()
enable_timing = self._rebalance_layers_per_chunk is None
if enable_timing:
torch.cuda.synchronize()
time_start = time.time()
logical_count = get_global_expert_distribution_recorder().dump_record(
output_mode="object"
@@ -49,8 +66,31 @@ class EPLBManager:
expert_location_metadata = ExpertLocationMetadata.init_by_eplb(
self._server_args, self._model_runner.model_config, logical_count
)
self._model_runner.update_expert_location(expert_location_metadata)
torch.cuda.synchronize()
time_end = time.time()
logger.info(f"[EPLBManager] rebalance end time={time_end - time_start:.3f}s")
update_layer_ids_chunks = self._compute_update_layer_ids_chunks()
for chunk_index, update_layer_ids in enumerate(update_layer_ids_chunks):
if len(update_layer_ids_chunks) > 1:
yield
self._model_runner.update_expert_location(
expert_location_metadata,
update_layer_ids=update_layer_ids,
)
msg = f"[EPLBManager] rebalance end"
if enable_timing:
torch.cuda.synchronize()
time_end = time.time()
msg += f" time={time_end - time_start:.3f}s"
logger.info(msg)
def _compute_update_layer_ids_chunks(self) -> List[List[int]]:
all_layer_ids = sorted(
list(self._model_runner.model.routed_experts_weights_of_layer.keys())
)
chunk_size = self._rebalance_layers_per_chunk or 1000000
return list(_chunk_list(all_layer_ids, chunk_size=chunk_size))
def _chunk_list(items: List, chunk_size):
for start_index in range(0, len(items), chunk_size):
yield items[start_index : start_index + chunk_size]

View File

@@ -33,6 +33,7 @@ logger = logging.getLogger(__name__)
@dataclass
class ExpertLocationMetadata:
physical_to_logical_map: torch.Tensor # (layers, num_physical_experts)
physical_to_logical_map_cpu: torch.Tensor
logical_to_all_physical_map: torch.Tensor # (layers, num_logical_experts, X)
logical_to_all_physical_map_num_valid: torch.Tensor # (layers, num_logical_experts)
# (layers, num_logical_experts)
@@ -203,6 +204,7 @@ class ExpertLocationMetadata:
return ExpertLocationMetadata(
physical_to_logical_map=physical_to_logical_map,
physical_to_logical_map_cpu=physical_to_logical_map.cpu(),
logical_to_all_physical_map=logical_to_all_physical_map_padded,
logical_to_all_physical_map_num_valid=logical_to_all_physical_map_num_valid,
logical_to_rank_dispatch_physical_map=(
@@ -223,6 +225,7 @@ class ExpertLocationMetadata:
def update(
self,
other: "ExpertLocationMetadata",
update_layer_ids: List[int],
):
for field in [
"ep_size",
@@ -231,15 +234,21 @@ class ExpertLocationMetadata:
for field in [
"physical_to_logical_map",
"physical_to_logical_map_cpu",
"logical_to_all_physical_map",
"logical_to_all_physical_map_num_valid",
"logical_to_rank_dispatch_physical_map",
]:
src = getattr(other, field)
dst = getattr(self, field)
assert (src is not None) == (dst is not None)
if dst is not None:
dst[...] = src
other_field = getattr(other, field)
self_field = getattr(self, field)
assert (other_field is not None) == (self_field is not None)
if self_field is not None:
mask_update = torch.tensor(
[i in update_layer_ids for i in range(self.num_layers)]
)
mask_update = mask_update.view(*([-1] + [1] * (self_field.dim() - 1)))
mask_update = mask_update.to(self_field.device, non_blocking=True)
self_field[...] = torch.where(mask_update, other_field, self_field)
# -------------------------------- usage ------------------------------------