06.10 Counting Sort / Bucketization 三方对比分析:博客 ↔ DeepEP ↔ DeepGEMM Mega MoE
分析日期: 2026-07-30
目标: 验证博客中 "Count → Prefix Sum → Scatter" 概念在 DeepEP 和 DeepGEMM Mega MoE 两个代码库中的具体实现,并进行三方对比
1. 核心结论
是的,Count → Prefix Sum → Scatter 在两个代码库中都存在,但实现策略有显著差异:
| 维度 | DeepEP | DeepGEMM Mega MoE |
|---|---|---|
| Count 位置 | Notify Warp(专用) | Dispatch Warp(与 scatter 共用) |
| Prefix Sum 策略 | 两级:SM 内 smem atomic → 全局 reduction → SM 0 warp-level scan | 两级:SM 内 smem atomic → SM 间 global atomic(64-bit 编码) |
| Scatter 产物 | dst_buffer_slot_idx(目标 rank 的 slot 位置) |
src_token_topk_idx(源 token 索引表,供 pull 使用) |
| 数据移动 | TMA store 直接写入目标 rank 对称内存 | NVLink pull:目标 rank 主动拉取 |
| 连续 layout 产生 | dispatch_copy_epilogue 中的 expand 模式 |
Pull 阶段写入 pool buffer |
关键演化:
- DeepEP 的 Counting Sort 是 通信准备(为 NVLink/RDMA 准备目标地址)
- Mega MoE 的 Counting Sort 是 计算准备(为 Tensor Core 准备 scatter 索引表)
- Mega MoE 的 Counting Sort 产物(scatter 索引表)同时服务于通信和计算
2. 博客原文引用
7.1 Does Sort Exist?
Yes, but not traditional sorting. Core process: Count → Prefix Sum → Scatter
Essentially Counting Sort / Bucketization. Goal: produce contiguous layout, not sort by size.
博客的核心洞察:
1. 不是传统排序,而是 Counting Sort / Bucketization
2. 目标:产生连续 layout,而非按大小排序
3. 核心流程:Count(统计每个 expert 的 token 数)→ Prefix Sum(计算每个 expert 的起始偏移)→ Scatter(将 token 写入对应位置)
3. DeepEP 实现分析
3.1 整体架构
DeepEP 的 Counting Sort 分布在 两个 kernel 中:
┌─────────────────────────────────────────────────────────────────────────────┐
│ dispatch_impl (主 Dispatch Kernel) │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Notify Warp (warp_idx < kNumNotifyWarps) │ │
│ │ │ │
│ │ Step 1: COUNT Step 2: PREFIX SUM Step 3: 输出 │ │
│ │ ┌──────────────┐ ┌───────────────────┐ ┌──────────────┐ │ │
│ │ │ atomicAdd to │ ──▶ │ SM 0 warp-level │──▶ │ psum_num_ │ │ │
│ │ │ smem expert_ │ │ inclusive/exclusive│ │ recv_tokens_ │ │ │
│ │ │ count[expert]│ │ scan │ │ per_expert │ │ │
│ │ └──────────────┘ └───────────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Dispatch Warp (warp_idx >= kNumNotifyWarps) │ │
│ │ │ │
│ │ Step 4: SCATTER │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ atomicAdd(scaleup_atomic_sender_counter + dst_rank_idx, 1) │ │ │
│ │ │ → 获得 dst_slot_idx │ │ │
│ │ │ → TMA store 到目标 rank 的对称内存 │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ dispatch_copy_epilogue_impl (Copy Epilogue Kernel) │
│ │
│ Step 5: EXPAND MODE SCATTER (可选) │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ atomicAdd(psum_num_recv_tokens_per_expert + dst_expert_idx, 1) │ │
│ │ → 获得 expert 内的 slot 位置 │ │
│ │ → TMA store 到 recv_x(Expert-major layout) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 Step 1: COUNT — Notify Warp 本地计数
文件: deep_ep/include/deep_ep/impls/dispatch.cuh,line 92-108
// Atomic add on shared memory
EP_STATIC_ASSERT(kNumTopk <= 32, "Insufficient lanes");
const auto global_warp_idx = warp_idx * kNumSMs + sm_idx;
for (int i = global_warp_idx; i < num_tokens; i += kNumNotifyWarps * kNumSMs) {
// Expert choice can not be redundant
const auto dst_expert_idx = lane_idx < kNumTopk ?
static_cast<int>(__ldg(topk_idx + i * kNumTopk + lane_idx)) : -1;
if (dst_expert_idx >= 0)
atomicAdd_block(expert_count + dst_expert_idx, 1);
// Rank choice should do deduplication here
const auto dst_rank_idx = dst_expert_idx >= 0 ? dst_expert_idx / kNumExpertsPerRank : -1;
if (ptx::deduplicate(dst_rank_idx, lane_idx) and dst_rank_idx >= 0)
atomicAdd_block(rank_count + dst_rank_idx, 1);
}
ptx::named_barrier<kNumNotifyThreads>(kNotifyBarrierIndex);
机制:
- expert_count 和 rank_count 是 smem 数组,大小为 kNumRanks + kNumExperts
- 每个 SM 的 notify warp 遍历本地 token 的 topk_idx
- 对每个 (token, expert) pair,执行 atomicAdd_block(expert_count + dst_expert_idx, 1)
- Rank 维度需要 deduplication(一个 token 的多个 expert 可能落在同一 rank)
- 结果:expert_count[i] = 本 SM 发给 expert i 的 token 数量
3.3 Step 2: PREFIX SUM — 跨 SM 聚合 + Warp-level Scan
文件: deep_ep/include/deep_ep/impls/dispatch.cuh,line 110-257
Level 1: 全局 Reduction(line 110-115)
// Do full-grid reduction
#pragma unroll
for (int i = thread_idx; i < kNumRanks + kNumExperts; i += kNumNotifyThreads) {
const int64_t counter = (1ll << 32ll) | rank_expert_count[i];
ptx::red_add(workspace_layout.get_notify_reduction_workspace_ptr() + i, counter);
}
机制:
- 每个 SM 将本地计数通过 red_add 原子加到全局 workspace
- 64-bit 编码:高 32-bit 是 SM 完成计数,低 32-bit 是 token 数
Level 2: SM 0 Warp-level Scan(line 232-257)
// Do prefix sum by the warps
const auto do_psum = [=](const int* count, int* out, const int n, const int is_exclusive) {
int psum = 0;
#pragma unroll
for (int i = 0; i < math::ceil_div(n + is_exclusive, 32); ++ i) {
const auto idx = i * 32 + lane_idx;
const auto mem_idx = idx - is_exclusive;
const auto value = (0 <= mem_idx and mem_idx < n) ? count[mem_idx] : 0;
const auto sum = psum + ptx::warp_inclusive_sum(value, lane_idx);
// Store into global memory
if (idx < n + is_exclusive)
out[idx] = sum;
// Update `psum` by using the last lane's value
psum = ptx::exchange(sum, 31);
}
};
if (warp_idx == 0) {
// Inclusive prefix sum
do_psum(rank_count, psum_num_recv_tokens_per_scaleup_rank, kNumRanks, 0);
} else if (warp_idx == 1) {
// Exclusive prefix sum for later expanding
do_psum(expert_count, psum_num_recv_tokens_per_expert, kNumExpertsPerRank, 1);
}
机制:
- 使用 warp-level inclusive scan(ptx::warp_inclusive_sum)
- Warp 0 做 rank 维度的 inclusive prefix sum
- Warp 1 做 expert 维度的 exclusive prefix sum(用于 expand 模式)
- 输出:psum_num_recv_tokens_per_scaleup_rank 和 psum_num_recv_tokens_per_expert
3.4 Step 3: SCATTER — Dispatch Warp 分配目标 Slot
文件: deep_ep/include/deep_ep/impls/dispatch.cuh,line 336-351
// Deduplicate ranks and assign slots
int stored_dst_slot_idx = -1;
if constexpr (kReuseSlotIndices) {
if (lane_idx < kNumTopk)
stored_dst_slot_idx = __ldg(dst_buffer_slot_idx + token_idx * kNumTopk + lane_idx);
stored_dst_slot_idx = stored_dst_slot_idx >= 0 ?
(stored_dst_slot_idx - rank_idx * kNumMaxTokensPerRank) : -1;
} else {
if (ptx::deduplicate(stored_dst_rank_idx, lane_idx) and stored_dst_rank_idx >= 0)
stored_dst_slot_idx = atomicAdd(workspace_layout.get_scaleup_atomic_sender_counter() + stored_dst_rank_idx, 1);
if (lane_idx < kNumTopk) {
const auto value = stored_dst_slot_idx >= 0 ?
rank_idx * kNumMaxTokensPerRank + stored_dst_slot_idx : -1;
dst_buffer_slot_idx[token_idx * kNumTopk + lane_idx] = value;
}
}
机制:
- 对每个 token 的 top-k 选择,去重后通过 atomicAdd 获取目标 rank 内的 slot 索引
- scaleup_atomic_sender_counter 是全局原子计数器,每个 rank 一个
- 结果写入 dst_buffer_slot_idx,用于后续 TMA store 寻址
3.5 Step 4: 数据移动 — TMA Store 到目标 Rank
文件: deep_ep/include/deep_ep/impls/dispatch.cuh,line 371-393
// Issue TMA NVLink stores
const auto dst_ptr = stored_dst_slot_idx >= 0 ?
gin.get_sym_ptr<team_t>(recv_buffer.get_token_buffer(stored_dst_slot_idx).get_base_ptr(), stored_dst_rank_idx) :
nullptr;
if (dst_ptr != nullptr)
ptx::tma_store_1d(dst_ptr, tma_buffer.get_base_ptr(), tma_buffer.get_num_bytes<false>());
ptx::tma_store_commit();
// Issue RDMA put
if constexpr (not kIsScaleupNVLink) {
ptx::tma_store_wait<1>();
if (stored_dst_slot_idx >= 0 and dst_ptr == nullptr) {
gin.put<team_t>(recv_buffer.get_token_buffer(stored_dst_slot_idx).get_base_ptr(),
send_buffer_ptr, tma_buffer.get_num_bytes<false>(), stored_dst_rank_idx);
}
}
3.6 Step 5: Expand Mode Scatter(Copy Epilogue Kernel)
文件: deep_ep/include/deep_ep/impls/dispatch_copy_epilogue.cuh,line 120-122
} else if (kDoExpand and not kCachedMode and dst_expert_idx >= 0) {
dst_tensor_idx = atomicAdd(psum_num_recv_tokens_per_expert + dst_expert_idx, 1);
}
机制:
- 在 expand 模式下,token 需要按 expert 连续排列
- 使用 psum_num_recv_tokens_per_expert 作为原子计数器
- atomicAdd 返回 expert 内的 slot 位置
- 最终通过 TMA store 写入 recv_x(Expert-major layout)
4. DeepGEMM Mega MoE 实现分析
4.1 整体架构
Mega MoE 的 Counting Sort 完全在 一个 kernel 内的 dispatch warp 中完成:
┌─────────────────────────────────────────────────────────────────────────────┐
│ sm100_fp8_fp4_mega_moe_impl │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Dispatch Warp (warp_idx < kNumDispatchWarps) │ │
│ │ │ │
│ │ Step 1: COUNT Step 2: PREFIX SUM Step 3: SCATTER │ │
│ │ ┌──────────────┐ ┌───────────────────┐ ┌──────────────┐ │ │
│ │ │ atomicAdd to │ ──▶ │ atomicAdd to │──▶ │ atomicAdd to │ │ │
│ │ │ smem_expert │ │ global expert_ │ │ get dst slot │ │ │
│ │ │ _count[expert]│ │ send_count → │ │ write src_ │ │ │
│ │ │ │ │ get global offset │ │ token_topk_idx│ │ │
│ │ └──────────────┘ └───────────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Pull Warp (同一 dispatch warp,角色切换) │ │
│ │ │ │
│ │ Step 4: PULL + 连续化 │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 读取 scatter 表 → TMA pull token → 写入 pool buffer │ │ │
│ │ │ (expert_pool_block_offset 决定 pool 中的位置) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Scheduler (GEMM 调度) │ │
│ │ │ │
│ │ Step 5: POOL BLOCK OFFSET (Prefix Sum) │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ get_pool_block_offset: 对 stored_num_tokens_per_expert │ │ │
│ │ │ 做 prefix sum → expert 在 pool 中的起始 block 位置 │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
4.2 Step 1: COUNT — Dispatch Warp 本地计数
文件: deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh,line 355-359
// Count experts' tokens
read_topk_idx([&](const uint32_t& token_topk_idx, const int& expert_idx) {
atomicAdd_block(shared_storage.expert_token_count + expert_idx, 1);
});
ptx::sync_aligned(kNumDispatchThreads, kDispatchBarrierIdx);
机制:
- expert_token_count 是 smem 数组,大小为 kNumExperts
- 每个 SM 的 dispatch warp 遍历本地 token 的 topk_idx
- 对每个 (token, expert) pair,执行 atomicAdd_block
- 结果:expert_token_count[i] = 本 SM 发给 expert i 的 token 数量
4.3 Step 2: PREFIX SUM — 跨 SM 聚合得到全局偏移
文件: deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh,line 361-368
// Get SM offset (~6.5 us)
#pragma unroll
for (uint32_t i = thread_idx; i < kNumExperts; i += kNumDispatchThreads) {
const uint64_t send_value = (1ull << 32) | static_cast<uint64_t>(shared_storage.expert_token_count[i]);
shared_storage.expert_token_count[i] = static_cast<uint32_t>(
ptx::atomic_add(workspace.get_expert_send_count_ptr(i), send_value));
}
ptx::sync_aligned(kNumDispatchThreads, kDispatchBarrierIdx);
机制:
- expert_send_count 是 global 数组(在 workspace 中),每个 entry 是 64-bit
- 高 32-bit 存储 "完成信号"(SM count),低 32-bit 存储 "累计 token 数"
- atomic_add 返回值 = 该 SM 在全局序列中的 起始偏移
- 结果写回 expert_token_count[i],此时它不再是计数,而是 本 SM 在全局 scatter 中的起始 offset
4.4 Step 3: SCATTER — 写入源 token-topk 索引
文件: deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh,line 370-377
// Write source indices (~2 us with 512 tokens)
read_topk_idx([&](const uint32_t& token_topk_idx, const int& expert_idx) {
const auto dst_rank_idx = expert_idx / kNumExpertsPerRank;
const auto dst_slot_idx = atomicAdd_block(shared_storage.expert_token_count + expert_idx, 1);
const auto dst_ptr = workspace.get_src_token_topk_idx_ptr(
expert_idx % kNumExpertsPerRank, sym_buffer.rank_idx, dst_slot_idx);
*sym_buffer.map(dst_ptr, dst_rank_idx) = token_topk_idx;
});
机制:
- 再次遍历本地 token 的 topk_idx
- atomicAdd_block(expert_token_count + expert_idx, 1) 返回当前可用的 slot index
- 将 token_topk_idx(编码了 src_token_idx + src_topk_idx)写入目标 rank 的 src_token_topk_idx 表
- 写入通过 NVLink(sym_buffer.map)直达目标 rank 的对称内存
4.5 Step 4: PULL + 连续化
文件: deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh,line 511-598
// Read source token-topk index (written by remote dispatch via NVLink)
const uint32_t src_token_topk_idx = *workspace.get_src_token_topk_idx_ptr(
current_expert_idx, current_rank_in_expert_idx, token_idx_in_rank);
const uint32_t src_token_idx = src_token_topk_idx / kNumTopk;
const uint32_t src_topk_idx = src_token_topk_idx % kNumTopk;
// TMA load token from remote rank into shared memory
ptx::tma_load_1d(
pull_buffer.get_base_ptr(),
sym_buffer.map(input_token_buffer.get_data_buffer(src_token_idx).get_base_ptr(),
current_rank_in_expert_idx),
pull_mbarrier, kHidden);
// Store token to local L1 buffer via TMA
const uint32_t pool_token_idx = expert_pool_block_offset * BLOCK_M + token_idx_in_expert;
ptx::tma_store_1d(
l1_token_buffer.get_data_buffer(pool_token_idx).get_base_ptr(),
pull_buffer.get_base_ptr(), pull_buffer.get_num_bytes());
4.6 Step 5: POOL BLOCK OFFSET — Scheduler 中的 Prefix Sum
文件: deep_gemm/include/deep_gemm/scheduler/mega_moe.cuh,line 235-243
CUTLASS_DEVICE uint32_t get_pool_block_offset(const uint32_t& expert_idx) const {
uint32_t num_blocks = 0;
#pragma unroll
for (uint32_t i = 0; i < kNumExpertsPerLane; ++ i) {
if (i * 32 + ptx::get_lane_idx() < expert_idx)
num_blocks += math::ceil_div(stored_num_tokens_per_expert[i], BLOCK_M);
}
return __reduce_add_sync(0xffffffff, num_blocks);
}
机制:
- 对 stored_num_tokens_per_expert 做 prefix sum
- 输入:每个 expert 的 token 数
- 输出:该 expert 在 pool 中的起始 block 位置
- 用途:GEMM 计算时确定 token 在 pool buffer 中的位置
5. 核心对比
5.1 Count 阶段对比
| 维度 | DeepEP | DeepGEMM Mega MoE |
|---|---|---|
| 执行 Warp | Notify Warp(专用,与 dispatch 分离) | Dispatch Warp(与 scatter 共用) |
| 计数维度 | Expert + Rank(两个维度) | 仅 Expert(Rank 通过 expert_idx / kNumExpertsPerRank 隐式计算) |
| 去重策略 | Rank 维度需要 ptx::deduplicate |
无需去重(每个 expert 独立计数) |
| 计数器位置 | smem(rank_expert_count) |
smem(expert_token_count) |
| 原子操作 | atomicAdd_block |
atomicAdd_block |
5.2 Prefix Sum 阶段对比
| 维度 | DeepEP | DeepGEMM Mega MoE |
|---|---|---|
| 策略 | 三级:SM 内 atomic → 全局 reduction → SM 0 warp-level scan | 两级:SM 内 atomic → SM 间 global atomic |
| 全局聚合 | ptx::red_add 到全局 workspace,SM 0 等待所有 SM 完成后统一 scan |
ptx::atomic_add 直接到 global expert_send_count,返回值即全局偏移 |
| 编码方式 | 64-bit:高 32-bit = SM 完成计数,低 32-bit = token 数 | 64-bit:高 32-bit = SM 完成计数,低 32-bit = token 数 |
| 输出 | psum_num_recv_tokens_per_scaleup_rank(inclusive)+ psum_num_recv_tokens_per_expert(exclusive) |
expert_token_count 复用为全局起始偏移 |
| 执行者 | SM 0 的 Warp 0 和 Warp 1 | 所有 SM 的 dispatch warp 并行 |
5.3 Scatter 阶段对比
| 维度 | DeepEP | DeepGEMM Mega MoE |
|---|---|---|
| Scatter 产物 | dst_buffer_slot_idx(目标 rank 内的 slot 位置) |
src_token_topk_idx(源 token 索引表) |
| 原子操作 | atomicAdd(scaleup_atomic_sender_counter + dst_rank_idx, 1) |
atomicAdd_block(expert_token_count + expert_idx, 1) |
| 数据移动方式 | Push:源 rank 通过 TMA store 直接写入目标 rank | Pull:目标 rank 根据 scatter 表主动拉取 |
| 写入目标 | 目标 rank 的 recv_buffer |
目标 rank 的 src_token_topk_idx 表 |
| 寻址 | rank_idx * kNumMaxTokensPerRank + slot_idx |
expert_idx % kNumExpertsPerRank, sym_buffer.rank_idx, slot_idx |
5.4 连续 Layout 产生对比
| 维度 | DeepEP | DeepGEMM Mega MoE |
|---|---|---|
| 产生位置 | dispatch_copy_epilogue kernel(expand 模式) |
Pull 阶段(同一 kernel 内) |
| Layout 类型 | Expert-major(recv_x[expert_idx][token_idx]) |
Expert-major(pool_buffer[expert_block_offset + token_idx]) |
| Offset 计算 | atomicAdd(psum_num_recv_tokens_per_expert + dst_expert_idx, 1) |
expert_pool_block_offset * BLOCK_M + token_idx_in_expert |
| 与 GEMM 关系 | 分离:copy epilogue → 外部 GEMM | 融合:pool buffer 直接供 Tensor Core 消费 |
6. Mermaid 流程图
6.1 DeepEP Counting Sort 流程
flowchart TB
subgraph NotifyWarp ["Notify Warp (专用)"]
A[遍历 topk_idx] --> B[atomicAdd_block to smem expert_count]
B --> C[sync barrier]
C --> D[red_add to global notify_reduction_workspace]
D --> E[SM 0 等待所有 SM 完成]
E --> F[Warp-level inclusive/exclusive scan]
F --> G[输出 psum_num_recv_tokens_per_scaleup_rank]
F --> H[输出 psum_num_recv_tokens_per_expert]
end
subgraph DispatchWarp ["Dispatch Warp"]
I[遍历 token] --> J[deduplicate rank]
J --> K[atomicAdd scaleup_atomic_sender_counter]
K --> L[获得 dst_slot_idx]
L --> M[TMA store to recv_buffer]
end
subgraph CopyEpilogue ["Copy Epilogue Kernel (可选 Expand)"]
N[遍历 recv_buffer] --> O[atomicAdd psum_num_recv_tokens_per_expert]
O --> P[获得 expert 内 slot]
P --> Q[TMA store to recv_x Expert-major]
end
NotifyWarp --> DispatchWarp
DispatchWarp --> CopyEpilogue
6.2 DeepGEMM Mega MoE Counting Sort 流程
flowchart TB
subgraph DispatchWarp ["Dispatch Warp (Count + Scatter)"]
A[read_topk_idx] --> B[atomicAdd_block to smem expert_token_count]
B --> C[sync barrier]
C --> D[atomic_add to global expert_send_count]
D --> E[返回值写回 smem 作为全局偏移]
E --> F[sync barrier]
F --> G[read_topk_idx again]
G --> H[atomicAdd_block expert_token_count 获取 slot]
H --> I[src_token_topk_idx = token_topk_idx]
I --> J[NVLink map to dst_rank]
J --> K[写入 dst_rank 的 src_token_topk_idx 表]
end
subgraph PullPhase ["Pull Phase (同一 warp 角色切换)"]
L[scheduler.fetch_expert_recv_count] --> M[等待所有 rank 完成]
M --> N[读取 src_token_topk_idx]
N --> O[解码 src_token_idx, src_topk_idx]
O --> P[TMA load from remote rank]
P --> Q[pool_token_idx = expert_pool_block_offset * BLOCK_M + token_idx_in_expert]
Q --> R[TMA store to l1_token_buffer]
end
subgraph Scheduler ["Scheduler (GEMM 调度)"]
S[fetch_expert_recv_count] --> T[get_pool_block_offset: prefix sum]
T --> U[输出 expert 在 pool 中的起始 block]
end
DispatchWarp --> PullPhase
PullPhase --> Scheduler
7. 与 Metadata、Dispatch/Combine、Buffer 的交叉引用
7.1 与 Metadata(Agent 5)的关系
| 概念 | DeepEP | Mega MoE |
|---|---|---|
| Top-K 索引 | topk_idx[token][k] → 路由决策 |
topk_idx[token][k] → 路由决策 |
| 源 Metadata | src_token_global_idx(rank * max_tokens + token_idx) |
TokenSrcMetadata{rank_idx, token_idx, topk_idx} |
| Scatter 索引表 | 无(直接用 slot_idx) | src_token_topk_idx[expert][rank][slot] |
| 用途 | Combine 时反向寻址 | Pull 时寻址 + Combine 时反向寻址 |
关键洞察:
- DeepEP 的 Metadata 是 源 → 目标 的映射(src_token_global_idx 随 token 发送)
- Mega MoE 的 Metadata 是 目标 → 源 的映射(src_token_topk_idx 在目标端构建)
- 这种差异反映了 Push vs Pull 通信范式的不同
7.2 与 Dispatch/Combine(Agent 1)的关系
| 概念 | DeepEP | Mega MoE |
|---|---|---|
| Dispatch 输出 | recv_buffer(Destination-major) |
src_token_topk_idx 表 + pool_buffer(Expert-major) |
| Combine 输入 | recv_x(Expert-major,由 copy epilogue 产生) |
pool_buffer(Expert-major,由 pull 产生) |
| Combine 寻址 | recv_src_metadata(源 rank + slot) |
TokenSrcMetadata(源 rank + token + topk) |
| 反向 Scatter | Combine kernel 读取 src_metadata 反向寻址 |
Combine 阶段读取 TokenSrcMetadata 写回 |
7.3 与 Buffer(Agent 2)的关系
| 概念 | DeepEP | Mega MoE |
|---|---|---|
| 接收 Buffer | recv_buffer(对称内存,按 rank 分区) |
src_token_topk_idx 表(workspace) |
| Expert Buffer | recv_x(Expert-major,外部 GEMM 消费) |
l1_token_buffer(pool buffer,Expert-major) |
| Buffer 布局 | Token-major → Destination-major → Expert-major | Token-major → Scatter 表 → Expert-major (pool) |
| 连续化时机 | Copy Epilogue(分离 kernel) | Pull Phase(同一 kernel 内) |
8. 关键演化总结
8.1 从 DeepEP 到 Mega MoE 的范式转变
DeepEP (Push 范式):
Source Rank → Count → Prefix Sum → Scatter → TMA Store → Dest Rank's Buffer
↓
Copy Epilogue (Expand)
↓
Expert Buffer (GEMM Input)
Mega MoE (Pull 范式):
Source Rank → Count → Prefix Sum → Scatter → Write Scatter Table (Dest Rank)
↓
Pull Phase (Dest Rank)
↓
Pool Buffer (GEMM Input)
8.2 为什么 Mega MoE 选择 Pull 范式?
- 计算融合:Pull 阶段与 GEMM 在同一 kernel,避免 kernel launch overhead
- SM 利用率:Dispatch warp 在 pull 阶段继续工作,不浪费 SM
- 内存层级:Pool buffer 直接写入 L1/L2 ring buffer,供 Tensor Core 消费
- 避免 Write Conflict:Push 模式下多个源 rank 写入同一目标 rank 需要同步;Pull 模式下目标 rank 独占写入
8.3 Counting Sort 的两种原子策略
| 策略 | DeepEP | Mega MoE |
|---|---|---|
| Prefix Sum 实现 | SM 0 集中 scan(warp-level) | 分布式 atomic to global |
| 优点 | 简单,无需全局同步 | 并行度高,延迟低 |
| 缺点 | SM 0 成为瓶颈 | 全局 atomic 竞争 |
| 适用场景 | 中等规模 expert 数 | 大规模 expert 数(需要更高并行度) |
9. 代码位置索引
DeepEP
| 代码位置 | 行号 | 功能 |
|---|---|---|
dispatch.cuh |
92-108 | COUNT: atomicAdd_block(expert_count + dst_expert_idx, 1) |
dispatch.cuh |
110-115 | 全局 Reduction: ptx::red_add(notify_reduction_workspace) |
dispatch.cuh |
232-257 | PREFIX SUM: do_psum warp-level scan |
dispatch.cuh |
336-351 | SCATTER: atomicAdd(scaleup_atomic_sender_counter) |
dispatch.cuh |
371-393 | 数据移动: TMA store + RDMA put |
dispatch_copy_epilogue.cuh |
120-122 | EXPAND SCATTER: atomicAdd(psum_num_recv_tokens_per_expert) |
layout.cuh |
10-177 | Workspace/Buffer/Token Layout 定义 |
elastic.py |
38-57 | EPHandle: psum_num_recv_tokens_per_scaleup_rank, psum_num_recv_tokens_per_expert |
DeepGEMM Mega MoE
| 代码位置 | 行号 | 功能 |
|---|---|---|
sm100_fp8_fp4_mega_moe.cuh |
355-359 | COUNT: atomicAdd_block(expert_token_count + expert_idx, 1) |
sm100_fp8_fp4_mega_moe.cuh |
361-368 | PREFIX SUM: atomic_add(expert_send_count) |
sm100_fp8_fp4_mega_moe.cuh |
370-377 | SCATTER: atomicAdd_block(expert_token_count) 返回 slot |
sm100_fp8_fp4_mega_moe.cuh |
511-598 | PULL + 连续化: TMA load + pool buffer store |
scheduler/mega_moe.cuh |
235-243 | POOL BLOCK OFFSET: get_pool_block_offset prefix sum |
layout/mega_moe.cuh |
40-44 | TokenSrcMetadata 结构定义 |
layout/mega_moe.cuh |
226-232 | get_src_token_topk_idx_ptr scatter 表访问 |
10. 核心发现总结
- Count → Prefix Sum → Scatter 在两个代码库中都存在,但实现策略不同
- DeepEP 使用分离的 Notify Warp 做 Count,Mega MoE 使用 Dispatch Warp 统一做 Count + Scatter
- Prefix Sum 策略差异:DeepEP 是 SM 0 集中 scan,Mega MoE 是分布式 global atomic
- Scatter 方向相反:DeepEP 是 Push(源 → 目标),Mega MoE 是 Pull(目标 → 源)
- 连续 Layout 产生时机不同:DeepEP 在 copy epilogue,Mega MoE 在 pull phase
- Mega MoE 多一层 Prefix Sum:Scheduler 中的
get_pool_block_offset为 GEMM 计算 pool 中的 block 位置 - 数据结构与博客一致:
expert_token_count(计数+prefix sum)、src_token_topk_idx(scatter 索引)
附录:关键数据结构对比
DeepEP
// smem 中的计数数组(Notify Warp 使用)
int* rank_count = rank_expert_count; // [kNumRanks]
int* expert_count = rank_expert_count + kNumRanks; // [kNumExperts]
// global workspace 中的 reduction 数组
int64_t* notify_reduction_workspace; // [kNumRanks + kNumExperts]
// 输出 prefix sum
int* psum_num_recv_tokens_per_scaleup_rank; // [kNumRanks] (inclusive)
int* psum_num_recv_tokens_per_expert; // [kNumExpertsPerRank] (exclusive)
// scatter 使用的全局原子计数器
int* scaleup_atomic_sender_counter; // [kNumRanks]
DeepGEMM Mega MoE
// smem 中的计数数组(Dispatch Warp 使用)
uint32_t expert_token_count[kNumExperts]; // 计数 → 全局偏移(复用)
// global workspace 中的 expert count
uint64_t* expert_send_count; // [kNumExperts] (64-bit 编码)
uint64_t* expert_recv_count; // [kNumRanks][kNumExpertsPerRank]
uint64_t* expert_recv_count_sum; // [kNumExpertsPerRank]
// scatter 索引表
uint32_t* src_token_topk_idx; // [kNumExpertsPerRank][kNumRanks][max_recv_tokens]
// combine 使用的源 metadata
TokenSrcMetadata* token_src_metadata; // [num_max_pool_tokens]