Skip to content

Moe reduce extract - #4596

Merged
coderfeli merged 10 commits into
mainfrom
moe-reduce-extract
Aug 6, 2026
Merged

Moe reduce extract#4596
coderfeli merged 10 commits into
mainfrom
moe-reduce-extract

Conversation

@coderfeli

Copy link
Copy Markdown
Collaborator

Motivation

Technical Details

Test Plan

Test Result

Submission Checklist

coderfeli and others added 6 commits August 2, 2026 11:32
The topk-reduction kernel (compile_moe_reduction) lived in
moe_gemm_2stage.py alongside the int4 (a16wi4) stage1/stage2 GEMM
builders, but it is dtype-agnostic: it is the epilogue for stage2
mode="reduce" for every dtype (fp8/a8w4/mxfp4/int4), reached via
moe_kernels._run_moe_reduction. Move it to its own module so it lives
independently of the int4 GEMM builders.

- New self-contained kernels/moe_reduce.py (own _if_then/_if_else SCF
  helpers, no back-reference to moe_gemm_2stage).
- moe_kernels._run_moe_reduction now imports from .kernels.moe_reduce
  (the live production path).
- moe_gemm_2stage.py imports compile_moe_reduction back so the (unused)
  compile_moe_gemm2_ex still resolves; int4 gemm1/gemm2 untouched.

Cleanup on the moved kernel: drop the `if True:` scaffold, the unused
i8_type() helper, and two dead no-op statements (get_hip_arch(),
ir.ShapedType.get_dynamic_size()); trim step-narration comments while
keeping the load-bearing rationale (64-bit base-offset fold, unsigned
Index->ult, EP gather). Behavior-preserving: fx.Index, buffer_ops, and
the scf.IfOp control flow are kept as-is.

Verified on gfx950 (FLYDSL_RUNTIME_ENABLE_CACHE=0): _run_moe_reduction
matches a torch reference for f16/bf16/f32 x masked/unmasked, over both
the full-vector and tail (model_dim=4100) paths (max err <= 1.9e-6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the flydsl-kernel-cleanup skill to the extracted reduction kernel:
replace the raw scf.IfOp + _if_then/_if_else context-manager boilerplate
with idiomatic Python if/else on typed fx.Index compares (the current
FlyDSL idiom for guarded side-effects), and drop the now-unused
contextlib / _mlir.ir / _mlir.dialects.scf imports and the two helpers.
Also inline the trivial compute_type()/i32_type() shims (-> T.f32/T.i32).

Kept as-is (load-bearing, per the skill's "behavior-preserving" rule):
- fx.Index for the bounds compares -- unsigned, lowers to ult; the guards
  rely on it.
- buffer_ops.create_buffer_resource_from_addr with the 64-bit base-offset
  fold -- a raw-addr + i64 base form with no make_buffer_tensor equivalent.

moe_reduce.py: 305 -> 222 lines.

Verified on gfx950: byte-identical results to the scf.IfOp version across
f16/bf16/f32 x masked/unmasked over full-vector and tail paths; and
perf-aligned (min-of-5, bf16 md=7168 topk=8): tok=32768 862.8->866.0us,
tok=8192 211.8->211.9us, tok=1024 19.8->19.6us -- all within noise, as
expected since Python-if lowers to the same scf.if.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gned

Replace the buffer_ops + fx.Index kernel with the FlyDSL layout API, and
close the perf gap that made the first layout attempt ~10% slower.

Layout API:
- fx.inttoptr per-token base + make_view + make_buffer_tensor (drops
  buffer_ops.create_buffer_resource_from_addr + manual buffer_load/store).
- TV tiling via logical_divide/slice; BufferCopy128b copy atoms into
  register fragments. No fx.Index (explicit fx.Int32/Int64).
- Descriptor sized to the token slab -> OOB-checked model_dim tail drops
  the scalar tail path and the token/col bounds guards.

Perf alignment (this was the crux; the naive layout port lost ~10% on this
latency-bound kernel at only ~55% of HBM peak, where instruction overhead is
not hidden):
- The topk rows are reached with a UNIFORM SCALAR soffset=k*model_dim via
  fx.copy(..., soffset=), so all topk loads share ONE per-thread voffset and
  issue back-to-back (scalar offsets land on the SALU). This removes the 7
  per-k v_add_u32 the naive port emitted before each load. ISA now: 8
  buffer_load_dwordx4 sharing v54 + s_mov soffsets, 0 v_add.
- Guard threads whose column group starts past model_dim (their loads would
  read the next row -- in-descriptor, wasted BW), matching base's tail skip.
  Emitted only when TILE does not divide model_dim (const_expr), so the
  common divisible case pays no branch.

Verified on gfx950 (FLYDSL_RUNTIME_ENABLE_CACHE=0): byte-exact vs the
buffer_ops version for f16/bf16/f32 x masked/unmasked over divisible,
whole-tile-tail (md=7168) and partial-vector-tail (md=4100) paths, plus
>4 GiB (70000 tokens, past the i32-voffset boundary). Perf min-of-6 vs the
buffer_ops baseline (bf16, topk=8): md=7168 aligned across tok=1024/8192/
32768 (within ~0.5%, faster at low tok); md=4096 within ~2% (a compiler
load-drain-granularity nuance, leaner ISA + faster at tok=1024).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Condense the module/function docstrings and inline comments to the
load-bearing "why" only (base fold, uniform-scalar soffset, OOB guard),
compact the compile_moe_reduction signature, drop the _view layout arg
(both callers pass make_layout(model_dim,1)), and inline one-shot
temporaries. Trace-time-only changes -> identical IR; 183->131 LOC.

Verified byte-exact via _run_moe_reduction (f16/bf16/f32 x masked/unmasked
over divisible / md=7168 / md=4100 tails + >4 GiB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the unused keyword defaults (both callers -- _run_moe_reduction and
compile_moe_gemm2_ex -- pass all five args explicitly) so the keyword-only
signature fits on one line under black's 88-col limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the compile_moe_reduction(...) closure factory + functools.lru_cache
with the idiomatic FlyDSL surface: a module-level @flyc.kernel
(moe_reduction_kernel) and @flyc.jit (moe_reduction) whose shape/dtype params
are Constexpr, so flyc specializes per shape. moe_reduce.py no longer has a
Python compile wrapper.

Dispatch: a direct @flyc.jit call re-runs ~34us of jit dispatch per call (4x
slower at tok=1/128 -- the decode path). So _run_moe_reduction caches the
compiled function per Constexpr set (_MOE_REDUCTION_CF) and fast-dispatches via
cf(*args), same mechanism _run_compiled used. Perf matches the old factory
(tok=1 ~11us, tok=32768 ~856us; byte-exact across f16/bf16/f32 x
masked/unmasked over the divisible / md=7168 / md=4100 tails + >4 GiB).

Also removes the now-dead reduce machinery orphaned by the extraction from
moe_gemm_2stage.py: MoeGemm2Mode, _MoeGemm2ReduceWrapper, compile_moe_gemm2_ex
(all unreferenced), plus the then-unused compile_moe_reduction / _run_compiled
imports there (-246 LOC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderfeli
coderfeli requested a review from a team August 6, 2026 06:38
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 4596 --add-label <label>

coderfeli and others added 4 commits August 6, 2026 07:02
…te-out

Resolves the #4596 conflict with main and ports main's new MXFP8 route-out
reduction feature onto the layout-API reduce kernel.

- moe_gemm_2stage.py: take the PR side (reduction lives in moe_reduce.py);
  keeps main's T.f8 -> default_f8_type() migration outside the conflict.
- moe_reduce.py: add dtype_str="fp8" branch + out_dtype_str param. Reduces the
  flat uint8 route-out rows [N fp8 | N/8 e8m0] -> bf16/f16, ported verbatim from
  main's _compile_moe_reduction_fp8 (same atoms/address math) but inlined into
  the cache-safe module-level @flyc.kernel (Constexpr-specialized, auto-name),
  not the factory form. Dense path byte-identical to the PR.
- moe_kernels._run_moe_reduction: fold main's is_fp8 handling (flat X,
  dtype_str=fp8, out_dtype_str) through the _MOE_REDUCTION_CF dispatch; thread
  out_dtype_str into the args tuple + cache key.

Validated on gfx950 (cache ON): fp8 output byte-exact vs main across fast/tail
paths, mask on/off, bf16/f16; dense path byte-exact vs torch; isolated dispatch
perf at parity with main (ratio 0.99-1.03).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the two separate reduce branches into a single make_tiled_copy /
partition_S/D body parametrized by a compile-time is_fp8 flag. Dense
(f16/bf16/f32) and fp8 route-out share buffer setup, the model_dim tiled copy,
the uniform-soffset topk load loop, the masked f32 accumulate and the
truncating store; only the input element/atom, row_stride and the decode step
(extf vs cvt_pk_f32_fp8 * e8m0) differ.

- Port both paths to fx.make_layout_tv + fx.make_tiled_copy + partition_S/D
  (layout-API tiled copy) instead of manual per-thread views.
- Drop the fp8 per-lane scalar tail: with model_dim % 8 == 0 (MX) and 8-aligned
  col_base, in-range threads always have full windows, so the col_base guard +
  descriptor store-drop cover the tail (fp8 ISA 153 -> 17 buffer insns).
- Use T.f32x2 for the cvt result; drop the _NUMERIC module global; trim comments.

Validated on gfx950 (cache ON): fp8 byte-exact vs main across fast/tail, mask
on/off, bf16/f16; dense byte-exact vs main all dtypes. Dense ISA byte-identical
to the pre-unification kernel (perf parity; measured deltas are shared-GPU
noise). 327 -> 212 LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…duction factory

Restore the compile_moe_reduction(**constexprs) factory + _run_compiled dispatch
(the codebase convention) instead of the module-level _MOE_REDUCTION_CF dict.

The factory is @functools.lru_cache'd and returns a distinct per-shape @flyc.jit
launcher, so _run_compiled's per-exe _cf cache stays correct across shapes. It
wraps the auto-named Constexpr moe_reduction_kernel, so it is cache-safe under
the production disk cache (main's old bug was the explicit-name @flyc.kernel
form, not the factory itself).

Verified on gfx950: dense byte-exact vs torch (bf16/f16/f32, mask on/off), fp8
deterministic, cache-safe across a cold second process (fresh + warm disk cache,
reversed shape order). Decode dispatch 3.6us (>= the dict's 4.5us); a bare jit
call is 28us.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderfeli
coderfeli requested a review from lalala-sh August 6, 2026 13:19
@coderfeli
coderfeli merged commit 633e3dd into main Aug 6, 2026
42 checks passed
@coderfeli
coderfeli deleted the moe-reduce-extract branch August 6, 2026 13:21
coderfeli pushed a commit to ROCm/FlyDSL that referenced this pull request Aug 7, 2026
…ion.py

Ports the stage2 topk-reduce (aiter ROCm/aiter#4596) onto the tiled-copy
layout API, replacing the legacy buffer_ops/scf.IfOp reduction.py.

New kernels/moe/moe_gemm_2stage/moe_reduce.py:
- make_layout_tv / make_tiled_copy / partition_S/D / zipped_divide /
  memref_load_vec, uniform soffset=k*row_stride, 64-bit base-ptr fold
  (voffsets stay i32-safe for X > 4 GiB).
- Unifies dense (f16/bf16/f32) + fp8 MXFP8 route-out ([N fp8 | N/8 e8m0])
  via fx.Constexpr params; fuses the EP gather
  valid = expert_mask[topk_ids[t,k]] != 0 (expert_mask + topk_ids, not a
  precomputed mask). New launcher takes fx.Pointer args.

gemm2.py: _MoeGemm2ReduceWrapper.__call__ now takes expert_mask/topk_ids and
dispatches the pointer launcher via tensor_shim._run_compiled + from_c_void_p;
compile_moe_gemm2_ex switches valid_mask=None -> use_mask: bool and passes
num_experts=experts. test_moe_reduce.py rewired to the new interface.

Verified: 8/8 non-large test_moe_reduce cases pass on GPU (dense + masked
EP-gather); fp8 path traces/compiles/runs. Bandwidth on par with the legacy
kernel (~5 TB/s on prefill shapes; ~15% lower latency on small decode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant