169 lines
5.2 KiB
Python
169 lines
5.2 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from collections.abc import Callable
|
|
from functools import wraps
|
|
from typing import Any
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
|
|
from vllm.config import ModelConfig
|
|
from vllm.distributed.eplb.eplb_state import EplbState
|
|
from vllm.logger import init_logger
|
|
from vllm.model_executor.models.interfaces import (
|
|
SupportsMultiModal,
|
|
is_mixture_of_experts,
|
|
)
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def _unwrap_moe(model: nn.Module) -> nn.Module:
|
|
# VLM wrappers (e.g. KimiK25ForConditionalGeneration) hold the MoE
|
|
# language model under `.language_model` but don't implement
|
|
# MixtureOfExperts themselves. Mirror the V1 path
|
|
# (see vllm/v1/worker/gpu_model_runner.py, PR #39805).
|
|
if not is_mixture_of_experts(model) and isinstance(model, SupportsMultiModal):
|
|
return model.get_language_model()
|
|
return model
|
|
|
|
|
|
def step_eplb_after(*, is_dummy: bool = False) -> Callable:
|
|
"""Step EPLB after a model runner method completes successfully."""
|
|
|
|
def decorator(fn: Callable) -> Callable:
|
|
@wraps(fn)
|
|
def wrapper(self: Any, *args, **kwargs) -> Any:
|
|
result = fn(self, *args, **kwargs)
|
|
if kwargs.get("skip_eplb", False):
|
|
return result
|
|
|
|
is_profile = kwargs.get("is_profile", False) if is_dummy else False
|
|
self.eplb.step(is_dummy=is_dummy, is_profile=is_profile)
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
class EPLBController:
|
|
def __init__(self, parallel_config: Any, device: torch.device):
|
|
self.parallel_config = parallel_config
|
|
self.device = device
|
|
self.state: EplbState | None = None
|
|
self.suppressed = False
|
|
self._has_registered_models = False
|
|
|
|
def prepare_load(self) -> None:
|
|
self.state = None
|
|
self._has_registered_models = False
|
|
if self.parallel_config.enable_eplb:
|
|
self.state = EplbState(self.parallel_config, self.device)
|
|
|
|
def maybe_register_speculator(
|
|
self,
|
|
speculator: Any | None,
|
|
speculative_config: Any | None,
|
|
load_dummy_weights: bool,
|
|
) -> bool:
|
|
# if speculator is a moe model, add it to eplb
|
|
if (
|
|
speculator is None
|
|
or not hasattr(speculator, "model")
|
|
or not self.parallel_config.enable_eplb
|
|
or load_dummy_weights
|
|
):
|
|
return False
|
|
|
|
draft_model = speculator.model
|
|
if not is_mixture_of_experts(draft_model):
|
|
return False
|
|
|
|
assert not self.parallel_config.enable_elastic_ep, (
|
|
"Elastic EP is not supported with draft model."
|
|
)
|
|
assert speculative_config is not None
|
|
assert speculative_config.draft_model_config is not None
|
|
assert self.state is not None
|
|
self.state.add_model(
|
|
draft_model,
|
|
speculative_config.draft_model_config,
|
|
)
|
|
speculator.set_eplb_state(self.state)
|
|
self._has_registered_models = True
|
|
return True
|
|
|
|
def maybe_register_model(
|
|
self,
|
|
model: nn.Module,
|
|
model_config: Any,
|
|
load_dummy_weights: bool,
|
|
) -> bool:
|
|
if not self.parallel_config.enable_eplb or load_dummy_weights:
|
|
return False
|
|
|
|
model = _unwrap_moe(model)
|
|
if not is_mixture_of_experts(model):
|
|
return False
|
|
|
|
logger.info_once("EPLB is enabled for model %s.", model_config.model)
|
|
assert self.state is not None
|
|
self.state.add_model(model, model_config)
|
|
self._has_registered_models = True
|
|
return True
|
|
|
|
def maybe_start_async_loop(self, eplb_models_added: bool) -> None:
|
|
if eplb_models_added and self.state is not None and self.state.is_async:
|
|
self.state.start_async_loop()
|
|
|
|
def step(
|
|
self,
|
|
is_dummy: bool = False,
|
|
is_profile: bool = False,
|
|
) -> None:
|
|
if (
|
|
not self.parallel_config.enable_eplb
|
|
or self.suppressed
|
|
or self.state is None
|
|
or not self._has_registered_models
|
|
):
|
|
return
|
|
|
|
self.state.step(
|
|
is_dummy,
|
|
is_profile,
|
|
log_stats=self.parallel_config.eplb_config.log_balancedness,
|
|
)
|
|
|
|
def prepare_forward(
|
|
self,
|
|
model_config: ModelConfig,
|
|
num_unpadded_tokens: int,
|
|
ubatch_slices: list | None = None,
|
|
) -> None:
|
|
if self.state is None or not self.parallel_config.enable_eplb:
|
|
return
|
|
self.state.prepare_forward(model_config, num_unpadded_tokens, ubatch_slices)
|
|
|
|
def setup_from_mapping(
|
|
self,
|
|
model: nn.Module,
|
|
model_config: Any,
|
|
expanded_physical_to_logical: torch.Tensor,
|
|
old_num_physical_experts: int,
|
|
) -> None:
|
|
model = _unwrap_moe(model)
|
|
assert is_mixture_of_experts(model)
|
|
|
|
self.state = EplbState.from_mapping(
|
|
model=model,
|
|
model_config=model_config,
|
|
device=self.device,
|
|
parallel_config=self.parallel_config,
|
|
expanded_physical_to_logical=expanded_physical_to_logical,
|
|
num_valid_physical_experts=old_num_physical_experts,
|
|
)
|
|
self._has_registered_models = True
|