专栏名称: GiantPandaLLM
专注于机器学习、深度学习、计算机视觉、图像处理等多个方向技术分享。团队由一群热爱技术且热衷于分享的小伙伴组成。我们坚持原创,每天一到两篇原创技术分享。希望在传播知识、分享知识的同时能够启发你,大家一起共同进步(・ω<)☆
目录
相关文章推荐
GiantPandaLLM  ·  Meta Shuffling的MoE ... ·  昨天  
GiantPandaLLM  ·  [vLLM实践][算子] ... ·  4 天前  
GiantPandaLLM  ·  MetaShuffling:Meta的Fus ... ·  2 天前  
51好读  ›  专栏  ›  GiantPandaLLM

MetaShuffling:Meta的Fused MoE kernel工程方案,更激进的Kernel...

GiantPandaLLM  · 公众号  · 3D  · 2025-06-05 21:54

正文

请到「今天看啥」查看全文


工作负载分片和额外的Kernels

与单GPU推理用例相比,没有引入额外的自定义kernel。对于GEMM、GroupedGEMM和非线性kernel,激活和权重都沿不同维度共享到1/TP,计算/内存开销也共享到1/TP。

如果仅应用张量并行,最后一步应该是AllReduce。或者,如果张量并行与序列并行一起应用,则使用ReduceScatter。

多卡推理的专家并行化

为了启用专家并行化(EP),我们将数据并行维度从路由专家中交换出来,作为路由专家内部的专家并行维度。注意,为了获得更好的GEMM效率,专家并行可以进一步与张量并行交换,但这会增加路由不平衡的风险,我们不会在本博客中介绍这种设计。

如果在token-choice路由中启用了专家并行,由于路由到不同专家组的token数量是动态的,我们必须在使用密集张量或使用静态形状之间做出选择。

  • 当优先使用eager模式时,我们使用密集张量和动态形状,以避免运行未Padding的AlltoAll造成的网络流量和内存空间浪费。
  • 当优先使用图模式时,我们使用稀疏张量和静态形状,以避免通过运行CUDAGraph导致的CPU启动开销和设备到主机同步产生的GPU气泡。

注意,使用Padding激活的网络流量浪费也可以通过使用自定义AlltoAll实现来避免,但我们不会在本博客中介绍任何关于自定义通信或通信和计算融合kernel的主题。

上图是使用张量并行和专家并行的多主机推理的整体运行时设计。与使用张量并行的单主机推理相比:

  • 实心红色箭头表示节点内通信。
  • 实心紫色箭头表示节点间通信。

Kernel接口和数据流

对于增加的基于专家并行的通信,我们使用3次All2All通信来交换形状和token:

  • 第1次A2A:交换设备上关于路由到每个专家的token数量的元数据张量,即 routed_token_counts_per_expert: [E] ,这是由IndexShuffling kernel生成的输出。
  • 第2次A2A:将token从基于数据并行转换为基于专家并行,根据路由分发到不同的EP ranks。
  • 第3次A2A:将token从基于专家并行转换为基于数据并行,根据路由从不同的EP ranks组合。

此外,我们增加了2个额外的shuffling kernel和1个特殊的scatter kernel:

  • CombineShuffling(密集或Padding) : 将接收到的token从按rank排序重新排列为按expert排序。后面的T*表示从所有对等节点接收的总token数,可以根据routed_token_counts_per_rank_per_expert张量的形状信息进一步解释为不规则维度。
    • 输入:received_tokens: [T*, D](首先按dp ranks排序,然后按专家索引排序); routed_token_counts_per_rank_per_expert: [EP, E // EP];
    • 输出:reshuffled_tokens: [T*, D](首先按专家索引排序,然后按dp ranks排序); routed_token_counts_per_expert: [E // EP];
  • SplitShuffling(密集或Padding) :CombineShuffling的反向过程。将待发送token从专家优先顺序重新排序为rank优先顺序。
    • 输入:reshuffuled_tokens: [T*, D](首先按专家索引排序,然后按dp ranks排序); routed_token_counts_per_rank_per_expert: [EP, E // EP];
    • 输出:to_send_tokens: [T*, D](首先按dp ranks排序,然后按专家索引排序);
  • ScatterAdd(Padding) :从Padding张量中scatter adds有效token。
    • 输入:共享输出token: [T, D]; 接收到的Padding路由输出token: [EP, K*T, D]; 路由token索引: [K * T]; 每个专家的路由token数量: [E];
    • 输出:组合输出token: [T, D]

我们将在"图模式下使用静态形状的Padding通信"部分详细介绍上述kernel。

Eager模式下使用动态形状的非Padding通信

运行时行为的高层示意图。不同组件的实际运行时间可能会根据软件和硬件的不同而变化。

最小化动态形状的使用

由于路由是每个MoE层动态的,所需的最小设备/主机同步次数为每层一次。为了实现这一点,我们延迟了 send_sizes 的D2H复制,并将其与 recv_sizes 连接起来,通过单个D2H复制一起传输。这减少了设备/主机同步次数为每层一次。

最小化动态形状的负面影响

为了进一步隐藏设备/主机同步开销,我们进一步将共享专家分为两部分。

  • 我们首先分发第一部分,在路由之后,但在分发A2A之前。然后,当设备/主机同步发生时,设备仍然保持忙碌运行共享专家。
  • 我们第二部分在MoE之后,但在组合A2A之前分发。这将进一步帮助重叠第二个A2A。

图模式下使用静态形状的Padding通信

最小化Padding的使用

在无丢弃token选择设计中,路由到任何单个专家的最大可能token数量是T。然而,如果我们通过专家并行分片将多个专家组合在一起并放置在单个GPU上,对于TopK路由:

  • 路由到1个专家的最大token数量是T。
  • 路由到2个专家的最大token数量是2 * T。
  • ...
  • 路由到K个专家的最大token数量是K * T。
  • 路由到K+1个专家的最大token数量仍然是K * T。
  • ...

因此,路由到N个专家组的最大token数量将被限制在min(N, K) * T个token。

对于Top1路由,路由到任意大小的专家组的token数量将始终被限制在T个token,由于有EP个专家组,分配和保存动态token所需的最小内存是EP * T个token。

为了实现最小所需的Padding,我们直接使用AllGather来从不同的EP ranks收集所有活跃token,然后通过自定义kernel在本地拆分和重新排列路由token。激活大小被压缩到1 / (E // EP),这对应于内存和网络流量的减少。

上图展示了Padding设计。每个方框代表一个token,蓝色/绿色表示具有专家分配的有效token,灰色表示Paddingtoken。RiTj表示专家并行组中第i个rank的第j个token。

最小化Padding的负面影响

尽管Padding被减少到最小允许,我们还通过承担设备形状信息 routed_token_counts_per_expert routed_token_counts_per_rank_per_expert 确保Padding只导致内存空间(分配)和网络流量(通信),而不是导致冗余计算(GroupedGEMM / NonLinear),冗余内存带宽(CombineShuffling / SplitShuffling / ScatterAdd)。

激活的概念解释

  • 最重要的是,当所有EP ranks上的活跃token总数较小时,这样做很重要,以避免在GroupedGEMM中激活冗余专家并导致额外内存流量。
  • 当所有EP ranks上的活跃token总数较大时,这样做也很重要,以避免将GroupedGEMM从memory bound转换为compute bound。

CombineShuffling : 当前EP rank分配的token被重新排列为从专家优先顺序到rank优先顺序,在AllGather之后。未分配的token不会被复制,并且张量末尾剩余的分配内存空间保持不变。

SplitShuffling : 当前EP rank分配的token被重新排列为从rank优先顺序到专家优先顺序,在AlltoAll之前。未分配的token不会被复制,并且重新排列的张量具有交错存储的Padding。

ScatterAdd (Padded) : 每个EP rank最终接收来自所有其他rank计算的激活,它将理解哪些是有效token,哪些是Padding token,然后只读取有效token进行scatter_add。







请到「今天看啥」查看全文