Skip to content

Add an opt-in a4w4 SiTUv2 MoE path and fix three SiTUv2 tuner defects - #4463

Merged
XiaobingSuper merged 7 commits into
mainfrom
k3/situv2-a4w4-moe
Aug 3, 2026
Merged

Add an opt-in a4w4 SiTUv2 MoE path and fix three SiTUv2 tuner defects#4463
XiaobingSuper merged 7 commits into
mainfrom
k3/situv2-a4w4-moe

Conversation

@XiaobingSuper

@XiaobingSuper XiaobingSuper commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Adds a third activation dtype for SiTUv2 (Kimi-K3) MoE — a4w4, behind AITER_SITUV2_A4W4=1 — next to the existing AITER_SITUV2_A8W4. The default is unchanged (a16w4), so this is inert unless the flag is set.

if activation == ActivationType.Situv2:
    if   AITER_SITUV2_A8W4 == "1": q_dtype_a = fp8      # a8w4  (existing)
    elif AITER_SITUV2_A4W4 == "1": q_dtype_a = fp4x2    # a4w4  (new)
    else:                          q_dtype_a = bf16     # a16w4 (default, unchanged)

The SiTUv2 test is hoisted above the Swiglu/INTERLEAVE branch so that branch cannot claim it and pick the activation dtype itself. All existing a16w4 routing and configs are left untouched.

Performance

TP8 shape (3584 x 384, 896 experts, topk 16), tuner us1 + us2:

token a16w4 a8w4 a4w4 vs a16w4 vs a8w4
1 28.3 20.1 20.0 +29.2% +0.4%
4 42.7 33.7 32.9 +23.1% +2.5%
16 107.3 93.0 91.2 +15.0% +1.9%
64 260.6 229.1 225.9 +13.3% +1.4%
256 374.7 329.8 325.3 +13.2% +1.4%
1024 422.5 365.7 349.9 +17.2% +4.3%
2048 651.3 475.8 428.2 +34.2% +10.0%
4096 1087.3 774.4 653.2 +39.9% +15.7%
8192 1994.4 1249.4 1020.3 +48.8% +18.3%
16384 3600.3 2074.8 1716.5 +52.3% +17.3%

a4w4 is ahead of a16w4 at every tier. Against a8w4 the small tiers land together — they are weight-bandwidth bound and the weights are fp4 either way, so the activation dtype only moves a few KB against tens of MB of weights — and the gap opens up once the A traffic and the MFMA rate start to matter.

Numbers are isolated kernel timings from the tuner, not end-to-end throughput.

Three tuner defects

Each of these mis-scored candidates silently rather than failing loudly, and each had to be fixed before this shape could be tuned at all.

1. SiTUv2 activation params never reached either side. torch_moe_stage1 fell back to its (2.0, 1.5) defaults while the FlyDSL kernels used (1.0, 1.0) — reference and kernel were evaluating different activation functions. Every SiTUv2 stage1 candidate came back at ~35% err, all were dropped by --errRatio, and the run aborted with:

Error: please check errRatio, stage1 and stage2 should be valid together!

Passing Kimi-K3's (4.0, 25.0) to both takes err1 from 33.4% → 0.0%. This is not a4w4-specific: the existing kimik3_fp4_tuned_fmoe.csv (a16w4) carries err1 ~34.7% for the same reason.

2. s1_compare_fn was only set for fp8. Fused fp4 stage1 fell through to the generic comparison, which reads a 1-byte dtype as raw bytes. fp4x2 packs two e2m1 codes per byte, so a low-nibble difference moves the byte by 1 and a high-nibble one by 16. All 4352 fused a4w4 candidates scored 100% err and none was ever selected — the tuner had been picking non-fused stage1 for a4w4 and paying for a separate quant kernel.

3. cosine_diff_compare faults the GPU on fp4x2. It starts with ref.double(), and

torch.randint(0, 255, (64,), dtype=torch.uint8, device="cuda").view(dtypes.fp4x2).double()
# HIP error: unspecified launch failure

64 elements is enough. Enabling that comparison for fp4 in (2) therefore crashed workers and restarted the pool instead of scoring anything. Fixed by decoding the nibbles into their represented values, which also avoids the packed-byte comparison from (2).

Also: skip k_batch / k_wave splits that do not divide the K axis at enumeration time. compile_mixed_moe_gemm1 rejects them anyway, but only after dispatch, and on some shapes the launch faults the queue rather than raising.

Config

kimik3_a4w4_{un,}tuned_fmoe.csv, 17 token tiers (1 … 32768). All 17 select the fused stage1 kernel (previously 0, because of defect 2), and err1/err2 are ≤0.3% everywhere.

The 32768 tier is included because get_padded_M maps everything in (16384, 32768] onto that key, and the tier fallback only fires strictly above it — without the row those batches silently drop to the hardcoded heuristic.

Validation

gfx950 TP8. All 17 token tiers hit the tuned rows at runtime, and the server log shows the fused (_fp4) kernel names on every tier with no heuristic fallback.

GSM8K 3-shot under the CI settings (baseline 0.95, threshold 0.94), three runs: 0.9484 / 0.9484 / 0.9522.

@XiaobingSuper
XiaobingSuper requested review from a team and Copilot July 30, 2026 12:25
@github-actions

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 4463 --add-label <label>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in SiTUv2 MoE activation-quantization route (a4w4) and aligns the SiTUv2 tuner’s reference numerics with the kernel launch by consistently forwarding situ_beta / situ_linear_beta.

Changes:

  • Adds Kimi-K3 SiTUv2 beta parameters to both FlyDSL stage1 launches and torch reference calls in the MoE tuner.
  • Updates fused_moe_ dispatch to prioritize SiTUv2 activation routing (including new AITER_SITUV2_A4W4) ahead of the Swiglu/INTERLEAVE branch.
  • Adds new Kimi-K3 a4w4 tuned/untuned CSV configs for MoE 2-stage dispatch.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
csrc/ck_gemm_moe_2stages_codegen/gemm_moe_tune.py Threads SiTUv2 beta parameters into tuner reference + kernel launch to avoid mismatched activation functions.
aiter/fused_moe.py Adds opt-in a4w4 SiTUv2 activation dtype routing and adjusts dispatch ordering so SiTUv2 isn’t claimed by the INTERLEAVE branch.
aiter/configs/model_configs/kimik3_a4w4_untuned_fmoe.csv Introduces untuned rows for the new a4w4 SiTUv2 path.
aiter/configs/model_configs/kimik3_a4w4_tuned_fmoe.csv Introduces tuned kernel selections/latencies for the new a4w4 SiTUv2 path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread csrc/ck_gemm_moe_2stages_codegen/gemm_moe_tune.py
Comment thread aiter/fused_moe.py
Comment thread aiter/fused_moe.py
Comment thread aiter/configs/model_configs/kimik3_a4w4_tuned_fmoe.csv Outdated
@XiaobingSuper XiaobingSuper changed the title Add an opt-in a4w4 SiTUv2 MoE path and fix the SiTUv2 tuner reference Add an opt-in a4w4 SiTUv2 MoE path and fix three SiTUv2 tuner defects Jul 30, 2026
@zufayu
zufayu self-requested a review July 31, 2026 01:18
@zufayu
zufayu requested review from lalala-sh and removed request for zufayu July 31, 2026 03:39
Comment thread aiter/fused_moe.py
Comment thread aiter/configs/model_configs/kimik3_a4w4_untuned_fmoe.csv
SiTUv2 (Kimi-K3) only had a16w4 and a8w4. Add a4w4 behind AITER_SITUV2_A4W4=1,
alongside the existing AITER_SITUV2_A8W4, with its own tuned config. The
default stays a16w4, so nothing changes unless the flag is set. Select SiTUv2
before the Swiglu/INTERLEAVE branch so that branch cannot claim it and pick the
activation dtype itself.

Tuning this shape needed three fixes in the tuner, each of which silently
mis-scored candidates rather than failing loudly:

1. situ_beta / situ_linear_beta were passed to neither the torch reference nor
   the kernel, so torch_moe_stage1 used its (2.0, 1.5) defaults while the
   FlyDSL kernels used (1.0, 1.0) -- the two sides were evaluating different
   activations. Every SiTUv2 stage1 candidate came back at ~35% err and was
   dropped by --errRatio, leaving stage1 with zero valid candidates and
   aborting with "stage1 and stage2 should be valid together". Pass Kimi-K3's
   (4.0, 25.0) to both; err1 goes 33.4% -> 0.0%. This also applies to the
   existing a16w4 config, whose err1 column reads ~34.7%.

2. s1_compare_fn was only set for fp8, so fused fp4 stage1 fell back to the
   generic comparison, which reads a 1-byte dtype as raw bytes. fp4x2 packs two
   e2m1 codes per byte, so that rejected candidates which were >99% code
   identical -- all 4352 fused a4w4 candidates scored 100% err and none was
   ever selected.

3. cosine_diff_compare starts with ref.double(), and fp4x2.double() faults the
   queue (HIP unspecified launch failure) -- a 64-element tensor is enough.
   Enabling it for fp4 in (2) therefore crashed workers and restarted the pool
   instead of scoring anything. Decode the nibbles into their represented
   values instead, which also avoids comparing two packed codes as one byte.

Also skip k_batch / k_wave splits that do not divide the K axis at enumeration
time. compile_mixed_moe_gemm1 rejects them anyway, but only after dispatch, and
on some shapes the launch faults the queue rather than raising.

kimik3_a4w4_{un,}tuned_fmoe.csv cover the TP8 shape (3584 x 384, 896 experts,
topk 16) over 17 token tiers, 1 through 32768. All 17 select the fused stage1
kernel and err1/err2 are <=0.3% everywhere. The 32768 tier matters because
get_padded_M maps everything in (16384, 32768] onto that key, and the tier
fallback only triggers above it, so without the row those batches drop to the
hardcoded heuristic.

Against the existing configs (us1+us2, us):

    token      a16w4     a8w4     a4w4    vs a16    vs a8
        1       28.3     20.1     20.0    +29.2%    +0.4%
       16      107.3     93.0     91.2    +15.0%    +1.9%
      256      374.7    329.8    325.3    +13.2%    +1.4%
     2048      651.3    475.8    428.2    +34.2%   +10.0%
    16384     3600.3   2074.8   1716.5    +52.3%   +17.3%

Small tokens are weight-bandwidth bound and the weights are fp4 either way, so
a4w4 and a8w4 land together there; the gap opens up once the A traffic and the
MFMA rate start to matter.

Verified on gfx950 TP8: all 17 tiers hit the tuned rows at runtime with no
heuristic fallback, and GSM8K 3-shot under the CI settings gives
0.9484 / 0.9484 / 0.9522 (baseline 0.95, threshold 0.94).
lalala-sh
lalala-sh previously approved these changes Jul 31, 2026
Mirror fused_moe's AITER_SITUV2_A4W4 routing in the test helper and route
SiTUv2 through the existing CSV runtime-mode skip so kimik3_a4w4 tuned rows
are not validated against the default a16w4 dispatch in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
XiaobingSuper and others added 4 commits July 31, 2026 03:19
Keep AITER_SITUV2_A4W4 routing only in _runtime_situv2_mxfp4_q_dtype_a,
matching how AITER_SITUV2_A8W4 was handled originally.

Co-authored-by: Cursor <cursoragent@cursor.com>
Route Situv2 through _runtime_situv2_mxfp4_q_dtype_a in _iter_csv_cases
so opt-in a4w4/a8w4 tuned rows are not validated against the default
a16w4 dispatch, without overloading the Swiglu runtime helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
_runtime_situv2_mxfp4_q_dtype_a checked gate_mode == INTERLEAVE before the
AITER_SITUV2_A8W4 / AITER_SITUV2_A4W4 opt-ins, mirroring the Swiglu helper it
was derived from. fused_moe tests SiTUv2 ahead of the Swiglu/INTERLEAVE branch
precisely so that branch cannot claim it, so the two disagreed whenever the
inferred gate mode was INTERLEAVE: the helper answered bf16/fp8 while dispatch
selected fp4x2, and _iter_csv_cases dropped those rows as a mode mismatch. The
opt-in a4w4/a8w4 rows were then skipped instead of validated, and CI stayed
green without having exercised them.

Test SiTUv2 first, as fused_moe does. gate_mode and token no longer enter into
it, so drop both parameters -- gate_mode is doubly unwelcome here because it is
inferred from the dtype layout rather than read from the CSV.

Checked against fused_moe's logic over
{default, A4W4=1, A8W4=1} x {SEPARATED, INTERLEAVE} x {token 64, 4096}:
4 of 12 combinations disagreed before, 0 after.
The branch-order fix in 674f144 started running SiTUv2 rows under
gate_mode=INTERLEAVE, which had never executed before. All 32 a16w4 rows
fail by ~99%: _effective_gate_mode requests INTERLEAVE while
get_flydsl_stage1_kernels binds gate_mode="separated" for every non-fp8
activation. Forcing SEPARATED does not help either, so leave these rows
out rather than claim a16w4 is validated; the a8w4 and a4w4 coverage the
ordering fix is for is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@XiaobingSuper
XiaobingSuper merged commit 4045b2d into main Aug 3, 2026
42 checks passed
@XiaobingSuper
XiaobingSuper deleted the k3/situv2-a4w4-moe branch August 3, 2026 07:32
yanboshao added a commit that referenced this pull request Aug 4, 2026
…to updated main

Reapply the gfx1250 fused-MoE ep_scatter feature (combine_mode="scatter_fused":
gemm2's TDM epilogue P2P-writes each route-weighted output row into peers'
comb_inp, so combine just sums -- no gather-reduce) onto the updated origin/main,
which meanwhile landed #4482 (real SiTUv2 + >512-expert chunked psum scan),
#4527 (LDS API refactor: lds_*_raw -> make_lds_copy_ops) and #4463 (fused_moe
SiTUv2 path). Squashed to a single commit.

Per-file resolution:
- dispatch_combine_v2/*: vendored cco-LSA v2 intranode dispatch/combine op-layer.
- mxfp4_preshuffle_gfx1250_tdm.py: on upstream's make_lds_copy_ops LDS API +
  SiTUv2 epilogue, add the TDM gather-store ep epilogue (tdm_scatter with
  in-kernel global_view/lds_view). tdm_scatter is vendored locally
  (tdm_gather_shim.py, on the stock FlyDSL wheel's low-level TDM intrinsics)
  so this branch needs no FlyDSL-side patch; route weight hoisted per wm row (_wf_rows).
- moe_contiguous_psum.py: adopt upstream's chunked scan (E>512 correct) for the
  non-EP remap; keep the multi-block grid-stride remap + ep_rowmap kernels for EP.
- grouped_moe_gfx1250.py: keep upstream SiTUv2 (stage1_act=3, situ_beta); add the
  ep_scatter dispatch wiring (ep_rowmap build, _ep_gemm2_kwargs, ep_scatter return).
- batched_gemm_mxfp4.py / fused_moe.py: thread both situ_* and ep_* params.
- tuned_grouped_fmoe.csv: tuning points (99 rows).

Dropped only the full-subtile PF prefetch pipeline (da8d794): loaders and
lds_addr_keepalive stay at upstream. lalala-sh's ds-read hoist is kept.

gfx1250-only; not run in this environment. Compile-verify the LDS-API migration +
ep/SiTUv2 epilogue and re-run test_mega_moe --combine on hardware.

Co-authored-by: lalala-sh <Jiaxing.Wen@amd.com>
Co-authored-by: zhimding <zhimding@amd.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.

3 participants