MoE call chain from ds_vllm (vllm-project/vllm latest): ex_engine/moe/ — 20 files, 8736 lines - modular_kernel.py (1630 lines) — base classes for modular MoE - experts/fused_batched_moe.py (972 lines) — NaiveBatchedExperts - prepare_finalize/batched.py (171 lines) — token grouping by expert - topk_weight_and_reduce.py (176 lines) — scatter-add finalize - fused_moe.py (1740 lines) — main fused_moe dispatch - config.py (1407 lines) — FusedMoEQuantConfig - activation.py, utils.py, layer.py, etc. xllm layer code (jd-opensource/xllm): ex_engine/xllm_layers/ — 39 files, 5859 lines - ilu/fused_moe.cpp (797 lines) — production ixformer 7-step MoE pipeline - ilu/attention.cpp (189 lines) — paged_attention + flash_attn bridge - npu_torch/qwen3_gated_delta_net_base.cpp (576 lines) — GDN reference - common/rms_norm.cpp, rotary_embedding.cpp, activation.cpp, dense_mlp.cpp xllm ILU kernels — synced 10 files to upstream (diffs from prior edits) These are reference implementations, NOT hand-written. Source repos: vllm-project/vllm, jd-opensource/xllm
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
|
|
import torch
|
|
|
|
import vllm._custom_ops as ops
|
|
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
|
|
|
|
|
|
class TopKWeightAndReduceDelegate(mk.TopKWeightAndReduce):
|
|
"""
|
|
Useful in the case when some FusedMoEExpertsModular
|
|
implementation does not perform weight application and reduction
|
|
but cannot address the needs of all the compatible PrepareAndFinalize
|
|
implementations.
|
|
For example, BatchedTritonExperts is compatible with both batched
|
|
PrepareAndFinalize implementations like DeepEPLLPrepareAndFinalize and
|
|
BatchedPrepareAndFinalize. Some PrepareAndFinalize implementations do
|
|
the weight-application + reduction as part of the combine kernel, while
|
|
BatchedPrepareAndFinalize needs an explicit implementation. To facilitate
|
|
this case, the BatchedTritonExperts could use TopKWeightAndReduceDelegate
|
|
so the PrepareAndFinalize implementations could choose how to
|
|
weight + reduce.
|
|
"""
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, TopKWeightAndReduceDelegate)
|
|
|
|
def apply(
|
|
self,
|
|
output: torch.Tensor | None,
|
|
fused_expert_output: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
topk_ids: torch.Tensor,
|
|
apply_router_weight_on_input: bool,
|
|
) -> torch.Tensor:
|
|
raise RuntimeError(
|
|
"The caller is expected to choose an appropriate "
|
|
"TopKWeightAndReduce implementation."
|
|
)
|
|
|
|
|
|
class TopKWeightAndReduceNoOP(mk.TopKWeightAndReduce):
|
|
"""
|
|
The fused_experts outputs have already been weight applied and reduced.
|
|
This implementation is a no-op.
|
|
"""
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, TopKWeightAndReduceNoOP)
|
|
|
|
def apply(
|
|
self,
|
|
output: torch.Tensor | None,
|
|
fused_expert_output: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
topk_ids: torch.Tensor,
|
|
apply_router_weight_on_input: bool,
|
|
) -> torch.Tensor:
|
|
# Weight application and reduction operations are already done.
|
|
if output is None:
|
|
return fused_expert_output
|
|
|
|
# Skip self-copy when caller aliased fused_out to output upstream.
|
|
if output is fused_expert_output:
|
|
return output
|
|
|
|
# MoEPrepareAndFinalizeNoDPEPModular needs the output to be in the `output`
|
|
# tensor.
|
|
assert output.size() == fused_expert_output.size(), (
|
|
"output shape is expected to match the fused_expert_output shape. "
|
|
f"But got output={output.size()}, "
|
|
f"used_expert_output={fused_expert_output.size()}"
|
|
)
|
|
output.copy_(fused_expert_output, non_blocking=True)
|
|
return output
|
|
|
|
|
|
class TopKWeightAndReduceContiguous(mk.TopKWeightAndReduce):
|
|
"""
|
|
TopKWeightAndReduce implementation for a fused_experts output
|
|
of shape (m, topk, K)
|
|
"""
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, TopKWeightAndReduceContiguous)
|
|
|
|
def apply(
|
|
self,
|
|
output: torch.Tensor | None,
|
|
fused_expert_output: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
topk_ids: torch.Tensor,
|
|
apply_router_weight_on_input: bool,
|
|
) -> torch.Tensor:
|
|
m, num_topk = topk_ids.size()
|
|
k = fused_expert_output.size(-1)
|
|
if fused_expert_output.ndim == 2:
|
|
fused_expert_output = fused_expert_output.view(m, num_topk, k)
|
|
|
|
assert fused_expert_output.size() == (m, num_topk, k), (
|
|
f"Expected fused_expert_output size {(m, num_topk, k)}. But got "
|
|
f"{fused_expert_output.size()}"
|
|
)
|
|
|
|
if not apply_router_weight_on_input:
|
|
fused_expert_output.mul_(topk_weights.view(m, -1, 1))
|
|
|
|
if output is None:
|
|
output = torch.empty(
|
|
(m, k),
|
|
device=fused_expert_output.device,
|
|
dtype=fused_expert_output.dtype,
|
|
)
|
|
assert output.size() == (m, k), (
|
|
f"Expected output size {(m, k)}. But got {output.size()}"
|
|
)
|
|
|
|
ops.moe_sum(fused_expert_output, output)
|
|
return output
|
|
|
|
|
|
class TopKWeightAndReduceNaiveBatched(mk.TopKWeightAndReduce):
|
|
"""
|
|
TopKWeightAndReduce implementation for a fused_experts output
|
|
of shape (num_experts, batch_size, K)
|
|
"""
|
|
|
|
def __init__(self, rank: int):
|
|
self.rank = rank
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, TopKWeightAndReduceNaiveBatched) and (
|
|
other.rank == self.rank
|
|
)
|
|
|
|
def apply(
|
|
self,
|
|
output: torch.Tensor | None,
|
|
fused_expert_output: torch.Tensor,
|
|
topk_weights: torch.Tensor,
|
|
topk_ids: torch.Tensor,
|
|
apply_router_weight_on_input: bool,
|
|
) -> torch.Tensor:
|
|
assert fused_expert_output.ndim == 3
|
|
num_tokens = topk_ids.size(0)
|
|
num_local_experts = fused_expert_output.size(0)
|
|
K = fused_expert_output.size(-1)
|
|
|
|
if output is None:
|
|
output = torch.zeros(
|
|
(num_tokens, K),
|
|
device=fused_expert_output.device,
|
|
dtype=fused_expert_output.dtype,
|
|
)
|
|
else:
|
|
output.fill_(0)
|
|
|
|
assert output.size() == (num_tokens, K), (
|
|
f"Expected output size {(num_tokens, K)}, but got {output.size()}"
|
|
)
|
|
|
|
first_expert = num_local_experts * self.rank
|
|
last_expert = first_expert + num_local_experts
|
|
|
|
for expert_id in range(first_expert, last_expert):
|
|
matching_tokens = topk_ids == expert_id
|
|
topks = torch.any(matching_tokens, dim=1).flatten()
|
|
rows = torch.count_nonzero(topks)
|
|
rhs = fused_expert_output[expert_id - first_expert, :rows, :]
|
|
if not apply_router_weight_on_input:
|
|
rhs.mul_(topk_weights[matching_tokens].view(rhs.size(0), 1))
|
|
output[topks] = output[topks] + rhs
|
|
|
|
return output
|