Prhub

#53280 [Kernel][MoE] Optimize batched_moe_align_block_size with cooperative writes

原始 PR 作者 mgoin 合并时间 2026-09-12 03:05 文件变更 3 提交数 3 评论 9 代码增减 +154 / -27

执行摘要

MoE 对齐内核协作写并行化,H200 最高 8 倍加速

原内核带有 // TODO(varun): This is a naive implementation. Could be optimized. 注释,写 sorted_ids 与 block_ids 时每个 batch 只由一个线程顺序写入,1024 个线程的大多数处于空闲,GPU 利用率很低。PR body 明确提出要「parallelize batched MoE token and block-ID output writes across cooperative thread groups」,同时「retain the original serial specialization for small capacities and high batch counts」。作者还专门解释了非重复性:PR #35435 与 #40370 曾提出类似的 cooperative writes 方案,但均因过期未合并而关闭,本 PR 是在当前 main 上的重写。

值得精读。重点关注三个设计点:

1) 用模板参数在同一内核里编译出激进与保守两条路径,避免运行时分支开销;
2) 用 H200 实测阈值做启动派发,量化「协调开销 vs 并行收益」的工程取舍;
3) size_t 索引化对超大 batch 的防溢出处理。另外建议阅读评论区 prakharPant 的全 block 方案与 Nsight 数据——它指出了本方案单 block 启动的设备利用率上限,可作为同内核的 follow-up 优化方向;后续若继续优化,建议先跑 benchmark_batched 复现再评估。

讨论亮点

核心讨论来自社区成员 prakharPant,他提出了一版更激进的内核改写并附上大量实测数据:

  • 替代方案:利用全部 block 而不是单 block,并为 sorted_ids 的每个输出索引分配一个线程。自测显示在 max tokens >= 16k 或 num experts >= 256 的高负载场景明显更快,例如 256 experts / 16k tokens 下约 197us 对比协作写方案(原评论数据中 256 experts 场景协作方案约 199us,串行方案更慢)。
  • Nsight Compute 证据:prakharPant 贴出 profiling,指出协作写方案 grid 太小((1,1,1) x (1024,1,1)),「This kernel grid is too small to fill the available resources on this device, resulting in only 0.02 full waves across all SMs」,DRAM 吞吐仅 1.66%,说明单 block 启动没有利用整卡 SM。
    • 该讨论未被合入本 PR,PR 最终按作者设计合并;但它暴露了协作写路径「仅 1 block 启动、设备波次利用率不足」的局限,是明确的后续优化方向。
    • coderabbitai[bot] 的自动 review 无 actionable comments;claude[bot] 仅提示仓库配置了手动 review,无实质内容。

实现拆解

实现可拆为以下 5 个步骤:

  1. 内核模板化双路径:在 csrc/libtorch_stable/moe/moe_align_sum_kernels.cu 中,把 batched_moe_align_block_size_kernel 改为 template <bool cooperative_writes>,编译期生成 <true>(协作写)与 <false>(串行)两个实例。<false> 实例完整保留原逻辑,避免小容量场景的协调开销。
  2. 协作写路径的核心逻辑:<true> 路径在 BlockScan 计算完每个 batch 的 exclusive cumsum 后,先把 batch_cumsum、valid_tokens、batch_num_blocks 写入 shared memory 并 __syncthreads();随后把 1024 个线程划分为 2 的幂个线程组(上限 32 组,每组至少一个 warp),每个线程组按 stride 负责多个 batch,组内线程再按 stride 并行写入该 batch 的 token ID 与 block ID,把串行写延迟摊薄到多线程并行度上。
  3. 索引类型防溢出:sorted_ids_size、block_ids_size、batch_offset、cumsum 等偏移量从 int32_t 提升为 size_t,避免 batch_id * max_tokens_per_batch 等乘法在大 batch 数下溢出 int32 导致索引回绕。
  4. 启动端阈值派发:host 侧 batched_moe_align_block_size 增加 cooperative_threshold = max(256, 8 * B)(B 为 batch 数)的 guard,max_tokens_per_batch >= threshold 时启动 <true> 实例,否则走 <false>。阈值来自 H200 实测,兼顾「小容量协调开销反超收益」的情况;同时保留 B <= num_threads 的原有断言。
  5. 配套基准与测试:benchmarks/kernels/benchmark_moe_align_block_size.py 新增 benchmark_batched 与 --batched 参数,构造 uniform / skewed / mostly_empty 三种 batch token 分布,直接通过 vllm._custom_ops.batched_moe_align_block_size 测原始 kernel;tests/kernels/moe/test_moe_align_block_size.py 把 simulate_empty_batches 布尔参数改为 token_distribution(random / alternating_empty / skewed),并新增 block_size = 48,覆盖空 batch、极不平衡分布等边界,525 个用例全部通过。
文件 模块 状态 重要度
csrc/libtorch_stable/moe/moe_align_sum_kernels.cu 对齐内核 modified 5.5
benchmarks/kernels/benchmark_moe_align_block_size.py 内核基准 modified 6.94
tests/kernels/moe/test_moe_align_block_size.py 内核测试 modified 4.67

关键符号

batched_moe_align_block_size_kernel batched_moe_align_block_size benchmark_batched

分析完成后,这里会展示 LLM 生成的相对完整源码片段和详细注释。

评论区精华

prakharPant 提出全 block 并行替代内核方案 性能

prakharPant 评论称:「I have written a modified version of your kernel that utilizes all blocks instead of just 1 and assigns one thread to each output index of the sorted_ids array. It performs much better in high load scenarios (max tokens 16k and greater or 256 or more num experts.)」并附上 8/16/64/256 experts 与 1k-16k tokens 的 latency 对比表,展示高负载下其方案明显更快(如 256 experts / 16384 tokens 场景约 197us)。

结论:PR 合并时未采纳该方案;作者的设计选择是「保留串行 fallback + H200 实测阈值」,而该讨论揭示了协作写路径单 block 启动的扩展性上限,是明确的后续优化方向。 · 待处理

Nsight Compute 显示 grid 利用率仅 0.02 full waves 性能

prakharPant 贴出 profiling 数据:kernel 以 (1,1,1)x(1024,1,1) 启动,Nsight 提示「This kernel grid is too small to fill the available resources on this device, resulting in only 0.02 full waves across all SMs」,DRAM 吞吐 1.66%、内存吞吐 3.24%,说明单 block 协作写仍未利用整卡资源。

结论:该证据佐证本方案的并行度受限于单 block 启动;阈值 guard 只能规避小容量协调开销,无法解决大容量场景 SM 空闲问题,后续可结合全 block 思路进一步优化。 · 待处理

风险与影响

  1. 跨架构回归风险:cooperative_threshold = max(256, 8 * B) 是 H200(SM90)实测调参结果。A100、L40S 或消费级 GPU 上,大容量场景的协作写收益可能不同甚至更慢;虽然阈值以下回退到串行路径保护了小容量场景,但阈值以上的回归窗口在非 H200 设备上未被验证。文件:csrc/libtorch_stable/moe/moe_align_sum_kernels.cu 启动端。
  2. 索引类型变更的隐形影响:size_t 化会改变内核寄存器与 shared memory 占用。作者只验证了串行实例保留 48 寄存器 / 5280 字节 shared memory 的旧 profile,未给出协作实例的资源占用、occupancy 与多架构数据。
  3. 测试覆盖盲区:测试矩阵 max_tokens_per_batch 最大仅 512,且 batch 数(8/16/32/64)全为 2 的幂。协作分支中 num_groups 向上取 2 幂的循环、非 2 幂 batch 数(如 24、100)、以及 16k token 级别的大容量场景均无回归测试,cooperative 分支在大规模下的正确性主要依赖基准脚本的手工验证。
  4. 缺少端到端模型验证:作者说明未跑模型评测,依据是「对齐算子输出逐位相同、不改变模型数值」。该论证对数值正确性合理,但未实证验证生产路径 BatchedMarlinExperts 在真实负载下的端到端收益。
  • 生产调用方:BatchedMarlinExperts 在使用 batched activation 格式且启用 expert-parallel 时调用该内核,这类配置直接获得对齐阶段 1.10x 到 8.00x 的延迟下降;但 align 只是 MoE 前处理中的一小步,端到端收益会显著低于 kernel 级数字。
  • 用户侧:无 API、无算子语义变化,输出逐位一致,属于透明性能优化;主要受益面是 MoE 大模型在 EP 配置下的首 token / decode 前处理时延。
  • 工程侧:沉淀了「template 双路径 + 启动阈值 guard」的落地范式,为同仓库后续 kernel 优化提供了可复制的「激进优化 + 保守回退」模式;新增的 benchmark_batched 给了团队一个可复现的 batched 对齐基准,方便后续跟进替代方案。
启动阈值基于 H200 调参,跨架构回归风险 cooperative 路径仅单 block 启动,GPU 利用率受限 测试未覆盖非 2 幂 batch 数与大 token 场景 缺少端到端模型数值验证

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论