Repository navigation
perf(rocke): build one K/V resource per page on the gfx942 2D i64 path - #14349
Conversation
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #14349 — one K/V resource per page on the gfx942 2D i64 path — 🟢 First-round: no closer look needed beyond AnaghaRaoAMD's two open threads — no other kernel's IR moves, both engines agree on the new loader, and taking out either half of the change makes its own check fail
No net-new findings. Utility: 🟢 Positive — the i64 K/V path on gfx942 2D no longer rebuilds a buffer resource and reloads a page id for every copy. It changes nothing on the i32 path or for any other kernel. Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good · ⚪ retracted First-round review: a local qwen3.6 model made the independent first pass. The orchestrator (Claude Opus 5.5) verified it. The checks were a regenerated golden for the merge and the native build and ctests. They also included byte identity for every family, a differential sweep of every anchored family against develop, and the new test with both engines in strict mode. Negative controls with develop's Python loader and develop's C++ twin, and the library CPU tests against develop, were run as well. Finally, they covered the CI logs at head. The orchestrator owns this merge-worthiness call. It flags whether a closer look is needed; it is not an approval or a rejection. |
Review checklist pass (rocKE PR-review checklist, two independent passes)Ran this PR through the team's rocKE PR-review checklist (byte-identity/two-engine parity, coverage/fallback behavior, DRY/blast-radius, dispatcher/spec integration, structure/hygiene, merge-readiness, verification honesty, paged/indirection safety, reusable design principles), then did a second independent pass focused on the addressing math and test coverage. No blockers in either pass. The core change is sound: addressing math is correct (page selection, in-page byte offsets, i64 base arithmetic all check out against the pre-existing stride/guard logic), the C++ twin's emit order matches Python exactly (verified op-by-op), and this is correctly scoped as an emitter-only fix (no selection/routing logic touched). Worth calling out explicitly: this also fixes a latent C++-only correctness gapBefore this PR, the C++ twin's non-fast paged-KV branch had no i64-addressing code path at all — it always built the plain i32 voffset regardless of MEDIUM
LOW
Open questions (not findings)
|
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #14349 — one K/V resource per page on the gfx942 2D i64 path — 🟢 Follow-up: no closer look needed — both of AnaghaRaoAMD's threads are fixed, the refactor leaves every parity config's IR unchanged, and the per-page resources cost 5 SGPRs while saving 22 VGPRs; one 🟡 doc line
Utility: 🟢 Positive — the i64 K/V path on gfx942 2D builds one resource per page instead of one per copy. With fewer VGPRs it is cheaper, and the unreachable fallback is now a clear error in both engines. Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good · ⚪ retracted Follow-up review: a local qwen3.6 model made the independent first pass. The orchestrator (Claude Opus 5.5) verified it. The checks were the IR of every parity config at the old head and on the merge, at three flavors, and out-of-envelope specs at both heads. They also included register and scratch use from gfx942 code objects against develop, the native build and ctests, byte identity, the golden gate, the structural test with both engines and the CI state. The orchestrator owns this merge-worthiness call. It flags whether a closer look is needed; it is not an approval or a rejection. |
|
@amd-khushbu Fixed
Measured, no change
Follow-ups, not in this PR (added to the description)
Open questions
|
With i64 K/V addressing, the gfx942 tiled 2D loader built a buffer resource for every K/V copy: each copy reloaded its page id, waited on that load and rebuilt the resource. Because vmcnt drains in order, those waits also drained the copies already in flight, which serialized the K/V stream for caches past the i32 buffer range. Load a tile's page ids once and build one resource per page, so each copy needs only its offset within the page. This applies when a tile holds whole pages and no copy crosses a page. The C++ twin gets the same loader, a gfx942 tiled 2D parity config pins the selector's i64 decode spec, and a representative-IR golden case is added; no existing hash changes and i32 paths emit unchanged IR. Co-Authored-By: Claude <noreply@anthropic.com>
Add test_attention_gfx942_kv_pages.py to the installed library GPU test list so CI runs it, and record its numeric cases in the GPU attention coverage map. The gfx942 CI lane has no torch, so the numeric test skips there and only the CPU structural check runs. Co-Authored-By: Claude <noreply@anthropic.com>
…gfx942 2D The spec requires tile_size to be a multiple of block_size, and supports_tiled_2d bounds a copy to at most one page, so every i64 K/V copy on the non-fast loader takes the per-page path. Gate per-page resources on I64_KV_ADDR alone, raise if a hand-built spec ever breaks those invariants, and remove the unreachable offset_i64_split branches from the K and V fallbacks, which are now i32-only. The C++ twin reports the same error. No emitted IR changes. Tighten the i64 decode test to atol=2e-3, below the output's spread.
The C++ twin held a tile's page pointers and resources in fixed 16-entry arrays and refused larger tiles, a limit the Python builder does not have. Allocate them from the builder arena per tile so both engines accept the same specs. Also update the use_i64_kv_addr field comment, which still described one resource per copy. No emitted IR changes. Co-Authored-By: Claude <noreply@anthropic.com>
Add gfx942 numeric cases for ragged sequence lengths whose last tile reads past the block table (the table's tail holds an unmapped page id), and for a cache just over 2 GiB whose pages straddle 2^31 bytes. Run the structural check over 1-warp/4-page, 4-warp/2-page and 1-page tiles. Add tiled 2D emit configs for those i64 geometries and for K copied by global_load_lds. The numeric bound adds a relative term for the bf16 rounding of large outputs on short sequences. Co-Authored-By: Claude <noreply@anthropic.com>
36fc391 to
866daeb
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Low-level GPU addressing changes need human confirmation of the reported numerical and Python/C++ parity results, especially because numerical tests skip in the stated CI lane.
No open issues 🎉
What changed in this PR
Optimizes rocKE’s gfx942 tiled 2D attention for large paged K/V caches by reusing one buffer resource per page instead of rebuilding it per copy, following the i64 addressing fix in #12446.
Changes:
- Implements per-page i64 K/V loading in Python and C++, including previously missing C++ i64 handling.
- Adds parity configurations, additive IR goldens, and structural and GPU numerical tests.
- Registers the tests with CTest and updates addressing and coverage documentation.
Paths below are relative to dnn-providers/hip-kernel-provider/rocke/.
| File | Description |
|---|---|
platform/tests/instances/rocke_ir_parity_harness.py |
Adds a representative i64 decode case. |
platform/tests/golden/rocke_representative_ir_sha256.json |
Adds hashes for the new case across three LLVM versions. |
platform/cpp/instances/gfx942/attention_tiled_2d_kv_descriptors_loads.cpp |
Implements matching C++ per-page resources and offsets. |
platform/CMakeLists.txt |
Registers the new test file with installed GPU tests. |
library/tests/test_attention_gfx942_kv_pages.py |
Tests resource counts, ragged sequences, and large-cache addressing. |
library/tests/parity/gfx942_attention_tiled_2d_emit.py |
Adds four Python i64 parity configurations. |
library/tests/parity/gfx942_attention_tiled_2d_emit.c |
Adds corresponding C parity configurations. |
library/kernels/gfx942/attention_tiled_2d.py |
Reuses page resources in the i64 K/V loaders. |
library/kernels/common/attention_unified.py |
Updates the addressing docstring and removes a stale performance claim. |
docs/gpu-attention-test-coverage.md |
Documents the new numerical cases and CI coverage limitations. |
Review effort: Balanced
CongMa13
left a comment
There was a problem hiding this comment.
No new issues found. I agree with the following existing comments:
- Make the whole-page i64 assumptions explicit and reject unsupported specs — AnaghaRaoAMD — #14349 (comment)
- Tighten the numeric test tolerance — AnaghaRaoAMD — #14349 (comment)
- Cover ragged table tails and real large addresses, and add coverage for alternate i64 geometries — amd-khushbu — #14349 (comment)
- Call out that GPU numeric tests skip in the torchless CI lane — copilot-pull-request-reviewer[bot] — #14349 (review)
- Update the
use_i64_kv_addrfield comment to describe per-page resources — cgmillette — #14349 (comment) - Track the K-sliced-ring i64 per-copy resource path as follow-up work — amd-khushbu — #14349 (comment)
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #14349 — one K/V resource per page on the gfx942 2D i64 path — 🟢 Follow-up 2: no closer look needed — every open item is fixed or listed under "Not in this PR". The new geometries are byte-identical ×3, the structural test fails on develop's per-copy loader in all three geometries, and no geometry spills. One 🟡 description line
Utility: 🟢 Positive — the i64 K/V path on gfx942 2D builds one resource per page. It needs fewer VGPRs on the decode and Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good · ⚪ retracted Follow-up review: a local qwen3.6 model made the independent first pass. The orchestrator (Claude Opus 5.5) verified it. The checks were:
The orchestrator owns this merge-worthiness call. It flags whether a closer look is needed; it is not an approval or a rejection. |
CongMa13
left a comment
There was a problem hiding this comment.
No new P0 or P1 issues were identified during this review. The changes are approved.
…ke/gfx1250-dgrad-depthwise Regenerate the IR golden after merging develop (#14349). This also refreshes stale kernel_name/bytes metadata for conv_direct_depthwise_tiled/gfx1250/dgrad_fp16_k51x5_w32 (missing the _p25x2 padding suffix); its IR sha256 is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TheRock Submodule Bump ActivityNewest first
|
Fixes #14474 # Summary: For human reviewers The golden file `llvm_gfx_all.json` is stale. The checks `rocke_differential_golden_ll` and `rocke_differential_golden_canonical` fail on `develop`. This PR records the golden again on `f37a7056c79`. The PR changes only `llvm_gfx_all.json`. ## What changed - **`target_intrinsics`**: #13723 changed the IR. The golden changes for llvm20, llvm22, llvm23 and canonical. - **`moe_fused_mega_fp8`**: #13976 changed the IR. The golden changes for llvm20, llvm22 and llvm23. - **`gfx942_attention_tiled_2d`**: The golden also changes for this family. The issue does not list it. The cause is not confirmed. ## Review request 1. Confirm the authors of #13723 and #13976 meant the IR changes. 2. Confirm the author of #14349 meant the `gfx942_attention_tiled_2d` change. The probable source is #14349. ## Verification - Before the change, the golden check failed on `develop` for llvm20 and llvm22. - After the change, `run_diff.py --mode ll --python-only --check-golden --flavor llvm20 --only target_intrinsics,moe_fused_mega_fp8` prints `GOLDEN OK`. - The full check for each flavor, the canonical check and the byte-identity gate did not run. CI runs them. # Summary: For AI reviewers - The diff changes one JSON file. No emitter code changes. - The command `run_diff.py --record-golden` wrote the file with `--python-only`. - Check the changed hashes only against the three families above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
… sliced-K ring, phase split and HBM-direct K/V to C++ JIRA ID : AICK-2712 ## Summary This is PR 5 of a stack of 7 that brings the rocKE C++ unified-attention engine to parity with Python (see the stack below). It sits on PR 4. The gfx942 fp16/bf16 flash prefill specs Python selects use the transposed-32x32 VALU stack (mask-limit, invariant hoist) and the sliced-K ring. The C++ builder rejected both, after PR 1 made it stop silently ignoring them. This PR ports them, plus the causal mask phase split and HBM-direct K/V, so C++ builds the production gfx942 flash specs byte-identically. ## Changes All paths are under `dnn-providers/hip-kernel-provider/rocke/platform/cpp` unless noted. - **Mask-limit and invariant hoist.** - The per-lane query-row invariants (and the ALiBi slope) are hoisted before the KV loop. - The mask-limit threshold compare honours `skip_mask`. - The ALiBi slope is picked as Python picks it; plain transposed + ALiBi previously failed to build. - The first parity configs to combine the production flash stack exposed other gaps on this path, fixed here: sinks seeding under transposed scalar state, the K single-buffer and split-cfvst branches, and C argument-order drift at four call sites. - **Sliced-K ring (depth 2 or 3).** - The transposed QK streams K in `k_slice_hd`-wide slices through `ring_depth` LDS slots, with `ring_depth - 1` slices in flight behind a partial `vmcnt` fence. - When a prefetch targets the slot the previous slice was read from, the slot is drained before reuse. The depth-3 ring uses the CK LdsSeq slot maps. - Slice geometries Python rejects are rejected the same way. - **Causal mask phase split, V/K from HBM.** `use_causal_mask_phase_split`, `use_v_hbm_direct` and `use_k_hbm_direct` are added to the shared spec, defaulted off; gfx950 ignores them. Their name tokens (`cmps`, `vhbm`, `khbm`) match Python. - **Gate:** the unified tiled-2D gate no longer rejects the gfx942 flash mask-limit and sliced-K ring cohorts. - **Stopgap: 64-bit KV offsets with the sliced-K ring.** - Develop (#14349) ported the gfx942 2D per-page i64 K/V loads. The ring's K slice loader still builds one i32 offset, so the C++ IR would differ from Python's and overflow at run time. - Until that is ported, the builder rejects `use_i64_kv_addr` with an active ring (NOTIMPL), and the 2D gate rejects those problems as "not ported to C++ yet". The gap is recorded in `known_gaps.md` §9. - A sweep of every i64 problem Python's 2D gate admits, run at the top of the stack: 810 specs, of which 558 are identical and the 252 ring specs are rejected. Co-Authored-By: Claude <noreply@anthropic.com>
…orward attention on gfx942/gfx950 (#14367) ISSUE ID : AICK-2772 ## Summary Forward-attention GPU-time optimizations on gfx942 and gfx950 for both rocKE KV layouts: the unified split-KV kernels (paged KV cache, plus non-paged strided-KV decode, which runs on the same 3D kernel) and the dense kernels (contiguous, non-paged Q/K/V). Measured against the 251-shape SDPA forward suite (decode, causal prefill, chunked prefill, sliding window with sinks, FP8 KV, head_size 192). Several changes build on #14216, #12714, #14276 and #14349; all four have landed on develop and this branch merges develop, so none of them appear in the diff. The results table below was measured at `afbf60ed85c`, before the latest develop merge; a refresh against the new develop is in progress. Python and C++ engines change together; byte-identity is green at every LLVM flavor. ## Optimizations **Decode (unified split-KV)** - Split-KV for GQA ratios that do not divide the 16-row q-block (e.g. 5, 6, 12), with padding-row store masking in the 3D kernel. - Route decode to the split-KV kernel when the 2D body is occupancy-bound (gfx942 and gfx950). - Head-size-256 decode split sizing (at least two pages per segment; about two workgroups per CU on gfx942), and a decode segment cap that counts one q-block per sequence. - LLC-aware K/V cache policy: stream K/V only when the per-call K+V read exceeds the last-level cache. **Dense prefill** - gfx942: 128-row query tile when the 256-row grid under-fills the device; transposed-V path with a NaN-free RNE pack for bf16 head_size 128. - gfx950 persistent body: softmax VALU trim (folded scale FMA, pair-reduced row sums) and K/V DMA overlapped with compute through LDS alias scopes (head_size 128). - Bottom-right causal on gfx942 dense, and a KV-segment split for dense prefill on both arches with a wave-filling segment count, so chunked prefill (Sq < Skv) runs on the dense kernels. - gfx942 sliding window: narrow-window geometry, a band-phase split, and a wave-uniform skip of fully masked tiles; the 128-row under-fill rule also covers head_size 64 and sinks. - gfx950 sliding window and sinks: the single-batch 32x32 combo admits windows and sinks at head_size 64/128, and dense windows get a 128-row default tile when the 256-row grid is not already full. - gfx950 persistent wide-DMA body: 128-row tile when the 256-row work count leaves the device under-filled, and default dispatch routes under-filled plain-causal head_size 128 to it. - gfx950 head_size 256 unified prefill: query blocks run longest-first, so the costly causal tail no longer finishes last. **Coverage and dispatch** - head_size 192 in the unified tiled kernels on both arches: single-batch combo on gfx950; on gfx942, prefill on the lean 4-warp body and decode with Q staged through the V LDS slot (3 workgroups per CU instead of 2). - FP8-KV prefill on the gfx950 single-batch 32x32 combo; FP8 prefill without a 2D kernel now takes the split-KV path instead of the scalar fallback. - Dense auto-selection for contiguous-layout prefill in `dispatch_attention`; contiguous-layout decode (Sq=1) now routes to strided split-KV decode, and the strided-decode support check follows the kernel gate, so GQA ratios 5/6/12 and head_size 192 dispatch too. **Fixes found along the way** - FP8 K/V scales are now forwarded through the explicit tuning binding. - The gfx950 3D page-id K/V loader handles partial-row copies (head_size 192). - The 3D K/V LDS swizzle is rejected when the chunks per row are not a power of two. - The dense KV-segment count is capped so the fp32 workspace index stays in int32 range. - The gfx942 lean 4-warp body (head_size 256 and 192) bounds every K/V, block-table and Q read to the sequence; odd-length sequences previously read past the cache. - Sink requests on the transposed path keep scalar softmax state, so sinks land on the right rows. - `DenseSpace.defaults()` fingerprints only the knobs a shape does not record, so a tuning ID replays across chunk shapes. ## Results rocKE against itself, GPU-only, geomean speedup of the faster of the unified and dense kernel per shape (higher is better). Each cell gives geomean / median speedup and the number of shapes faster / slower by more than 2%. All arms passed the on-silicon numeric check (max |error| <= 0.05 against an fp64 reference) in the measured runs. | Arch | Config | develop -> cherry-picks (#14276, #14349) | develop -> this PR | cherry-picks -> this PR | Newly covered shapes | |---|---|---|---|---|---| | gfx950 | tuned per-shape configs | +0.1% / +0.0% (faster 8, slower 6 of 240) | **+7.0%** / +2.5% (faster 128, slower 1 of 240) | **+6.9%** / +2.7% (faster 130, slower 2 of 240) | 9 | | gfx950 | default dispatch | +0.0% / +0.0% (faster 6, slower 7 of 240) | **+34.0%** / +7.7% (faster 160, slower 1 of 240) | **+33.9%** / +7.2% (faster 165, slower 1 of 240) | 9 | | gfx942 | tuned per-shape configs | +0.5% / -0.2% (faster 5, slower 15 of 238) | **+57.5%** / +19.1% (faster 170, slower 2 of 238) | **+56.6%** / +19.7% (faster 175, slower 1 of 238) | 11 | | gfx942 | default dispatch | +0.6% / +0.0% (faster 16, slower 25 of 238) | **+57.0%** / +18.3% (faster 170, slower 6 of 238) | **+56.1%** / +18.8% (faster 169, slower 3 of 238) | 9 | By workload, cherry-picks -> this PR (gfx950 / gfx942, default dispatch unless noted): - decode: +63.3% / +148.5% (tuned configs: +0.6% / +149.4%, see caveats) - causal prefill d128/d256: +7.5% / +19.5% (tuned configs: +10.4% / +19.5%) - prefill d64: +5.1% / -0.1% (tuned configs: +4.9% / +0.9%) - chunked prefill: +50.2% / +4.4%; with the dense kernel serving Sq < Skv on gfx942, +225.6% (2.2x to 6.1x on 9 shapes, measured separately) - sliding window and sinks: +49.4% / +21.6% (tuned configs: +3.5% / +24.0%) - FP8 KV prefill: +513% on gfx950 (2 shapes); on gfx942, 2 shapes newly covered under tuned configs - head_size 192: newly covered (9 shapes per arch: 4 prefill, 5 decode) - non-paged (strided-KV) decode, measured separately on all decode shapes: +7.7% (gfx950) / +12.4% (gfx942) geomean, plus 20 shapes the cherry-pick base rejects (GQA ratios 5/6/12 and head_size 192) **Method.** Each kernel call is captured 64 times in a HIP graph; 1 warm replay and 4 timed replays, upper median per shape. Three trees: develop (`98737a5c5f4`, the develop this PR merged), cherry-picks (develop plus #14276 and #14349, `8617b561496`), and this PR (`afbf60ed85c`). Per arch and config, all three trees ran one after another in the same job, on the same node; both gfx950 jobs ran on one node and both gfx942 jobs on another, one sample per cell. On gfx942 the four FP8 shapes are excluded from the default-dispatch rows, because develop and the cherry-picks run them on the scalar fallback. Software: torch 2.13 / ROCm 10.0 wheels, LLVM 23 comgr, `ROCKE_LLVM_FLAVOR=llvm23`. "Tuned per-shape configs" pins each shape's kernel configuration from a tuning run that predates this work; "default dispatch" calls `dispatch_attention` with no pinned configuration. **Caveats.** These results hold for the measured configurations only. Because the pinned per-shape configurations predate this work, they still select the earlier decode and chunked-prefill kernels on gfx950 (decode +0.6% tuned against +63.3% default); the default-dispatch rows show those gains, and re-tuning the configurations would expose them. On gfx942, the dense path for chunked prefill is reached only when the caller routes Sq < Skv to dense; the +225.6% figure uses such a caller. ## Verification - Byte-identity gate (Python vs C++ engine LLVM IR) GREEN at llvm20, llvm22 and llvm23, all families, with new emitter configs for the GQA-ratio-5 and head_size 192 3D paths. `test_rocke_ci_static.py` 5/5. - Python vs C++ lowerer parity (`test_attention_ir_cpp_parity.py`, with the `rocke_engine` extension built): every `attention_dense` and `attention_d256` case byte-identical at all three flavors. - CPU pytest after the develop merge: `library/tests -k "attention and not bwd"` 1961 passed, 3 failed; `library/tests/dispatch` 1593 passed, 2 failed. Two of the failures were `test_unified_lse_dispatch.py` config keys that moved with the K/V cache-policy change; they are re-pinned in the last commit, and that file now passes 31/31. The remaining failure (`TestMaskScalar::test_scalar_matches_reference[mixed-no_mask]`) also fails on develop. - Off-suite GPU parity stress on both arches (non-power-of-two GQA ratios with multi-block prefill, bottom-right offsets with KV segments, sliding-window boundaries, fully masked rows, large logits, FP8, head_size 192; at least 3 seeds): 0 failures, 0 NaN/Inf; positive controls with a deliberately shifted mask fail as expected. - Known pre-existing issue, unchanged by this PR: a comgr codegen failure for gfx950 head_size 256 with block_size 64. --------- Co-authored-by: Adam Osewski <Adam.Osewski@amd.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: dlangbe <David.Langbehn@amd.com> Co-authored-by: Anton Gorenko <anton@streamhpc.com>
…ime (#14530) JIRA ID : AICK-2676 The gfx942 tiled attention kernels (2D, 4-warp GQA, 3D split-KV and reduce) now read the head counts and the sliding-window size as kernargs and derive the `cu_q` search trip count from `num_seqs`. One compiled binary therefore serves every head pair at a given GQA ratio, every window size and every batch. This is the kernel-side readiness hipDNN AOT packaging needs: a packaged catalog ships one binary per kernel symbol and cannot enumerate head counts, window sizes or batch sizes. Follow-up to #13372 and #13889, which removed raw shape fields from the tiled and scalar cache keys. Builds on #14216 and #14349, merged from develop. The gfx950 counterpart (AICK-2675) reuses the plumbing added here. ## Risk Assessment Risk 4. Every gfx942 tiled kernel changes its argument list and emitted IR, the tiled cache keys change, and the paged K/V descriptor gains runtime strides in both engines. That is a performance-critical path. Mitigations: byte identity is GREEN at llvm20, llvm22 and llvm23 for every family; gfx950 and gfx1250 tiled IR is unchanged (golden); and the gfx942 numerics and an A/B against develop were run on hardware (see Testing). ## ASIC Coverage - **gfx942:** all kernel changes. Verified manually on gfx942: numeric tests, the existing gfx942 GPU suites, and a develop-vs-branch A/B. Superbuild and TheRock CI do not run these GPU numerics on gfx942. - **gfx950 / gfx1250:** host-only changes (signature helpers, cache keys, launch-cache keys). Their tiled spec classes declare no runtime kernargs, so their IR and goldens do not move. PR CI is sufficient. ## Testing Summary - **Byte identity:** full `check_byte_identity.py` GREEN at llvm20, llvm22 and llvm23. The gfx942 2D, 2D stress, 3D and reduce emit configs cover windowed, i64 and sinks specs. - **Goldens:** the representative-IR golden moves only for the gfx942 tiled cases (2D, 3D, reduce and both 4-warp kernels); the strided-KV library golden only for its gfx942 configs. Every gfx950 and gfx1250 entry is unchanged. - **Cache keys (CPU):** - shapes that differ only in head pair (same GQA ratio), window size or batch bucket share one key and lower to byte-identical IR at every flavor, on 2D, 4-warp D256, the 4-warp D128 fold and 3D; - the invariant sweep holds; - gfx950 and gfx1250 still split on the batch bucket. - **Launch signature (CPU):** every production builder route and three gfx942 tuning candidates are checked: the kernel's params equal its launch signature by name and type. - **One binary, many shapes (gfx942 GPU):** `test_attention_gfx942_runtime_shape_numeric.py` runs pairs of shapes that share a key back to back through `run_unified_attention_torch`. The second launch must compile nothing, and both outputs must match an fp32 paged reference (max_abs ≤ 4e-2; bf16/fp16 inputs, randn) with unused block-table slots on a poison block. - Pairs cover 2D default, windowed 2D, D256 4-warp, the D128 window fold, 3D decode and two-token 3D. One pair changes only the head counts at identical token counts. - Checked against mutants: baking the window back in fails the windowed pair (max_abs 0.88); baking the kv head count into the K/V stride faults on the device. - **Existing GPU suites (gfx942):** 69 attention, MLA and strided-KV library test files. Results match develop; the remaining failures fail identically on develop (MLA comgr environment errors, strided-KV and selector-parity cases). **A/B, develop vs this branch (gfx942, 304 CUs)** - **Workload:** 116 problems through `run_unified_attention_torch` with production routing (`backend="auto"`): 98 on the 2D path, 18 on 3D. Cohorts cover d64/d128/d256, bf16/fp16 and fp8 K/V, GQA ratios 1, 4 and 8, sinks, sliding window, the 4-warp D256 and fold paths, and a 64/16 head pair. - **Method:** HIP-event mean over 50 launches after 10 warmup, median over 3 rounds. Arm order alternates per round, and each arm runs in a fresh process each round. - **Toolchain:** torch 2.13.0+rocm10.0.0, LLVM 23 from the ROCm 10 SDK wheels, Python lowering. - **Baseline:** develop at `f37a7056c791`. - **Numeric gate:** every row of both arms is within 4e-2 of an fp32 reference; worst max_abs is 1.24e-2 in both arms. - **Results (branch / develop):** | Measure | Result | |---|---| | Compiled binaries per process | 18 vs 54 | | Total first-launch time | 0.37× | | Steady state, geometric mean over 116 problems | 0.994× (range 0.87–1.06×) | | Per cohort | within 0.98–1.01×, except the two below | | d64 GQA-8 block-32 **sinks** (2 problems, 2D) | 1.05–1.06× | | d64 GQA-8 **fp8 K/V** decode (4 problems, 3D) | 1.01–1.03× | - **Sinks caveat, isolated:** - Timing each commit puts the whole sinks cost in the head-count commit, and only on the narrow 2D kernel. - A timing-only variant with the K/V token stride restored to a constant removes it; restoring the query head count changes nothing. - The per-copy K/V offset now multiplies the token index by the runtime stride inside the KV loop, where the constant stride used to compile to a shift. - Accepted here as a known cost. Hoisting the in-page offsets out of the loop belongs with the planned per-page i32 K/V loader rework. **AOT dry run:** a draft engine manifest pinned to one gfx942 tuning candidate (`transposed_x8`, 4 warps, tile 4× block) expands with `registry_enumerator.py --plan-space`: 24 cells (6 head configurations × bf16/fp16 × block 16/32), production sweep, 20 varying knobs. Result: - 324 instances (within the default per-arch budget of 1000); - 162 distinct kernels on this branch against 324 on develop, because head pairs at one GQA ratio now build the same kernel. ## Testing Checklist - [x] Byte identity - `ROCKE_LLVM_FLAVOR=llvm{20,22,23} python tools/check_byte_identity.py` - Status: Passed - [x] Representative-IR golden - `python tests/instances/rocke_ir_parity_harness.py --check tests/golden/rocke_representative_ir_sha256.json` - Status: Passed - [x] Library host tests (attention, MLA, strided KV, dispatch, layering), GPU hidden - `python -m pytest <file>` per file - Status: Passed (3 files fail identically on develop) - [x] Platform dynamic-descriptor tests - `python -m pytest tests/core/test_transforms_dynamic.py` - Status: Passed - [x] gfx942 GPU: new runtime-shape numeric test - `python -m pytest tests/test_attention_gfx942_runtime_shape_numeric.py` - Status: Passed (14/14) - [x] gfx942 GPU: existing attention/MLA/strided-KV library suites (69 files) - Status: Passed, matching develop - [x] gfx942 GPU: develop-vs-branch A/B, 116 problems, 3 rounds - Status: Passed (numeric gate), caveats above ## Technical Changes - **Runtime-kernarg plumbing.** Tiled spec classes declare `RUNTIME_KERNARG_FIELDS`. `_tiled_2d_signature` / `_tiled_3d_signatures` append those fields to the launch signature, and `_runtime_kernarg_values` packs them. Every gfx942 launch site uses the arch-aware signature. The 4-warp GQA kernels now declare `qq_bias_stride_0`, which the shared 2D signature already passed. - **Search trip count.** `binary_search_seq_idx_trip_count` computes `bit_length(num_seqs)` from the `num_seqs` kernarg. It's exact below 2^24 sequences; past that it adds at most one no-op iteration. Specs declare `RUNTIME_SEARCH_ITERS` and the keys drop the batch bucket. The existing helper and its other callers emit the same IR. - **Sliding window.** The size is a kernarg. Whether a window applies still selects structure, so the name token is `sw` and the keys carry `window > 0`. - **Head counts.** `num_query_heads` and `num_kv_heads` are kernargs. The Q/O, segment-output and paged K/V strides are runtime values through `DynamicTensorDescriptor`, which now takes mixed int/Value strides plus runtime strides in `offset_i64_split` (both engines) and `offset_i64` (Python). Names carry `g<Hq/Hkv>`, and the keys carry the GQA ratio. Every selector that reads absolute head counts feeds an output that's already keyed. - **Launch caches.** `_shape_key` (2D grid, 3D grids and workspace) now carries both head counts, since one binary now serves several head pairs. - **i64 name token.** The gfx942 2D name carries `i64` when `use_i64_kv_addr` is set; i32 and i64 builds used to share a symbol. - **C++ twins.** The 2D, 3D and reduce twins mirror all of the above. The gfx942 2D C++ entry also builds the gfx950 fastKV register-P twin, so its runtime changes are gated to gfx942. - **CI enrollment.** `test_attention_tiled_launch_signature.py` joins the installed attention CPU cohort; `test_attention_gfx942_runtime_shape_numeric.py` joins the library GPU cohort. **ABI note:** in this repo, the gfx942 tiled kernels are launched only through `run_unified_attention_torch`, the dispatch tuning candidates and the harnesses updated here. Anything that launches them by name must append `num_query_heads`, `num_kv_heads` and `sliding_window` (2D and 3D) or `num_query_heads` (reduce). **Notes for the packaging work:** - The gfx942 fast paged-KV descriptor path (`use_fast_paged_kv_desc`, reachable only through the `fastkv` tuning knob) still hard-codes 64/8 heads, pinned by its spec validator. A packaged catalog must serve it only at 64/8 or leave that knob out. - gfx942 routes windowed decode to 2D, so production never takes the windowed 3D path; byte identity covers it. - `AttentionTuningSpec.cache_key()` remains field-complete, so it still splits on the runtime fields. That over-keys but is never wrong. ## Submission Checklist - [ ] Look over the contributing guidelines at https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests. --------- Co-authored-by: Claude <noreply@anthropic.com>
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout. With it, Q/O/K/V strides, the query head count, the sliding window and dense-or-ragged Q are kernel arguments; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout. With it, Q/O/K/V strides, the query head count, the sliding window and dense-or-ragged Q are kernel arguments; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout on top of the runtime shape kernargs the tiled 2D bodies already take (num_query_heads, num_kv_heads, sliding_window, and the cu_q search trip count derived from num_seqs). With it, the Q/O/K/V strides and a dense-or-ragged Q are kernel arguments too; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. The mode appends 15 kernel arguments after the runtime shape kernargs. Its window mask is always on, with the sliding_window kernarg as its size, and its ragged q-block search uses the shared runtime trip count (no iteration for a dense Q). K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. The runtime-layout checks take their head-count rule and text from the shared check_head_counts (rocke_attention_check_head_counts in C++). The rocKE C++ twin mirrors the mode op for op. rocke_attention_tiled_2d_spec_t gains runtime_layout, so ROCKE_ABI_VERSION and BINARY_ABI move to 10. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout on top of the runtime shape kernargs the tiled 2D bodies already take (num_query_heads, num_kv_heads, sliding_window, and the cu_q search trip count derived from num_seqs). With it, the Q/O/K/V strides and a dense-or-ragged Q are kernel arguments too; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. The mode appends 15 kernel arguments after the runtime shape kernargs. Its window mask is always on, with the sliding_window kernarg as its size, and its ragged q-block search uses the shared runtime trip count (no iteration for a dense Q). K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. The runtime-layout checks take their head-count rule and text from the shared check_head_counts (rocke_attention_check_head_counts in C++). The rocKE C++ twin mirrors the mode op for op. rocke_attention_tiled_2d_spec_t gains runtime_layout, so ROCKE_ABI_VERSION and BINARY_ABI move to 10. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
JIRA ID : AICK-2606 Partially addresses #14651 (the `TestKvAddrLimit` classification failure on develop). ## Motivation #12446 added a launch-time guard that rejects a paged KV cache larger than the compiled kernel can address. Some gfx942 K/V loaders form an i32 byte offset from the cache base even when the spec is built with `use_i64_kv_addr=True`. The guard caps those specs through `_I32_ONLY_KV_LOADERS`. The conflict-free V store (cfvst) loader is the capped loader that matters in production. It is the default V loader for gfx942 bf16 D64 and fp16 D64/D128 prefill (no sliding window). A sweep of the gfx942 default-route specs one block past 2^31 bytes found that cfvst is the only capped loader any shipped i64 spec selects. Its V offsets wrap past 2^31 bytes. Before #12446 that read wrong data or faulted the GPU; since #12446 the guard raises `ValueError` before launch. Dispatch has no fallback route, so a deployment whose per-layer K cache is over 2^31 bytes fails every prefill on these configurations. This PR gives the cfvst loader an i64 path that costs no more than the i32 path, and removes the cap. That lifts the cap for every shipped gfx942 configuration. ## Technical Details **cfvst V loader** (`library/kernels/gfx942/attention_tiled_2d.py`, `_cfvst_load_v_regs`) - Each cfvst load call covers a fixed range of tokens: 16 at D64 and 8 at D128 with 4 warps. On every geometry the launcher and the tuning space build, that range lies inside one page, and which page is known at build time. - Under `I64_KV_ADDR` the loader loads the tile's page ids once, builds one 64-bit page pointer per page, and indexes each V load with an i32 offset within the page. The per-lane select and divide stay i32, as on the i32 path. The pointers come from `_tile_page_ptrs`, split out of #14349's `_tile_pages` with the same op order. - The in-page byte offset, the kv head's byte offset and the `PAGE_RSRC_I64` rule come from `_kv_page_off`, `_kv_head_page_b` and the shared predicate, which the per-page K/V loaders use too, so a change to the paged K/V layout reaches cfvst as well. They use the runtime token stride from #15024. - A geometry whose load calls would span pages raises `ValueError` at build time. This follows the existing rule for K/V copies that straddle pages. No such geometry can launch: the launcher accepts block sizes 16, 32 and 64, and the tuning space offers 1, 2 or 4 warps. - It covers every load form: the vector `global_load_vN` path and the `HIPDNN_GFX942_CFV_STORE_SEPOFF` / `_SCALAR_LOAD` diagnostic paths. - The i32 cfvst loader emits the same IR as before. - An earlier commit in this PR used full per-lane i64 offsets (`offset_i64`). That was correct, but at fp16 D128, which already sits at the VGPR limit, it added register spills and made the i64 kernel noticeably slower than the i32 one. The page-pointer version replaces it. **Running-max carry on the transposed softmax path** (`attention_tiled_2d.py`, the loop yield) - After merging develop, the fp16 D128 i64 cfvst kernel was 1.3x slower than its i32 form. A bisect over the merged develop commits found #14542, which carries the raw running max. On the transposed path the raw max then stayed live through the whole body beside the clamped exponent reference. This kernel already sits at the VGPR ceiling, so the one extra value made the compiler route the per-tile compares through VCC one at a time. - The carry is now rebuilt at the yield as `new_l == 0 ? -inf : m_new`. The raw max is -inf exactly while the row has seen no key, which is when l is 0 (a seen key adds P = 1 at the max), so the carried value is the same and #14542's behaviour is kept. Testing l rather than the max compare matters: LLVM folds a rebuild from the compare back into the raw max. - This changes the IR of every gfx942 tiled 2D kernel on the transposed path, i32 included. The non-transposed paths and gfx950 are unchanged. **Scalar `global_load` lowering** (`core/lower_llvm.py`, `cpp/core/lower_llvm/mem.cpp`) - `global_load_typed` always wrote its GEP index as `i32`, so an i64 element index gave invalid IR. It now uses the index operand's own type, as `global_load_vN` already does. This was found while writing the first i64 loader. The final cfvst loader no longer needs it, but the lowering was wrong for any i64 index. Every existing caller passes an i32 index, so no existing kernel changes. **C++ twin** - The page-pointer cfvst loader and the same build error are mirrored in `attention_tiled_2d_conflict_free_v.cpp`. `rocke_gfx942_attn2d_tile_page_ptrs`, `rocke_gfx942_attn2d_kv_page_off`, `rocke_gfx942_attn2d_kv_head_page_b` and `rocke_gfx942_attn2d_page_rsrc_i64` are shared with the per-page K/V loaders. - The rebuilt running-max carry is mirrored in `attention_tiled_2d_kv_body_qk_softmax.cpp` and `attention_tiled_2d_kv_body_pv.cpp`, only when that body builds for gfx942. gfx950 fastkv_regp shares the C++ body but its Python (`gfx950/attention_tiled_2d.py`) still carries the raw max, so it keeps that carry. - No emit config covered cfvst before, and the twin did not match Python. It is now byte-identical: - It computes the per-tile values once and reuses the cached `max_seq_prefix_len`. - It places the split load and store where the transposed softmax issues them. - It fixes C argument evaluation order (unspecified, right-to-left in practice) in `v_t_slot`, the transposed PV token base, and the cfvst block guard and prezero loop. **Lift the cap** - `use_conflict_free_v_store` is removed from `_I32_ONLY_KV_LOADERS` and classified as i64-safe in `test_every_tiled_flag_is_classified_for_kv_addressing`. - `test_gfx942_cfvst_meta_has_no_limit`, `test_limit_memo_is_keyed_per_kernel` and `TestGfx942RouteGuard.test_cfvst_route_launches_past_2gib` now expect no limit. The route test launches at 65535, 65536, 65537 and 69632 blocks and checks the spec is i64 cfvst. - `use_additive_bias` (#13120) is also classified (`_NO_KV_ADDRESS_FLAGS`). The classification test already failed on develop without it. **Byte-identity coverage** - `library/tests/parity/gfx942_attention_tiled_2d_emit.{py,c}`: cfg18 is i32 cfvst and cfg19 is i64 cfvst, both on the bf16 D64 GQA 32/8 prefill geometry. The shipped spec also sets the sliced-K ring, direct-Q-global and the mask-limit stack. The C twin does not port those yet, so the configs leave them off. They still cover the new V loader code. - `llvm_gfx_all.json`: cfg18/19 blessed at llvm20, llvm22, llvm23, llvm24 and in canonical IR. The running-max change re-blesses the 18 transposed-path configs of `gfx942_attention_tiled_2d` and `gfx942_attention_tiled_2d_stress`; no other family moves. - Representative-IR golden: only `attention/gfx942/2d_lse_transposed32_bf16_d128` moves, from the running-max change. **Tests and docs** - `platform/tests/core/test_global_load_index_width.{py,cpp}` check the GEP index width; the C++ one is a host CTest. - `library/tests/test_gfx942_cfvst_i64_kv_addr.py` (CPU): - every V load of the shipped i64 cfvst spec indexes a page pointer with an i32 offset, including both diagnostic forms; - the i32 spec keeps i32 offsets from the cache base; - a page-spanning geometry is refused; - every cfvst geometry in the gfx942 tuning space builds with i64. - `library/tests/test_gfx942_cfvst_i64_kv_addr_numeric.py` (gfx942 GPU lane): a CPU routing anchor per shipped shape, plus an on-GPU check for bf16 D64, fp16 D64 and fp16 D128. Each runs `run_unified_attention_torch` on a 2.125 GiB GQA 32/8 causal cache with every block-table entry above 2^31 bytes, against an fp32 reference. It gates on `get_device_arch() == "gfx942"`. - `library/tests/test_attention_unified_masked_tile_ir.py` accepts the rebuilt carry in its exact form and still rejects a clamped carry. - CI lanes: `test_gfx942_cfvst_i64_kv_addr.py` runs in `rocke_library_attention_cpu_pytest`, and the numeric file in the library GPU lane. `rocke_global_load_index_width` is registered in `tests/cmake/CoreTests.cmake` and listed in the quick and ffm-quick tiers of `ROCKE_ENGINE_test_categories_external.yaml`. - `builders/gfx950/attention/README.md`: describes the cfvst page-pointer addressing; the paged-cache K/V limit list no longer names cfvst. **Out of scope (follow-ups)** - The other `_I32_ONLY_KV_LOADERS` entries: cfv, vhbm and khbm on gfx942, and fp8-PV on gfx942 and gfx950. No shipped spec selects them under i64. - Under i64 the sliced-K ring K loader still builds a buffer resource per copy, the pattern #14349 replaced in the other K/V loaders. - `global_store_typed` still writes its GEP index as `i32` in both engines, so an i64 store index would give invalid IR. No caller passes one. - Running `test_selector_parity.py` in a CI lane (the rest of #14651). - Replacing the hardcoded `0x7FFF0000` K/V `num_records` with one named limit (changes IR and goldens). - Exposing the cache-size limit to `supports_native_unified_attention*`. - Porting the sliced-K ring, direct-Q-global and the mask-limit stack to the gfx942 C twin, so an emit config can match a full shipped spec. ## Test Plan CPU: - Byte-identity gate at every flavor: `ROCKE_LLVM_FLAVOR=llvm20|llvm22|llvm23|llvm24 python tools/check_byte_identity.py` - Differential golden, as CI runs it: `run_diff.py --mode ll --python-only --check-golden --flavor llvm20,llvm22,llvm23,llvm24` `run_diff.py --mode ir --canonical --python-only --check-golden` - Representative-IR golden: `rocke_ir_parity_harness.py --check tests/golden/rocke_representative_ir_sha256 --flavor <flavor>` at each flavor. - `test_selector_parity.py`, `test_gfx942_cfvst_i64_kv_addr.py`, `test_attention_unified_masked_tile_ir.py`, `test_attention_gfx942_kv_pages.py`, `test_global_load_index_width.py` and the attention spec/selector suites, with the GPU hidden. - `rocke_global_load_index_width` CTest. gfx942 GPU (torch 2.13.0+rocm10.0.0 wheels, Python lowering): - `test_gfx942_cfvst_i64_kv_addr_numeric.py`. - #14542's `test_attention_unified_2d_masked_tile_numeric.py` and `test_attention_unified_scalar_masked_tile_numeric.py` (fully masked first tile, NaN rows), plus `test_attention_tiled_runtime_shape_numeric.py` and `test_gfx942_4wgqa_bounds_numeric.py`. - `builders/gfx942/attention/prefill/parity_unified_attention.py --scenario default`. - Kernel resources (VGPR and SGPR counts, spills) from the code object notes of the shipped cfvst kernels, i32 and i64. - Bisect of the fp16 D128 i64 slowdown: each develop commit merged into the pre-merge head, compiled, and its SGPR count read; the boundary then timed. - Performance on the shipped prefill shapes, i32 vs i64 on this branch. The workload is 2 sequences of 4096 tokens, causal, GQA 32/8, 32-token pages. - Both arms run the same small cache. The i64 arm only claims a larger block count, which selects the i64 spec. - Each arm is first checked against an fp32 reference (declared gate max_abs ≤ 4e-2). - Time is the median of 50 launches after 10 warm-up launches, measured with HIP events, over two repeats. ## Test Result - Byte-identity (`check_byte_identity.py`, every family): GREEN at llvm20, llvm22, llvm23 and llvm24, including cfg18/19. - Differential golden: GOLDEN OK at every flavor and in canonical IR. Representative-IR golden: OK at all four flavors. - CPU tests pass. The new CPU tests fail on develop and pass here. - gfx942 numeric, at this head: all three shipped shapes pass on caches past 2^31 bytes (bf16 gate 4e-2, fp16 gate 2e-2). #14542's masked-tile tests pass, including the NaN rows on the transposed path. - gfx942 parity (default group): 22 PASS, 6 SKIP, 0 FAIL. The skips are D256 shapes over the LDS budget. - Kernel resources: the fp16 D128 i64 kernel uses 252 VGPRs and 101 SGPRs with no spills; before the running-max change it used 254 and 49. - Performance, at these measured configurations, as time ratios. Every arm passed the numeric gate (max_abs 7.9e-3 bf16, 1.1e-3 fp16). | shape | branch i64 / branch i32 | fp16 D128 i64, running-max change / without it | |---|---|---| | bf16 D64 | 1.01–1.02 | | | fp16 D64 | 0.99–1.00 | | | fp16 D128 | 0.95 | 0.72 | The running-max change leaves the i32 kernels' times unchanged within run-to-run noise. ## Submission Checklist - [ ] Look over the contributing guidelines at https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests. --------- Co-authored-by: Claude <noreply@anthropic.com>
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout on top of the runtime shape kernargs the tiled 2D bodies already take (num_query_heads, num_kv_heads, sliding_window, and the cu_q search trip count derived from num_seqs). With it, the Q/O/K/V strides and a dense-or-ragged Q are kernel arguments too; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. The mode appends 15 kernel arguments after the runtime shape kernargs. Its window mask is always on, with the sliding_window kernarg as its size, and its ragged q-block search uses the shared runtime trip count (no iteration for a dense Q). K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. The runtime-layout checks take their head-count rule and text from the shared check_head_counts (rocke_attention_check_head_counts in C++). The rocKE C++ twin mirrors the mode op for op. rocke_attention_tiled_2d_spec_t gains runtime_layout, so ROCKE_ABI_VERSION and BINARY_ABI move to 10. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout on top of the runtime shape kernargs the tiled 2D bodies already take (num_query_heads, num_kv_heads, sliding_window, and the cu_q search trip count derived from num_seqs). With it, the Q/O/K/V strides and a dense-or-ragged Q are kernel arguments too; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. The mode appends 15 kernel arguments after the runtime shape kernargs. Its window mask is always on, with the sliding_window kernarg as its size, and its ragged q-block search uses the shared runtime trip count (no iteration for a dense Q). K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. The runtime-layout checks take their head-count rule and text from the shared check_head_counts (rocke_attention_check_head_counts in C++). The rocKE C++ twin mirrors the mode op for op. rocke_attention_tiled_2d_spec_t gains runtime_layout, so ROCKE_ABI_VERSION and BINARY_ABI move to 10. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
…ed attention Adds UnifiedAttention2DTiledSpec.runtime_layout on top of the runtime shape kernargs the tiled 2D bodies already take (num_query_heads, num_kv_heads, sliding_window, and the cu_q search trip count derived from num_seqs). With it, the Q/O/K/V strides and a dense-or-ragged Q are kernel arguments too; the page tables and seq lens are read on device; a grid-stride loop covers every q block; page-table and K/V reads stop at seq_len; rows with no visible key write zeros, and so do the query rows a sequence owns past seq_lens_q (padding), as the hipDNN contract requires. The mode appends 15 kernel arguments after the runtime shape kernargs. Its window mask is always on, with the sliding_window kernarg as its size, and its ragged q-block search uses the shared runtime trip count (no iteration for a dense Q). K pages through block_tables_ptr and V through the appended block_tables_v_ptr, which shares block_table_stride and may be the same pointer, so cuDNN-style graphs with separate K and V page tables are served. K/V use one 64-bit buffer resource per page, built once per tile load from one page-table read: a runtime_layout tile never spans more than one page. On gfx942 this is the per-page mechanism of the i64 path (#14349), which the runtime_layout loaders sit beside. runtime_layout=False emits unchanged IR. Additive bias (gfx950), the optional LSE output (emit_lse) and the gfx950 longest-first query-block order (use_qblock_reverse) are not combinable with runtime_layout and are rejected by the runtime-layout checks; the gfx942 4-warp GQA builder refuses a runtime_layout spec. On gfx942 the support gate and both builders (Python and C++) charge runtime_layout_lds_bytes against ArchTarget lds_capacity_bytes and refuse a spec over it. The runtime-layout checks take their head-count rule and text from the shared check_head_counts (rocke_attention_check_head_counts in C++). The rocKE C++ twin mirrors the mode op for op. rocke_attention_tiled_2d_spec_t gains runtime_layout, so ROCKE_ABI_VERSION and BINARY_ABI move to 13. Dispatch never sets runtime_layout: it is a semantic field (no sweep varies it), and while it is off it stays out of the tuning spec's cache key and config defaults, so every spec_hash, config_key and stored pin is unchanged. The CPU checks of the two runtime_layout test files run in the new rocke_library_attention_runtime_layout_pytest CTest entry (-m "not gpu"); their GPU numerics run in rocke_library_gpu_pytest. The scenarios include sequences with 0 < seq_len < q_len, whose leading rows see no key.
JIRA ID : AICK-2717
Motivation
#12446 made paged K/V caches past the i32 buffer range (
> 0x7FFF0000bytes) use i64 K/V addressing, which fixed silent zero reads of the cache's top 64 KiB. On gfx942 the tiled 2D i64 path turned out much slower than the i32 path. For bf16 D128 decode on exactly-2-GiB caches (B=256, page size 16; the decode shapes of Llama-3-8B with Skv=4096 and GQA 32/8, Llama-3-70B with Skv=4096 and GQA 64/8, and Qwen3-235B-A22B with Skv=8192 and GQA 64/4), develop's i64 path takes 1.41x to 1.43x as long as its i32 path (measured as described under Performance).Cause, from the gfx942 ISA: with i64 addressing the K/V loader built a buffer resource for every copy (192 per kernel for these shapes). Each copy reloaded its page id from the block table, waited for it (
v_readfirstlaneneeds the value), and rebuilt the resource. Becausevmcntdrains in order, each of those waits also drained the K/V copies issued before it, so the K/V stream ran with little memory-level parallelism. A 64-token tile spans only 4 pages.Technical Details
Changes
kernels/gfx942/attention_tiled_2d.py: when i64 addressing is on, a tile holds whole pages (T % BS == 0), and no copy crosses a page ((BS * HD) % KV_HALVES_PER_CALL == 0), the K and V loaders load the tile's page ids once (_tile_pages: one masked block-table load and onereadfirstlaneper page) and build one resource per page. Each copy takes its page's resource and an offset within the page (_page_voff). Other cases, the fast paged descriptor and the i32 path are unchanged.attention_tiled_2d_kv_descriptors_loads.cpp): the same loader, with builder calls in Python's evaluation order. The comments that said i64 never reaches the non-fast loader are corrected._enable_i64_kv_addrdocstring: drops a stale measured-cost claim.Behaviour change
Coverage
gfx942_attention_tiled_2d_emit.{py,c}), byte-identical at llvm20/22/23. Develop's C++ loader drifts on exactly cfg7-10:global_load_lds.attention/gfx942/2d_bf16_d128_b16_decode_i64(additive diff only).tests/test_attention_gfx942_kv_pages.py:atol=2e-3, rtol=2**-8(the relative term covers bf16 rounding of large outputs on short sequences):num_kv_blocksoverstated to force i64). Fails if the in-page offset drops the kv-head term.docs/gpu-attention-test-coverage.md. The gfx942 CI lane has no torch, so the numeric cases skip there and only the structural check runs.Not in this PR
block_table_max_idx) but not checked against the cache's block count before they form a 64-bit base, the same as the i32 path and the fast descriptor. Bounding them needs the block count as a kernel argument, which is an ABI change.readfirstlane; the per-copy waits are gone.Performance
Relative results only. gfx942, ROCm 10.0.0 wheels (torch 2.13.0+rocm10.0.0, LLVM 23 from the ROCm SDK, the attention suite's default toolchain), one idle GPU. Each arm: 5 warmup launches through
run_unified_attention_torch, then the median of 5 batches of 50 launches timed with HIP events. Correctness gate in the same run: max_abs against an fp32 PyTorch reference on sampled sequences, tolerance 1e-3; every arm passed with matching max_abs (2.8e-4 to 3.4e-4). Page tables were tested contiguous and random; the ratios below hold for both.Decode on an exactly-2-GiB cache (B=256, D128, bf16, page size 16), branch on the i64 path:
The third column is a near-equivalent comparison: the i32 arm uses a cache two pages smaller, the largest i32 can address, with the same kernel otherwise.
Other shapes on the i64 path (forced by overstating
num_kv_blocks, median of 5 batches of 20 launches, same correctness gate), branch vs develop: prefill B=4 S=1024 GQA 32/8 1.21x faster; prefill B=1 S=4096 GQA 64/8 1.30x faster; decode B=64 Skv=8192 GQA 64/8 2.07x faster. These are diagnostic configurations, not shipped cache sizes.Test Plan
ROCKE_LLVM_FLAVOR=llvm23 python rocke/platform/tools/check_byte_identity.py: GREEN.python rocke/tools/run_checks.py --steps gate,parity(llvm20 + llvm22): GREEN.gfx942_attention_tiled_2dcfg7 drifts.rocke_ir_parity_harness.py --check: OK before the new case; after re-bless the golden diff only adds the new case and count bumps.tests/test_attention_gfx942_kv_pages.pyon gfx942 with ROCm 10.0.0 wheels (LLVM 23) and torch 2.10.0+rocm7.0 (LLVM 20): 3 passed. Structural test fails on the old loader; numeric test fails with a broken in-page offset.library/testson CPU, one pytest per file with the GPU hidden (backward attention excluded): 169 files pass; the 6 failing files (test_strided_kv_decode,test_strided_kv_lds,test_kda_hostpack,test_conv_abi,test_conv_dgrad_patch, and the gfx950use_additive_biascase intest_selector_parity) fail the same way on develop.platform/tests/test_rocke_ci_static.py: 5 passed.Submission Checklist