Skip to content

[FlyDSL] [AOT] Build gfx1250-strict wheels from the gfx1250 kernel family - #7379

Merged
coderfeli merged 2 commits into
mainfrom
fix-flydsl-strict-aot
Nov 4, 2026
Merged

coderfeli merged 2 commits into
mainfrom
fix-flydsl-strict-aot

Conversation

@zufayu

@zufayu zufayu commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

GPU_ARCHS=gfx1250-strict produced a silently empty / wrong FlyDSL AOT wheel: GEMM matched 0 tuned rows yet reported success (empty wheel), and chunk_gdn_h compiled gfx942 kernels while labelling the run strict.

Root cause is on the aiter side (not FlyDSL): the AOT job filters compared the tuned-row arch (gfx1250) against the exact requested target (gfx1250-strict) and dropped everything, because tuning is done on gfx1250 and there are no gfx1250-strict tuned rows.

What this fixes (one line)

Makes a GPU_ARCHS=gfx1250-strict wheel build actually emit gfx1250-strict (ELF mach 0xeb) FlyDSL kernels — or fail loudly — instead of a silent empty/wrong wheel.

Fix

gfx1250-strict (MI450 A0) and gfx1250 (B0) are one source/kernel family, so a gfx1250-tuned row serves a gfx1250-strict build and is compiled for the exact strict target (0xeb, not gfx1250 0x49). Two small helpers in common.py:

  • resolve_target_arch(job_arch, targets) — exact requested target in the same family (via base_gfx_arch), or None; retargets a gfx1250 row to gfx1250-strict.
  • assert_targets_covered(...) — fail loud when a family present in the tuned data produces no job, instead of shipping an empty/partial wheel.

Applied where each AOT driver filters and groups by arch:

driver covers change
replay.py (the shared driver from #6956) GEMM, conv, chunk_gdn_h, FP8 FMHA family-match + retarget in both the orchestrator (group/replay by the resolved target) and the per-arch worker; fail-loud guard
moe.py FlyDSL MoE family-match + retarget in the orchestrator and the replay worker; the GateMode/quant gfx1250 checks become family-aware
mxfp4_moe.py mxfp4 MoE (gfx950-only) skip a strict build instead of emitting gfx950 kernels

mega_moe.py / FP8 FMHA are gfx950-only and correctly skip a strict build (the latter now through replay.py's family-aware filter).

Depends on

FlyDSL compiles the strict target as of flydsl 0.4.2.dev1008 (bare gfx1250 to MLIR Chipset::parse, full gfx1250-strict to LLVM codegen), landing in the 0.4.2 release. The pin bump is tracked separately; with an older pin these drivers select the right rows but the compile still fails at the FlyDSL layer.

Validation

  • Real MI450 A0 (asicRevision 0): a gfx1250 row compiled for gfx1250-strict (flydsl 0.4.2.dev1008) yields an artifact with rocdl chip=gfx1250-strict and embedded ELF e_flags=0x4eb (mach 0xeb). FlyDSL runtime-JIT sanity (batched gemm / mha 256 cfg / quant / rmsnorm2d) is bit-identical to 0.4.1.

Rebased onto main's #6956 (shared replay.py AOT driver); the strict family-match now lives in that one driver rather than per-entry. Follow-up to #6767 (strict identity) and #7185 (old-ROCm A0 Opus fallback), both merged.

🤖 Generated with Claude Code

@zufayu
zufayu requested a review from a team November 3, 2026 05:48
@github-actions

github-actions Bot commented Nov 3, 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:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs (added automatically when gfx942 configs change); main branch always runs both MI35X and MI300X
ci:triton-355 Run the full Triton test suite on MI35X, not only the tests the change affects
ci:windows Windows JIT op smoke test on the gfx1201 ROCm runner
ci:multi-gpu Aiter multi-GPU tests on the 8-GPU runner
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: DeepSeek-V4-Pro, MiniMax-M3-MXFP4
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 7379 --add-label <label>

One backend per PR:
A PR changes one kernel backend: [Triton/Gluon] (Triton and Gluon count as one), [HIP], [ASM], [CK], [OPUS] or [FlyDSL]. If the title ends up with two backend tags, split the PR -- as stacked pull requests when one part cannot merge without the other.

PR title tags & labels:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title and as PR labels automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf], op tags like [MLA], and human labels (ci:*) are left untouched. Add the no-auto-title label to stop the title rewrites; labels stay in sync either way.

@github-actions github-actions Bot added the FlyDSL label Nov 3, 2026
@aiter-bot

aiter-bot commented Nov 3, 2026 •

Copy link
Copy Markdown

NOT READY FOR HUMAN REVIEW

PR Self-Review

Revision 77a4d6651282 · mode shadow

Waiting for the PR author. The readiness report will appear after the self-review answers are complete for this revision.

Questions to answer in the PR description

  1. What behavior does this PR change, and why is the existing behavior insufficient?
  2. What did you validate, and what remains unverified? Include commands, hardware/configuration and results.
  3. What is the most important boundary or failure mode introduced by this change, and what evidence covers it?
  4. For every changed op_tests/ file, what behavior does it cover and why is it not redundant? Write None if no op test changed.

Edit the PR Self-Review section in the PR description. Include the current revision SHA; this comment refreshes automatically.

Missing or invalid answers

  • FAIL — missing ### Reviewed revision in the PR description
  • FAIL — missing ### Behavior and intent in the PR description
  • FAIL — missing ### Validation and unverified scope in the PR description
  • FAIL — missing ### PR-specific question in the PR description
  • FAIL — missing ### Changed op tests in the PR description
  • FAIL — Reviewed revision must contain at least the first 7 characters of the current head SHA
  • FAIL — Behavior and intent has no substantive answer
  • FAIL — Validation and unverified scope has no substantive answer
  • FAIL — PR-specific question has no substantive answer

Shadow mode reports what would block but keeps the status successful. Set AITER_PR_SELF_REVIEW_MODE=enforce only after reviewing real false positives.

@zufayu
zufayu force-pushed the fix-flydsl-strict-aot branch 2 times, most recently from 501e85a to 1943392 Compare November 3, 2026 07:30
@zufayu

zufayu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Self-review (aiter review-pr skill + author)

Ran the advisory review-pr skill on this PR and self-reviewed for junk / simplification / test coverage. Summary below; pushed fixes for the author-found items.

review-pr (advisory) — clean

Derived 19 rules across 8 families (dispatch-change, flydsl, fp8-quant, new-routing-value, api-signature, …). Forensic axes all came back clean: no deleted guard, every first-party import resolves against the merge target, no AOT-pairing contract left unaware, no FlyDSL unbounded buffer/descriptor, no struct-ABI shift, no copied-file asymmetry, CI runs the added test.

  • C4/C5 (arch literal / chip_info ISA gate) do not fire: this is build-time AOT orchestration, not runtime dispatch. It keys on GPU_ARCHS (aot_target_archs()) and the tuned-row arch — exactly what C5 prescribes for build-time codegen — never get_gfx_runtime(). The moe.py change swaps job_arch(row) == "gfx1250" for is_gfx1250_family(job_arch(row)); job_arch is the row's tuned arch, still build-time.
  • Kernel-timing validation is N/A: the changed files are codegen/orchestration that is read and looked up, not a runtime kernel route that is executed, so there is no base-vs-head latency to measure. Correctness is covered by CPU unit tests instead.

Author self-review — 3 items, all addressed

  1. Inconsistent fail-loud guard — assert_targets_covered was only in gemm + chunk_gdn. Added it to moe as well, so every entry that can emit kernels refuses a silent empty/partial strict wheel the same way. (conv's filter is inline per-CSV row; its retarget is correct, the loud guard is best-effort elsewhere.)
  2. Minor redundancy — chunk_gdn_h computed cu_num_to_arch twice per job; now once (zip(jobs, bases)).
  3. Test depth — added TestFilterRetargetBehavior that locks the actual bug fix (a strict build selects 2 of 3 gfx1250/gfx942 rows and retargets them, where the old exact-match filter kept 0), plus multi-arch / plain-gfx1250 / native cases. op_tests/test_flydsl_aot_strict_target.py is now 13 CPU cases over resolve_target_arch, assert_targets_covered, and the composed filter.

No dead code (moe's skipped Counter is still consumed by the [SKIP] summary). The core is two ~20-line helpers in common.py; the rest is the same family-match + retarget pattern applied per AOT entry.

…mily

GPU_ARCHS=gfx1250-strict matched zero FlyDSL tuned rows and printed
"All compilations succeeded" -- a silent empty wheel -- because the AOT job
filters compared the tuned-row arch (gfx1250) against the exact requested
target (gfx1250-strict) and dropped everything. chunk_gdn_h ignored GPU_ARCHS
entirely and compiled gfx942 kernels while labelling the run strict.

gfx1250-strict (MI450 A0) and gfx1250 (B0) are one source/kernel family, so a
gfx1250-tuned row should serve a gfx1250-strict build and be compiled for the
exact strict target (ELF mach 0xeb, not gfx1250 0x49). Add
common.resolve_target_arch (family-match + retarget to the exact requested
arch) and common.assert_targets_covered (fail loud when a family present in the
tuned data produces no job). Every AOT entry now honors GPU_ARCHS for strict:

- gemm.py / conv.py: keep family-matching rows, retarget the compile.
- chunk_gdn_h.py: honor GPU_ARCHS (previously unfiltered -> gfx942), retarget.
- moe.py: family-match + retarget in both the orchestrator and the per-arch
  replay worker; the GateMode/quant gfx1250 checks become family-aware.
- mega_moe.py / fmha_fp8.py / mxfp4_moe.py are gfx950-only; they now skip a
  strict build instead of emitting gfx950 kernels into it (mega_moe already
  did; fmha_fp8 and mxfp4_moe gained the same GPU_ARCHS guard).

FlyDSL compiles the strict target as of flydsl 0.4.2.dev1008 (bare gfx1250 to
MLIR Chipset::parse, full gfx1250-strict to LLVM codegen), landing in the
0.4.2 release; the pin bump is tracked separately. Verified on real MI450 A0: a
gfx1250 row compiled for gfx1250-strict yields an artifact with rocdl
chip=gfx1250-strict and ELF e_flags 0x4eb (mach 0xeb).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zufayu
zufayu force-pushed the fix-flydsl-strict-aot branch from 1943392 to 48380a9 Compare November 3, 2026 07:34
@zufayu

zufayu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Correction to the self-review above: per the "don't add op_tests unless necessary" convention (they add CI machine time), the op_tests/test_flydsl_aot_strict_target.py file mentioned above was removed. The change is build-time AOT orchestration with no runtime kernel route; its correctness is covered by the real-A0 end-to-end verification (a gfx1250 row compiled for gfx1250-strict on flydsl 0.4.2.dev1008 yields an artifact with rocdl chip=gfx1250-strict / ELF mach 0xeb), not a dedicated op_test. The simplification (chunk_gdn cu_num_to_arch once) and the fail-loud consistency fix (assert_targets_covered added to moe) remain.

main's #6956 moved the GEMM/conv/chunk_gdn_h/FP8-FMHA AOT drivers into a shared
aiter/aot/flydsl/replay.py. Re-apply the gfx1250-strict family-match + retarget
there (one place) instead of the now-removed per-entry filters; take main's
version of the four refactored entries. moe.py and mxfp4_moe.py keep their own
drivers and retain the strict fix; common.py keeps resolve_target_arch /
assert_targets_covered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zufayu
zufayu requested a review from coderfeli November 4, 2026 01:31
@zufayu
zufayu requested review from amd-ruitang3 and zhiding512 and removed request for amd-ruitang3 November 4, 2026 01:58
@coderfeli
coderfeli merged commit 7cb3db1 into main Nov 4, 2026
59 checks passed
@coderfeli
coderfeli deleted the fix-flydsl-strict-aot branch November 4, 2026 06:21
zufayu added a commit that referenced this pull request Nov 5, 2026
…0-strict wheels (#7434)

* [FlyDSL][AOT] Map gfx1250-strict to the gfx1250 kernel family at AOT dispatch/tuned-lookup sites

Follow-up to #7379. With GPU_ARCHS=gfx1250-strict, #7379 fixed job *selection*
(strict was selecting 0 jobs), but the enqueued jobs were then rejected a
second time inside the op layer, where 8 sites compared the exact arch to the
literal "gfx1250". That capped the strict AOT wheel at ~7% coverage (140 vs
1002 artifacts) with 5 hard [FAIL]s. This maps strict to the gfx1250 family at
exactly those AOT-traversed sites:

- 3 dispatch gates (capability / codegen choice) -> is_gfx1250_family():
  gemm_op_a8w8 mxfp8_128 gate and blockscale_abpreshuffle guard (the rc=1
  raise), and flydsl moe_kernels LDS selection.
- 5 tuned-config lookups -> exact identity first, gfx1250-family fallback when
  strict has no tuned row of its own: tuned_gemm (a16w16), gemm_op_a4w4
  (a4w4 mxscale), grouped_moe CSV, plus the a8w8 mxscale and shared CK config
  helpers reached once the gates above open.

A0 bring-up reuses the gfx1250 tuned data (A0 and B0 share the gfx1250 ISA;
performance parity is a later follow-up). An exact strict tuned row still wins
when one exists, and the fallback guard (base_gfx_arch(gfx) != gfx) keeps the
table scan off the normal non-strict runtime path. Build targets, cache keys,
and prebuilt .co paths stay keyed by the exact identity, per #6767's two-layer
model.

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

* [FlyDSL][AOT] Family-map the second layer of gfx1250 op gates for strict

A0 acceptance of the first commit surfaced a deeper layer: opening the
first-layer gates let 816 previously-silently-dropped jobs reach a *second*
set of exact-"gfx1250" checks -- all GATE, zero genuine kernel failures. Deduped
to 2 hard points + the silent coverage gaps:

- [A] gemm_op_a8w8 gemm_a8w8_mxfp8_128_bpreshuffle_flydsl guard (812 of 816
  FAILs) + the bpreshuffle WMMA dispatch gate (189) and untuned fallback (917).
- [B] opus get_kernel_instance now folds arch to the gfx1250 family (get_registry
  already did; this was the missed sibling -> "unknown OPUS kid" on strict).
- [D] flydsl batched_gemm_a8w8 _RUNNERS lookup.
- [C]/[E] a8w8 int8 and mxfp8_32 tuned-config lookups -> exact-then-family
  fallback; plus the mxfp8_bpreshuffle_tuned predicate and the
  a_scale_preshuffled gate, mapped proactively to avoid a third round.

Same two patterns as the first commit (dispatch gate -> is_gfx1250_family;
tuned lookup -> exact identity first, gfx1250 fallback). Pending A0 re-acceptance.

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

* [FlyDSL][AOT] Family-map the BMM tuned-config lookup for strict

An A0 per-row kernel-identity diff against the gfx1250 baseline found one
remaining silent single-op drop: lookup_mxscale_bmm_config (opus/policy.py)
keyed the mxscale BMM tuned table by the exact gfx, so a strict build missed
all 50 BMM rows and 43 of them silently compiled a different heuristic kernel
than gfx1250 (tile_k 512/nb2 instead of the tuned 256/nb4), with no log.

Same exact-identity-first, gfx1250-family fallback as the other tuned lookups.

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

* [FlyDSL][AOT] Gate the opus strict->gfx1250 fold to AOT replay only

The get_kernel_instance fold this PR added was unconditional, which let a non-CO
kid resolve under a strict *runtime* identity instead of being rejected --
breaking op_tests/test_gfx1250_strict_alias::test_non_co_kid_keeps_real_id
(the expected ValueError was no longer raised).

Only an AOT replay (AITER_AOT_REPLAY=1) compiles the whole gfx1250 kernel family
for the strict target and needs the fold; the product is still emitted for the
exact strict arch (0xeb). At runtime the exact identity is kept, so a non-CO kid
under strict is rejected as before, and CO kids keep resolving via
get_registry's existing fold. Verified locally: the test passes again.

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

* [FlyDSL][AOT] Gate A0-errata ops off strict + fail the build on hollow kernels

gfx1250-strict deliberately disables wmma-f4-insts and wmma-16x16x128fp8-insts
for A0 silicon errata, so a strict build of the ops that need them (a8w8 ptpc,
a4w4 / a4w4_mx32_compute, MoE mxfp4) silently compiled HOLLOW, compute-less
kernels (rc 0). Earlier family-mapping routed these to the gfx1250 fast-WMMA
kernels, which is wrong for strict.

- Gate the errata ops off the strict AOT build: skip the a8w8_bpreshuffle and
  a4w4_mxscale_bpreshuffle tables (STRICT_ERRATA_TABLES) and MoE mxfp4 rows
  (_is_mxfp4_kname) when the target is a gfx1250-strict identity. A0 falls back
  to runtime JIT for these. The good ops (mx128/mx32/BMM/a16w16/fp8-TDM, which
  use retained instructions) are unaffected.
- Defense in depth: fail_build_on_missing_target_feature wraps run_aot and fails
  the build (fd-level capture of the LLVM 'requires target feature' diagnostic)
  if any hollow kernel is still produced, so a missed errata op can never
  silently ship a hollow wheel.

The feature-disable is correct; the compiler-side ask (hard-fail / safe
lowering) is tracked separately. Pending A0 validation.

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

* [FlyDSL][AOT] Fix the MoE strict gate: key on fp4 q_dtype_a, gate the worker too

A0 STEP-0 validation found the MoE errata gate was a no-op: _is_mxfp4_kname
matches "flydsl_mxmoe_g", but the gfx1250 grouped rows name their kernel
grouped_gemm1_fp4_gugu (also shared by the fine fp8-activation a8w4 rows), so no
row was gated. Key on q_dtype_a == float4 instead, which cleanly separates the
hollow fp4 rows from the fp8-activation ones. Also add the gate to the `--arch`
worker, which re-reads the CSV and filters independently of run().

GEMM gate confirmed correct by STEP 0 (a8w8_bpreshuffle ptpc + a4w4_mxscale
tables, all hollow; every other table clean).

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

* [FlyDSL][AOT] Guard direct driver invocation too, and make the guard reentrant

The hollow-kernel build guard only wrapped run_aot (the setup.py wheel path), so
a direct `python -m aiter.aot.flydsl.<driver>` build was unguarded. Wrap the
driver orchestrator entry points (replay.main, moe.main) as well.

To avoid double-capturing fd 2 when guards nest -- run_aot's guard around a
driver whose main also guards, or an orchestrator around its per-arch worker
subprocesses -- the guard now sets AITER_AOT_FEATURE_GUARD=1 (inherited by
children); a nested or child guard sees it and no-ops, so only the outermost
capture is active.

(The remaining cache-hit gap -- a stale pre-gate hollow object that is not
recompiled, so prints no diagnostic -- is addressed by building strict wheels
from a clean cache; the complete errata gate means a fresh build never produces
one.)

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

* [FlyDSL][AOT] Audit the strict cache for stale hollow errata kernels

A0 showed the compile-time guard misses a hollow kernel already in the FlyDSL
cache: a cache hit recompiles nothing and prints no 'requires target feature'
diagnostic, and setup.py's cache persists across builds, so a stale hollow object
would ship silently. Add a cheap post-build name audit: after a strict run_aot,
fail if the cache still holds any gemm_a4w4* / gemm_a8w8_ptpc* / mxfp4_tdm_fp4*
object -- the errata families strict gates off. It inspects the cache, not the
compiler output, so it is immune to cache hits.

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

* [FlyDSL][AOT] Rewrite the stale-hollow cache audit to read kernel IR

A0 found the glob-by-filename audit could not work: cache paths are
<launcher>_<manager_key>/<hash>.pkl, so the kernel name is not in the path --
ptpc shares launch_gemm_a8w8 with mx128 and fp4 MoE shares launch_gemm_a8w4_tdm
with fp8, so the ptpc/mxfp4 globs never matched, gemm_a4w4 matched only a
directory name, and a mixed gfx1250+strict build false-positived on legitimate
gfx1250 a4w4 objects.

Read each cached object's IR instead: flag an errata-family kernel (gemm_a4w4 /
gemm_a8w8_ptpc / mxfp4_tdm_fp4) only in objects whose ROCDL target chip is a
strict identity. A0-validated on the same cache set: dirty caches report
134 / 282 / 101 offenders; clean, plain, and mixed-target all pass. ~1000 pkl,
a few seconds.

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

---------

Co-authored-by: zufayu <8138883+zufayu@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zhiding512 added a commit that referenced this pull request Nov 9, 2026
Main's gfx1250-strict wheel builds (#7379, #7434) and MegaMoEV2 (#6881)
edited the code this branch rewrites; both sides are kept:

- moe.py: an arch's replay keeps the jobs of that arch's family, retargets
  them to it and leaves out the MXFP4 ones on gfx1250-strict; deployment
  jobs now pass through that filter as rows do. run_aot and moe.py's main
  run inside fail_build_on_missing_target_feature(), and run_aot still ends
  with the strict errata check.
- mega_moe.py: the MTPR 8192 instances build the opt-in small- and
  large-input paths, and --check compiles nothing. FUSED_PATHS_MTPR and
  FUSED_PATHS_ENV stay module constants: they say how the replay builds an
  instance, not what a deployment is.
- README: main's gdn_fused_prefill entries next to the deployment ones.

#6464 moved DeepSeek-V4-Pro's TP8 rows at M 32 and 2048 to MXMOE stage 1,
which compiles the clamp in, so the clamp the TP8 deployment passes now
picks kernels the deployment builds.

The merge changes no job list: the 1195 deployment jobs are 88ab025's, the
14374 tuned-row jobs main's (apart from the empty expert_mask_size this
branch adds) and the 192 MegaMoE jobs main's.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants