-
Notifications
You must be signed in to change notification settings - Fork 7.1k
[Feature] Enable return routed experts #12162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
cf8d0fd
a720fae
542d490
cfc330b
1b9e9fa
5a70eb7
6924725
1f40d43
794af1f
e15db98
f92a997
41bebeb
4ec04be
fa916bc
98c8fe0
8da8de1
6c92475
812421a
ded85ca
ce7463d
bf63df9
22e3910
b2af1cb
911bd1a
0a0645f
b041ddc
c70bded
3d54694
1b701a8
31d9509
b72a6f2
fdb5c47
b77a5fb
83192fb
aa4df5e
e7b1fc9
65471d5
4c23438
aaca6e3
823ea6b
54ba922
7d9a69d
d49fe1a
d8e2e34
e8c276a
4457751
b4f4aaf
5a3e735
f9a442b
676612f
041dfcd
e6af67d
067f185
be8ce97
09b203b
017d0b9
808d29f
dbe9775
33fe8ee
5770aa2
2f580f5
b32e693
7da8cfa
37a6c76
0189d33
ee2a780
f486811
ca4b3a4
efee10b
67210c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| import logging | ||
| from abc import ABC | ||
| from typing import Optional | ||
|
|
||
| import numpy as np | ||
| import torch | ||
|
|
||
| from sglang.srt.configs.model_config import ModelConfig | ||
| from sglang.srt.server_args import get_global_server_args | ||
|
|
||
| # from sglang.srt.utils.torch_memory_saver_adapter import TorchMemorySaverAdapter | ||
| # from sglang.srt.constants import GPU_MEMORY_TYPE_KV_CACHE | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| GB = 1024 * 1024 * 1024 | ||
| MB = 1024 * 1024 | ||
|
|
||
|
|
||
| def get_tensor_size_bytes(t: torch.Tensor): | ||
| return np.prod(t.shape) * t.dtype.itemsize | ||
|
|
||
|
|
||
| class RoutedExpertsDeviceCache: | ||
|
fzyzcjy marked this conversation as resolved.
Outdated
|
||
| def __init__( | ||
| self, model_config: ModelConfig, max_running_requests: int, device: str | ||
| ) -> None: | ||
| self.buffer = torch.zeros( | ||
| ( | ||
| max( | ||
| get_global_server_args().chunked_prefill_size, max_running_requests | ||
| ), | ||
| model_config.hf_text_config.num_hidden_layers, | ||
| model_config.hf_text_config.num_experts_per_tok, | ||
| ), | ||
| dtype=torch.int32, | ||
| device=device, | ||
| ) | ||
| self._finalize_allocation_log() | ||
|
|
||
| def get_buffer_size_bytes(self): | ||
| assert hasattr(self, "buffer") | ||
| return get_tensor_size_bytes(self.buffer) | ||
|
|
||
| def capture_fwd_routed_experts(self, layer_id: int, topk_ids: torch.Tensor): | ||
| assert layer_id is not None, "capturing routing experts but get layer_id None" | ||
| batch, _ = topk_ids.shape | ||
| self.buffer[:batch, layer_id, :] = topk_ids | ||
|
|
||
| def _finalize_allocation_log(self): | ||
| """Common logging and memory usage computation for captured experts buffers.""" | ||
| buffer_size_MB = self.get_buffer_size_bytes() / MB | ||
| logger.info( | ||
| f"Routing experts device buffer allocated. #shape: {tuple(self.buffer.shape)}, size: {buffer_size_MB:.2f} MB" | ||
| ) | ||
|
|
||
|
|
||
| class RoutedExpertsHostCache: | ||
|
ocss884 marked this conversation as resolved.
Outdated
|
||
| def __init__( | ||
| self, | ||
| model_config: ModelConfig, | ||
| num_tokens: int, | ||
| ) -> None: | ||
| self.num_tokens = num_tokens | ||
| self.buffer = torch.zeros( | ||
| ( | ||
| num_tokens, | ||
| model_config.hf_text_config.num_hidden_layers, | ||
| model_config.hf_text_config.num_experts_per_tok, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this name must exist for all models? or maybe we need some ways to get the correct field
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to unify the processing logic during model_config init. It is just a temp solution here for my test on Qwen (like deepseek V3 arch definitely need to add the shared expert or other model like step3 using moe_top_k). |
||
| ), | ||
| dtype=torch.int32, | ||
| device="cpu", | ||
| ) | ||
| self._finalize_allocation_log() | ||
|
|
||
| def get_buffer_size_bytes(self): | ||
| assert hasattr(self, "buffer") | ||
| return get_tensor_size_bytes(self.buffer) | ||
|
|
||
| def set_experts_buffer(self, layer_id: int, loc: torch.Tensor, top_k: torch.Tensor): | ||
| self.buffer[layer_id, loc, :] = top_k.cpu() | ||
|
|
||
| def _finalize_allocation_log(self): | ||
| """Common logging and memory usage computation for captured experts buffers.""" | ||
| buffer_size_GB = self.get_buffer_size_bytes() / GB | ||
| logger.info( | ||
| f"Routing experts host buffer allocated. #tokens: {self.num_tokens}, size: {buffer_size_GB:.2f} GB" | ||
| ) | ||
|
|
||
|
|
||
| class RoutedExpertsCapturer(ABC): | ||
| @staticmethod | ||
| def create( | ||
| enable: bool, | ||
| model_config: ModelConfig, | ||
| num_tokens: int, | ||
| max_running_requests: int, | ||
| device: str, | ||
| ): | ||
| if enable: | ||
| return _RoutedExpertsCapturerReal( | ||
| model_config, | ||
| num_tokens=num_tokens, | ||
| max_running_requests=max_running_requests, | ||
| device=device, | ||
| ) | ||
| else: | ||
| return _RoutedExpertsCapturerNoop() | ||
|
|
||
| def capture(self, layer_id: int, topk_ids: torch.Tensor): | ||
| raise NotImplementedError | ||
|
|
||
| def sync_fwd_experts_buffer_DtoH(self, batch: int, loc: torch.Tensor): | ||
| raise NotImplementedError | ||
|
|
||
| def get_host_cache(self): | ||
| raise NotImplementedError | ||
|
|
||
| def get_device_cache(self): | ||
| raise NotImplementedError | ||
|
|
||
|
|
||
| class _RoutedExpertsCapturerReal(RoutedExpertsCapturer): | ||
| """Capturer for routed experts with host buffer""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| model_config: ModelConfig, | ||
| num_tokens: int, | ||
| max_running_requests: int, | ||
| device: str, | ||
| ): | ||
|
|
||
| self.host_cache = RoutedExpertsHostCache(model_config, num_tokens) | ||
|
|
||
| self.device_cache = RoutedExpertsDeviceCache( | ||
| model_config, max_running_requests, device | ||
| ) | ||
|
|
||
| def capture(self, layer_id: int, topk_ids: torch.Tensor): | ||
| self.device_cache.capture_fwd_routed_experts(layer_id, topk_ids) | ||
|
|
||
| def sync_fwd_experts_buffer_DtoH(self, loc: torch.Tensor): | ||
| batch = loc.shape[0] | ||
| self.host_cache.buffer[loc] = self.device_cache.buffer[:batch].cpu() | ||
|
|
||
| def get_host_cache(self): | ||
| return self.host_cache | ||
|
|
||
| def get_device_cache(self): | ||
| return self.device_cache | ||
|
|
||
|
|
||
| class _RoutedExpertsCapturerNoop(RoutedExpertsCapturer): | ||
| def __init__(self): | ||
| pass | ||
|
|
||
| def capture(self, layer_id: int, topk_ids: torch.Tensor): | ||
| pass | ||
|
|
||
| def sync_fwd_experts_buffer_DtoH(self, loc: torch.Tensor): | ||
| pass | ||
|
|
||
| def get_host_cache(self): | ||
| pass | ||
|
|
||
| def get_device_cache(self): | ||
| pass | ||
|
|
||
|
|
||
| _global_expert_capturer: Optional[RoutedExpertsCapturer] = _RoutedExpertsCapturerNoop() | ||
|
|
||
|
|
||
| def get_global_experts_capturer(): | ||
| return _global_expert_capturer | ||
|
|
||
|
|
||
| def set_global_experts_capturer(capturer: RoutedExpertsCapturer): | ||
| global _global_expert_capturer | ||
| _global_expert_capturer = capturer | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,7 @@ | |
| get_moe_runner_backend, | ||
| should_use_flashinfer_trtllm_moe, | ||
| ) | ||
| from sglang.srt.layers.moe.routed_experts_capturer import get_global_experts_capturer | ||
| from sglang.srt.utils import ( | ||
| cpu_has_amx_support, | ||
| get_bool_env_var, | ||
|
|
@@ -193,6 +194,7 @@ def __init__( | |
| self, | ||
| top_k: int, | ||
| *, | ||
| layer_id: Optional[int] = None, | ||
| use_grouped_topk: bool = False, | ||
| topk_group: Optional[int] = None, | ||
| num_expert_group: Optional[int] = None, | ||
|
|
@@ -213,6 +215,7 @@ def __init__( | |
| if use_grouped_topk: | ||
| assert num_expert_group is not None and topk_group is not None | ||
|
|
||
| self.layer_id = layer_id | ||
| self.topk_config = TopKConfig( | ||
| top_k=top_k, | ||
| use_grouped_topk=use_grouped_topk, | ||
|
|
@@ -238,6 +241,7 @@ def forward_native( | |
| self.topk_config.torch_native = True | ||
| return select_experts( | ||
| hidden_states=hidden_states, | ||
| layer_id=self.layer_id, | ||
| router_logits=router_logits, | ||
| topk_config=self.topk_config, | ||
| num_token_non_padded=num_token_non_padded, | ||
|
|
@@ -284,6 +288,7 @@ def forward_cuda( | |
| self.topk_config.torch_native = False | ||
| return select_experts( | ||
| hidden_states=hidden_states, | ||
| layer_id=self.layer_id, | ||
| router_logits=router_logits, | ||
| topk_config=self.topk_config, | ||
| num_token_non_padded=num_token_non_padded, | ||
|
|
@@ -300,6 +305,7 @@ def forward_cpu( | |
| ) -> TopKOutput: | ||
| return select_experts( | ||
| hidden_states=hidden_states, | ||
| layer_id=self.layer_id, | ||
| router_logits=router_logits, | ||
| topk_config=self.topk_config, | ||
| num_token_non_padded=num_token_non_padded, | ||
|
|
@@ -356,6 +362,7 @@ def forward_npu( | |
| self.topk_config.torch_native = True | ||
| return select_experts( | ||
| hidden_states=hidden_states, | ||
| layer_id=self.layer_id, | ||
| router_logits=router_logits, | ||
| topk_config=self.topk_config, | ||
| num_token_non_padded=num_token_non_padded, | ||
|
|
@@ -789,6 +796,7 @@ def select_experts( | |
| router_logits: torch.Tensor, | ||
| topk_config: TopKConfig, | ||
| *, | ||
| layer_id: Optional[int] = None, | ||
| num_token_non_padded: Optional[torch.Tensor] = None, | ||
| expert_location_dispatch_info: Optional[ExpertLocationDispatchInfo] = None, | ||
| ) -> StandardTopKOutput: | ||
|
|
@@ -886,5 +894,8 @@ def select_experts( | |
| ) | ||
|
|
||
| get_global_expert_distribution_recorder().on_select_experts(topk_ids=topk_ids) | ||
|
|
||
| get_global_experts_capturer().capture( | ||
| layer_id=layer_id, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. about how to pass in layer_id: ExpertDistributionRecorder has a with_current_layer. what about refactoring into sth like and let both ExpertDistributionRecorder and the new RoutedExpertsCapture to use current_layer_mgr.get_the_current_layer_id_value |
||
| topk_ids=topk_ids, | ||
| ) | ||
| return StandardTopKOutput(topk_weights, topk_ids, router_logits) | ||
Uh oh!
There was an error while loading. Please reload this page.