Skip to content

[FLYDSL] Support prefill GDN K5 fp32 chunk states - #4732

Open
huizzhan wants to merge 24 commits into
mainfrom
dev/huizzhan/flydsl_k5_fp32_snapshot_v2
Open

[FLYDSL] Support prefill GDN K5 fp32 chunk states#4732
huizzhan wants to merge 24 commits into
mainfrom
dev/huizzhan/flydsl_k5_fp32_snapshot_v2

Conversation

@huizzhan

@huizzhan huizzhan commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

fp32 chunk snapshots (K5 mfma16_hip fork)

  • snapshot_dtype decoupled from state_dtype, defaulting to k.dtype like the HIP path
  • fp32 snapshots go out in two half-BV rounds through the transpose buffer, so the
    f32 tile stays within the LDS budget; the bf16 specialization is byte-identical

Host overhead on the K5 wrapper

  • stop re-reading chunk_offsets every forward (the .tolist() D2H was stalling the
    launch stream: 169-384us host gap -> 1.7us when prefill metadata is passed through)
  • allocate the null argument slots instead of casting into them
  • cache the int32 offsets on the baseline FlyDSL path too

AOT coverage for the kernel production actually dispatches

  • new aiter/aot/flydsl/chunk_gdn_h_mfma16_hip.py: the previous module only covered the
    baseline chunk_gdn_fwd_h_flydsl_vk, so the mfma16_hip fork K5 dispatches had zero
    cache overlap and still paid a 2.3s cold JIT
  • shapes come from a csv that doubles as the runtime BV table, so adding a model is a
    csv edit; each shape fans out over every legal BV, both snapshot/state dtypes, both
    g-layouts, and state-pool indices where the row allows one (240 jobs, 8.1s)

Performance

K5 device kernel time (us) via torch.profiler, one gfx942, SeqLen=8192,
sweeping max_num_batched_tokens. fly/hip > 1 means FlyDSL is faster.

varlen-qwen-ali-tp1 (TP1, Hg=16, H=32)

T FlyDSL bf16 FlyDSL fp32 fp32 cost HIP bf16 HIP fp32 fly/hip bf16 fly/hip fp32
8192 478.6 510.9 +6.7% 508.6 612.4 1.06x 1.20x
16384 958.1 1010.8 +5.5% 1013.7 1214.8 1.06x 1.20x
24576 1414.4 1512.3 +6.9% 1500.5 1826.4 1.06x 1.21x
32768 1919.3 2032.2 +5.9% 2006.9 2435.7 1.05x 1.20x
40960 1951.5 2040.9 +4.6% 2017.5 2452.2 1.03x 1.20x
49152 2380.8 2542.1 +6.8% 2514.3 3042.5 1.06x 1.20x
57344 2843.1 3054.0 +7.4% 3003.8 3650.1 1.06x 1.20x
65536 3404.2 3607.5 +6.0% 3530.7 4267.1 1.04x 1.18x

varlen-qwen3.5-397b-ptpc-ali (TP8, Hg=2, H=8)

T FlyDSL bf16 FlyDSL fp32 fp32 cost HIP bf16 HIP fp32 fly/hip bf16 fly/hip fp32
8192 237.7 244.9 +3.0% 269.7 264.7 1.13x 1.08x
16384 304.0 298.9 -1.7% 349.1 372.2 1.15x 1.25x
24576 448.2 461.6 +3.0% 492.4 602.8 1.10x 1.31x
32768 462.6 497.0 +7.4% 513.1 605.7 1.11x 1.22x
40960 483.9 505.2 +4.4% 511.3 611.5 1.06x 1.21x
49152 916.6 955.1 +4.2% 995.7 1185.3 1.09x 1.24x
57344 930.0 974.9 +4.8% 1001.8 1193.4 1.08x 1.19x
65536 945.2 984.8 +4.2% 1004.5 1195.3 1.06x 1.21x

FlyDSL pays 3-7% for fp32 snapshots where HIP pays 19-22%. Triton is 2.0-3.6x
slower than HIP on both dtypes and is omitted from the tables. The two TP8 rows
where HIP fp32 lands at or below its own bf16 (T=8192, 16384) are noise on
those points; every other length shows the same ~20% HIP fp32 cost.

Block level

bench_gated_delta_rule_block.py runs the same shapes through
chunk_gated_delta_rule_opt_vk, so the snapshot policy is also visible in K6
(it reads the snapshot back) and in the wall-minus-device gap. One gfx942,
varlen-qwen-ali-tp1 T=8192, fp32 snapshots, wall = median of 50 CUDA-event
timings, device = 20-iter profiler self time:

stage (us) FlyDSL FlyDSL no-meta HIP HIP no-meta
K1+K2 cumsum/KKT 150.9 150.6 150.5 150.6
K3 solve_tril 422.6 421.6 421.9 421.3
K4 W/U 310.5 310.1 309.7 310.2
K5 state scan 506.4 506.9 609.4 608.0
K6 output 842.5 843.2 842.0 839.4
other 0.0 0.0 0.0 11.9
kernel total 2232.9 2232.3 2333.4 2341.4
wall total 2235.7 2233.3 2335.1 2510.8
host gap 2.8 0.9 1.7 169.4

With one prebuilt metadata the whole 100us block gap is K5, i.e. the store
coalescing; K6 is identical because both backends hand it the same fp32
snapshot (fp32 costs K6 ~23% over bf16 on this shape, for either backend).
The no-meta columns are the same run with prefill_metadata=None: the HIP
wrapper then rebuilds the chunk schedule per forward and reads chunk_offsets
back with a blocking D2H, worth 169us of host gap plus ~12us of schedule
kernels, while the FlyDSL wrapper caches that result on the cu_seqlens
tensor. That gap is wrapper-side and orthogonal to this PR's kernel work; the
same caching still wants to land on the HIP wrapper.

Host-side commits, measured on the same shape before the store work: caching
the chunk counts instead of reading chunk_offsets.tolist() per forward closed
a 66us launch gap (block 738 -> 667us, Memcpy DtoH gone from the profile),
and allocating the null slots instead of casting into them dropped two copy
kernels per launch.

Test plan

  • op_tests/flydsl_tests/test_flydsl_linear_attention_prefill.py (correctness + e2e dispatch, 111 passed)
  • op_tests/test_gated_delta_rule.py -k "indexed or flydsl or state_pool" (19 passed)
  • full AOT compile (240/240 ok), then every tuned shape re-run under
    FLYDSL_RUNTIME_RUN_ONLY=1 to prove none of them fall back to the JIT

huizzhan and others added 16 commits August 13, 2026 09:51
Align the FlyDSL mfma16/HIP K5 fork with the HIP kernel's snapshot-dtype
policy: `snapshot_dtype` defaults to `k.dtype` and is independent of
`state_dtype`, so fp32 per-chunk snapshots become an opt-in that no longer
has to be rejected on the FlyDSL dispatch path.

fp32 snapshots store the f32 accumulator cells directly, the same trade-off
the HIP `SNAPSHOT_BF16=false` specialization makes. Routing them through the
existing [V][K] LDS transpose buffer is not an option: an fp32 buffer adds
8-16 KB and the bf16 layout already sits at the 64 KB limit at BV=64. The
bf16 specialization keeps its LDS transpose and coalesced b128 store
unchanged, and the fp32 one reclaims that buffer.

Co-authored-by: Cursor <cursoragent@cursor.com>
`PrefillGroup` / `PrefillArgs` gain a `snapshot_dtype` field (None -> k.dtype,
i.e. the bf16 specialization), passed to the FlyDSL, HIP and Triton calls in
the correctness sweep and the perf comparison. A group now selects fp32
snapshots with one field, the pytest id gains a `_snapFP32` suffix, and the
perf table gains a `snap` column so both specializations are distinguishable
in one run.

The varlen-64k qwen-ptpc rows are duplicated into bf16/fp32 snapshot pairs (and
renamed to name the model they came from) to track the fp32 store path's cost
against the bf16 one.

Co-authored-by: Cursor <cursoragent@cursor.com>
The K5 BV selector needs total_chunks and max_seq_chunks and took them from
chunk_offsets.tolist() on every forward.  That blocking D2H sits between the
K1..K4 launches and K5's, so the host waited for the whole front end to retire
and the GPU then idled through the remaining host work for K5 and K6 rather
than the host running ahead.  The copy itself is 4us; the bubble it opened was
66us of a 738us block.

Prefer the host-side counts prebuilt metadata already carries
(GatedDeltaRuleChunkSchedule.total_chunks / .max_seq_chunks, derived from
seq_lens_cpu).  The old call sat outside the prefill_metadata branch, so
passing metadata did not avoid the transfer -- the case #4532 missed here.
Without metadata, cache the counts on chunk_offsets itself, the same
tensor-attribute pattern as _as_int32 and _resolve_prologue: it comes from the
tensor_cache'd prologue, so its identity is stable across forwards and the
counts cannot change underneath the cache.

varlen-64k-qwen-ptpc-ali T=8192 (Hg=2, H=8, packed varlen with state I/O, one
80-CU gfx942): the block goes 738.1us -> 667.5us, its launch gap 66.1 -> 0.7us,
and Memcpy DtoH disappears from the profile.  The 79 non-perf cases in
test_flydsl_linear_attention_prefill.py are unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 24b6f80)
…them

The mfma16_hip launcher built its int32 placeholders by casting the float32
one -- `dummy.to(torch.int32)` for the state_indices slot on every forward
that does not use a state pool, and again for cu/chunk_offsets on the dense
path. The kernel reads nothing but the base pointer of a disabled slot, so
each cast launched an elementwise kernel to copy the single element it never
looks at. torch.empty gives the same valid non-null address with no launch.

Profiled at ~5.2us per forward on gfx942, all of it launch overhead rather
than the 4 bytes: 1% of a T=8192 varlen GQA block and constant in T. The
block bench loses a kernel from every FlyDSL K5 path -- 670.0 -> 665.9us
with the Triton prepare pair, 510.8 -> 508.2us with the fused prepare.

Tests: op_tests/flydsl_tests/test_flydsl_linear_attention_prefill.py 79
passed (the state-pool paths pass a real index array and are untouched),
op_tests/test_gated_delta_rule.py 142 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 723bd83)
The mfma16_hip fork already narrows cu_seqlens and chunk_offsets through
_as_int32, which caches the result on the source tensor, and takes its null
slots from an allocation. The baseline wrapper still cast on every call: two
copy kernels per forward on a varlen batch and two more for the dense
placeholders, for slots whose base pointer is all the kernel reads.

Measured on the mfma16_hip fork, the same placeholder cast cost ~5.2us per
forward, all launch overhead. Not measured here: this wrapper's kernel uses
mfma.f32.16x16x32.bf16, so it cannot be built on gfx942 (LLVM cannot select
the intrinsic) and it has no in-tree caller, which leaves it outside the
test suite. The change is a static equivalence -- _as_int32 returns an
already-int32 tensor untouched and otherwise caches the narrowing, and both
spellings of the placeholder are a one-element int32 tensor -- but it wants
a run on gfx950 to confirm.

The aiter HIP K5 path is unaffected, as it does not enter this module:
use_chunk_hip=True profiles identically before and after across dense and
varlen, same kernels and bit-identical outputs.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 79adfdd)
… buffer

fp32 snapshots stored straight from the f32 accumulators, mirroring the HIP
kernel.  That layout is the problem: an MFMA C fragment owns 4 contiguous K per
lane and 16 K (64 B) of a V row per wave, and the neighbouring 64 B belongs to
another wave, so one buffer_store_dwordx4 turns into 16 half-line requests
where a coalesced store issues 8 full ones.  Re-pointing the store at addresses
that cover whole 128 B lines (wrong data placement, perf probe only) took the
kernel 630 -> 508us, so 122 of the 146us fp32 sat above bf16 was the request
count, not the 2x bytes; 8x128 B and 2x512 B per wave measured identical, so
reaching a full line is all that matters.  Ablating the snapshot store entirely
lands at 400us, and moving it later in the chunk (638us) or turning
SCHED_GFX942 off (630us, i.e. its 27us GEMM1 win erased) does not help.

Route fp32 through the same [V][K] transpose buffer bf16 uses.  The f32 tile is
twice the bytes and the bf16 layout already sits exactly at the 64 KB LDS
limit, so it goes out in two half-BV rounds.  Both rounds stage while the
accumulators still hold the chunk-start state, and each is placed where the
loop already has the barrier it needs -- round 0 before the top-of-loop publish
barrier, round 1 after GEMM1's WAR barrier (which also retires round 0's
reads) -- so the split costs no extra barrier.  Each flush follows a GEMM so
the MFMA chain covers the ds_read latency, worth another 21us over flushing
both rounds back-to-back at the top.  LDS returns to the bf16 layout's 64 KB,
occupancy unchanged at 1 workgroup/CU.

varlen-qwen-ali-tp1 T=8192 (Hg=16, H=32, one gfx942): fp32 631 -> 512.9us
against 483.2us for bf16, +30% -> +6%; HIP fp32 is 609.6us, so fly/hip goes
0.97x -> 1.19x.  Over both snapshot perf groups (8 batched-token lengths each)
the fp32 overhead is 3-7% where HIP pays 19-22%, and fly/hip is 1.18-1.31x.
bf16 is byte-identical and measures unchanged (483.2 vs 483.8us).  SCHED_GFX942
matters again for fp32 too: 514.5 vs 546.5us with it off.

The 110 non-perf cases in test_flydsl_linear_attention_prefill.py pass, as do
the 71 snapshot/fp32 cases with FLYDSL_K5_MFMA16HIP_BV forced to 32 (two
rounds, one cell each) and 16 (single round).

Co-authored-by: Cursor <cursoragent@cursor.com>
The four snapshot groups sweep max_num_batched_tokens from 8192 to 65536, so
"varlen-64k-" named only the last row.  Use the model name alone.

Co-authored-by: Cursor <cursoragent@cursor.com>
The harness let every backend rediscover the chunk schedule.  FlyDSL caches
that host-side result on the cu_seqlens tensor, so it pays it once for the
whole sweep; the HIP wrapper rebuilds it per forward and reads chunk_offsets
back with a blocking D2H, and the triton path builds its own.  The reported
number is K5 device self time, so this mostly leaks in as launch-stream stalls
and a few schedule-building kernels rather than as K5 time, but it makes the
backends differ in host caching on top of the kernel work the table is meant
to compare -- and it is not what a serving stack does, which builds one
GatedDeltaRulePrefillMetadata per forward and reuses it across the layer stack.

Build the schedule once per shape and pass it to all three backends (the
file's HIP adapter grows the pass-through argument).  Dense shapes get None:
without cu_seqlens the wrappers take the layout from the tensor shapes and
there is no schedule to prebuild.  Every backend now also runs
metadata.validate() on the same object, so the sweep covers that they agree on
one schedule.

varlen-qwen-ali-tp1 T=8192 fp32 is unchanged within noise (FlyDSL 510.2us, HIP
607.0us); at block level the same change takes HIP's host gap from 169 to
1.7us.

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

bench_gated_delta_rule_snapshot_dtype.py times K5 and K6 in isolation, and the
K5 test harness reports K5 device self time.  Neither answers what the fp32
snapshot costs the layer a serving stack actually runs: K6 reads the snapshot
back, so its 2x bytes land there too, and a wrapper that stalls the launch
stream shows up in neither.

Run chunk_gated_delta_rule_opt_vk and split the result into wall time (median
of CUDA-event timings), profiler self time bucketed into K1..K6, and the wall
minus device gap that isolates launch overhead.  Shapes come from the K5
suite's PrefillArgs cases, selected by their pytest ids (--list-cases), so a
block row and a K5 row describe the same workload instead of a second copy of
the shape table that can drift.  The block entry always cumsums a gate, so the
suite's no-g cases (which cover K5's padding masking) are skipped.

The block is timed with a prebuilt metadata by default, the way a serving
stack reuses one schedule across the layer stack; --without-metadata adds the
None column that exposes the chunk-schedule D2H as host gap.

varlen-qwen-ali-tp1 T=8192 fp32 snapshots, one gfx942:

  stage (us)          flydsl  flydsl no-meta     hip  hip no-meta
  K5 state scan        506.4           506.9   609.4        608.0
  K6 output            842.5           843.2   842.0        839.4
  wall total          2235.7          2233.3  2335.1       2510.8
  host gap               2.8             0.9     1.7        169.4

so with the schedule prebuilt the block gap is the ~100us of K5, and HIP's
remaining 169us is its per-forward chunk_offsets readback, which the FlyDSL
wrapper already caches.

Co-authored-by: Cursor <cursoragent@cursor.com>
The example -k filter named varlen-64k-qwen-ptpc-ali, which the group rename
removed, so copying the line selected nothing.  Use one of the current
snapshot groups.

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

Sweeping several PrefillGroups printed one stage table per case, which does
not read across cases, and the host gap alone was ambiguous: the TP8 T=8192
rows showed 100-400us of it, which looks like the wrapper stall the
metadata=None runs expose but is not.

Add a cross-case summary once more than one case runs: cases down (one row per
snapshot dtype, so mixing bf16 and fp32 groups keeps the columns dense),
backend x metadata variant across, one table per metric, plus a speedup column
against HIP under the same variant, >1 meaning the other backend is faster.
--summary-metrics picks the metrics, --summary-only drops the per-case tables.

Add `launch only`, the median python-side cost of one call measured without
syncing, which is what reads the host gap. It is a floor on wall time: at
TP8 T=8192 the block needs ~750us of host per call against 678us of device
work, so wall lands at ~800us for both backends and the gap is that shortfall,
not a stall. With `prefill_metadata=None` the HIP wrapper's blocking
chunk_offsets readback instead takes `launch only` from 780 to 2504us, i.e. up
to wall, which is what a stall looks like.

Co-authored-by: Cursor <cursoragent@cursor.com>
The mfma16_hip fork sizes its value tile from a runtime heuristic alone, so a
shape the heuristic reads wrong cannot be corrected without patching the rule.
Let the wrapper consult a tuned table first and fall back to the rule for
everything the table does not cover. The key is built from host-side
quantities only, so the probe stays one dict lookup (~0.09us against ~46us of
wrapper host time) and never forces a device-to-host copy; the table is read
once at import with stdlib csv rather than lazily, keeping the file read out of
a live server's first prefill.

The seeded rows come from the varlen-qwen3.5-397b-ptpc-ali and
varlen-qwen-ali-tp1 sweeps, measured by the accompanying tune script. On those
the rule already picks the optimum for all 32 combinations, so the table
changes no selection today -- it pins that result against future regressions
and records the K5 time each tile achieved.

Co-authored-by: Cursor <cursoragent@cursor.com>
aiter.aot.flydsl.chunk_gdn_h covers the baseline chunk_gdn_fwd_h_flydsl_vk,
but use_chunk_flydsl dispatches the mfma16_hip fork -- a separate compiled
product with no AOT coverage at all. Every process therefore JITs it on the
first prefill (2.3s cold), and JITs again mid-serving whenever a batch shifts
the selected BV or a caller switches snapshot/state dtype.

Add an AOT kind driven by the same tuned table the runtime reads. The table's
BV column deliberately does not narrow the fan-out: shapes it does not cover
fall back to the CU/LDS rule and can land on any tile, so every legal BV is
built, as are both dtype specializations. That is 24 jobs for today's two
shapes, 2.4s of build time, and it takes the first call down to ~0.1-0.3s with
FLYDSL_RUNTIME_RUN_ONLY=1 passing on off-table shapes too.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the K5 table with the third case family, which unlike the two already
in it batches ragged segments (33/7/4 sequences under a fixed 32k token
budget), exercising the max_seq_chunks half of the lookup key. BV=64 wins all
three, again matching what the rule already picks.

Co-authored-by: Cursor <cursoragent@cursor.com>
The two Qwen3.5-* prefill groups run dense with output_final_state=False,
which the tuned table could not express: use_h0/store_fs were neither part
of the lookup key nor of the AOT fan-out, so a dense no-final-state row
would have collided with an existing varlen row and its kernel would still
have hit the JIT. Add both switches as columns, key on them, and fan the
AOT jobs over the values each row asks for (24 -> 84 jobs).

Qwen3.5-35B at TP2/T2500 is the first shape where the CU/LDS rule is
wrong: BV=64 measures 144.3us against 147-150us for the rule's BV=32,
repeatable across three 200-iteration runs. The other 19 shapes confirm
the rule.

Rows can now describe the same physical batch shape under two model names
(varlen-32k-qwen at TP8/T8192 is the same 4x8192 batch as the 397b-ptpc
mnbt32768 row), so warn instead of silently overwriting when two rows
disagree on BV.

Co-authored-by: Cursor <cursoragent@cursor.com>
…er it in AOT

chunk.py rejected initial_state_indices / inplace_final_state whenever
use_chunk_flydsl was set, even though the K5 wrapper implements both: it
gathers each sequence's slot on read and writes the final state back into
the pool in place. Drop the guard and forward both arguments, matching what
the HIP branch already did.

That makes USE_STATE_INDICES reachable, so AOT fans out over it on the rows
that permit a pool (use_h0 and store_fs both set; the wrapper rejects the
rest). Also fan out G_HEAD_MAJOR: chunk.py pins it True, but False is the
wrapper's own default, so a caller reaching the wrapper directly was the one
combination AOT skipped. 84 -> 240 jobs, 8.1s.

Remaining pinned switches are unreachable through the production dispatch:
g is always passed, gk never is, v_new is always saved, use_exp2 pre-scaling
is on, wu_contig is hardcoded at the call site, and bf16_convert_trunc only
flips in a unit test that checks RNE rounding.

Co-authored-by: Cursor <cursoragent@cursor.com>
@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: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; 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 4732 --add-label <label>

- EXE001: the new AOT module carries a shebang like every other module in
  aiter/aot/flydsl, so give it the executable bit those have (chunk_gdn_h.py,
  common.py, moe.py, fhmoe.py). gemm.py and grouped_moe.py miss it too, but
  they are outside this PR's diff.
- I001: let ruff sort the tune script's imports, which splits the aliased k5
  import into its own statement.
- C408: dict() -> a literal in the tune script's kernel-argument builder.

Co-authored-by: Cursor <cursoragent@cursor.com>
@huizzhan
huizzhan marked this pull request as ready for review August 13, 2026 10:20
@huizzhan
huizzhan requested a review from a team August 13, 2026 10:20
@zufayu
zufayu requested a review from yadaish August 14, 2026 01:31
@huizzhan
huizzhan requested a review from yiijin August 14, 2026 07:05
@huizzhan huizzhan changed the title [FLYDSL] K5 fp32 chunk states, host-overhead cuts, and AOT/BV coverage [FLYDSL] Support prefill GDN K5 fp32 chunk states, host-overhead cuts, and AOT coverage Aug 14, 2026
@huizzhan huizzhan changed the title [FLYDSL] Support prefill GDN K5 fp32 chunk states, host-overhead cuts, and AOT coverage [FLYDSL] Support prefill GDN K5 fp32 chunk states Aug 14, 2026
huizzhan and others added 2 commits August 14, 2026 07:13
The sweep harness is a local tool, not something the repo needs to carry, so
drop it from the PR. The csv and the AOT README described the data's origin by
naming its path; say what the measurement is instead, and note that resolving
the ~2% gaps takes a few hundred iterations, which is the part worth knowing
when reproducing a row.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep only what the code cannot show (the LDS budget forcing two snapshot
rounds, the barriers round 1 rides on, why the host metadata is cached) and
restore the wording that was already there for the rest.

Co-authored-by: Cursor <cursoragent@cursor.com>
@huizzhan
huizzhan requested a review from coderfeli August 14, 2026 08:30
huizzhan and others added 2 commits August 14, 2026 08:31
Both K5 compiled products now live in one module: chunk_gdn_h.py grows a
_KERNEL_SPECS table that drives parse/compile/print per kernel, plus a
--kernel {vk,mfma16_hip,all} switch defaulting to all, so one command
builds both. chunk_gdn_h_mfma16_hip stays behind as a re-export, keeping
OpKind.CHUNK_GDN_H_MFMA16_HIP and its documented python -m entry point
working with no change to common.py or the README.

The job sets are byte-identical to before the merge (vk 24, mfma16_hip
240), and all 240 mfma16_hip configs load their AOT artifact under
FLYDSL_RUNTIME_RUN_ONLY=1, so no batch shape can fall back to the JIT.

Also corrects a stale doc line carried over from the old module:
chunk_gdn_h_tuned.csv is an AOT seed list only -- runtime BV comes from
the _heuristic_bv rule, which never reads the file.

Co-authored-by: Cursor <cursoragent@cursor.com>
chunk_gdn_h.py already carries both K5 compiled products, so the
mfma16_hip module was nothing but a re-export. Delete it and point
common.py's OpKind dispatch straight at the chunk_gdn_h symbols; the
enum member stays, since it still buckets jobs and build logs.

The only entry point is now `python -m aiter.aot.flydsl.chunk_gdn_h
[--kernel vk|mfma16_hip|all]`, which is why the README goes back to its
mainline text and the tuned csv header points at the surviving module.

Job counts and artifacts are unchanged: vk 24, mfma16_hip 240, and all
240 still load from cache under FLYDSL_RUNTIME_RUN_ONLY=1.

Co-authored-by: Cursor <cursoragent@cursor.com>
huizzhan and others added 2 commits August 14, 2026 09:28
The block benchmark only ever ran dense per-sequence states, so the
indexed pool a serving stack actually uses went unmeasured. Passing
--with-state-pool adds it as a variant next to the dense one, the way
--without-metadata already works, so a single run reads the cost of the
slot gather and the in-place write-back. The pool is oversized and the
slots scattered, since an identity mapping would hide the gather.

This also corrects chunk_gated_delta_rule_opt_vk's docstring, which
still claimed the pool was unsupported on the FlyDSL path; all three K5
paths forward the indices, and the benchmark now exercises that.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant