# MegaMoE SM90 Implementation Design (Final) ## 1. Executive Summary 本文档描述 DeepGEMM MegaMoE 在 SM90 (Hopper: H100/H200/H20) 架构上的最终实现设计。设计基于对 SM100 fused kernel (`sm100_fp8_fp4_mega_moe.cuh`, PR304) 的详细分析,以及对三条现有 SM90 探索路线 (PR323 fused, PR352 split, PR360 pingpong/cooperative) 的横向对比研究。 ### 1.1 核心设计决策 | # | 决策项 | 最终选择 | 理由 | |---|--------|---------|------| | 1 | Kernel 架构 | **Fused single kernel** | 单 kernel launch 减少 overhead,代码简洁 (~1900 行) | | 2 | Warpgroup 策略 | **Cooperative (BM=128) + single-WG (BM≤64)** | Cooperative 共享 weight load 主攻 HBM bottleneck;小 M 退化为 single-WG 避免 compute 浪费 | | 3 | BLOCK_M | **动态 128/64/32** | 按 expected_tokens_per_expert 三档选择,覆盖 decode 到 prefill | | 4 | BLOCK_N | **固定 128** | 64 个 accumulator regs/WG,寄存器压力可控 | | 5 | Cluster | **固定 1** | Cooperative 已共享 B-load;cluster=2 的 amax 跨 CTA 同步过于复杂 | | 6 | 线程布局 | **384 = 64 dispatch + 64 TMA + 256 math/epilogue** | 与 SM100 的 512 相比省出 WG 寄存器预算 | | 7 | SF 策略 | **Exact float SF** | 与 PyTorch 标准 FP8 quantize 一致 | | 8 | Weight SF 加载 | **Math WG `__ldg`** | 无额外 SMEM 占用,PR323/360 已验证 | | 9 | L2 Act SF | **Per-64 dual-half SF** | 精度更高,PR323/360 采用 | | 10 | L2 Scheduling | **Auto N-major** | 大 M 下减少 weight L2 cache thrash | | 11 | Math WG Regs | **224 reg/warp** | 总占用 96.9%,PR360 已验证可行 | | 12 | L1 TMA Store | **Double-buffered** | TMA store 与 epilogue 计算重叠 | | 13 | API | **独立 SM90 入口** | Python 层显式区分 SM90/SM100 | | 14 | Combine | **复用 SM100 逻辑** | Chunked TMA combine 与 TMEM/UMMA 无关,可直接移植 | | 15 | 文件组织 | **单文件 `sm90_fp8_mega_moe.cuh`** | 模板参数区分 cooperative/single-WG | | 16 | Clamp + FastMath | **两者都支持** | 模板参数,零开销 | --- ## 2. SM100 vs SM90 Architecture Gap ### 2.1 硬件能力对比 | 能力 | SM100 (Blackwell) | SM90 (Hopper) | 影响 | |------|-------------------|---------------|------| | Tensor Memory (TMEM) | 有 | **无** | accumulator 必须驻留在 registers | | UMMA (Unified MMA) | 有(`tcgen05`) | **无** | 使用 WGMMA (`wgmma.mma_async`) | | FP4 Tensor Core | 有(E2M1) | **无** | weight 必须使用 FP8,HBM 读取量翻倍 | | UTCCP (SF transport) | 有 | **无** | SF 通过 TMA 或 `__ldg` 加载 | | 2-CTA Cluster MMA | 有(`Allocator2Sm`) | 不适用 | cluster 仅用于 TMA multicast | | Scale Factor Format | UE8M0 packed `int` | **float** | per-128-K granularity, 32-bit | | SMEM Capacity | 232 KB | 232 KB | 相同 | | Registers/SM | 65,536 | 65,536 | 相同,但 TMEM 缺失使 register 压力剧增 | | WGMMA Shape | N/A (UMMA) | M=64, N=8..256, K=32 (FP8) | 固定 M=64 per warpgroup | ### 2.2 从 SM100 移植的关键改动点 1. **UMMA → WGMMA**:SM100 leader CTA 单 warp 发 UMMA,结果存 TMEM;SM90 每个 math WG(4 warps)独立发 WGMMA,结果驻留 WG register file 2. **TMEM epilogue → Register epilogue**:SM100 epilogue WG 从 TMEM load;SM90 math WG 自身持有 accumulator,math 和 epilogue 在同一 WG 中串行 3. **FP4 weights → FP8 weights**:weight HBM 读取量翻倍,是 SM90 bandwidth bottleneck 的根本原因 4. **UE8M0 SF → float SF**:activation/weight SF 使用 32-bit float,layout 简化(无需 4×32 UTCCP transpose) 5. **2-CTA cluster allocation → single CTA**:SM90 cluster 固定为 1,不做 2-SM MMA 协同 --- ## 3. Kernel Architecture: Fused Single Kernel ### 3.1 设计选型理由 选择 **fused single kernel** 而非 dual-kernel 或 split-kernel: | 方案 | 优势 | 劣势 | 结论 | |------|------|------|------| | **Fused single kernel** ✓ | 单次 launch,无 overhead;代码简洁 | 无法独立优化 L1/L2 的线程配置 | **选定** | | Dual-kernel auto-routing | 各 kernel 独立优化 | 两套 ~1400 行 kernel 维护负担 | 不选 | | Split L1/L2 | L1/L2 独立 register budget | Kernel launch overhead,无 pipeline overlap | 不选 | ### 3.2 模板参数驱动的 Warpgroup 模式 通过模板参数 `kCooperativeMode` 在编译期选择 cooperative 或 single-WG: ```cpp template <..., bool kCooperativeMode, ...> void sm90_fp8_mega_moe_impl(...) { // kCooperativeMode=true: BLOCK_M=128, 两个 math WG M-split // kCooperativeMode=false: BLOCK_M=64/32, 只有 WG1 工作, WG2 空闲 } ``` | BLOCK_M | kCooperativeMode | Math WGs | 每 WG 行数 | 场景 | |---------|-----------------|----------|-----------|------| | 128 | true | 2 (cooperative) | 64 | 大 M prefill | | 64 | false | 1 (single) | 64 | 中 M decode | | 32 | false | 1 (single) | 32 (WGMMA M=64, 用 32 行) | 极小 M decode | ### 3.3 Fused Kernel Pipeline 单 kernel 覆盖 dispatch → L1 GEMM → SwiGLU → L2 GEMM → NVLink scatter → combine reduce: ``` Phase 1: Dispatch WG0 dispatch warps → count experts, write source metadata, grid sync → NVLink barrier → pull remote tokens/SF/weights into local L1 pool → l1_arrival_count release Phase 2: Linear1 (L1 GEMM + SwiGLU) WG0 TMA warps → wait l1_arrival_count, TMA load L1 acts/weights WG1 (+ WG2 if cooperative) → WGMMA FP8×FP8, float SF scaling → SwiGLU epilogue in registers, top-k weight multiply → per-row amax, FP8 quantize, double-buffered TMA store l2_acts → write float l2_acts_sf, set l2_arrival_mask Phase 3: Linear2 (L2 GEMM + NVLink scatter) WG0 TMA warps → wait l2_arrival_mask, TMA load L2 acts/weights WG1 (+ WG2 if cooperative) → WGMMA FP8×FP8, per-64 dual-half SF → BF16 convert, SMEM staging, NVLink scatter to remote combine buffer Phase 4: Combine Reduce NVLink barrier → math/epilogue warps TMA-load top-k BF16 chunks → FP32 accumulate → BF16 cast → TMA store output y ``` --- ## 4. Thread Layout ### 4.1 384 Threads = 12 Warps = 3 Warpgroups ``` WG0 (128 threads = 4 warps): w0-w1 (64 threads): Dispatch + NVLink pull + workspace cleanup w2 (32 threads): TMA load A (activations + SFA) w3 (32 threads): TMA load B (weights); weight SF 由 math WG ldg WG1 (128 threads = 4 warps): w4-w7: Math WG0 - WGMMA + epilogue Cooperative: rows [0, 64) Single-WG: rows [0, BLOCK_M) WG2 (128 threads = 4 warps): w8-w11: Math WG1 Cooperative: rows [64, 128) Single-WG: idle (register-deallocated) ``` ### 4.2 Register Allocation | Role | Warps | Reg/Warp | Total | |------|-------|----------|-------| | WG0 (dispatch+TMA) | 4 | 48 | 6,144 | | WG1 (math+epilogue) | 4 | 224 | 28,672 | | WG2 (math+epilogue) | 4 | 224 | 28,672 | | **Total** | 12 | — | **63,488 / 65,536 (96.9%)** | Single-WG mode 下 WG2 同样分配 224 但不执行计算(寄存器仍被占用以保持 `__launch_bounds__` 一致性)。 --- ## 5. WGMMA Pipeline ### 5.1 MMA Instruction ``` WGMMA FP8: MMA_64x128x32_F32E4M3E4M3_SS_TN M = 64 (per warpgroup) N = 128 (= BLOCK_N) K = 32 (sub-step, 4 steps per BLOCK_K=128) Accumulator: float[64] per WG (64×128/128 = 64 registers) ``` ### 5.2 K-Loop Inner Pipeline ``` for k_block_idx in range(num_k_blocks): // Wait TMA full barrier (SMEM A/B ready) full_barriers[stage].wait(phase) // WGMMA fence + commit warpgroup_arrive() for k_step in range(BLOCK_K / 32): // 4 steps desc_a = advance_gmma_desc(smem_a[stage], wg_m_offset, k_step * 32) desc_b = advance_gmma_desc(smem_b[stage], 0, k_step * 32) WGMMA::wgmma(desc_a, desc_b, accum, k_step > 0 or k_block_idx > 0) warpgroup_commit_batch() warpgroup_wait<0>() // Signal TMA empty barrier (SMEM consumed) empty_barriers[stage].arrive() advance_pipeline(stage, phase) ``` `wg_m_offset`:cooperative mode 下 WG1=0, WG2=64;single-WG mode 下 WG1=0。 ### 5.3 Scale Factor Application WGMMA 完成后在 registers 中软件 apply SF: ``` // After WGMMA accumulation for a tile: // 1. Load SFA (per-128 activation scale) from SMEM via ld_shared // 2. Load SFB (per-128 weight scale) from global via __ldg (software prefetch) // 3. Apply: accum[i] *= sfa[row_i] * sfb[col_i] ``` ### 5.4 L2 Per-64 Dual-Half SF Scaling L1 epilogue 对每 64 列 output 生成独立 float SF。L2 GEMM K=128 需要拆成两个 per-64 half: ``` // L2 WGMMA K-loop produces raw accum over K=128 // After all K iterations: // Load two per-64 SFA halves sfa_half0 = l2_acts_sf[k_group_0 * stride + pool_token_idx] // k=[0,64) sfa_half1 = l2_acts_sf[k_group_1 * stride + pool_token_idx] // k=[64,128) // Apply: need to track partial sums per half during K-loop // Option A: Two separate WGMMA passes (K=64 each), scale, add // Option B: Single K=128 pass, then decompose using SF ratio // // Chosen: Option A (two K=64 passes with intermediate scaling) // K-step 0..1 → accum_half0, scale by sfa_half0 * sfb // K-step 2..3 → accum_half1, scale by sfa_half1 * sfb // final = accum_half0 + accum_half1 ``` --- ## 6. Cooperative Mode (BLOCK_M=128) ### 6.1 M-Split Layout ``` smem_A [128 rows × BLOCK_K]: WG1 → desc points to rows [0, 64) WG2 → desc points to rows [64, 128) smem_B [BLOCK_N × BLOCK_K]: SHARED - TMA loads ONCE, both WGs consume smem_cd [128 rows × output_cols]: WG1 → writes rows [0, 64) WG2 → writes rows [64, 128) ``` ### 6.2 TMA Load ``` TMA A warp (w2): load full 128-row A tile → full_barriers[stage].arrive_and_expect_tx(SMEM_A_SIZE) TMA B warp (w3): load BLOCK_N-row B tile (shared) → full_barriers[stage].arrive_and_expect_tx(SMEM_B_SIZE) ``` Weight HBM 读取量:相比 single-WG 的两次独立 B load,cooperative 只 load 一次 → **weight HBM 减半**。 ### 6.3 L1 Epilogue Sync 两个 WG 各自完成自己 64 行的 SwiGLU + FP8 quant + TMA store 后: ``` // 等两个 WG 的 TMA store 都完成 tma_store_wait<0>() sync_aligned(256, kEpilogueFullBarrierIdx) // 一个 WG 代表设置 l2_arrival_mask if (epilogue_warp_idx == 0 and elect_one_sync()) red_or_rel_gpu(l2_arrival_mask[pool_block_idx], 1 << n_block_idx) ``` ### 6.4 smem_cd Aliasing Guard L1 (FP8) 和 L2 (BF16) 的 smem_cd 复用同一 SMEM 区域。L2 epilogue scatter 完成前不得进入下一 tile: ``` // L2 epilogue 末尾: sync_aligned(256, kEpilogueFullBarrierIdx) // 等所有 WG scatter 完成 ``` --- ## 7. Single-WG Mode (BLOCK_M=64/32) ### 7.1 退化行为 当 BLOCK_M ≤ 64 时,`kCooperativeMode=false`: - **WG1** 执行全部 WGMMA + epilogue 工作 - **WG2** 在 kernel 开始后立即 `warpgroup_reg_dealloc` 并 idle - TMA A warp 只加载 BLOCK_M 行(而非 128 行) - TMA B warp 不变 ### 7.2 BLOCK_M=32 的 WGMMA 适配 WGMMA M 固定为 64,但 BLOCK_M=32 时只有 32 个有效行。处理方式: - WGMMA 仍计算 64×128 的 tile,但只有 accumulator 中前 32 行的结果被 epilogue 使用 - epilogue 中 `valid_m = min(num_tokens_in_block, BLOCK_M)` 控制实际写入行数 - 计算利用率 50%,但在 token-per-expert ≤ 16 的极端 decode 场景下 tile 数量少,总 compute 量不大 --- ## 8. Shared Memory Layout ### 8.1 SMEM Regions ``` smem_buffer (232 KB total, 1024-byte aligned): ┌──────────────────────────────────────────────────────┐ │ Expert Count (kNumExperts × 4B, align to 1024B) │ Dispatch region ├──────────────────────────────────────────────────────┤ │ Send Buffers (num_dispatch_warps × hidden, aligned) │ ├──────────────────────────────────────────────────────┤ │ smem_cd: max(L1 FP8 double-buf, L2 BF16 staging) │ │ L1: store_block_m × (BN/2) × sizeof(FP8) × 2 │ │ L2: BLOCK_M × BN × sizeof(BF16) │ ├──────────────────────────────────────────────────────┤ │ smem_a[0..num_stages-1]: │ Pipeline stages │ BLOCK_M × BLOCK_K × sizeof(FP8) per stage │ ├──────────────────────────────────────────────────────┤ │ smem_b[0..num_stages-1]: │ │ BLOCK_N × BLOCK_K × sizeof(FP8) per stage │ ├──────────────────────────────────────────────────────┤ │ smem_sfa[0..num_stages-1]: │ Float SF │ align(2 × BLOCK_M × sizeof(float), 128) per stage │ ├──────────────────────────────────────────────────────┤ │ Amax reduction scratch │ ├──────────────────────────────────────────────────────┤ │ Barriers + combine barriers │ └──────────────────────────────────────────────────────┘ ``` ### 8.2 Pipeline Stage Budget ``` Cooperative (BLOCK_M=128, BLOCK_N=128, BLOCK_K=128): smem_a = 128 × 128 × 1B = 16 KB smem_b = 128 × 128 × 1B = 16 KB smem_sfa = align(256 × 4B, 128) = 1 KB Total per stage ≈ 33 KB Fixed ≈ 42 KB (expert_count + send_buffers + smem_cd + amax + barriers) Available = 232 - 42 = 190 KB Num stages = 190 / 33 ≈ **5 stages** Single-WG (BLOCK_M=64): smem_a = 64 × 128 × 1B = 8 KB smem_b = 128 × 128 × 1B = 16 KB smem_sfa = align(128 × 4B, 128) = 512 B Total per stage ≈ 24.5 KB Num stages = 190 / 24.5 ≈ **7 stages** Single-WG (BLOCK_M=32): smem_a = 32 × 128 × 1B = 4 KB Total per stage ≈ 20.5 KB Num stages = 190 / 20.5 ≈ **9 stages** ``` --- ## 9. Dispatch Phase ### 9.1 与 SM100 的差异 | 项目 | SM100 | SM90 | |------|-------|------| | Dispatch threads | 128 (WG0 全部) | **64** (WG0 w0-w1) | | Register budget | 48 reg/warp | 48 reg/warp (相同) | | SF copy | UTCCP 4×32 transpose 写入 l1_sf_buffer | **直接按自然 layout 写入** (无 transpose) | | Token pull | TMA load 1D + TMA store 1D | 相同 | ### 9.2 Dispatch Pull Path ``` for each assigned token (round-robin across global warps): // Round-robin rank selection via iterative min-peeling (same as SM100) select source rank // TMA load token from remote rank into SMEM send buffer tma_load_1d(pull_buffer, remote_token_ptr, pull_mbarrier, kHidden) // Load float SF from remote rank (direct copy, no UTCCP transpose) for each sf_element: local_sf[k_group * stride + pool_token_idx] = remote_sf[...] // Load top-k weight, wait TMA, store token via TMA store 1D ... // Write source metadata + signal block ready token_src_metadata[pool_token_idx] = {rank, token_idx, topk_idx} red_add_rel(l1_arrival_count[pool_block_idx], 1) ``` --- ## 10. L1 Epilogue: SwiGLU + FP8 Quantization ### 10.1 Register-Based Flow SM90 L1 epilogue 在 math WG 自身的 registers 中执行: ``` // After WGMMA completes for a L1 tile: // accum[64] holds FP32 values // 1. Apply SF: accum[i] *= sfa[row_i] * sfb[col_i] // 2. Load top-k weight for each row topk_weight = l1_topk_weights[pool_token_idx + row_offset] // 3. SwiGLU: silu(gate) * up * topk_weight // Gate/up pairs interleaved in N dimension (granularity 8) for each gate-up pair: if kActivationClamp != inf: gate = clamp(gate, -clamp, clamp) up = clamp(up, -clamp, clamp) silu_gate = gate / (1 + (kFastMath ? __expf(-gate) : expf(-gate))) result = silu_gate * up * topk_weight // 4. Per-row amax reduction across 4 warps in WG amax = warp_reduce<4>(max(abs(result_values))) // 5. Compute exact float SF, quantize to FP8 E4M3 sf = amax / 448.0 fp8_result = cast_to_fp8(result / sf) // 6. Write float SF to l2_acts_sf (MN-major, per-64-K layout) l2_acts_sf[k_idx * stride + pool_token_idx] = sf // 7. STSM to smem_cd, double-buffered TMA store to l2_acts stsm(smem_cd[tma_stage], fp8_result) tma_store_2d(tensor_map_l1_output, smem_cd[tma_stage], n_idx, m_idx) // 8. Signal L2 ready red_or_rel_gpu(l2_arrival_mask[pool_block_idx], 1 << n_block_idx) ``` ### 10.2 Cooperative L1 Epilogue - WG1 处理 rows [0, 64), WG2 处理 rows [64, 128) - 各自独立计算 amax、SF、FP8 quant、TMA store - `sync_aligned(256, kEpilogueFullBarrierIdx)` 等两个 band 都完成后设置 `l2_arrival_mask` --- ## 11. L2 Epilogue: BF16 Scatter + Combine ### 11.1 L2 BF16 Epilogue ``` // After WGMMA with dual-half SF scaling: // 1. Cast FP32 → BF16, STSM to smem_cd_l2 bf16_values = float_to_bf16(final_accum) stsm(smem_cd_l2, bf16_values) // 2. sync WG-internal sync_aligned(128, kEpilogueWGBarrierStartIdx + wg_idx) // 3. Read source metadata, NVLink scatter for each valid row: metadata = token_src_metadata[m_idx + row_offset] packed = ld_shared(smem_cd_l2 + row * BLOCK_N * sizeof(BF16)) *sym_buffer.map(combine_dst_ptr, metadata.rank_idx) = packed // 4. Cooperative: sync_aligned(256) guard smem_cd alias sync_aligned(256, kEpilogueFullBarrierIdx) ``` ### 11.2 Combine Reduce (复用 SM100 逻辑) NVLink barrier 后,epilogue warps 执行 chunked TMA-based top-k reduce: ``` // Per warp processes one token at a time for token_idx in stride(sm_idx * num_epi_warps + warp_idx, num_tokens, num_sms * num_epi_warps): // Double-buffered TMA load all active top-k BF16 chunks // Accumulate in float registers float2 reduced[chunk_elems] = {0} for each active slot: tma_load_1d(load_buffer[stage], combine_chunk_ptr, barrier, chunk_bytes) barrier.wait() for each element: reduced[i] += bf16_to_float2(load_buffer[stage][i]) flip stage // Cast to BF16, TMA store to output y tma_store_1d(y + token_idx * hidden + chunk_offset, store_buffer, chunk_bytes) ``` --- ## 12. Scheduler ### 12.1 Wave-Based Expert Scheduling 复用 SM100 `MegaMoEScheduler` 核心设计,移除 2-CTA cluster 约束: - `block_idx = blockIdx.x`(SM100: `blockIdx.x` 隐含 2-CTA 配对) - L1/L2 block N counts 不再要求偶数 - `kClusterSize = 1` 固定 Wave 结构不变:每 wave 处理 `kNumExpertsPerWave` 个 expert,先遍历 L1 blocks 再回到 wave 起点遍历 L2 blocks。 ### 12.2 Auto N-Major ```cpp bool use_n_major_l2 = (expected_tokens_per_expert >= 256); // 可通过 DG_SM90_MOE_NMAJOR 覆盖: -1=auto, 0=off, 1=on // M-major: block_idx → (m_block, n_block) = (idx / num_n_blocks, idx % num_n_blocks) // N-major: block_idx → (m_block, n_block) = (idx % num_m_blocks, idx / num_m_blocks) ``` --- ## 13. Heuristics ### 13.1 BLOCK_M 三档选择 ```cpp static auto get_block_config_for_sm90_mega_moe( int num_ranks, int num_experts, int num_max_tokens_per_rank, int num_topk, int num_tokens) { float expected = float(num_tokens) * num_ranks * num_topk / num_experts; if (expected <= 16.5) { // 极小 decode: RL long-tail, 极大 EP return {.block_m = 32, .cooperative = false, .store_block_m = 16}; } else if (expected <= 64.5) { // 中等 decode return {.block_m = 64, .cooperative = false, .store_block_m = 32}; } else { // 大 M prefill / 大 EP decode return {.block_m = 128, .cooperative = true, .store_block_m = 32}; } } ``` ### 13.2 Wave Count ```cpp static int get_num_experts_per_wave(...) { // Same logic as SM100: // 1. Estimate L1 blocks per expert // 2. Find smallest value whose total blocks keep all SMs busy // 3. Round up to nearest divisor of num_experts_per_rank } ``` ### 13.3 Pipeline Stages ```cpp static auto get_pipeline_config(...) { constexpr int smem_capacity = 232448; // SM90 // Fixed regions: expert_count + send_buffers + smem_cd + amax + barriers // Per-stage: smem_a + smem_b + smem_sfa + 2 barriers int num_stages = (smem_capacity - smem_fixed) / smem_per_stage; assert(num_stages >= 3); return {num_stages, total_smem}; } ``` --- ## 14. Synchronization Map ### 14.1 Barrier Inventory | Barrier | Type | Count | Usage | |---------|------|-------|-------| | `dispatch_barriers` | `ClusterTransactionBarrier` | 2 (dispatch warps) | Dispatch pull TMA mbarrier | | `full_barriers` | `ClusterTransactionBarrier` | num_stages | TMA→math: SMEM data ready | | `empty_barriers` | `ClusterTransactionBarrier` | num_stages | Math→TMA: SMEM consumed | | `combine_barriers` | `ClusterTransactionBarrier` | num_epi_warps × 2 | Combine TMA double-buffer | ### 14.2 Intra-SM Named Barriers | ID | Name | Scope | Usage | |----|------|-------|-------| | 0 | `kDispatchBarrierIdx` | 64 dispatch threads | Dispatch internal phases | | 1 | `kDispatchWithEpilogueBarrierIdx` | 64 + 256 threads | Dispatch↔epilogue handshake | | 2 | `kEpilogueFullBarrierIdx` | 256 epilogue threads | L1/L2 cooperative sync + smem_cd alias guard | | 3+ | `kEpilogueWGBarrierStartIdx` | 128 threads (per WG) | WG-local smem_cd write sync | ### 14.3 Cross-Phase Sync | Sync Point | Mechanism | Producer | Consumer | |------------|-----------|----------|----------| | Dispatch → L1 TMA | `l1_arrival_count` release-add | Dispatch warp | TMA A warp | | L1 Epilogue → L2 TMA | `l2_arrival_mask` bit-OR release | Math WG | TMA A warp | | L2 Scatter → Combine | NVLink barrier | All epilogue threads | All epilogue threads | | Combine → Cleanup | `kDispatchWithEpilogueBarrierIdx` | Epilogue | Dispatch | --- ## 15. Weight Transform ### 15.1 SM90 Weight Transform ```python def transform_weights_for_mega_moe_sm90(l1_weights, l2_weights): """ SM90 weight transform: - L1: gate/up FP8 weight interleave along N (granularity 8) - L2: no transform - Weight SF: natural MN-major float layout, NO UTCCP transpose """ E, N, K = l1_weights.shape # N = 2 * intermediate_hidden half_N = N // 2 interleaved = torch.empty_like(l1_weights) for i in range(0, half_N, 8): interleaved[:, 2*i:2*i+8, :] = l1_weights[:, i:i+8, :] # gate interleaved[:, 2*i+8:2*i+16, :] = l1_weights[:, half_N+i:half_N+i+8, :] # up return interleaved, l2_weights ``` ### 15.2 Weight SF Layout ``` SM100: UE8M0 packed int, UTCCP 4×32 transposed, per-32-K SM90: float, natural MN-major, per-128-K Shape: (num_experts, N/128, K/128) float32 Kernel indexing: sf[expert * (N/128 * K/128) + n_block * (K/128) + k_block] ``` --- ## 16. API Design ### 16.1 独立 SM90 入口 ```cpp // csrc/apis/sm90_mega.hpp (新文件) static void fp8_mega_moe( const torch::Tensor& y, const std::tuple& l1_weights_tuple, const std::tuple& l2_weights_tuple, const std::optional& cumulative_local_expert_recv_stats, const torch::Tensor& sym_buffer, const std::vector& sym_buffer_ptrs, const int& rank_idx, const int& num_max_tokens_per_rank, const int& num_experts, const int& num_topk, const std::tuple& recipe, // (1, 1, 128) for FP8 const std::string& activation, const std::optional& activation_clamp_opt, const bool& fast_math ); static void register_sm90_apis(pybind11::module_& m) { m.def("get_token_alignment_for_sm90_mega_moe", ...); m.def("get_symm_buffer_size_for_sm90_mega_moe", ...); m.def("fp8_mega_moe", &fp8_mega_moe); m.def("transform_weights_for_mega_moe_sm90", ...); } ``` ### 16.2 Python 层路由 ```python # deep_gemm/mega/__init__.py def fp8_mega_moe(y, l1_weights, l2_weights, ...): if _is_sm90(): _C.fp8_mega_moe(...) elif _is_sm100(): _C.fp8_fp4_mega_moe(...) else: raise RuntimeError("Unsupported architecture") def transform_weights_for_mega_moe(l1_weights, l2_weights): if _is_sm90(): return _C.transform_weights_for_mega_moe_sm90(...) else: return _C.transform_weights_for_mega_moe(...) # SM100 FP4+UTCCP ``` --- ## 17. File Layout ``` deep_gemm/include/deep_gemm/ impls/ sm90_fp8_mega_moe.cuh # Fused kernel (cooperative + single-WG via template) scheduler/ sm90_mega_moe.cuh # SM90 scheduler (no 2-CTA, N-major support) csrc/ jit_kernels/ heuristics/ sm90_mega_moe.hpp # SM90 config: BLOCK_M selection, wave, stages impls/ sm90_fp8_mega_moe.hpp # JIT runtime: template instantiation, TMA desc, launch apis/ sm90_mega.hpp # SM90-only API: fp8_mega_moe, buffer alloc, pybind11 deep_gemm/ mega/__init__.py # Extended: SM90 routing + transform tests/ test_mega_moe_sm90.py # SM90 correctness + benchmark ``` --- ## 18. Environment Variables | Variable | Values | Default | Description | |----------|--------|---------|-------------| | `DG_SM90_MOE_NMAJOR` | `-1\|0\|1` | `-1` (auto) | L2 N-major scheduling | | `DG_SM90_MOE_PHASE_PROFILE` | `0\|1` | `0` | 启用 per-phase nsys profiling | --- ## 19. Known Risks & Mitigations ### 19.1 Register Budget (P0) 63,488 / 65,536 = 96.9% 利用率。余量 2,048 registers。 - 严格 `__launch_bounds__(384, 1)` + `warpgroup_reg_alloc<224>` / `warpgroup_reg_dealloc<48>` - Per-64 dual-half SF 在 L2 中需要维护两组 partial accumulator —— 需要仔细编写避免 spill - 若 224 不够可降至 208 (总 60,416, 92.2%) ### 19.2 HBM Bandwidth Bottleneck (P0) SM90 FP8 weights 比 SM100 FP4 大 2×,NCU 显示 ~90% DRAM utilization。 - Cooperative kernel 共享 B-load,weight 读取减半 - Auto N-major L2 scheduling 改善 weight tile L2 cache 命中率 - 未来可考虑 cluster=2 TMA multicast(需解决 amax 跨 CTA 同步) ### 19.3 Single-WG BLOCK_M=32 Compute 浪费 (P2) WGMMA M=64 但只用 32 行,50% compute 浪费。在极端 decode (token-per-expert ≤ 16) 场景下可接受,因为总 compute 量极小。若需进一步优化,未来可考虑 `mma.sync` 路径(PR352 已探索)。 ### 19.4 NCU Profiling Limitations (P1) Symmetric memory + NVLink barrier 与 NCU kernel replay 冲突。 - 使用 `nsys profile` + NVTX markers 做 timeline 分析 - 单 rank 模式 (num_ranks=1) 下可用 NCU - `DG_SM90_MOE_PHASE_PROFILE` 分段计时 --- ## 20. Correctness Verification ### 20.1 Baseline PyTorch FP32/BF16 reference:dispatch → L1 dequant GEMM → SwiGLU → FP8 quant → L2 dequant GEMM → combine。 ### 20.2 Test Matrix | Test | Tokens | Experts | Hidden × Inter | TopK | Ranks | |------|--------|---------|----------------|------|-------| | Smoke | 1-4 | 8 | 4096×2048 | 6 | 2 | | Decode | 16-128 | 48 | 7168×2048 | 6 | 8 | | Prefill | 256-8192 | 48 | 7168×3072 | 6 | 8 | | DSV4 Flash | 16-8192 | 256 | 4096×2048 | 6 | 8 | | DSV4 Pro | 16-8192 | 384 | 7168×3072 | 6 | 8 | | MiMo-V2.5 | 16-8192 | 256 | 4096×2048 | 8 | 8 | | Edge: 0 tokens | 0 | any | any | any | 2 | ### 20.3 Tolerance - BF16 output: `allclose(atol=0.05, rtol=0.1)` --- ## 21. 工程实现 Phases ### Phase 0: 环境搭建与 Baseline 验证 (Day 1-3) **目标**:建立开发环境、确认 SM100 fused kernel 可正常运行、准备 SM90 PyTorch reference baseline。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 0.1 在 H200 集群搭建开发环境 | 可编译运行的 DeepGEMM 环境 | `tests/test_mega_moe.py` SM100 path 通过 | | 0.2 编写 PyTorch FP32/BF16 reference | `tests/test_mega_moe_sm90.py` baseline 函数 | 与 SM100 kernel output 在 BF16 精度内一致 | | 0.3 搭建 SM90 单 rank 测试框架 | 可在 H200 上以 `__CUDA_ARCH__=900` 编译的 stub kernel | JIT 编译通过,kernel launch 不崩溃 | | 0.4 建立 nsys profiling 脚本 | `scripts/run_nsys_mega_moe_sm90.sh` | 能生成 timeline report | **依赖**:无 **风险**:PyTorch symmetric memory API 在特定 NCCL/CUDA 版本下 multicast 不可用 → 使用 `NCCL_NVLS_ENABLE=0` fallback --- ### Phase 1: 基础设施层 (Day 4-10) **目标**:完成 kernel 外围的所有 C++/Python 基础设施,使 JIT 编译框架可以实例化空 kernel 并正确分配 buffer。 | 步骤 | 文件 | 交付物 | 验证标准 | |------|------|--------|----------| | 1.1 SM90 Scheduler | `deep_gemm/include/deep_gemm/scheduler/sm90_mega_moe.cuh` | 移除 2-CTA 约束的 scheduler,支持 N-major | 单元测试:给定 expert token counts 和 SM 数,验证 block 分配覆盖所有 expert tiles | | 1.2 SM90 Heuristics | `csrc/jit_kernels/heuristics/sm90_mega_moe.hpp` | BLOCK_M 三档 (32/64/128),wave count,pipeline stages 计算 | 给定 shape 参数验证输出 config 合理(stages ≥ 3,smem ≤ 232KB) | | 1.3 JIT Runtime | `csrc/jit_kernels/impls/sm90_fp8_mega_moe.hpp` | Template instantiation 代码生成、TMA descriptor 创建、kernel launch | 空 kernel 可 JIT 编译 + launch(立即 return) | | 1.4 API & Buffer | `csrc/apis/sm90_mega.hpp` | `fp8_mega_moe`、`get_symm_buffer_size_for_sm90_mega_moe`、pybind11 注册 | Python 可调用 `_C.get_symm_buffer_size_for_sm90_mega_moe(...)` | | 1.5 Weight Transform | Python `mega/__init__.py` 扩展 | `transform_weights_for_mega_moe_sm90` (L1 gate/up interleave) | 对比 SM100 的 interleave 结果在 FP8 数据维度一致 | | 1.6 Python Routing | `deep_gemm/mega/__init__.py` | `_is_sm90()` 检测 + 路由到 SM90 API | 在 H200 上 import deep_gemm 并调用 SM90 路径不报错 | **验证里程碑**:`python -c "import deep_gemm; deep_gemm.get_symm_buffer_for_mega_moe(...)"` 在 H200 上正确返回 buffer views。 **依赖**:Phase 0 完成 **关键参考**: - SM100 对应文件:`csrc/jit_kernels/heuristics/mega_moe.hpp` (heuristics)、`csrc/jit_kernels/impls/sm100_fp8_fp4_mega_moe.hpp` (JIT) - SM90 GEMM 参考:`csrc/jit_kernels/heuristics/sm90.hpp` (arch spec) --- ### Phase 2: Dispatch + L1 Pool 填充 (Day 11-15) **目标**:只实现 dispatch 数据路径,不进入 TMA/WGMMA。验证 expert count、token routing、source metadata、跨 rank pull、`l1_arrival_count` 等 dispatch 产物正确,为后续 L1 GEMM 提供稳定输入。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 2.1 Dispatch-only kernel 骨架 | `sm90_fp8_mega_moe.cuh` 中 WG0 的 64-thread dispatch 角色分配;TMA/math WG 可 idle | 编译通过,launch_bounds(384,1),launch 不崩溃 | | 2.2 Dispatch barrier / workspace 初始化 | dispatch mbarrier、grid sync counter、expert count、arrival counter 初始化 | 连续两次调用无残留 workspace 污染 | | 2.3 Expert count | 统计本 rank token/top-k 到 local experts 的 recv count,并写入 workspace | expert token counts 与 PyTorch routing reference 一致 | | 2.4 Source metadata | 写入 `token_src_metadata[pool_token_idx] = {rank, token_idx, topk_idx}` | metadata 与 routing reference 逐项一致 | | 2.5 Token/SF/top-k pull | 将输入 token、float SFA、top-k weight 拉入 local L1 pool | 单 rank 下 `l1_acts` / `l1_acts_sf` / `l1_topk_weights` 与输入逐字节或逐值一致 | | 2.6 Cross-rank dispatch smoke | NVLink barrier + remote pull 路径打通 | 2-rank / 8-rank 下无 hang,pool 内容与 reference 一致 | | 2.7 `l1_arrival_count` release | 每个 pool block dispatch 完成后 release-add arrival count | arrival count 达到 block 内 token 数,TMA consumer 可安全等待 | **验证里程碑**:单 rank 和多 rank 下 dispatch-only 输出 `l1_acts`、`l1_acts_sf`、`l1_topk_weights`、`token_src_metadata`、`l1_arrival_count` 与 PyTorch routing reference 一致;不要求任何 GEMM 计算。 **依赖**:Phase 1 完成 **关键挑战**: - pool token index / expert block offset 计算必须与 scheduler 后续消费保持一致 - symmetric memory `sym_buffer.map` 与 NVLink barrier 的 phase 计数必须可连续调用 - debug dump 只能临时使用,必须按 AGENTS.md 加 `// DEBUG` / `# DEBUG` 并 stash --- ### Phase 3: L1 TMA + 单 tile WGMMA (Day 16-20) **目标**:在 Phase 2 dispatch 产物上实现 L1 TMA/WGMMA pipeline,**不含 epilogue**(accumulator 可直接丢弃或写 debug buffer)。验证 TMA A/SFA/B 加载、WGMMA FP8×FP8 累积、SF scaling、empty barrier 的正确性。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 3.1 TMA/math kernel 骨架 | WG0 w2/w3 作为 TMA A/B warp,WG1/WG2 作为 math warpgroup | 编译通过,launch_bounds(384,1) | | 3.2 Full/empty barrier 初始化 | full/empty mbarrier per pipeline stage 初始化 + `__syncthreads` | 无 launch failure / deadlock | | 3.3 TMA A/SFA load | w2 TMA A warp:wait `l1_arrival_count`,TMA load L1 acts + float SFA to SMEM | dump SMEM A/SFA,与 global pool 数据逐字节/逐值对比 | | 3.4 TMA B load | w3 TMA B warp:TMA load L1 FP8 weights to SMEM | dump SMEM B 区域对比 | | 3.5 WGMMA descriptor | 构造 A/B GMMA descriptor;cooperative 下 WG1/WG2 指向不同 M offset | descriptor 地址和 swizzle 与 SM90 GEMM reference 一致 | | 3.6 WGMMA pipeline | math WG:`warpgroup_arrive`, 4× K-step `WGMMA::wgmma`, `warpgroup_commit_batch`, `warpgroup_wait<0>` | 单 tile accumulator 与 PyTorch FP32 GEMM 结果对比(未 scale) | | 3.7 SF scaling | accum *= sfa * sfb (sfb via `__ldg`) | Scale 后 accumulator 与 reference scaled GEMM 一致 | | 3.8 Empty barrier | WGMMA 完成后 arrive empty_barrier → TMA 可重用 SMEM | 多 K-block pipeline 不死锁 | **验证里程碑**:单 expert、BLOCK_M=128 的 L1 GEMM(无 epilogue)输出与 PyTorch `(dequant(x) @ dequant(w).T) * sfa * sfb` 在 FP32 accumulator 精度内 `allclose(atol=1e-2, rtol=0.05)`。 **依赖**:Phase 2 完成 **关键挑战**: - WGMMA descriptor 构造(参考 `deep_gemm/mma/sm90.cuh` 中的 `make_gmma_desc`) - Cooperative mode 下两个 WG 的 desc 指向 smem_a 的不同 64-row 偏移 - `warpgroup_arrive()` / `warpgroup_commit_batch()` / `warpgroup_wait<0>()` 的 fence 语义 --- ### Phase 4: L1 Epilogue — SwiGLU + FP8 Quant (Day 21-30) **目标**:在 Phase 3 的 WGMMA 结果上完成 L1 epilogue 全流程,输出正确的 FP8 `l2_acts` 和 float `l2_acts_sf`。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 4.1 Top-k weight load | 从 `l1_topk_weights_buffer` load weight per row | 值与 dispatch 写入一致 | | 4.2 SwiGLU 计算 | gate/up deinterleave + `silu(gate) * up * topk_weight` in registers | 与 reference SwiGLU 一致(精度:atol=0.02) | | 4.3 Activation clamp | `kActivationClamp` 模板参数控制 clamp | clamp 后无超范围值 | | 4.4 fast_math SwiGLU | `__expf` + `fast_rcp` 快速路径 | 与精确版相差 < 0.5% | | 4.5 Per-row amax | `warp_reduce<4>(max(abs(...)))` 跨 WG 内 4 warps reduce | amax 值与 PyTorch `abs().max(dim=-1)` 一致 | | 4.6 Cooperative amax | 两个 WG 独立 amax(各自 64 行),SMEM scratch 区域用于跨 warp reduce | 每行 amax 正确 | | 4.7 FP8 quantize | `sf = amax / 448.0`,`fp8 = cast(result / sf)` | 反量化后与 reference 误差 < 1% | | 4.8 Float SF write | 写入 `l2_acts_sf` (MN-major, per-64-K layout) | SF 值正确、layout 与 L2 TMA load 兼容 | | 4.9 STSM + double-buf TMA store | STSM 到 `smem_cd[stage]`,TMA store 到 `l2_acts` | `l2_acts` 内容与 reference FP8 quant output 一致 | | 4.10 `l2_arrival_mask` 设置 | `red_or_rel_gpu(mask, 1 << n_block_idx)` | 单 expert 全部 L1 N-blocks 完成后 mask == expected | | 4.11 Cooperative sync | `sync_aligned(256)` 等两个 WG 都 TMA store 完成后设置 mask | 无 race condition | **验证里程碑**:单 expert 的 `l2_acts` (FP8) 和 `l2_acts_sf` (float) 与 reference `SwiGLU → FP8_quant` 一致。Multi-expert 场景下 `l2_arrival_mask` 正确触发。 **依赖**:Phase 3 完成 **关键挑战**: - WGMMA register layout 到 gate/up 列的映射关系(参考 SM100 的 `SM100_TMEM_LOAD_16dp256b1x` 读取模式,SM90 的 register layout 不同) - Double-buffered TMA store 的 `tma_store_wait<1>` 与 smem_cd 复用 - 寄存器压力:SwiGLU 中间变量 + amax + SF + FP8 cast 共享 224 reg/warp 的 math WG budget --- ### Phase 5: L2 GEMM + NVLink Scatter (Day 31-40) **目标**:L2 WGMMA pipeline + per-64 dual-half SF + BF16 epilogue + NVLink scatter 到 remote combine buffer。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 5.1 L2 TMA wait | TMA A warp wait `l2_arrival_mask == expected` | 正确等待 L1 所有 N-blocks 完成 | | 5.2 L2 TMA load | TMA load `l2_acts` + 两个 per-64 SFA halves | SMEM 数据正确 | | 5.3 L2 WGMMA dual-half | K-step 0-1 累积 half0,K-step 2-3 累积 half1 | 两组 partial accum 正确 | | 5.4 Dual-half SF apply | `final = half0 * sfa0 * sfb + half1 * sfa1 * sfb` | 与 reference L2 dequant GEMM 一致 | | 5.5 Weight SF `__ldg` | Math WG prefetch L2 weight SF from global | 值正确 | | 5.6 BF16 cast + STSM | FP32 → BF16 cast,STSM 到 `smem_cd_l2` (BF16 swizzle) | SMEM 内容正确 | | 5.7 Source metadata read | `token_src_metadata[pool_token_idx + row]` → {rank, token, topk} | 值与 dispatch 写入一致 | | 5.8 NVLink scatter | `ld_shared` + `sym_buffer.map` + remote store | 单 rank 下本地 combine buffer 数据正确 | | 5.9 Cooperative smem_cd guard | `sync_aligned(256)` 防止下一 tile 覆盖 smem_cd | 多 tile 序列无 data corruption | | 5.10 Multi-expert persistent loop | Scheduler `for_each_block` 遍历多 expert 的 L1→L2 cycle | 全量 expert 输出正确 | **验证里程碑**:多 expert、单 rank 下 `combine_token_buffer` 内容与 reference L2 GEMM BF16 输出一致。`l2_arrival_mask` 正确控制 L1→L2 依赖。 **依赖**:Phase 4 完成 **关键挑战**: - Per-64 dual-half 需要在 K-loop 中区分 half0/half1(k_step 0-1 vs 2-3),需要两组 accumulator 或 intermediate scale → **224 reg budget 最紧张的地方** - NVLink scatter 的 `sym_buffer.map` 在 SM90 上的行为验证(与 SM100 一致) - smem_cd_l2 (BF16) 和 smem_cd_l1 (FP8) 的 aliasing — 同一 SMEM 地址 --- ### Phase 6: Combine Reduce + 端到端 Fused (Day 41-48) **目标**:完成 combine reduce、NVLink barrier、workspace 清理。实现完整端到端 fused kernel。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 6.1 NVLink barrier | Epilogue grid sync + cross-rank signal | 多 rank 下所有 rank 到达 barrier | | 6.2 Dispatch↔Epilogue handshake | `kDispatchWithEpilogueBarrierIdx` sync | Dispatch 和 epilogue 阶段正确交替 | | 6.3 Combine TMA load | Double-buffered TMA 从 combine buffer load BF16 chunks | Load 数据与 scatter 写入一致 | | 6.4 FP32 accumulate | 逐 top-k slot 累加到 float registers | 累加结果与 reference `sum(topk_weights * expert_outputs)` 一致 | | 6.5 BF16 cast + TMA store | Cast + TMA store 到 output `y` | `y` 与 reference 在 BF16 精度内一致 | | 6.6 Workspace cleanup | Dispatch warps 清理 expert counts、arrival counters、metadata | 下次 kernel 调用前 workspace 已归零 | | 6.7 NVLink cleanup barrier | 跨 rank 确认 workspace 清理完成 | 连续两次 kernel 调用无 data corruption | | 6.8 端到端 correctness | 完整 `fp8_mega_moe(...)` 调用 | `test_mega_moe_sm90.py` Smoke test 通过 (2 ranks, 4096×2048, topk=6) | **验证里程碑**:2-rank Smoke test (4 tokens, 8 experts) `allclose(atol=0.05, rtol=0.1)` 通过。 **依赖**:Phase 5 完成 **关键挑战**: - Combine 逻辑直接移植自 SM100 (与 TMEM/UMMA 无关),但需要确认 SM90 下 TMA 1D load/store 的行为 - NVLink barrier 的 phase 计数在多次 kernel 调用间的正确性 - Workspace 清理时序:dispatch warps 与 epilogue warps 并行执行不同的清理任务 --- ### Phase 7: Single-WG 变体 (BLOCK_M=64/32) (Day 49-55) **目标**:实现 `kCooperativeMode=false` 路径,支持小 BLOCK_M 场景。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 7.1 模板分支 | `if constexpr (kCooperativeMode)` 控制 WG2 行为 | 两种模式均编译通过 | | 7.2 WG2 idle | WG2 在 `kCooperativeMode=false` 时 `warpgroup_reg_dealloc` 后 idle | 无 deadlock / launch failure | | 7.3 TMA A 调整 | TMA A 只加载 BLOCK_M 行(64 或 32)而非 128 | SMEM A 数据正确 | | 7.4 WGMMA 有效行 | `valid_m = min(tokens_in_block, BLOCK_M)` 控制 epilogue 写入范围 | 无 OOB 写入 | | 7.5 Scheduler 适配 | 动态 BLOCK_M 下 scheduler 的 block 分配和 pool_block_offset 正确 | 全量 expert 覆盖,无漏 tile | | 7.6 Heuristics JIT 分发 | 根据 `expected_tokens_per_expert` 选择 BLOCK_M,JIT 编译对应模板 | 正确选择 config 并编译 | | 7.7 Small M correctness | BLOCK_M=64: 单 WG 处理完整 64 行 | Smoke test 通过 | | 7.8 Extreme decode | BLOCK_M=32: token-per-expert=1-2 | 极端 decode 场景输出正确 | **验证里程碑**:BLOCK_M=64 和 BLOCK_M=32 的 Smoke test 通过。动态 BLOCK_M 选择逻辑根据 token count 正确切换。 **依赖**:Phase 6 完成(cooperative 端到端已验证) **关键挑战**: - BLOCK_M=32 时 WGMMA 计算 64 行但只用 32 行的 register 浪费 - Single-WG mode 下 WG2 的 named barrier arrive 需要调整(WG2 不参与 epilogue barrier) --- ### Phase 8: 多 Rank 集成测试 (Day 56-62) **目标**:在 8×H200 集群上验证完整 multi-rank 正确性和性能。 | 步骤 | 交付物 | 验证标准 | |------|--------|----------| | 8.1 Multi-rank launch | 8-rank torchrun 启动 MegaMoE kernel | 无 hang / crash | | 8.2 Correctness sweep | Token sweep (1-8192) × Shape sweep × BLOCK_M 三档 | 全部 `allclose` 通过 | | 8.3 Edge cases | 0 tokens, unbalanced routing, single-expert-per-token | 无 crash,输出正确 | | 8.4 Benchmark framework | `bench_mega_moe_sm90.py`:latency, TFLOPS, HBM GB/s | 可生成 performance table | | 8.5 nsys timeline | Phase profile: dispatch / L1 / L2 / combine 各阶段时间 | 识别主要 stall 来源 | | 8.6 DeepEP v1 baseline | 对比 DeepEP dispatch + grouped GEMM + combine | 输出 speedup ratio | | 8.7 Regression test | CI 集成到 `tests/test_mega_moe_sm90.py` | `pytest` 通过 | **验证里程碑**:8-rank × DSV4 Flash shape (4096×2048, E256, topk6):correctness 通过 + fused latency < 3000us (128 tokens)。 **依赖**:Phase 7 完成 --- ### Phase 9: 性能优化 (Day 63-75) **目标**:基于 nsys timeline 分析,针对瓶颈进行优化迭代。 | 步骤 | 优化方向 | 预期收益 | 依据 | |------|---------|---------|------| | 9.1 N-major scheduling tuning | 调整 `tokens/expert >= 256` 阈值 | 大 M 下 weight L2 cache 命中率提升 | NCU L2 sector miss 数据 | | 9.2 SFA 软件 prefetch | L2 per-64 SFA 的 `__ldg` prefetch 提前一个 K-block | 减少 math stall waiting on SF | nsys timeline 中 SF load 占比 | | 9.3 Combine chunk 大小调优 | 根据 hidden dim 选择 1 vs 2 chunks | 减少 combine 阶段 TMA round-trip | combine 占总时间比例 | | 9.4 Dispatch pull 优化 | 增加 dispatch warp 内的 token parallelism | 减少 dispatch phase 时间 | nsys dispatch 耗时 | | 9.5 Register spill 消除 | 检查 SASS 中的 local memory access,调整变量生命周期 | 消除 spill 带来的 DRAM 流量 | cuobjdump --dump-sass | | 9.6 wave_count 调优 | 根据实测 per-shape 最优 experts_per_wave | 减少 tail effect | benchmark sweep | | 9.7 Optional: cluster=2 探索 | TMA multicast B tile(需解决 amax 跨 CTA sync) | weight HBM 进一步减半 | 若 cooperative 的 HBM 仍是瓶颈 | **验证里程碑**:相对 Phase 8 baseline,主要 shape 上 latency 降低 10-20%。 --- ### Phase 依赖关系图 ``` Phase 0 (环境) │ v Phase 1 (基础设施) │ v Phase 2 (Dispatch) │ v Phase 3 (L1 TMA + WGMMA) │ v Phase 4 (L1 Epilogue) │ v Phase 5 (L2 GEMM + Scatter) │ v Phase 6 (Combine + 端到端) │ ├──────────────────┐ v v Phase 7 (Single-WG) Phase 8 (多 Rank 集成) │ │ └──────┬───────────┘ v Phase 9 (性能优化) ``` ### 时间线总结 | Phase | 天数 | 累计 | 核心交付 | |-------|------|------|---------| | 0 | 3 | 3 | 环境 + baseline | | 1 | 7 | 10 | 基础设施 (可 JIT 编译) | | 2 | 5 | 15 | Dispatch + L1 pool 正确 | | 3 | 5 | 20 | L1 TMA + WGMMA 正确 | | 4 | 10 | 30 | L1 SwiGLU + FP8 quant 正确 | | 5 | 10 | 40 | L2 GEMM + NVLink scatter 正确 | | 6 | 8 | 48 | 端到端 fused kernel 正确 (2-rank) | | 7 | 7 | 55 | 动态 BLOCK_M 全变体正确 | | 8 | 7 | 62 | 8-rank 集成 + benchmark | | 9 | 13 | 75 | 性能调优完成 | **总工期**:约 75 工作日(10-11 周),含充分的验证和调试时间。关键路径是 Phase 2-6 的串行 kernel 开发。