Files
DeepGEMM/megamoe-research-reports/pr357_green_context_split_megamoe_notes.md
Xinyi Liu 062cb160cf Phase 0: SM90 MegaMoE design doc, reference baseline, nsys script
- MEGAMOE_SM90_DESIGN.md: complete design document with finalized decisions
  (fused single kernel, cooperative + single-WG, dynamic BLOCK_M, etc.)
- tests/test_mega_moe_sm90.py: PyTorch FP32/BF16 reference implementation
  for dispatch → L1 GEMM → SwiGLU → L2 GEMM → combine pipeline
- scripts/run_nsys_mega_moe_sm90.sh: nsys profiling wrapper script
- megamoe-research-reports/: research analysis of PR304/323/347/352/357/360
2026-06-16 18:01:12 +08:00

21 KiB
Raw Blame History

PR357 Green-Context Split MegaMoE 代码review报告

1. 范围

  • Remote: git@github.com:RayWang96/DeepGEMM.git
  • 分支: split_mega_moe
  • 本地 worktree: pr-357
  • HEAD: bb837421b
  • 审查方式: 代码review不跑实验

pr-357 分支包含两段独立改动:

范围 Commit 主题 规模
核心 PR357 bb837421b Add green-context split-kernel MegaMoE 13 files, +3803/-4
旁路改动 41d89ee6c Add FP16-weights variant of FP8 MQA logits kernel 7 files, +668/-9

核心工作是 SM100 FP8/FP4 MegaMoE split pipeline,不是 SM90/Hopper 实现。它把原本 fused MegaMoE 拆成 dispatch_l1_swiglul2_combinecombine_reduce 三个 kernel并用 CUDA Runtime 13.1 green context 将 K1/K2 放到互斥 SM 分区中并发执行。


实现概述

K1: dispatch_l1_swiglu (dispatch + L1 GEMM + SwiGLU)

deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/dispatch_l1_swiglu.cuh (1013 lines)

K1 将 fused kernel 的 dispatch + L1 + SwiGLU 各阶段整合到一个 kernel 中。

warp 分工:

Warp Index 数量 Threads Role 所属 Warpgroup
0..kNumDispatchWarps-1 4 (128 threads) 128 Route-based dispatch: 扫描 topk_idx写 route count/entryNVLink pull token+SF WG0
kNumDispatchWarps 1 32 TMA A (l1_acts) + SFA loader WG1
kNumDispatchWarps+1 1 32 TMA B (l1_weights) + SFB loader WG1
kNumDispatchWarps+2 1 32 MMA issue warp (leader CTA only) WG1
kNumDispatchWarps+3 1 32 Idle (warpgroup 占位) WG1
剩余 ≥4 (≥128 threads) 128256 Math UMMA + SwiGLU epilogue + FP8 quant + TMA store + L2 arrival mask write WG2+

寄存器分配 (与 SM100 fused kernel 一致, dispatch=128, TMA=128, epilogue=256):

Role Reg/thread Threads 总 Reg
Dispatch 48 128 (4 warps) 6,144
TMA/non-epilogue 40 128 (4 warps) 5,120
Math/epilogue 208 256 (8 warps) 53,248
总计 512 (16 warps) 64,512/64K

关键约束:

  • kNumDispatchThreads % 128 == 0 → dispatch 独占整数个 warpgroup
  • kNumNonEpilogueThreads == 128 → TMA 恰好一个 warpgroup
  • Route-based dispatch: K1 不物化完整 routed token pool而是在 dispatch warp 中通过 route metadata 边拉取边写 L1 arrival count
  • K1 epilogue 写 l2_arrival_maskrelease-or通知 K2 可以开始消费

K2: l2_combine (L2 GEMM + Cross-Rank Combine Scatter)

deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/l2_combine.cuh (660 lines)

K2 等待 K1 的 L1 output 就绪,执行 L2 GEMM 并将结果 scatter 到目标 rank 的 combine buffer。K2 无 dispatch warp线程布局从 K1 config 继承 non_epilogue + epilogue。

warp 分工 (non_epilogue=128, epilogue=256):

Warp Index 数量 Threads Role 所属 Warpgroup
0 1 warp 32 TMA A (l2_acts) + SFA loader WG0
1 1 warp 32 TMA B (l2_weights) + SFB loader WG0
2 1 warp 32 MMA issue warp WG0
3 1 warp 32 Idle (warpgroup 占位) WG0
411 8 warps 256 Math UMMA + BF16 NVLink scatter epilogue WG1, WG2

精确 warp 统计: 12 warps = 384 threads = 3 warpgroups

寄存器分配:

Role Reg/thread Threads 总 Reg
TMA/non-epilogue 40 128 (4 warps) 5,120
Math/epilogue 208 256 (8 warps) 53,248
总计 384 (12 warps) 58,368/64K

关键设计:

  • 使用独立的 Kernel2L2Scheduler,等待 expert_recv_count_sum 高位达到 kKernel1SMs * kNumRanks 才开始调度
  • 每个 pool block 等待 l2_arrival_mask == expected_mask(由 K1 epilogue 写入)
  • K2 结束前执行 NVLink barrier保证 K3 reduce 时 combine buffer 跨 rank 可见

K3: combine_reduce (最终 Top-K Reduce)

deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/combine_reduce.cuh (142 lines)

warp 分工 (kNumThreads=512, JIT 模板实例化时设置,覆盖默认值 256):

Warp Index 数量 Threads Role
015 16 warps 512 Top-K reduce: 读取 top-k combine partials (BF16)FP32 accumulateBF16 store

精确 warp 统计: 16 warps = 512 threads

寄存器分配 (轻量 kernel, register 压力低):

Role Reg/thread Threads 总 Reg
Reduce ~32 512 (16 warps) ~16,384

K3 不使用 green context运行在 primary context。它依赖 graph node dependency等 K1/K2 graph node 结束)和 K2 的 NVLink barrier 保证数据可见。

Green Context 资源划分

Context Kernel SM 分配 (默认) Cluster Dim
Green Context 0 K1 dispatch_l1_swiglu kernel1_sms = 96 2
Green Context 1 K2 l2_combine kernel2_sms = 52 2
Primary Context K3 combine_reduce 无限制 1

2. 代码差异总览

2.1 核心 split MegaMoE 文件

文件 作用
deep_gemm/mega/__init__.py Python API新增 split buffer 和 SM100FP8FP4MegaMoESplitGraph 包装类
csrc/apis/mega.hpp C++ binding新增 split buffer size API 和 pybind graph class
csrc/jit_kernels/impls/sm100_fp8_fp4_mega_moe_split.hpp JIT runtime、配置推导、green context 创建、CUDA graph 构建
deep_gemm/include/deep_gemm/layout/mega_moe_split.cuh 新增 SplitWorkspace,扩展 route-based dispatch metadata
deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/dispatch_l1_swiglu.cuh K1路由 dispatch、L1 GEMM、SwiGLU、FP8 quant、L2 arrival mask
deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/l2_combine.cuh K2等待 K1 output、L2 GEMM、跨 rank combine scatter
deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/combine_reduce.cuh K3按 top-k 对 combine partial 做最终 reduce
deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe_split/common.cuh split pipeline 的 state tensor offset 定义
csrc/jit/handle.hpp lazy-load CUDA 13.1 green context 和 graph node API
deep_gemm/include/deep_gemm/common/utils.cuh 新增 rank/token peel helper用于 route-based pool slot 映射
deep_gemm/include/deep_gemm/ptx/ld_st.cuh 新增 CUDA 13 longlong4_32a 兼容和部分 load/store helper
tests/test_mega_moe_split.py correctness 和 perf 测试,对 fused kernel 做 bitwise 对比

2.2 旁路 MQA 改动

文件 作用
csrc/apis/attention.hpp weights.dtype == fp16 时选择新 SM100 FP8 MQA logits kernel
csrc/jit_kernels/impls/smxx_fp8_fp4_mqa_logits.hpp 新增 SM100FP8MQALogitsF16WeightsRuntime
deep_gemm/include/deep_gemm/impls/sm100_fp8_mqa_logits_f16_weights.cuh FP8 input + FP16 weights 的 SM100 2-CTA logits kernel
tests/test_attention.py 增加 FP16 weights 路径覆盖

3. Public API 变化

3.1 Split symmetric buffer

deep_gemm/mega/__init__.pySymmBuffer 增加 split: bool = False。当 split=Truebuffer sizing 使用 _C.get_symm_buffer_size_for_mega_moe_split

新增 API

deep_gemm.get_symm_buffer_for_mega_moe_split(...)

与 fused buffer 暴露相同的 tensor view

x, x_sf, topk_idx, topk_weights, l1_acts, l1_acts_sf, l2_acts, l2_acts_sf

区别在于 split buffer 的 workspace 头部更大,因为需要 route-based dispatch metadata。输入、pool、combine 区域的语义与 fused 对齐,方便 correctness 对比。

3.2 Weight transform

transform_weights_for_mega_moe 负责把权重变成 SM100 MegaMoE 需要的 layout

权重 变换
L1 weight gate/up interleave
L1 scale factor gate/up interleave再做 UTCCP 需要的 transpose
L2 weight 不 interleave
L2 scale factor 做 UTCCP transpose

3.3 Split graph object

新增 Python 包装类:

deep_gemm.SM100FP8FP4MegaMoESplitGraph(...)
graph.replay()
graph.get_green_context_ids()

构造函数接受 statesyssym_buffers、L1/L2 weights、stats以及 kernel1_smskernel2_smsreduce_sms 等参数。实际 C++ 构造函数会 JIT 编译三个 kernel创建两个 green context并构建一个 CUDA graph。


4. SplitWorkspace 布局

layout::SplitWorkspace 继承原 fused Workspace,保留 barrier、expert send/recv count、expert recv count sum、L1 arrival count、L2 arrival mask 等公共区域,同时新增 route-based dispatch 区域。

新增区域包括:

区域 用途
src_token_topk_idx[expert][rank][token] 记录每个 local expert 收到的来自哪个 rank/token/topk slot
src_route_count[rank][token] 记录一个 token 在目标 rank 上命中了几条 top-k route
src_route_entry[rank][token][topk] 多 route 时记录 packed route entry
token_src_metadata[pool_token] K2 scatter 时恢复目标 rank、原 token、topk slot

这个设计把 dispatch 从“先复制完整 routed token pool”改成“写路由元数据按 expert pool 顺序拉取 token”使 K1 可以边 dispatch、边 L1、边产生 L2 input。


5. CUDA Graph 与 Green Context

5.1 CUDA 13.1 依赖

sm100_fp8_fp4_mega_moe_split.hpp 只在 CUDART_VERSION >= 13010 下启用真实实现。低于 CUDA Runtime 13.1 时,SM100FP8FP4MegaMoESplitGraph 是 stub构造和 replay 都直接报错。

csrc/jit/handle.hpp lazy-load 了以下 runtime symbol

API 用途
cudaDeviceGetExecutionCtx 获取 primary execution context
cudaDeviceGetDevResource 查询 SM/workqueue resource
cudaDevSmResourceSplit 把 SM 资源切成两个 group
cudaGreenCtxCreate 创建 green context
cudaGraphAddNode 添加带 context 的 graph node
cudaGraphKernelNodeSetAttribute 设置 cluster dim
cudaGraphLaunch replay graph

5.2 SM 资源划分

create_green_contexts() 读取 device SM resourcekernel1_smskernel2_sms 切成两个 SM group

Context Kernel 资源
green_contexts_[0] K1 dispatch_l1_swiglu kernel1_sms
green_contexts_[1] K2 l2_combine kernel2_sms
primary_context_ K3 combine_reduce 未 green-context 限制

K1/K2 都用 cluster_dim=2,并设置 coscheduledSmCount=2。workqueue resource 使用 cudaDevWorkqueueConfigScopeGreenCtxBalanced

5.3 Graph node dependency

build_graph() 的 dependency 关系:

Node chain Dependency
K1 nodes 多 buffer 时 K1 串行依赖前一个 K1
K2 nodes 多 buffer 时 K2 串行依赖前一个 K2
第一个 K3 node 依赖最后一个 K1 和最后一个 K2
后续 K3 nodes 串行依赖前一个 K3

单 buffer 下语义直接K1 和 K2 同时启动K2 在 kernel 内 busy-wait K1 的 arrival maskK3 等 K1/K2 graph node 都结束后 reduce。

多 buffer 下 K1 chain 和 K2 chain 可以重叠,但 K3 等所有 K1/K2 chain 结束后再开始,不是 per-buffer K3 逐个接在各自 K1/K2 后面。


6. 三段 Kernel 实现

6.1 K1: dispatch_l1_swiglu

入口:

sm100_fp8_fp4_mega_moe_split_dispatch_l1_swiglu_impl(...)

K1 同时承担三件事:

阶段 行为
Dispatch metadata 扫描 topk_idx,写 expert send count、route count、route entry、remote src_token_topk_idx
L1 GEMM 按 expert pool 顺序拉取输入 token 和 SF做 L1 FP8/FP4 UMMA
SwiGLU/quant epilogue 中做 clamp、SwiGLU、乘 top-k weight、量化成 FP8l2_actsl2_acts_sf

关键机制:

机制 说明
route-based pull dispatch warp 通过 route metadata 拉取 remote input token不提前物化全部 token pool
duplicate route 去重 如果一个 token 对同一目标 rank 有多个 top-k routeroute_count 和 packed route entry 复用一次 remote pull
expert count 同步 本地写 send count跨 rank 用 atomic_add_sys 汇总 recv count
NVLink barrier dispatch metadata 完成后,通过 comm::nvlink_barrier 保证各 rank 可见
L1 arrival count 每个 pool block 的 token 写入后,l1_arrival_count release-addL1 GEMM warp 等待完整 block
L2 arrival mask K1 epilogue 完成一个 L1 output block 后,对 l2_arrival_mask 做 release-or通知 K2 可消费

K1 的 shared memory 与 fused kernel 不同。JIT 侧新增 get_mega_moe_split_kernel1_pipeline(),把 dispatch send buffer、expert count、CD staging、barrier、TMA stage 全部纳入 SMEM sizing避免复用 fused heuristic 导致 SMEM 不足。

6.2 K2: l2_combine

入口:

sm100_fp8_fp4_mega_moe_split_l2_combine_impl(...)

K2 只做 L2 和 combine scatter

阶段 行为
Scheduling Kernel2L2Scheduler 等待 expert_recv_count_sum 的高位达到 kKernel1SMs * kNumRanks
Readiness wait 每个 pool block 等待 l2_arrival_mask == expected_mask
L2 GEMM TMA load l2_acts/l2_weights 和 scale factors执行 SM100 MXF8F6F4 UMMA
Combine scatter epilogue 将 BF16 L2 output 通过 sym_buffer.map 写回目标 rank 的 combine buffer
Before reduce barrier K2 epilogue 结束后执行 NVLink barrier保证 K3 reduce 前 combine buffer 已跨 rank 可见

K2 的 scheduler 使用 block_idx += kKernel2SMs 方式分配 block因此 K2 的工作分区与其 green-context SM 数绑定。

6.3 K3: combine_reduce

入口:

sm100_fp8_fp4_mega_moe_split_combine_reduce_impl(...)

K3 是最简单的一段:

阶段 行为
Grid 一个 CTA 对应一个 original token
Load top-k 读取该 token 的 topk_idx
Reduce combine_token_buffer[topk_slot][token] 中的 BF16 partial 做 FP32 accumulate
Store cast 回 BF16y[token]

K3 依赖 graph node dependency 和 K2 内部 NVLink barrier 来保证数据可见。它不使用 green context也没有被 reduce_sms 限制。


7. 同步与状态设计

7.1 Workspace 同步

同步对象 Producer Consumer 作用
expert_send_count K1 dispatch K1/K2 scheduler 每个 expert 的发送 token 数
expert_recv_count_sum K1 dispatch K1/K2 scheduler 每个 local expert 的总接收 token 数和到达计数
l1_arrival_count K1 dispatch pull K1 L1 GEMM pool block 输入 token 是否完整
l2_arrival_mask K1 epilogue K2 L2 GEMM L1 output 的 N-block 是否完整
token_src_metadata K1 dispatch pull K2 combine scatter scatter 回原 rank/token/topk slot

7.2 state tensor

common.cuh 定义了一个至少 7 个 int32 的 state tensor

Offset 名称 当前用途
0 K1ReadyTasks 已定义,当前实现中未见核心调度使用
1 K1DoneBlocks 已定义,当前实现中未见核心调度使用
2 K2ClaimCounter 已定义,当前实现中未见核心调度使用
3 K2DoneTasks K2 epilogue 完成 task 后 atomicAdd
4 K2DoneBlocks K2 kernel block 完成后 atomicAdd
5 K3DoneElements K3 每 token 完成后 atomicAdd
6 K2Checksum 已定义,当前实现中未见核心调度使用

当前实现主要依赖 workspace arrival/count 和 graph dependencystate 更像是调试/进度计数和未来扩展预留。


8. Correctness 与测试策略

tests/test_mega_moe_split.py 是主要验证入口,本文未运行。

测试逻辑:

步骤 内容
构造输入 FP8 activation、FP4 L1/L2 weights、相同 top-k routing
分配 buffer fused buffer 和 split buffer 分开分配
跑 fused 调用 deep_gemm.fp8_fp4_mega_moe 得到 reference
跑 split 构造 SM100FP8FP4MegaMoESplitGraphreplay()
correctness torch.equal(y_split, y_fused),要求 bitwise identical
perf 对 fused CUDA graph 和 split graph 做 best-of-N wall-clock对比 TFLOPS/HBM/NVLink 粗略指标

默认参数:

参数 默认值
num_processes 8
num_tokens 8192
hidden 7168
intermediate_hidden 3072
num_experts 384
num_topk 6
kernel1_sms 96
kernel2_sms 52
reduce_sms 148

测试注释声称 split pipeline 通常略快于 fused但这是代码注释非本文实验结论。


9. 代码review发现与潜在问题

9.1 reduce_smsreduce_work_iters 当前未实际生效

Python docstring 说 K3 在 reduce_sms 上运行,但 C++ build_graph() 中 K3 node 使用:

add_kernel_node(primary_context_, kernel3_graph_kernel_, num_tokens_, 512, 0, 1, ...)

具体情况:

参数 当前情况
reduce_sms 构造时保存并做正数检查,但未用于 K3 grid 或 green context
reduce_work_iters 构造时保存,但未传给 K3也未影响调度
K3 SM 限制 不使用 green context运行在 primary context

这不一定是 bug可能是 API 预留或尚未完成的功能。但文档和实现存在不一致,后续使用不要把 reduce_sms 当成有效的 SM partition 控制。

9.2 K1/K2 依赖 busy-wait arrival异常路径可能死锁

K1/K2 之间的真实数据流依赖 workspace 中的 count/mask而非 graph dependency。K2 会等待 expert_recv_count_suml2_arrival_mask。如果 K1 提前失败、route metadata 被破坏、或某个 arrival mask 没有写满K2 可能长时间 spin 无法退出。

9.3 Split buffer 比 fused buffer 更大,且调用者必须显式 reset

SplitWorkspace 增加了 route metadata 区域buffer size 大于 fused。测试里每次 replay 前都会:

split_buffer.buffer.zero_()
state.zero_()
fill(split_buffer)

Public graph API 本身不自动 reset state/workspace。调用者如果 replay 多次,需要自行清零并重新填输入,否则 arrival/count/mask 会残留。

9.4 Graph construction 固化 shape 和指针

SM100FP8FP4MegaMoESplitGraph 在构造时固化:

固化对象 后果
num_tokens、hidden、intermediate、experts、topk 不适合动态 shape 直接复用
TMA descriptors weights/buffer pointer 和 layout 固定
green contexts 生命周期绑定 graph object
graph node params 多 buffer 列表长度固定

动态 token 数或不同权重需要重新构造 graph。

9.5 SM100 only

所有新增 split kernels 都要求 __CUDA_ARCH__ >= 1000,且 graph 实现要求 CUDA Runtime 13.1+。这不是 SM90/H100/H200 路径。


10. 与已有 MegaMoE split 方向的关系

pr357 的 split 不是简单的"L1 kernel 后接 L2 kernel"。其关键差异是 green-context concurrency

维度 常规 split L1/L2 pr357 green-context split
K1/K2 launch 两个 kernel 串行或外部并发 一个 CUDA graph 内两个 green context node
SM partition 默认由调度器抢占/共享 K1/K2 分别绑定固定 SM resource group
K2 等待方式 通常等 L1 kernel 完成 K2 kernel 先启动,按 block 等 K1 arrival mask
数据流 L1 全量完成后 L2 消费 K1 产生 pool blockK2 可逐 block 消费
K3 split 后单独 reduce graph dependency 等 K1/K2 后 reduce

目标是重叠 dispatch/L1 和 L2/combine并用固定 SM 切分减少 K1/K2 互相抢占。


11. FP16-weight MQA logits 旁路改动

这部分不是 split MegaMoE 主线,但在 pr-357 分支内。

11.1 API dispatch

fp8_fp4_mqa_logitsweights.scalar_type() == torch::kFloat16 时选择新 kernel

sm100_fp8_mqa_logits_f16_weights(...)

约束:

约束 原因
arch_major == 10 只支持 SM100
seq_len % 4 == 0 2-CTA kernel 的 query tiling 没有 per-row bound check
不支持 FP4 Q/KV 代码显式 assert not (is_fp4 and weights_is_f16)
FP16 accumulation QK score 和 per-head weighted sum 都用 FP16 accumulation速度快但更容易溢出

11.2 Kernel 结构

sm100_fp8_mqa_logits_f16_weights.cuh 是一个 2-CTA cluster kernel

组件 说明
Q/KV FP8 E4M3
weights FP16
KV scale FP32
SMEM pipeline Q、weights、KV、KV scale、KV offsets 多 stage
TMEM 使用 SM100 tensor memory accumulator
Launch num_specialized_threads=128num_math_threads=256cluster_dim=2

这是 attention/MQA 路径优化,不影响 split MegaMoE API。


12. 结论

pr357 的核心价值是引入了一个面向 SM100/CUDA 13.1 的 green-context split-kernel MegaMoE execution model。它不是单纯把 fused MegaMoE 拆成三个 kernel而是让 K1 和 K2 在一个 CUDA graph 中用不同 green context 同时运行,借助 workspace arrival mask 实现 producer/consumer 流水线。

最重要的实现要点:

结论 说明
K1/K2 真正并发 green context 将 kernel1_smskernel2_sms 切成独立 SM partition
数据流按 pool block 流水化 K1 写 l2_arrival_maskK2 等 mask 后消费
dispatch 变为 route-based pull SplitWorkspace 保存 route metadata减少重复 remote token pull
correctness 目标是 bitwise fused 测试直接要求 torch.equal(y_split, y_fused)
API 仍有预留/不一致 reduce_smsreduce_work_iters 当前未控制 K3
分支含旁路 MQA 优化 FP16-weight MQA logits 是独立 sidecar不属于 MegaMoE split 主链路