Skip to content

[Triton/Gluon] [ASM] [HIP] Add Kimi-K3 KDA decode kernels on gfx950 - #2

Closed
yuychang wants to merge 92 commits into
mainfrom
feat/kimi-k3-kda-decode-flydsl
Closed

yuychang wants to merge 92 commits into
mainfrom
feat/kimi-k3-kda-decode-flydsl

Conversation

@yuychang

@yuychang yuychang commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Motivation

Kimi-K3 KDA decode currently uses separate operations. This PR adds a fused FlyDSL path on AMD gfx950 GPUs to reduce kernel launches and global-memory traffic.

Technical Details

  • Fuses causal convolution, FP32 recurrent-state updates, and RMSNorm/sigmoid gating for the TP8 layout (12 local heads, 128-dim state).
  • Exports flydsl_kda_decode, flydsl_kda_decode_with_f_b, and is_flydsl_kda_decode_supported from aiter.ops.flydsl.kda_decode and the package.
  • Validates inputs; non-positive state indices produce zero output without changing caches.
  • Uses operation-based names only, without compatibility aliases. The SGLang adapter must update its module import and API names.

Test Plan

pytest op_tests/flydsl_tests/test_kda_decode.py -v

Covers reference correctness at batch sizes 1/8/16, cache behavior, input validation, and public API exports. Tests skip on unsupported hardware or when FlyDSL is unavailable.

Test Result

Reported on MI355X (gfx950):

13 passed, 2 warnings in 6.35s

Performance

On MI355X (gfx950), the new FlyDSL APIs were about 2× faster than the parent commit's fused Triton decode across batches 1–128:

Batch Decode: Triton → FlyDSL (µs) Speedup Projection + decode: Triton → FlyDSL (µs) Speedup
1 13.69 → 6.40 2.14× 17.04 → 7.28 2.34×
2 13.79 → 6.41 2.15× 17.15 → 7.88 2.18×
4 13.87 → 6.69 2.07× 17.27 → 8.05 2.15×
8 13.86 → 7.06 1.96× 17.30 → 8.46 2.05×
16 15.07 → 7.66 1.97× 19.12 → 9.43 2.03×
32 27.55 → 12.95 2.13× 31.97 → 15.11 2.12×
64 49.07 → 20.24 2.42× 52.58 → 24.13 2.18×
128 90.67 → 37.43 2.42× 94.56 → 43.85 2.16×

Compared 80a3b0b09 with 301f09d83 using the TP8 layout (12 heads, dimension 128). Results are medians of seven alternating A/B rounds with warmed GPU graph replay. The projection baseline uses a separate BF16 linear operation before Triton decode.

All benchmark output/state checks passed (relative RMSE < 1%); convolution caches matched exactly. These are kernel timings, not serving throughput. Results were collected at 301f09d83, not the newer PR head; callers must opt into the new APIs.

Submission Checklist

  • I have read and agree to the contributing guidelines.
  • Added tests and ran them on target hardware.
  • Exported the public APIs.
  • No unrelated changes.

@github-actions

Copy link
Copy Markdown

🏷️ 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
multigpu 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: 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 2 --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 changed the title feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 [FlyDSL] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 Sep 30, 2026
@yuychang yuychang changed the title [FlyDSL] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 Sep 30, 2026
zovonoir and others added 7 commits September 30, 2026 18:25
…BF16 GEMM tunings (ROCm#5974)

Tuned on MI308X (gfx942, 80 CUs) for the TP2 shapes of
Qwen3.8-Flash-Next-PTPC-FP8, M = 1..16384. None of these shapes had a tuned
entry, so each ran AITER's default kernel.

- qwen38_flash_next_a8w8_bpreshuffle_tuned_gemm.csv (60 rows, CK / asm):
  GDN in_proj_qkvz and out_proj, QSA qkv_proj and o_proj, shared expert
  gate_up / down. CK-tile candidates are left out: the tuner timed them far
  below their runtime cost (N=2560, K=3072, M=1: 7.2 us tuned vs 30 us served).
- qwen38_flash_next_bf16_tuned_gemm.csv (74 rows, hipBLASLt): MoE router
  gate, GDN in_proj_ba, QSA indexer projection, hyper-connection projections.
- The matching untuned files list the same shapes, in the same order.

Previously shipped and registered from ATOM (ROCm/ATOM#2418).

Co-authored-by: Cursor <cursoragent@cursor.com>
…OCm#5763)

* Add gfx950 MXFP4 SiTUv2 FLAT 16x192/16x32 kernels and KK3 fake tuner CSVs.

Route fused_moe 1-stage Situv2 through the new situv2 hsaco and relax asm MXFP4 model_dim to multiples of 512 so remote GPU checks can pin token 8/32 via AITER_CONFIG_FMOE.

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

* fmoe situv2 16x192: rebuild .co with the LDS DS-offset fix

The vendored 16x192 binary predated the D_LDS_RD_DIRECT / D_LDS_RD_REBASED
fix in FMOE_MXFP4_G1U1_1TG_4W_32mx1_64nx4_FLAT_SUBGU192.sp3. The DS read
offset is a 16-bit immediate, so at PF_D=4 / SUB_GU=192 the fourth gemm2
D-prefetch buffer (LDS byte 73728) truncated to 8192 and read buffer 0's
weights: every fourth 256-dim output slice was garbage.

16x32 was unaffected -- its whole group segment is 46080 B, so all four
prefetch buffers stay under the 65536 immediate limit. Its .co is
instruction-identical to a fresh build and is not touched here.

Rebuilt with:
  X_BF16=1 ACT_TYPE=2 MARKER_ZERO=1 \
    poc_kl/scripts/fused_moe/build_fp4_64nx4_flat_subgu192.sh aiter

test_moe_2stage.py -q 4 -a situv2 -e 896 -k 16 -p t, logits_diff:
  3584x384 t=1  0.238   -> 0.00017
  3584x384 t=2  0.215   -> 0.00030
  3584x192 t=1  0.2115  -> 0.00026

* Remove temporary csv files

* remove redundant asm path

* Improve C4 scenario for KK3

* Load U as nt to avoid cache contention with G

* Update 16x128 emsort kernel with deeper D prefetch

* Remove redundant entry for token=3 from the config

* Cleanup kernels and csv files

* Update csv and enable nt for U for 16x192 and 16x32

* Fix typo in csv manifest

---------

Co-authored-by: Sergey Solo <ssolovye@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
K3's KDA gate projection `f_b_proj` is `[M, 128] x [128, 128 * heads/tp]`.
K is the KDA head dim, 128 at any tp; N is `num_local_heads * 128`, so the
96 KDA heads give N=1536 at tp8 and N=3072 at tp4. Only 1536 was tuned, so
tp4 misses the table on every token.

A miss here is not a slower kernel. `gemm_a8w8_bpreshuffle` falls through
to `gemm_a8w8_bpreshuffle_ck`, whose `rowwise_heuristic_dispatch` has
branches only for `K >= 512` and `K >= 192 && K % 64 == 0`, so K=128 hits

    TORCH_CHECK(false, "Unsupported K for heuristic dispatch: 128.
                        Supported K greater than 192 and K % 64 == 0.")

and the process raises on its first token. Tuning confirms there is no CK
kernel for this shape at all: every `ck` candidate reported "This GEMM is
not supported!", and the winners are all cktile or flydsl.

Same M grid the N=1536 rows carry (64-step between 1024 and 4096), so the
padded-M lookups behave the same at both widths. 62 shapes, no failures;
5 pick cktile at small M and 57 flydsl above, which is the split N=1536
has. Measured on gfx950 / 256 CU:

    tp8  M=1..16384  N=1536  K=128  ->  OK    (before and after)
    tp4  M=1..16384  N=3072  K=128  ->  FAIL  before, OK after

The untuned CSV gets the same 62 shapes so the rows can be regenerated.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: gbyu-amd <Guanbao.Yu@amd.com>
* initial kernel and tests

* fix bench to not use random avg context length

* simplify fp8_paged_mqa_logits: operator style for i32 arithmetic, flatten variant scaffolding

* perf: upgrade paged MQA logits to mfma_scale_f32_32x32x64 (K_STEPS 8→2, 6-13% faster)

* review: un-hardcode ChunkK / block_kv in paged mqa logits host

Address PR review feedback (ROCm#4136): ChunkK was hardcoded to
_BLOCK_KV in the public flydsl_fp8_paged_mqa_logits call despite
compile_fp8_paged_mqa_logits already accepting block_kv. Now ChunkK is
forwarded, defaulting to _BLOCK_KV (128) and validated to be a multiple
of MFMA_N before dispatch.

* style: apply ruff + black to flydsl mqa-logits files

* feat(flydsl): paged mqa-logits KVBlockSize>=1 + correctness-only test

Kernel: extend fp8_paged_mqa_logits from KVBlockSize==1 to KVBlockSize>=1
(block-flat co-packed layout: KVB fp8 key rows grouped first, then KVB f32
scales). Per-lane gather resolves each logical KV column independently
through the block table (kv_indices[b, col // KVB] -> block, col % KVB ->
token), so KVB is a compile-time constant that folds to the per-token slot
layout at KVB==1. i32 gather-offset ceiling now accounts for KVB*index_dim.

Test: add --kv-block-size sweep axis (1, 64) and reference block-flat
dequant; convert to a pure correctness test -- drop run_perftest / the
fn() wrapper for a direct kernel call, and remove the timing-derived
us/TFLOPS output columns (gate stays exact -inf mask + calc_diff < 1e-3).

Verified on gfx950 from a fresh compile cache across KVB {1,64}, dtypes
{fnuz,fn}, SplitKV {auto,1,4}, heads/head_dim {64,128}, short/long context.

* feat(flydsl): add Preshuffle support to paged fp8 mqa-logits + isolation bench

Support the production shuffle_weight(16x16) KV data layout in the FlyDSL paged
fp8 MQA-logits kernel (Preshuffle=True, KVBlockSize % 16 == 0), making it a true
drop-in for deepgemm_fp8_paged_mqa_logits(Preshuffle=True). Only the intra-block
key-byte offset changes; the block-table gather, co-packed/token-ordered scale
gather, MFMA, and causal mask are unchanged.

- _mqa_logits_common: add load_pack_v8i32_preshuffle (closed-form offset into
  the shuffle_weight(16x16) data section; 8-byte halves stay contiguous).
- fp8_paged_mqa_logits: thread a compile-time preshuffle flag through the kernel
  builder (with a _ps name tag), compile_* cache key, and the public API; replace
  the NotImplementedError guard with the KVBlockSize % 16 == 0 assert.
- test: add a preshuffle path (oracle from the unshuffled cache, kernel fed the
  shuffled copy) and a small dedicated case set (kvb 16/64, D 64/128, FN patch).
- bench_flydsl_vs_gluon: note the A/B is fixed at KVBlockSize=1 (Gluon faults at
  KVBlockSize>1); KVBlockSize>1 / Preshuffle coverage lives in the flydsl test.
- add bench_flydsl_paged_mqa_logits.py: standalone single-config bench (no Gluon)
  for rocprofv3 isolation profiling, covering both block-flat and preshuffle paths.

Validated on gfx950: block-flat and preshuffle correctness pass the unchanged
gate (exact -inf mask + calc_diff < 1e-3); rocprofv3 traces the _ps kernel.

* refactor(flydsl): move paged mqa-logits kernel into mqa_logits subpackage

Upstream relocated the mqa-logits kernels into
aiter/ops/flydsl/kernels/mqa_logits/. Move the paged FP8 kernel and its
shared helper module there too for consistency:

  kernels/fp8_paged_mqa_logits.py -> kernels/mqa_logits/fp8_paged_mqa_logits.py
  kernels/_mqa_logits_common.py   -> kernels/mqa_logits/_mqa_logits_common.py

Fix the sibling .tensor_shim imports to ..tensor_shim (now one level up)
and update the public __init__ import path. No behavior change; public API
(flydsl_fp8_paged_mqa_logits) is unchanged.

* fix(flydsl): size paged mqa-logits bench KV pool from real block demand

Both paged benchmarks sized the block pool from max_model_len and then
handed blocks out with `pool[counter % num_blocks]`, so the block table
wrapped and every sequence reused the same physical blocks. At B=16,
kv_len=32768 that is a 1024-block pool serving 8192 block references: an
8.65 MB KV working set standing in for 69 MB, small enough to live in
cache. Every number measured against it was therefore a cache benchmark.

That flattered the kernel by ~13% on its own, and distorted the
head-to-head against production Gluon unevenly -- given the honest
footprint FlyDSL loses 31-51% while the reference loses only 1-4%, since
the reference is slow enough that memory is not its constraint. The
reported speedup falls from 1.3-4.2x to 1.7-3.1x.

Size the pool from the blocks the batch actually needs, build the block
table vectorized rather than one element per GPU write, and report the
touched footprint alongside the requested bytes. --pool-blocks shrinks
the pool deliberately for cache-residency studies, and the vs-gluon
bench now fails with a clear message when a shape would exceed the
kernel's 2^31 i32 gather-offset limit instead of tripping an assert
inside the launch path.

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

* perf(flydsl): retune paged mqa-logits launch geometry (9.3% across shapes)

SplitKV was auto-computed with the production Triton host formula, which
ignores how many waves a CTA contains and so drifts with shape: it lands
2.5 waves per SIMD at B=16 but 10 at B=128. Resident wave count is the
lever that matters here -- this kernel is bound by memory throughput
rather than per-wave latency, so too few waves leaves the memory system
idle and too many just queue. Measured across six shapes (batch 4..128,
kv_len 4k..128k) the curve is flat between 2.0 and 2.5 waves/SIMD and
rises steeply either side: 1.0/SIMD costs 33.9us and 4.0/SIMD 29.4us
against 27.6us at the optimum.

Target WavePerEU waves per SIMD directly, accounting for the CTA's wave
count, and default to paged_w1. At equal resident wave count, splitting a
CTA across 2 or 4 waves is slower on every shape measured: a 1-wave CTA
schedules more freely, and the waves have no work to share anyway since
each owns a disjoint set of KV columns with no barrier or LDS between
them.

Worth 4.6-14.4% per shape, 9.3% aggregate, and it removes the shape
sensitivity -- all six shapes now land within 1% of 27.7us for the same
total KV work, where the old defaults spread them over 29.2-32.5us.
Verified against the full 870-case correctness sweep (max error 5.3e-4,
tolerance 1e-3); the block-flat KVBlockSize=1 path is unchanged.

Two public-surface notes for reviewers. This deliberately diverges from
the deepgemm_fp8_paged_mqa_logits host formula it previously mirrored.
And WavePerEU changes meaning, from a multiplier in the old formula to
the target waves per SIMD; its default of 2 is where the curve bottoms
out. FP8_PAGED_MQA_LOGITS_DEFAULT_VARIANT changes paged_w4 -> paged_w1.

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

* refactor(flydsl): use FlyDSL operator sugar in paged mqa-logits

The kernel was written against raw MLIR builders -- 58 _to_raw() calls and
65 arith.* calls -- for arithmetic FlyDSL already overloads. The worst case
was the store predicate, 17 lines of nested arith.andi/arith.cmpi to say
`(lane_div_N == 0) & (col <= q_limit)`.

Route unsigned division and modulo through new udiv/umod helpers rather
than the bare `//` and `%` operators: fx.Int32 is signed, so `//` lowers to
floordivsi, which expands to several instructions to get negative-operand
rounding right. Every quantity divided here is non-negative, so going via
fx.Uint32 keeps the single-instruction divui/remui the hand-written calls
emitted.

Verified codegen-neutral: the ATT-decoded ISA is byte-identical to the
previous commit, 655 instructions in the same order. 870/870 correctness
cases pass at 5.3e-4, timing is unchanged at 28.0us, and ruff reports the
same findings as before.

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

* refactor(flydsl): drop dead helpers and tighten mqa-logits docs

_mqa_logits_common carried three helpers nothing referenced: fn_to_fnuz_i64
(45 lines -- the dense kernel defines its own local copy), load_pack_i64
(likewise), and i32_add. Removing them is the bulk of the shrink.

Comments and docstrings were 30% of the two files, much of it restating the
code or duplicating the PR description. Trimmed to what a reader cannot
derive: the co-pack layout contract, the ReLU scale-hoist identity, the i32
gather ceiling, the D-reg-to-head mapping, and the warning that a Python
`if` on KVB would become a runtime scf.if and drop branch-local addresses.

932 -> 770 lines across the two files. ISA byte-identical to the previous
commit (655 instructions), 870/870 correctness at 5.3e-4, 27.8us unchanged,
no new ruff findings.

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

* refactor(flydsl): drive the paged mqa-logits tile loop with scf.for_

Replaces the hand-built scf.ForOp plus `with ir.InsertionPoint(body)` and
trailing scf.YieldOp with the scf.for_ generator, which owns the insertion
point and terminator. Same pattern the fmha_gfx1250 kernel already uses.

Only two lines, but it removes the last raw MLIR region plumbing from the
kernel's main loop. The masked store keeps scf.IfOp: FlyDSL exposes no `if_`
helper, and switching to a predicated store would change codegen.

ISA byte-identical (655 instructions), 870/870 correctness at 5.3e-4,
27.7us.

Also investigated expressing the preshuffle as a FlyDSL layout, as
examples/04-preshuffle_gemm.py does in two lines. Not adopted: that example
indexes a whole contiguous tensor, whereas our access is a paged gather off
a per-lane runtime base. GTensor._linear_offset is element-granular, and
while every preshuffle stride happens to be dword-aligned, a 5-D view would
just replace one multiply-add chain with another for about two lines. It
only pays off alongside FlyDSL-native tensors and copy atoms.

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

* refactor(flydsl): express the KV preshuffle as a layout, not a code path

The paged gather hand-computed byte offsets and branched on Preshuffle to
pick between two of them. Both layouts are actually the same coordinate
space -- (block, token//16, token%16, K-step, sub-group, lane half) -- and
differ only in the stride vector:

    block-flat  (blk, 4D, D/4, 16,  4,  2)   token c at c*D
    shuffled    (blk, 4D, 4,   256, 64, 2)   token c at (c//16)*16D + (c%16)*16

So the choice moves into make_kv_key_view and the kernel loses its `if
preshuffle` branch entirely; one load_pack_kv serves both. The co-packed
scale tail becomes a (block, token) view whose base absorbs the KVB*D key
offset, replacing the manual scale_byte arithmetic.

load_pack_kv slices the view down to the `sub` axis once rather than
indexing all six coordinates per load. Without that the invariant part of
the address is re-derived in each of the four loads and the kernel grows 49
instructions; with it, 14.

Trade, measured rather than assumed: the kernel file drops 15 lines and a
branch, the shared module gains 21 for the two view constructors, so the
pair is +6 overall. Codegen is 14 instructions larger (669 vs 655) because
the hand-written version exploited disjoint bit-fields to fuse addresses
into v_or3_b32/v_add3_u32 that the general dot product does not reach for.

Speed holds on every path (3 reps each, layout vs previous): preshuffle
27.58 vs 27.83us, block-flat KVB=64 9.78 vs 9.98us, KVB=1 10.20 vs 10.53us.
870/870 correctness at 5.3e-4, no new ruff findings.

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

* test(flydsl): curate the paged mqa-logits matrix, 870 cases -> 29

The sweep crossed seven axes into 864 cases that compiled only 12 distinct
kernels, re-running each about 72 times. The kernel specialises at compile
time on (heads, head_dim, KVBlockSize, Preshuffle); batch, next_n, context
length, SplitKV and the Q dtype are runtime values, and none of the axes
interact -- nothing couples head_dim to SplitKV or the dtype to KVBlockSize
-- so the product was testing interactions that cannot exist. q_dtype alone
cost 432 cases while selecting no different code path on gfx950, where
convert_q_fn is always False.

Now: a full cross of the compile-time axes (16 cases, one per distinct
kernel -- more preshuffle coverage than before, which only hand-picked 4),
each runtime axis varied alone off a base, and four interaction cases where
the tile-range arithmetic genuinely couples. --exhaustive still runs the
original 870 for release validation.

Validated by mutation testing rather than by the suite passing, since a
suite cut too far also passes. Eleven deliberate kernel bugs -- swapped
preshuffle and block-flat strides, causal off-by-one, dropped tail clamp,
wrong lane half, wrong block-table index, dropped kv_scale, wrong head
weight, floor-instead-of-ceil split (needs SplitKV>1), batch-blind output
row (needs batch>1), next_n-blind causal bound (needs next_n>1). The full
suite catches 11/11; the curated matrix also catches 11/11.

29 cases in 8.7s against 870 in 79s. Harness in att_profiles/mutation_check.py.

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

* test(flydsl): cover the wave-split and ChunkK axes

Neither was ever exercised: every case ran the default variant at
ChunkK=128, so paged_w2 and paged_w4 had no coverage at all, and the Tier-1
change of DEFAULT_VARIANT silently moved which one CI touched.

That hole was real, not theoretical. A mutation of the wave-to-column-tile
assignment (`wave * N_TILES_PER_WAVE` -> `wave * (N_TILES_PER_WAVE - 1)`) is
correct by construction at one wave per CTA, where `wave` is always 0, and
wrong for any multi-wave variant. It survived both the curated suite *and*
the original 870-case exhaustive sweep.

Adds variant and chunk_k parameters plumbed through to the kernel, reported
in the results table, and six cases: paged_w2 and paged_w4 at the default
tile width, ChunkK 64 and 256 against the variants that divide them, and a
preshuffle x paged_w4 cross. 35 cases in 9.0s, and the mutation set now runs
12/12 where the exhaustive suite manages 11/12.

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

* test(flydsl): condense the paged mqa-logits test prose and plumbing

Docstrings trimmed to what a reader cannot derive -- the oracle's formula
and mask rule, the co-pack byte layout, what the preshuffled copy is for --
dropping the restatement and the cross-references to other files.

_build_inputs returned an 8-tuple that was unpacked over ten lines at the
call site; it now returns a NamedTuple, so the test body reads inp.q_fp8
rather than positional unpacking, and the contract is documented by the
type. Its block table is also built vectorized instead of one element per
GPU write, which is why the suite went from 9.0s to 7.5s. Same values: the
per-case errors are unchanged to the digit.

The pool there is still sized from max_model_len, so sequences share blocks
once the batch outgrows it. Left alone deliberately, with a comment saying
why: harmless for correctness and arguably extra coverage of block reuse,
unlike in the benchmarks where it shrank the measured working set into cache.

Still 35 cases, still 12/12 on the mutation set.

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

* docs(flydsl): drop benchmark figures from the paged mqa-logits source

Timings and percentages go stale as soon as anything around them changes,
and the source is the wrong place to keep them -- the numbers behind these
choices are in the commit history and the PR description.

Removes the measured wave-count curve from _auto_split_kv, the "4-14%
slower" claim from the DEFAULT_VARIANT note, and the "where the measured
curve bottoms out" aside from the WavePerEU docs, keeping what the formula
computes and why resident wave count is the lever. In the test, the block
table comment keeps its warning that shared blocks are deliberate but no
longer cross-references what that would cost a benchmark.

Comments only; 35/35 correctness cases unchanged at 3.97e-4.

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

* docs(flydsl): fix stale --variant default in the paged bench help

The help text still advertised paged_w4 after DEFAULT_VARIANT moved to
paged_w1. Read it from the constant so it cannot drift again.

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

* refactor: move the FlyDSL benchmarks out of op_benchmarks/triton

They were sitting in the Triton benchmark directory alongside 73 unrelated
files, which is misleading -- neither uses Triton. op_benchmarks/flydsl/ now
holds both, matching how op_tests/flydsl_tests/ already separates the FlyDSL
correctness tests.

No __init__.py needed: op_benchmarks subdirectories are namespace packages,
which is also why the vs-gluon benchmark keeps importing kv_cache_cast_to_fp8
from op_benchmarks.triton.bench_deepgemm_attention -- that file is genuinely
a Triton benchmark and stays put.

Paths updated in the bench's own rocprofv3 example and in pr.md. Both run
clean from the new location: microbenchmark 27.8us with its correctness gate
passing, head-to-head 3.11x at B=16.

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

* style(flydsl): satisfy the Black and Ruff pre-checks on PR ROCm#4221

CI reported five failures; only two are real. check-signal waits on the
pre-checks workflow and Aiter Test Gate depends on check-signal, so the
gate failed in four seconds without running a single GPU test. Fixing
style unblocks all three.

These findings were invisible locally because I had been diffing ruff
against the branch's own history. CI runs it through reviewdog with
filter-mode=diff_context against upstream/main, where none of these files
exist -- so every line is an added line and every finding is in the diff.

Seven auto-fixed: import ordering in five files plus __all__ ordering in
aiter/ops/flydsl/__init__.py. Five needed a decision, taken to match what
the repo already does rather than inventing a convention -- it carries 214
BLE001, 21 B008 and 19 PLR0124 suppressions, including this exact
`fx.Stream(None)` default in moe_route_maps.py and this exact NaN-check
idiom in test_fused_qk_norm_rope_group_quant.py. So noqa for the blind
except, the FlyDSL stream default and the self-comparison, and the two
unused `heads` unpackings renamed to `_heads`. Black reformatted one file.

Verified as CI runs them, repo-wide: `black --check .` leaves 1063 files
unchanged, and `ruff check .` leaves five findings, all in upstream files
this PR does not touch. Behaviour unchanged: 35/35 correctness at 3.97e-4,
27.93us, and the public exports still resolve after __init__ was resorted.

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

* refactor(flydsl): drop dead conversion plumbing from the paged kernel

convert_q_fn and convert_kv_fn were threaded through the kernel builder, the
compile cache and the host, and neither is ever read inside the emitted
kernel -- they only appended a name tag. convert_kv_fn was additionally
hardcoded False at every call site. Only scale_mul reaches codegen, so it is
now the sole carrier of the FN->FNUZ compensation and tags the kernel name
itself.

That exposed a docstring that was simply untrue: compile_fp8_paged_mqa_logits
claimed these flags "mark an FP8 FN operand whose -0 (0x80) byte the kernel
patches to FNUZ +0". No such patch exists on this path -- the vector<8xi32>
operand has no byte-patch, which the kernel body's own comment admitted. The
host comment now states the actual gfx942 behaviour, including that FN -0
decodes to FNUZ NaN unpatched. Flagged rather than fixed: it needs gfx942 to
validate.

Also: mfma_head_reduce now derives res_ty and f32_0 and defaults fm_fast, so
the call site is one line instead of eleven and the kernel drops two locals;
make_out_row_view takes the i32 stride and widens it itself. Both are safe
because _mqa_logits_common turns out to have exactly one consumer -- upstream's
dense fp8_mqa_logits carries its own copies and does not import it, so the
module docstring claiming otherwise is corrected too.

569 -> 535 lines in the kernel. ISA byte-identical to HEAD (669 instructions),
35/35 correctness at 3.97e-4, 27.92us, black and ruff clean.

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

* fix(flydsl): pass buffer load/store aux as an i32 SSA operand

upstream/main treated aux as an optional IntegerAttr, but RawPtrBufferLoadOp
expects it as the fourth operand Value. With cache_modifier=0 that left aux
None and broke JIT compilation of kernels using GTensor buffer loads.

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

* fix(flydsl): align buffer_ops and MFMA helpers with flydsl 0.3.1

Revert buffer load/store aux to the upstream IntegerAttr form expected by
flydsl 0.3.1, and route scaled MFMA through the expr wrapper so cbsz/blgp
attributes lower correctly during paged MQA kernel JIT.

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

* perf(flydsl): software-pipeline block-table and K0 prefetch in paged MQA logits

Prefetch kv_indices one tile ahead (skip reload when ind_off unchanged) and
carry K-step-0 across the inner column loop to hide VMEM latency on the
production kvb64/preshuffle decode path.

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

* style(flydsl): fix Black/Ruff on paged MQA logits prefetch helpers

Bind loop SSA values via default args (B023) and apply Black formatting.

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

* address comments by Sami

* style(flydsl): fix Black/Ruff on paged MQA logits files

Apply Black formatting and resolve Ruff PLR0124/I001 so CI pre-checks pass.

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

* feat(flydsl): add gfx950 H64 16x16x128 paged FP8 MQA logits kernel

Land the production indexer mapping (resident Q, depth-4 NT B-ring, SplitKV)
without the v2/v3 experiments or local profiling artifacts.

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

* feat(flydsl): specialize gfx950 paged MQA logits for H=32 and H=64

Compile a separate 16x16x128 kernel per head count so the indexer matches
gluon's ChunkQ=heads set without changing the FlyDSL page pipeline.

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

* feat(flydsl): compile-time next_n for gfx950 paged MQA logits

Drop the unused second query row on next_n=1 so MFMA/VALU and store waits match the actual decode shape.

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

* feat(flydsl): dual-bank page pipeline for gfx950 paged MQA logits

Keep next-page K in fixed register banks instead of scf.for yield copies, store one wave-wide page of logits, and prefetch the page table so gather latency stays overlapped.

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

* feat(flydsl): dispatch gfx950 paged MQA logits as the default decode path

Route H32/H64 D128 KVB64 preshuffle next_n in {1,2} through the 16x16x128 kernel, keep the 32x32x64 implementation as the generic fallback, and satisfy Black/Ruff.

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

* refactor(flydsl): make gfx950 the only paged FP8 MQA logits path

Drop the generic 32x32x64 kernel and fold helpers into the gfx950 decoder so
H32/H64 stays on the tuned 16x16x128 pipeline, with i64 output-row addressing
and idiomatic FlyDSL MMA/scheduling that still matches the previous ISA.

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

* style(flydsl): black-format gfx950 paged MQA logits and tests

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

* dynamic next_n scheduling WIP.

* cleanup

* feat(flydsl): accept compact Q[B, next_n, 32, 128] for gfx950 paged MQA logits

Match the Gluon contract so next_n can be 1..8 without padding Q to 8 rows;
optional next_n_lens still selects live rows per sequence.

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

* perf(flydsl): raise wave priority around scaled MFMA

Give compute waves s_setprio 3 during gemm so they win over helper G2L.

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

* perf(flydsl): hold s_setprio across both scaled MFMAs per row

Avoid dropping priority between the two 16x16x128 gems.

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

* perf(flydsl): hint waves_per_eu=6 to match LDS-bound occupancy

Compiler was told 2 waves/EU while the CTA actually lands 6.

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

* perf(flydsl): unroll next_n scoring by two rows

Issue a pair of Q/weight LDS packs per iteration of the row loop.

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

* perf(flydsl): unroll next_n scoring by four rows

Give the compiler a larger Q/weight LDS pack window per tile.

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

* perf(flydsl): fully unroll next_n scoring over MAX_NN

Replace the 4/2/1 while ladder with predicated constexpr rows.

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

* perf(flydsl): cache paged MQA logit stores for the following topk

Write logits with buffer cache policy 0 so L2 can retain the row for the next kernel; keep KV page DMA nontemporal so those loads do not evict it.

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

* refactor(flydsl): drop dead gfx950 paged-MQA helpers and restore scalar logit stores

Scalar GTensor writes already lower to cached buffer_store; the explicit store path and unused arith/B_RING bits did not change ISA. Share MAX_NN from the kernel in tests.

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

* test(flydsl): keep paged MQA driver out of pytest collection

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

* cleanup(flydsl): remove stale paged MQA test paths

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

* test(flydsl): put paged MQA logits on the aiter op_test template

Move the gfx950 indexer tests to op_tests/test_*.py with @benchmark
candidates, TFLOPS/TB/s, a torch-only reference, and one markdown table
per signature so CI runs python3 op_tests/test_flydsl_fp8_paged_mqa_logits.py.

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

* test(flydsl): fold paged MQA pytest coverage into the op_test tables

Keep a single gfx950-gated test_*.py as the source of truth, grade wide
output against torch, and leave the flydsl_tests files as shims.

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

* test(flydsl): drop paged-MQA shims and add the probe_1079 decode table

Keep a single op_test as the source of truth, with the production
scattered-pool contract (mean Nq, next_n × ctx) graded against torch.

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

---------

Co-authored-by: yashagar <yashagar@amd.com>
Co-authored-by: Folkert Huizinga <fhuizing@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* [CI] Triton board: drive PR status from the workflow (draft=Backlog, open=In review)

* [CI] Triton board: work from the current PR state; remove items that lose their label/assignee

* Clean up

* [CI] Triton board: remove only on a lost qualifier; drop the enable variable

- `unassigned` takes an item off the board only when the person removed is
  a member of @ROCm/triton_kernels_team (and no rule still applies).
  Unassigning someone unrelated used to remove hand-added items.
- When the team lookup fails, nothing is removed: without the team we cannot
  tell whether an assignee still qualifies the item. The item is left alone
  with a warning; adding through the other rules still works.
- The job no longer waits for the repository variable AITER_TRITON_BOARD_ADD.
@yuychang
yuychang force-pushed the feat/kimi-k3-kda-decode-flydsl branch from 0caa013 to 1d48028 Compare September 30, 2026 14:26
@github-actions github-actions Bot changed the title feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 [FlyDSL] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 Sep 30, 2026
@yuychang
yuychang force-pushed the feat/kimi-k3-kda-decode-flydsl branch from 1d48028 to 145a1d8 Compare September 30, 2026 14:41
- Add fused KDA decode and head-local f_b projection kernels for the fixed Kimi-K3 TP8 layout
- Use operation-based module, kernel, public API, and test names without compatibility aliases
- Export decode APIs and the gfx950 support predicate through the FlyDSL package
- Add reference correctness, cache behavior, input validation, and public API tests
@yuychang
yuychang force-pushed the feat/kimi-k3-kda-decode-flydsl branch from 145a1d8 to 301f09d Compare September 30, 2026 14:53
yuychang added a commit to yuychang/sglang that referenced this pull request Sep 30, 2026
…decode

- Match yuychang/aiter#2, which upstreams the kernels as aiter.ops.flydsl.kda_decode
- Load the AITER source from aiter.ops.flydsl.kda_decode and its flydsl_kda_decode* entry points
- Rename the vendored module to kda_flydsl/kda_decode.py so both sources expose one API
- Rename the kernel builders to kda_decode_gfx950.py and kda_decode_fused_projection_gfx950.py
- Rename the public functions, builders, compiled kernel names and registry op ids to the AITER spelling
- Keep the vendored batch-2 fused-projection tuning as optional builder keywords
- The vendored and AITER kernels match bitwise on gfx950 at batch 1, 2, 8 and 16
Boss2002n and others added 4 commits September 30, 2026 13:24
* feat(triton): add SiLU multiply backward

Signed-off-by: DaiXindi-AMD <xdai@amd.com>

* fix(triton): address SiLU multiply backward review

Signed-off-by: DaiXindi-AMD <xdai@amd.com>

* fix(triton): narrow SiLU backward support to gfx950

Signed-off-by: DaiXindi-AMD <xdai@amd.com>

---------

Signed-off-by: DaiXindi-AMD <xdai@amd.com>
* Fix gfx950 gluon unified attention compile failure with int64 seq_lens

* Remove int64 metadata unified attention test
@github-actions github-actions Bot changed the title [FlyDSL] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 [Triton/Gluon] [ASM] [HIP] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 Oct 1, 2026
@yuychang yuychang changed the title [Triton/Gluon] [ASM] [HIP] feat(flydsl): add Kimi-K3 KDA decode kernels on gfx950 [FlyDSL] Add Kimi-K3 KDA decode kernels on gfx950 Oct 1, 2026
- Sort KDA decode imports to satisfy Ruff I001.
- Sort public exports to satisfy Ruff RUF022 without changing kernel behavior.
@github-actions github-actions Bot changed the title [FlyDSL] Add Kimi-K3 KDA decode kernels on gfx950 [Triton/Gluon] [ASM] [HIP] Add Kimi-K3 KDA decode kernels on gfx950 Oct 1, 2026
- Rebase the state and conv-state buffer descriptors on the selected slot using 64-bit byte offsets.
- Pass the state and conv-state slot strides to the kernels and launchers as fx.Int64.
- Keep per-access buffer offsets 32-bit and slot-relative, which fixes wrapped reads/writes past 4 GiB.
- Add a large-cache regression test for both decode entry points that checks results and untouched slots.
azaidy and others added 29 commits October 5, 2026 10:46
…weights (ROCm#6092)

* Fix Triton Conv2D/Conv3D caching for inference tensor weights

* Review 1
Simplify inference-tensor weight handling

* Update Conv2D test inventory count
* Add per token scale to a8w8 moe gemm

* cleanup
…gfx950 (ROCm#4383)

* Merge branch 'ROCm:main' into quant_mxfp4

* Update bench_quant_mxfp4.py

* Resolve formatting issues

* Resolve formatting issues

* Resolve formatting issues

* Resolve formatting issues

* Resolve typing deprecation and import formatting

* Fix Ruff import and __all__ ordering

* Replace deprecated typing.Tuple in quant module

* Use PEP 604 unions for optional tensor annotations

* Include instruction for mxfp4 qunatization.

* Change hueristic
Added pre fetch
slight modification in bench script

* Consolidate comments and fix formatting/duplicate imports

* Modifying shapes to be tested

* Remove the inline asm part as the test triton patch provided by compiler team, now successfully uses the correct instruction

* Removing unwanted files

* Removing json file

* Migrate gfx950 Gluon mxfp4/mxfp8 quant tuning to JSON configs

Also update mxfp4/mxfp8 quant test files with representative shapes covering the benchmark shape sweep.

* COnfig file format change

* Address PR review comments: bucket-key config lookup, kernel repr, lazy Gluon import, config dir rename, trim redundant test shapes

- Replace the rule-tree select_tuned_config with lookup_tuned_config, a
  flattened bucket-key resolver ({schema, "M_GEQ_33.N_LEQ_16384": {...},
  "any": {...}}) matching unified_attention_utils's existing pattern.
- Add make_kernel_repr-based repr= to both gfx950 Gluon quant kernels.
- Move the gfx950 Gluon kernel imports in quant.py out of the module
  header into the arch-gated dispatch branches that use them.
- Rename configs/gfx950/gluon/quant/{mxfp4,mxfp8}_quant -> {mxfp4,mxfp8}
  to match the sibling PR's naming convention.
- Drop one of three redundant large-non-square shapes from the mxfp4/
  mxfp8 quant parametrize lists.

* docs(quant): shorten config lookup and gfx950 kernel comments

* Move gfx950 Gluon quant shape-derived block sizes into JSON and retune mid-M configs

- Drop the Python BLOCK_SIZE_M/N overrides in dynamic_mxfp4_quant and
  dynamic_mxfp8_quant; launch configs now come only from the JSON tables.
- Tune the 33<=M<=1024 buckets (more, smaller CTAs) for MXFP4 and MXFP8.
- Compute e8m0 scale bias in int32 before narrowing to uint8.

* Use lookup_config for gfx950 Gluon quant configs to match the sibling PR

Replace lookup_tuned_config with lookup_config(table, axes, **values) and
drop the "schema" key from the gfx950 mxfp4/mxfp8 JSON tables.

* Refactor gfx950 Gluon quant: shared helpers, extended select_leq_config, backend argument

- Share the e8m0 scale math (common/utils.py), the tile store and the warp
  layout between the MXFP4 and MXFP8 Gluon kernels.
- Extend select_leq_config to many axes and reject bad calls; add
  get_quant_config and drop lookup_config.
- Add a keyword-only backend argument (None, "triton", "gluon") to
  dynamic_mxfp4_quant and dynamic_mxfp8_quant; the benchmark gets a gluon
  provider and the triton provider forces the Triton kernel.
- Drop unused SCALING_MODE and waves_per_eu=0 from the Gluon launches; add
  num_warps and EVEN_M_N to the kernel repr keys.
- Add CPU-only tests for the config lookup and the Gluon quant JSON tables;
  update the config docs.

* Fix black formatting in test_quant_configs
…mm_a8w8_blockscale (ROCm#6071)

Both benchmarks crash before printing a single result on current main.

bench_moe_gemm_int8_smoothquant passes add_residual=False to
moe_gemm_int8_smoothquant, but the MXFP8 GEMM and A8W4 MoE optimization (ROCm#3292)
renamed that argument to swiglu_add_residual. The meaning is unchanged:
both compute s * (linear + 1) when set. The call now fails with
"unexpected keyword argument 'add_residual'".

bench_moe_gemm_a8w8_blockscale builds the routing GEMM inputs in fp32
(xg = x.to(torch.float32), and wg with the default dtype) and passes them
to gemm_a16w16. Since ROCm#4860, gemm_a16w16 asserts fp16/bf16 activations
and weights. Build both in bf16, as bench_moe_gemm_a8w8 and
bench_moe_gemm_int8_smoothquant already do.
The benchmark cannot run: it imports generate_gemm_afp4wfp4_pre_quant_inputs
from op_tests.triton_tests.gemm.basic.test_gemm_afp4wfp4_pre_quant_atomic,
a module that no longer exists. The DS FP4 fusion work (ROCm#1371, ROCm#1434)
renamed that test to test_gemm_a16wfp4.py, so every run fails with
ModuleNotFoundError.

ROCm#2601 tried to restore the helper. It was closed because
gemm_afp4wfp4_pre_quant is a deprecated wrapper, superseded by
gemm_a16wfp4 and slated for removal, and the bench should be deleted.
This does that, and drops the bench's entry from bench_schema.yaml.
* [Triton/Gluon]: Add Inkling Model GEMM Configs

* Switch to using FlyDSL

---------

Co-authored-by: Rahul Batra <rahbatra@amd.com>
* DSV4.1 ATOM-port local fixes (flydsl MoE LDS-DMA wait, fp4 prefill cache, MoE tuner GPU datagen, torch.Stream, tuned FMoE CSV)

- flydsl mixed MoE stage1: full vmcnt drain + sched_barrier before the LDS-DMA
  barrier (partial waits let LDS-DMA loads cross it; ROCm#5561)
- fp4 prefill mqa logits: unbounded kernel cache (lru_cache(32) evicted and
  recompiled per shape)
- MoE tuner: generate the E x 2I x H test weights on the GPU (CPU fp4 packing
  took tens of minutes per shape at E=385)
- cpp_itfs torch_utils: accept any torch.Stream (derive the HIP handle when
  cuda_stream is missing)
- add DSV4.1 TP2 shared-experts-fusion fp8/fp4 tuned FMoE config

* Merge DSV4.1 TP2 fp8/fp4 FMoE tuning into dsv41_fp4_{tuned,untuned}_fmoe.csv

- move the 23 shared-experts-fusion rows (E385 topk7, E129 topk4, fp8 activation x
  fp4 weight) into dsv41_fp4_tuned_fmoe.csv and their shapes into
  dsv41_fp4_untuned_fmoe.csv instead of a separate file
- drop the torch_utils, fp4 prefill cache and flydsl stage1 wait changes

---------

Co-authored-by: wunhuang <wunhuang@amd.com>
…d 64-row pages (ROCm#6145)

* feat(flydsl): row-group FP4 paged MQA logits for 8- and 64-row pages

Add pa_mqa_logits_fp4_rowgroup, an FP4 paged MQA logits kernel in which a
wave scores a group of a sequence's query rows off each key load (the
existing kernel reads every key once per row), and route
flydsl_pa_mqa_logits_fp4 to it for kv_block_size 8 and 64.

- Page 64 keeps the existing preshuffled cache layout and its writers;
  page 8 (DeepSeek-V4.1's indexer pages) has its own layout
  (pack_kv_cache), so a candidate layer's kept blocks are its block table
  with no gather.
- Work is shared out by each sequence's real length inside the kernel
  (static split, or a length-balanced share when that walks shorter); the
  grid depends on the batch and max_seq_len only, so a captured graph keeps
  it. No schedule launch; cta_info / block_k / num_warps /
  parallel_unit_num do not apply on this path.
- Keys and scales are read through 64-bit addresses (pools past 4 GiB);
  sequences are handled in runtime chunks of 64 (one build per shape for
  any batch).
- A given `out` is left as it was past each row's bound (no -inf fill).
- compute_varctx_schedule moves to pa_mqa_logits_fp4_common.

Cold, MI355X, against the original FlyDSL kernel (its schedule prebuilt):
page 64 1.07-3.0x (B = 64, H 32/64, next_n 1/2/6, up to 100K keys) and
1.25-2.9x at B = 1-16; page 8 V4.1 full layers 1.5-3.4x (T = 6-768, 200K
context). Against aiter's Triton paged_mxfp4_mqa_logits: page 8 1.03-1.5x
on every T; page 64 ahead or level on most shapes.

Also: rope_rotate_activation (fp4) gains round_rope, rounding the rotated
values to the input dtype before quantizing them, as a model whose RoPE
writes its output back in that dtype (DeepSeek-V4.1's index query) does;
with a bit-exact test (op_tests/test_dsv4_rotate_quant.py --round-rope).

* feat(flydsl): ragged query rows in the row-group FP4 MQA logits kernel

flydsl_pa_mqa_logits_fp4_rowgroup takes query_start_loc [B + 1] and
max_query_len: sequence b's rows are the flattened rows
query_start_loc[b] .. query_start_loc[b + 1] - 1, any count up to
max_query_len (0 included, as cudagraph padding has). q_fp4 is
[rows, 1, H, D / 2]; q_scale, weights, row_ends and out go by flattened
row. Without row_ends, row n of a count-row sequence sees
context - count + n + 1 keys.

A sequence's rows still share each key load, so a batch whose requests
carry different numbers of query rows no longer has to be scored one row a
sequence. Rows past a sequence's count read row 0 and get bound 0: no keys
walked, no stores. One build flag (ragged); counts and batch stay runtime
values. The rectangular [B, NEXT_N] call is unchanged.

V4.1 full layers (page 8, 200K context, T = 6..768, cold, MI355X): the
ragged call runs 1.0-2.35x faster than scoring each row as its own
sequence (T = 48: 24.8 vs 43.4 us, T = 768: 246 vs 580 us), and within
-4.5..+1.6% of the rectangular call; uniform counts give the rectangular
call's logits bit for bit.

* feat(flydsl): ragged row-group MQA logits take their lengths from row_ends

Ragged rows (query_start_loc) now require row_ends and take no
context_lens: a sequence's length, which the kernel shares its workgroups
out by, is its last row's bound, read in the kernel. A caller with per-row
bounds no longer builds a per-sequence length.

pages_per_block: a block-table entry may name that many consecutive pages
(its first page's id / pages_per_block), expanded in the kernel where the
page is used, so a caller whose pages are a multiple of the kernel's page
size (DeepSeek-V4.1: 16 pages of 8 rows a 128-row PAGE) passes its own
table as is.

flydsl_pa_mqa_logits_fp4 forwards row_ends, query_start_loc,
max_query_len and pages_per_block to the row-group kernel (page 8 / 64) and
refuses them on its other pages.

Uniform per-row bounds and pages_per_block = 1 build the same kernel as
before; op_tests/test_flydsl_pa_mqa_logits_fp4.py passes at page 64 and 8.

* feat(configs): DeepSeek-V4-Pro TP8 wo_b in the dsv4 mxscale bmm table

N=7168, K=2048 (128x128 blocks), M 1..65536: the attention output
projection at TP8, which had no row and took the heuristic kernel. Rows
tuned on gfx950 as the table's others were; errRatio <= 0.0009 against the
dequantized reference. Against the heuristic pick, same operands, cold:
1.01-1.03x at most M, 1.22x at M = 256. Only rows added.

* feat(flydsl): M-tile FP4 MQA row-group kernel with a shape-only launch plan

The row-group kernel scores MQA as a GEMM: M a sequence's query rows times
the heads, cut into tiles of whole rows (tile_m, a row's head sum stays in
its tile), N its keys. Prefill and decode are the same ragged rows
(query_start_loc, row_ends); next_n is gone from the kernel. Work is shared
out over M tiles by real length inside the kernel, so a long sequence's
tiles get more workgroups.

- make_fp4_mqa_plan / Fp4MqaPlan: the launch (tile from TILE_ROWS, rows a
  wave, warps, workgroups, depth, nontemporal loads) from the batch's shape
  alone, so a captured graph keeps it; lru_cached.
- flydsl_pa_mqa_logits_fp4_rowgroup(plan, ...) takes ragged rows only.
- Page 8 compiles a single depth-2 walk: the two walks in one kernel spilled
  40 VGPRs at 6 rows of 32 heads; one walk is as fast or faster on every
  shape measured (B=64 6 rows at 131K keys 185 -> 157 us, per-row tables
  T=3072 954 -> 838 us). Page 64 keeps both walks.
- Launch through a cached CompiledFunction (tensor_shim._run_compiled):
  host cost per call 87 -> 16 us (FlyDSL rebuilt the cache key from every
  argument on each call).
- flydsl_pa_mqa_logits_fp4: ragged rows (query_start_loc + row_ends, an
  optional plan) run the row-group kernel; a rectangular call runs the
  varctx kernel as before rowgroup existed, no silent conversion. The page-8
  op test passes its rows as ragged rows.

Prefill (V4.1 geometry) 1.9-13x faster than scoring each row as its own
sequence; decode on par with the previous row-group kernel; outputs
bit-exact against it.

* update

* style: black-format the row-group FP4 MQA kernel
…ROCm#6163)

The multi-GPU job has timed out at its 120-minute limit on every recent
main run. Most multi-GPU tests start a new Pool and run
init_distributed_environment for every case. Each init takes 3-30+ s,
while the collective being tested takes microseconds. On MI350X,
test_fused_ar_rms_per_group_quant (80 inits) and test_custom_allreduce
(31 inits) alone took 60-90 % of the budget. Because of the timeouts,
the tests that run last in alphabetical order never ran at all.

Each worker now inits once, loops over every case, and tears down once.
Per-case results come back to the parent as small scalars. Inputs are
generated on the GPU from a per-case seed, and the reference is computed
on the rank itself, so no large tensors are pickled or reduced on the
CPU. Workers never raise inside the loop, so all ranks stay aligned on
every collective; the parent does the assertions. CUDA graphs captured
for each case are kept alive until teardown. Custom all-reduce caches a
peer's IPC address under the local buffer pointer, so freeing a graph
mid-sweep and getting the same address in a later capture would replay
with stale peer pointers.

CLI options, per-case log lines, and summary columns are unchanged.
Separate launches remain only where a setting must be fixed before init:
- tp size and topology
- AITER_QUICK_REDUCE_QUANTIZATION
- AITER_AR_1STAGE
- the AITER_CUSTOM_AR_MAX_SIZE=0 fallback path

Number of inits with CI default arguments:
  test_fused_ar_rms_per_group_quant      80 -> 1
  test_custom_allreduce (sweep)          31 -> 1
  gfx1250_poc/{allgather,p2p_bw,sync}    97 -> 3
  gfx1250_poc/allreduce                  20 -> 2
  test_allgather                         20 -> 1
  test_fused_ar_mhc_post_only            18 -> 1
  test_fused_ar_rms_mxfp4_quant           9 -> 3
  test_reduce_scatter                     8 -> 2
  test_custom_group                       8 -> 4
  test_quick_all_reduce                   7 -> 3
  test_fused_qknorm_ar                    6 -> 1

test_quick_all_reduce_rmsnorm had no __main__ block, so running it with
`python3` in CI executed nothing. It now calls pytest.main from __main__.

test_init_dist_env used a fixed env:// port (49374). In the first CI run
that reached this test, the port was already in use (EADDRINUSE), so the
test sat for 60 minutes while the peer ranks waited out the TCPStore
connect timeout. It now uses a free port with a tcp:// init method.
test_collective_profile's spawn path had the same problem with its fixed
29500 fallback; it now picks a free port before spawning.
* [Triton/Gluon] [gfx942] Enable sparse_mla_fwd on gfx942

sparse_mla_fwd was gated to gfx950. The Gluon kernel itself needs no change to
run on gfx942 -- the gl.amd.cdna4 intrinsics it uses lower fine there -- so the
gate was the only thing in the way, plus a tile size chosen against gfx950's
LDS budget.

gfx942 has 64 KB of LDS per workgroup against gfx950's 160 KB, so BLOCK_K=64
asks for 68608 B and fails to launch:

  OutOfResources: shared memory, Required: 68608, Hardware limit: 65536

BLOCK_K=32 is the largest tile that fits. num_warps is pinned at 4 rather than
following block_k // 16, so the LDS cap does not halve it as a side effect
rather than as a tuning decision; that is worth 1.19x -> 1.55x on the prefill
shapes.

dot_precision="fp8" is rejected on gfx942. It feeds the cache's own code points
to the matrix core, which needs OCP e4m3 MFMA; CDNA3 has fp8 MFMA in the fnuz
encoding only, so those code points come out wrong. It was silently wrong
(rel-err 7.5e-1) rather than faulting, so this is now an explicit error naming
the alternative. dot_precision="bf16" dequantizes the tile into LDS and works
on every arch and every cache format, fp8 caches included.

The bench's fp8-dot series is built from FP8_DOT_ARCHS for the same reason:
with the arch gate widened it would otherwise reach that series on gfx942 and
raise on its first point, and triton's perf_report does not catch it, so the
run died before printing a table.

The test's cache builders go back to an explicit OCP e4m3 rather than the
arch-native fp8 dtype. These records are OCP by definition of the format, and
the kernel's dequant reads OCP code points; on gfx942 the native dtype is
float8_e4m3fnuz, so building with it decoded to garbage (rel-err 1.0) on every
fp8-cache case. _classify_flat already rejects fnuz when a uint8 view is not
hiding the dtype.

gfx950 is unchanged: _arch_block_k -> 64, _arch_num_warps -> 4, lds_limited
False, FP8_DOT_ARCHS contains gfx950, and FP8_DTYPE is OCP e4m3 there either
way, so every value and branch resolves exactly as before.

Validated on MI325X (gfx942), TP4, GLM-5.3-Flash at 131k context, where this
path serves the rope-free NoPE MLA that has no AITER kernel today:

  op_tests test_sparse_mla   32 passed, 12 skipped (fp8 dots)
  bench_sparse_mla           runs clean, bf16 series only
  sparse-MLA decode bucket   3.099 -> 0.400 ms/step   7.74x
  sparse-MLA prefill         10988 -> 10330 us/call   1.06x
  end-to-end sweep           +7.14% mean output tok/s, 7/7 rows won
  gsm8k                      0.9719 vs 0.9712 strict-match (neutral)

* [Triton/Gluon] [gfx942] Bound the sparse_mla geometry against gfx942's LDS

_check_geometry has never had an upper bound on kv_lora_rank, and gfx942 already
takes the smaller of the two tiles this wrapper selects, so a latent too wide to
fit has nowhere left to go. It surfaced as Triton's OutOfResources at launch
rather than as something a caller can act on.

Measured on MI325X by reading the required byte count back out of
OutOfResources, the gfx942 footprint is exactly

  BLOCK_K * (kv_lora_rank + 8) * 2
  + BLOCK_K * (qk_rope_head_dim + 8) * 2    (separate rope only)
  + 32 * BLOCK_K

on every point at BLOCK_K 32 and 64: 67072 B at kv_lora_rank=1024 rope-free,
71680 with a 64-wide rope, 132608 at 2048. Those constants hold only for bf16
tiles with the async path off, which is every gfx942 launch, since fp8 dots are
rejected there and lds_limited forces ASYNC_LDS off.

The check raises a ValueError naming the geometry, its footprint and the budget.
gfx950 is not in the table, so it is left to the launcher exactly as before.

  test_sparse_mla   32 passed, 12 skipped (unchanged)
  guard boundary    512 and 512+rope still launch; 1008, 1024 and 2048 are
                    rejected, and the computed figures match the measured
                    Required: byte counts exactly

* [Triton/Gluon] [gfx942] Cover the fp8-dot arch gate

The gate had no test on any arch, not just gfx942: the shape matrix skips its
fp8 cases off gfx950, so the branch that rejects fp8 dots was never reached and
the branch that accepts them only incidentally.

_resolve_dot_precision's arch parameter no longer defaults. That default was the
real hazard behind the missing coverage: there is one call site, and dropping
its third argument would have silently resolved every launch as gfx950 and
re-enabled fp8 dots on gfx942 -- which a unit test on the helper cannot see,
since the helper still behaves correctly when handed an explicit arch. Required,
that same edit is a TypeError caught by 32 of the 34 tests; verified by making
it and running the suite.

test_dot_precision_arch_gate parametrizes over SUPPORTED_ARCHS, so it needs no
GPU, never skips, and covers a newly added arch automatically.

  test_sparse_mla   34 passed, 12 skipped (was 32 passed, 12 skipped)

* [Triton/Gluon] [gfx942] Reject the packed caches outside gfx950

fp8_dsv4_mla, fp8_g64 and the SWA+top-k two-loop return through _forward_paged
before the kernel's own arch gate, so widening that gate never reached them.
They route to pa_decode_sparse, whose packed driver is gated on
DEVICE_ARCH == "gfx950"; anywhere else they land in its fallback path, which
reads a plain grouped fp8 pool rather than these records and rejects them on
dtype:

  fp8_g64        AssertionError: kv_scales supplied but unified_kv is
                 torch.uint8, expected torch.float8_e4m3fnuz
  fp8_dsv4_mla   RuntimeError: unified_kv dtype mismatch: kv=torch.uint8,
                 q=torch.bfloat16

Neither names the arch, so on gfx942 both read as a caller mistake. The
ordering is pre-existing, since the early return precedes the assert on main
too, but listing gfx942 in SUPPORTED_ARCHS advertises formats that have no
implementation there, so the wrapper now says so itself.

Reaching the fallback path instead is not an option: it wants the arch-native
fnuz dtype and these records carry OCP code points, so it would decode them
wrong in exactly the way dot_precision="fp8" does.

Verified on gfx942 through the public wrapper:

  bf16, fp8_scalar, fp8_dsv32_mla   launch, unchanged
  fp8_g64, fp8_dsv4_mla             ValueError naming the arch
  test_sparse_mla                   36 passed, 12 skipped (was 34 and 12)

gfx950 is in PACKED_ARCHS, so every branch resolves there as before.

* [Triton/Gluon] [gfx942] Cover the sparse_mla LDS budget guard

The GPU suite only launches the default 512/64 geometry, so a regression
in _check_lds_budget would restore OutOfResources while tests stayed
green. CPU-only cases pin the measured rope-free and separated-rope
boundaries, including the exact 64 KB point.

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Reject fp8 q and caches on gfx942

The kernel reads every fp8 byte -- q, the cache, and the operands of the fp8
dots -- as OCP e4m3, which is gfx950's native fp8. On gfx942 the native fp8
is fnuz: vLLM's fp8 KV cache (current_platform.fp8_dtype()) and aiter's
quantizers (dtypes.fp8) both write float8_e4m3fnuz there. The encodings share
a bit layout with exponent bias 7 against 8, so a gfx942 cache read as OCP
comes out 2x too large, and fnuz's saturated +-240 (0x7F/0xFF) are NaN in OCP.

The fp8-cache tests passed on gfx942 only because their fixtures had been
switched to OCP to match the kernel, which no gfx942 producer writes. A cache
arrives as bytes, usually behind a uint8 view, so the wrapper cannot tell the
encoding from the tensor; _classify_flat catches a fnuz-typed flat pool and
nothing else.

So gfx942 now takes bf16 q and a bf16 cache only, and says why. That is what
the GLM-5.3-Flash path this PR enables uses: vLLM keeps that model's MLA
cache in bf16 and dispatches only bf16 q/kv here. FP8_DOT_ARCHS becomes
FP8_ARCHS and covers fp8 q and caches as well as the dots. The test fixtures
go back to the arch-native fp8, and the fp8 cases skip on gfx942 instead of
running on data nothing there produces. Reading fnuz natively, which would
also let the fp8 dots use CDNA3's fnuz MFMA directly, is left to a follow-up.

The >2 GB global-load test gains a bf16 case, so gfx942 keeps that path
covered now that the fp8 case skips there.

Verified on MI325X (gfx942):

  test_sparse_mla     32 passed, 29 skipped (was 44 passed, 12 skipped;
                      every skip is an fp8 case)
  native fnuz cache   rejected through sparse_mla_fwd as fp8_scalar and
                      fp8_dsv32_mla; with the gate stubbed out the same call
                      returns rel-err 1.01 and the wrapper test fails
  bench_sparse_mla    runs, bf16 series only

gfx950 is in FP8_ARCHS, so every branch resolves there as before.

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Take the sparse_mla LDS budget from arch_info

_check_lds_budget kept its own copy of gfx942's LDS capacity. It now reads
arch_info._LDS_CAP_BYTES, as pa_decode_sparse and the GEMM num_stages picker
do, so the capacity has one definition.

_LDS_CAP_BYTES lists gfx950 as well, so dict membership can no longer pick
the checked arch, and the gfx942-only gate is now explicit. The footprint
model was measured for gfx942's bf16, non-async tiles; gfx950 stays with the
launcher as before, which test_lds_budget_gfx950_is_unchecked pins.

  test_sparse_mla   32 passed, 29 skipped (unchanged)

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Pick the sparse_mla launch config by arch

_async_launch_config took an lds_limited flag that sparse_mla_fwd derived
from the arch. It now checks arch_info.get_arch() itself, so the arch
switch is visible where the config is chosen, and its signature is back to
main's.

No launch changes: lds_limited was exactly arch == "gfx942".

  test_sparse_mla   32 passed, 29 skipped (unchanged)

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Reword the fp8-dot arch error

"{arch}'s decodes fnuz" read as malformed. It now says the arch's native
fp8 is fnuz, the wording _check_fp8_arch already uses.

  test_sparse_mla   32 passed, 29 skipped (unchanged)

Signed-off-by: Jin Tao <jin.tao@amd.com>
* [Triton/Gluon] [gfx942] Say which caches each dot_precision takes per arch

The dot_precision docstring still said bf16 dots work with every cache
format, and did not say fp8 dots are gfx950-only. On gfx942 the wrapper
takes a bf16 cache alone and rejects the rest, so the public docstring now
states the same scope the code enforces.

Docstring only.

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Sort the sparse_mla test and bench imports

This PR's import edits in test_sparse_mla.py and bench_sparse_mla.py left
both blocks unsorted, which the pinned ruff 0.16.0 in pre-checks reports as
I001. Both files are clean at the merge base. Import order only.

  ruff 0.16.0 check   all three sparse_mla files pass
  test_sparse_mla     32 passed, 29 skipped (unchanged)
  bench_sparse_mla    runs, bf16 series only

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Check the sparse_mla LDS budget at the launch's KV pad

_check_lds_budget modelled the KV tile at the kernel's default 8-element row
pad, but a prefill launch (num_queries >= _PREFILL_MIN_ROWS, bf16 dots)
passes KV_LDS_PAD=16, so the guard undercounted every prefill launch by
BLOCK_K * 8 * 2 = 512 B. The pad is now computed once and handed to both
the guard and the launch, so the two cannot drift apart again.

No power-of-two geometry changes its accept/reject result, so no launch
that used to work is rejected. What changes is that the guard now reports
the footprint the launch actually needs, and it no longer depends on the
gap between pow-2 widths to stay safe. Widths in between never reach the
launcher: the KV tile's PaddedSharedLayout asserts a pow-2 shape.

Measured on MI325X with the guard stubbed out, reading metadata.shared when
the kernel fits and OutOfResources' Required: when it does not, at BLOCK_K
32, decode (8 queries) and prefill (2048):

  kv_lora_rank / rope   decode    prefill
  512 / 0               34304     34816     fit
  512 / 64              38912     39424     fit
  512 / 256             51200     51712     fit
  512 / 512             67584     68096     rejected
  1024 / 0              67072     67584     rejected
  1024 / 64             71680     72192     rejected
  2048 / 0              132608    133120    rejected

The guard now reproduces every one of those through sparse_mla_fwd. The
boundary test gains the prefill pad, with exact-fit points at 1000 (decode)
and 992 (prefill). Dropping the pad from the guard again fails the five
prefill rejects.

  test_sparse_mla   43 passed, 29 skipped (was 32 and 29; the new ones are
                    boundary cases)

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Read the sparse_mla tile and warps from configs/

_ARCH_BLOCK_K and _ARCH_NUM_WARPS were per-arch launch values in Python,
which the config rules in aiter-ops-triton.instructions.md and
configs/CLAUDE.md place in JSON. They now live in

  configs/gfx942/gluon/attention/sparse_mla/DEFAULT.json   BLOCK_K 32, num_warps 4
  configs/gfx950/gluon/attention/sparse_mla/DEFAULT.json   BLOCK_K 64, num_warps 4

read through resolve_config_dir() + load_config_json(), the single
DEFAULT.json read that configs/CLAUDE.md describes for attention wrappers.
The gfx950 file carries main's hardcoded block_k = 64 and
num_warps = block_k // 16, so nothing changes there. The entry is keyed by
the kernel, _sparse_mla, so the reduce can get its own entry later.

Only the values this PR made per-arch move. The rest of the launch policy
(the BLOCK_M rule, split-K, the async tile choice, the prefill KV pad) is
main's and stays as it was. Moving it all would rework gfx950's launch path,
which is its own change.

test_launch_config_published checks from any machine that every arch in
SUPPORTED_ARCHS ships the file, so adding an arch without one fails on
every runner rather than only on that arch's. The LDS boundary test takes
gfx942's BLOCK_K from the same file.

Verified on MI325X (gfx942): the compile-time launch arguments the wrapper
passes (BLOCK_K, num_warps, waves_per_eu, ASYNC_LDS, KV_LDS_PAD and the
rest) are identical before and after over 96 shapes spanning decode and
prefill, H 8/16/64, rope 0/64, has_invalid and forced split-K.
_get_config() costs 0.65 us per call.

  test_sparse_mla   45 passed, 29 skipped (was 43 and 29; +2 config checks)

Signed-off-by: Jin Tao <jin.tao@amd.com>

* [Triton/Gluon] [gfx942] Leave gfx942's waves_per_eu to main's rule

_async_launch_config's gfx942 branch returned early with its own copy of
main's waves_per_eu rule, so gfx942 carried a second, arch-specific
statement of a launch value. All the branch has to do is keep the async
tiles off, which are sized for gfx950's LDS, so the arch check now sits in
the condition that enables them, and gfx942 falls through to main's rule
for everything else. The switch still reads arch_info.get_arch() inside
_async_launch_config, and the signature is still main's.

The async path also needs fp8 dots, which gfx942 rejects before it gets
here, so today the check only takes effect once fnuz fp8 dots exist there.
It stays for that case.

No launch changes. Checked on MI325X:

  _async_launch_config   old and new agree on all 10752 combinations of
                         its inputs under both gfx942 and gfx950, fp8_dots
                         included
  launch arguments       identical to the PR head over the same 96 shapes
  test_sparse_mla        45 passed, 29 skipped (unchanged)

Signed-off-by: Jin Tao <jin.tao@amd.com>
…able (ROCm#6174)

Running DeepSeek-V4.1-Flash at TP4 with DSpark (the ATOM accuracy job)
logs "not found tuned config" for six bf16-in/bf16-out GEMM (N, K) pairs
that have no gfx950 row anywhere in the bf16_tuned_gemm family:
(32, 5120), (128, 512), (128, 5120), (384, 5120), (512, 5120) and the
TP4 lm_head (32320, 5120).

Each pair is tuned at the 25 M values the dsv41 table already uses
(1..16384). That covers every power of two, which is where the lookup's
gl=1 padding lands, so every M seen in CI resolves to a row. It also
includes 6/12/24/48/96/192/288/384/768/1536, which are the DSpark
verify batches (requests x 6) and now hit a row exactly.

Backends tried: asm, opus, flydsl, triton, skinny, torch. err_ratio
<= 0.031 for every row. Speedup over the torch default, median/max:
  (32, 5120)     2.13x / 2.29x
  (128, 512)     2.11x / 2.53x
  (128, 5120)    2.07x / 2.81x
  (384, 5120)    1.72x / 2.41x
  (512, 5120)    1.79x / 2.40x
  (32320, 5120)  1.02x / 1.19x  (weight-bandwidth bound; torch stays
                                 best at 8 of the 25 M values)

Validation:
- test_config_shape_collision passes.
- --run_config on all 150 rows: every row reports OK.
* gfx950 tuning bf16 tp8 shapes

* Drop small-win Kimi K3 bf16 configs
…5909)

* [Triton] Gluon PA decode: fix VGPR blow-up on Triton 3.8

Carry the prefetched KV tiles unconditionally in the PS decode kernels.
Triton 3.8 if-converts the conditional carry into selects that keep
both key tiles live (~260 VGPRs, 1 wave per SIMD).

Add ps_decode_performance_test() to reproduce (not run in CI).

* Apply review comments
* Run AITER full releases weekly at 1 a.m. Central

* Update actionlint for timezone schedule support
* Fix MXFP4 and MXFP8 fallback for older Triton

* Remove redundant quantization regression test

* Cache scaled downcast capability and handle missing Gluon
* [Triton/Gluon] Add fp32 reference and tests for fused Qwen3-Next GDN decode

Defines the op contract for a fused Qwen3-Next Gated DeltaNet decode kernel
before any kernel lands: packed qkvz/ba split, depthwise causal conv1d with
bias + SiLU, GDN gating, the fp32 recurrent state update, gated RMSNorm with a
SiLU gate, and an optional per-head group FP8 quantization epilogue.

This is a sibling of fused_kda_decode, not a variant of it. KDA uses a
per-channel lower-bounded sigmoid gate, a sigmoid output gate, uniform head
counts and no conv bias; Qwen3-Next GDN uses a per-head
exp(-exp(A_log) * softplus(a + dt_bias)) decay, a SiLU output gate,
num_v_heads == 2 * num_k_heads and a biased conv. The head topology alone
(lp = H*K offsets) cannot express VH == 2*KH, so a mode flag on the existing
kernel is not viable.

The reference has been cross-checked on MI355X against all seven Artemis Gluon
variants across all nine specialized batch sizes (M = 1,2,4,8,16,32,64,128,256).
conv_state matches bit-exactly on every band; the other outputs agree within
the ATOL/RTOL 0.05 bar this directory already uses for fused_kda_decode, with
the residual being a few bf16 ULP from differing fp32 evaluation orders.

The kernel-side tests are written against the pad_slot_id convention that
aiter's other GDN ops already honour (fused_conv_recurrent_norm skips
state_idx < 0; causal_conv1d_update_split_qkv takes an explicit pad_slot_id),
rather than the reserved-slot-0 redirection the out-of-tree Artemis adapter
maintains via a monkeypatched _replay_metadata. They skip until the op lands.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Add fused Qwen3-Next GDN decode op for gfx950

One Gluon launch replaces the four-kernel Qwen3-Next decode chain
(fused_qkvzba_split_reshape_cat -> causal_conv1d_update ->
fused_recurrent_gated_delta_rule_packed_decode -> layer_norm_gated) and can
optionally emit the per-head group-128 FP8 activations that a block-FP8
out_proj consumes directly, removing a separate quantization launch.

A sibling of fused_kda_decode, not a variant of it: KDA uses a per-channel
lower-bounded sigmoid gate, a sigmoid output gate, uniform head counts and no
conv bias, and its lp = H*K offsets structurally cannot express VH == 2*KH.

Verified bit-identical to the upstream Artemis kernel on every output
(out, quantized, scales, ssm_state, conv_state) at M = 1, 32 and 256 on MI355X,
and green against the fp32 reference across M = 1..128.

Two changes on top of the upstream source:

* pad_slot_id: rows whose state index is the sentinel are skipped without
  reading or writing state, matching the contract SGLang's
  replay_state_indices_validator documents and the one aiter's
  fused_conv_recurrent_norm already implements. This removes the upstream
  kernel's dependence on a caller-maintained reserved slot 0.
* HAS_FP8: the quantization epilogue is optional, because an unquantized
  out_proj cannot accept an (fp8, scales) tuple.

Support is reported by fused_gdn_decode_qkvz_supported() as (bool, reason)
rather than asserted, so callers fall back and can log why. Coverage is
deliberately narrow and stated: gfx950, VH == 2*KH, head dims 128, conv width 4.

The Gluon body is generated by build_aiter_kernel.py, which pins the source
sha256 and asserts every edit it applies; the source is ~600 lines of dense
Gluon with explicit register layouts, where a transcription slip would surface
only as a numerical mismatch.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Drop the kernel generator from the aiter tree

build_aiter_kernel.py reads the Artemis kernel pack, which is not part of this
repository, so shipping it here would put a script in the tree that cannot run.
Provenance stays where a reviewer needs it -- the generated kernel's header
records the source file and its sha256 -- and the generator itself lives with
the pack it reads.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Add benchmark for fused_gdn_decode_qkvz

The maintainer guide asks for kernel + wrapper + test + benchmark; this is the
missing fourth.

Reports achieved HBM bandwidth against arch peak. The kernel performs no MFMA
and no gl.dot -- it is bound by the FP32 recurrent state, read and written once
per token per value head, which dominates every other tensor by an order of
magnitude. So bytes moved is the right roofline denominator.

Two measurement choices the script documents inline, because getting either
wrong silently invalidates the result: it times under CUDA graph replay, since
timing the Python call measures host overhead that at small batches is ~2.5x the
kernel itself; and it sizes the state pool independently of the batch, since
sizing it to the batch puts small batches entirely in cache.

Measured on MI355X: the kernel is latency-bound at small batch (1.4% of peak at
batch 1) and becomes bandwidth-bound from batch 64, saturating at 5.0-5.2 TB/s,
62-65% of the 8 TB/s peak.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Label the benchmark's timing as per-call

The header said 'us'. The timed region is a CUDA graph enclosing the whole
wrapper, so the number is per call. This op always issues a single launch, so
the two coincide here -- but the label should not depend on that staying true,
and the Artemis sources it derives from do use multi-launch paths at small
batch where per-call and per-launch differ.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Add the large-batch band to fused_gdn_decode_qkvz

Above batch 128 the op now uses a second decomposition that keeps the whole
[V, K] state tile in registers across both matrix-vector products, instead of
the latency-hiding path that serves smaller batches.

This is not a tuning of the existing kernel and could not be reached by one. The
two move identical bytes -- one read and one write of the fp32 state tile,
confirmed by source-level count -- and three candidate explanations for the gap
were tested and rejected: launch-order locality (rewriting the grid to the other
kernel's order gained 0.75%), store cache policy (.cs to .wt was slightly
worse), and routing large batches through the kernel that does carry a batch
ladder (about 5% of the gap). What remains is the register layout and reduction
structure, which is a different algorithm.

At batch 256 this takes the op from 53.5 to 46.5 us, and from 65% to 74.5% of
peak HBM bandwidth with the same byte count. At 128 the two are within 2%; below
128 the existing path is better by up to 15%, so the threshold sits at the
measured crossover.

Selection is a range check rather than a shape table, so every batch size above
the threshold is covered -- there are no gaps to fall into. Correctness is
verified against the fp32 reference at 128, 192 and 256, and at the
non-power-of-two sizes 136, 160, 200 and 248.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Address review: predicate coverage, test gaps, conventions

Review findings from ROCm#5520, verified against the code before fixing.

The most serious was a hole in the tests: the correctness case computed
ref_quant and never compared it, so the FP8 output was checked only through its
scales. A broken epilogue could pass every case as long as the bf16 output,
scales and state updates were right. Now asserted, in value space rather than
code space -- a 1-ULP e4m3 code difference near the top of the range is enormous
as a raw integer and meaningless as a value, so both sides are dequantized with
their own scale.

The support predicate claimed more than it checked:
  - conv_state and ssm_state are indexed with the same slot, but their slot
    counts were never compared, so a shorter conv pool passed and was then read
    out of bounds
  - only the two pools were required contiguous, while projected_qkvz,
    projected_ba, conv_weight, conv_bias, A_log, dt_bias and norm_weight are all
    addressed with flat pointer arithmetic -- a same-shaped strided view passed
    and was read from the wrong locations
  - conv_bias was missing from the device check, and A_log, dt_bias and
    norm_weight were not parameters at all, so the predicate could return
    (True, "") for a launch whose operands were on the CPU or wrongly shaped

Also: the bf16-only path is now checked numerically rather than only for dtype
and None, since HAS_FP8=False is a separate compiled specialization; a broken op
module now fails instead of silently skipping every kernel test, with the skip
narrowed to the optional Gluon dependency; the kernel module no longer imports
torch, so it stays importable without PyTorch as the convention requires; and
the three launchable kernels carry config-aware repr= callbacks.

Dropped float8_e4m3fnuz rather than testing it: that is the gfx942 format and
this op is gfx950-only, so advertising it promised a path that can never run.

PAD_SLOT_ID is deliberately excluded from the repr keys. It is a protocol
sentinel rather than a tuning key, and make_kernel_repr does not sanitize the
sign of a negative value -- including it emits an invalid function identifier
and fails at compile.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Fix style CI: format generated output, modernize annotations

The style checks were red on files I had not linted the same way CI does. Three
distinct causes:

The generated Gluon module was never formatted. It is copied from sources that
are not black-formatted, so black rejected it. The generator now formats its own
output rather than leaving it to be hand-patched.

My local ruff runs used --select E4,E7,E9,F, which is narrower than the rule set
the repository's pyproject actually enforces, so C408, ISC004, RUF059, UP006,
UP035, UP045 and I001 all went unseen. Fixed at source: dict() calls rewritten
as literals, implicit string concatenation inside return tuples parenthesised,
typing.Optional/Tuple replaced with PEP 604/585 spellings, imports sorted, and
the unread second element of the inline-asm pairs renamed to _scratch.

Also removes build_aiter_kernel.py, which had been copied to the repository root
by mistake and was never meant to ship here.

Verified with the versions CI pins -- black 26.5.1 and ruff 0.16.0 -- run from a
tree carrying this repository's pyproject, over every file the branch touches.
26 op tests still pass.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Sort imports as the repository config does

Ruff's I001 fired on three files. My earlier local run missed it because I linted
copies flattened into a scratch directory, where the aiter package path no longer
matched, so isort classified aiter imports as third-party and sorted them into a
different, self-consistent order. Running ruff 0.16.0 against the real paths with
the repository's own pyproject reproduces CI exactly.

Worth noting for anyone reproducing: the ruff step uses reviewdog with
filter-mode=diff_context and -fail-on-error=true, so any finding on a changed
line fails the check -- whole-repo cleanliness is not the bar, the diff is.

Verified with the pinned versions, black 26.5.1 and ruff 0.16.0, on the real
tree. 26 op tests still pass.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Move the op test into the gated_delta_net category

Mirrors the public wrapper's category, as requested in review. Adds __init__.py
to match the sibling test directories.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Route the large band through the shared device helpers

Addresses the review asks to stop duplicating operations inside the kernel file
and to use the repository's activation helpers.

_decode_group now calls _head_dynamics and _quantization_scale instead of
re-implementing the gate, softplus, exp, sigmoid and FP8 scale inline, and
_sigmoid delegates to _triton_kernels/activation._sigmoid_exp2.

Outputs are bit-identical to the previous kernel on all five results -- bf16
output, quantized, scales, and both mutated state pools -- at batch 1, 2, 8, 32,
64, 128, 192 and 256. Kernel time is unchanged: 10.47 -> 10.53 us at batch 32,
27.03 -> 26.76 at 128, 46.58 -> 46.45 at 256.

_normalize_qk is left in place. Unlike the others it is not a call-site swap:
it takes a shared-memory handle and reads through .gather() with a hardcoded
128 offset, where the large band uses .slice().load() with an explicit
gl.SliceLayout tied to its register layout. The arithmetic is the same either
way, so routing through it would change layouts in a kernel that is
bandwidth-bound above batch 128 without changing results.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

* [Triton/Gluon] Make the decode op import-safe and gate on Triton >= 3.8

The wrapper imported the Gluon tile kernels at module scope, so the module
raised ImportError on any build without triton.experimental.gluon -- before
fused_gdn_decode_qkvz_supported() could report the reason and let the caller
fall back, which is the one case the probe exists for. Import the three
kernels at their launch sites instead, and defer arch_info for the same
reason: detection can touch the driver at import time. Mirrors
fused_gdn_prefill_qkvz.py.

Add _gluon_supported(), an explicit Triton >= 3.8 floor checked before the
import probe. Some ROCm 3.7 builds carry an earlier, incompatible Gluon where
the import succeeds but the layout ops these kernels use miscompile, so
probing the import alone is not enough.

Trim the batch ladder from ten sizes to one per dispatch branch
(1, 32, 64, 127, 128, 256), adding 127 to cover the previously untested
65-127 path. The branch mapping is recorded above the list.

The Gluon kernel is unchanged and outputs stay bit-identical across batches
1-256, so the reported accuracy and end-to-end numbers are unaffected.

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>

---------

Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>
…ue head size (ROCm#3698)

* [Triton] unified_attention: mask V load and output store by value head size

unified_attention sizes everything off HEAD_SIZE = q.shape[-1] and reuses one
dim_mask (offs_d < HEAD_SIZE) for the Q load, the K load, the V load and the
output store. That holds when qk_head_dim == v_head_dim, but models with
asymmetric heads (e.g. MiMo with qk=192, v=128) have a V cache and an output
whose per-head width is the value head size, not HEAD_SIZE. With the single
192-wide mask the V load reads past the 128-wide head into the next kv-head
(and past the buffer on the last head), and the output store writes 192 dims
into a row whose per-head stride is 128, clobbering the next head and running
off the end on the last head. On MiMo decode this shows up as garbled logits
at low concurrency and a hipErrorIllegalAddress under load. Prefill is
unaffected.

Mask the V load by the V cache head stride (stride_v_cache_2) and the output
store by the output per-head stride (output_stride_1), both already passed to
the kernels. For symmetric models HEAD_SIZE, stride_v_cache_2 and
output_stride_1 are all equal, so this is a no-op. No kernel signature or
launcher change.

Signed-off-by: adlashab <adlashab@amd.com>

* [Triton] unified_attention: use explicit V_HEAD_SIZE for V load and output store

The first version of this PR masked the V load and the output store with the
V cache head stride (stride_v_cache_2) and the output per-head stride
(output_stride_1). That works for the sglang layout but it leans on
qk_head_size > v_head_size: when the value head is wider than the query head
the V tile would only be loaded partially.

Replace the stride trick with an explicit V_HEAD_SIZE / V_HEAD_SIZE_PADDED
constexpr pair, mirroring HEAD_SIZE / HEAD_SIZE_PADDED. The V load, the acc
tile, the segment buffer and the output store now size off the value head,
while Q and K keep using HEAD_SIZE, so both orderings (v < qk and v > qk) are
handled. This includes the V load in the SPLIT_UNMASKED_LOOP prefill loop of
the 2d kernel. The triton subwrappers pass V_HEAD_SIZE from
_UAParams.head_size_v, which already falls back to head_size for the shuffled
layout, so for symmetric heads V_HEAD_SIZE == HEAD_SIZE and the generated
kernel is identical to before. The gfx950 / gfx1250 gluon kernels are not
touched, asymmetric heads are already routed away from them.

Add asymmetric-head cases to test_unified_attention.py: the MiMo case
(qk=192, v=128), a symmetric case, and the reverse ordering (qk=128, v=192),
across decode and prefill shapes, bf16 and fp8, both backends.

Signed-off-by: adlashab <adlashab@amd.com>

* [Triton] unified_attention: keep asymmetric heads off the gfx1250 gluon 2d and reduce kernels

The gfx1250 gluon 2d kernel and the gluon reducer still size V and the
output off HEAD_SIZE, but is_2d_gluon_available and
is_reduce_gluon_available only checked head_size_v == head_size on gfx950.
Add the same check to both gfx1250 gates so asymmetric heads fall back to
the triton kernels there. The gfx1250 3d path only runs with the shuffled
cache, where head_size_v is head_size, so it needs no change.

Also add V_HEAD_SIZE (and V_HEAD_SIZE_PADDED for the 2d kernel, which
already lists HEAD_SIZE_PADDED) to the kernel reprs so specializations
that differ only in the value head get different names.

Signed-off-by: adlashab <adlashab@amd.com>

* [Triton] unified_attention: guard asymmetric heads, pick config by the wider head

Three follow-ups for asymmetric qk/v heads:

- deny pre-shuffled KV cache with asymmetric heads for now. head_size_v
  falls back to head_size for the shuffled layout (its last dim is the
  vector width, not the head size) and the shuffled kernels lay V out with
  HEAD_SIZE, so an asymmetric shuffled cache would silently give wrong
  output. Compare the per-page element count of k and v instead.
- assert out.shape[-1] == head_size_v. The kernels store with the V stride
  and mask, so an out sized by the qk head would get a partial write.
- look the config up with max(head_size, head_size_v). There are no
  asymmetric configs yet, so pick by the wider head, the one that sets the
  accumulator and V tile size.

Adds a small test for the two asserts.

Signed-off-by: adlashab <adlashab@amd.com>

---------

Signed-off-by: adlashab <adlashab@amd.com>
Signed-off-by: Martin Domanský <ragua@email.cz>
…gfx1250 (ROCm#4894)

* Merge branch 'ROCm:main' into quant_mxfp4

* Update bench_quant_mxfp4.py

* Resolve formatting issues

* Resolve formatting issues

* Resolve formatting issues

* Resolve formatting issues

* Resolve typing deprecation and import formatting

* Fix Ruff import and __all__ ordering

* Replace deprecated typing.Tuple in quant module

* Use PEP 604 unions for optional tensor annotations

* Include instruction for mxfp4 qunatization.

* Change hueristic
Added pre fetch
slight modification in bench script

* Consolidate comments and fix formatting/duplicate imports

* Modifying shapes to be tested

* Remove the inline asm part as the test triton patch provided by compiler team, now successfully uses the correct instruction

* Removing unwanted files

* Removing json file

* Migrate gfx950 Gluon mxfp4/mxfp8 quant tuning to JSON configs

Also update mxfp4/mxfp8 quant test files with representative shapes covering the benchmark shape sweep.

* COnfig file format change

* Add MXFP4/MXFP8 gfx950/gfx1250 gluon quant kernels (squashed)

* style: black-format bench_quant_mxfp4_fp8.py

* refactor(quant): switch gfx1250 mxfp4/mxfp8 block-config to JSON rule trees

Replace the hardcoded if/else tuning chains and the flat bucket-key config
lookup with configs/gfx1250/gluon/quant/{quant_mxfp4,quant_mxfp8}/DEFAULT.json
default+rules trees, resolved via config_utils.select_tuned_config. Drops
the now-unused quant_config_utils module.

* fix(quant): pin mxfp4 gfx1250 config lookup to arch=gfx1250

_mxfp4_gfx1250_config's launch config is also reused, unchanged, by the
plain-Triton fallback kernel on non-gfx1250 archs. Without an explicit arch
override, resolve_config_dir defaulted to the running arch, so the config
lookup 404'd on every non-gfx1250 arch (e.g. gfx950), breaking every
dynamic_mxfp4_quant call there -- including the gfx950 stochastic-rounding
tests and downcast_to_mxfp_pow2_scale_fp4.

* fix(quant): remove dead/broken leftover code in _mxfp4_pack_op from main merge

The merge of main introduced main's own refactor splitting _mxfp4_quant_op
into scale computation + a new _mxfp4_pack_op(qx, ...) helper that takes
already-scaled qx. The merge left dead leftover lines inside
_mxfp4_pack_op that referenced an undefined 'x' (not a parameter of this
function) and recomputed an unused scale, causing a NameError at trace
time (flagged by CI as ruff F821/RUF059).

* perf(quant): narrow gfx1250 mxfp8 small-M tile width for K > 1024

For M <= 32, the launch config previously picked BLOCK_SIZE_N purely from
a fixed element budget over BLOCK_SIZE_M, independent of K. Benchmark
sweeps (repeated trials, not single samples) show that for K > 1024 a
narrower 128-wide tile robustly beats the wider budget-derived tile --
more independent CTAs hides TDM latency better than one bigger transfer
per CTA at this size. ~11-13% bandwidth improvement at M=8, K=3072/7168
(closes the largest lag vs the Triton fallback at these shapes).

* refactor(quant): table-based mxfp8 small-M BLOCK_SIZE_N + tighten comments

Replace inline if/else BLOCK_SIZE_N arithmetic for mxfp8's M<=32 branch with an explicit _MXFP8_SMALL_M_BLOCK_SIZE_N lookup table + helper. Also shorten a duplicated multi-line comment in the gluon kernel and condense select_tuned_config's docstring.

* Address PR review comments: bucket-key config lookup, kernel repr, lazy Gluon import, config dir rename, trim redundant test shapes

- Replace the rule-tree select_tuned_config with lookup_tuned_config, a
  flattened bucket-key resolver ({schema, "M_GEQ_33.N_LEQ_16384": {...},
  "any": {...}}) matching unified_attention_utils's existing pattern.
- Add make_kernel_repr-based repr= to both gfx950 Gluon quant kernels.
- Move the gfx950 Gluon kernel imports in quant.py out of the module
  header into the arch-gated dispatch branches that use them.
- Rename configs/gfx950/gluon/quant/{mxfp4,mxfp8}_quant -> {mxfp4,mxfp8}
  to match the sibling PR's naming convention.
- Drop one of three redundant large-non-square shapes from the mxfp4/
  mxfp8 quant parametrize lists.

* fix(quant): address gfx950-PR review feedback applicable to gfx1250

- Replace select_tuned_config's invented default+rules JSON schema with
  the repo's established composite bucket-key format (config_utils.
  lookup_config), matching the pattern used by unified_attention/gemm/
  conv config families. Flattens mxfp4/mxfp8 DEFAULT.json into complete,
  mutually exclusive buckets instead of stacked rules.
- Add repr= (make_kernel_repr) to the two launched gfx1250 gluon kernels
  so tuned specializations are distinguishable in traces/artifacts.
- Make the gluon kernel import lazy, gated behind the gfx1250 arch
  check, matching gemm_a16w16.py's pattern -- importing aiter.ops.triton.
  quant.quant no longer unconditionally pulls in triton.experimental.gluon.
- Rename configs/gfx1250/gluon/quant/{quant_mxfp4,quant_mxfp8} to
  {mxfp4_quant,mxfp8_quant} to align directory naming with the gfx950
  sibling PR (ROCm#4383).

* docs(quant): shorten config lookup docstrings and comments

* docs(quant): shorten config lookup and gfx950 kernel comments

* revert(quant): restore _triton_kernels/quant/quant.py to main (drop unneeded fp32-cast removal)

* Update quant.py

* Move gfx950 Gluon quant shape-derived block sizes into JSON and retune mid-M configs

- Drop the Python BLOCK_SIZE_M/N overrides in dynamic_mxfp4_quant and
  dynamic_mxfp8_quant; launch configs now come only from the JSON tables.
- Tune the 33<=M<=1024 buckets (more, smaller CTAs) for MXFP4 and MXFP8.
- Compute e8m0 scale bias in int32 before narrowing to uint8.

* Use lookup_config for gfx950 Gluon quant configs to match the sibling PR

Replace lookup_tuned_config with lookup_config(table, axes, **values) and
drop the "schema" key from the gfx950 mxfp4/mxfp8 JSON tables.

* perf(quant): retune gfx1250 gluon mxfp4/mxfp8 configs; JSON is gluon-only

- Re-tune per-bucket block/warp/buffer configs via the dispatch path with
  cold inputs (old config kept as a candidate); mxfp4 +10.6%, mxfp8 +5.4%
  geomean vs previous gluon configs.
- Move the former in-code shape rules (small M / small N tile sizes) into
  the JSON buckets so the JSON is the single source of block sizes.
- mxfp4: JSON is used only by the gfx1250 gluon launch; the Triton
  fallback is main's original heuristics again.
- mxfp4: fix compile failures for M<=8 at N>1024 and M<=4 (wide BLOCK_SIZE_N
  at tiny BLOCK_SIZE_M) by using BLOCK_SIZE_N=128 tiles there.
- Shorten comments/docstrings across the touched files.

* bench_quant_mxfp4_fp8: match the gfx950 branch (drop duplicate line_vals, original comment)

* gfx1250 quant: shared e8m0 helper, tile helpers, drop dead params, waves_per_eu in JSON, backend tests

* gfx1250 quant: drop unreachable table entries, restore async_store note, add mxfp8 bucket shape

* gfx1250 quant: int64 mxfp8 strides, overlap mxfp4 stores, infer table axes in get_quant_config

* gfx1250 quant: retune mxfp4 bucket table (38 -> 30 keys)

---------

Co-authored-by: Satya Nikhil Kodukula <nikhil.kodukula@gmail.com>
Actions/stale, daily: a pull request is labelled `stale` after 15 days without
activity; any push or comment clears it. Nothing is closed -- -1, since leaving
it unset would mean the action's 7-day default. Drafts and PRs labelled
keep-open, blocked, do-not-close or dependencies are exempt. Issues are out of
scope. Pinned to the v10.4.0 commit.
Updated exempt PR labels to only include 'do-not-close'.
@yuychang yuychang closed this Oct 8, 2026
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.