Commit Graph

1 Commits

Author SHA1 Message Date
Maybe_Jayden 79f2211f1f
[Perf][MoE] Grouped-GEMM TMA OOB zero-fill + unpermute out=: eliminate redundant copies; add fair baseline benchmark (#1580)
## Summary

Supersedes #1567. Bundles the fair grouped-GEMM baseline benchmark with
a
cleaner redundant-copy elimination in the routed-MoE experts pipeline,
replacing
the `a_aligned`/`a_padded` "skip A-pad on aligned M" mechanism from
#1567 with
SM90 TMA out-of-bounds (OOB) hardware zero-fill — which works for
**arbitrary**
(unaligned) routing, not just block-aligned, and drops the per-call host
sync.

### Grouped-GEMM kernel (`GroupedGemmPersistent3WGKernel`)
- Declare `A_shape = (numel, K)` unconditionally in both templates
(pingpong /
cooperative). The last expert's partial-tile A over-read past `numel` is
hardware zero-filled by TMA (descriptor globalDim = numel); the existing
  partial-tile epilogue masks the store. No `F.pad`, no guard rows, no
  `torch.all` alignment host sync.
- Keep the `out=` parameter (reuse an output buffer across calls) and
its
  validation. GEMM math is untouched.

### Unpermute (`MoeUnpermuteKernel` / `MoeUnpermuteFwdOp`)
- Add `out=` so callers can write into a pre-allocated buffer.
- Fold `routed_scaling_factor` into the prim_func (scale before
cast/store).
- `fused_routed_expert.forward` now writes `output` directly from
unpermute and
  drops the trailing `output.copy_` / `torch.mul`.

### Benchmark
- Fair grouped-GEMM baseline benchmark, framework-ified (workload +
roofline
subclass + parametrized test), with torch / triton / triton-tma /
deepgemm /
vLLM baselines and per-call `torch.cuda.synchronize()` so CUPTI times
async
  baselines correctly.

## Effect

Eliminates two grouped-GEMM `F.pad` copies + one `output.copy_` (~1.3 GB
DRAM /
forward on DeepSeek-V3 prefill) and the per-call device->host sync.
Compute-bound
pipeline, so the wall-clock win is modest but real and reproducible —
measured
under synchronized, interleaved A/B (experts forward):

| workload | before | after | delta |
| --- | --- | --- | --- |
| qwen3 decode (T=512, E=128)  | 3.401 ms | 3.219 ms | -5.3% |
| qwen3 prefill (T=4096, E=128) | 7.226 ms | 6.778 ms | -6.2% |
| deepseek decode (T=512, E=256)  | 6.455 ms | 6.318 ms | -2.1% |
| deepseek prefill (T=4096, E=256) | 10.011 ms | 9.465 ms | -5.5% |

Every shape's slowest after-sample beats its fastest before-sample (no
overlap).
The aligned grouped-GEMM baseline bench shows no change (old aligned
path and new
OOB path compile the same `A_shape`), as expected.

## Test Plan

- [x] `tests/kernels/test_grouped_gemm_persistent_3wg.py` — 21 passed
(incl. new `test_no_host_pad_on_unaligned`; partial-tile + cooperative
      correctness exercise the OOB path on both templates)
- [x] `tests/ops/test_moe_unpermute.py` — 11 passed (incl. new `out=` +
scaling)
- [x] `tests/ops/test_fused_moe_experts.py` — 33 passed (fused/unfused
parity)
- [x] end-to-end `routed_scaling_factor=2.827` linearity verified
(max_err 6e-6)
- [x] `benchmarks/ops/bench_grouped_gemm_baselines.py` runs without the
dropped
      `a_aligned` arg (4 passed / 8 OOM-skipped)

## Benchmark results — grouped-GEMM baselines (all cases, TFLOPS)

H200, 132 SM, bf16. Per-case grouped GEMM `C[e] = A[e] @ B[e]^T` across
all
`bench_grouped_gemm_baselines.py` CASES, comparing the 3WG persistent
kernel
(`tileops`) against torch / Triton / Triton-TMA / DeepGEMM. Higher is
better.

Numbers were captured per case in isolated processes: the single-process
sweep
intentionally OOM-skips the largest cases (it holds every baseline's
buffers at
once), so the `T=262144` (M=8192) cases only fit when run alone. `—`
marks a
baseline that skips a case on its own constraints (DeepGEMM requires
specific
shape alignment).

| case | M | N | K | tileops | torch | triton | triton-tma | deepgemm |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| GLM-5-744B up T=32768 | 1024 | 4096 | 6144 | **599.8** | 601.9 | 468.9
| 567.5 | 650.5 |
| GLM-5-744B up T=65536 | 2048 | 4096 | 6144 | **607.6** | 625.9 | 478.7
| 605.7 | 667.5 |
| GLM-5-744B up T=131072 | 4096 | 4096 | 6144 | **614.8** | 607.6 |
487.5 | 624.9 | 662.1 |
| GLM-5-744B up T=262144 | 8192 | 4096 | 6144 | **612.7** | 599.5 |
490.4 | 630.4 | 657.1 |
| GLM-5-744B down T=32768 | 1024 | 6144 | 2048 | **564.6** | 588.0 |
437.7 | 549.2 | — |
| GLM-5-744B down T=65536 | 2048 | 6144 | 2048 | **596.3** | 607.9 |
442.7 | 591.8 | 649.6 |
| GLM-5-744B down T=131072 | 4096 | 6144 | 2048 | **603.0** | 603.0 |
445.2 | 606.7 | 648.6 |
| GLM-5-744B down T=262144 | 8192 | 6144 | 2048 | **602.9** | 586.8 |
445.9 | 612.5 | 635.0 |
| Llama4-128E up T=131072 | 1024 | 16384 | 5120 | **549.9** | 622.9 |
436.0 | 550.3 | 649.9 |
| Llama4-128E down T=131072 | 1024 | 5120 | 8192 | **604.8** | 632.6 |
466.3 | 586.2 | — |
| qwen3.5-397B up T~52429 | 1024 | 2048 | 4096 | **613.5** | 582.0 |
449.3 | 558.4 | 640.7 |
| qwen3.5-397B down T~52429 | 1024 | 4096 | 1024 | **575.8** | 524.2 |
408.9 | 459.2 | 614.0 |

Reading: 3WG (`tileops`) lands within a few percent of cuBLAS (`torch`)
and
Triton-TMA, ~5–10% behind DeepGEMM, and well ahead of the non-TMA Triton
path,
across all 12 shapes. Triton baselines are autotuned per case (the
autotune
cache is cleared between cases so each shape gets its own config).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: jieneng.yu <1033160740@qq.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 17:52:25 +08:00