Skip to content

fix(rocke): address #13205 review: develop merge, sinks gates, case study - #5

Merged
AviralGoelAMD merged 129 commits into
Parthiv911:users/Parthiv911/rocke/gfx942-dense-cfvst-kdb-vpffrom
ROCm:users/avirgoel/rocke/pr13205-review-fixes
Oct 14, 2026
Merged

AviralGoelAMD merged 129 commits into
Parthiv911:users/Parthiv911/rocke/gfx942-dense-cfvst-kdb-vpffrom
ROCm:users/avirgoel/rocke/pr13205-review-fixes

Conversation

@AviralGoelAMD

@AviralGoelAMD AviralGoelAMD commented Oct 14, 2026 •

Copy link
Copy Markdown

JIRA ID: AICK-1900

Why this change

Review fixes for ROCm#13205, stacked on its head (6e35a8a6d3a). Merging this PR into users/Parthiv911/rocke/gfx942-dense-cfvst-kdb-vpf brings the branch up to date with develop (including ROCm#12620, gfx942 dense sinks) and addresses four of @aosewski's review comments. The changes are scoped to those comments.

What changed

Area / files Crux of change Review comment
kernels/gfx942/attention_dense.py (lse_store) Merge resolution: final running max m_i plus develop's sinks-aware row_has_mass. The pipeline's loop.results binding is renamed loop_res, so a stale res[0] read fails at build time. r4269756145
kernels/gfx942/attention_dense.py (supports_attention_dense) Rejects use_sinks on the bf16 CFVST and lds_num_buffers=2 paths. ROCm#12620 removed the blanket sinks rejection, and sinks x CFVST/KDB-VPF has no golden or numeric coverage. r4269756158
tests/test_attention_dense_gfx942.py 3 rejection rows: CFVST + sinks, KDB/VPF + sinks, KDB/VPF + sinks + LSE + persistent. r4269756158
tests/golden/*.json, tests/test_attention_dense_gfx942_golden.py Union of both sides. No existing hash changes, nothing re-blessed. r4269756145
builders/.../gfx942_dense_cfvst_kdb_vprefetch_case_study.md Device identifier instead of a product name; H1 and a scoped opening; bounded claim (10.1-16.3% at the 12 measured configurations); register-usage table at two toolchains. r4269756167, r4269756182, r4269756187
_use_cfvst and module docstrings Qualitative; they point to the case study instead of claiming that bf16 CFVST always spills. r4269756187

Register usage (r4269756187)

gfx942, B1 S4096 Hq32 Hkv8 D128 causal. Values come from the code-object notes (.vgpr_count / .vgpr_spill_count / .private_segment_fixed_size). The waves_per_eu=2 cap is 256 VGPRs.

variant ROCm 7.1 comgr, llvm20: VGPR / spills / scratch B ROCm 10 SDK comgr, llvm23: VGPR / spills / scratch B
bf16 default (CFVST off) 243 / 0 / 0 219 / 0 / 0
bf16 use_cfvst=True 256 / 0 / 0 234 / 0 / 0
+ lds_num_buffers=2 256 / 4 / 20 230 / 0 / 0
+ emit_lse=True 256 / 5 / 24 230 / 0 / 0
+ persistent=True, num_persistent=304 256 / 16 / 68 256 / 7 / 32
  • Spilling depends on the toolchain. At llvm23 the KDB/VPF pipeline fits; only the persistent grid spills.
  • Control-flow (dominator) analysis of the disassembly: in every spilling variant, the inner KV loop has 0 scratch instructions. On the persistent grid, the reloads run once per work item (16 at llvm20, 7 at llvm23).
  • llvm22 was not measured.

How we validated

Tested tree: this branch.

Gate Result
GPU numeric, ROCm 7.13 image, gfx942 304-CU test_attention_dense_gfx942_numeric.py -k "cfvst_pipeline or kdbvpf or lse or sink": 165 passed
GPU numeric, ROCm 10 (llvm23), gfx942 304-CU Same selection: 165 passed
CPU, gfx942 dense/attention 610 passed, 10 skipped (GPU-only), 304 subtests passed
Golden IR, llvm20/22/23 (Python lowerer) Passes; 37 cases per flavor, 0 changed hashes
Merge tripwires (mutation runs) res[0] without the rename: lse_kdbvpf_d128_bf16_causal drifts in all 3 flavors. res[0] with the rename: UnboundLocalError. PR-side row_has_key: lse_sink_persist_empty_window_d128_bf16 drifts in all 3 flavors. New rejection rows: 3 fail with the gate removed.
pre-commit run --from-ref origin/develop --to-ref HEAD Passed

Notes

  • Still open in the case study, and only the author can fill them: the measured run's correctness tolerance, and the baseline arm's full spec (persistent / exp2_fast, hand-built or dispatch-resolved). The opening paragraph marks both as not yet recorded (see also r4269756253).
  • The other review comments (persistent multi-work-item coverage, _COHORT tightening, ablation provenance, dispatch reachability, docstring wording, and the lds_num_buffers split) are not addressed here.

Abuudiii and others added 30 commits October 11, 2026 23:03
…12752)

## Motivation

JIRA ID : AIFFT-517

The bitwise reproducibility tests store a fingerprint (hash) of each FFT
result and compare it on later runs. The hash had three problems:
- It used `std::hash`, which can give different results on different
compilers or runs, so stored fingerprints could stop matching even when
the output was identical.
- Each value was multiplied by a weight and rounded before hashing, so a
change in the last bit could be lost.
- Each element's position was computed as `row + column`, so a
transposed output produced the same hash.

## Technical Details

- `shared/fft_hash.h`: each element is hashed from its exact bits
together with its real position (batch, then row-major index), using a
fixed hash function. Results are still added together, so the thread
count doesn't affect the hash.
- `projects/hipfft/shared/fft_hash.h`: kept identical to the rocFFT
copy.
- `clients/tests/buffer_hash_test.cpp`: new tests check that a
transpose, a 1-bit change and a batch swap all change the hash, and that
padding doesn't. Fixed expected hashes cover half, single and double
precision.
- `testing.md`: updated to describe the new hash.

SHA-256 (`shared/sha256.h`) isn't used because its result depends on the
order of the data, so it would change with the number of threads.

Existing stored fingerprints must be regenerated.

## Test Plan

On MI300X (gfx942), ROCm 7.2.4:
- Ran the new tests against the old hash and the new one.
- Ran the bitwise repro test twice in separate processes with
`--repro-db`, for 5 FFT cases.

## Test Result

- The old hash fails the transpose, 1-bit and padding tests. The new
hash passes all hash tests.
- Repro-db: the first run stored all 5 fingerprints, and the second run
matched all 5.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/ROCm/blob/develop/CONTRIBUTING.md#pull-requests.
JIRA ID : AIRADSW-483

## Motivation

On gfx1201 the TN GEMM types with 1-byte inputs F8F8S, B8B8S and I8II
use GridBased logic files of about 780 solutions
each. A 19-solution Origami (Prediction) catalog is faster on average
and about 40x smaller in solutions.

## Technical Details

- Removes
`asm_full/gfx1201/GridBased/gfx1201_Cijk_Alik_Bljk_{F8F8S,B8B8S}_BH_Bias_SHB_HA_S_SAB_SCD_SAV_UserArgs.yaml`
and `..._I8II_BH_SHB_HA_S_SCD_SAV_UserArgs.yaml`, and adds Prediction
catalogs with the same names in
  `asm_full/gfx1201/Origami/`, 


F8F8S against stock GridBased:
| shape set | geomean speedup | 
|---|---|
| 400 random TN shapes, blind | 1.153 | 
| 200 real TN workload shapes, blind | 1.231 | 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…13387)

JIRA ID: AIRADSW-483

## Motivation

On gfx1100 (Radeon RX 7900 XTX, "navi31"), hipBLASLt chooses the GEMM
kernel for fp16 and bf16 NN matrix multiplies from GridBased libraries.
A GridBased library is a lookup table that maps tuned problem sizes to
kernels; sizes it was not tuned for fall back to the nearest entry. The
NN tables are thin (471 rows each, against about 9 700 for TN), so most
NN sizes take a fallback kernel.

This PR replaces the four NN tables with Origami Prediction libraries,
the approach navi32 adopted in #11305. A Prediction library holds a
short list of kernels, and the Origami analytical model ranks them for
each problem at run time.

On fresh problem sizes held out of tuning, the new libraries are faster
in geomean (the mean of per-shape speedups) for every type:
- fp16 NN: 34% faster off the stock table, 17% faster on the stock
table's own rows.
- bf16 NN: 41% faster.
- Aux (UseE) NN: 13–17% faster.
- On a blind 194-shape benchmark set that was never used for tuning,
fp16 NN wins 19 of 20 categories: geomean 1.334, +1.54M GFLOPS net.

The exceptions are listed under Known limits. The TN libraries are not
touched by this PR.

## Technical Details

Directory:
`projects/hipblaslt/library/src/amd_detail/rocblaslt/src/Tensile/Logic/asm_full/navi31/`.
Each new file in `Origami/` replaces the `GridBased/` file of the same
name. Both files describe the same problem type, so they cannot be in
one library together. There are no code changes; the last commit adds
one CHANGELOG line.

| file (`navi31_Cijk_…_UserArgs.yaml`) | types | before | after |
|---|---|---|---|
| `Ailk_Bljk_HHS_BH_Bias_HAS_SAV` | fp16 in/out, fp32 compute | 70
kernels + 471-row table | 42 kernels |
| `Ailk_Bljk_BBS_BH_Bias_HAS_SAV` | bf16 | 73 kernels + 471-row table |
42 (port of HHS-NN) |
| `Ailk_Bljk_HHS_BH_Bias_AuxH_HAS_SAV` | fp16, also writes E | 70
kernels + 471-row table | 39 (port, StreamK→0) |
| `Ailk_Bljk_BBS_BH_Bias_AuxB_HAS_SAV` | bf16, also writes E | 73
kernels + 471-row table | 39 (port, StreamK→0) |

**Ports.**
- The bf16 and Aux files are the fp16 catalog with the whole problem
type copied from the stock file of the target type.
- Aux means UseE: the GEMM also writes the pre-activation output E. With
UseE, StreamK=3 kernels fail code generation on this tree, so those
members were converted to StreamK=0. One converted member duplicated an
existing one and was removed (40 → 39).
- The Aux files are based on the 40-kernel HHS-NN catalog with the
member-13 rollback. They do not include the narrow-N update, whose new
StreamK=3 kernel cannot be used with UseE.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…950 (#13374)

## Motivation

On gfx942 and gfx950, each XCD has its own L2 cache, and the L2 caches
are not coherent with each other. Agent-scope acquire and release fences
therefore lower to a full L2 invalidate (`buffer_inv sc1`) and a full L2
write-back (`buffer_wbl2`). The level-scheduled triangular solves and
incomplete factorizations issue these fences for every row: the producer
before it sets the row's done flag, and the consumer after it has waited
for the flag. As a result, these routines spend most of their time on
cache maintenance on these architectures.

The analysis stage has a second issue. Every row updates the maximum row
length with its own device-scope atomic on a single address, and on
these architectures these atomics serialize across the XCDs.

## Technical details

**Solve and factorization kernels.** On gfx942, gfx950 and
gfx9-4-generic (`has_noncoherent_xcd_l2`), row results and done flags
are exchanged through device-scope relaxed atomic loads and stores,
which carry `sc1` and are coherent across the XCD L2s. They replace
plain accesses ordered by agent-scope fences.
- Producer: waits for its result stores to complete
(`__builtin_amdgcn_s_waitcnt(0)` behind a workgroup release fence)
before it stores the done flag. No `buffer_wbl2`.
- Consumer: after the spin loop on the flag, a workgroup acquire fence
keeps the result loads after the flag loads. No `buffer_inv`.

The new helpers are in `rocsparse_common.hpp`:
- `acquire_done_rows`
- `load_done_row_data`
- `store_done_row_data`
- `store_done_row_result` (falls back to `nontemporal_store` on other
architectures)
- `release_done_row_data`
- `set_done_flag`
- `wait_store_completion` (gfx9 only; used by `release_done_row_data`
and `set_done_flag` when `has_noncoherent_xcd_l2` is true)

On all other architectures the helpers compile to the previous code:
plain loads and stores, nontemporal stores, `__threadfence()`, and the
agent-scope acquire fence and release store.

**Small-block BSR factorizations.** In these kernels one wavefront
factorizes a block row, so only the blocks that other block rows read
need device-scope accesses. The blocks that only the same wavefront
reads back use plain loads and stores, because the memory model needs no
cache maintenance for coherence between the lanes of one wavefront.
- bsrilu0 (block sizes up to 8): dependent rows read only the diagonal
and upper blocks, so only those are stored with device scope. The block
row's own loads and its lower-block stores are plain.
- bsric0 (block sizes up to 16): dependent rows read every block a row
stores, so all stores stay device-scope. The re-reads of the row's own
blocks are plain loads. The store wait after each block is replaced with
a wavefront-scope fence, because `set_done_flag` waits for all stores
before it sets the flag.

Affected kernels:
- solves: csrsv (also used for CSC and COO), ellsv, csrsm, and bsrsv for
block sizes up to 32
- factorizations: csrilu0, csric0, and csrildlt0 (hash and binsearch
variants), bsrilu0 for block sizes up to 8, and bsric0 for block sizes
up to 16 with at most 128 blocks per block row

bsrsm, bsrsv for block sizes above 32, and the larger-block bsrilu0 and
bsric0 kernels keep develop's fence-based synchronization. These kernels
reread each dependency's result many times, and every device-scope read
bypasses the local L2, so the fence pair per dependency is cheaper
there.

**Analysis (gfx942, gfx950 and gfx9-4-generic only).** Each row updates
a per-workgroup maximum row length with a shared-memory atomic. The last
row of the workgroup to finish publishes it with a single device-scope
atomic. This applies to the analysis of the CSR, CSC, COO, and BSR
solves and factorizations, including the analysis stage of
`rocsparse_spilu0`, `rocsparse_spic0`, and `rocsparse_spildlt0`. Other
architectures keep one device-scope atomic per row.

JIRA ID : ROCM-32435

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…nd it (#12638)

## Motivation

A user hit an intermittent hang on MI350 spawning 128 worker `wthreads`
from a coordinator `wthread`, well below the reported scheduler capacity
of 256.

It wasn't the scheduler. The coordinator kept its worker handles in a
local container and overflowed its device stack. A `wthread` callable's
stack budget is `hipLimitStackSize`, 1024 bytes by default, and
`sizeof(hip::wthread)` is 16 bytes on device so it could hold about 63
handles regardless of `hardware_concurrency()`.

This wasn't documented anywhere, and the failure gives no usable
diagnostic, so reading it as a scheduler bug was reasonable. This PR
documents the budget so users can size callables deliberately.

Related: ROCM-31414
Closes #12473 

## Technical Details

Docs only.

`conceptual/hipThreads-execution-model.rst` — new "Stack space for
callables" section: why the budget applies (the scheduler dispatches
callables indirectly, so the runtime falls back to `hipLimitStackSize`
instead of a compiler-computed frame), what counts against it, the two
ways to design around it (device heap, or raise the limit), and how to
recognize an overflow.

`reference/limitations.rst` — one line on the existing private-stacks
bullet cross-referencing the above, so it stays discoverable.

## Test Result

- Scheduler kernel compiles with `.uses_dynamic_stack: true` /
`.private_segment_fixed_size: 72`; faulting dispatch shows
`private_seg_size=1024`.
- Defaults measured: `hipLimitStackSize 1024 B`, `hipLimitMallocHeapSize
8 MB`, `sizeof(hip::wthread) 16`.
- Ceiling lands exactly at 1024/16: 63 workers pass, 64 faults.
- Both documented workarounds run
- An ordinary HIP kernel with a 4 KB local gets a 4112-byte frame and
runs fine, confirming the limit doesn't bind for normal HIP code.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

---------

Co-authored-by: Daniel McIntosh <Daniel.Mcintosh@amd.com>
## Motivation
First PR of a stack adding a generic sparse matrix sort,
`rocsparse_spsort`. It sorts an input matrix A into an output matrix B,
or in place when the same descriptor is passed for both. COO, CSR and
CSC are supported, including uniform strided batched matrices (every
batch has the same sparsity pattern, and only the values differ).

This PR adds only the public API and its documentation, so the interface
can be reviewed on its own:
- `rocsparse_spsort_buffer_size` and `rocsparse_spsort` in
`internal/generic/rocsparse_spsort.h`.
- The `rocsparse_spsort_descr` descriptor with
`rocsparse_spsort_descr_create`, `rocsparse_spsort_descr_destroy` and
`rocsparse_spsort_set_input`.
- The `rocsparse_spsort_alg`, `rocsparse_spsort_input` and
`rocsparse_spsort_stage` enums.
- Entries in the reference docs and in the hipGraph support table.

The functions are declared but not implemented until the following PRs
in the stack. The doc example snippets are added together with the
examples.

## Test plan
- Compiled a C and a C++ translation unit including `rocsparse.h` and
using the new types.
- Built the library with this PR.

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553 ⬅ this PR
2. #12555
3. #12556
4. #12557
5. #12558
6. #12559
7. #12560

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
## Motivation
Implements the SpSort descriptor, `rocsparse_spsort_buffer_size` and
`rocsparse_spsort` for COO matrices:
- Sorts by row (`rocsparse_direction_row`) or by column
(`rocsparse_direction_column`).
- Out of place (A into B) or in place (the same descriptor for A and B).
- Uniform strided batched matrices, where A and B may have different
batch strides. Every batch has the same sparsity pattern, so the indices
are sorted once and the resulting permutation is applied to the values
of every batch.

Adds the internal `rocsparse::coosort_buffer_size` and
`rocsparse::coosort` in the existing `rocsparse_coosort.cpp`. They take
the algorithm, the direction, the source and target sparse matrix
descriptors and the buffer size, and gather the values of each batch
with the shared permutation. The index type dispatch happens right
before the kernel launch through the existing `gcoosort` routines, so
the coosort templates are unchanged.

The SpSort descriptor is defined in `rocsparse_spsort.hpp`, with
`rocsparse_spsort_descr.cpp` (descriptor and
`rocsparse_spsort_set_input`), `rocsparse_spsort_buffer_size.cpp` and
`rocsparse_spsort.cpp`.

The extra gather buffer for the values is only allocated when sorting in
place. CSR and CSC return `rocsparse_status_not_implemented` until the
next PRs.

## Test plan
- Built the library and ran `example_rocsparse_spsort_coo`.
- Ran the existing `coosort` tests (quick and pre_checkin).
- Ran the `spsort_coo` tests added later in the stack against this
library (quick and pre_checkin).

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555 ⬅ this PR
3. #12556
4. #12557
5. #12558
6. #12559
7. #12560

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
## Motivation
Adds CSR support to `rocsparse_spsort`. It sorts the column indices, and
the corresponding values, within each row. The direction must be
`rocsparse_direction_row`, and any other direction returns
`rocsparse_status_invalid_value`. Out of place, in place and strided
batched matrices are supported.

Batches must be uniform: every batch has the same sparsity pattern, so
the column indices are sorted once and the resulting permutation is
applied to the values of every batch.

Batch rules:
- The columns/values batch stride must be at least nnz.
- The offsets batch stride must be 0 (shared row pointer) or at least m
+ 1.
- B may only share its row pointer if A does.

Changes:
- Adds the internal `rocsparse::csrsort_buffer_size` and
`rocsparse::csrsort` in the existing `rocsparse_csrsort.cpp`, which take
the source and target sparse matrix descriptors like the COO ones. They
wrap `rocsparse::csxsort_buffer_size` and `rocsparse::csxsort`, which
CSC reuses in the next PR.
- Turns the existing csrsort implementation in the same file into file
local templates on the row pointer and column index types, dispatched on
the index types right before the call. The `rocsparse_csrsort` C API now
calls those templates.
- Adds the mixed 32-bit key and 64-bit value segmented radix sort
instantiations needed for 64-bit row pointers with 32-bit column
indices.

## Test plan
- Built the library and ran `example_rocsparse_spsort`.
- Ran the existing `csrsort` and `cscsort` tests (quick and
pre_checkin).
- Ran the `spsort_csr` tests added later in the stack against this
library (quick and pre_checkin).

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556 ⬅ this PR
4. #12557
5. #12558
6. #12559
7. #12560

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
## Motivation
Adds CSC support to `rocsparse_spsort`. It sorts the row indices, and
the corresponding values, within each column. Out of place, in place and
strided batched matrices are supported, with the same batch rules as
CSR, using n + 1 for the column pointer.

Adds the internal `rocsparse::cscsort_buffer_size` and
`rocsparse::cscsort` in the existing `rocsparse_cscsort.cpp`. They take
the source and target sparse matrix descriptors and reuse
`rocsparse::csxsort_buffer_size` and `rocsparse::csxsort` from the CSR
PR, which sort a CSC matrix as the CSR layout of its transpose.

## Test plan
- Built the library.
- Ran the existing `csrsort` and `cscsort` tests.
- Ran the `spsort_csc` tests added later in the stack against this
library (quick and pre_checkin).

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556
4. #12557 ⬅ this PR
5. #12558
6. #12559
7. #12560

---------

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

## Motivation
Adds tests and a benchmark for `rocsparse_spsort` with COO matrices:
- Checks out of place results (A unchanged) and in place results against
a host reference, for both directions.
- Covers strided batched matrices, with A padded between batches to
check that its stride is honoured.
- Checks the SpSort argument handling: descriptor inputs, stage
ordering, matching A and B, and batch counts and strides.
- Adds the `spsort_coo` benchmark.

The test times are about 0.6 s for quick, 6 s for pre_checkin and 67 s
for nightly.

## Test plan
- `rocsparse-test --gtest_filter='*spsort*'`: 382 tests passed.
- `rocsparse-bench -f spsort_coo`

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556
4. #12557
5. #12558 ⬅ this PR
6. #12559
7. #12560

---------

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

## Motivation
Adds tests and a benchmark for `rocsparse_spsort` with CSR matrices:
- Checks out of place and in place results against a host reference that
sorts each row.
- Covers all supported row pointer and column index type combinations
(i32/i32, i64/i32, i64/i64).
- Covers strided batched matrices, with A's offsets and columns/values
padded between batches.
- Checks the CSR specific argument handling: the direction and the batch
stride rules.
- Adds the `spsort_csr` benchmark.

The test times are about 0.5 s for quick, 5 s for pre_checkin and 35 s
for nightly.

## Test plan
- `rocsparse-test --gtest_filter='*spsort*:-nightly*spsort_coo*'`: 486
tests passed, including 179 nightly `spsort_csr` tests.
- `rocsparse-bench -f spsort_csr`

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556
4. #12557
5. #12558
6. #12559 ⬅ this PR
7. #12560

---------

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

## Motivation
Adds tests and a benchmark for `rocsparse_spsort` with CSC matrices:
- Checks out of place and in place results against a host reference that
sorts each column.
- Covers all supported column pointer and row index type combinations.
- Covers strided batched matrices.
- Checks the CSC specific argument handling: the direction and the batch
stride rules.
- Adds the `spsort_csc` benchmark.

The test times are about 0.5 s for quick, 5 s for pre_checkin and 33 s
for nightly.

## Test plan
- `rocsparse-test
--gtest_filter='quick*spsort*:pre_checkin*spsort*:nightly*spsort_csc*'`:
651 tests passed.
- `rocsparse-bench -f spsort_csc`

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556
4. #12557
5. #12558
6. #12559
7. #12560 ⬅ this PR

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…ed odist (#13017)

## Motivation

`rocfft_plan_create` fails ("Can't find valid buffer assignment with
current buffers.") for 1D complex transforms of lengths involving large
prime factors that use the multi-kernel fused Bluestein algorithm, when
the output batch distance is larger than the transform length (e.g.
length 8209, batch 2, odist 20000).

JIRA ID: AIFFT-551 (issue found while working on that)

## Technical Details

Buffer assignment described the output of fused Bluestein nodes as a
single transform length, while pairing it with *all* of the node's
output strides. `compute_ptrdiff` then mispaired lengths and strides
(and could underflow), so `CheckAssignmentValid` rejected every valid
candidate assignment.

- Add `OutputFootprint(node)`: the output lengths of a node with their
matching output strides. The final fused Bluestein stage
(`BFT_INV_CHIRP_MUL` block-RC) only stores the first `transform_length`
elements of its padded dims 0-1, so its footprint is that length plus
the remaining dims.
- `CheckAssignmentValid`: size the output with `OutputFootprint`, so
lengths and strides are paired correctly.
- `dataFits`: use `OutputFootprint` when matching against the user
buffer's length/stride. The dimension decomposition check still uses the
output lengths as before. For transposes/SBCC writing to a
non-unit-stride user buffer, the length/stride matching now uses
correctly paired dimensions.
- `MatchingLengthStride`: return `false` instead of reading out of
bounds on inconsistent inputs.

Stack note: with this fix, plans for inner-batched layouts (idist =
odist = 1) using fused Bluestein are created but can fault on the GPU.
That is fixed by the next PR in the stack
(`users/regan-amd/fix_fused_bluestein_unit_distance`).

## Test Plan

- New adhoc accuracy token:
`complex_forward_len_8209_single_op_batch_2_istride_1_CI_ostride_1_CI_idist_8209_odist_20000_ioffset_0_0_ooffset_0_0`
- Regular test suite.

## Test Result

- New token fails before this change (plan creation failure) and passes
after it.
- Regular test suite passes on gfx1201.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.
…ce (#13020)

## Motivation

JIRA ID: AIFFT-13, AIFFT-551 (issue found while working on that)

1D complex transforms of lengths involving large prime factors that use
the multi-kernel fused Bluestein algorithm fail for inner-batched
layouts (unit batch distance, i.e. idist = odist = 1 and istride =
ostride = batch). On develop, such plans either fail in
`rocfft_plan_create` or fault on the GPU at execution. With the previous
PR in this stack, plan creation succeeds, but execution still faults.

## Technical Details

The L1D_CC "dist 1" special case (`CC1DNode::AssignParams_internal`)
reshapes its children's kernel launch geometry, swapping the batch and
sub-FFT dimensions so that the batch dimension is the adjacent one.
Fused Bluestein stages have already set their Bluestein strides and
dists against the original geometry, so after the reshape their kernels
index past the Bluestein buffer (GPU memory access fault in the
`sbcc_*_chirp_mul` kernels).

The reshape is now skipped for fused Bluestein nodes
(`BT_MULTI_KERNEL_FUSED`), keeping their launch geometry consistent with
their Bluestein strides. This keeps the fused path (no fallback to
non-fused Bluestein).

## Test Plan

- New `fused_bluestein_non_unit_stride` accuracy suite (9 tokens):
- inner-batched (idist = odist = 1, istride = ostride = batch) fused
Bluestein layouts: forward/inverse, single/double,
in-place/out-of-place, including the AIFFT-13 reproducers (lengths 4182,
4167, 4128 with batches 2456, 1039, 8092);
  - general non-unit stride and non-unit dist fused Bluestein layouts.
- Regression: `prime_*`, `adhoc*`, `fused_bluestein*` and all
`*_idist_1_*` suites in full, plus a 5% sample of all other
single-device suites.

## Test Result

- Before this stack: 6 of the new tokens fail in `rocfft_plan_create`;
`complex_inverse_len_8191_single_op_batch_8_..._idist_1_odist_1` faults
on the GPU (`sbcc_inv_chirp_mul`).
- After this change: all 9 new tests pass.
- Regression (gfx1201): full suite pass, no failures.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.
Updated all pinned TheRock workflow and source refs to `015ba17` due to
submodule bump

Baseline run ID:
[37869200457](https://github.com/ROCm/TheRock/actions/runs/37869200457)

Co-authored-by: therockbot <therockbot@amd.com>
JIRA ID : ROCM-32153

Targets `develop`. It removes the quarantine that #13005 (AIHPBLAS-5015,
now merged) added for these cases.

## Motivation

On gfx950, two f16 GEMM solutions return success with wrong values for
integer inputs at 4,099 x 2,053 x 129: a TN custom-schedule MT256x160x64
direct-to-LDS kernel, and an NN MT32x192x64 kernel. Neither is a
tail-loop or edge problem. The TN kernel fails whenever its custom main
loop runs, and the NN kernel fails at every K from 64 to 256.

The TN kernel matters most, because the heuristic picks it by default
for f16 TN 1,536 x 6,144 x 3,072 on MI355X, and it gave wrong answers in
6 of 30 runs there. I didn't find a small, clearly correct codegen fix
for either kernel, so this PR removes them from the gfx950 logic. The
custom schedule itself is tracked in ROCM-32343, which also covers its
bf16 copies.

## Technical Details

- Three solution entries come out of the gfx950 logic files (under
`library/src/amd_detail/rocblaslt/src/Tensile/Logic/asm_full/gfx950/`):
- the TN kernel in `gfx950/Equality/..._Alik_Bljk_HHS...yaml` (#135) and
its copy in `gfx950_id75a3/Equality/` (#151);
  - the NN kernel in `gfx950/Equality/..._Ailk_Bljk_HHS...yaml` (#334).
- Each removal renumbers the solutions after it and drops the one
exact-size entry the solution was tuned for, so that size falls back to
the remaining selection. Every other exact-size entry still points at
the same solution, and the build's logic validation passes. No tests or
tuning files reference these kernels.
- `known_bugs.yaml`: the two f16 `M: 4099` entries come out.

## Why remove these kernels instead of keeping them

I know these kernels are fast. I looked hard at keeping them, and I
don't think either one can stay as it is.

**TN (custom-schedule MT256x160x64).** It fails whenever its custom main
loop runs (K > 128), not just at odd sizes. That includes the one size
it was tuned for: at f16 TN 1,536 x 6,144 x 3,072 the heuristic picks it
by default on MI355X, and it was wrong in 6 of 30 runs. The kernel the
heuristic picks instead (MT192x192x64) takes 58.7 µs against 57.1 µs, so
about 2.8% slower, and it was right every time. On MI350X the difference
is smaller. There, 68.6 µs against 68.1 µs is about 1% slower (the old
kernel happened to be right in 10 of 10 runs there, but it fails the
4,099 x 2,053 x 129 cases on MI350X too). I think a few percent on one
shape is a fair price for not returning wrong answers.

**NN (MT32x192x64, StreamK 3).** I hoped to keep this one and just
restrict it to the size it was tuned for (24 x 168 x 96, batch 6,144).
The data doesn't support that. At exactly that size it was wrong in 30
of 30 runs on MI355X, with integer inputs (max error 3,344 to 3,480 ULP,
where a correct kernel gives 0). It's wrong on MI350X there too. It's
correct for up to 256 batches and wrong from 304 batches up, so my guess
is it breaks once there are more tiles than CUs. On MI350X it was the
heuristic's default at that size, so users were getting wrong answers
from it. The replacement default (MT32x192x32) takes 84.6 µs against
74.8 µs, so it's about 13% slower, but it's correct. On MI355X the
default at that size is a different kernel, and it doesn't change.

One thing that made the NN problem easy to miss: `hipblaslt-bench -v`
doesn't flag it. The bench computes the allclose result and then
discards it, and the atol and rtol columns it prints are tolerances, not
a verdict (ROCM-32137). Here the norm error was about 108, but the bench
still exited 0. `--ulp` shows the problem right away.

ROCM-32343 tracks fixing the custom schedule, so the TN kernels can come
back once it's right. If someone fixes the NN kernel's Stream-K path, it
can come back the same way.

## Test Plan

On gfx950 (MI355X and MI350X): the f16 every-solution cases at 4,099 x
2,053 x 129 with the same test binary against this branch's library and
the stack tip's, repeated; the ticket's `hipblaslt-bench -v` repros; and
the heuristic default and timing at the sizes the removed solutions were
tuned for.

## Test Result

ROCm 10.2.0 (TheRock 10.2.0a20260928):

- MI355X: with the stack tip's library, 4 of 16 f16 every-solution tests
fail (NN and TN at 4,099); with this branch, 16 of 16 pass, and 96 of 96
over 6 repeats.
- MI350X: the same, 4 of 16 failing before and 16 of 16 passing after.
- The ticket's bench repros fail on the stack tip with CPU-reference
errors of 0.073 and 0.045 (TN, both copies) and 0.019 (NN).
- The heuristic default at 4,099 x 2,053 x 129 is unchanged and correct
(MT224x160x32 for TN, MT256x144x32 for NN).
- f16 TN 1,536 x 6,144 x 3,072 on MI355X now uses MT192x192x64: 58.7 µs
with 0 of 10 runs wrong, against 57.1 µs for the removed kernel, which
was sometimes wrong. That's about 2.8% slower.
- Math CI precheckin passed against develop.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level

Low (2/5): logic-file data only, no code. Three wrong solutions stop
being offered on gfx950; one tuned size gets a correct kernel that is
about 3% slower on MI355X.

Co-authored-by: Cursor <cursoragent@cursor.com>
## Motivation
Adds the `BUILD_WITH_SPSORT` build option, which gates
`rocsparse_spsort` so that it can be enabled or disabled at build time,
in the same way as `BUILD_WITH_DIAGONAL_SOLVE`. The option is `ON` by
default.

When the option is `OFF`, `ROCSPARSE_WITH_SPSORT` is not defined in
`rocsparse-config.h` and the following are left out:
- The `rocsparse_spsort` and `rocsparse_spsort_buffer_size`
declarations, the `rocsparse_spsort_descr_create`,
`rocsparse_spsort_descr_destroy` and `rocsparse_spsort_set_input`
declarations, and the `rocsparse_spsort_descr`, `rocsparse_spsort_alg`,
`rocsparse_spsort_input` and `rocsparse_spsort_stage` types.
- The implementation of those routines. Their sources are still listed
in the library, but compile to nothing.
- The `spsort_coo`, `spsort_csr` and `spsort_csc` tests and benchmarks.
- The `rocsparse_spsort` documentation examples, which build as a stub
that prints that rocSPARSE was built without `rocsparse_spsort`.

`ROCSPARSE_WITH_SPSORT` is also added to the Doxygen `PREDEFINED` list
so the documentation is still generated.

The legacy `coosort`, `csrsort` and `cscsort` routines, and the fixes
made to them earlier in the stack, are not gated.

This PR only adds the guards. It does not change the code of the PRs
below it, so their CI results are unaffected.

## Test plan
- Build with `-DBUILD_WITH_SPSORT=OFF`: the library, `rocsparse-test`,
`rocsparse-bench` and the samples build, `librocsparse.so` exports no
`rocsparse_spsort*` symbols, and the spsort examples print the stub
message. `rocsparse-test
--gtest_filter='*coosort*:*csrsort*:*cscsort*'`: 556 tests passed.
- Build with `-DBUILD_WITH_SPSORT=ON`: `rocsparse-test
--gtest_filter='*spsort*'`: 1482 tests passed.

JIRA ID: AISPARSE-717

## Stack
This PR is part of a stack. Review and merge in order:

1. #12553
2. #12555
3. #12556
4. #12557
5. #12558
6. #12559
7. #12560
8. #13392 ⬅ this PR

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
JIRA ID : AIHPBLAS-4195

## Motivation
HostNumerics is a new CPU-only component under `shared/hostnumerics/`
that will hold shared data-type definitions, test-data generation, and
CPU reference math for hipBLASLt, TensileLite, and rocRoller. Its
standalone automated GitHub checks currently build and test it with GCC
only. Those checks do not catch invalid memory access, undefined
behavior such as signed integer overflow, or bugs on code paths the
tests never run.

## Technical Details
- Renames the two existing jobs to start with "GCC, " and sets `CXX=g++`
so the compiler is explicit.
- Adds "Clang, ASan and UBSan", a Debug build with Clang 18 using
AddressSanitizer (detects out-of-bounds and use-after-free memory
access) and UndefinedBehaviorSanitizer (detects undefined behavior such
as signed overflow). `-fno-sanitize-recover=all` makes any finding fail
the test.
- Adds "Clang-tidy", which runs clang-tidy 18, a static analyzer that
inspects code without running it, over the C++ sources and HostNumerics
headers. Any warning fails the job.
- Adds `shared/hostnumerics/.clang-tidy`, enabling the Clang static
analyzer checks plus 13 `bugprone-*` checks, including checks for
narrowing, integer division, misplaced widening, signed-char misuse, and
loop-variable width.
- The clang-tidy job builds with Python on and analyzes
`python/src/bindings.cpp` as well as the C++ tests and HostNumerics
headers. Its header filter excludes nanobind headers. The sanitizer job
keeps Python off; sanitizing the Python bindings remains follow-up work.
- Static analysis stays in the standalone workflow because the shared
clang-tidy workflow currently installs TheRock and ROCm on a ROCm
worker; HostNumerics needs only a sparse component checkout and an
Ubuntu CPU runner.
- No product code or build files change.

## Test Plan
Each job was shown to fail on a deliberate defect, using four throwaway
PRs based on this one:
1. #13498 binds Python's `storage_bits` to the wrong field.
2. #13499 turns tests on by default for embedded consumers.
3. #13500 adds a heap buffer overflow and a signed integer overflow in
separate tests.
4. #13501 adds a null pointer dereference on a path the runtime tests
never execute.

## Test Result
All four HostNumerics jobs passed on the original PR head
([run](https://github.com/ROCm/rocm-libraries/actions/runs/38009876405)).
The review updates were validated locally by replaying all four jobs
with GCC 13.3 and Clang 18.1.3. A temporary null dereference in
`bindings.cpp` and deliberate defects for each added numeric check
failed analysis as expected. All four HostNumerics jobs also pass on the
updated head
([run](https://github.com/ROCm/rocm-libraries/actions/runs/38301400794));
its clang-tidy log confirms that `python/src/bindings.cpp` is analyzed.
In each demonstration PR, only the targeted job failed and the other
three passed:
- #13498: [GCC, Python ON, tests
ON](https://github.com/ROCm/rocm-libraries/actions/runs/38268525203/job/114876732319)
failed its metadata test.
- #13499: [GCC, Python OFF, tests
OFF](https://github.com/ROCm/rocm-libraries/actions/runs/38268533527/job/114876759448)
failed its embedded-defaults check.
- #13500: [Clang, ASan and
UBSan](https://github.com/ROCm/rocm-libraries/actions/runs/38268545089/job/114876797348)
reported `heap-buffer-overflow` and `signed integer overflow`.
- #13501:
[Clang-tidy](https://github.com/ROCm/rocm-libraries/actions/runs/38268554101/job/114876843557)
reported `clang-analyzer-core.NullDereference`.

TheRock CI was skipped with `ci:skip` because only the HostNumerics
workflow changes.

## Submission Checklist
- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level
Low (2/5): changes only the HostNumerics CI workflow and adds a
clang-tidy config used by that workflow. No product code changes, and
these jobs are not required checks, so a false failure would not block
other PRs.
JIRA ID : AIHPBLAS-4195

## Motivation
HostNumerics is a new CPU-only component under `shared/hostnumerics/`.
It will hold the data-type definitions, test-data generation, and CPU
reference math that hipBLASLt, TensileLite, and rocRoller each implement
separately today. #13405 added `ScalarType`, an enum naming every number
format the component supports. Templated C++ code also needs to find the
`ScalarType` for a C++ type such as `float` or `std::complex<double>`,
and there is currently no way to do that.

## Technical Details
- Adds `nativeScalarType<T>`, a compile-time constant giving the
`ScalarType` for a C++ type. For example, `nativeScalarType<const
float&>` is `ScalarType::Float32`; `const`, `volatile`, and references
are ignored.
- Adds the `NativeScalar<T>` concept, a compile-time yes/no check, so
callers can test for a mapping without causing a compile error.
- Maps `bool`, the fixed-width integers `int8_t` through `uint64_t`,
`float`, `double`, `std::complex<float>`, and `std::complex<double>`.
Any other type, including `long double`, pointers, arrays, and strings,
is a compile error.
- The lookup table is in `detail::NativeScalarType` and is not meant to
be extended by users.
- Header-only change to `scalar_type.hpp`, which now includes
`<complex>` and `<type_traits>`. No build files change, and no code
outside `shared/hostnumerics/` uses the component yet.

## Test Plan
`shared/hostnumerics/tests/scalar_type_test.cpp` adds 24 `static_assert`
checks: each of the 13 mappings, qualifier handling on 3 types, 2
positive `NativeScalar` checks, and rejection of 6 unsupported types.
Because these are compile-time checks, a wrong mapping fails the build
of `hostnumerics-scalar-type-test`.

## Test Result
The HostNumerics standalone CI job "Python ON, tests ON, Ninja
Multi-Config" on Ubuntu 24.04 built and ran the test and passed. TheRock
CI was skipped with the `ci:skip` label because no other ROCm component
builds HostNumerics yet. No Windows build has been run.

## Submission Checklist
- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level
Low (2/5): adds new header-only declarations and changes no existing
behavior. Nothing outside `shared/hostnumerics/` includes this header
yet, so no product, GPU, or binary-interface behavior can change.
…13389)

## Summary
- Register `rocsparse-unit-test` and `rocsparse-unit-test-device` on the
`standard`, `comprehensive`, and `full` ctest labels, including the
installed `bin/rocsparse/CTestTestfile.cmake` that TheRock runs. `quick`
does not include them.
- Each binary is registered twice. TheRock on gfx1151 requires the
`ex_gpu_gfx1151` label, and on every other GPU it excludes `ex_gpu`, so
one entry cannot satisfy both.
- Include the gfx8/gfx9 wfreduce lane check from #13388. Without it,
`rocsparse-unit-test-device` fails 14 cases on gfx94X and gfx950, which
are TheRock Linux test targets. This supersedes #13388.

## Test plan
- [ ] TheRock `standard` on gfx94X and gfx950 runs both unit-test
binaries, and the wfreduce cases pass
- [ ] gfx1151 selects the `ex_gpu_gfx1151` entries rather than the plain
ones
- [ ] `ctest -L quick` does not run either unit-test binary

---------

Co-authored-by: Kim Liegeois <kliegeois@users.noreply.github.com>
Co-authored-by: James Sandham <33790278+jsandham@users.noreply.github.com>
)

Resolves: #13517

## Motivation
On ROCm 7.0.2, `hipblaslt-test
--gtest_filter='*fast_check_placement*:*fast_check_mx_fp8_placement*-*known_bug*'`
fails 4 of 30 tests with `unknown error` at the first
`hipGetLastError()` check in
`testing_matmul_with_bias` (`CHECK_DEVICE_ALLOCATION` after allocating
A). The failing
tests vary with allocation order (e.g. `PLACE_c`, nightly `PLACE_b`,
mx-fp8 `PLACE_scale_b`),
pass when run alone, and the error is not caused by the failing test: it
is left behind by
the previous test's `PlacedRegion` teardown, where `hipMemAddressFree`
fails with
`hsa_amd_vmem_address_free ... status:4097`
(`HSA_STATUS_ERROR_INVALID_ARGUMENT`).

## Technical Details
Root cause is a reference leak in the HIP runtime before ROCm 7.1:

1. `testing_matmul_with_bias` destroys its stream without synchronizing
it. The last
command on that stream is usually fast_check's poison refill of the
placed buffer
   (`PlacedRegion::fill_poison`).
2. `HostQueue::terminate()` takes a reference to the stream's last
command, and when a
CPU submission batch is still pending it queues a marker and overwrites
that pointer
   without releasing it. Fixed upstream in ROCm/rocm-systems@33085dd232
(SWDEV-533220, "Release marker when HostQueue is destroyed", ROCm
7.1.0+).
3. The leaked kernel command keeps the placed buffer window's
`amd::Memory` alive, so
`hipMemUnmap` does not destroy it, and the window keeps the VA
reservation's object
   alive after `hipMemAddressFree`.
4. The next `hipMemAddressReserve` that returns the same base cannot
replace HIP's stale
`VirtualMemObjMap_` entry (`insert` does not overwrite).
`hipMemAddressFree` ignores
its size argument and frees using the stale object's size, which ROCr
rejects when the
sizes differ. The error stays as the thread's last error and is reported
by the next
   test.

This was confirmed with gdb reference-count tracing in the test process,
and with a
standalone HIP reproducer: map a window, launch a kernel on it on a
stream, destroy the
stream without syncing, unmap/free, then reserve a different size at the
same base.

The fix synchronizes the stream before `hipStreamDestroy`, so no batch
is pending when
the stream is destroyed and nothing leaks. This is harmless on runtimes
that already
have the upstream fix.

## Test Plan
- Build `hipblaslt-test` against ROCm 7.0.2 (gfx950) and run

`--gtest_filter='*fast_check_placement*:*fast_check_mx_fp8_placement*-*known_bug*'`
  with `AMD_LOG_LEVEL=1`, with and without this change.
- Run the same filter on ROCm 7.2.1 (gfx950) to check for regressions.

## Test Result
- ROCm 7.0.2, gfx950, without the change: 26/30 passed, 4 failed, 4
failed
  `hsa_amd_vmem_address_free` calls.
- ROCm 7.0.2, gfx950, with the change: 30/30 passed in two consecutive
runs, no failed
  `hsa_amd_vmem_address_free` calls.
- ROCm 7.2.1, gfx950, with the change: the 26 non-MX placement tests
pass and no frees fail.
The 4 `pre_checkin_matmul_fast_check_mx_fp8_placement` tests fail with
incorrect D values,
identically with and without this change; that is a separate issue,
unrelated to this fix.

## Submission Checklist
- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level
Low. Test-client-only change: one `hipStreamSynchronize` before
destroying a stream that
has already been used synchronously, at the end of the test function.

Co-authored-by: Cursor <cursoragent@cursor.com>
JIRA ID: AICK-2310

# Add scheduled TDM FMHA for FP16 and BF16

## Summary

- Add the `qr_tdm_sched` forward FMHA pipeline for FP16/BF16 on gfx125:
Q/K–V dimensions 64/64, 128/128, and 192/128. The implementation uses
geometry-derived QK/PV scheduling, packed LDS, and scheduled
WMMA/LDS-read placement.
- Generate and try `qr_tdm_sched` first for its supported row-major V
cases with `max_seqlen_q >= 128`, `sink_ptr == nullptr`, and no bias,
dropout, soft cap, or quantization. Dense and supported causal masks are
included; LSE is optional.
- Select N64 for D64 and N128 for D128. D192 selects N128 when the
average K length is at least 512, K-length pointers are absent, the N128
workgroup count fits the CU count, and the mask is dense or unrestricted
square causal; other supported D192 cases select N64.
- Fall back to the existing `qr_tdm` candidate when a scheduled trait is
ineligible, including short Q or a non-null virtual-sink pointer. Shapes
or features outside `qr_tdm` continue through the existing `qr`
fallback.
- Separate the no-sink and finite-sink kernel specializations, and add
codegen, dispatch, layout, schedule, sink, and numerical regression
tests under `test/ck_tile/fmha`.

## BF16 performance

All performance numbers in this PR description are taken from the
unmodified `tile_example_fmha_fwd` output. The two binaries use the same
example main, inputs, GPU timer, and command-line options; one links
`qr_tdm_sched` from this PR and the other links upstream `qr_tdm`.
Kernel names were printed and checked on every run.

Measured on gfx1250 **B0 stepping** (256 CUs). SCLK was neither locked
nor logged by the benchmark, so no fixed SCLK value is claimed. The
historical 110 workloads per dimension use BSHD layout, `Sq = Sk` in
{256, 2048, 8192, 16384, 32768}, dense or causal masking, LSE, no sink,
and the same scale and input seed for both kernels. The example uses
GPU-event timing, five warmups, each workload’s historical repeat count,
and two adjacent alternating rounds. For each kernel, the table reports
the median of the two values printed by the example. Speedup divides the
two printed TFLOP/s medians; above 1× favors `qr_tdm_sched`. The example
prints latency to 0.001 ms and throughput to 0.01 TFLOP/s.

| Q/K–V dimension | Workloads faster with qr_tdm_sched (Introduced by
this PR) | Median speedup vs qr_tdm | Speedup range |
| --- | ---: | ---: | ---: |
| 64/64 | 110/110 | 1.317× | 1.054–1.819× |
| 128/128 | 110/110 | 1.319× | 1.145–1.882× |
| 192/128 | 110/110 | 1.179× | 1.035–1.882× |

The full per-workload results follow. Each kernel cell shows **execution
time in µs / effective TFLOP/s**. `B/H/Hkv` denotes batch size, query
heads, and key/value heads. Causal TFLOP/s uses triangular attention
work.

<details><summary>BF16 Q/K=64, V=64: all 110 workloads</summary>

| B/H/Hkv | Sq = Sk | Mask | qr_tdm_sched (Introduced by this PR)<br>µs
/ TFLOP/s | qr_tdm<br>µs / TFLOP/s | qr_tdm_sched / qr_tdm |
| --- | ---: | --- | ---: | ---: | ---: |
| 1/8/8 | 256 | dense | 6.5 / 20.60 | 8 / 16.43 | 1.254× |
| 1/8/8 | 256 | causal | 9 / 7.87 | 9.5 / 7.07 | 1.112× |
| 1/8/8 | 2048 | dense | 20 / 428.70 | 32 / 268.56 | 1.596× |
| 1/8/8 | 2048 | causal | 22 / 198.80 | 29 / 148.99 | 1.334× |
| 1/8/8 | 8192 | dense | 105 / 1306.69 | 143 / 961.35 | 1.359× |
| 1/8/8 | 8192 | causal | 76.5 / 899.52 | 104 / 658.12 | 1.367× |
| 1/8/8 | 16384 | dense | 352.5 / 1558.99 | 516 / 1065.49 | 1.463× |
| 1/8/8 | 16384 | causal | 205.5 / 1336.91 | 231 / 1191.24 | 1.122× |
| 1/8/8 | 32768 | dense | 1316 / 1670.73 | 1783.5 / 1233.18 | 1.355× |
| 1/8/8 | 32768 | causal | 683.5 / 1608.76 | 831 / 1323.27 | 1.216× |
| 1/64/8 | 256 | dense | 6 / 167.20 | 8 / 131.09 | 1.275× |
| 1/64/8 | 256 | causal | 9 / 61.97 | 10 / 55.78 | 1.111× |
| 1/64/8 | 2048 | dense | 54 / 1272.37 | 77 / 896.91 | 1.419× |
| 1/64/8 | 2048 | causal | 35 / 972.00 | 44 / 784.50 | 1.239× |
| 1/64/8 | 8192 | dense | 670.5 / 1640.03 | 889.5 / 1235.90 | 1.327× |
| 1/64/8 | 8192 | causal | 366.5 / 1499.62 | 443 / 1241.95 | 1.207× |
| 1/64/8 | 16384 | dense | 2584.5 / 1701.43 | 3442.5 / 1277.50 | 1.332×
|
| 1/64/8 | 16384 | causal | 1362 / 1614.49 | 1637.5 / 1343.09 | 1.202× |
| 1/64/8 | 32768 | dense | 10242.5 / 1717.59 | 13515.5 / 1301.62 |
1.320× |
| 1/64/8 | 32768 | causal | 5295.5 / 1661.06 | 6376.5 / 1379.45 | 1.204×
|
| 1/64/64 | 256 | dense | 6 / 166.81 | 8 / 129.20 | 1.291× |
| 1/64/64 | 256 | causal | 9 / 61.73 | 10 / 55.41 | 1.114× |
| 1/64/64 | 2048 | dense | 54.5 / 1260.94 | 77 / 895.85 | 1.408× |
| 1/64/64 | 2048 | causal | 36 / 961.11 | 44.5 / 776.22 | 1.238× |
| 1/64/64 | 8192 | dense | 700.5 / 1569.05 | 906 / 1214.18 | 1.292× |
| 1/64/64 | 8192 | causal | 375.5 / 1464.12 | 450 / 1222.10 | 1.198× |
| 1/64/64 | 16384 | dense | 2859.5 / 1538.04 | 3619.5 / 1214.97 | 1.266×
|
| 1/64/64 | 16384 | causal | 1461 / 1505.70 | 1733 / 1269.13 | 1.186× |
| 1/64/64 | 32768 | dense | 11411.5 / 1541.58 | 14538 / 1210.10 | 1.274×
|
| 1/64/64 | 32768 | causal | 5834.5 / 1507.61 | 6931.5 / 1269.02 |
1.188× |
| 1/12/12 | 256 | dense | 8 / 26.42 | 9 / 21.56 | 1.225× |
| 1/12/12 | 256 | causal | 10 / 10.31 | 11 / 9.39 | 1.098× |
| 1/12/12 | 2048 | dense | 23 / 565.14 | 35 / 368.93 | 1.532× |
| 1/12/12 | 2048 | causal | 23 / 279.37 | 30.5 / 211.40 | 1.321× |
| 1/12/12 | 8192 | dense | 147.5 / 1399.60 | 181.5 / 1137.03 | 1.231× |
| 1/12/12 | 8192 | causal | 92 / 1122.19 | 115 / 898.60 | 1.249× |
| 1/12/12 | 16384 | dense | 516.5 / 1596.95 | 670.5 / 1229.97 | 1.298× |
| 1/12/12 | 16384 | causal | 280 / 1471.83 | 349 / 1181.07 | 1.246× |
| 1/12/12 | 32768 | dense | 1977 / 1668.20 | 2623.5 / 1257.21 | 1.327× |
| 1/12/12 | 32768 | causal | 1021 / 1614.88 | 1219 / 1353.24 | 1.193× |
| 1/32/32 | 256 | dense | 6 / 85.71 | 8 / 67.95 | 1.261× |
| 1/32/32 | 256 | causal | 8 / 32.12 | 9.5 / 28.52 | 1.126× |
| 1/32/32 | 2048 | dense | 30 / 1143.37 | 41 / 831.19 | 1.376× |
| 1/32/32 | 2048 | causal | 25 / 696.09 | 32 / 541.97 | 1.284× |
| 1/32/32 | 8192 | dense | 349.5 / 1574.21 | 460 / 1194.55 | 1.318× |
| 1/32/32 | 8192 | causal | 192 / 1430.57 | 235 / 1171.82 | 1.221× |
| 1/32/32 | 16384 | dense | 1334 / 1648.75 | 1763 / 1247.51 | 1.322× |
| 1/32/32 | 16384 | causal | 696 / 1580.20 | 832.5 / 1321.26 | 1.196× |
| 1/32/32 | 32768 | dense | 5428.5 / 1620.33 | 7043.5 / 1248.83 | 1.297×
|
| 1/32/32 | 32768 | causal | 2760.5 / 1593.35 | 3289.5 / 1336.97 |
1.192× |
| 32/8/8 | 256 | dense | 13 / 341.98 | 15 / 295.50 | 1.157× |
| 32/8/8 | 256 | causal | 15 / 146.99 | 15 / 139.49 | 1.054× |
| 32/8/8 | 2048 | dense | 213 / 1290.18 | 268.5 / 1022.58 | 1.262× |
| 32/8/8 | 2048 | causal | 148.5 / 927.29 | 175.5 / 782.73 | 1.185× |
| 32/8/8 | 8192 | dense | 2637 / 1668.03 | 3477 / 1264.95 | 1.319× |
| 32/8/8 | 8192 | causal | 1474 / 1492.24 | 1785.5 / 1231.93 | 1.211× |
| 32/8/8 | 16384 | dense | 10304 / 1707.32 | 13556 / 1297.74 | 1.316× |
| 32/8/8 | 16384 | causal | 5525 / 1592.06 | 6652.5 / 1322.32 | 1.204× |
| 32/8/8 | 32768 | dense | 40970 / 1717.57 | 53682 / 1310.85 | 1.310× |
| 32/8/8 | 32768 | causal | 21349 / 1648.11 | 25695 / 1369.36 | 1.204× |
| 1/4/4 | 256 | dense | 6 / 11.62 | 8 / 8.68 | 1.339× |
| 1/4/4 | 256 | causal | 8 / 4.21 | 9 / 3.75 | 1.120× |
| 1/4/4 | 2048 | dense | 18 / 233.25 | 30 / 141.31 | 1.651× |
| 1/4/4 | 2048 | causal | 21 / 104.25 | 28 / 76.84 | 1.357× |
| 1/4/4 | 8192 | dense | 68.5 / 1005.61 | 114 / 604.32 | 1.664× |
| 1/4/4 | 8192 | causal | 67.5 / 508.64 | 97 / 353.75 | 1.438× |
| 1/4/4 | 16384 | dense | 197 / 1394.20 | 272.5 / 1008.87 | 1.382× |
| 1/4/4 | 16384 | causal | 144 / 952.23 | 200.5 / 685.69 | 1.389× |
| 1/4/4 | 32768 | dense | 681.5 / 1613.03 | 1019.5 / 1078.21 | 1.496× |
| 1/4/4 | 32768 | causal | 390 / 1411.11 | 442.5 / 1242.26 | 1.136× |
| 1/3/3 | 256 | dense | 6 / 8.69 | 8 / 6.55 | 1.327× |
| 1/3/3 | 256 | causal | 8 / 3.17 | 9 / 2.82 | 1.122× |
| 1/3/3 | 2048 | dense | 18 / 174.82 | 30 / 106.75 | 1.638× |
| 1/3/3 | 2048 | causal | 21 / 77.13 | 28 / 57.34 | 1.345× |
| 1/3/3 | 8192 | dense | 64 / 802.98 | 112 / 461.57 | 1.740× |
| 1/3/3 | 8192 | causal | 66.5 / 388.10 | 96 / 267.89 | 1.449× |
| 1/3/3 | 16384 | dense | 178.5 / 1155.61 | 263.5 / 783.27 | 1.475× |
| 1/3/3 | 16384 | causal | 137.5 / 749.85 | 194 / 531.33 | 1.411× |
| 1/3/3 | 32768 | dense | 528.5 / 1560.32 | 667 / 1236.47 | 1.262× |
| 1/3/3 | 32768 | causal | 316 / 1305.88 | 412 / 1000.44 | 1.305× |
| 1/2/2 | 256 | dense | 6 / 5.72 | 8 / 4.40 | 1.301× |
| 1/2/2 | 256 | causal | 8 / 2.21 | 9 / 1.97 | 1.124× |
| 1/2/2 | 2048 | dense | 17.5 / 123.09 | 29 / 73.66 | 1.671× |
| 1/2/2 | 2048 | causal | 20 / 54.11 | 27 / 39.39 | 1.374× |
| 1/2/2 | 8192 | dense | 61 / 561.33 | 109 / 316.07 | 1.776× |
| 1/2/2 | 8192 | causal | 65 / 264.22 | 95 / 181.44 | 1.456× |
| 1/2/2 | 16384 | dense | 127 / 1079.66 | 220 / 625.33 | 1.727× |
| 1/2/2 | 16384 | causal | 128 / 537.47 | 186.5 / 368.42 | 1.459× |
| 1/2/2 | 32768 | dense | 375.5 / 1463.87 | 533.5 / 1030.62 | 1.420× |
| 1/2/2 | 32768 | causal | 275 / 999.47 | 386 / 712.56 | 1.403× |
| 1/1/1 | 256 | dense | 5 / 3.51 | 6 / 2.59 | 1.355× |
| 1/1/1 | 256 | causal | 7 / 1.19 | 8 / 1.06 | 1.128× |
| 1/1/1 | 2048 | dense | 16 / 66.02 | 28 / 37.91 | 1.741× |
| 1/1/1 | 2048 | causal | 19 / 27.58 | 27 / 20.00 | 1.379× |
| 1/1/1 | 8192 | dense | 58 / 295.06 | 106 / 162.21 | 1.819× |
| 1/1/1 | 8192 | causal | 63 / 136.24 | 92.5 / 92.93 | 1.466× |
| 1/1/1 | 16384 | dense | 117 / 585.65 | 211 / 325.26 | 1.801× |
| 1/1/1 | 16384 | causal | 124 / 278.17 | 182 / 188.73 | 1.474× |
| 1/1/1 | 32768 | dense | 244 / 1128.26 | 427.5 / 642.97 | 1.755× |
| 1/1/1 | 32768 | causal | 247.5 / 556.17 | 365 / 376.88 | 1.476× |
| 32/1/1 | 256 | dense | 5 / 109.78 | 7 / 82.30 | 1.334× |
| 32/1/1 | 256 | causal | 7 / 37.70 | 8 / 33.35 | 1.130× |
| 32/1/1 | 2048 | dense | 27.5 / 1238.43 | 39 / 876.60 | 1.413× |
| 32/1/1 | 2048 | causal | 28 / 609.23 | 36 / 474.27 | 1.285× |
| 32/1/1 | 8192 | dense | 349 / 1575.91 | 460 / 1194.72 | 1.319× |
| 32/1/1 | 8192 | causal | 233 / 1180.41 | 299 / 920.61 | 1.282× |
| 32/1/1 | 16384 | dense | 1308.5 / 1680.83 | 1748.5 / 1258.01 | 1.336×
|
| 32/1/1 | 16384 | causal | 771 / 1426.37 | 953.5 / 1153.07 | 1.237× |
| 32/1/1 | 32768 | dense | 5088.5 / 1728.62 | 6778 / 1297.69 | 1.332× |
| 32/1/1 | 32768 | causal | 2802.5 / 1569.31 | 3492.5 / 1259.32 | 1.246×
|

</details>

<details><summary>BF16 Q/K=128, V=128: all 110 workloads</summary>

| B/H/Hkv | Sq = Sk | Mask | qr_tdm_sched (Introduced by this PR)<br>µs
/ TFLOP/s | qr_tdm<br>µs / TFLOP/s | qr_tdm_sched / qr_tdm |
| --- | ---: | --- | ---: | ---: | ---: |
| 1/8/8 | 256 | dense | 5 / 49.45 | 7 / 40.56 | 1.219× |
| 1/8/8 | 256 | causal | 7 / 19.76 | 10 / 14.05 | 1.406× |
| 1/8/8 | 2048 | dense | 19.5 / 883.62 | 26 / 652.13 | 1.355× |
| 1/8/8 | 2048 | causal | 21 / 411.26 | 36 / 238.12 | 1.727× |
| 1/8/8 | 8192 | dense | 139.5 / 1969.99 | 162.5 / 1694.40 | 1.163× |
| 1/8/8 | 8192 | causal | 91.5 / 1500.30 | 141 / 974.51 | 1.540× |
| 1/8/8 | 16384 | dense | 510 / 2154.63 | 589 / 1865.78 | 1.155× |
| 1/8/8 | 16384 | causal | 281.5 / 1951.12 | 383.5 / 1434.64 | 1.360× |
| 1/8/8 | 32768 | dense | 1956 / 2248.86 | 2269.5 / 1938.01 | 1.160× |
| 1/8/8 | 32768 | causal | 1020.5 / 2154.98 | 1377.5 / 1596.22 | 1.350×
|
| 1/64/8 | 256 | dense | 6 / 372.57 | 7 / 306.49 | 1.216× |
| 1/64/8 | 256 | causal | 7 / 153.30 | 10 / 108.67 | 1.411× |
| 1/64/8 | 2048 | dense | 78.5 / 1752.28 | 91 / 1513.26 | 1.158× |
| 1/64/8 | 2048 | causal | 50 / 1369.23 | 66 / 1035.78 | 1.322× |
| 1/64/8 | 8192 | dense | 1009 / 2180.05 | 1164 / 1888.82 | 1.154× |
| 1/64/8 | 8192 | causal | 560 / 1964.26 | 736 / 1493.53 | 1.315× |
| 1/64/8 | 16384 | dense | 3924 / 2241.60 | 4520.5 / 1945.76 | 1.152× |
| 1/64/8 | 16384 | causal | 2078 / 2116.28 | 2770 / 1587.78 | 1.333× |
| 1/64/8 | 32768 | dense | 16023 / 2195.86 | 18352 / 1917.18 | 1.145× |
| 1/64/8 | 32768 | causal | 8186.5 / 2148.93 | 10847 / 1621.95 | 1.325×
|
| 1/64/64 | 256 | dense | 6 / 368.88 | 7 / 306.31 | 1.204× |
| 1/64/64 | 256 | causal | 7 / 153.55 | 10 / 108.72 | 1.412× |
| 1/64/64 | 2048 | dense | 80.5 / 1708.78 | 92.5 / 1486.65 | 1.149× |
| 1/64/64 | 2048 | causal | 51 / 1351.80 | 67.5 / 1016.80 | 1.329× |
| 1/64/64 | 8192 | dense | 1096.5 / 2005.53 | 1267.5 / 1734.80 | 1.156×
|
| 1/64/64 | 8192 | causal | 584 / 1883.05 | 751 / 1464.62 | 1.286× |
| 1/64/64 | 16384 | dense | 4300 / 2045.51 | 5004.5 / 1757.77 | 1.164× |
| 1/64/64 | 16384 | causal | 2245.5 / 1958.67 | 2935 / 1498.59 | 1.307×
|
| 1/64/64 | 32768 | dense | 17102 / 2057.36 | 19826.5 / 1774.61 | 1.159×
|
| 1/64/64 | 32768 | causal | 8833 / 1991.76 | 11570.5 / 1520.44 | 1.310×
|
| 1/12/12 | 256 | dense | 6 / 68.67 | 7 / 57.87 | 1.187× |
| 1/12/12 | 256 | causal | 7 / 28.33 | 10 / 20.48 | 1.383× |
| 1/12/12 | 2048 | dense | 21 / 1222.07 | 28 / 923.80 | 1.323× |
| 1/12/12 | 2048 | causal | 22 / 592.16 | 37 / 348.87 | 1.697× |
| 1/12/12 | 8192 | dense | 210.5 / 1955.64 | 247.5 / 1664.91 | 1.175× |
| 1/12/12 | 8192 | causal | 125.5 / 1637.74 | 157.5 / 1311.42 | 1.249× |
| 1/12/12 | 16384 | dense | 775.5 / 2125.59 | 894.5 / 1844.38 | 1.152× |
| 1/12/12 | 16384 | causal | 420 / 1964.32 | 551.5 / 1495.67 | 1.313× |
| 1/12/12 | 32768 | dense | 3062 / 2154.51 | 3586 / 1839.65 | 1.171× |
| 1/12/12 | 32768 | causal | 1585.5 / 2079.95 | 2073 / 1591.16 | 1.307×
|
| 1/32/32 | 256 | dense | 6 / 192.74 | 7 / 157.75 | 1.222× |
| 1/32/32 | 256 | causal | 7 / 78.35 | 10 / 55.27 | 1.418× |
| 1/32/32 | 2048 | dense | 39.5 / 1733.26 | 46.5 / 1474.77 | 1.175× |
| 1/32/32 | 2048 | causal | 28 / 1225.74 | 41 / 838.59 | 1.462× |
| 1/32/32 | 8192 | dense | 520 / 2115.86 | 597 / 1842.40 | 1.148× |
| 1/32/32 | 8192 | causal | 287.5 / 1912.56 | 380 / 1445.95 | 1.323× |
| 1/32/32 | 16384 | dense | 2052 / 2143.53 | 2386 / 1843.25 | 1.163× |
| 1/32/32 | 16384 | causal | 1068.5 / 2058.40 | 1401.5 / 1569.14 |
1.312× |
| 1/32/32 | 32768 | dense | 8160 / 2155.86 | 9495 / 1852.79 | 1.164× |
| 1/32/32 | 32768 | causal | 4212.5 / 2088.30 | 5575 / 1577.80 | 1.324×
|
| 32/8/8 | 256 | dense | 9 / 948.69 | 11 / 807.17 | 1.175× |
| 32/8/8 | 256 | causal | 10 / 425.35 | 13 / 329.72 | 1.290× |
| 32/8/8 | 2048 | dense | 301.5 / 1822.08 | 354.5 / 1550.15 | 1.175× |
| 32/8/8 | 2048 | causal | 205 / 1339.68 | 264 / 1040.47 | 1.288× |
| 32/8/8 | 8192 | dense | 4004.5 / 2196.34 | 4650 / 1891.64 | 1.161× |
| 32/8/8 | 8192 | causal | 2218 / 1983.39 | 2948 / 1492.08 | 1.329× |
| 32/8/8 | 16384 | dense | 15650 / 2248.20 | 18080 / 1946.01 | 1.155× |
| 32/8/8 | 16384 | causal | 8324 / 2113.67 | 11086.5 / 1586.88 | 1.332×
|
| 32/8/8 | 32768 | dense | 62354 / 2257.07 | 71921.5 / 1956.83 | 1.153×
|
| 32/8/8 | 32768 | causal | 32432.5 / 2169.79 | 43190 / 1629.34 | 1.332×
|
| 1/4/4 | 256 | dense | 6 / 23.94 | 7 / 19.89 | 1.203× |
| 1/4/4 | 256 | causal | 7 / 9.84 | 10 / 6.97 | 1.410× |
| 1/4/4 | 2048 | dense | 18.5 / 464.11 | 25 / 338.91 | 1.369× |
| 1/4/4 | 2048 | causal | 20 / 211.88 | 36 / 120.81 | 1.754× |
| 1/4/4 | 8192 | dense | 80 / 1713.53 | 102 / 1345.16 | 1.274× |
| 1/4/4 | 8192 | causal | 71 / 961.78 | 129 / 531.40 | 1.810× |
| 1/4/4 | 16384 | dense | 268.5 / 2047.22 | 310.5 / 1771.69 | 1.156× |
| 1/4/4 | 16384 | causal | 172 / 1599.62 | 271.5 / 1013.66 | 1.578× |
| 1/4/4 | 32768 | dense | 996.5 / 2206.86 | 1157 / 1900.56 | 1.161× |
| 1/4/4 | 32768 | causal | 539.5 / 2037.87 | 746.5 / 1472.63 | 1.384× |
| 1/3/3 | 256 | dense | 6 / 17.51 | 7 / 14.69 | 1.192× |
| 1/3/3 | 256 | causal | 7 / 7.22 | 10 / 5.21 | 1.385× |
| 1/3/3 | 2048 | dense | 18 / 349.55 | 25 / 257.00 | 1.360× |
| 1/3/3 | 2048 | causal | 20 / 159.11 | 35 / 91.00 | 1.748× |
| 1/3/3 | 8192 | dense | 72 / 1433.12 | 96.5 / 1068.22 | 1.342× |
| 1/3/3 | 8192 | causal | 70 / 737.01 | 127.5 / 404.37 | 1.823× |
| 1/3/3 | 16384 | dense | 235 / 1756.28 | 277 / 1488.26 | 1.180× |
| 1/3/3 | 16384 | causal | 155.5 / 1327.24 | 262.5 / 785.17 | 1.690× |
| 1/3/3 | 32768 | dense | 761 / 2167.19 | 902.5 / 1826.72 | 1.186× |
| 1/3/3 | 32768 | causal | 423 / 1947.91 | 569.5 / 1448.76 | 1.345× |
| 1/2/2 | 256 | dense | 5 / 12.27 | 7 / 10.12 | 1.212× |
| 1/2/2 | 256 | causal | 7 / 4.96 | 10 / 3.50 | 1.414× |
| 1/2/2 | 2048 | dense | 18 / 238.93 | 25 / 174.05 | 1.373× |
| 1/2/2 | 2048 | causal | 20 / 108.42 | 35 / 61.23 | 1.771× |
| 1/2/2 | 8192 | dense | 66 / 1041.96 | 92 / 748.55 | 1.392× |
| 1/2/2 | 8192 | causal | 67.5 / 509.00 | 125.5 / 273.87 | 1.859× |
| 1/2/2 | 16384 | dense | 151.5 / 1813.10 | 193.5 / 1419.58 | 1.277× |
| 1/2/2 | 16384 | causal | 136 / 1010.78 | 250 / 549.94 | 1.838× |
| 1/2/2 | 32768 | dense | 512 / 2148.24 | 595 / 1848.09 | 1.162× |
| 1/2/2 | 32768 | causal | 326.5 / 1685.10 | 527.5 / 1041.99 | 1.617× |
| 1/1/1 | 256 | dense | 5 / 6.31 | 7 / 5.12 | 1.231× |
| 1/1/1 | 256 | causal | 7 / 2.51 | 10 / 1.77 | 1.421× |
| 1/1/1 | 2048 | dense | 17 / 123.37 | 24 / 89.28 | 1.382× |
| 1/1/1 | 2048 | causal | 20 / 54.98 | 35 / 30.91 | 1.778× |
| 1/1/1 | 8192 | dense | 62 / 553.37 | 88 / 389.71 | 1.420× |
| 1/1/1 | 8192 | causal | 65 / 262.91 | 123 / 139.94 | 1.879× |
| 1/1/1 | 16384 | dense | 126 / 1086.95 | 178 / 773.91 | 1.404× |
| 1/1/1 | 16384 | causal | 129 / 531.38 | 243 / 282.38 | 1.882× |
| 1/1/1 | 32768 | dense | 287.5 / 1911.71 | 372.5 / 1475.77 | 1.295× |
| 1/1/1 | 32768 | causal | 264.5 / 1038.78 | 487.5 / 563.97 | 1.842× |
| 32/1/1 | 256 | dense | 6 / 193.55 | 7 / 159.84 | 1.211× |
| 32/1/1 | 256 | causal | 7 / 78.92 | 10 / 55.59 | 1.420× |
| 32/1/1 | 2048 | dense | 39 / 1755.37 | 47 / 1456.70 | 1.205× |
| 32/1/1 | 2048 | causal | 35 / 980.40 | 51 / 675.58 | 1.451× |
| 32/1/1 | 8192 | dense | 524 / 2098.01 | 611 / 1798.82 | 1.166× |
| 32/1/1 | 8192 | causal | 334.5 / 1643.07 | 470 / 1170.80 | 1.403× |
| 32/1/1 | 16384 | dense | 1975 / 2226.80 | 2305.5 / 1907.85 | 1.167× |
| 32/1/1 | 16384 | causal | 1132 / 1942.68 | 1580 / 1391.70 | 1.396× |
| 32/1/1 | 32768 | dense | 7753.5 / 2268.94 | 9014.5 / 1951.53 | 1.163×
|
| 32/1/1 | 32768 | causal | 4220.5 / 2084.35 | 5883.5 / 1495.04 | 1.394×
|

</details>

<details><summary>BF16 Q/K=192, V=128: all 110 workloads</summary>

| B/H/Hkv | Sq = Sk | Mask | qr_tdm_sched (Introduced by this PR)<br>µs
/ TFLOP/s | qr_tdm<br>µs / TFLOP/s | qr_tdm_sched / qr_tdm |
| --- | ---: | --- | ---: | ---: | ---: |
| 1/8/8 | 256 | dense | 7 / 48.02 | 8 / 43.73 | 1.098× |
| 1/8/8 | 256 | causal | 9 / 19.61 | 11 / 16.02 | 1.224× |
| 1/8/8 | 2048 | dense | 23 / 925.25 | 35 / 617.25 | 1.499× |
| 1/8/8 | 2048 | causal | 26 / 416.77 | 44 / 242.61 | 1.718× |
| 1/8/8 | 8192 | dense | 182 / 1888.17 | 201.5 / 1707.81 | 1.106× |
| 1/8/8 | 8192 | causal | 133 / 1292.51 | 177 / 971.64 | 1.330× |
| 1/8/8 | 16384 | dense | 665.5 / 2064.98 | 745 / 1845.91 | 1.119× |
| 1/8/8 | 16384 | causal | 392 / 1752.87 | 452.5 / 1518.08 | 1.155× |
| 1/8/8 | 32768 | dense | 2587 / 2125.11 | 2912 / 1888.20 | 1.125× |
| 1/8/8 | 32768 | causal | 1408 / 1952.06 | 1712 / 1605.57 | 1.216× |
| 1/64/8 | 256 | dense | 7 / 369.48 | 8 / 333.00 | 1.110× |
| 1/64/8 | 256 | causal | 9 / 151.57 | 11 / 124.22 | 1.220× |
| 1/64/8 | 2048 | dense | 102 / 1678.51 | 112.5 / 1521.56 | 1.103× |
| 1/64/8 | 2048 | causal | 69 / 1250.12 | 77 / 1121.22 | 1.115× |
| 1/64/8 | 8192 | dense | 1322 / 2078.82 | 1481 / 1856.32 | 1.120× |
| 1/64/8 | 8192 | causal | 757 / 1816.07 | 891.5 / 1541.56 | 1.178× |
| 1/64/8 | 16384 | dense | 5171 / 2126.24 | 5815.5 / 1890.62 | 1.125× |
| 1/64/8 | 16384 | causal | 2859 / 1923.03 | 3381 / 1626.04 | 1.183× |
| 1/64/8 | 32768 | dense | 20964 / 2097.89 | 23605.5 / 1863.16 | 1.126×
|
| 1/64/8 | 32768 | causal | 11295.5 / 1946.87 | 13384.5 / 1642.97 |
1.185× |
| 1/64/64 | 256 | dense | 7 / 369.46 | 8 / 333.52 | 1.108× |
| 1/64/64 | 256 | causal | 9 / 152.22 | 11 / 124.11 | 1.226× |
| 1/64/64 | 2048 | dense | 105.5 / 1627.51 | 114.5 / 1499.87 | 1.085× |
| 1/64/64 | 2048 | causal | 70.5 / 1212.61 | 78.5 / 1094.97 | 1.107× |
| 1/64/64 | 8192 | dense | 1458 / 1885.32 | 1605.5 / 1712.16 | 1.101× |
| 1/64/64 | 8192 | causal | 798 / 1721.78 | 918 / 1496.87 | 1.150× |
| 1/64/64 | 16384 | dense | 5716.5 / 1923.35 | 6349 / 1731.78 | 1.111× |
| 1/64/64 | 16384 | causal | 3107.5 / 1769.16 | 3614 / 1521.28 | 1.163×
|
| 1/64/64 | 32768 | dense | 22649 / 1941.85 | 25245 / 1742.13 | 1.115× |
| 1/64/64 | 32768 | causal | 12231.5 / 1797.93 | 14312.5 / 1536.50 |
1.170× |
| 1/12/12 | 256 | dense | 7 / 68.65 | 8 / 63.35 | 1.084× |
| 1/12/12 | 256 | causal | 9 / 28.48 | 11 / 23.48 | 1.213× |
| 1/12/12 | 2048 | dense | 25.5 / 1273.39 | 37 / 877.98 | 1.450× |
| 1/12/12 | 2048 | causal | 27 / 594.66 | 46 / 353.45 | 1.682× |
| 1/12/12 | 8192 | dense | 275 / 1872.67 | 318 / 1619.62 | 1.156× |
| 1/12/12 | 8192 | causal | 161 / 1602.28 | 195 / 1324.34 | 1.210× |
| 1/12/12 | 16384 | dense | 1020 / 2021.97 | 1133.5 / 1819.31 | 1.111× |
| 1/12/12 | 16384 | causal | 567 / 1817.85 | 676 / 1525.85 | 1.191× |
| 1/12/12 | 32768 | dense | 4026.5 / 2047.83 | 4507.5 / 1829.59 | 1.119×
|
| 1/12/12 | 32768 | causal | 2155.5 / 1913.13 | 2552.5 / 1615.36 |
1.184× |
| 1/32/32 | 256 | dense | 7 / 187.38 | 8 / 168.97 | 1.109× |
| 1/32/32 | 256 | causal | 9 / 77.41 | 11 / 63.06 | 1.228× |
| 1/32/32 | 2048 | dense | 53 / 1621.17 | 58 / 1485.64 | 1.091× |
| 1/32/32 | 2048 | causal | 40 / 1071.55 | 51 / 844.96 | 1.268× |
| 1/32/32 | 8192 | dense | 687.5 / 1999.30 | 762 / 1803.20 | 1.109× |
| 1/32/32 | 8192 | causal | 391.5 / 1755.41 | 466.5 / 1473.47 | 1.191× |
| 1/32/32 | 16384 | dense | 2731 / 2013.10 | 3041 / 1807.96 | 1.113× |
| 1/32/32 | 16384 | causal | 1467.5 / 1873.78 | 1720.5 / 1597.70 |
1.173× |
| 1/32/32 | 32768 | dense | 10799.5 / 2036.24 | 12074 / 1821.28 | 1.118×
|
| 1/32/32 | 32768 | causal | 5818.5 / 1889.83 | 6846.5 / 1606.04 |
1.177× |
| 32/8/8 | 256 | dense | 12 / 910.68 | 12 / 880.00 | 1.035× |
| 32/8/8 | 256 | causal | 13 / 412.21 | 15 / 369.06 | 1.117× |
| 32/8/8 | 2048 | dense | 395.5 / 1737.20 | 434.5 / 1580.08 | 1.099× |
| 32/8/8 | 2048 | causal | 273 / 1260.08 | 313 / 1098.72 | 1.147× |
| 32/8/8 | 8192 | dense | 5265 / 2088.28 | 5900.5 / 1863.57 | 1.121× |
| 32/8/8 | 8192 | causal | 3038.5 / 1809.44 | 3584 / 1534.17 | 1.179× |
| 32/8/8 | 16384 | dense | 20551.5 / 2139.96 | 23140 / 1900.63 | 1.126×
|
| 32/8/8 | 16384 | causal | 11441.5 / 1922.14 | 13574 / 1620.11 | 1.186×
|
| 32/8/8 | 32768 | dense | 82542.5 / 2131.29 | 92859.5 / 1894.49 |
1.125× |
| 32/8/8 | 32768 | causal | 44795 / 1963.68 | 53240 / 1652.22 | 1.189× |
| 1/4/4 | 256 | dense | 7 / 23.82 | 8 / 21.95 | 1.085× |
| 1/4/4 | 256 | causal | 9 / 9.81 | 10.5 / 8.02 | 1.224× |
| 1/4/4 | 2048 | dense | 22 / 487.02 | 34 / 320.13 | 1.521× |
| 1/4/4 | 2048 | causal | 25 / 213.91 | 44 / 123.07 | 1.738× |
| 1/4/4 | 8192 | dense | 96 / 1791.19 | 136 / 1266.14 | 1.415× |
| 1/4/4 | 8192 | causal | 90 / 958.32 | 163 / 525.59 | 1.823× |
| 1/4/4 | 16384 | dense | 346 / 1984.35 | 386.5 / 1779.49 | 1.115× |
| 1/4/4 | 16384 | causal | 254.5 / 1350.47 | 342.5 / 1003.10 | 1.346× |
| 1/4/4 | 32768 | dense | 1310 / 2098.09 | 1470 / 1869.88 | 1.122× |
| 1/4/4 | 32768 | causal | 754 / 1823.20 | 878 / 1564.96 | 1.165× |
| 1/3/3 | 256 | dense | 7 / 17.62 | 8 / 16.11 | 1.094× |
| 1/3/3 | 256 | causal | 9 / 7.25 | 11 / 5.95 | 1.219× |
| 1/3/3 | 2048 | dense | 22 / 366.83 | 33 / 240.81 | 1.523× |
| 1/3/3 | 2048 | causal | 25 / 160.38 | 43 / 92.67 | 1.731× |
| 1/3/3 | 8192 | dense | 86 / 1496.30 | 130 / 988.91 | 1.513× |
| 1/3/3 | 8192 | causal | 87 / 739.11 | 161 / 400.90 | 1.844× |
| 1/3/3 | 16384 | dense | 307.5 / 1674.18 | 347.5 / 1482.52 | 1.129× |
| 1/3/3 | 16384 | causal | 238 / 1082.90 | 331 / 778.76 | 1.391× |
| 1/3/3 | 32768 | dense | 1007 / 2047.31 | 1181 / 1745.87 | 1.173× |
| 1/3/3 | 32768 | causal | 567.5 / 1816.11 | 715 / 1441.41 | 1.260× |
| 1/2/2 | 256 | dense | 7 / 12.18 | 8 / 11.12 | 1.094× |
| 1/2/2 | 256 | causal | 9 / 4.94 | 10 / 4.03 | 1.225× |
| 1/2/2 | 2048 | dense | 21 / 252.14 | 33 / 163.94 | 1.538× |
| 1/2/2 | 2048 | causal | 25 / 108.95 | 43 / 62.28 | 1.749× |
| 1/2/2 | 8192 | dense | 78 / 1099.51 | 125 / 684.67 | 1.606× |
| 1/2/2 | 8192 | causal | 85 / 504.75 | 159 / 269.97 | 1.870× |
| 1/2/2 | 16384 | dense | 179.5 / 1914.47 | 260 / 1321.57 | 1.449× |
| 1/2/2 | 16384 | causal | 171.5 / 1001.16 | 317.5 / 541.13 | 1.850× |
| 1/2/2 | 32768 | dense | 667.5 / 2059.55 | 744.5 / 1846.57 | 1.115× |
| 1/2/2 | 32768 | causal | 492.5 / 1395.38 | 670.5 / 1024.41 | 1.362× |
| 1/1/1 | 256 | dense | 7 / 6.20 | 7.5 / 5.61 | 1.105× |
| 1/1/1 | 256 | causal | 8 / 2.50 | 10 / 2.03 | 1.232× |
| 1/1/1 | 2048 | dense | 21 / 129.12 | 32 / 83.85 | 1.540× |
| 1/1/1 | 2048 | causal | 24 / 55.22 | 43 / 31.50 | 1.753× |
| 1/1/1 | 8192 | dense | 73 / 584.67 | 121 / 355.15 | 1.646× |
| 1/1/1 | 8192 | causal | 83 / 259.93 | 155 / 138.75 | 1.873× |
| 1/1/1 | 16384 | dense | 149.5 / 1148.66 | 243.5 / 705.19 | 1.629× |
| 1/1/1 | 16384 | causal | 163 / 526.39 | 307.5 / 279.66 | 1.882× |
| 1/1/1 | 32768 | dense | 341.5 / 2012.12 | 507.5 / 1354.29 | 1.486× |
| 1/1/1 | 32768 | causal | 331.5 / 1036.67 | 622.5 / 551.94 | 1.878× |
| 32/1/1 | 256 | dense | 7 / 191.09 | 8 / 175.12 | 1.091× |
| 32/1/1 | 256 | causal | 9 / 78.16 | 11 / 63.94 | 1.222× |
| 32/1/1 | 2048 | dense | 53 / 1614.35 | 58.5 / 1474.45 | 1.095× |
| 32/1/1 | 2048 | causal | 50.5 / 845.21 | 58.5 / 734.68 | 1.150× |
| 32/1/1 | 8192 | dense | 681 / 2018.51 | 760.5 / 1808.03 | 1.116× |
| 32/1/1 | 8192 | causal | 459 / 1496.40 | 565.5 / 1215.26 | 1.231× |
| 32/1/1 | 16384 | dense | 2588.5 / 2124.03 | 2905 / 1892.45 | 1.122× |
| 32/1/1 | 16384 | causal | 1559.5 / 1762.61 | 1917 / 1434.18 | 1.229× |
| 32/1/1 | 32768 | dense | 10155 / 2165.40 | 11427 / 1924.38 | 1.125× |
| 32/1/1 | 32768 | causal | 5901.5 / 1863.09 | 7211 / 1524.86 | 1.222× |

</details>

## Clang 24 performance disclosure

A separate matched Clang 24 study found slower `qr_tdm_sched`
performance in some D64/D128 causal configurations. That study used a
different timing harness, so its quantitative results are excluded from
this description to keep every reported performance number sourced from
`tile_example_fmha_fwd`. A matched Clang 24 example run is still needed
before assigning a case count or slowdown magnitude to this benchmark.
The dispatch intentionally keeps `qr_tdm_sched` priority for its
supported configurations; the Clang 23 results above should not be
extrapolated to Clang 24.

## Toolchain and build environment

| Component | Clang 23 example comparison | Clang 24 matched validation
/ separate diagnostic |
| --- | --- | --- |
| Device compiler | AMD Clang `23.0.0git`; LLVM
`2a67d831a09cc538194bd595279ee91258570c10`; `clang++` SHA256
`b552c9fa9e9158688cd4944a4aa899dfedc7426b700097ef9338f4476dc6f6f6` | AMD
Clang `24.0.0git`; LLVM `4f43f4746ede4cc49ab4129f473dbd54e0d9db4f`;
`clang++` SHA256
`1cfe274fcb8f92e5ccb423173e207dda6036e4a9efcfcdf29193049849d33979` |
| `hipcc -v` / HIP | Container `hipcc` reports Clang 23 and HIP 7.15.0;
`hipconfig --version` reports `7.15.0-0000000` | The pinned Clang 24
build invokes `clang++` directly, with `--hip-path=/opt/rocm`; container
HIP headers/runtime are 7.15.0. Container `hipcc` was not used for this
build. |
| LLVM linker and device libraries | AMD LLD 23.0.0;
`/opt/rocm/amdgcn/bitcode` | AMD LLD 24.0.0; pinned Clang 24
device-library bundle |
| Target and common build options | `gfx1250`, `-O3`, `-DNDEBUG`,
`CK_TILE_FMHA_FWD_FAST_EXP2=1`; scheduled objects use `-mllvm
-amdgpu-expert-scheduling-mode` | Same target and relevant
optimization/math settings; scheduled objects use expert scheduling |
| Source | This PR at `4688449fa9b` vs upstream `qr_tdm` at
`9e3337709130` (includes PR #12347) | Matched final-candidate source and
upstream `qr_tdm`; numerical results omitted here because they were not
produced by `tile_example_fmha_fwd` |

## Validation

- Current-candidate FP16/BF16 generated-dispatch correctness passed
924/924 configurations across D64/D128/D192; sequences through 4096
checked all rows, while longer sequences used deterministic sampled rows
plus whole-output finite and LSE scans.
- The generated-dispatch GTests passed 480/480; finite virtual-sink
fallback passed 96/96 and selected `qr_tdm`; direct layout, softmax,
fragment, and schedule GTests passed 79/79. Python codegen/dispatcher
tests passed 40/40.
- All 72 generated scheduled objects compiled under the pinned Clang 24
toolchain. Local benchmark scripts and codegen filters are excluded from
the PR.
JIRA ID : AIHPBLAS-4357, AIHPBLAS-4369, AIHPBLAS-4390, AIHPBLAS-4394

| Ticket | Summary | Covered by |
|---|---|---|
| AIHPBLAS-4357 | UnboundLocalError in `computeLoadSrd()` for
non-batched GEMM with SupportUserArgs on gfx1250 | `Batched=False`
reject |
| AIHPBLAS-4369 | Kernel generation crash when StridedBatched is set to
false | `StridedBatched=False` reject |
| AIHPBLAS-4390 | UseInitialStridesCD=True triggers unimplemented
AssertionError in KernelWriterConversion | `UseInitialStridesCD=True`
reject |
| AIHPBLAS-4394 | Segmentation fault with OutputAmaxD=true on gfx1250 |
Main fix in [#11525](#11525);
this PR fixes the amax instruction-size under-count that makes
`CheckASMCodeSize` fail |

## Motivation

Running the gfx1250 end-to-end TensileLite yamls from
[#11117](#11117) shows three
independent problems:

1. Some ProblemType configurations cannot be generated for gfx1250. Each
one
   either aborts TensileCreateLibrary or produces a kernel that does not
   assemble, so the failure is a crash instead of a clean rejection.
2. OutputAmaxD kernels on gfx1250 fail `CheckASMCodeSize` (228 bytes
short).
StinkyTofu's instruction-size model counts `v_max_num_f32 vD, vD,
|vSrc|`
   as 4 bytes, but the abs modifier forces the 8-byte VOP3 encoding.
3. Five benchmark yamls abort with `hipErrorNoBinaryForGpu` because the
   generated code object contains no kernels.

## Technical Details

**1. Reject unsupported problem types on gfx1250**
(`SolutionStructs/Solution.py`)

The rejects apply to ISA (12, 5), which covers gfx1250, gfx1250-strict
and
gfx1250v0. Other architectures are unchanged.

| Configuration | Why it fails |
|---|---|
| `StridedBatched=False` | `loadBatchedAddress` emits the `moduleWg`
labels twice, which StinkyTofu's CFG builder aborts on.
`KernelWriterConversion` also emits invalid HIP for Batch{C,D}. General
batch stays available at runtime via ArgType==3 on StridedBatched
SupportUserArgs kernels. (AIHPBLAS-4369) |
| `Batched=False` | `computeLoadSrd` branches to
`StridedBatchedGemmLoad` labels that only exist when there is a batch
index. `KernelWriterConversion` still emits `batchIdx = id2`.
(AIHPBLAS-4357) |
| `UseInitialStridesCD=True` | The `KernelWriterConversion` and
`KernelWriterBetaOnly` helpers hard-code `stride0 == 1` and assert on
it. (AIHPBLAS-4390) |
| `UseInitialStridesAB=True` | A and B each need `len(IndexAssignments)`
strides (e.g. 3). rocisa's kernarg loader has no 3-dword form, and
`DeviceUserArguments` holds only two strides per tensor. |

Like the General Batched checks above them, these rejects have no early
`return`. With `NoReject` set, derivation runs to completion instead of
failing
later with `KeyError 'UseDotInstruction'` ("Error processing
permutation").
Optional keys are read with `.get()` so that partial ProblemTypes in
unit tests
still work.

**2. Count abs/neg source modifiers as VOP3** (`shared/stinkytofu`,
AIHPBLAS-4394)

`getEffectiveBaseSizeInBytesImpl` now returns 8 bytes for a compact VALU
instruction when either:

- it carries `VOP3Modifiers` with any `neg_src*` or `abs_src*` set; or
- a source register has `reg.isAbs` or `reg.isMinus` set. This is what
the
  rocisa-to-stinky conversion produces for `vgpr(..., isAbs=True)`.

`clamp` and `omod` are not counted because the emitter does not print
them.

The new `tensilelite/Tests/common/amaxd/gfx1250/amax_gfx1250.yaml`
(taken from
[#11525](#11525)) covers this
path.
It mirrors `common/amaxd/8rn_amaxd.yaml`, which skips gfx1250, so
develop never
exercised the under-count. On gfx1250 the yaml uses the
`flat_atomic_dec_u32`
synchronizer fallback, and every group has the full bias/scale epilogue
with
Activation enabled.

**3. Assemble each unique kernel once** (`TensileCreateLibrary/Run.py`)

Deduplication uses the full kernel key, but the `.s`/`.o` file name
comes from
`getRequiredParametersMin`. A duplicate kernel therefore writes the same
path
as the kernel it duplicates, and its source is empty. `ParallelMap2`
assembled
both in parallel; when the duplicate finished last, its empty `.s`
replaced the
real one. `writeSolutionsAndKernels` now drops duplicates before
assembling.
This is the same change as the `Run.py` hunk in
[#11525](#11525).

Known follow-up, not addressed here: MagicDivAlg, DebugStreamK,
ScheduleGlobalRead/LocalWrite and StreamKAtomic change codegen but are
not part
of the kernel name, so variants that differ only in those parameters
still map
to the same file.

## Test Plan

All testing ran on a gfx1250 B0 host (`HSA_DISABLE_GFX12_STRICT=0`) with
ROCm
`10.2.0a20261012`, the newest nightly at the time. This host cannot run
gfx1250-strict (A0) binaries.

Clean rebuild of this branch (`75ea1572641`, on develop `e2aef14a26f`),
following the gfx1250-strict A0 install guide (sections 5, 6 and 8):

- StinkyTofu `ctest`, including 10 new `InstructionSizeCostingTest`
cases.
- hipBLASLt and hipSPARSELt builds for gfx1250, gfx1250v0 and
gfx1250-strict,
  with the library layout check.
- TensileLite tox unit tests, `test_arch_steppings.py` and the packaging
script
  tests.
- Runtime smoke test, `hipblaslt-test` full run and `*quick*`.
- FP4 regression: `hipblaslt-bench` over every FP4 solution, plus the
mxf4
  smoke tests.
- TensileLite tox `py3` (`tensilelite/Tests/common -k "not streamk"`).
- `hipsparselt-test` full run.

The same plan also ran on the pre-rebase base (develop `3377e3006f4`
plus these
commits), with the same results apart from the new develop tests noted
below.

Earlier on the same branch, with the previous ROCm nightly:

- `amax_gfx1250.yaml` on GPU with `CheckASMCodeSize` enabled.
- The 94 gfx1250 e2e yamls from #11117, run before and after these
changes.

## Test Result

| Step | Result |
|---|---|
| StinkyTofu `ctest` | 1636/1636 passed |
| hipBLASLt build | rc=0, 3 groups; `gfx1250` 412 / `gfx1250v0` 397 /
`gfx1250-strict` 400 files; targets `gfx1250`, `gfx1250`,
`gfx1250-strict`; layout OK |
| hipSPARSELt build | rc=0; 131 files in each of the three subtrees |
| TensileLite tox unit | 10451 passed, 0 failed (328 skipped, 2 xfailed,
1 xpassed) |
| `test_arch_steppings.py` | 829 passed |
| Packaging scripts | 18 passed |
| Smoke | 1152 passed |
| `hipblaslt-test` all | 67970 passed, 8 failed (see below), 167 skipped
|
| `hipblaslt-test` `*quick*` | 18043 passed |
| FP4 bench | 345 of 346 solutions pass (see below) |
| FP4 mxf4 smoke | 32 passed |
| tox `py3` | 10072 passed, 0 failed, 1518 skipped, 3 xfailed |
| `hipsparselt-test` | 165405 passed, 0 failed |

Failures, none caused by this PR:

- `hipblaslt-test`: the 8 failures are all new tests from
[#13031](#13031), which
arrived
  with the rebase. `matmul_fast_check_mx_fp8` and
`matmul_fast_check_mx_fp8_large_k` run on `gpu_arch: '(950|1250)'` and
cover
every fp8 pair. Every case with a bf8 operand (bf8×bf8, f8×bf8, bf8×f8)
fails
with `NO solution found!`. gfx1250's logic has MX kernels for f8×f8, F4
and
F8F4 only, with no bf8 MX logic, so develop fails the same way. The
f8×f8
  cases pass.
- FP4 bench: the one failure is solution 1830
  (`F4BS ... MT256x256x128_MI16x16x1`). It was added by
[#13201](#13201) and returns
`nan`
  or `inf` for k ≥ 384 on develop as well. It is only reached when every
  solution is listed or the solution index is forced.

Other results:

- `amax_gfx1250.yaml`: 34/34 passed. Without change 2 it fails
  `CheckASMCodeSize`, 228 bytes short.
- The rejects turn the generator crashes for these configurations into
clean
  "0 valid solutions" results.
- Duplicate-kernel fix: `magic_div_alg`, `debug_flags`,
  `schedule_global_local` and `assert_params` go from a core dump to all
passing (8, 7, 10 and 16 tests). `max_lds_occupancy` no longer core
dumps;
  its last step finds 0 valid solutions because of a bug in the yaml
(MaxOccupancy 4 needs 81920 B of LDS, above MaxLDS 65536). After this
PR,
  #11117 needs to drop the `_xfail` suffix from the first four.
- On the first pre-rebase `hipblaslt-test` attempt the GPU hung on

`nightly_matmul_deepbench_bf16_rbf16_rbf16_rbf16_rf32_r_NN_576_64_28000_1_576_28000_1_576_576_3_t2`
(gfxhub no-retry page fault, then MES `REMOVE_QUEUE` timeout) and the
host
had to be rebooted. That test has since passed on its own in both runs,
and
  no GPU errors appeared in dmesg during either run, so the hang did not
  reproduce.

## Submission Checklist

- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.
…fx942 (#13292)

JIRA ID: https://amd-hub.atlassian.net/browse/AIROCBLAS-1268
JIRA ID: https://amd-hub.atlassian.net/browse/AIROCBLAS-1267
JIRA ID: https://amd-hub.atlassian.net/browse/AIHPBLAS-3019

## Motivation

rocBLAS currently defaults to the Tensile Solution Library for FP16,
BF16, and FP64 GEMM on gfx942. As part of the GEMM backend unification
initiative, the default backend is flipped to hipBLASLt for these three
input types, following the performance parity threshold of within 5% of
Tensile or acceptable performance.

This extends the two preceding flips — DGEMM on gfx950 (#12424) and
DGEMM on the 228-CU MI300A (#12724) — as follows:

* FP64 now defaults to hipBLASLt on every gfx942 device (MI300X and
MI300A alike), rather than MI300A (228 CUs) only.
* FP16 and BF16 now default to hipBLASLt on the 304-CU MI300X only.
MI300A (228 CUs) continues to default to Tensile for FP16/BF16 in this
PR, but it will be changed by #13045.
* 
## Technical Details

`useHipBLASLt()` (`library/src/tensile_host.cpp`) previously routed only
`double` to hipBLASLt on gfx942, and only when `multiProcessorCount ==
228` (MI300A). The gfx942 branch for MI300x now applies FP16, BF16, and
FP64.

No change is needed in `isDefaultHipBLASLtArch()` (`handle.hpp`): `942`
was already added there by #12724, and that function only gates eager
hipBLASLt handle construction, not per-datatype routing.

`docs/conceptual/rocblas-design-notes.rst` is also updated. It
previously stated that hipBLASLt is the default "for problems on the
gfx12 architecture", which was already stale after #12424 and #12724
flipped FP64 on gfx950 and MI300A. It now carries a table of which
architectures default to hipBLASLt and for which data types.

## Test Plan

1. Confirm backend routing per datatype on gfx942 with
`ROCBLAS_LAYER=8`.
2. Run the GEMM gtest suite on MI300X for regressions.

## Test Result

### Backend routing (MI300X, gfx942, `ROCBLAS_LAYER=8`)

| Input type | Backend selected |
|---|---|
| `f16_r` | `rocblas_gemm_hipblaslt_backend` |
| `bf16_r` (via `gemm_ex`, compute `f32_r`) |
`rocblas_gemm_hipblaslt_backend` |
| `f64_r` | `rocblas_gemm_hipblaslt_backend` |
| `f32_r` | `rocblas_gemm_tensile_backend` (unchanged) |

### gtest

`rocblas-test --gtest_filter='*hipblaslt*:*non_hipblaslt*-*known_bug*'`:

| PASS | FAIL | SKIP | total | TEST_EXIT |
|---|---|---|---|---|
| 2,208 | 0 | 0 | 2,208 | 0 |

`./rocblas-test --gtest_filter=*gemm*f64_r*-*known_bug*`:

| PASS | FAIL | SKIP | total | TEST_EXIT |
|---|---|---|---|---|
| 163,672 | 0 | 44 | 163,716 | 0 |

` ./rocblas-test --gtest_filter=*gemm*f16_r*:*gemm*bf16_r*-*known_bug*`

| PASS | FAIL | SKIP | total | TEST_EXIT |
|---|---|---|---|---|
| 497,739 | 19 | 192 | 497,950 | 0 |

Those failed tests are all stress_gemm with ILP tests, which requires
appropriate ILP library link, so after I rebuilt the library, I reran
those failed tests as below, and all the tests PASSED.

`./rocblas-test
--gtest_filter=_/gemm.*stress*ILP64*f16_r*:_/gemm_ex.*stress*ILP64*f16_r*:_/gemm_ex.*stress*ILP64*bf16_r*`

| PASS | FAIL | SKIP | total | TEST_EXIT |
|---|---|---|---|---|
| 23 | 0 | 0 | 23 | 0 |

## Submission Checklist

- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.
JIRA ID : AIHPBLAS-5103

  ## Motivation

  Add some optimization to enhance the post loop performance.
The gfx1250 narrow-float (half/bf16/f8/bf8) store epilogue had two
avoidable
  stalls:

1. The alpha multiply ran as a bulk pass over *all* batch elements
before the
per-element saturation/pack/store loop, so element 0's store could not
issue
until every element's alpha was emitted — delaying the first
buffer_store.
With GroupLoadStore, the up-front alpha of the next batch also overwrote
the
previous batch's store source VGPRs before that store had read them,
which ATT
showed as a ~500-cycle `s_wait_alu depctr_vm_vsrc(0)` at the start of
every
     batch after the first.
  2. The fp8 (E4M3) pk8 saturation clamped each element with
`v_cmp_class + v_med3 + v_cndmask`, which was expensive in the NonEdge
path.

  ## Technical Details

  Two commits:

**perf(tensilelite): speed up gfx1250 fp8 epilogue saturation and
grouped stores**
- Saturation: add `v_maximumminimum_f32` (rocisa `VMaximumMinimumF32`,
StinkyTofu
opcode/map/logical-IR/unit test, adaptor shim, `HasVMaximumMinimumF32`
cap).
    `PackData_FLOAT8` pk8 now emits `v_pk_fma_f32 x*0+x` (inf→NaN) + a
`v_maximumminimum_f32` clamp, falling back to the old sequence without
the cap.
Matches `__HIP_SATFINITE` over all 2^32 inputs except the NaN sign of
+inf;
    `s_set_vgpr_msb` in the NonEdge path drops 1621→213.
- GroupLoadStore: emit one `s_wait_alu(va_vdst=0)` before the grouped
stores
    instead of one per element when `ExpertSchedulingMode > 0`.
- `incrementToNextRow`: skip the `s_lshl_b32` and use the stride SGPR
directly
    for 1-byte BPE and a single row.

**feat(tensilelite): add InterleaveAlpha=2 to fuse alpha into the
per-element pack**
- `InterleaveAlpha` gains value `2`: each element's alpha multiply is
deferred to
just before its own conversion+pack (in the store loop) instead of
scaling all
elements up front. Alpha still lands on the raw accumulator (before
beta*C,
bias, activation, pack), so it is numerically general across dest types
and
routes through the up-front alpha path like `0` — the win is register
lifetime,
    letting the first store issue after only element 0's work.
  - `ValidParameters`: `InterleaveAlpha` now `[0, 1, 2]`.
- `GlobalWriteBatch`: value `2` takes the up-front alpha path
(comparisons made
explicit — `!= 1` / `== 1` — since `2` is truthy);
`_interleaveAlphaWithPack()`
    fires on `InterleaveAlpha == 2` for any narrow-float output.
- `Solution`: matrix-instruction kernels reject only `InterleaveAlpha ==
1`
(`2` is allowed); the ExpertSchedulingMode support gate treats `2` like
`0`.
- `RequiredParameters`: add `InterleaveAlpha` to the kernel-name set.
Value `2`
produces different assembly than `0`, so without it in the name the `0`
and `2`
kernels collide and one is dropped as a duplicate (`Naming._getName`),
leaving a
    dangling `.s` and "named symbol not found" at launch.
- Adds
`Tests/common/gemm/gfx12/interleave_alpha2_group_store_gfx1250.yaml`,
which sweeps `NumElementsPerBatchStore [0,16] × GroupLoadStore
[False,True] ×
    InterleaveAlpha [0,2]` for bf16 and fp8.

  ## Test Plan

  - tensilelite tox
  - hipblaslt-test

  ## Test Result
  pass

  ## Submission Checklist
- [ ] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

  ## Risk level

Low–Medium. Scoped to the gfx1250 narrow-float store epilogue. The
saturation and
grouped-store changes fall back to the prior sequence when the new cap /
`ExpertSchedulingMode` is absent. `InterleaveAlpha=2` is opt-in (default
`0`
behavior is unchanged), but it does add `InterleaveAlpha` to the kernel
name,
which changes generated kernel names — relevant to any externally cached
library
  logic keyed by name.

---------

Co-authored-by: Serge Lu <Serge.Lu@amd.com>
## Motivation

Fix dgrad conv regressions after switch aot

## Technical Details
Depthwise 7x7 stride-1 dgrad — the AOT row-streaming kernel was
prefetching whole KH rows, blowing up to 377–512 VGPRs and spilling to
scratch. Ported the forward-depthwise tricks (ceil(16/n_cols) row
prefetch, i32 offset bounds instead of lane masks, empty-asm on
accumulators, FMA loop over ceil(Ho/KH) groups + flush-only tail).
Brought it down to ~180 VGPRs and ~2x faster; also added block_w=7 to
the dgrad block-width table.

Dense g=1 dgrad (direct MFMA path) — added channel caps
(_DENSE_MFMA_DGRAD_CHANNELS) so these cases probe with groups=1 instead
of failing to compile with block_groups>1; direct now beats igemm (0.117
vs 0.227 in one measured case).

Patchify dgrad (stride == f

## Test Plan

CI

## Test Result

Pending

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

JIRA ID: AICK-2527
## Motivation

Wgrad GEMMs usually have a filter-sized output (few output tiles) and a
very long reduction (N*Ho*Wo). Split-K cuts every tile the same number
of ways, so on these shapes it leaves CUs idle. This PR ports CK Tile's
stream-K to rocKE wgrad. Stream-K balances MAC iterations instead of
tiles:
- Tiles that fill the device run data-parallel.
- Only the remainder is spread across a pool of CTAs.

Stream-K is opt-in. The grouped-convolution dispatcher and its benchmark
are unchanged (they are due for a refactor), so no production path picks
stream-K on its own.

## Technical Details

- **Core IR:** three new leaf ops in both engines: `memref.fence`,
`memref.global_flag_store` and `memref.global_flag_wait_eq`.
- Each lowers to an LLVM fence, or to an atomic store/load with
release/acquire ordering at a chosen sync scope.
- The Python and C++ verifiers check arity, a global i32 flag pointer,
i32 index/value operands, and the allowed scope/ordering values.
- All four lowerers reject a missing or unknown scope/ordering with the
same message.
- **Partitioner:** `StreamKIterPartition` (Python) and
`rocke_streamk_iter_partition_t` (C++) mirror CK's DP + SK split.
- `iters_per_sk_cta` is clamped to at least 1, so an all-DP partition is
safe.
  - `flags_bytes(n_waves)` sizes the per-(CTA, wave) flag table.
  - The device-side emitters read the partition from runtime values.
- **Wgrad kernel:**
  - Launch modes: `streamk = dp_sk | persistent`.
- Reductions: `linear` and `tree` are deterministic and hand off through
per-(CTA, wave) flags. `atomic` (fp32 dW only) and `workspace` (f32
scratch + Stage-2 reduce) are the other two.
- Conv groups are folded into GEMM-M per group, so the fold is exact for
any wg_M. The C++ engine now builds grouped wgrad on the stream-K path.
- **AOT:** the partition is passed as an `sk_*` kernarg block, appended
after `ks_count` so existing kernarg offsets don't change.
  - Python side: `conv_abi` and `ConvArgs.streamk_partition`.
  - C++ side: `rocke_conv_arg_names_ex`.
- The CTA pool is a launch parameter, so one binary serves any problem
shape and any pool. `streamk_ctas = -1` resolves to the arch default
pool, in both engines.
- **Sweep / benchmark (opt-in):** `benchmark_implicit_gemm_conv.py`
(also reachable through `benchmark_conv_compare.py`) gains `--streamk
{off,dp_sk,persistent,all}` (default `off`), `--streamk-reductions` and
`--streamk-ctas`.
- Stream-K wgrad candidates are added to the JIT sweep, the
`--compile-all` AOT cache (`kernel_sweep.py`), and `--run-from-cache`.
- With stream-K off, the cache identity and kernel labels are unchanged.
- The stream-K launcher allocates the reduction's workspace and zeroes
what each reduction accumulates into. It shares the Stage-2 reduce with
the two-stage path.
- **Constraints:** gfx942 and gfx950 only, MFMA wave64. Both engines
reject the following with identical messages: split-K, two-stage, async
DMA, unroll, cshuffle, chiplet swizzle and group merge.

## Test Plan

- Byte identity across both engines at llvm20, llvm22 and llvm23.
- Differential parity: new wgrad configs 21–30 and negative configs
104–108.
- Representative-IR golden: all 8 `{dp_sk, persistent} × {linear, tree,
atomic, workspace}` combos (dense gfx950 plus grouped gfx942).
- CPU tests:
  - partitioner invariants
- validator agreement across both engines, including multi-cause rejects
and the arch gate
  - admits-implies-builds matrix
  - host plan and default-pool agreement between Python and C++
  - stream-K ABI and launch values
  - sweep launcher, including all-DP partitions
  - dual-engine lowering
- C++ ctests: `rocke_streamk_iter_partition`,
`rocke_wgrad_streamk_host`, `rocke_future_intrinsic_lowering` and
`rocke_ir_serialize_roundtrip`.
- GPU tests:
- numerics for both launch modes × all four reductions on dense, strided
and grouped shapes, including a partial last M tile
  - one binary launched on several shapes (AOT)
- 20-launch bit-exact repeat test for linear and tree, with partials and
dW poisoned before each launch
- flag ordering pinned in IR (acquire spin / release publish) and ISA
(cache invalidate / writeback)
  - sweep launcher on all-DP partitions (linear and workspace)

## Test Result

- Byte identity GREEN for all families at llvm20, llvm22 and llvm23.
31/31 wgrad configs pass differential parity.
- Representative-IR golden OK at all three flavors after merging
develop.
- CPU tests, C++-binding tests and ctests pass.
- GPU tests pass on gfx950. gfx942 is covered by the IR, golden and CPU
gates only.

JIRA ID : AICK-1943

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…grad on gfx950 (#12951)

## Summary

Depthwise backward-weight convolution has had merged-group (`Gm`)
support in the
kernel since #12359, but nothing ever asked for it. `group_merge`
defaults to 1
and `dispatch_conv_grouped` never set it, so the only ways to reach a
merged
kernel were to hand-construct a spec in a test or pass `--group-merge N`
on the
  benchmark CLI. Every production depthwise wgrad ran unmerged.

This PR adds the selection policy — `_wgrad_merge_degree()` — plus the
plumbing
that lets a non-default degree survive the trip from spec selection to
launch.
  No kernel capability is added or changed.

Why it matters: in wgrad the per-group GEMM is `kpg x (Z*Y*X*cpg) x
(N*Ho*Wo)`,
so depthwise (`cpg == kpg == 1`) makes the free axis one element wide
and every
B load scalar. Merging makes that run `Gm` elements long — `Gm` *is* the
load
  vector width.

  ## What changed

  `library/dispatch/grouped_convolution.py`:

- **`_wgrad_merge_degree()`** — the policy. Picks the largest degree the
kernel's
    own gate admits at the tile that already ships.
- **`ConvGroupedSpec.group_merge`** field, forwarded by
`to_wgrad_spec()`, and
emitted by `kernel_name()` as `gm{N}` — merging changes the GEMM, so
merged and
    unmerged kernels must not collide on the compile-cache key.
- **`_wgrad_grid()`** — z extent becomes `(groups / Gm) * split_k`;
without this
    we would launch `Gm x` too many CTAs.
- **`_resolve_wgrad_split_k()`** — split-K is chosen from GEMM dims that
merging
    scales, and merged + `split_k > 1` is routed to two-stage.

  The workspace sizing is deliberately left **unmerged**, mirroring
`wgrad_two_stage_workspace_nbytes`: merging changes how many CTAs write
`dW`, not
how much `dW` there is. Scaling it by `gm` would overstate the
allocation
  quadratically and start refusing two-stage on problems that fit.

  ### There is no fitted constant, and that is the result

The forward selector (#12793) is a fitted closed form with eight
constants.
Backward-weight ended up with none. Forward puts `Gm` on GemmN+GemmK,
trading
vector width against a working set growing in `Y*X*Gm` — a genuine
interior
  optimum. Wgrad puts `Gm` on GemmM+GemmN and the gate caps it at
`Z*Y*X*cpg*Gm <= tile_n`; with the tile fixed, the admissible set is two
or three
degrees wide and its top is nearly always best. Three richer policies
were fitted
and rejected; `score_policy.py` re-derives the rejection from the raw
sweeps.

  ## Measured evidence

Relative ratios only, per `platform/AGENTS.md` §Compliance. No absolute
latency,
throughput, or achieved-FLOP figure appears here or in any commit
message.

  - **Hardware target:** gfx950.
- **Workload / axes:** backward-weight (`-F 4`) grouped convolution,
depthwise
(`cpg == kpg == 1`), fp16 and bf16, NHWC. 197 distinct shapes — ~half
depthwise
stages from 35 published architectures (mobilenet v1–v4, efficientnet
b0–b6/v2,
convnext/convnextv2, replknet/unireplknet, shufflenetv2, ghostnet,
mnasnet,
xception, yolo11/12, …), ~half a stratified random grid including a
deliberate
    ~12% unmergeable minority.
  - **Baseline:** the configuration that shipped at this PR's merge-base
(`2489f0b9153`) — same tile, `Gm = 1`. Ratios are against that, not
against an
    oracle, because that is what the change is worth.
- **Method:** `measure_degrees.py`, 2 warmup + 10 timed iterations per
config,
tile pinned, degree axis fully open, split-K open, epilogue pinned to
two-stage
so the comparison isolates the degree. Both arms measured in the same
process
    on the same silicon.

At these measured configurations, against the previously shipped point:

  | | ratio |
  |---|---|
  | geometric mean over 197 shapes | **1.98x** |
  | median | 1.25x |
  | p90 | 7.12x |
  | max | 36.7x |
  | min | 0.97x |

62/197 shapes select `Gm = 1` and are bit-for-bit the previous
behaviour. 90/197
land within ±2%. One shape regresses beyond 2% (0.971x, `C128 K128 G128
5x5`).

Against the best degree available at the shipped tile, the policy
captures 0.998
of the oracle, and picks the exact best on 97 of the 135 shapes that
have more
than one admissible degree; of the other 38 the worst is 0.954x and none
falls
  below 0.95x.

**Honest losses:** the three rejected ladder policies all scored a
higher
geometric mean (up to 2.30x) by spending a wider `tile_n` — and all
carried
20–45 regressions out of 197, worst 0.462x. Buying the mean with a long
tail of
halvings was not a trade worth shipping. Details and the rejected fits
are in the
  case study.

  ## Correctness

- **Numeric gate in the measured run:** `--verify` was forced for every
config;
13,244 ranked configurations across three `tile_n` rungs, **13,244 PASS,
0
FAIL**. Checked against an independent torch reference at relative error
`< 5e-2` for 16-bit compute (`benchmark_implicit_gemm_conv.py:288`). No
    unverified kernel was allowed to set an oracle or a ratio.
- **On-GPU suite:** `library/tests/test_conv_wgrad_correctness.py` — 66
passed,
    346 subtests.
  - **Selection suite:** 512 passed, 2279 subtests across
    `library/tests/dispatch/`, `test_conv_wgrad_group_merge_gate.py`,
    `test_library_layering.py`.

`TestWgradMergeDegree::test_every_choice_is_admissible` is the
load-bearing gate:
at `split_k == -1` the kernel gate's split-K clause is vacuous, so a
degree the
gate refuses does not merely merge badly — `support()` rejects and
depthwise
wgrad stops dispatching entirely. That test replays every selected
degree through
the real `wgrad_group_merge_available`, so a clause that moves in the
kernel and
  not in the selector fails loudly.

The second commit pins a separate invariant: merged + `split_k > 1` must
be
two-stage. A merged tile computes a `Gm x Gm` block of group *pairs* and
wants
only the diagonal; the packed-atomic epilogue cannot mask off the rest,
so taking
it would accumulate an off-diagonal pair into a live `dW` element — a
wrong
  gradient with nothing raised.

  ## Reproducing

  All of it, from `examples/gfx950/conv_wgrad/merge_degree_model/`:

  ```bash
  python3 gen_shapes.py --count 200 --model-cap 100 -o shapes_wgrad.txt
  python3 measure_degrees.py shapes_wgrad.txt -o deg_tn64.csv
  python3 score_policy.py --shipped deg_tn64.csv

The corpus is checked in so results are reproducible; the sweep CSVs are
not —
they are raw per-kernel timings carrying absolute numbers.
score_policy.py
  degrades cleanly when they are absent.

  Narrative and the rejected fits: wgrad_merged_groups_case_study.md.

JIRA-ID : AICK-2486

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@AviralGoelAMD

Copy link
Copy Markdown
Author

Superseded: these commits were pushed directly to users/Parthiv911/rocke/gfx942-dense-cfvst-kdb-vpf (6e35a8a6d3a..e3ccb2e37ce) and are now on ROCm#13205.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment