[FlyDSL] Support SiTUv2 in the packed-int4 MoE stage1 epilogue - #4471
[FlyDSL] Support SiTUv2 in the packed-int4 MoE stage1 epilogue#4471maeehart wants to merge 4 commits into
Conversation
Kimi-K3 needs its packed-int4 MoE stage-1 path to apply SiTUv2 instead of silently using SiLU. Thread the activation parameters to both non-split epilogues, reject unsupported split-K activation handling, and allow the packed-int4 tuner path outside gfx950. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit b5014e1)
SiTUv2 beta values change the generated activation code. Include the activation mode and exact beta pair in the persistent module name so the FlyDSL cache cannot reuse a binary with different semantics. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit e9c9147)
Keep the activation regression independent of stage2 accumulation and report-formatting dependencies so gfx942 failures identify the stage that produced them. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit b9c58fd)
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
The gfx942 path requantized Kimi-K3's MXFP4 experts to groupwise int4 whenever the hardware matched, without the user asking. The conversion is lossy, so it is now opt-in through --quantization-config.moe.weight int4 and gfx942 keeps the native MXFP4 path otherwise. Also refuse to load when the installed AITER predates ROCm/aiter#4471. Before that fix the packed-int4 stage1 dropped the requested activation and hardcoded SiLU, so Kimi-K3 served fluent text while computing SiLU instead of the SiTUv2 its config asks for. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes incorrect activation behavior in the FlyDSL packed-int4 (a16wi4) MoE stage1 epilogue by threading activation selection (SiLU vs SiTUv2) and SiTUv2 beta parameters through the int4 stage1 compile path, and by ensuring the compiled module name uniquely encodes activation + betas to prevent kernel-cache collisions.
Changes:
- Add
act,situ_beta, andsitu_linear_betatocompile_moe_gemm1, implement SiTUv2 epilogue math, and route both epilogue sites through a shared activation helper. - Forward activation + beta parameters through the int4 branch of
compile_flydsl_moe_stage1, and update the FlyDSL kernel module naming to include activation/betas (preventing cache collisions). - Expand/adjust tests to cover SiTUv2 routing for packed-int4 and to regression-test module-name discrimination.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| op_tests/test_moe_2stage.py | Updates per_1x32 gfx gating and expands SiTUv2 coverage to include packed-int4 cases; adjusts SiTUv2 default sweep beta handling. |
| op_tests/flydsl_tests/test_flydsl_moe_module_name.py | Adds regression test ensuring stage1 module naming distinguishes activation and beta pairs and is wired into the kernel decorator. |
| op_tests/flydsl_tests/test_flydsl_moe_a16wi4.py | Adds a focused gfx942 correctness test comparing packed-int4 SiTUv2 stage1 against the torch reference. |
| csrc/ck_gemm_moe_2stages_codegen/gemm_moe_tune.py | Updates per_1x32 gating to allow packed-int4 weights on non-gfx950. |
| aiter/ops/flydsl/moe_kernels.py | Forwards act/beta parameters through the a16wi4 (bf16×int4) stage1 compile path. |
| aiter/ops/flydsl/kernels/moe_gemm_2stage.py | Implements activation selection (SiLU/SiTUv2), adds validation, and encodes activation/betas into the FlyDSL module name. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if act == "situv2": | ||
| if situ_beta <= 0.0: | ||
| raise ValueError(f"situ_beta must be > 0, got {situ_beta!r}") | ||
| if situ_linear_beta <= 0.0: | ||
| raise ValueError( | ||
| f"situ_linear_beta must be > 0, got {situ_linear_beta!r}" | ||
| ) |
| The default cases hardcode the supported MXFP4 family instead of relying on | ||
| the -a list: | ||
| * a8w4 (fp8 activation, fp4 weight) at a 256-aligned inter_dim shape | ||
| beta / linear_beta come from --beta / --linear-beta (None -> kernel 1.0). | ||
| Non-gfx950 runs are skipped inside test_fmoe's per_1x32 gfx guard. |
|
Ran the tests on gfx942 (MI325X), building this branch rather than patching an installed AITER. The a16wi4 test compares the packed-int4 stage1 against Activation now reaches the kernel and the cache keys separate: Three distinct module names, so SiLU and SiTUv2 cannot collide in the on-disk FlyDSL cache and neither can two beta pairs. Requesting an unsupported activation raises One note for anyone testing this against an existing install. The patched |
The gfx942 path requantized Kimi-K3's MXFP4 experts to groupwise int4 whenever the hardware matched, without the user asking. The conversion is lossy, so it is now opt-in through --quantization-config.moe.weight int4 and gfx942 keeps the native MXFP4 path otherwise. Also refuse to load when the installed AITER predates ROCm/aiter#4471. Before that fix the packed-int4 stage1 dropped the requested activation and hardcoded SiLU, so Kimi-K3 served fluent text while computing SiLU instead of the SiTUv2 its config asks for. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
Black reformats three files and ruff sorts the imports in the two new tests. The exec in the module-name test now carries an explicit noqa for S102 with the reason, since the test evaluates a single function parsed out of this repository in order to stay runnable without a GPU. The kernel file parses to an identical AST before and after, so this changes formatting only. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
The gfx942 path requantized Kimi-K3's MXFP4 experts to groupwise int4 whenever the hardware matched, without the user asking. The conversion is lossy, so it is now opt-in through --quantization-config.moe.weight int4 and gfx942 keeps the native MXFP4 path otherwise. Also refuse to load when the installed AITER predates ROCm/aiter#4471. Before that fix the packed-int4 stage1 dropped the requested activation and hardcoded SiLU, so Kimi-K3 served fluent text while computing SiLU instead of the SiTUv2 its config asks for. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
Motivation
The packed-int4 (a16wi4) FlyDSL stage1 path ignores the requested activation and always computes SiLU.
compile_flydsl_moe_stage1forwardsact,situ_betaandsitu_linear_betato every dispatch branch exceptb_dtype == "int4".compile_moe_gemm1has noactparameter, soy = silu(vg) * vuis hardcoded at both live stage1 epilogue sites, the CShuffle one and the direct-store one. Nothing raises. The kernel compiles, runs, and returns fluent text while computing the wrong function.This affects Kimi-K3 on gfx942, which specifies
hidden_act = situwithactivation_situ_beta = 4.0andactivation_situ_linear_beta = 25.0. gfx942 has no scaled MXFP4 MFMA, so the MXFP4 experts are requantized to int4 at load and dispatched to this kernel. gfx950 is unaffected, since there bf16 x MXFP4 routes tocompile_mixed_moe_gemm1_a16w4, which implements SiTUv2 natively.Reproducing needs no model. Compile the same shape twice through
compile_flydsl_moe_stage1, once withact="silu"and once withact="situv2". The int4 path returns the identical executable for both. The mxfp4 path returns two different ones. Against a SiTUv2 reference the current kernel scores cosine 0.961917, and against a SiLU reference it scores 0.999976.Technical Details
act,situ_betaandsitu_linear_betatocompile_moe_gemm1. Defaults keep every existing caller bit-identical.silu()into a reusablesigmoid(), add the SiTUv2 form, and route both epilogue sites through a singleapply_activation().compile_flydsl_moe_stage1.lru_cachekey already covered the new parameters but the on-disk module name did not, so SiLU and SiTUv2 binaries could collide in the kernel cache.siluandsituv2on this path instead of silently computing SiLU.tanhis built as2 * sigmoid(2x) - 1over the existingexp2andrcpfast path rather thanrocdl.tanh, which lowers tollvm.amdgcn.tanhand only selects on gfx1250. The identity saturates correctly at both ends without clamping, which matters becausetanh(up / 25)saturates for any|up|above roughly 100.situv2with split-K raises rather than falling back. The split-K path writes raw gate and up partials and defers activation to a reduction kernel that only implements SiLU. This is inert at current Kimi-K3 shapes becauseget_ksplitreturns 0, and it closes the same class of failure against future tuning.Test Plan
pytest op_tests/flydsl_tests/test_flydsl_moe_a16wi4.pypytest op_tests/flydsl_tests/test_flydsl_moe_module_name.pypython3 op_tests/test_moe_2stage.py -d bf16 -q 6 -t 1 8 32 -a situv2 --beta 4.0 --linear-beta 25.0python3 op_tests/test_moe_2stage.py -d bf16 -q 6 -t 1 8 32 -a silufor the unchanged pathTest Result
Direct SiTUv2 stage1 passes on gfx942. SiLU output is unchanged bit-for-bit for default callers. End to end on 8x MI325X the fix is throughput neutral at -0.07 percent, which is inside run-to-run noise.
When testing SiTUv2 by hand, pass the same betas to the kernel and the reference.
torch_moe_stage1defaults to (2.0, 1.5) and the FlyDSL kernels default to (1.0, 1.0), so a reference built on the defaults compares two different activation functions and reports a large error unrelated to the kernel. #4463 fixes that separately.Submission Checklist