Repository navigation
Conversation
Add fuse-gate mode to topk_softmax so the shared-expert sigmoid gate is computed inside the routed softmax top-k kernel, exposing the new hidden_states/gate_weight/shared_expert_scale/shared_expert_base args through the pybind registration. Adds an op_test with a perf+correctness sweep. Co-Authored-By: Claude <noreply@anthropic.com>
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags & labels: |
Satisfies the pinned ruff 0.16.0 (I001, UP045) and black CI style jobs; no functional change. Co-Authored-By: Claude <noreply@anthropic.com>
…perts) The gate-staging __shared__ buffer was sized NUM_SHARED_EXPERTS * 4096 * sizeof(DTYPE); for fp32 with 8 shared experts that is 128KB, exceeding the 64KB LDS limit and failing the PREBUILD_KERNELS build (only surfaces there, not in JIT op_tests). Gate the LDS staging on a compile-time fit check: when the buffer would exceed 64KB, size it to 1 and read the gate from global instead. Result is identical; only the caching optimization is skipped for that combo. Qwen (bf16, 1 shared expert) still stages into LDS unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
|
Hi I'd like to understand the baseline this is being compared against, because downstream the shared-expert gate already appears to be folded into the router GEMM. self.gate = MergedReplicatedLinear(
config.hidden_size,
[config.num_experts, config.n_shared_experts], # routed | shared, one weight
bias=False, quant_config=None, ...)
...
logits = self.gate(hidden_states) # single GEMM -> [M, E + S]
router_logits = logits # trailing columns are the shared gate logit
If there's no case the merged gate can't cover, I'd rather not add a second way to do this: Happy to go through the kernel-level findings (static LDS sizing, vector-load alignment vs. |
Address review on ROCm#5652: instead of extending topk_softmax (which added an unconditional s_gate LDS buffer that penalized existing/legacy shared-expert callers), add a standalone op topk_softmax_fused_shared_gate with its own kernel + dispatch. topk_softmax and its kernel are reverted to base verbatim, so non-fuse-gate callers carry no LDS penalty. - New: topkGatingSoftmaxFusedGate kernel + launcher + LAUNCH_SOFTMAX_FUSED* macros + applySharedExpertSigmoidFusedGate + dispatch + host op topk_softmax_fused_shared_gate (+ moe_op.h decl, rocm_ops.hpp pybind, moe_op.py wrapper). - op_test: separate fused / unfused unit benchmarks for fused-vs-not-fused perf. - Guards: require contiguous hidden_states/gate_weight; meaningful index-width check; fix the scale-wording comment (sigmoid, then scale). Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks! please look at these:
|
- Alignment (UB): host op AITER_CHECKs 64B-aligned row stride; drop dead tail loop
- Mandatory scoring: require num_shared_experts > 0 and sigmoid (was silent no-op)
- Fuse-only kernel: remove dead legacy branch + redundant null/base guards
- token_expert_indices: size [num_tokens, topk] to match the kernel's stride
- Marker: use NUM_EXPERTS + NUM_SHARED_EXPERTS so a filtered slot can't alias shared expert 0
- Non-power-of-2 experts: reject via AITER_CHECK; delete slow serial GEMV helper
- Tests: real pytest test_correctness (S {1,2,8}, fp32, H=8192) + alignment/non-pow2 negatives
- Bad input now raises RuntimeError instead of abort()
Co-Authored-By: Claude <noreply@anthropic.com>
Thanks for the second review. Every point is fixed in the new op and kernel. Here is what changed,
|
The timed region is ~9 kernels, and most of them are artifacts of the torch plumbing topk_softmax(r_w, r_i, r_tei, gating, renorm) # 1 necessary
shared_w = torch.sigmoid(hs.float() @ gate_weight.float().t()) * scale # 2 upcasts + mm + sigmoid + mul
w[:, :topk] = r_w; w[:, topk:] = shared_w # 2 copies
ids[:, :topk] = r_i; ids[:, topk:] = shared_ids # 2 copies
Suggested rewrite (~4 kernels, and the sigmoid/scale/store could be folded further): w = torch.empty(tokens, total, dtype=dtypes.fp32) def fn():
|
- Make the unfused benchmark a fair baseline: write routed columns in place via strided views, run the gate GEMV in bf16 (no fp32 upcast), and precompute the input-independent shared ids outside the timed region. - Extend the token sweep to [1, 4, 16, 64, 256, 1024, 4096, 16384] to expose the decode->prefill crossover. - Add the missing test_correctness axes (need_renorm=False, scale!=1.0, num_experts=256, M=1/M=65 tail), plus a guarded M=0 no-op and its test. - Drop the dead softmax_workspace parameter from the fused op (C++ entry, moe_op.h, pybind, wrapper); the base topk_softmax keeps it. Co-Authored-By: Claude <noreply@anthropic.com>
|
please update your forked aiter |
Thanks for the detailed review. All 3 points are fixed and re-tested on 1) The unfused baseline was not a fair comparisonIt was true: the old
Small note: your snippet used Effect: the unfused baseline got faster (roughly 45 us -> 28 us at M = 64), so 2) The token (M) sweep was decode-onlyAgreed. The default sweep now runs What the data shows (Qwen shape: experts=512, hidden=4096, top_k=10,
*** PROVISIONAL — single run, REPLACE with medians before posting ***
The rows marked "unfused noisy" bounced between runs; treat the exact ratios as One honesty note for our own numbers: the UNFUSED column is noisy run-to-run at 3) Missing axes in test_correctnessThe test was pinned at tokens=64, experts=512, scale=1.0, renorm=True, which left
On M = 0: you asked whether it should be a guarded no-op. We decided yes. An |
Follow the aiter op_test standard (op_tests/test_quant.py): - Merge bench_unfused/bench_fused into one @benchmark fn with an {unfused, fused} candidates dict, so both paths are timed and compared in a single markdown table. - Correctness is the per-candidate err (checkAllclose vs a torch reference), now asserted (err == 0) so it runs AND gates under `python3 <file>` -- the way CI invokes op_tests. The previous pytest test_* functions were never executed in CI; they are removed. - Fold the extra correctness axes (num_experts=256, hidden=8192, fp32, num_shared=8, M=65) and the negative/guard behaviors (misaligned raise, non-power-of-2 raise, M=0 no-op) into main() so they run in CI too. Co-Authored-By: Claude <noreply@anthropic.com>
Put first-party aiter imports in their own group after third-party, so ruff's import-order check passes. Co-Authored-By: Claude <noreply@anthropic.com>
Other aiter op_tests (e.g. op_tests/test_quant.py) record the per-candidate err in the table and let checkAllclose log mismatches; they do not assert on numerical err. Follow that convention: correctness still runs under `python3 <file>` (CI) and the err is reported in the table and logs, but a numerical mismatch no longer hard-fails via exit code. The guard asserts (misaligned / non-power-of-2 raise, M=0 no-op) stay, since they check raise/no-op behavior. Co-Authored-By: Claude <noreply@anthropic.com>
… experts (#6125) Add tuned rows for DeepSeek-V4.1-Flash routed experts sharded across TP (model_dim 5120, per-rank inter 640 at TP4 / 1152 at TP2; target 384x6 and DSpark draft 128x3; tokens 1..32768) with MXFP8 activations on interleaved gate/up rows (A8W4), the precision the checkpoint's reference runs. The default tuner sweep (TUNE_ONLY=flydslv2) only generates the fused-fp8 stage1 + moe2_layout family. At 32-64 tokens that family loses to the heuristic's bf16-out `_gui` stage1 + non-layout stage2 by up to 1.25x, and a v2-only table raised V4.1 TP4 decode TPOT from 8.2 ms to 12.8 ms. The rows here take, per shape, the faster of a v2 sweep and a TUNE_ONLY=flydsl sweep (v1 family). Each row was then checked against the heuristic on the production fused_moe op. The 7 shapes where the tuned pick measured >3% slower there carry the heuristic's own kernel pair instead; their us is the measured op time and us1/us2/err are left empty, since the tuner never timed that pair. End to end (V4.1 TP4, DSpark-5, ISL/OSL 1024/512, conc 8): 2597 vs 2552 tok/s for the heuristic, within run-to-run spread. Also fix a build error from #5652 in topk_softmax_kernels.cu: `__shared__ alignas(64) T x[]` is rejected by ROCm 7.1.1's clang ("'alignas' attribute cannot be applied to types"), which breaks module_moe_asm and with it the MoE tuner. Use `__attribute__((aligned(64)))`, as elsewhere in csrc.



Add fuse-gate mode to topk_softmax so the shared-expert sigmoid gate is computed inside the routed softmax top-k kernel, exposing the new hidden_states/gate_weight/shared_expert_scale/shared_expert_base args through the pybind registration. Adds an op_test with a perf+correctness sweep.
Motivation
Technical Details
Test Plan
Test Result
Submission Checklist