大模型推理引擎vLLM(21): vllm0.21.0中EP基类代码modular_kernel.py详细走读
目录
2.1 Modular:FusedMoEKernelModularImpl
2.2 Monolithic:FusedMoEKernelMonolithicImpl
3 topk_ids, topk_weights的由来以及内容
4 class TopKWeightAndReduce(ABC):
5 PrepareResultType与ReceiverType
6 class FusedMoEPrepareAndFinalize(ABC):
6.1 def post_init_setup(self, fused_experts: "FusedMoEPermuteExpertsUnpermute"):
6.2 def activation_format(self) -> FusedMoEActivationFormat:
6.3 def topk_indices_dtype(self) -> torch.dtype | None:
6.4 def max_num_tokens_per_rank(self) -> int | None:
7 class FusedMoEPrepareAndFinalizeModular(FusedMoEPrepareAndFinalize):
7.3.2 fused_expert_output: torch.Tensor
7.3.3 topk_weights: torch.Tensor
7.3.5 apply_router_weight_on_input: bool
7.3.6. weight_and_reduce_impl: TopKWeightAndReduce
8 class FusedMoEPrepareAndFinalizeMonolithic(FusedMoEPrepareAndFinalize):
10 class FusedMoEExpertsModular(FusedMoEExperts):
11 class FusedMoEKernelModularImpl:
abstract:
modular_kernel.py
modular 英/ ˈmɒdjələ(r) / 组合式的;模块化的;模数的;有标准组件的。
monolithic 英/ ˌmɒnəˈlɪθɪk / 整体的;巨石的,庞大的;完全统一的, 单块集成电路,单片电路。
router_logits是由线性层矩阵乘法得到,然后再做softmax和topk的操作,
topk_ids [M, topk]:每个 token 选哪些专家(整数 ID)topk_weights [M, topk]:topk专家权重(浮点,用来加权求和)
tuple 英/ tjʊpəl; ˈtʌpəl / 元组不可变
@property函数调用不用加括号。
indices 英/ ˈɪndɪsiːz / 目录(index的复数)
expert_map,全局专家到本地专家的映射
expected_m 每个专家要处理的期望token数量,主要是给 GEMM 库(DeepGEMM 这类 masked grouped GEMM)当「规模提示」,用来做 kernel 选型/调度优化,不是用来决定算哪些 token。
token数M K ,权重E N K的转置
Standard、BatchedExperts
调用普通实例函数自动传实例;调用@classmethod 自动传类;调用@staticmethod函数什么都不传;
- 纯 DBO(无共享专家):发起 combine(异步,后台跑)---立刻 register hook + dbo_yield() → 让给 batch B---receiver() 等 combine 完成
- DBO + 共享专家 overlap:发起 combine(异步,后台跑)---算共享专家(前台,和 combine 并行) 多出来的这一步---register hook + dbo_yield() ,才让给 batch B---receiver() 等 combine 完成
本来 combine 一发起就让给 B;现在有共享专家时,先不让,在 combine 还在后台跑的时候做共享专家,共享专家算完再让出给 B。
0 引言
之前基于vllm015读过这段代码,但是那时候是刚开始第一遍读,有些理解的不太对,另一个就是新版本代码正好有了一些改变,这次再详细的过一遍。
1 文件整体介绍
#
# This file defines a set of base classes used to make MoE kernels more modular.
# The goal is to be able to utilize different communication mechanisms with
# any fused MoE kernel without needing to have combinatoric implementations.
#
# The fused moe kernels are broken down into the following components:
#
# [Router] → [Quantize-Dispatch] → [Permute-Experts-Unpermute] → [Combine]
#
# Each component will be independent of (but may inform) the others except for
# [Quantize-Dispatch] and `[Combine] (see below). The components can then be
# mixed and matched with so that DP+EP can be supported easily for multiple
# MoE kernel implementations.
#
# The following main classes are defined:
# * FusedMoEPrepareAndFinalizeModular - an abstract base class for preparation of MoE
# inputs (e.g. quantization, distribution) and finalization of Moe outputs.
# The prepare method must take care of any needed quantization and the
# finalize method, informed by the FusedMoEExpertsModular method,
# may apply weights and/or do the final reduction of the output.
# * FusedMoEExpertsModular - an abstract base class for the main fused
# MoE operation, i.e matmul + act_mul + optionally quant + matmul.
# Some FusedMoEExpertsModular implementations may choose to do
# the weight application and/or reduction. The class communicates this
# to [Finalize] via a TopKWeightAndReduce object.
# * FusedMoEModularKernel - an interface class that combines a
# FusedMoEPrepareAndFinalizeModular and a FusedMoEExpertsModular to
# provide the standard fused MoE kernel interface.
# * TopKWeightAndReduce - A TopKWeightAndReduce implementation chosen
# by the FusedMoEExpertsModular implementation that is passed
# on to [Finalize].
#
# [Quantize-Prepare] and [Finalize] functionality are bundled into a single
# class `FusedMoEPrepareAndFinalizeModular` since they could use collective
# communication mechanisms that need to be consistent.
#
个人理解:
其实这里就是说,这个modular_kernel.py是为了让moe这块代码模块化,这样后面要是比如替换了什么通信库,或者替换了专家计算的kernel,那么这块代码不用修改,也就是模块化,减少耦合,然后让不同的通信库和计算库可以任何组装,然后这个文件定义了几个基类,
然后这个文件主要是下面的这三个类
- FusedMoEPrepareAndFinalizeModular:这个类就是负责前处理和后处理的,其实就是负责dispatch和combine的;
- FusedMoEExpertsModular:这个是负责那两次专家计算以及激活的;
- FusedMoEModularKernel:这个是对外的,也就是用户只需要用这个类就可以了,然后这个类里面其实就包含了上面说的两个类。
但是其实看了后面代码会发现,这个代码注释是错误的,老的,其实真正对外开放的类是FusedMoEKernel,然后这个类里面是分了两种实例,
- FusedMoEKernelModularImpl
- FusedMoEKernelMonolithicImpl
# Initialize the implementation (monolithic or modular).
self.impl: FusedMoEKernelModularImpl | FusedMoEKernelMonolithicImpl
if isinstance(
prepare_finalize, FusedMoEPrepareAndFinalizeModular
) and isinstance(fused_experts, FusedMoEExpertsModular):
self.impl = FusedMoEKernelModularImpl(
prepare_finalize,
fused_experts,
shared_experts,
inplace,
N,
K,
)
elif isinstance(
prepare_finalize, FusedMoEPrepareAndFinalizeMonolithic
) and isinstance(fused_experts, FusedMoEExpertsMonolithic):
assert not inplace
self.impl = FusedMoEKernelMonolithicImpl(
prepare_finalize,
fused_experts,
)
2 Monolithic 和 Modular的区别
| Modular | Monolithic | |
|---|---|---|
|
路由输入 |
已经算好的 |
原始的 |
|
Router 谁做 |
框架里先 |
往往 fuse 在 kernel 里面(或整条链路按 logits 走) |
|
典型场景 |
Triton、DeepEP、大多数通用 MoE |
FlashInfer TRTLLM 等 router+experts 一体 的 kernel |
|
对外接口 |
|
|
2.1 Modular:FusedMoEKernelModularImpl
上层 Router 先算 topk
↓
prepare(topk_ids, topk_weights) # dispatch / 量化
↓
experts.apply(topk_ids, topk_weights) # w13 + 激活 + w2
↓
finalize(..., TopKWeightAndReduce) # combine + 加权 + reduce
2.2 Monolithic:FusedMoEKernelMonolithicImpl
整条链路按「router logits 还没拆成 topk」来设计:
# TODO(rob): add inplace support.
a1q, a1q_scale, router_logits = self.prepare_finalize.prepare(
hidden_states,
router_logits=router_logits,
quant_config=self.fused_experts.quant_config,
defer_input_quant=self.fused_experts.expects_unquantized_inputs,
)
fused_out = self.fused_experts.apply(
hidden_states=a1q,
w1=w1,
w2=w2,
router_logits=router_logits,
activation=activation,
global_num_experts=global_num_experts,
expert_map=expert_map,
apply_router_weight_on_input=apply_router_weight_on_input,
a1q_scale=a1q_scale,
# grouped topk + fused topk bias parameters
num_expert_group=num_expert_group,
e_score_correction_bias=e_score_correction_bias,
routed_scaling_factor=routed_scaling_factor,
topk_group=topk_group,
)
output = self.prepare_finalize.finalize(fused_out)
return output
和 Modular 对比:
| 步骤 | Modular | Monolithic |
|---|---|---|
|
prepare 入参 |
|
|
|
experts 入参 |
|
|
|
finalize 入参 |
|
只要 |
注释里写得很直白:给「router 和 experts 已经 fuse 在一起」的 kernel 用,例如 FLASHINFER_TRTLLM。
这类 kernel 内部自己完成 routing(topk、分组 topk 等),框架不必先在外面 select_experts 再传 topk_ids。
2.3 个人理解以及疑问
Router 线性层
↓
router_logits [M, num_experts] ← 原始打分Modular:
select_experts(router_logits)
→ softmax/topk 等
→ topk_ids, topk_weights
→ prepare → experts → finalizeMonolithic:
跳过 select_experts
→ 把 router_logits 和 hidden_states 一起交给 fused kernel
→ kernel 内部自己做 routing + 专家计算
router_logits是由线性层矩阵乘法得到,然后再做softmax和topk的操作,
3 topk_ids, topk_weights的由来以及内容
vllm021/vllm/model_executor/layers/fused_moe/router/fused_topk_router.py文件中
def fused_topk(
hidden_states: torch.Tensor,
gating_output: torch.Tensor,
topk: int,
renormalize: bool,
indices_type: torch.dtype | None = None,
scoring_func: str = "softmax",
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
assert hidden_states.size(0) == gating_output.size(0), "Number of tokens mismatch"
M, _ = hidden_states.size()
topk_weights = torch.empty(
M, topk, dtype=torch.float32, device=hidden_states.device
)
topk_ids = torch.empty(
M,
topk,
dtype=torch.int32 if indices_type is None else indices_type,
device=hidden_states.device,
)
token_expert_indices = torch.empty(
M, topk, dtype=torch.int32, device=hidden_states.device
)
if scoring_func == "softmax":
topk_func = dispatch_topk_softmax_func(
use_rocm_aiter=rocm_aiter_ops.is_fused_moe_enabled()
)
topk_weights, topk_ids = topk_func(
topk_weights, topk_ids, token_expert_indices, gating_output, renormalize
)
return topk_weights, topk_ids, token_expert_indices
elif scoring_func == "sigmoid":
topk_func = dispatch_topk_sigmoid_func(
use_rocm_aiter=rocm_aiter_ops.is_fused_moe_enabled()
)
topk_weights, topk_ids = topk_func(
topk_weights, topk_ids, token_expert_indices, gating_output, renormalize
)
return topk_weights, topk_ids, token_expert_indices
else:
raise ValueError(f"Unsupported scoring function: {scoring_func}")
router_logits [M, num_experts]
↓ softmax/sigmoid + 选 top-k
topk_ids [M, topk] 选中了哪几个专家
topk_weights [M, topk] 这几个专家各多大权重
4 class TopKWeightAndReduce(ABC):
class TopKWeightAndReduce(ABC):
"""
An abstract base class for weight application and reduction implementations.
"""
@abstractmethod
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:
"""
Apply topk_weights to the fused_experts_outputs and/or reduce.
If an output tensor is not passed, it will be created in the
function.
"""
raise NotImplementedError
这是个基类,然后用来计算topk加权求和的,
MoE 里每个 token 会走 topk 个专家,专家输出形状一般是
fused_expert_output: [M, topk, K]
最后要变成:
output: [M, K]
需要:乘 topk_weights + 在 topk 维上求和。
但不同实现差别很大:
- 有的 experts 里已经乘权重、已经 reduce 了
- 有的 只算未加权结果,要等 finalize/combine 再做
- Batched 布局和 Standard 布局 reduce 方式也不同
所以抽象出 TopKWeightAndReduce,由 Experts 实现选一个策略,交给 Finalize 用。
| 参数 | 含义 |
|---|---|
|
|
专家输出,通常 |
|
|
路由权重和专家 ID |
|
|
权重是否已在输入上乘过 |
|
|
写入的最终 |
5 PrepareResultType与ReceiverType
#
# PrepareResultType is a tuple of:
# - quantized + dispatched a.
# - quantized + dispatched a1_scales.
# - Optional ExpertTokensMetadata containing gpu/cpu tensors
# as big as the number of local experts with the information about the
# number of tokens assigned to each local expert.
# - Optional dispatched expert topk IDs
# - Optional dispatched expert topk weight
#
# See `prepare` method below.
#
PrepareResultType = tuple[
torch.Tensor,
torch.Tensor | None,
ExpertTokensMetadata | None,
torch.Tensor | None,
torch.Tensor | None,
]
ReceiverType = Callable[[], PrepareResultType]
这里ReceiverType = Callable[[], PrepareResultType]的意思就是定义了一个可调用类似,他的参数是空的,返回值类型是PrepareResultType。然后这个PrepareResultType是个元组,包含下面五个。
| 位置 | 你的理解 | 补充一句 |
|---|---|---|
|
0 |
激活 |
dispatch(+ 可能量化)后的 |
|
1 |
激活的量化参数 |
|
|
2 |
每个专家分到多少 token |
|
|
3 |
每个 token 选哪些专家 |
dispatch 之后 的 |
|
4 |
topk 专家权重 |
dispatch 之后 的 |
6 class FusedMoEPrepareAndFinalize(ABC):
这个是基类,然后下面的两个类,都会继承这个类。
- class FusedMoEPrepareAndFinalizeModular(FusedMoEPrepareAndFinalize):
- class FusedMoEPrepareAndFinalizeMonolithic(FusedMoEPrepareAndFinalize):
6.1 def post_init_setup(self, fused_experts: "FusedMoEPermuteExpertsUnpermute"):
def post_init_setup(self, fused_experts: "FusedMoEPermuteExpertsUnpermute"):
"""
Initialize FusedMoEPrepareAndFinalize settings that depend on
FusedMoEPermuteExpertsUnpermute experts object.
The FusedMoEPrepareAndFinalize implementations that have such
dependencies may choose to override this function.
"""
return
这个函数是后初始化函数,为什么有个post,是因为这里的FusedMoEPermuteExpertsUnpermute类和FusedMoEPrepareAndFinalize类是分别单独创建的,所以这里相当于等FusedMoEPermuteExpertsUnpermute创建完之后,才能知道他又哪些功能,然后再根据他去对FusedMoEPrepareAndFinalize类进行相应的配置。
6.2 def activation_format(self) -> FusedMoEActivationFormat:
@property
@abstractmethod
def activation_format(self) -> FusedMoEActivationFormat:
"""
A property indicating the output format of the activations for the
'prepare' method.
"""
raise NotImplementedError
这个函数就是获取dispatch之后激活的张量布局,MoE 的 prepare() 会把 token 量化并 dispatch 到各 EP rank。dispatch 之后,activation 有两种常见排布:
| 枚举值 | 形状 | 直观含义 |
|---|---|---|
|
|
|
所有 token 打平成一维,按 token 顺序排 |
|
|
|
按 local expert 分批,每个 expert 一行 buffer |
6.3 def topk_indices_dtype(self) -> torch.dtype | None:
@abstractmethod
def topk_indices_dtype(self) -> torch.dtype | None:
"""
The PrepareFinalize All2All implementations generally constrain the
dtype of the topk_ids they support. This function returns the
required topk indices dtype so it can be respected.
Return None if there are no such restrictions.
"""
raise NotImplementedError
这个函数其实就是返回topk_ids的类型。
6.4 def max_num_tokens_per_rank(self) -> int | None:
@abstractmethod
def max_num_tokens_per_rank(self) -> int | None:
"""
Some PrepareFinalize All2All implementations are batched. Meaning,
they can process only as set of tokens at a time. This
function returns the batch size i.e the maximum number of tokens
the implementation can process at a time.
Return None if there are no such restrictions.
"""
raise NotImplementedError
这个就是每个rank上处理的token上限,这个是低延迟的要用,
7 class FusedMoEPrepareAndFinalizeModular(FusedMoEPrepareAndFinalize):
7.1 def prepare(
@abstractmethod
def prepare(
self,
a1: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
quant_config: FusedMoEQuantConfig,
) -> PrepareResultType:
"""
Perform any quantization (and/or) dispatching needed for this kernel.
- a1: The (unquantized) input to the MoE layer.
- topk_ids: The topk ids.
- topk_weights: The topk weights.
- num_experts: The total number of experts in the global expert space.
- expert_map: A tensor mapping expert indices from the global expert
space to the local expert space of the expert parallel shard.
- apply_router_weight_on_input: When True, apply the weights to the
activations, before quantization + dispatching.
- quant_config: Quantization info provided by the fused experts.
Returns a tuple of:
- quantized + dispatched a.
- Optional quantized + dispatched a1_scales.
- Optional ExpertTokensMetadata containing gpu/cpu tensors
as big as the number of local experts with the information about the
number of tokens assigned to each local expert.
- Optional dispatched expert topk IDs
- Optional dispatched expert topk weight
"""
raise NotImplementedError
| 参数 | 类型 | 含义 |
|---|---|---|
|
|
|
MoE 层输入激活,一般是 |
|
|
|
|
|
|
|
|
|
|
|
全局专家总数(所有 rank 上的专家数,不是本 rank 本地专家数)。 |
|
|
|
EP 时用:全局专家 ID → 本 rank 本地专家 ID 的映射;不在本 rank 的专家会映到 -1 等。无 EP 时常为 |
|
|
|
是否在 dispatch 之前 把 |
|
|
|
量化配置(FP8/INT8、scale、是否 per-token 等),由 Experts 侧提供,Prepare 按此决定何时、如何量化。 |
这里面唯一一个注意的是, - expert_map: A tensor mapping expert indices from the global expert space to the local expert space of the expert parallel shard.这个是全局专家到本地专家的映射,什么意思,这是个一维的,然后它的长度就是全局专家个数,某个位置用0 1 2 3表示全局专家在当前rank的编号,某个专家-1表示这个专家不在当前rank,所以这个expert_map是每个rank都有一份。举例子,[0, -1, 1, 2, -1, 3, 4 ,5,]
| 序号 | 返回值 | 含义 |
|---|---|---|
|
0 |
|
量化 + dispatch 后的激活。本 rank 上要算的专家输入;形状可能是 |
|
1 |
|
激活的 scale( |
|
2 |
|
每个本地专家分到多少 token(长度 E 的计数),不是「具体哪几个 token」。Batched / DeepEP 路径常见;简单路径可为 |
|
3 |
|
dispatch 后的 |
|
4 |
|
dispatch 后的 |
7.2 def prepare_async(
def prepare_async(
self,
a1: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
quant_config: FusedMoEQuantConfig,
) -> tuple[Callable, ReceiverType] | ReceiverType:
"""
Perform any quantization (and/or) dispatching needed for this kernel
but do not wait for results from other workers.
- a1: The (unquantized) input to the MoE layer.
- a1_scale: Optional scales for a1
- a2_scale: Optional scales for the second MoE gemm. Required to make
sure the quantization is consistent for both gemms.
- topk_ids: The topk ids.
- topk_weights: The topk weights.
- num_experts: The total number of experts in the global expert space.
- expert_map: A tensor mapping expert indices from the global expert
space to the local expert space of the expert parallel shard.
- apply_router_weight_on_input: When True, apply the weights to the
activations, before quantization + dispatching.
Returns a callback or a hook callback pair that when invoked waits for
results from other workers and has the same return signature as
`prepare`, if a hook is returned this is more lightweight check that
the recv is complete without doing extra work (used by DBO, will be
refactored in the very near future)
e.g.
ret = obj.prepare_async(...)
if isinstance(ret, tuple):
hook, receiver = ret
hook()
if hook is not None:
a, a_scales, expert_meta, topk_ids, topk_weights = receiver()
is equivalent to:
a, a_scales, expert_meta, topk_ids, topk_weights = obj.prepare(...)
"""
raise NotImplementedError
这个就是异步的prepare函数,其他的没啥。
7.3 def finalize(
@abstractmethod
def finalize(
self,
output: torch.Tensor,
fused_expert_output: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
apply_router_weight_on_input: bool,
weight_and_reduce_impl: TopKWeightAndReduce,
) -> None:
"""
Perform any combine plus apply weights and perform a reduction on the
fused experts output.
- output: The output tensor, written in place. Must be (M, K) shape.
- fused_expert_output: The unweighted, unreduced output of the fused
experts, it will have (M, topk, K) shape.
- topk_weights: The weights to be applied to the fused_experts_output.
- topk_ids: The topk_ids.
- apply_router_weight_on_input: When False, apply the weights to
fused_expert_output.
- weight_and_reduce_impl: An optional TopKWeightAndReduce
implementation.
"""
raise NotImplementedError
- All2All Combine:把各 rank 上算完的 expert 输出,按 token 路由 送回原 rank
- 乘 topk_weights:每个 token 的 topk 路输出按路由权重加权
- Reduce:把 topk 路合成一路,
[M, topk, K]→[M, K]
7.3.1 output: torch.Tensor
MoE 层的最终输出 buffer,原地写入。
| 属性 | 说明 |
|---|---|
|
形状 |
|
|
谁分配 |
|
|
谁写入 |
|
DeepEP LL 里直接传给 combine kernel:
self.buffer.low_latency_combine(..., out=output)
无 EP 时由 TopKWeightAndReduceContiguous 写入:
ops.moe_sum(fused_expert_output, output) # 结果进 output
7.3.2 fused_expert_output: torch.Tensor
Experts 算完、尚未 combine / 加权 / reduce 的原始输出。
| 属性 | 说明 |
|---|---|
|
文档形状 |
|
|
含义 |
每个 token 的 topk 路 expert 输出,还没乘权重、没把 topk 维加起来 |
|
来源 |
|
数学上大致是:
fused_expert_output[m, i, :] = 第 m 个 token 的第 i 个选中 expert 的输出向量
DeepEP combine 会拿它和 topk_ids、topk_weights 一起做 combine + 加权 + 聚合,直接写入 output。
7.3.3 topk_weights: torch.Tensor
路由权重,softmax 后 topk 得到的浮点权重。
| 属性 | 说明 |
|---|---|
|
形状 |
|
|
含义 |
每个 token 对每个选中 expert 的权重(和为 1 或经 scaling) |
|
用途 |
finalize 里乘到 |
TopKWeightAndReduceContiguous 里:
fused_expert_output.mul_(topk_weights.view(m, -1, 1)) # 广播到 K 维
ops.moe_sum(fused_expert_output, output) # topk 维求和
DeepEP combine 也会把 topk_weights 传进 low_latency_combine。
7.3.4. topk_ids: torch.Tensor
每个 token 选了哪些 expert(全局或物理 id)。
| 属性 | 说明 |
|---|---|
|
形状 |
|
|
含义 |
与 |
|
用途 |
combine 时知道每路输出该送回哪个 token、对应哪条 expert 槽位 |
DeepEP LL 会先映射 id:
combine_topk_ids = self._map_global_to_physical_ids(topk_ids)
combine kernel 用 topk_ids 做 unpermute / 路由还原,把分散在各 rank 的结果拼回 [M, topk, K] 再聚合。
7.3.5 apply_router_weight_on_input: bool
路由权重是否已经乘在输入 activation 上(一般仅 topk=1 时支持)。
| 值 | 含义 | finalize 行为 |
|---|---|---|
|
|
权重还没乘到输入 |
在 finalize 里对 |
|
|
prepare 里已对 |
finalize 不应再乘,避免乘两次 |
DeepEP LL 的处理:
if apply_router_weight_on_input:
combine_topk_weights = torch.ones_like(topk_weights) # 当作全 1
else:
combine_topk_weights = topk_weights
TopKWeightAndReduceContiguous 同样:
if not apply_router_weight_on_input:
fused_expert_output.mul_(topk_weights.view(m, -1, 1))
7.3.6. weight_and_reduce_impl: TopKWeightAndReduce
对于使用deepep的场景,这个weight_and_reduce_impl函数是用不到的,是在deepep的combine接口内部做的reduce
8 class FusedMoEPrepareAndFinalizeMonolithic(FusedMoEPrepareAndFinalize):
这个类先不看了,目前先用的是前面的modular那个类。
9 class FusedMoEExperts(ABC):
这个也是基类,然后下面的两个类都会继承这个类
- class FusedMoEExpertsModular(FusedMoEExperts):
- class FusedMoEExpertsMonolithic(FusedMoEExperts):
9.1 def is_supported_config(
这里面有个这个函数,这里的意思就是,当选择某个expert kernel的时候,只有满足了这个函数的各种条件之后,才会选择这个expert kernel类。
10 class FusedMoEExpertsModular(FusedMoEExperts):
这个类继承了刚才的FusedMoEExperts类,然后
10.1 def moe_problem_size(
def moe_problem_size(
self,
a1: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_ids: torch.Tensor,
) -> tuple[int, int, int, int, int]:
"""
Extract the MoE problem size from the given tensor arguments:
- a: The hidden states, input to the MoE layer.
- w1: The first set of expert weights.
- w2: The second set of expert weights.
- topk_ids: The topk ids.
Note: extracting the problem shape from the weight and activation
tensors is not obvious. It needs to be done this way specifically
due to subtle issues with particular kernels, e.g. the int4 kernels
divide the trailing dimension by two, so it's not "correct" to
extract N or K from the trailing dimension of w1 or w2. Similarly,
some kernels transpose the weights, so this needs to be kept in mind.
Note: This implementation covers most cases. However, if experts
require a specialized implementation, like MarlinExperts, they are free
to override this function.
"""
if w1.dim() == 6:
assert w2.dim() == 6
E = w1.size(0)
# [E, K/64, N/16, 4, 16, 16] packed layout
N = w1.size(2) * w1.size(4)
K = w1.size(1) * w1.size(3) * w1.size(5)
else:
assert len(w1.shape) == 3 and len(w2.shape) == 3
E, N, _ = w1.shape
K = a1.size(-1)
if a1.dim() == 2:
# Make sure we are using the correct a1 (pre-permute).
assert topk_ids.size(0) == a1.size(0), f"{topk_ids.size(0)} != {a1.size(0)}"
M = a1.size(0)
else:
assert a1.dim() == 3
assert a1.size(0) == E, f"{a1.size(0)} == {E}"
M = a1.size(1) # This is max_num_tokens
assert topk_ids.dim() == 2
topk = topk_ids.size(1)
return E, M, N, K, topk
这个类其实就是计算出来了E M N K topk,
10.2 def workspace_shapes(
@abstractmethod
def workspace_shapes(
self,
M: int,
N: int,
K: int,
topk: int,
global_num_experts: int,
local_num_experts: int,
expert_tokens_meta: ExpertTokensMetadata | None,
activation: MoEActivation,
) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
"""
Compute the shapes for the temporary and final outputs of the two gemms
and activation in the fused expert function. Since the gemms are
independent, the workspace for the first gemm can be shared with the
workspace for the last gemm.
Inputs:
- M: number of tokens.
- N: Row (or column) dimension of expert weights.
- K: hidden dimension
- topk: The number of top-k experts to select.
- global_num_experts: global number of experts.
- local_num_experts: local number of experts due to DP/EP.
- expert_tokens_meta: number of tokens per expert metadata for batched
format.
Returns a tuple of:
- workspace13 shape tuple: must be large enough to hold the
result of either expert gemm.
- workspace2 shape tuple: must be large enough to hold the
result of the activation function.
- output shape tuple: must be exact size of the final gemm output.
- Note: workspace shapes can be 0 if the workspace is not needed.
But in order for activation chunking to work, the first dimension
of each tuple must be the number of tokens when the shape is
not 0.
"""
raise NotImplementedError
这里其实就是根据之前计算的E N K 等一些信息,计算两次gemm计算需要的空间大小,
10.3 def apply(
@abstractmethod
def apply(
self,
output: torch.Tensor,
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
activation: MoEActivation,
global_num_experts: int,
expert_map: torch.Tensor | None,
a1q_scale: torch.Tensor | None,
a2_scale: torch.Tensor | None,
workspace13: torch.Tensor,
workspace2: torch.Tensor,
expert_tokens_meta: ExpertTokensMetadata | None,
apply_router_weight_on_input: bool,
) -> None:
"""
This function computes the intermediate result of a Mixture of Experts
(MoE) layer using two sets of weights, w1 and w2.
Parameters:
- output: (torch.Tensor): The unweighted, unreduced output tensor.
- hidden_states: (torch.Tensor): The (quantized) input tensor to the MoE
layer.
- w1 (torch.Tensor): The first set of expert weights.
- w2 (torch.Tensor): The second set of expert weights.
- topk_weights: A map of row to expert weights. Some implementations
choose to do weight application.
- topk_ids (torch.Tensor): A map of row to expert id.
- activation (str): The activation function to apply after the first
MoE layer.
- global_num_experts (int): The total number of experts in the global
expert space.
- expert_map (Optional[torch.Tensor]): A tensor mapping expert indices
from the global expert space to the local expert space of the expert
parallel shard.
- a1q_scale (Optional[torch.Tensor]): Optional quantized scale to be
used for a1. Result of quantization from prepare/finalize and not
from the FusedMoEQuantConfig.
- workspace13 (torch.Tensor): A scratch tensor used for gemm outputs
must be large enough to hold output of either MoE gemm.
- workspace2 (torch.Tensor): A scratch tensor used for the activation
function.
- expert_tokens_meta (Optional[ExpertTokensMetadata]) - An optional
ExpertTokensMetadata object containing gpu/cpu tensors
as big as the number of local experts with the information about the
number of tokens assigned to each local expert.
- apply_router_weight_on_input: True if router weights are already
applied on the input. This is relevant if the implementation
chooses to do weight application.
"""
raise NotImplementedError
这个函数就是负责做那两次矩阵计算和激活的,也就是gemm1---激活----gemm2。
| 参数 | 类型 | 典型形状 / 格式 | 谁提供 | 作用 |
|---|---|---|---|---|
|
output |
|
HT: |
|
w2 GEMM 输出 buffer;未加权、未跨 rank reduce;结果 in-place 写入 |
|
hidden_states |
|
HT: |
Prepare( |
Expert 输入;可能已量化、已 dispatch 到本 rank |
|
w1 |
|
|
MoE 层权重 |
gate+up 合并权重(w13);第一次 GEMM |
|
w2 |
|
|
MoE 层权重 |
down 权重;第二次 GEMM; |
|
topk_weights |
|
|
Router + Prepare |
每个 token 对各 expert 的 router 权重;部分实现在 |
|
topk_ids |
|
|
Router + Prepare |
每个 token 选中的 全局 expert id;HT 用于 permute/unpermute |
|
activation |
|
枚举值 |
模型配置 |
w13 后激活类型(SiLU 等);影响中间维是否 |
|
global_num_experts |
|
标量 |
上层传入 |
全局 expert 总数;用于负载估计、路由映射等 |
|
expert_map |
|
长度 = 全局 E |
EP 配置 |
global id → local id(无则为 |
|
a1q_scale |
|
随量化方案 |
Prepare 动态量化 |
第一次 GEMM 输入的 scale;与 |
|
a2_scale |
|
随量化方案 |
|
第二次 GEMM 输入的静态 scale;多数路径由激活后动态 quant 替代 |
|
workspace13 |
|
见 |
|
w13 / permute 等 scratch;需能容纳较大的一次 GEMM 输出 |
|
workspace2 |
|
见 |
|
激活阶段 scratch;宽度通常为 |
|
expert_tokens_meta |
|
每 expert 一个计数 |
Prepare(DeepEP LL) |
各本地 expert 实际 token 数;LL masked GEMM 必需 |
|
apply_router_weight_on_input |
|
标量 |
模型 / 配置 |
|
11 class FusedMoEKernelModularImpl:
这个就是具体的实施类,这里面会用到这两个类分别作前后处理和专家计算
- prepare_finalize: FusedMoEPrepareAndFinalizeModular,
- fused_experts: FusedMoEExpertsModular,
11.1 def _allocate_buffers(
def _allocate_buffers(
self,
out_dtype: torch.dtype,
device: torch.device,
M_chunk: int,
M_full: int,
N: int,
K: int,
top_k: int,
global_num_experts: int,
local_num_experts: int,
expert_tokens_meta: ExpertTokensMetadata | None,
activation: MoEActivation,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Allocate temporary and output buffers for the fused experts op.
Inputs:
- out_dtype: output type of workspace and output tensors.
- device: the device of the workspace and output tensors.
See `workspace_shapes` for a description of the remainder of arguments.
Returns a tuple of (workspace13, workspace2, output) tensors.
"""
assert M_full > 0 and M_chunk > 0
workspace_dtype = self.fused_experts.workspace_dtype(out_dtype)
# Get intermediate workspace shapes based off the chunked M size.
workspace13_shape, workspace2_shape, _ = self.fused_experts.workspace_shapes(
M_chunk,
N,
K,
top_k,
global_num_experts,
local_num_experts,
expert_tokens_meta,
activation,
)
# Get final output shape based on the full M size.
_, _, fused_out_shape = self.fused_experts.workspace_shapes(
M_full,
N,
K,
top_k,
global_num_experts,
local_num_experts,
expert_tokens_meta,
activation,
)
# We can reuse the memory between cache1 and cache3 because by the
# time we need cache3, we're done with cache1.
# Reuse workspace13 for the output since there is only one chunk.
max_shape_size = max(prod(workspace13_shape), prod(fused_out_shape))
common_workspace, workspace2 = current_workspace_manager().get_simultaneous(
((max_shape_size,), workspace_dtype),
(workspace2_shape, workspace_dtype),
)
workspace13 = _resize_cache(common_workspace, workspace13_shape)
fused_out = _resize_cache(common_workspace, fused_out_shape)
return workspace13, workspace2, fused_out
这个函数在 _fused_experts() 里、调用 fused_experts.apply() 之前执行,负责为 MoE 两次 GEMM + 激活阶段申请三块 buffer,并尽量复用同一块物理内存。
| 参数 | 含义 |
|---|---|
|
|
输出 / workspace 的数据类型,一般等于 |
|
|
张量所在设备,来自 |
|
|
中间 workspace 按多大 token 数算形状;当前实现 = |
|
|
最终输出 |
|
|
专家中间维(w1 的 N,gate+up 合并后的宽度) |
|
|
hidden dim(输入/输出特征维) |
|
|
每个 token 选几个专家 |
|
|
全局专家总数 |
|
|
本 rank 本地专家数(通常 |
|
|
BatchedExperts 布局下每个专家分到的 token 数;Standard 路径常为 |
|
|
激活类型(SiLU 等),影响中间 buffer 宽度(是否 |
返回值(三个张量):
| 返回值 | 用途 |
|---|---|
|
|
第 1 次 GEMM 和第 3 次 GEMM 的共享 scratch(名字里的 1 和 3) |
|
|
激活阶段的 scratch |
|
|
专家计算的最终输出 buffer,作为 |
另外,
# We can reuse the memory between cache1 and cache3 because by the
# time we need cache3, we're done with cache1.
# Reuse workspace13 for the output since there is only one chunk.
max_shape_size = max(prod(workspace13_shape), prod(fused_out_shape))
common_workspace, workspace2 = current_workspace_manager().get_simultaneous(
((max_shape_size,), workspace_dtype),
(workspace2_shape, workspace_dtype),
)
workspace13 = _resize_cache(common_workspace, workspace13_shape)
fused_out = _resize_cache(common_workspace, fused_out_shape)
这里的意思,从workspace13和fused_out_shape中选出来一个最大的,按照这个最大的去申请内存,然后后面将复用做workspace13和fused_out
11.2 def _maybe_apply_shared_experts(
def _maybe_apply_shared_experts(
self,
shared_experts_input: torch.Tensor | None,
x_and_scale_quanted: tuple[torch.Tensor, torch.Tensor] | None = None,
):
if self.shared_experts is not None:
assert shared_experts_input is not None
self.shared_experts.apply(
shared_experts_input,
SharedExpertsOrder.MK_INTERNAL_OVERLAPPED,
x_and_scale_quanted=x_and_scale_quanted,
)
这里是如果有共享专家的话,那么这里共享专家会和combin进行overlap。
11.3 def _prepare(
def _prepare(
self,
hidden_states: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
global_num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
prequanted_a1_scale: torch.Tensor | None = None,
prequanted_a1: bool = False,
) -> tuple[
torch.Tensor,
torch.Tensor | None,
ExpertTokensMetadata | None,
torch.Tensor,
torch.Tensor,
]:
"""
The _prepare method is a wrapper around self.prepare_finalize.prepare
that handles DBO and async.
"""
use_all2all_kernels = (
self.moe_parallel_config is not None
and self.moe_parallel_config.use_all2all_kernels
)
if (
prequanted_a1
and not use_all2all_kernels
):
return hidden_states, prequanted_a1_scale, None, topk_ids, topk_weights
if (
self.fused_experts.activation_format()
== FusedMoEActivationFormat.BatchedExperts
):
num_tokens = hidden_states.shape[0] if hidden_states is not None else None
num_dispatchers = getattr(self.fused_experts, "num_dispatchers", None)
topk = topk_ids.shape[1] if topk_ids is not None else None
if (
num_tokens is not None
and num_dispatchers is not None
and topk is not None
and global_num_experts is not None
and global_num_experts > 0
):
expected_m = (
num_tokens * num_dispatchers * topk + global_num_experts
) // global_num_experts
self.fused_experts.set_expected_m(expected_m)
else:
logger.warning_once(
"Skip set_expected_m because required values are invalid: "
"num_tokens=%s, num_dispatchers=%s, topk=%s, "
"global_num_experts=%s",
num_tokens,
num_dispatchers,
topk,
global_num_experts,
)
if not self.prepare_finalize.supports_async():
# We shouldn't be running an a2a kernel that doesn't
# support async prepare/finalize
# TODO(lucas): enable in follow-up
assert not dbo_enabled()
(
a1q,
a1q_scale,
expert_tokens_meta,
_expert_topk_ids,
_expert_topk_weights,
) = self.prepare_finalize.prepare(
hidden_states,
topk_weights,
topk_ids,
global_num_experts,
expert_map,
apply_router_weight_on_input,
self.fused_experts.quant_config,
defer_input_quant=self.fused_experts.expects_unquantized_inputs,
)
else:
# Overlap shared expert compute with all2all dispatch.
dbo_maybe_run_recv_hook()
prepare_ret = self.prepare_finalize.prepare_async(
hidden_states,
topk_weights,
topk_ids,
global_num_experts,
expert_map,
apply_router_weight_on_input,
self.fused_experts.quant_config,
defer_input_quant=self.fused_experts.expects_unquantized_inputs,
)
# TODO(lucas): refactor this in the alternative schedules followup
# currently unpack if we have hook + receiver pair or just
# receiver (see finalize_async docstring)
hook, receiver = (
prepare_ret if isinstance(prepare_ret, tuple) else (None, prepare_ret)
)
if hook is not None:
if dbo_enabled():
# If DBO is being used, register the hook with the ubatch
# context and call it in dbo_maybe_run_recv_hook instead of
# passing it to the receiver.
dbo_register_recv_hook(hook)
dbo_yield()
else:
hook()
(
a1q,
a1q_scale,
expert_tokens_meta,
_expert_topk_ids,
_expert_topk_weights,
) = receiver()
# Maybe prepare gathered topk_ids and topk_weights from other EP ranks.
topk_ids = topk_ids if _expert_topk_ids is None else _expert_topk_ids
topk_weights = (
topk_weights if _expert_topk_weights is None else _expert_topk_weights
)
return a1q, a1q_scale, expert_tokens_meta, topk_ids, topk_weights
_prepare 是 FusedMoEKernelModularImpl 里的内部包装函数,不直接对外暴露,我看他其实就是封装了一层,本质上其实就还是调用的self.prepare_finalize.prepare(,只不过增加了一下其他的处理工作,比如DBO相关的代码,从注释这里也能看出来是这样的。
The _prepare method is a wrapper around self.prepare_finalize.prepare
that handles DBO and async.
这里面
expected_m = (
num_tokens * num_dispatchers * topk + global_num_experts
) // global_num_experts
就是当前rank上有num_tokens,那么乘以num_dispatchers就是说估算下这次所有rank一共有多少个token,然后乘以topk,也就是说我们这个每个专家可能要负责多个token,这三个值乘积其实就是计算出来所有专家一共要做多少个token的计算,然后除以global_num_experts就是估算出来每个专家大约要处理多少个token,
expected_m 用来给 DeepGEMM 等 masked grouped GEMM 做 kernel 选型/调度提示,不决定实际算哪些 token。
11.4 _fused_experts(
def _fused_experts(
self,
in_dtype: torch.dtype,
a1q: torch.Tensor,
a1q_scale: torch.Tensor | None,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
activation: MoEActivation,
global_num_experts: int,
local_num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
expert_tokens_meta: ExpertTokensMetadata | None,
output_alias: torch.Tensor | None = None,
) -> torch.Tensor:
_, M_full, N, K, top_k = self.fused_experts.moe_problem_size(
a1q, w1, w2, topk_ids
)
if self.N > 0:
N = self.N
K = self.K
# This happens when none of the tokens from the all2all reach this
# EP rank. Also, note that this is only relevant for CUDAGraph
# incompatible all2all kernels like the DeepEP high-throughput
# kernels. CUDAGraph compatible all2all kernels like the DeepEP
# low-latency kernels are always batched and can never run into
# the tensor.numel() == 0 case.
if M_full == 0:
return torch.empty_like(a1q, dtype=in_dtype)
workspace13, workspace2, fused_out = self._allocate_buffers(
in_dtype,
a1q.device,
M_full,
M_full,
N,
K,
top_k,
global_num_experts,
local_num_experts,
expert_tokens_meta,
activation,
)
# If caller's output buffer already matches fused_out shape/dtype, alias
# to skip the redundant copy in TopKWeightAndReduceNoOP.apply downstream.
# This eliminates ~94% of __amd_rocclr_copyBuffer events (Copy 2 of the
# double-copy MoE write-back path).
if current_platform.is_rocm():
from vllm._aiter_ops import rocm_aiter_ops
if (
rocm_aiter_ops.is_fused_moe_enabled()
and output_alias is not None
and output_alias.shape == fused_out.shape
and output_alias.dtype == fused_out.dtype
and output_alias.device == fused_out.device
and output_alias.is_contiguous()
):
fused_out = output_alias
self.fused_experts.apply(
output=fused_out,
hidden_states=a1q,
w1=w1,
w2=w2,
topk_weights=topk_weights,
topk_ids=topk_ids,
activation=activation,
global_num_experts=global_num_experts,
expert_map=expert_map,
a1q_scale=a1q_scale,
a2_scale=self.fused_experts.a2_scale,
workspace13=workspace13,
workspace2=workspace2,
expert_tokens_meta=expert_tokens_meta,
apply_router_weight_on_input=apply_router_weight_on_input,
)
return fused_out
这个函数其实就是做专家计算的,也是算对fused_experts.apply(的封装调用,
# If caller's output buffer already matches fused_out shape/dtype, alias
# to skip the redundant copy in TopKWeightAndReduceNoOP.apply downstream.
# This eliminates ~94% of __amd_rocclr_copyBuffer events (Copy 2 of the
# double-copy MoE write-back path).
if current_platform.is_rocm():
from vllm._aiter_ops import rocm_aiter_ops
if (
rocm_aiter_ops.is_fused_moe_enabled()
and output_alias is not None
and output_alias.shape == fused_out.shape
and output_alias.dtype == fused_out.dtype
and output_alias.device == fused_out.device
and output_alias.is_contiguous()
):
fused_out = output_alias

11.5 def _finalize(
def _finalize(
self,
output: torch.Tensor,
fused_out: torch.Tensor,
hidden_states: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
apply_router_weight_on_input: bool,
shared_experts_input: torch.Tensor | None,
x_and_scale_quanted: tuple[torch.Tensor, torch.Tensor] | None = None,
) -> torch.Tensor:
"""
The _finalize method is a wrapper around self.prepare_finalize.finalize
that handles DBO, async and shared expert overlap.
Args:
shared_experts_input: Optional separate input for shared experts.
When latent MoE is used, hidden_states is the latent-projected
tensor (smaller dimension) used by routed experts, while
shared_experts_input is the original hidden_states (full
dimension) needed by the shared expert MLP.
"""
if not self.prepare_finalize.supports_async():
assert not dbo_enabled()
self.prepare_finalize.finalize(
output,
fused_out,
topk_weights,
topk_ids,
apply_router_weight_on_input,
self.fused_experts.finalize_weight_and_reduce_impl(),
)
else:
finalize_ret = self.prepare_finalize.finalize_async(
output,
fused_out,
topk_weights,
topk_ids,
apply_router_weight_on_input,
self.fused_experts.finalize_weight_and_reduce_impl(),
)
self._maybe_apply_shared_experts(shared_experts_input, x_and_scale_quanted)
# TODO(lucas): refactor this in the alternative schedules followup
# currently unpack if we have hook + receiver pair or just
# receiver (see finalize_async docstring)
hook, receiver = (
finalize_ret
if isinstance(finalize_ret, tuple)
else (None, finalize_ret)
)
if hook is not None:
if dbo_enabled():
# If DBO is being used, register the hook with the ubatch
# context and call it in dbo_maybe_run_recv_hook instead of
# passing it to the receiver.
dbo_register_recv_hook(hook)
dbo_yield()
else:
hook()
receiver()
return output
从这段注释就能看出来这个函数的作用
The _finalize method is a wrapper around self.prepare_finalize.finalize
that handles DBO, async and shared expert overlap.
这个函数也是个封装层,主要就是调用了finalize函数,还有就是处理了DBO以及共享专家的overlap。
这里除了DBO,其实里面还加上了共享专家计算的overlao,
- 纯 DBO(无共享专家):发起 combine(异步,后台跑)---立刻 register hook + dbo_yield() → 让给 batch B---receiver() 等 combine 完成
- DBO + 共享专家 overlap:发起 combine(异步,后台跑)---算共享专家(前台,和 combine 并行) 多出来的这一步---register hook + dbo_yield() ,才让给 batch B---receiver() 等 combine 完成
本来 combine 一发起就让给 B;现在有共享专家时,先不让,在 combine 还在后台跑的时候做共享专家,共享专家算完再让出给 B。
11.6 def apply(
def apply(
self,
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_ids: torch.Tensor,
topk_weights: torch.Tensor,
activation: MoEActivation = MoEActivation.SILU,
global_num_experts: int = -1,
expert_map: torch.Tensor | None = None,
apply_router_weight_on_input: bool = False,
shared_experts_input: torch.Tensor | None = None,
quanted_hidden_states: torch.Tensor | None = None,
scale: torch.Tensor | None = None,
) -> torch.Tensor:
"""
This function computes a Mixture of Experts (MoE) layer using two sets
of weights, w1 and w2, and top-k gating mechanism.
Parameters:
- hidden_states: (torch.Tensor): The input tensor to the MoE layer.
- w1 (torch.Tensor): The first set of expert weights.
- w2 (torch.Tensor): The second set of expert weights.
- topk_weights (torch.Tensor): The topk weights applied at the end of the layer.
- topk_ids (torch.Tensor): A map of row to expert id.
- activation (MoEActivation): The activation function to apply after the first
MoE layer.
- global_num_experts (int): The total number of experts in the global
expert space.
- expert_map (Optional[torch.Tensor]): A tensor mapping expert indices
from the global expert space to the local expert space of the expert
parallel shard.
- apply_router_weight_on_input (bool): When true, the topk weights are
applied directly on the inputs. This is only applicable when topk is
1.
- shared_experts_input (Optional[torch.Tensor]): Optional separate
input for shared experts. For latent MoE, this is the original
hidden_states before latent projection.
Returns:
- torch.Tensor: The output tensor after applying the MoE layer.
"""
if self.inplace:
assert self.shared_experts is None
assert not disable_inplace()
output = hidden_states
else:
output = torch.empty_like(hidden_states)
local_num_experts = w1.shape[0]
if global_num_experts == -1:
global_num_experts = local_num_experts
a1q, a1q_scale, expert_tokens_meta, topk_ids, topk_weights = self._prepare(
quanted_hidden_states
if quanted_hidden_states is not None
else hidden_states,
topk_weights,
topk_ids,
global_num_experts,
expert_map,
apply_router_weight_on_input,
prequanted_a1_scale=scale,
prequanted_a1=quanted_hidden_states is not None,
)
fused_out = self._fused_experts(
in_dtype=hidden_states.dtype,
a1q=a1q,
a1q_scale=a1q_scale,
w1=w1,
w2=w2,
topk_weights=topk_weights,
topk_ids=topk_ids,
activation=activation,
global_num_experts=global_num_experts,
local_num_experts=local_num_experts,
expert_map=expert_map,
apply_router_weight_on_input=apply_router_weight_on_input,
expert_tokens_meta=expert_tokens_meta,
output_alias=output,
)
return self._finalize(
output,
fused_out,
hidden_states,
topk_weights,
topk_ids,
apply_router_weight_on_input,
shared_experts_input=shared_experts_input,
x_and_scale_quanted=(
(quanted_hidden_states, scale)
if quanted_hidden_states is not None and scale is not None
else None
),
)
这个没啥,这个其实就是调用了前面的那三个函数,就是prepare, fused_experts, finalize,
12 class FusedMoEKernel:
@final
class FusedMoEKernel:
def __init__(
self,
prepare_finalize: FusedMoEPrepareAndFinalize,
fused_experts: FusedMoEExperts,
shared_experts: SharedExperts | None = None,
inplace: bool = False,
N: int = -1,
K: int = -1,
):
super().__init__()
# Initialize the implementation (monolithic or modular).
self.impl: FusedMoEKernelModularImpl | FusedMoEKernelMonolithicImpl
if isinstance(
prepare_finalize, FusedMoEPrepareAndFinalizeModular
) and isinstance(fused_experts, FusedMoEExpertsModular):
self.impl = FusedMoEKernelModularImpl(
prepare_finalize,
fused_experts,
shared_experts,
inplace,
N,
K,
)
elif isinstance(
prepare_finalize, FusedMoEPrepareAndFinalizeMonolithic
) and isinstance(fused_experts, FusedMoEExpertsMonolithic):
assert not inplace
self.impl = FusedMoEKernelMonolithicImpl(
prepare_finalize,
fused_experts,
)
else:
raise ValueError(
"prepare_finalize and fused_experts must both be either monolithic "
f"or non-monolithic but got {prepare_finalize.__class__.__name__} "
f"and {fused_experts.__class__.__name__}"
)
self._post_init_setup()
@property
def owns_shared_experts(self) -> bool:
if isinstance(self.impl, FusedMoEKernelModularImpl):
return self.impl.shared_experts is not None
else:
return False
@property
def is_monolithic(self) -> bool:
return isinstance(self.impl, FusedMoEKernelMonolithicImpl)
@property
def prepare_finalize(self) -> FusedMoEPrepareAndFinalize:
return self.impl.prepare_finalize
@property
def fused_experts(self) -> FusedMoEExperts:
return self.impl.fused_experts
def supports_lora(self) -> bool:
return self.fused_experts.supports_lora()
def _post_init_setup(self):
"""
Resolve any leftover setup dependencies between self.prepare_finalize
and self.fused_experts here.
"""
self.prepare_finalize.post_init_setup(self.impl.fused_experts)
assert (
self.prepare_finalize.activation_format
== self.fused_experts.activation_format()
)
def supports_expert_map(self) -> bool:
"""
A flag indicating whether or not this class supports expert maps.
"""
return self.fused_experts.supports_expert_map()
def output_is_reduced(self) -> bool:
"""
Indicates whether or not the output of fused MoE kernel
is reduced across all ranks.
"""
return self.prepare_finalize.output_is_reduced()
def apply_monolithic(
self,
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
router_logits: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
activation: MoEActivation,
global_num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
# grouped topk + fused topk bias parameters
num_expert_group: int | None = None,
e_score_correction_bias: torch.Tensor | None = None,
routed_scaling_factor: float | None = None,
topk_group: int | None = None,
) -> torch.Tensor:
assert isinstance(self.impl, FusedMoEKernelMonolithicImpl)
return self.impl.apply(
hidden_states=hidden_states,
w1=w1,
w2=w2,
router_logits=router_logits,
activation=activation,
global_num_experts=global_num_experts,
expert_map=expert_map,
apply_router_weight_on_input=apply_router_weight_on_input,
num_expert_group=num_expert_group,
e_score_correction_bias=e_score_correction_bias,
routed_scaling_factor=routed_scaling_factor,
topk_group=topk_group,
)
def apply(
self,
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
activation: MoEActivation,
global_num_experts: int,
expert_map: torch.Tensor | None,
apply_router_weight_on_input: bool,
shared_experts_input: torch.Tensor | None = None,
quanted_hidden_states: torch.Tensor | None = None,
scale: torch.Tensor | None = None,
) -> torch.Tensor:
assert isinstance(self.impl, FusedMoEKernelModularImpl)
return self.impl.apply(
hidden_states=hidden_states,
w1=w1,
w2=w2,
topk_weights=topk_weights,
topk_ids=topk_ids,
activation=activation,
global_num_experts=global_num_experts,
expert_map=expert_map,
apply_router_weight_on_input=apply_router_weight_on_input,
shared_experts_input=shared_experts_input,
quanted_hidden_states=quanted_hidden_states,
scale=scale,
)
这里其实就是根据init时候传入的不同实例创建不同的implLei ,
prepare_finalize + fused_experts
↓
都是 Modular → FusedMoEKernelModularImpl
都是 Monolithic → FusedMoEKernelMonolithicImpl
类型不匹配 → 报错
参考文献
更多推荐





所有评论(0)