DeepEP Buffer System 与 DeepGEMM SymmBuffer 映射分析
分析日期: 2026-07-30
目标: 将 DeepEP 博客描述的 Buffer System 映射到 DeepGEMM SymmBuffer 的具体实现
1. SymmBuffer 内部布局
1.1 内存分段总览
SymmBuffer 是一个单一大块对称内存(symm_mem.empty(num_bytes, dtype=torch.int8, device='cuda')),内部按功能划分为多个逻辑段。从 csrc/apis/mega.hpp 的 get_symm_buffer_size_for_mega_moe 函数可以看到完整布局:
SymmBuffer 内存布局 (从低地址到高地址):
┌─────────────────────────────────────────────────────────────┐
│ Workspace (元数据区) │
│ ├─ Grid Sync Counters (32 bytes) │
│ ├─ Expert Send Count (num_experts × 8 bytes) │
│ ├─ Expert Recv Count (num_ranks × num_experts_per_rank × 8) │
│ ├─ Expert Recv Count Sum (num_experts_per_rank × 8) │
│ ├─ L1 Arrival Count (num_max_pool_blocks × 4) │
│ ├─ L2 Arrival Mask (num_max_pool_blocks × 8) │
│ ├─ Src Token-Topk Idx (dispatch pulling 用) │
│ └─ Token Src Metadata (combine write-back 用) │
├─────────────────────────────────────────────────────────────┤
│ Input Token Buffer (FP8) │
│ shape: [num_max_tokens_per_rank, hidden] │
├─────────────────────────────────────────────────────────────┤
│ Input SF Buffer (K-major, per-32 UE8M0) │
│ shape: [num_max_tokens_per_rank, hidden/128] │
├─────────────────────────────────────────────────────────────┤
│ Input TopK Idx Buffer (int64) │
│ shape: [num_max_tokens_per_rank, num_topk] │
├─────────────────────────────────────────────────────────────┤
│ Input TopK Weights Buffer (float32) │
│ shape: [num_max_tokens_per_rank, num_topk] │
├─────────────────────────────────────────────────────────────┤
│ L1 Token Buffer (FP8, pool) │
│ shape: [num_max_pool_tokens, hidden] │
├─────────────────────────────────────────────────────────────┤
│ L1 SF Buffer (M-major, UTCCP 128-aligned) │
│ shape: [num_padded_sf_pool_tokens, hidden/128] │
├─────────────────────────────────────────────────────────────┤
│ L1 TopK Weights Buffer (per-pool-token) │
│ shape: [num_max_pool_tokens, 1] │
├─────────────────────────────────────────────────────────────┤
│ L2 Token Buffer (FP8, pool) │
│ shape: [num_max_pool_tokens, intermediate_hidden] │
├─────────────────────────────────────────────────────────────┤
│ L2 SF Buffer (M-major, UTCCP 128-aligned) │
│ shape: [num_padded_sf_pool_tokens, intermediate_hidden/128] │
├─────────────────────────────────────────────────────────────┤
│ Combine Token Buffer (BF16) │
│ shape: [num_topk, num_max_tokens_per_rank, hidden] │
└─────────────────────────────────────────────────────────────┘
1.2 代码证据:Buffer 分配
// csrc/apis/mega.hpp: get_symm_buffer_size_for_mega_moe
// Workspace: barriers, counts, metadata
const auto workspace = layout::Workspace(nullptr, num_ranks, num_experts,
num_max_tokens_per_rank, num_topk, block_m);
// Input buffers (Token Buffer 等价物)
const auto input_token_buffer = layout::Buffer(
fp8_token_layout, 1, num_max_tokens_per_rank, workspace.get_end_ptr());
const auto input_sf_buffer = layout::Buffer(
fp8_sf_layout, 1, num_max_tokens_per_rank, input_token_buffer.get_end_ptr());
const auto input_topk_idx_buffer = layout::Buffer(
input_topk_idx_layout, 1, num_max_tokens_per_rank, input_sf_buffer.get_end_ptr());
const auto input_topk_weights_buffer = layout::Buffer(
input_topk_weights_layout, 1, num_max_tokens_per_rank, input_topk_idx_buffer.get_end_ptr());
// L1 pool: 所有本地 expert 共享的 token pool
const auto l1_token_buffer = layout::Buffer(
fp8_token_layout, 1, num_max_pool_tokens, input_topk_weights_buffer.get_end_ptr());
const auto l1_sf_buffer = layout::Buffer(
fp8_sf_layout, 1, num_padded_sf_pool_tokens, l1_token_buffer.get_end_ptr());
// L2 pool: L1 输出 → L2 输入
const auto l2_token_buffer = layout::Buffer(
fp8_intermediate_token_layout, 1, num_max_pool_tokens, l1_topk_weights_buffer.get_end_ptr());
const auto l2_sf_buffer = layout::Buffer(
fp8_intermediate_sf_layout, 1, num_padded_sf_pool_tokens, l2_token_buffer.get_end_ptr());
// Combine buffer: BF16, 用于跨 rank 写回
const auto combine_token_buffer = layout::Buffer(
bf16_token_layout, num_topk, num_max_tokens_per_rank, l2_sf_buffer.get_end_ptr());
1.3 Python 侧 View 映射
# deep_gemm/mega/__init__.py
(self.x, self.x_sf,
self.topk_idx, self.topk_weights,
self.l1_acts, self.l1_acts_sf,
self.l2_acts, self.l2_acts_sf) = slice_input_buffers(self.buffer)
注意:Python 侧只暴露了 8 个 view,Combine Buffer 和 Workspace 不暴露给 Python,仅供 kernel 内部使用。
2. SymmBuffer 段 → DeepEP Buffer 层映射
2.1 映射关系
| DeepEP Buffer 层 | SymmBuffer 对应段 | 说明 |
|---|---|---|
| Token Buffer | input_token_buffer + input_sf_buffer + input_topk_idx_buffer + input_topk_weights_buffer |
Router 输出,Token-major 布局 |
| Dispatch Buffer | ❌ 无独立对应 | Mega MoE 没有显式的 Dispatch Buffer |
| Chunk Buffer | ❌ 无独立对应 | Mega MoE 没有显式的 Chunk 聚合 |
| Receive Buffer | l1_token_buffer / l2_token_buffer (pool) |
每个 rank 的 L1/L2 pool 即为 receive 目标 |
| Expert Buffer (L1) | l1_token_buffer + l1_sf_buffer + l1_topk_weights_buffer |
Expert GEMM L1 输入 |
| Expert Buffer (L2) | l2_token_buffer + l2_sf_buffer |
Expert GEMM L2 输入 |
| Combine Buffer | combine_token_buffer (BF16) |
L2 输出写回 + top-k reduce |
2.2 关键差异:Dispatch/Chunk Buffer 的消失
DeepEP 的 Normal Kernel 有完整的 5 层 buffer:
Token → Dispatch → Chunk → Network → Receive → Expert
Mega MoE 的 buffer 层级:
Token(input) → [NVLink pull] → L1 pool → L1 GEMM+SwiGLU → L2 pool → L2 GEMM → Combine buffer → [NVLink write-back] → output
核心洞察: Mega MoE 通过 symmetric memory + dispatch warp 直接 pull 消除了显式的 Dispatch Buffer 和 Chunk Buffer。数据从 remote rank 的 input_token_buffer 直接通过 NVLink TMA load 拉到 local rank 的 l1_token_buffer,无需中间暂存。
3. Symmetric Memory 工作机制
3.1 什么是 Symmetric Memory
Symmetric Memory 是 PyTorch 2.x 引入的跨 rank 共享内存机制 (torch.distributed._symmetric_memory)。
# deep_gemm/mega/__init__.py
import torch.distributed._symmetric_memory as symm_mem
# 1. 每个 rank 分配相同大小的 buffer
self.buffer = symm_mem.empty(num_bytes, dtype=torch.int8, device='cuda')
# 2. 通过 rendezvous 注册到进程组,获取所有 rank 的地址
self.handle = symm_mem.rendezvous(self.buffer, group=group)
# 3. 获取所有 rank 的 buffer 地址列表
sym_buffer.handle.buffer_ptrs # List[int64], 长度 = num_ranks
3.2 SymBuffer 结构:地址映射核心
// deep_gemm/include/deep_gemm/layout/sym_buffer.cuh
template <uint32_t kNumRanks = 72>
struct SymBuffer {
int64_t base; // 本地 buffer 基地址
int64_t offsets[kNumMaxRanks]; // 各 rank 相对于本地的偏移
uint32_t rank_idx; // 当前 rank 编号
// 核心操作:将本地指针映射到目标 rank 的地址空间
template <typename ptr_t>
CUTLASS_DEVICE ptr_t map(const ptr_t& ptr, const uint32_t& dst_rank_idx) const {
int64_t mapped_ptr = offsets[dst_rank_idx] + reinterpret_cast<int64_t>(ptr);
return *reinterpret_cast<ptr_t*>(&mapped_ptr);
}
};
map 操作的本质:同一个指针(如 input_token_buffer 中的某个地址)在不同 rank 的 symmetric memory 中指向逻辑上对应的位置。通过 map(ptr, dst_rank),当前 rank 可以直接访问 dst_rank 的 buffer 中相同偏移处的数据。
3.3 为什么叫 "Symmetric"
"Symmetric" 的含义是对称性:
- 大小对称: 每个 rank 分配完全相同大小的 buffer
- 布局对称: 每个 rank 内部的 segment 布局完全相同
- 访问对称: 任何 rank 都可以通过相同的 offset 访问其他 rank 的对应位置
Rank 0 的 SymmBuffer Rank 1 的 SymmBuffer
┌──────────────────┐ ┌──────────────────┐
│ Workspace │ │ Workspace │
├──────────────────┤ ├──────────────────┤
│ Input Token Buf │◄────────►│ Input Token Buf │ ← 相同 offset 访问
├──────────────────┤ NVLink ├──────────────────┤
│ L1 Token Pool │ │ L1 Token Pool │
├──────────────────┤ ├──────────────────┤
│ L2 Token Pool │ │ L2 Token Pool │
├──────────────────┤ ├──────────────────┤
│ Combine Buf │◄────────►│ Combine Buf │
└──────────────────┘ └──────────────────┘
3.4 Cross-Rank Shared Memory 的含义
Cross-rank shared memory 不是真正的共享内存(物理上各 rank 的 GPU 显存独立),而是通过 NVLink P2P 访问实现的逻辑共享:
// kernel 中跨 rank 读取 remote token 的示例
// sm100_fp8_fp4_mega_moe.cuh: 546-550
ptx::tma_load_1d(
pull_buffer.get_base_ptr(), // 本地 smem 目标
sym_buffer.map(input_token_buffer.get_data_buffer(src_token_idx).get_base_ptr(),
current_rank_in_expert), // 远程 rank 的 token 地址
pull_mbarrier, kHidden);
这里 sym_buffer.map(...) 将本地 input_token_buffer 的指针映射到 current_rank_in_expert 的地址空间,然后通过 TMA (Tensor Memory Accelerator) 执行 NVLink 远程加载。
4. Token→Chunk→Network 聚合的处理
4.1 DeepEP 的做法
DeepEP Normal Kernel 显式聚合:
Token Stream → Chunk (多个 token 聚合) → NVLink/RDMA 传输
- Token = scheduling granularity
- Chunk = communication granularity
- 目的: 减少小数据包开销,提高带宽利用率
4.2 Mega MoE 的做法:无显式 Chunk
Mega Mo E 没有显式的 Chunk Buffer。它的 dispatch 策略是:
// sm100_fp8_fp4_mega_moe.cuh: 457-600
// Dispatch warps 按全局 token 索引轮转处理
for (uint32_t token_idx = sm_idx * kNumDispatchWarps + warp_idx; ; token_idx += kNumGlobalWarps) {
// 1. 确定该 token 属于哪个 expert、来自哪个 rank
// 2. 从 remote rank 的 input_token_buffer 直接 TMA load 到本地 smem
// 3. 从 smem TMA store 到本地 l1_token_buffer (pool)
// 4. 同时搬运 SF 和 topk_weights
// 5. 写 arrival count 通知 epilogue
}
关键区别:
- DeepEP: Token 先聚合到 Chunk,然后 Chunk 整体发送
- Mega MoE: 每个 token 独立通过 NVLink pull,无聚合
4.3 为什么 Mega Mo E 不需要 Chunk
- Decode 场景为主: Mega Mo E 面向 Blackwell SM100,主要优化 decode(小 batch),与 DeepEP Low-Latency Kernel 类似
- NVLink 带宽充足: 节点内 NVLink 带宽极高(~900 GB/s),小数据包开销相对可接受
- Compute-Communication 融合: 通过 persistent kernel + warp specialization,dispatch 和 GEMM 重叠执行,chunk 聚合的收益降低
- 对称内存的直接访问: symmetric memory 使得 remote access 像本地一样自然,无需显式 "发送-接收" 模型
5. Normal / Low-Latency Buffer 路径
5.1 Mega Mo E 的答案:单一路径
Mega Mo E 没有独立的 Normal / Low-Latency buffer 路径。它只有一套统一的 buffer 布局。
但从行为上看,Mega Mo E 更接近 DeepEP 的 Low-Latency Kernel:
- 无 Chunk 聚合
- 直接 NVLink pull(类似 Direct RDMA)
- Token 级粒度调度
5.2 代码证据
# deep_gemm/mega/__init__.py
# 只有一个 SymmBuffer 类,没有 Normal/LowLatency 变体
class SymmBuffer:
def __init__(self, group, num_experts,
num_max_tokens_per_rank, num_topk,
hidden, intermediate_hidden,
use_fp8_dispatch=True, activation='swiglu'):
# 统一分配,无路径选择
// csrc/apis/mega.hpp
// 只有一个 fp8_fp4_mega_moe 入口
static void fp8_fp4_mega_moe(...) {
// 直接调用 sm100_fp8_fp4_mega_moe
// 无 if (is_normal_kernel) 分支
}
6. Buffer 尺寸计算
6.1 关键参数
| 参数 | 含义 | 控制什么 |
|---|---|---|
num_max_tokens_per_rank |
每个 rank 最大 token 数 | Input Token Buffer 和 Combine Buffer 的第一维 |
num_topk |
每个 token 选择的 expert 数 | TopK Idx/Weights 的宽度,Combine Buffer 的 topk 维 |
hidden |
模型隐藏维度 | Token 的 K 维度 |
intermediate_hidden |
FFN 中间维度 | L2 Token 的 K 维度 |
num_experts |
全局 expert 总数 | Workspace 中 count 数组大小 |
num_ranks |
进程数 | 影响 pool 容量计算 |
6.2 Pool 容量计算
// deep_gemm/include/deep_gemm/layout/mega_moe.cuh
template <typename T>
CUTLASS_HOST_DEVICE constexpr T get_num_max_pool_tokens(
T num_ranks, T num_max_tokens_per_rank, T num_topk,
T num_experts_per_rank, T block_m) {
// 最坏情况: 所有 rank 的所有 token 都路由到本地 expert
const auto num_max_recv_tokens = num_ranks * num_max_tokens_per_rank;
const auto num_max_experts_per_token = math::constexpr_min(num_topk, num_experts_per_rank);
return math::constexpr_align(
num_max_recv_tokens * num_max_experts_per_token + num_experts_per_rank * (block_m - 1),
block_m);
}
直觉理解:
- 每个 rank 最多发 num_max_tokens_per_rank 个 token
- 每个 token 选 num_topk 个 expert
- 最坏情况所有 token 都选本地的 num_experts_per_rank 个 expert 之一
- 所以本地 pool 需要容纳 num_ranks × num_max_tokens_per_rank × min(num_topk, num_experts_per_rank) 个 token
- 再加上 per-expert 的 BLOCK_M 对齐 padding
6.3 SF Pool 容量
template <typename T>
CUTLASS_HOST_DEVICE constexpr T get_num_padded_sf_pool_tokens(T num_max_pool_tokens, T block_m) {
return (num_max_pool_tokens / block_m) * math::constexpr_align(block_m, 128);
}
SF 需要 128-aligned(UTCCP 硬件要求),所以每个 pool block 的 SF 需要 padding 到 128。
6.4 数值示例
以 test_mega_moe.py 默认参数为例:
- num_max_tokens_per_rank = 8192
- num_topk = 6
- num_experts = 384, num_ranks = 8 → num_experts_per_rank = 48
- hidden = 7168, intermediate_hidden = 3072
- block_m = 192
num_max_pool_tokens = align(8 × 8192 × min(6, 48) + 48 × 191, 192)
= align(393,216 + 9,168, 192)
= 393,408 (约 384K tokens)
num_padded_sf_pool_tokens = (393,408 / 192) × align(192, 128)
= 2,049 × 256
= 524,544
Buffer 总大小 ≈ Workspace + Input(8192×7168 + ...) + L1 pool(393408×7168 + ...) + L2 pool + Combine
≈ 数十 GiB 级别
7. transform_weights_for_mega_moe 的作用
7.1 功能
# deep_gemm/mega/__init__.py
def transform_weights_for_mega_moe(l1_weights, l2_weights):
# L1: interleave gate/up, 然后 transpose SF for UTCCP
l1_interleaved = _interleave_l1_weights(l1_weights)
l1_weights = (l1_interleaved[0], _transpose_sf_for_utccp(l1_interleaved[1]))
# L2: 只 transpose SF for UTCCP
l2_weights = (l2_weights[0], _transpose_sf_for_utccp(l2_weights[1]))
return l1_weights, l2_weights
7.2 L1 权重 Interleave
def _interleave_l1_weights(l1_weights):
# 原始布局: [gate: 0..N/2 | up: 0..N/2] (SwiGLU 的 gate 和 up 分开)
# 目标布局: [gate: 0..7, up: 0..7, gate: 8..15, up: 8..15, ...] (8 粒度交错)
为什么需要 interleave:
- SwiGLU 激活函数: output = silu(gate) * up
- Epilogue 中 TMEM load 以 8 为一组读取
- Interleave 使得 gate/up 对在内存中相邻,SwiGLU 可以高效执行
7.3 SF Transpose (UTCCP)
def _transpose_sf_for_utccp(sf):
# 将 SF 从 K-major 转为 MN-major (4x32 transpose)
# 满足 SM100 UTCCP (Universal Tensor Memory Copy with Transpose) 的硬件要求
为什么需要 transpose SF:
- SM100 的 block-scaled MMA 使用 UTCCP 指令将 SF 从 smem 搬到 TMEM
- UTCCP 要求 SF 是 MN-major 布局(4×32 粒度)
- 原始 SF 是 K-major(per-32 elements along K)
- 需要 transpose 以匹配 UTCCP 的 4×32 数据搬运模式
7.4 代码中的使用
# tests/test_mega_moe.py
l1_weights = cast_grouped_weights_to_fp4(l1_weights)
l2_weights = cast_grouped_weights_to_fp4(l2_weights)
transformed_l1_weights, transformed_l2_weights = deep_gemm.transform_weights_for_mega_moe(
l1_weights, l2_weights
)
# transformed_* 直接传入 kernel
deep_gemm.fp8_fp4_mega_moe(y, transformed_l1_weights, transformed_l2_weights, buffer, ...)
8. 完整数据流图
8.1 DeepEP Buffer 层级 (Mermaid)
flowchart LR
subgraph DeepEP_Normal["DeepEP Normal Kernel (Throughput)"]
direction TB
TB["Token Buffer<br/>(Token-major)"]
DB["Dispatch Buffer<br/>(Dest-major)"]
CB["Chunk Buffer<br/>(aggregated)"]
NB["Network<br/>NVLink/RDMA"]
RB["Receive Buffer<br/>(dest GPU)"]
EB["Expert Buffer<br/>(Expert-major)"]
TB --> DB --> CB --> NB --> RB --> EB
end
subgraph DeepEP_LL["DeepEP Low-Latency Kernel (Decode)"]
direction TB
TB2["Token Buffer"]
NB2["Direct RDMA"]
RB2["Receive Buffer"]
EB2["Expert Buffer"]
TB2 --> NB2 --> RB2 --> EB2
end
8.2 Mega MoE SymmBuffer 数据流 (Mermaid)
flowchart TB
subgraph Input["Input Phase (每个 rank 本地)"]
X["x (FP8 tokens)<br/>+ x_sf + topk_idx + topk_weights<br/>→ 写入 input_token_buffer"]
end
subgraph Dispatch["Dispatch Phase (跨 rank)"]
PULL["Dispatch Warps<br/>NVLink TMA Load<br/>从 remote rank 拉取 token"]
META["Metadata 交换<br/>expert_send_count<br/>src_token_topk_idx"]
end
subgraph L1Pool["L1 Expert Pool"]
L1T["l1_token_buffer<br/>(FP8, pool)"]
L1SF["l1_sf_buffer<br/>(M-major)"]
L1W["l1_topk_weights"]
end
subgraph L1GEMM["L1 GEMM + SwiGLU"]
GEMM1["FP8×FP4 GEMM<br/>(block-scaled MMA)"]
SWIGLU["SwiGLU + topk_weight<br/>in epilogue"]
end
subgraph L2Pool["L2 Expert Pool"]
L2T["l2_token_buffer<br/>(FP8, pool)"]
L2SF["l2_sf_buffer<br/>(M-major)"]
end
subgraph L2GEMM["L2 GEMM"]
GEMM2["FP8×FP4 GEMM<br/>输出 BF16"]
end
subgraph Combine["Combine Phase"]
CB["combine_token_buffer<br/>(BF16, per-topk-slot)"]
REDUCE["Top-K Reduce<br/>累加 + write-back to y"]
end
X --> PULL --> L1T
META --> PULL
L1T & L1SF & L1W --> GEMM1 --> SWIGLU --> L2T
L2T & L2SF --> GEMM2 --> CB --> REDUCE
8.3 对比图:DeepEP vs Mega MoE (Mermaid)
flowchart LR
subgraph EP["DeepEP Buffer 层级"]
direction TB
A1["Token Buffer"] --> A2["Dispatch Buffer"] --> A3["Chunk Buffer"]
A3 --> A4["Network"] --> A5["Receive Buffer"] --> A6["Expert Buffer"]
end
subgraph MM["Mega MoE SymmBuffer 映射"]
direction TB
B1["input_token_buffer<br/>(= Token Buffer)"]
B2["❌ 无 Dispatch Buffer<br/>直接 NVLink pull"]
B3["❌ 无 Chunk Buffer<br/>token 级直接传输"]
B4["l1_token_buffer<br/>(= Receive + Expert Buffer)"]
B5["l2_token_buffer<br/>(= L2 Expert Buffer)"]
B6["combine_token_buffer<br/>(= Combine 输出)"]
B1 --> B4 --> B5 --> B6
end
A1 -.-> B1
A5 -.-> B4
A6 -.-> B5
9. 核心洞察总结
9.1 SymmBuffer 的本质
SymmBuffer 是一个以 pool 为中心的融合 buffer 架构:
- Input 区:每个 rank 的本地 Router 输出(Token Buffer 等价物)
- Pool 区:所有本地 expert 共享的 token pool(Receive + Expert Buffer 融合)
- Combine 区:L2 输出暂存 + top-k reduce(Combine Buffer)
- Workspace:同步原语 + metadata
9.2 与 DeepEP 的根本差异
| 维度 | DeepEP | Mega MoE (SymmBuffer) |
|---|---|---|
| Buffer 层级 | 5 层 (Token→Dispatch→Chunk→Receive→Expert) | 3 层 (Input→Pool→Combine) |
| Dispatch 方式 | 显式 Dispatch Kernel + 中间 buffer | Warp 直接 NVLink pull,无中间 buffer |
| Chunk 聚合 | 有(Normal Kernel) | 无(token 级) |
| Normal/LL 分离 | 两套 kernel + buffer | 单一套 buffer |
| 内存模型 | 普通显存 + 显式 send/recv | Symmetric Memory + 地址映射 |
| 通信语义 | Push 模型(source 主动发送) | Pull 模型(destination 主动拉取) |
9.3 为什么 Mega Mo E 可以消除 Dispatch/Chunk Buffer
- Symmetric Memory 的地址对称性:任何 rank 可以直接访问任何其他 rank 的 buffer,无需 "打包-发送-解包"
- Persistent Kernel + Warp Specialization:dispatch warps 和 GEMM warps 同时运行,dispatch 可以按需实时 pull
- NVLink 的全连接拓扑:节点内所有 GPU 两两直连,pull 和 send 的延迟差异不大
- Decode 场景为主:小 batch 下 chunk 聚合无收益,直接 token 级传输更优
9.4 Pull vs Push 的范式转变
DeepEP (Push):
Source GPU: "我要发 token X 给 Dest GPU" → 写入 Dest 的 Receive Buffer
Mega Mo E (Pull):
Dest GPU: "我需要 token X 从 Source GPU" → 从 Source 的 Input Buffer 拉取
Pull 模型的优势:
- 接收方控制速率:避免发送方过快导致接收方溢出
- 天然负载均衡:处理快的 rank 可以多 pull
- 减少同步:不需要全局 "发送完毕" 信号,只需 per-block arrival count
10. 代码索引
| 文件 | 关键内容 |
|---|---|
deep_gemm/mega/__init__.py |
SymmBuffer 类定义、Python 接口 |
csrc/apis/mega.hpp |
Buffer 尺寸计算、slice 函数、C++ 入口 |
deep_gemm/include/deep_gemm/layout/sym_buffer.cuh |
SymBuffer 结构(地址映射) |
deep_gemm/include/deep_gemm/layout/mega_moe.cuh |
Workspace、Buffer、Data 布局定义 |
deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh |
完整 kernel 实现(dispatch/GEMM/combine) |
csrc/jit_kernels/heuristics/mega_moe.hpp |
配置计算(block size、pool 容量、pipeline) |
tests/test_mega_moe.py |
使用示例、正确性验证 |
11. 参考源码片段
11.1 SymmBuffer 初始化
# deep_gemm/mega/__init__.py
class SymmBuffer:
def __init__(self, group, num_experts,
num_max_tokens_per_rank, num_topk,
hidden, intermediate_hidden,
use_fp8_dispatch=True, activation='swiglu'):
# 1. 计算总字节数
num_bytes, slice_input_buffers = _C.get_symm_buffer_size_for_mega_moe(
group.size(), num_experts,
num_max_tokens_per_rank, num_topk,
hidden, intermediate_hidden,
use_fp8_dispatch, activation
)
# 2. 分配对称内存
self.buffer = symm_mem.empty(num_bytes, dtype=torch.int8, device='cuda')
# 3. 注册到进程组
self.handle = symm_mem.rendezvous(self.buffer, group=group)
# 4. 创建 view
(self.x, self.x_sf, self.topk_idx, self.topk_weights,
self.l1_acts, self.l1_acts_sf, self.l2_acts, self.l2_acts_sf) = \
slice_input_buffers(self.buffer)
11.2 跨 rank 访问
// sm100_fp8_fp4_mega_moe.cuh: 546-550
// Dispatch warp 从 remote rank 拉取 token
ptx::tma_load_1d(
pull_buffer.get_base_ptr(), // 本地 smem
sym_buffer.map(
input_token_buffer.get_data_buffer(src_token_idx).get_base_ptr(),
current_rank_in_expert // 目标 rank
),
pull_mbarrier, kHidden
);
11.3 Combine 写回
// sm100_fp8_fp4_mega_moe.cuh: 1197-1202
// Epilogue warp 将 L2 输出写回 remote rank 的 combine buffer
const auto dst_token = combine_token_buffer
.get_rank_buffer(dst_topk_idx)
.get_data_buffer(dst_token_idx);
const auto dst_ptr = math::advance_ptr<float4>(
dst_token.get_base_ptr(),
n_idx * sizeof(nv_bfloat16) + (lane_idx % 16) * sizeof(float4));
*sym_buffer.map(dst_ptr, dst_rank_idx) = packed;
分析基于 DeepGEMM 源码 (commit: 当前 main 分支) 与 DeepEP 博客文本