59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
696 lines
23 KiB
Python
696 lines
23 KiB
Python
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from enum import Enum, IntEnum, auto
|
|
from typing import Any
|
|
|
|
import torch
|
|
import torch.distributed as dist
|
|
|
|
__all__ = [
|
|
"Buffer",
|
|
"DeepEPBuffer",
|
|
"DeepEPDispatchMode",
|
|
"DeepEPDispatcher",
|
|
"DeepEPMode",
|
|
]
|
|
|
|
logger = logging.getLogger(__file__)
|
|
|
|
|
|
def _raise_deepep_unavailable() -> None:
|
|
raise ImportError(
|
|
"DeepEP is not available. Install the `deep_ep` package to use DeepEP "
|
|
"communication."
|
|
)
|
|
|
|
|
|
class _MissingBufferMeta(type):
|
|
def __getattr__(cls, name):
|
|
del name
|
|
_raise_deepep_unavailable()
|
|
|
|
|
|
class _MissingBuffer(metaclass=_MissingBufferMeta):
|
|
def __init__(self, *args, **kwargs):
|
|
del args, kwargs
|
|
_raise_deepep_unavailable()
|
|
|
|
|
|
try:
|
|
from deep_ep.buffer import Buffer
|
|
except ImportError:
|
|
Buffer = _MissingBuffer
|
|
|
|
|
|
def _get_available_gpu_memory(gpu_id: int, empty_cache: bool = True) -> float:
|
|
if torch.cuda.current_device() != gpu_id:
|
|
logger.warning(
|
|
"current device is not %s, but %s, which may cause useless memory allocation for torch CUDA context.",
|
|
gpu_id,
|
|
torch.cuda.current_device(),
|
|
)
|
|
if empty_cache:
|
|
torch.cuda.empty_cache()
|
|
free_gpu_memory, _ = torch.cuda.mem_get_info(gpu_id)
|
|
return free_gpu_memory / (1 << 30)
|
|
|
|
|
|
class DeepEPMode(Enum):
|
|
normal = "normal"
|
|
low_latency = "low_latency"
|
|
auto = "auto"
|
|
|
|
def enable_normal(self):
|
|
return self in [DeepEPMode.normal, DeepEPMode.auto]
|
|
|
|
def enable_low_latency(self):
|
|
return self in [DeepEPMode.low_latency, DeepEPMode.auto]
|
|
|
|
def resolve(self, forward_mode):
|
|
if self != DeepEPMode.auto:
|
|
return self
|
|
|
|
if forward_mode.is_decode():
|
|
return DeepEPMode.low_latency
|
|
return DeepEPMode.normal
|
|
|
|
|
|
class DeepEPDispatchMode(IntEnum):
|
|
NORMAL = auto()
|
|
LOW_LATENCY = auto()
|
|
|
|
|
|
class DeepEPBuffer:
|
|
_buffer = None
|
|
_dispatch_mode: DeepEPDispatchMode | None = None
|
|
_hidden_size: int | None = None
|
|
_num_max_dispatch_tokens_per_rank: int | None = None
|
|
_num_experts: int | None = None
|
|
|
|
@classmethod
|
|
def get_deepep_buffer(
|
|
cls,
|
|
group: dist.ProcessGroup,
|
|
hidden_size: int,
|
|
param_bytes: int,
|
|
deepep_mode: DeepEPMode,
|
|
num_max_dispatch_tokens_per_rank: int = None,
|
|
num_experts: int = None,
|
|
):
|
|
if cls._buffer is not None:
|
|
return cls._buffer
|
|
|
|
cls._hidden_size = hidden_size
|
|
cls._num_max_dispatch_tokens_per_rank = num_max_dispatch_tokens_per_rank
|
|
cls._num_experts = num_experts
|
|
|
|
num_nvl_bytes, num_rdma_bytes = 0, 0
|
|
if deepep_mode.enable_normal():
|
|
hidden_bytes = hidden_size * param_bytes
|
|
for config in (
|
|
Buffer.get_dispatch_config(group.size()),
|
|
Buffer.get_combine_config(group.size()),
|
|
):
|
|
num_nvl_bytes = max(
|
|
config.get_nvl_buffer_size_hint(hidden_bytes, group.size()),
|
|
num_nvl_bytes,
|
|
)
|
|
num_rdma_bytes = max(
|
|
config.get_rdma_buffer_size_hint(hidden_bytes, group.size()),
|
|
num_rdma_bytes,
|
|
)
|
|
if deepep_mode.enable_low_latency():
|
|
assert num_max_dispatch_tokens_per_rank is not None
|
|
assert num_experts is not None and num_experts % group.size() == 0
|
|
num_rdma_bytes = max(
|
|
Buffer.get_low_latency_rdma_size_hint(
|
|
num_max_dispatch_tokens_per_rank,
|
|
hidden_size,
|
|
group.size(),
|
|
num_experts,
|
|
),
|
|
num_rdma_bytes,
|
|
)
|
|
|
|
# Calculate num_qps_per_rank consistently with DeepEP examples:
|
|
# refer: https://github.com/deepseek-ai/DeepEP/blob/main/tests/test_internode.py#L235
|
|
if deepep_mode == DeepEPMode.normal:
|
|
num_qps_per_rank = Buffer.num_sms
|
|
elif deepep_mode == DeepEPMode.low_latency:
|
|
# refer: https://github.com/deepseek-ai/DeepEP/blob/main/tests/test_low_latency.py#L176
|
|
num_qps_per_rank = num_experts // group.size()
|
|
elif deepep_mode == DeepEPMode.auto:
|
|
# low-latency and normal mode all need run
|
|
num_qps_per_rank = max(Buffer.num_sms, num_experts // group.size())
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
free_gpu_memory_begin = _get_available_gpu_memory(torch.cuda.current_device())
|
|
cls._buffer = Buffer(
|
|
group,
|
|
num_nvl_bytes,
|
|
num_rdma_bytes,
|
|
low_latency_mode=deepep_mode.enable_low_latency(),
|
|
num_qps_per_rank=num_qps_per_rank,
|
|
allow_mnnvl=True,
|
|
)
|
|
free_gpu_memory_end = _get_available_gpu_memory(torch.cuda.current_device())
|
|
logger.info(
|
|
"DeepEPBuffer use memory %s GB", free_gpu_memory_begin - free_gpu_memory_end
|
|
)
|
|
return cls._buffer
|
|
|
|
@classmethod
|
|
def clean_buffer(cls):
|
|
if cls._buffer is None:
|
|
return
|
|
if not cls._buffer.low_latency_mode:
|
|
return
|
|
cls._buffer.clean_low_latency_buffer(
|
|
cls._num_max_dispatch_tokens_per_rank,
|
|
cls._hidden_size,
|
|
cls._num_experts,
|
|
)
|
|
|
|
@classmethod
|
|
def set_dispatch_mode_as_normal(cls):
|
|
cls._dispatch_mode = DeepEPDispatchMode.NORMAL
|
|
|
|
@classmethod
|
|
def set_dispatch_mode_as_low_latency(cls):
|
|
if cls._dispatch_mode == DeepEPDispatchMode.NORMAL:
|
|
cls.clean_buffer()
|
|
cls._dispatch_mode = DeepEPDispatchMode.LOW_LATENCY
|
|
|
|
|
|
class _DeepEPDispatcherImplBase:
|
|
def __init__(
|
|
self,
|
|
group: torch.distributed.ProcessGroup,
|
|
router_topk: int,
|
|
permute_fusion: bool,
|
|
num_experts: int,
|
|
num_local_experts: int,
|
|
hidden_size: int,
|
|
params_dtype: torch.dtype,
|
|
deepep_mode: DeepEPMode,
|
|
low_latency_max_num_tokens_per_gpu: int,
|
|
):
|
|
self.group = group
|
|
self.router_topk = router_topk
|
|
self.permute_fusion = permute_fusion
|
|
self.num_experts = num_experts
|
|
self.num_local_experts = num_local_experts
|
|
self.hidden_size = hidden_size
|
|
self.params_dtype = params_dtype
|
|
self.deepep_mode = deepep_mode
|
|
|
|
self.params_bytes = 2
|
|
self.num_max_dispatch_tokens_per_rank = low_latency_max_num_tokens_per_gpu
|
|
|
|
self.handle = None
|
|
|
|
def dispatch_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
):
|
|
raise NotImplementedError
|
|
|
|
def dispatch_b(self, *args, **kwargs):
|
|
raise NotImplementedError
|
|
|
|
def combine_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
raise NotImplementedError
|
|
|
|
def combine_b(self, *args, **kwargs):
|
|
raise NotImplementedError
|
|
|
|
def _get_buffer(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
class _DeepEPDispatcherImplNormal(_DeepEPDispatcherImplBase):
|
|
def __init__(self, async_finish: bool, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
self.async_finish = async_finish
|
|
self.src2dst = None
|
|
|
|
def dispatch_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
):
|
|
from tokenspeed_kernel.ops.gemm.fp8_utils import per_token_group_quant_fp8
|
|
|
|
hidden_states = per_token_group_quant_fp8(hidden_states, 128)
|
|
topk_idx = topk_idx.to(torch.int64)
|
|
topk_weights = topk_weights.to(torch.float32)
|
|
previous_event = Buffer.capture() if self.async_finish else None
|
|
return hidden_states, topk_idx, topk_weights, previous_event
|
|
|
|
def dispatch_b(self, hidden_states, topk_idx, topk_weights, previous_event):
|
|
(
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
num_recv_tokens_per_expert_list,
|
|
event,
|
|
) = self._dispatch_core(hidden_states, topk_idx, topk_weights, previous_event)
|
|
event.current_stream_wait() if self.async_finish else ()
|
|
|
|
return (
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
None, # reorder_topk_ids
|
|
num_recv_tokens_per_expert_list,
|
|
None, # seg_indptr
|
|
None, # masked_m
|
|
)
|
|
|
|
def _dispatch_core(
|
|
self,
|
|
x: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
previous_event,
|
|
):
|
|
# Note: We intentionally do not switch devices here.
|
|
# DeepEP buffer is initialized on a specific device context and
|
|
# switching devices during dispatch can cause "invalid resource handle" errors.
|
|
# The caller is responsible for ensuring tensors are on the correct device.
|
|
buffer = self._get_buffer()
|
|
(
|
|
num_tokens_per_rank,
|
|
num_tokens_per_rdma_rank,
|
|
num_tokens_per_expert,
|
|
is_token_in_rank,
|
|
previous_event,
|
|
) = buffer.get_dispatch_layout(
|
|
topk_idx,
|
|
self.num_experts,
|
|
previous_event=previous_event,
|
|
async_finish=self.async_finish,
|
|
allocate_on_comm_stream=previous_event is not None,
|
|
)
|
|
|
|
# In principle ``handle`` should travel alongside the dispatched tokens
|
|
# into combine(). Today that path triggers a synchronization issue, so
|
|
# keep the handle on the dispatcher instance instead.
|
|
|
|
(
|
|
recv_x,
|
|
recv_topk_idx,
|
|
recv_topk_weights,
|
|
num_recv_tokens_per_expert_list,
|
|
self.handle,
|
|
event,
|
|
) = buffer.dispatch(
|
|
x,
|
|
topk_idx=topk_idx,
|
|
topk_weights=topk_weights,
|
|
num_tokens_per_rank=num_tokens_per_rank,
|
|
num_tokens_per_rdma_rank=num_tokens_per_rdma_rank,
|
|
is_token_in_rank=is_token_in_rank,
|
|
num_tokens_per_expert=num_tokens_per_expert,
|
|
previous_event=previous_event,
|
|
async_finish=self.async_finish,
|
|
allocate_on_comm_stream=(previous_event is not None) and self.async_finish,
|
|
expert_alignment=128,
|
|
)
|
|
|
|
return (
|
|
recv_x,
|
|
recv_topk_idx,
|
|
recv_topk_weights,
|
|
num_recv_tokens_per_expert_list,
|
|
event,
|
|
)
|
|
|
|
def combine_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: tuple[torch.Tensor, torch.Tensor],
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
previous_event = Buffer.capture() if self.async_finish else None
|
|
return hidden_states, previous_event
|
|
|
|
def combine_b(
|
|
self,
|
|
output: torch.Tensor,
|
|
previous_event,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: tuple[torch.Tensor, torch.Tensor],
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
hidden_states, event = self._combine_core(
|
|
output, previous_event, topk_idx, topk_weights, moe_origin_input
|
|
)
|
|
event.current_stream_wait() if self.async_finish else ()
|
|
self.handle = None
|
|
self.src2dst = None
|
|
return hidden_states
|
|
|
|
def _combine_core(
|
|
self,
|
|
x: torch.Tensor,
|
|
previous_event,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: tuple[torch.Tensor, torch.Tensor],
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
topk_idx_ori, topk_weights_ori, topk_weights_recv = (
|
|
(topk_idx, topk_weights[0], topk_weights[1])
|
|
if moe_origin_input is not None
|
|
else (topk_idx, None, topk_weights)
|
|
)
|
|
buffer = self._get_buffer()
|
|
combine_args = {
|
|
"x": x,
|
|
"handle": self.handle,
|
|
"async_finish": self.async_finish,
|
|
"previous_event": previous_event,
|
|
"allocate_on_comm_stream": previous_event is not None,
|
|
}
|
|
if moe_origin_input is not None:
|
|
combine_args.update(
|
|
{
|
|
"topk_weights": topk_weights_recv,
|
|
"topk_idx_ori": topk_idx_ori,
|
|
"topk_weights_ori": topk_weights_ori,
|
|
"x_ori": moe_origin_input,
|
|
}
|
|
)
|
|
combined_x, _, event = buffer.combine(**combine_args)
|
|
return combined_x, event
|
|
|
|
def _get_buffer(self):
|
|
DeepEPBuffer.set_dispatch_mode_as_normal()
|
|
return DeepEPBuffer.get_deepep_buffer(
|
|
self.group,
|
|
self.hidden_size,
|
|
self.params_bytes,
|
|
self.deepep_mode,
|
|
self.num_max_dispatch_tokens_per_rank,
|
|
self.num_experts,
|
|
)
|
|
|
|
|
|
class _DeepEPDispatcherImplLowLatency(_DeepEPDispatcherImplBase):
|
|
def __init__(self, return_recv_hook: bool, use_fp8: bool = False, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
"""
|
|
num_max_dispatch_tokens_per_rank: the actual batch size in the decoding engine should be less than 256
|
|
https://github.com/deepseek-ai/DeepEP?tab=readme-ov-file#example-use-in-inference-decoding
|
|
"""
|
|
self.return_recv_hook = return_recv_hook
|
|
self.use_fp8 = use_fp8
|
|
|
|
def dispatch_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
):
|
|
# DeepEP requires independent contiguous tensors to prevent issues with
|
|
# upstream tensor aliasing or non-standard strides. We clone to ensure
|
|
# complete memory isolation, which is critical for low-latency dispatch.
|
|
#
|
|
# Dtype requirements:
|
|
# - hidden_states: preserve original dtype (bf16/fp16/fp32)
|
|
# - topk_idx: must be int64 (DeepEP C++ kernel API requirement for expert indices)
|
|
# - topk_weights: use float32 for routing precision to avoid numerical issues
|
|
hidden_states = hidden_states.contiguous().clone()
|
|
topk_idx = topk_idx.to(torch.int64).contiguous().clone()
|
|
topk_weights = topk_weights.to(torch.float32).contiguous().clone()
|
|
hidden_states, masked_m, event, hook = self._dispatch_core(
|
|
hidden_states,
|
|
topk_idx,
|
|
use_fp8=self.use_fp8,
|
|
)
|
|
return (
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
masked_m,
|
|
event,
|
|
hook,
|
|
)
|
|
|
|
def dispatch_b(
|
|
self,
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
masked_m,
|
|
event,
|
|
hook,
|
|
):
|
|
hook() if self.return_recv_hook else event.current_stream_wait()
|
|
|
|
return (
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
None, # reorder_topk_ids
|
|
None, # num_recv_tokens_per_expert_list
|
|
None, # seg_indptr
|
|
masked_m,
|
|
)
|
|
|
|
def _dispatch_core(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
use_fp8: bool = False,
|
|
):
|
|
"""
|
|
# For H20, there will be an CUDA error: DeepEP/csrc/kernels/internode_ll.cu:337 'too many blocks in cooperative launch'.
|
|
# Please make sure to change DeepEP code in internode_ll.cu dispatch / combine as below first and then reinstall.
|
|
# More details refer: https://github.com/deepseek-ai/DeepEP/issues/15#issuecomment-2709715782
|
|
|
|
diff --git a/csrc/kernels/internode_ll.cu b/csrc/kernels/internode_ll.cu
|
|
index 76ae2e2..8ecd08f 100644
|
|
--- a/csrc/kernels/internode_ll.cu
|
|
+++ b/csrc/kernels/internode_ll.cu
|
|
@@ -310,8 +310,8 @@ void dispatch(void* packed_recv_x, float* packed_recv_x_scales,
|
|
int num_topk, int num_experts, int rank, int num_ranks, bool use_fp8,
|
|
void* workspace, cudaStream_t stream, int phases) {
|
|
constexpr int kNumMaxTopK = 9;
|
|
- constexpr int kNumWarpsPerGroup = 10;
|
|
- constexpr int kNumWarpGroups = 3;
|
|
+ constexpr int kNumWarpsPerGroup = 8;
|
|
+ constexpr int kNumWarpGroups = 4;
|
|
EP_STATIC_ASSERT(kNumMaxTopK + 1 <= kNumWarpGroups * kNumWarpsPerGroup, "Too many top-k selections");
|
|
|
|
const auto num_warps = kNumWarpGroups * kNumWarpsPerGroup;
|
|
@@ -501,8 +501,8 @@ void combine(void* combined_x,
|
|
int num_combined_tokens, int hidden, int num_max_dispatch_tokens_per_rank,
|
|
int num_topk, int num_experts, int rank, int num_ranks,
|
|
void* workspace, cudaStream_t stream, int phases) {
|
|
- constexpr int kNumWarpsPerGroup = 10;
|
|
- constexpr int kNumWarpGroups = 3;
|
|
+ constexpr int kNumWarpsPerGroup = 8;
|
|
+ constexpr int kNumWarpGroups = 4;
|
|
constexpr int kNumMaxTopk = 9;
|
|
|
|
const auto num_warps = kNumWarpGroups * kNumWarpsPerGroup;
|
|
"""
|
|
buffer = self._get_buffer()
|
|
packed_recv_hidden, packed_recv_count, self.handle, event, hook = (
|
|
buffer.low_latency_dispatch(
|
|
hidden_states,
|
|
topk_idx,
|
|
self.num_max_dispatch_tokens_per_rank,
|
|
self.num_experts,
|
|
use_fp8=use_fp8,
|
|
async_finish=not self.return_recv_hook,
|
|
return_recv_hook=self.return_recv_hook,
|
|
)
|
|
)
|
|
return packed_recv_hidden, packed_recv_count, event, hook
|
|
|
|
def combine_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
hidden_states, event, hook = self._combine_core(
|
|
hidden_states, topk_idx, topk_weights, moe_origin_input
|
|
)
|
|
return hidden_states, event, hook
|
|
|
|
def combine_b(self, hidden_states, event, hook):
|
|
hook() if self.return_recv_hook else event.current_stream_wait()
|
|
return hidden_states
|
|
|
|
def _combine_core(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
buffer = self._get_buffer()
|
|
combined_hidden_states, event, hook = buffer.low_latency_combine(
|
|
hidden_states,
|
|
topk_idx,
|
|
topk_weights,
|
|
self.handle,
|
|
async_finish=not self.return_recv_hook,
|
|
return_recv_hook=self.return_recv_hook,
|
|
)
|
|
self.handle = None
|
|
return combined_hidden_states, event, hook
|
|
|
|
def _get_buffer(self):
|
|
DeepEPBuffer.set_dispatch_mode_as_low_latency()
|
|
return DeepEPBuffer.get_deepep_buffer(
|
|
self.group,
|
|
self.hidden_size,
|
|
self.params_bytes,
|
|
self.deepep_mode,
|
|
self.num_max_dispatch_tokens_per_rank,
|
|
self.num_experts,
|
|
)
|
|
|
|
|
|
class DeepEPDispatcher:
|
|
def __init__(
|
|
self,
|
|
config: Any,
|
|
deepep_mode: DeepEPMode = DeepEPMode.auto,
|
|
async_finish: bool = True,
|
|
return_recv_hook: bool = True,
|
|
use_fp8: bool = False,
|
|
):
|
|
self.deepep_mode = deepep_mode
|
|
|
|
common_kwargs = dict(
|
|
group=config.group,
|
|
router_topk=config.top_k,
|
|
permute_fusion=True,
|
|
num_experts=config.num_experts,
|
|
num_local_experts=config.num_experts // config.world_size,
|
|
hidden_size=config.hidden_size,
|
|
params_dtype=config.params_dtype,
|
|
deepep_mode=deepep_mode,
|
|
low_latency_max_num_tokens_per_gpu=config.low_latency_max_num_tokens_per_gpu,
|
|
)
|
|
|
|
if self.deepep_mode.enable_low_latency():
|
|
self._low_latency_dispatcher = _DeepEPDispatcherImplLowLatency(
|
|
return_recv_hook=return_recv_hook,
|
|
use_fp8=use_fp8,
|
|
**common_kwargs,
|
|
)
|
|
if self.deepep_mode.enable_normal():
|
|
self._normal_dispatcher = _DeepEPDispatcherImplNormal(
|
|
async_finish=async_finish,
|
|
**common_kwargs,
|
|
)
|
|
|
|
def dispatch(self, *args, **kwargs) -> tuple:
|
|
self.dispatch_a(*args, **kwargs)
|
|
return self.dispatch_b()
|
|
|
|
def dispatch_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
forward_mode,
|
|
):
|
|
topk_idx = topk_idx.to(torch.int64)
|
|
inner_state = self._get_impl(forward_mode).dispatch_a(
|
|
hidden_states=hidden_states,
|
|
topk_idx=topk_idx,
|
|
topk_weights=topk_weights,
|
|
)
|
|
self._dispatch_intermediate_state = forward_mode, inner_state
|
|
|
|
def dispatch_b(self):
|
|
forward_mode, inner_state = self._dispatch_intermediate_state
|
|
del self._dispatch_intermediate_state
|
|
return self._get_impl(forward_mode).dispatch_b(*inner_state)
|
|
|
|
def combine(self, *args, **kwargs) -> tuple:
|
|
self.combine_a(*args, **kwargs)
|
|
return self.combine_b()
|
|
|
|
def combine_a(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
topk_idx: torch.Tensor,
|
|
topk_weights: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
|
|
forward_mode,
|
|
moe_origin_input: torch.Tensor = None,
|
|
):
|
|
topk_idx = topk_idx.to(torch.int64)
|
|
inner_state = self._get_impl(forward_mode).combine_a(
|
|
hidden_states=hidden_states,
|
|
topk_idx=topk_idx,
|
|
topk_weights=topk_weights,
|
|
moe_origin_input=moe_origin_input,
|
|
)
|
|
self._combine_intermediate_state = (
|
|
forward_mode,
|
|
inner_state,
|
|
topk_idx,
|
|
topk_weights,
|
|
moe_origin_input,
|
|
)
|
|
|
|
def combine_b(self):
|
|
forward_mode, inner_state, topk_idx, topk_weights, moe_origin_input = (
|
|
self._combine_intermediate_state
|
|
)
|
|
if self.deepep_mode.resolve(forward_mode) == DeepEPMode.normal:
|
|
inner_state = inner_state + (topk_idx, topk_weights, moe_origin_input)
|
|
del self._combine_intermediate_state
|
|
return self._get_impl(forward_mode).combine_b(*inner_state)
|
|
|
|
def _get_impl(self, forward_mode) -> _DeepEPDispatcherImplBase:
|
|
resolved_deepep_mode = self.deepep_mode.resolve(forward_mode)
|
|
if resolved_deepep_mode == DeepEPMode.normal:
|
|
return self._normal_dispatcher
|
|
if resolved_deepep_mode == DeepEPMode.low_latency:
|
|
return self._low_latency_dispatcher
|
|
raise ValueError(f"Invalid deepep_mode: {self.deepep_mode}")
|