Repository navigation
[HIP] [CK] [FlyDSL] Accelerate MXFP4 FMoE AOT tuning ~9.5x on gfx950 - #6405
Conversation
Clean replay of the fast-scan tuning fix from PR #3 onto current upstream main, excluding the unmerged worker-policy fan-out. - gemm_moe_tune: accuracy-aware fast scan for the FlyDSL mxfp4 a4w4 tuner (--fast-scan): deduplicated parallel precompile, shared fixture/reference tensors, device-side cosine reduction, coarse scan -> latency finalists -> accuracy promotion -> ordered backfill, plus finalist summary. Non-mxfp4 tuners reject --fast-scan. - aot/flydsl: optional start_method for the file pool; batch compile helpers in mxfp4_moe. - mp_tuner: skip the 3s pid stagger when mp_num == 1. - test_common: configurable profile_warm_iters for perftest. - gen_instances: add GLM-5.3 / GLM-5.3-Flash / Qwen3-Coder-Next / Inkling / MiMo-V2.5-Pro shapes. - Add glm53 fp4 tuned/untuned configs, fast-scan README section, and a CPU-only regression test for the scan funnel.
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
One backend per PR: PR title tags & labels: |
|
Note to AMD devs, this code/pr was originally created in early Sept but I currently do not have access to MI350 to re-validate. Please triggee CI and validate this again on MI350. |
Review fixes for the MXFP4 fast-scan tuning path: - build the shared per-shape torch reference with the same AITER_MXFP4_TUNE_SWIGLU_LIMIT clamp that _run_candidate applies to the per-candidate reference and to the kernel launch; without it every candidate was rejected against an unclamped reference whenever the tuning limit was set - remove the unused _rank_mxfp4_candidates timing-bucket helper (only its own test referenced it) and route the coarse finalist/backfill ordering through the tested _select_mxfp4_performance_finalists so production and tests share one ordering - align the README funnel description with the actual finalist selection - extend the CPU regression suite: shared-reference clamp propagation, kernel/reference clamp parity, and in-memory jobs_from_rows equivalence with parse_csv on the shipped GLM-5.3 tuned CSV
Review follow-ups on the MXFP4 fast scan: - Prefer the raw `_err` ratio over the rounded err1/err2 percent strings when a finalist retime merges the coarse error, so a 1.1% display string can no longer round a candidate below a slower one with a true 0.01120. - Apply AITER_MXFP4_TUNE_SWIGLU_LIMIT to the stage-1 precompile jobs, matching the runtime clamp. Without it every precompiled kernel missed the cache and the scan JITed while scoring. - Add CPU-only regression tests for both paths and register the module in the tuning test split and the level 0+1 CI suite. - Correct the README's profiler-sample and accuracy-bucket claims, and document the swiglu-limit contract.
The Checks workflow runs `black --check --diff .` (psf/black@stable, 26.10.1 today) and `ruff check .` (pinned 0.16.0) over the whole tree. main passes both; this branch regressed them. - black 26.10.1 collapses the hand-wrapped argument lists and conditions in gemm_moe_tune.py, mp_tuner.py and the new tuning test module, so the style gate failed on three files. - ruff SIM115 flagged the conditional `open()` in parse_csv: choose the path-or-stream context manager inside the `with` statement instead of binding it first, so the file stays managed there. - ruff SIM117 flagged two nested `with` blocks in the new tests; merge each pair into one multi-context `with` (identical semantics). Validated: `black --check .` and `ruff check .` are clean at this head, and op_tests.tuning_tests.test_fast_tuning still passes 24/24.
|
Added a Fast-scan arguments and usage section to the PR description, with the complete argument table, explicit tuning and production-validation commands, and measurement semantics checked against head
Warm-up clarification: the first round already compiles and exercises every surviving candidate. A finalist normally reaches retiming after 1 untimed accuracy-check invocation and 10 measured invocations. The final round intentionally adds no redundant initialization warm-up. Cache/clock conditions may evolve between rounds, but that is separate from needing to compile or initialize the candidate again. 100 final iterations is adjustable, not a correctness requirement. The description includes a 32-iteration experimental alternative without claiming it has the same validated winner retention. The current collector enables 1.5×IQR outlier filtering only above 30 measured iterations: 10 and 30 are unfiltered; 32 and 100 are filtered. The fast path does not automatically discard the first profiled sample. These settings replace the ordinary iteration/warm-up counts in fast-scan mode. The new documentation also explains accuracy checks, finalist backfill, the fixed 1% final selection band, and the distinction between candidate search-space selection and measurement budgets. Published benchmark results remain attributed to their original configuration. This update changes documentation only; no iteration defaults or kernel behavior were changed. |
|
In my private AITER fork, I already run AOT compilation and tuning automatically during model loading when a model encounters a previously unseen configuration within a supported module/kernel path. The kernel implementations are prevalidated; new configurations are compiled, tuned, and numerically checked before use. Once this PR is validated, I believe we can integrate the faster tuning flow into model loading upstream. That would make selecting and preparing kernels for new models largely automatic wherever existing kernel support applies, without requiring a separate offline tuning sweep. |
Single conflict in aiter/aot/flydsl/mxfp4_moe.py: this branch's include_bias_variants gate and main's use_valid_token_count variants (ROCm#5804) both rewrote the same stage-2 option list in parse_csv(). Resolution keeps both behaviours: include_bias_variants still selects enable_bias_options, and count_options still adds the atomic-epilog use_valid_token_count variants inside that loop, so the parser emits the union of both job sets and the tuner path keeps its no-bias fast scan.
|
One area that may deserve a closer look before merging is the runtime performance impact. The tuning wall-time improvement is clear, but Could the reviewers please confirm that the committed configs do not regress performance versus the current |
@zufayu @zhiding512 I no longer have access to MI355X with FP4 support. I only just got AMD dev cloud credits today for MI300 so I need help from AMD staff regarding validation. I just don't have the hardware. |
|
@zhiding512 Could you or another reviewer with access to gfx950/MI350 please help run the performance validation discussed above? The author no longer has access to the hardware. The main remaining gap is to compare fast scan vs full scan for the committed GLM-5.3 shapes, including the selected kernel pairs and |
|
One CI failure does need a PR-side fix: The separate HSTU dtype failure appears unrelated to this PR; it looks like global default dtype leakage from another test. |
Signed-off-by: Qubitium <qubitium@modelcloud.ai>
Conflicts: - aiter/aot/flydsl/mxfp4_moe.py: kept. The MXFP4 tuner's --fast-scan (#6405) precompiles its candidates with jobs_from_rows and compile_job_batch. It stays out of the AOT build, which replays these rows through fused_moe; its docstring and the README say so. - aiter/jit/core.py: #6365 moved the duplicate-shape check into _raise_on_duplicate_shapes, shared with the single-file path. Comparing key columns as numbers, so 256 and 256.0 stay one shape, and ordering on a numeric `us` move there with it, and the single file is read as text like the merged ones, so a dedup rewrite keeps every cell as written. Signed-off-by: zhimding <zhimding@amd.com>
Upstream #6405 added GLM-5.3, GLM-5.3-Flash, Inkling, MiMo-V2.5-Pro and Qwen3-Coder-Next shapes to the MXFP4 MoE auxiliary generator but not to MXFP4_MOE_SUPPORTED_SHAPES. fused_moe therefore discarded every shipped GLM-5.3 (E=288, H=4096, I=2048, topk=8) MXMOE row with "generated MXFP4 auxiliary kernels do not cover" and fell back to default heuristics. Add the five generator shapes to the catalog and check that every shipped tuned MXMOE row is registered. Validation on gfx950: public fused_moe with the merged default FMOE config executes the CSV G1/G2 pair for all 16 GLM-5.3 rows (tokens 1..32768) with no discarded config, and run_config passes with cosine diff <= 7.5e-4. The same rows were discarded before this change. Signed-off-by: Wulley <bernard.liu@amd.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream #6405 added GLM-5.3, GLM-5.3-Flash, Inkling, MiMo-V2.5-Pro and Qwen3-Coder-Next shapes to the MXFP4 MoE auxiliary generator but not to MXFP4_MOE_SUPPORTED_SHAPES. fused_moe therefore discarded every shipped GLM-5.3 (E=288, H=4096, I=2048, topk=8) MXMOE row with "generated MXFP4 auxiliary kernels do not cover" and fell back to default heuristics. Add the five generator shapes to the catalog and check that every shipped tuned MXMOE row is registered. Validation on gfx950: public fused_moe with the merged default FMOE config executes the CSV G1/G2 pair for all 16 GLM-5.3 rows (tokens 1..32768) with no discarded config, and run_config passes with cosine diff <= 7.5e-4. The same rows were discarded before this change. Signed-off-by: Wulley <bernard.liu@amd.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream #6405 added GLM-5.3, GLM-5.3-Flash, Inkling, MiMo-V2.5-Pro and Qwen3-Coder-Next shapes to the MXFP4 MoE auxiliary generator but not to MXFP4_MOE_SUPPORTED_SHAPES. fused_moe therefore discarded every shipped GLM-5.3 (E=288, H=4096, I=2048, topk=8) MXMOE row with "generated MXFP4 auxiliary kernels do not cover" and fell back to default heuristics. Add the five generator shapes to the catalog and check that every shipped tuned MXMOE row is registered. Validation on gfx950: public fused_moe with the merged default FMOE config executes the CSV G1/G2 pair for all 16 GLM-5.3 rows (tokens 1..32768) with no discarded config, and run_config passes with cosine diff <= 7.5e-4. The same rows were discarded before this change. Signed-off-by: Wulley <bernard.liu@amd.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream #6405 added GLM-5.3, GLM-5.3-Flash, Inkling, MiMo-V2.5-Pro and Qwen3-Coder-Next shapes to the MXFP4 MoE auxiliary generator but not to MXFP4_MOE_SUPPORTED_SHAPES. fused_moe therefore discarded every shipped GLM-5.3 (E=288, H=4096, I=2048, topk=8) MXMOE row with "generated MXFP4 auxiliary kernels do not cover" and fell back to default heuristics. Add the five generator shapes to the catalog and check that every shipped tuned MXMOE row is registered. Validation on gfx950: public fused_moe with the merged default FMOE config executes the CSV G1/G2 pair for all 16 GLM-5.3 rows (tokens 1..32768) with no discarded config, and run_config passes with cosine diff <= 7.5e-4. The same rows were discarded before this change. Signed-off-by: Wulley <bernard.liu@amd.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Makes gfx950 MXFP4 FlyDSL FMoE AOT tuning practical by moving cold kernel compilation out of the serial GPU candidate loop and replacing exhaustive per-pair measurement with an accuracy-aware two-stage scan.
This PR also addresses an important accuracy-selection issue: main can select a less accurate kernel for a negligible speed advantage. In the new
--fast-scanpath, final selection prefers the lowest worst observed numerical error among validated, retimed finalists within 1% of the fastest finalist. This preserves better numerical accuracy in cases where the latency difference is minor.The tuner currently JIT-compiles each coupled G1/G2 pair inside the GPU scoring loop and rebuilds/reshuffles the full expert fixture plus torch reference for every candidate. On
token=1, hidden=4096, inter=2048, experts=288, topk=8(GLM-5.3-Flash TP=1) a cold sweep takes ~223 s, of which ~123 s is serial compilation.Measured on MI355X (gfx950), same shape, fixed seed:
Accuracy-selection fix: preserve accuracy when latency is nearly equal
Main uses numerical accuracy as a pass/fail gate, then chooses the lowest measured latency. Once candidates pass
--errRatio, lower error does not influence the winner. Consequently, even a tiny measured speed advantage can displace a substantially more accurate candidate; exact latency ties retain the earlier candidate regardless of accuracy.The new
--fast-scanfinal selection changes that behavior:The raw, unrounded error is retained across coarse warm-up, coarse measured-output, and final measured-output checks. A final retime cannot overwrite a larger previously observed error with a smaller one. Numerical rejection thresholds remain in force.
Illustrative example, assuming both candidates pass validation and reach final retiming:
These are illustrative values demonstrating the selection rule, not new GPU benchmark results or a claim about downstream model accuracy. The reported token-1 benchmark below selected the same kernel pair on main and the PR.
Scope: this corrects cases of unnecessary accuracy loss within the retimed finalist set. It does not guarantee the most accurate eligible candidate across the entire coarse pool, because the funnel may exclude a candidate before final retiming. The ordinary scan path without
--fast-scanremains latency-first.Sources: main's accuracy gate, main's latency-only winner selection, and PR's 1% band and accuracy-based final selection.
What changed
csrc/ck_gemm_moe_2stages_codegen/gemm_moe_tune.py— opt-in--fast-scanfor the MXFP4 FlyDSL tuner:--fast-scanis rejected by the non-MXFP4 tuners at parse time and at dispatchaiter/aot/flydsl/common.py— optionalstart_methodfor the compile file pool (compile-only children can be forked from a clean fork server)aiter/aot/flydsl/mxfp4_moe.py— batch-compile helper and in-memory CSV parsing so tuner candidates can reuse the AOT job builderaiter/utility/mp_tuner.py— skip the 3 s pid stagger whenmp_num == 1aiter/test_common.py—profile_warm_itersis now configurable instead of hard-coded to 1csrc/kernels/mxfp4_moe/moe_aux/codegen/gen_instances.py— add GLM-5.3 / GLM-5.3-Flash / Qwen3-Coder-Next / Inkling / MiMo-V2.5-Pro shapesglm53_fp4_tuned_fmoe.csv+glm53_fp4_untuned_fmoe.csv, a "Fast MXFP4 FlyDSL scan" section in the codegen README, and a CPU-only regression test (op_tests/tuning_tests/test_fast_tuning.py) covering the funnel, finalist selection, backfill order, accuracy parsing and CLI gatingFast-scan arguments and usage
The following controls are implemented by
Mxfp4FlydslTuner. Enable them with--mxfp4-flydsl --fast-scan;--fast-scanis rejected for unsupported tuner modes. An iteration here means one complete coupled MoE invocation (sorting, applicable quantization, G1, G2, and associated reduction), not one individual GPU kernel.--fast-scan--fast-scan-iters N10--fast-scan-final-iters N100--fast-scan-finalists N5--fast-scan-warmup-accuracy-checks N1In fast-scan mode, the dedicated controls above supply the measurement and warm-up counts instead of the ordinary
--iters/--warmupsettings. The existing--errRatioaccuracy gate still applies. Candidate enumeration is separate: the existing--mxfp4-search-modedefaults toprune; use--mxfp4-search-mode fullto enumerate all statically supported GEMM1 variants. The reported 174 candidate pairs describe the benchmark's configured search space, not every possible shape/search mode.Default-budget example
Run from the repository root:
The four numeric settings are shown explicitly for discoverability; omitting them uses these same defaults. For the reported 174-candidate case with no failures, this means 174 untimed warm-up/check invocations, 1,740 coarse measured invocations, and 600 final measured invocations.
Validate the generated rows through production dispatch:
Adjusting the measurement budget
For an experimental shorter final round, use the same tuning command with
--fast-scan-final-iters 32and a separate output path. Keep--fast-scan-iters 10to leave initial screening unchanged. This is an example of the configurable budget, not a claim that 32 iterations has been validated to retain every winner or reproduce the published benchmark. Increase the initial-round count when coarse rankings are unstable; adding final samples cannot recover a candidate already excluded from the finalist set.Finalists do not receive another untimed warm-up: they have already been compiled and exercised during the initial round (normally 1 untimed + 10 measured invocations). Repeating initialization warm-up is intentionally avoided. This does not assert that cache contents or GPU clock conditions remain identical between rounds. There is currently no separate final-round warm-up argument.
Timing, filtering, and selection semantics
profile_warm_iters=0, so it does not automatically discard the first profiled invocation.Q1 - 1.5*IQR/Q3 + 1.5*IQRfences and averages the retained samples. Therefore the default 10-iteration coarse round is unfiltered, whereas the 100-iteration final round is filtered. Changing 100 to 30 disables this filtering; changing it to 32 retains it. The threshold is currently hardcoded.Implementation references at the reviewed head: arguments, candidate execution and two-stage scan, and timing/filtering.
Testing
python3 -m py_compileon all changed filespython3 -m unittest op_tests.tuning_tests.test_fast_tuning(CPU-only; no GPU required)--mxfp4-flydsl --fast-scansweep and the--run_configverification pass on MI355X / gfx950The fast scan still only checks kernel-level correctness. Selected rows should be validated through the production operator (for example via
--run_config) before the tuned table is committed, because that also covers runtime dispatch and integration.