Skip to content

[FlyDSL] [JIT] Build the GEMM, conv, chunk_gdn_h and FP8 FMHA AOT caches by replaying the runtime - #6956

Merged
coderfeli merged 52 commits into
mainfrom
zhiding512/flydsl-aot-replay-ops-20261026
Nov 3, 2026
Merged

coderfeli merged 52 commits into
mainfrom
zhiding512/flydsl-aot-replay-ops-20261026

Conversation

@zhiding512

@zhiding512 zhiding512 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Applies the design of #6362, which builds the MoE and MegaMoE AOT caches by replaying the runtime on CPU, to every other FlyDSL AOT module.

Motivation

gemm.py, conv.py, chunk_gdn_h.py and fmha_fp8.py re-derived, by hand, the kernel names, compile-time parameters and launch arguments the runtime would use. Any drift between that copy and the runtime makes the AOT artifact unreachable, and nothing reports it. One such drift is live on main today. Since #6494 (2026-10-15) the gfx950 bf16 hgemm runtime allocates its split-K semaphore as int64, while the AOT still builds it as int32. The tensor dtype is part of the FlyDSL cache key, so every gfx950 flydsl_hgemm row (2,543 tuned rows, 780 kernels) misses the AOT cache and JIT-compiles on the serving path, while the AOT log reports those kernels as compiled.

What changes

As in #6362, each tuned row (or configured head shape) becomes the arguments a caller passes the op's public entry, as FakeTensors. That entry runs inside dry_run, where the runtime does its own lookup, kernel selection, sizing and launch, and every FlyDSL launch compiles into the cache instead of running.

  • replay.py (new): the shared driver.
    • One process per target arch (replay_env, GPU_ARCHS honoured), with jobs batched per worker. The per-arch processes get the command line as given.
    • Bisection between a row's M and the smallest M it serves (the previous tuned M + 1), wherever the requested kernels differ. On gfx1250 (SCAN_ARCHS) every M of that range is identified without compiling instead.
    • The kernels a launch requests are identified by the key FlyDSL itself derives for its cache lookup (requested_kernels).
    • --check: compile nothing and fail on any kernel the cache lacks.
    • Summary lines: [SKIP] for NotImplementedError; [REFUSED] at in-between sizes, only for a limit the module declares in REFUSALS; [UNREACHED] for rows the runtime hands to Triton or asm, told by the error's traceback passing through Triton's code or the asm gate. Any other error fails the build.
  • gemm.py: about 1,340 lines of per-family compile code become one caller per tuned table (TABLES): gemm_a8w8_bpreshuffle; gemm_a8w8_blockscale and gemm_a8w8_blockscale_bpreshuffle with FP32 scales ([CK] [FlyDSL] Introduce FP8 a8w8 gemm kernel with fp32 blockscale to uplift the MI355 blockwise gemm perf #5969's flydsl_blockscale_8w_*, each row with bf16 and fp16 output); gemm_a8w8_blockscale_abpreshuffle; gemm_a8w8_mxfp8_bpreshuffle; gemm_a8w8_blockscale_bpreshuffle with e8m0 scales; batched_gemm_a8w8_mxscale_bpreshuffle; gemm_a16w16; and gemm_a4w4. --csv points the runtime's lookups at the given tables.
  • conv.py: replays flydsl_conv_implicit with both output layouts; an NCDHW input also reaches the pre-transpose.
  • chunk_gdn_h.py: replays chunk_gated_delta_rule_fwd_h_flydsl_opt at each row's batch shape, and over batch shapes the table never measured, where the BV rule decides.
  • fmha_fp8.py: replays flydsl_flash_attn_fp8_func over a batch × seqlen grid, bisecting between grid points, and each launcher the wrapper can pick, pinned through its own overrides.
  • common.run_aot: now runs every module's replay. The pooled per-kernel path, OpKind and JobLabel are removed.
  • Shared infrastructure:
    • torch_guard.run_python_ops_for_real / dry_run(python_ops=True): a torch_compile_guard op with a Python body runs that body under FakeTensorMode, while C++ ops still dispatch to their fakes. Outside a dry run this costs one global check in the existing wrapper.
    • The jit/core.py ctypes path reads its stream/device getters per call from _pybind_develop_hooks. It used to capture torch.cuda.current_device at decoration time, so get_padded_m reached the GPU in a dry run.
    • dry_run stands in for torch.cuda.device, MemPools, record_stream, Triton's driver queries and the raw stream handle FlyDSL launches pass since [Perf] Avoid torch Stream construction across grouped MoE launches #7219. Reaching the GPU runtime raises GpuRuntimeReached, which always fails the build.
    • The four FP32-scale blockscale CK and CK-tile ops get fakes of their own signatures. They shared a five-tensor fake, so under FakeTensorMode (torch.compile too) every tuned call to them, which passes kernelName, raised TypeError.
  • moe.py: shares requested_kernels with the driver; its behaviour is unchanged.

Non-public interfaces the replay depends on

The replay runs the runtime's own host code without a GPU, so it stands in for some interfaces that are not public. Each is in dry_run.py or replay.py, and a change to any of them fails the build instead of passing silently:

Interface What the replay does When it changes
FlyDSL JitFunction._compile_or_execute, _build_full_cache_key records the key of each launch; with stop, ends it there a renamed method fails the patch on entry; a launch that no longer derives its key there raises ReplayHookBroken
FlyDSL CompiledArtifact._ensure_engine forbidden creating an engine without a GPU only prints a HIP error, so the patch is what makes such a dry run fail
torch's torch.cuda._lazy_init; the torch.cuda stream, device and property queries and MemPools; the Tensor methods whose bindings take a device guard (__getitem__, contiguous, data_ptr, record_stream) _lazy_init raises GpuRuntimeReached; the rest are stood in for any other way to the GPU raises too, since the replay processes hide the GPUs
Triton's active driver answers target queries; a launch raises a RuntimeError a call that requested no FlyDSL kernel, never passed through FlyDSL code and failed in Triton's code is [UNREACHED]; anything else fails
aiter's own _pybind_develop_hooks_cache, tensor_shim._get_current_raw_stream, graph_alloc._persistent_pool, chip_info._detect_native stood in for as above

The stream stand-in is registered through FlyDSL's public JitArgumentRegistry, and the per-arch processes get the command line as given rather than rebuilt through argparse internals. tests/test_flydsl_aot_replay.py covers the FlyDSL hooks, the [REFUSED] classification and the raw stream stand-in on CPU, and CI's wheel build replays the default tables for gfx942 and gfx950.

Validation

This revision (d7f84a2, merged with main at 6d8c8e9c2) is c9fba78 below with ce0b847 reverted, so the PR no longer changes aiter/jit/utils/asm_guard.py and a hand-off to Triton or asm is told by its traceback again, and with TestFlydslAotReplay dropped from op_tests/tuning_tests/test_fast_tuning.py. ce0b847 had found both ways of telling a hand-off give the same outcomes on the tables where the build meets Triton and asm. The merge drops #7309's and #6767's edits to the hand-written GEMM AOT; the replay reaches what they added through the runtime (see the merge commit). On this revision tests/test_flydsl_aot_replay.py passes (5), and black and ruff at CI's versions are clean on every changed file; the c9fba78 results below were not rerun.

c9fba78 (merged with main at 3febddf6a, FlyDSL 0.4.1, gfx950 box with 2 CPUs):

  • Identify-only census of every default job (each launch stopped at its cache key, so nothing compiles), walking rows, smallest sizes and bisection or scan as the build does: GEMM gfx950 22,878 rows and 24,481 in-between sizes (73 more handed to Triton), GEMM gfx1250 1,483 rows and 691 sizes (17 rows and 91 sizes handed to Triton or asm, 5 sizes [REFUSED]). conv 152 rows, chunk_gdn_h 1,344 rows and 876 in-between sizes, fmha_fp8 3,112 rows (760 [SKIP]: tensors of 2^31 elements or more), 0 failed. A sample of the MoE replay (298 gfx1250 and 246 gfx950 jobs) has 0 failures.
  • [CK] [FlyDSL] Introduce FP8 a8w8 gemm kernel with fp32 blockscale to uplift the MI355 blockwise gemm perf #5969's tables: for the GLM-5.2 FP32 blockscale tables (27 and 6 FlyDSL rows), the replay builds the same 8 artifacts main's hand-written AOT builds (same launcher directories and cache keys). On gfx950 the real calls at each row's M, its smallest M and a middle M, with bf16 and fp16 output (182 FlyDSL calls), all run from that cache under FLYDSL_RUNTIME_RUN_ONLY=1 and match a torch reference; against an empty cache all 182 miss.
  • pytest tests/: 210 passed, 7 of them in tests/test_flydsl_aot_replay.py. The AOT tests of op_tests/test_gemm_group32_interface.py (3), op_tests/tuning_tests/test_fast_tuning.py::TestFlydslAotReplay (3) and op_tests/test_fhmoe.py -k aot (8) pass. black and ruff at CI's versions are clean.

Earlier revisions (c759a80 and before, FlyDSL 0.4.0, both caches built from empty):

--check of the runtime replay against the cache main's AOT builds:

module rows (+ in-between sizes) main misses
GEMM gfx950 7,982 (+9,146) 2,543 rows (every bf16 hgemm row: the semaphore dtype drift) + 1
GEMM gfx1250 617 (+3,171) 21 rows (all 20 batched mxscale rows have no AOT wiring on main) + 24
conv3d 152 0
chunk_gdn_h 1,344 (+876) 0
fmha_fp8 3,020 (+12,098) 0

Every kernel the replay built compiled, and --check of every module against that cache found 0 misses. On real GPUs with FLYDSL_RUNTIME_RUN_ONLY=1 against the replay-built cache: gfx950 conv 152/152 calls, GEMM sample rows 80/80, fmha 326/326; gfx942 chunk_gdn_h and gfx1250 GEMM and MoE in the comments below.

Notes for reviewers

PR Self-Review

Reviewed revision

d7f84a2

Behavior and intent

The FlyDSL AOT build of the GEMM, conv3d, chunk_gdn_h and FP8 FMHA caches no longer restates the runtime's kernel selection: it calls each op's public entry on FakeTensors in a GPU-less dry run and compiles exactly the launches the runtime makes. The hand-written AOT drifts from the runtime without any report; today every gfx950 bf16 hgemm row misses the cache main builds (int32 vs int64 split-K semaphore), and the 20 gfx1250 batched mxscale rows have no AOT at all. Runtime behaviour is unchanged except that the four blockscale CK/CK-tile fakes now accept their ops' arguments under FakeTensorMode.

Validation and unverified scope

Commands and results are in Validation above. On this revision: tests/test_flydsl_aot_replay.py (5 passed), and black and ruff at CI's versions on every changed file. At c9fba78, before the revert of ce0b847 and the merge with main: an identify-only replay of every default job of the four modules (0 failures); python -m aiter.aot.flydsl.gemm --csv with GLM-5.2's FP32 blockscale tables, from an empty cache and again over it (the 8 artifacts main's AOT builds; the rebuild writes none); a FLYDSL_RUNTIME_RUN_ONLY=1 run of 182 real FlyDSL calls on gfx950 (all hit; all miss on an empty cache); pytest tests/ (210 passed) and the AOT op tests. Unverified on this revision: those c9fba78 results, rerun after the revert and the merge, in particular the replay of the four tables #7309 retuned, which CI's wheel build replays for gfx950; a compiling build of every default table (the box has 2 CPUs; the identify-only walk shows which kernels are requested, not that each compiles, which CI's wheel build does for gfx942 and gfx950), real-GPU runs on gfx942 and gfx1250 (done at c759a80, see the comments), the gfx1250 MoE replay beyond a 298-job sample, and MegaMoE, which needs 8-GPU EP with mori.

PR-specific question

No dependency changes. The cache holds what the runtime requests, keyed as it looks them up: at c759a80 main's build had 1,239 artifacts no runtime path requests (780 of them hgemm under the stale key) and lacked the kernels of 2,543 gfx950 and 21 gfx1250 rows. The replay relies on FlyDSL's private JitFunction._compile_or_execute and _build_full_cache_key at the pinned 0.4.1 (a launch that stops deriving its key there fails with ReplayHookBroken) and forbids CompiledArtifact._ensure_engine. Clean build: every module built from an empty cache, and --check passed on that cache and failed on an empty one (c759a80); at c9fba78 GLM-5.2's FP32 blockscale tables built from empty into the 8 artifacts main builds. Existing cache: a rebuild over that complete cache writes no artifact, and FLYDSL_RUNTIME_RUN_ONLY=1 calls run from it on gfx950.

Changed op tests

  • op_tests/test_gemm_group32_interface.py: its three AOT tests asserted on the hand-written AOT this PR removes (parse_csv's per-kind jobs, _compile_bmm_mfma_to_cache), and three of them failed under pytest. They now replay the rows under dry_run and compare the kernels the replay identifies with those of a call shaped as a caller makes it, which no other test covers. They also clear the _mxscale_bpreshuffle_tuned_nk cache that leaked between tests.

zhiding512 and others added 30 commits October 9, 2026 11:33
_gen_opus_moe_stage1_a8w4_fake_tensors and
_gen_opus_moe_stage2_a8w4_decode_fake_tensors lacked the scalar parameters
of the ops they are registered for, so any call under FakeTensorMode
raised TypeError (10 of 18 and 9 of 14 positional arguments accepted).

Signed-off-by: zhimding <zhimding@amd.com>
The MoE AOT scripts (moe.py, mxfp4_moe.py, fhmoe.py) restated the
runtime's kernel selection and launch parameters, so every kernel or
wrapper change needed a matching AOT change, and drift between the two left
kernels the runtime uses uncompiled.

moe.py now builds what a caller passes fused_moe for each tuned row as
FakeTensors and calls the runtime's own _fused_moe_impl (_fhmoe_impl for
shared-expert rows) inside dry_run, which sets COMPILE_ONLY=1 and forbids
the GPU runtime; every FlyDSL launch on that path is compiled into the cache
instead of run. The AOT side only enumerates what a row does not fix:
expert bias, gate/up layout and SiTUv2 betas on the caller side, and the M
values get_padded_M maps to the row. Each target arch replays in its own
process with the GPUs hidden, and --check verifies a cache without
compiling.

Runtime-side support:
- torch_guard: scalar-returning custom ops can run their implementation
  under FakeTensorMode, whose fakes carry no value (off by default).
- chip_info: with no GPU to detect, a single explicit GPU_ARCHS stands in
  for the live GPU.
- fhmoe: fhmoe_ is split into a plain _fhmoe_impl and the decorated op.

mxfp4_moe.py and fhmoe.py are removed; moe.py replays their rows.

Signed-off-by: zhimding <zhimding@amd.com>
A from-scratch build forked one worker per job (9903) from the full-import
arch process, so it was bound by the parent's fork rate, and the variants of
one row ran concurrently and waited on each other's compile locks. A worker
now replays up to 12 jobs of one row in turn, equal-sized batches are
shuffled so that rows of one model do not start together, and the arch
processes run side by side on a split worker budget.

Fresh full build at 96 workers: 537 s -> 187 s; rebuild with a complete
cache: 157 s -> 21 s. The compiled artifact set is unchanged.

Signed-off-by: zhimding <zhimding@amd.com>
mega_moe.py restated MegaMoEV2's buffer geometry, placeholder tensors,
Stage2 variant expansion and combine geometry lookup, so MegaMoE kernel
changes needed matching AOT changes. It had also drifted: it compiled
only the mask_topk_ids=False combine kernels, so a caller passing
mask_invalid_slots=True compiles online.

Each job now builds MegaMoEV2 for one rank of a deployment profile
inside dry_run, with FakeTensor weights and a mori shmem that hands out
FakeTensors and fake peer addresses, and calls its preload_aot_bundles()
and quantize(). The module keeps only the deployment profiles, and one
job covers both stages, so 192 jobs become 96.

dry_run takes an optional device name, since the combine launch
geometry is tuned per GPU model, and stands in for torch.cuda Stream
and Event.

Signed-off-by: zhimding <zhimding@amd.com>
Merging read each table with type inference, so a column with a blank
cell turned float and every integer in it was written back as N.0:
6 of the 15 merged tables carry such cells, among them tuned_fmoe and
tuned_grouped_fmoe. The gfx1250 grouped MoE runtime parses integers
from that text, so since #6090 added a second grouped table it raises
ValueError on every row of the merged one.

Read the tables as text so the merge reproduces each cell. Shapes are
still compared on numeric values, so 256 and 256.0 stay one shape, and
the fastest duplicate is still picked on a numeric us.

Signed-off-by: zhimding <zhimding@amd.com>
grouped_moe.py compiled only the auxiliary kernels around the grouped
GEMM: the runtime has since moved the GEMM to the TDM batched kernel,
which got no AOT coverage and compiled on first use, and most of the
auxiliary variants it did compile were for the removed non-TDM path.

On gfx1250 _fused_moe_impl hands the grouped table's rows to FlyDSL's
grouped GEMM, so moe.py now replays those rows as well, with the
gate/up-interleaved weights and n32k4 scales gfx1250 callers pass.
Rows tuned for EP only (topk -1, or ep_fused) are skipped: the runtime
picks them only under a caller's expert mask or MegaMoE's stage-2
scatter, and the table records neither the global expert count nor the
real topk. The two-stage host query is no longer preloaded when an
arch replays only grouped rows, since that path never makes it.

grouped_moe.py and OpKind.GROUPED_MOE are removed.

Signed-off-by: zhimding <zhimding@amd.com>
A row serves a bucket of M, and the replay compiled the bucket's two
ends, which covers any choice that switches once. The gfx1250 stage-1
quant's K split switches several times in one bucket (7168x768, E384,
topk 6, token 2048: K split 7, then 4, then the compact quant), so M in
the middle asked for a kernel the cache did not have.

Where a variant's two ends request different FlyDSL kernels, identified
by FlyDSL's own cache key, the worker now bisects the bucket to every M
at which they change and replays it. The default build replays 775
more M (573 on gfx1250, 202 on gfx950) and compiles nothing new on
gfx942/gfx950; a densely sampled RUN_ONLY replay of every bucket now
finds no missing kernel on any of the three archs.

Signed-off-by: zhimding <zhimding@amd.com>
mxfp4_moe.py stays deleted. #5804 taught it the use_valid_token_count
GEMM2 variant, which the runtime selects when the caller passes
num_local_tokens; the next commit has the replay enumerate that caller
choice instead.

Signed-off-by: zhimding <zhimding@amd.com>
Callers behind an EP dispatch pass the received token count as
num_local_tokens. Since #5804 the runtime then bounds the layout-v2
GEMM2 atomics by it, a compile-time choice the old mxfp4_moe.py had to
be taught by hand; on inline-sort rows the runtime instead drops the
tuned config for heuristic FlyDSL kernels that no AOT compiled.

Each row is now replayed with num_local_tokens too. Of the old script's
use_valid_token_count kernels, the replay has every one the runtime
reaches; the 227 it lacks come from bias variants of Silu/SiTUv2 rows
(the runtime enables bias for Swiglu only since #6376), from inline-sort
rows the runtime abandons under num_local_tokens, and from a token=3
row that get_padded_M never selects.

Signed-off-by: zhimding <zhimding@amd.com>
…rch replays

The wheel build's AOT step failed both replays:

- The per-arch MoE replays run side by side and share module_moe_mxfp4_aux,
  whose host query they call. The first to build it built it for its own arch.
  The others then refused it, since the runtime rebuilds a module whose device
  code lacks the running arch, and with the build lock just released did not
  rebuild it either, so the gfx950 replay died. dry_run now skips that check:
  only host code runs under it.
- MegaMoE replayed in setup.py's own process, which loads aiter/jit/core.py a
  second time as jit.core. The first load of a C++ module there is recorded in
  the other copy, so every job raised KeyError: 'module_aiter_core'. It now runs
  in a process of its own, like the MoE replay, with GPU_ARCHS=gfx950 and the
  GPUs hidden. The environment of those processes moves to common.replay_env.

Signed-off-by: zhimding <zhimding@amd.com>
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>
On a host without mori, _fake_shmem installs a stand-in module. A second
call in the same process then asked importlib.util.find_spec("mori"), which
raises ValueError for a module whose __spec__ is None. Reuse the stand-in
instead: it stays, since modules imported under it keep referring to it.
The AOT build runs each job in a process of its own, so it was not affected.

Signed-off-by: zhimding <zhimding@amd.com>
Conflict in aiter/aot/flydsl/mega_moe.py: #4980 added A4W4 MegaMoE and taught
the old script its kernels (quant mode, FP4 output and scale shapes). The
replay needs none of that, since MegaMoEV2 picks them itself; it only adds
"a4w4" to DEFAULT_QUANTS, which takes the default build from 96 jobs to 192.

The A4W4 replay passes all 96 jobs. Every one of the 3218 artifacts the old
script builds for A4W4 is in its cache: 3082 byte-identical, 136 differing
only in v_add3_u32 vs v_add_u32 + v_sub_u32 (2-7 asm lines), the FlyDSL/LLVM
run-to-run variation also seen for A8W4. The other 144 are the
mask_topk_ids=True combine kernels the old script never built.

Signed-off-by: zhimding <zhimding@amd.com>
The per-arch replays import aiter side by side, and the first to take the
build lock built module_aiter_core with its own arch's flags. When that was
the gfx1250 replay, the build turned on cluster launch, which the HIP of
ROCm 7.2 (CI's toolchain) does not declare, and all three replays failed.
The module has no device code, so build it once, for the arch with the most
jobs, before the replays start.

Signed-off-by: zhimding <zhimding@amd.com>
Signed-off-by: zhimding <zhimding@amd.com>
The test cast fused_topk's route weights to bf16, which the gfx1250
gather-reduce required until it learned to read fp32 weights natively
(#4150). Serving passes fp32 route weights, and so does the replay, so
the cache holds only the fp32 gather-reduce and the test, which runs
run-only by default, missed launch_moe_gather_reduce on every row.

The bf16 and f16 weights the EP route kernel writes for gather-reduce
are still exercised, now by the gather-reduce sweep of
test_flydsl_moe_aux.py, which until now read only fp32 weights.

Signed-off-by: zhimding <zhimding@amd.com>
Signed-off-by: zhimding <zhimding@amd.com>
#6589 made get_dtype_fp8() return uint8 whenever torch sees no device.
The MoE replays hide the GPUs and name their target in GPU_ARCHS, so
dtypes.fp8 became uint8 in every replay: fp8 activations failed
`q_dtype_a == dtypes.fp8` and were quantized to fp4, MXFP8 rows found no
input recipe, and the gfx1250 grouped rows compiled other kernels than
the runtime requests without raising anything.

Without a visible device, a single explicit GPU_ARCHS target now stands
in for the GPU, as it already does in chip_info.get_gfx_runtime(); the
runtime detector is still not imported.

Signed-off-by: zhimding <zhimding@amd.com>
Since #6478 hands a caller's stage-1 bias to the CK-Tile split-k route,
MXFP4 callers with gate/up-interleaved weights and a bias reach its
interleaved epilogue, which cannot apply one. That is a missing feature,
not a bad argument, so raise NotImplementedError like fused_moe's other
unsupported combinations; the MoE replay records such caller-side
variants as unsupported rather than failing on them.

Signed-off-by: zhimding <zhimding@amd.com>
The Qwen3 8x2400B fp4 row tuned at token 32768 stages a 5.4 GB
route-reduce output, beyond the 32-bit buffer addressing #6050 now
guards: the runtime raises NotImplementedError before it launches
anything, and op_tests/test_moe_2stage.py skips the case. The replay
failed the build on it although there is nothing to compile.

When the runtime serves a row's default caller at some of the row's
token counts but refuses it at others, the refused token counts are now
reported as [REFUSED]. Refused at every token count, the row still
fails, as does any other error.

Signed-off-by: zhimding <zhimding@amd.com>
A replay process learned its target arch only indirectly: replay_env hid
the GPUs and set GPU_ARCHS, and get_gfx_runtime() and get_dtype_fp8()
fell back to a single GPU_ARCHS entry whenever no GPU was visible. A
replay process that could see a GPU took the machine's arch instead
(gfx950's fp8 type for gfx942 kernels), and the fallback changed both
functions for every GPU-less process with one GPU_ARCHS entry, which
main's test_linux_runtime_errors_are_not_fallbacks rejects.

replay_env now also sets AITER_AOT_REPLAY=1. Only under that flag do
get_gfx_runtime() and dtypes.fp8 answer the one GPU_ARCHS arch, without
probing a device; everywhere else they behave as on main. A replay
process checks at entry that it was started that way and that
dtypes.fp8 is its arch's type.

The parent replays only the archs GPU_ARCHS names, as setup.py's wheel
build sets it, intersected with the tables' gfx column; unset or native,
every arch the tables have. CI's gfx942;gfx950 build thus no longer
compiles gfx1250 MoE kernels, and MegaMoE runs only when gfx950 is
listed.

Signed-off-by: zhimding <zhimding@amd.com>
The wheel build replays only the archs GPU_ARCHS names, so a gfx942;gfx950
build leaves the gfx1250 rows out; GPU_ARCHS=gfx1250 builds them on their
own. That build imports aiter and so needs its C++ core module, which
ROCm 7.2.4 cannot build for gfx1250 (its HIP lacks the cluster launch
API); the module has host code only, so one built for a CDNA arch serves.

Signed-off-by: zhimding <zhimding@amd.com>
Silu/SiTUv2 never pass expert bias at serve time, and INTERLEAVE x bias
hits CK-Tile's unsupported split-k epilogue, so keep those out of the
replay instead of widening the runtime to report them as unsupported.

Signed-off-by: zhimding <zhimding@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2f85842 dropped every gate_mode=interleave x bias variant from the
replay to keep CK-Tile's interleaved split-k epilogue, which cannot
apply a bias, out of it. The same commit limits bias to Swiglu + MXFP4
rows, and since then no row the replay parses reaches that epilogue:
the only Swiglu MXFP4 rows with a cktile_ stage 1 are the 14
gptoss_fp8fp4 rows tagged flydsl_fallback, which parse_csv skips.

What the filter did drop is the path GPT-OSS a8w4 serves on gfx950
(gate/up-interleaved weights with expert bias), and every biased
gfx1250 caller, which takes only the interleaved layout. The runtime
serves all of those jobs and requests kernels the cache then lacked:
CI's MI35X shard failed op_tests/test_moe_2stage.py on the
gptoss_fp8fp4 rows with "no usable AOT cache for
launch_mixed_moe_gemm1".

A full replay of every arch now has 9440 jobs (324 more) and no
failure, and its cache gains 31 kernel directories and loses none. The
extra [SKIP]s are MiniMax-M3 a4w4 interleave x bias combinations at
inter_dim 384, which the runtime refuses for want of a safe MXFP4
backend, as it already does for their other variants.

Signed-off-by: zhimding <zhimding@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
A replay process imports aiter for the arch it replays, so its
dtypes.fp8 matches the table's fp8 flavour. The pytest process imports
aiter for its host instead: on gfx942 dtypes.fp8 is float8_e4m3fnuz,
_caller_inputs finds no recipe for the HY4 float8_e4m3fn row, and both
cases of test_fhmoe_aot_replay_forwards_caller_choices failed on CI's
MI300X shard. Pin the alias as _mock_gfx950_fhmoe_metadata does.

Signed-off-by: zhimding <zhimding@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Reading tuned tables as text keeps every cell as written, but
_raise_on_duplicate_shapes still sorted on `comparable`, where a column
with one unparseable written cell stays text. One us written as nan, on
any row, made the lowest-us pick a text sort: "10.5" sorts before
"9.5", so the slower duplicate was kept and the faster row deleted from
its source table, where main, parsing nan as NaN, keeps 9.5. Coerce us
on its own.

op_tests/tuning_tests/test_config_shape_collision.py also pins the two
behaviours the text merge exists for: integer cells of a column with a
blank stay integers in the merged table, and 256 and 256.0 stay one
shape.

Signed-off-by: zhimding <zhimding@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

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.

🔵 Needs a closer look

Human assessment is needed because stale device-info caching across CU-count changes can select unintended kernel configurations and produce misleading build-coverage results.

No open issues 🎉

Review effort: Balanced

zhiding512 and others added 4 commits November 2, 2026 15:06
Conflicts:

- aiter/aot/flydsl/gemm.py: #5969 taught the hand-written AOT the gfx950
  FP32-scale blockscale kernels (flydsl_blockscale_8w_*), which it compiled
  for every flydsl row of a8w8_blockscale_tuned_gemm and
  a8w8_blockscale_bpreshuffle_tuned_gemm, with bf16 and fp16 output. The
  replay keeps no per-family code; a later commit replays those two tables
  through gemm_a8w8_blockscale and gemm_a8w8_blockscale_bpreshuffle instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
gemm_a8w8_blockscale_ck, gemm_a8w8_blockscale_cktile and their bpreshuffle
counterparts shared one fake that takes only the five tensors. The tuned
dispatch passes each of them kernelName, and the non-preshuffled ones
splitK, so under FakeTensorMode, as in torch.compile or the AOT replay's
dry run, all four raised TypeError on such a call. Each op now has a fake
of its own signature, as the file's other ops do.

A later commit replays the FP32-scale blockscale tables, which reaches
gemm_a8w8_blockscale_bpreshuffle_cktile at 17 untuned sizes inside FlyDSL
rows' ranges, where the runtime's padded M finds a CK-tile row.

Co-authored-by: Cursor <cursoragent@cursor.com>
Since #7219 the FlyDSL MoE route, quant and grouped GEMM launches pass the
raw stream handle (tensor_shim.current_stream_arg), taken from the torch._C
query tensor_shim resolves at import rather than through torch.cuda. With
the GPUs hidden that query raises AcceleratorError (no ROCm-capable device),
so every gfx1250 MoE replay job failed, on main too: a sample of 298 jobs
in 47 batches failed all 298. The dry run now answers the query with stream
0, as it does the ctypes path's getter, and the same sample replays its 298
rows and 404 sizes between them. A gfx950 sample (246 jobs) does not reach
the query and replays as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
Since #5969, gemm_a8w8_blockscale and gemm_a8w8_blockscale_bpreshuffle
serve gfx950 FlyDSL rows with FP32 128x128 scales (flydsl_blockscale_8w_*):
6,513 rows of a8w8_blockscale_tuned_gemm in 63 shapes and 670 of
a8w8_blockscale_bpreshuffle_tuned_gemm in 76. Main's hand-written AOT
compiled their kernels; the merge dropped that code with the rest. Both
tables are now replayed through their public entries, each row with bf16
and fp16 output: the rows leave it open and the kernels specialize on it,
as main's AOT compiled both. That is 14,366 of the default build's 22,878
gfx950 GEMM jobs.

For GLM-5.2's two tables (27 and 6 FlyDSL rows) the replay builds the same
8 artifacts main's AOT builds, under the same launcher directories and
cache keys, and a rebuild over them writes none. On gfx950 the real calls
at each row's M, the smallest M it serves and one between, with bf16 and
fp16 output (182 FlyDSL calls), run from that cache under
FLYDSL_RUNTIME_RUN_ONLY=1 and match a torch reference; against an empty
cache all 182 miss. An identify-only walk of every default GEMM job
replays all 22,878 gfx950 and 1,500 gfx1250 rows with no failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI balanced review requested due to automatic review settings November 2, 2026 15:26

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.

🟡 Changes recommended

FlyDSL tracing or compilation errors can be counted as successful skips, allowing builds to pass without required artifacts and failed kernels to be treated as already built.

1 open issue

Review effort: Balanced

Comment thread aiter/aot/flydsl/replay.py Resolved
zhiding512 and others added 3 commits November 3, 2026 03:41
…back paths"

This reverts commit ce0b847, so the replay no longer changes the gfx1250
asm gate: aiter/jit/utils/asm_guard.py stays as on main, and the dry run's
Triton stand-in raises a plain RuntimeError again. replay_job tells a call
another backend takes by the traceback frames in Triton's code and
asm_guard.py, as before, still only when the call requested no FlyDSL
kernel, did not pass through FlyDSL code, and raised neither
GpuRuntimeReached nor ReplayHookBroken.

The raw stream query test db4ae7e added next to the reverted tests stays.

Co-authored-by: Cursor <cursoragent@cursor.com>
op_tests/tuning_tests/test_fast_tuning.py is back as on main, without
the class's three cases (the per-arch worker waves, a GEMM table found by
its AITER_CONFIG_* variable under any file name, and the FP8 FMHA
launchers pinned through the wrapper's overrides) and the shutil and
tempfile imports only they used.

Co-authored-by: Cursor <cursoragent@cursor.com>
Conflicts:

- aiter/aot/flydsl/gemm.py: #7309 taught the hand-written AOT the gfx950
  MX preshuffle prefetch and b_nt kernels (_pf, _nt) and both l2_reduce
  grids of a fused split-K row, and #6767 let its gfx1250 A16W16 path take
  gfx1250-strict rows. The replay keeps no per-family code:
  run_gemm_a8w8_mxfp8_gfx950, which serves every table #7309 retuned,
  reads prefetch and b_nt off the tuned kernelName itself, and
  flydsl_mxscale_preshuffle_gemm picks l2_reduce from M, so it switches at
  most once in a row's range, where the replay bisects. No tuned table has
  a gfx1250-strict row.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 03:48

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.

🟡 Changes recommended

Replay handling can incorrectly accept unrelated Triton exceptions as successful checks, while multi-file configuration overrides cause valid source CSVs to be rejected.

2 open issues

Review effort: Balanced

Comment thread aiter/aot/flydsl/replay.py Resolved
@zhiding512
zhiding512 force-pushed the zhiding512/flydsl-aot-replay-ops-20261026 branch from d7f84a2 to 2ce2911 Compare November 3, 2026 09:02
@coderfeli
coderfeli merged commit 78e18eb into main Nov 3, 2026
60 checks passed
@coderfeli
coderfeli deleted the zhiding512/flydsl-aot-replay-ops-20261026 branch November 3, 2026 14:08
zufayu added a commit that referenced this pull request Nov 4, 2026
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>
fsx950223 added a commit that referenced this pull request Nov 4, 2026
Resolve conflicts with #6956 (AOT caches built by replaying the runtime)
and #7079 (gfx1250 PA decode partition merges).

- aiter/aot/flydsl/pa_decode_plan.py now follows the replay protocol
  instead of the removed OpKind/compile_one_config dispatch: it replays
  plan_pa_decode on FakeTensors at the largest batch of each bucket and
  at each capacity where the block size can change, dense and windowed.
  Each arch replays with the most CUs of its devices, so the set equals
  the previous plan_variants minus the variants no device can reach
  (e.g. bucket 1 at 1024 threads). common.py registers it in AOT_MODULES;
  arch selection moves from --arch to GPU_ARCHS, as for every module.
- README: main's module table and GPU_ARCHS wording, plus the planner
  entry and section.
- op_tests: #7079 dropped test_pa_decode_plan; keep this PR's version
  alongside main's new tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IzacharyI added a commit to IzacharyI/aiter that referenced this pull request Nov 4, 2026
The AOT GEMM module now replays each tuned row through the public
gemm_a16w16 entry (ROCm#6956), so it no longer dispatches on a per-kernel
kind. Drop the skinny registration it needed: tuned_gemm already routes
a psk row into flydsl_hgemm, which the replay reaches on its own.
IzacharyI added a commit to IzacharyI/aiter that referenced this pull request Nov 4, 2026
The AOT GEMM module now replays each tuned row through the public
gemm_a16w16 entry (ROCm#6956), so it no longer dispatches on a per-kernel
kind. Drop the skinny registration it needed: tuned_gemm already routes
a psk row into flydsl_hgemm, which the replay reaches on its own.
huizzhan added a commit that referenced this pull request Nov 4, 2026
main gained #6956, which rewrote aiter/aot/flydsl/conv.py to build the AOT
cache by replaying the runtime. That deleted the hand-enumerated job and
per-kernel compile machinery this branch had extended for gfx1250, so all
six hunks conflicted; take main's file outright.

The gfx1250 compile keys now come from replaying flydsl_conv_implicit, which
already owns the channel pad (CGP_ALIGN), the split-K resolver and the
dyn_hw gate, so this branch's _conv_dedupe_key, _probe/_conv_probe_args and
_compile_conv3d_gfx1250_to_cache paths have no remaining caller.

Checked on gfx1250 (one card): replay builds 10 artifacts for a 5-row tuned
table, --check passes, and every entry point of those rows (2 input x 2
output layouts, bias omitted where the table has none) hits under
FLYDSL_RUNTIME_RUN_ONLY=1.

Numerical accuracy was not re-validated here: this container's toolchain
reproduces the same op-test failures as the pre-merge head, so the merge is
behaviour-neutral but the head's accuracy is still open.
yuychang added a commit to yuychang/aiter that referenced this pull request Nov 4, 2026
- Resolve aiter/aot/flydsl/gemm.py to main: ROCm#6956 replays the runtime's gemm_a16w16, so AOT already uses int64 semaphores
- Drop alloc_split_k_buffers: no AOT caller is left, so gemm_a16w16_gfx950.py matches main again
- Drop the cache-key test of the removed _compile_hgemm_to_cache; key equality is now by construction
- Rework the RUN_ONLY test: AOT-build one split-K A16W16 row through the replay driver, then run gemm_a16w16 from it
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants