Skip to content

fix(rocke): reach _resolve_attention_arch through the module handle - #10508

Merged
AnaghaRaoAMD merged 6 commits into
developfrom
users/anarao/fix-attention-arch-patch-seam
Aug 17, 2026
Merged

AnaghaRaoAMD merged 6 commits into
developfrom
users/anarao/fix-attention-arch-patch-seam

Conversation

@AnaghaRaoAMD

@AnaghaRaoAMD AnaghaRaoAMD commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

ISSUE ID : AICK-1898

Problem

attention_spec_builder.py bound-imported _resolve_attention_arch. A from ... import sym freezes the reference at import time, so mock.patch.object(au, "_resolve_attention_arch", ...) never reached the builder. It then resolved the real device arch, _tiled_2d_impl returned that arch's spec class, and the gfx950-only override fields hit:

TypeError: UnifiedAttention2DTiledSpec.__init__() got an unexpected keyword argument 'use_q_direct_reg'

Only fires when the builder is already in sys.modules before the patch, so it looked like flaky ordering. Pre-existing on develop; introduced by #9233, which added the _kau module handle and a comment about this exact hazard but applied it to _d256_gfx950_fast only.

Fix

Route both call sites through the existing _kau module handle. Import plumbing only — no emission or spec change.

Four follow-up commits from @aosewski (756dc0e3..55c3ecb4): 818c75f repairs rocke_ir_parity_harness.py, which read the removed attribute and took rocke_golden_static red with 12 drift entries; adfc04a contains _RESOLVED_ATTENTION_ARCH leaks via a conftest fixture; 7c47e74 adds test_arch_binding_guard.py (verified to fail on the unfixed builder); 55c3ecb makes the resolver test reproduce both preconditions in-test.

Verification

pytest library/tests -m "not gpu" on gfx942:

result
develop @ 223bff09f90 5 failed, 499 passed, 34 skipped
this branch @ 55c3ecb 507 passed, 34 skipped

Also at head: platform/tests 668 passed, golden test_rocke_ci_static.py 5 passed (IR parity OK [llvm20, llvm22, llvm23], no re-bless), and 520 passed / 0 failed on a gfx942 MI300X node.

test_attention_ir_cpp_parity.py is the dual-engine gate for these families; it skips where rocke_engine isn't built, so it did not execute in the runs above.

Side effect

prove_parity_binding.py patches the resolver at :1465 but imports _tiled_spec_from_problem at :1431, so run_selector_parity(arch=...) previously compared a Python spec for the host arch against a C++ spec for the requested arch. Fixed as a side effect, but earlier selector-parity numbers aren't comparable and masked mismatches may surface — worth a re-run across arches.

Two adjacent defects are out in #10625, not here: the parity gate's skip-on-first-case behaviour, and a stale parity_unified_attention path missing its .prefill segment.

`builders/common/attention_spec_builder.py` bound-imported
`_resolve_attention_arch` from `kernels.common.attention_unified`, which
froze the reference at import time and silently defeated
`mock.patch.object(au, "_resolve_attention_arch", ...)`.

With the patch bypassed, `_tiled_spec_from_problem` resolved the REAL
device arch, `_tiled_2d_impl` handed back that arch's spec class, and the
gfx950-only override fields (e.g. `use_q_direct_reg`) raised TypeError.
The failure only triggered when this module was already in `sys.modules`
before the patch was applied, so it presented as flaky test ordering.

Route both call sites through the existing `_kau` module handle -- the
same handle, and the same reason, already documented one line above for
`_d256_gfx950_fast`. Import plumbing only; no emission change.

Fixes 5 failures in `library/tests/test_lds_budget_resolver.py`:
  before: 5 failed, 499 passed, 34 skipped, 208 subtests passed
  after:            504 passed, 34 skipped, 208 subtests passed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AnaghaRaoAMD
AnaghaRaoAMD requested review from a team as code owners August 7, 2026 00:36
@github-actions github-actions Bot added project: hip-kernel-provider rocke-lib: attention Touches rocKE library attention/FMHA kernels labels Aug 7, 2026
@therock-pr-bot

therock-pr-bot Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

therock-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

@AnaghaRaoAMD AnaghaRaoAMD changed the title fix(rocke): reach _resolve_attention_arch through the module handle fix(rocke): reach _resolve_attention_arch through the module handle [AICK-1898] Aug 7, 2026
@AnaghaRaoAMD AnaghaRaoAMD changed the title fix(rocke): reach _resolve_attention_arch through the module handle [AICK-1898] fix(rocke): reach _resolve_attention_arch through the module handle Aug 7, 2026

@aosewski aosewski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanism here is right, and it reproduces cleanly: pre-import the builder, set au._RESOLVED_ATTENTION_ARCH = "gfx942", then run test_lds_budget_resolver.py — 3 failures + 2 errors before this change, 9/9 green after. Your point about the memo also holds up under a two-factor check: with a cold memo on a real gfx942 the 5 failures still appear, and with a polluted memo on a non-gfx942 host they don't. So the bound import is the whole mechanism and AICK-1898's title is indeed misnamed.

A few things worth a second look before this goes in.

The verification didn't cover the lane that this change affects. library/kernels/ having no C++ mirror isn't quite the relevant axis — library/tests/test_attention_ir_cpp_parity.py is the dual-engine gate for these kernels (_FAMILIES = ("attention_dense", "attention_d256")), and the golden-SHA gate lives in platform/tests, which pytest library/tests never collects. On a box without rocke_engine built, the parity test skips, so that run produced no evidence either way. Worth re-running pytest platform/tests/test_rocke_ci_static.py and the parity gate with the engine built. Good news: the change is genuinely IR-neutral — I re-lowered both attention_d256 cases against the committed golden and got 6/6 SHA matches across llvm20/22/23, so no re-bless is needed.

test_attention_ir_cpp_parity.py:68 masks failures. The pytest.skip sits inside the per-case loop, so the first case with an unavailable C++ lowering aborts the whole gate. The 18 attention_dense cases are ordered ahead of the 2 attention_d256 ones, so anything wrong with d256 is unreachable in practice. Collecting unavailability per case and skipping only if all were unavailable would make it honest.

The test_attention_builds.py failures are still there. Two of them (TestAttentionHarnessTimers) fail identically on the base commit and here: builders.gfx950.attention.parity_unified_attention is missing the .prefill segment, so find_spec returns None. Same lead the ticket flagged. They're not this PR's doing, but they're AICK-1898 deliverable 3 and they keep the lane red — worth either the one-word path fix or a linked ticket.

Minor: the description carries internal Jira URLs and the ticket ID. platform/AGENTS.md asks for internal tracker links to stay out of public artifacts; the commit message itself is clean, so it's just the PR body. The GitHub refs are fine to keep.

aosewski and others added 4 commits August 11, 2026 09:37
The harness read `asb._resolve_attention_arch` to pin the arch the D256
fast routes gate on. #10508 removed that bound import from the spec
builder, so `_pinned_attention_arch` now raises AttributeError and both
`attention_d256` cases drop out of the golden set -- `rocke_golden_static`
goes red with 12 drift entries.

Drop the `asb` half and additionally pin `_RESOLVED_ATTENTION_ARCH`, the
process-wide memo `_resolve_attention_arch` returns before doing anything
else. Pinning the memo makes the pin independent of *how* a consumer
reaches the resolver, which matters here specifically: the harness is
installed standalone (platform/CMakeLists.txt) and run by
`rocke_installed_golden_test.py` without the library test tree, so it
cannot delegate that invariant to a guard test.

No re-bless: IR parity OK [llvm20, llvm22, llvm23].

Follow-up to #10508.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`attention_unified._RESOLVED_ATTENTION_ARCH` memoizes the real device
arch, and a dozen tests pin it directly. One that leaves it set changes
what every later test resolves, which shows up as order-dependent
failures on a GPU runner and not at all on a CPU one. An autouse fixture
restores it and clears the derived `_2D_LAUNCH_META` memo, making the
leak class impossible instead of relying on each test's own teardown.

The module is looked up in sys.modules rather than imported, so a test
with nothing to do with attention does not pay for the import -- and an
import regression in `attention_unified` stays an attention failure
instead of erroring every test in the tree during fixture setup.

Env overrides are deliberately left out of the fixture: a blanket
snapshot would mask an env leak rather than fix it. The one real leak,
in the GPU-gated D128 ring test, is fixed at its source by switching
`os.environ.pop` to `monkeypatch.delenv`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing in the language stops the spec builder from re-acquiring a bound
import of `_resolve_attention_arch`, and the regression is silent: the
builder falls back to the real device arch and the test measures the
host. Assert it once, statically (`hasattr`) and behaviourally (two
archs across both the 2D and 3D builder entry points, each of which
holds its own call site).

Two archs, not one, because the resolver falls back to "gfx950" with no
device and `_tiled_2d_impl` maps every arch except gfx942/gfx1250 onto
the gfx950 impl. Which pin catches the regression therefore depends on
the host: on a gfx90a box the gfx942 pin fails, on a gfx942 box the
gfx950 pin fails. Neither alone is a guard; the pair is.

With the invariant centralized, the per-test `getattr`-guarded
dual-module patch scaffolding in `test_selector_parity` and
`test_attention_builds` is dead weight; drop it and point the docstrings
at the guard.

Verified to fail on the pre-#10508 builder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The original breakage needed two conditions an earlier test happened to
supply: `attention_spec_builder` already in sys.modules, and
`_RESOLVED_ATTENTION_ARCH` holding a non-gfx950 arch. Reproducing both
in-test means the guard cannot lapse when collection order changes.

Verified to fail on the pre-#10508 builder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aosewski

Copy link
Copy Markdown
Contributor

I've pushed four commits to this branch (756dc0e3..55c3ecb4) rather than leave the review open — revert any of them freely if you'd rather take a different route.

818c75f fix(rocke): restore attention_d256 in the golden IR parity gate
adfc04a test(rocke): contain attention arch state leaks between tests
7c47e74 test(rocke): pin the lazy arch-resolver binding with a guard test
55c3ecb test(rocke): restate the arch-pin failure deterministically

The first is the blocking one — it clears the red rocke_golden_static gate. Besides dropping the asb half of the pin, it also pins the _RESOLVED_ATTENTION_ARCH memo, because that harness is installed standalone without the library test tree and so can't rely on a guard test to hold the invariant for it.

Your diagnosis reproduces exactly on real hardware. On a gfx942 MI300X node at the base commit: the resolver test alone gives 9 passed, and after test_attn_bf16_d128_ring.py gives 5 FAILED — matching the ticket's pairing table, with the failure being a TypeError out of dataclasses.replace(). That confirms the bound import is the whole mechanism and the memo is not, so the ticket's root-cause title really is misnamed. With this branch as it now stands, the same node gives 520 passed / 0 failed.

One thing worth knowing about the guard test: it pins two archs deliberately. The resolver falls back to "gfx950" with no device, and _tiled_2d_impl maps everything except gfx942/gfx1250 onto the gfx950 impl — so which pin catches the regression flips with the host. On a gfx90a box the gfx942 pin fails; on the gfx942 node the gfx950 pin fails. A single-pin guard would have been silently vacuous on one of those two machines.

Two further things I found are not yours and are out in #10625 instead of here, to keep this PR reviewable: the parity gate's skip-on-first-case behaviour (which is why the harness breakage went unnoticed in the first place), and a stale parity_unified_attention module path missing its .prefill segment that fails on develop today. Because that second one isn't on this branch, TestAttentionHarnessTimers still shows 2 pre-existing failures here.

Verified before pushing: platform/tests 685 passed (was 684 + 1 failed), golden IR parity OK [llvm20, llvm22, llvm23] with no re-bless, black 25.12.0 clean.

@AnaghaRaoAMD AnaghaRaoAMD changed the title fix(rocke): reach _resolve_attention_arch through the module handle fix(rocke): reach _resolve_attention_arch through the module handle [AICK-1898] Aug 11, 2026
@AnaghaRaoAMD
AnaghaRaoAMD requested a review from a team August 11, 2026 23:38
@AnaghaRaoAMD AnaghaRaoAMD changed the title fix(rocke): reach _resolve_attention_arch through the module handle [AICK-1898] fix(rocke): reach _resolve_attention_arch through the module handle Aug 11, 2026

@AviralGoelAMD AviralGoelAMD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

aosewski added a commit that referenced this pull request Aug 17, 2026
…ess module path (#10625)

Two independent pre-existing fixes in the rocKE attention test lane.
Neither depends on the other; they are together only because both
surfaced while reviewing #10508, and both are separable from it.

## 1. The dual-engine parity gate could not see most of its cases

`library/tests/test_attention_ir_cpp_parity.py` is the C++/Python
byte-identity gate for the `attention_dense` and `attention_d256`
kernels. Its per-case handler called `pytest.skip` on **any** exception
from the C++ lowering. Since `pytest.skip` aborts the whole test, the
first case with an unavailable engine ended the run — and the 18
`attention_dense` cases sort ahead of the 2 `attention_d256` ones, so
d256 was effectively unreachable on any box without `rocke_engine`
built.

That is not hypothetical. It is exactly why a breakage in the
`attention_d256` harness builds reached CI unflagged: the gate that
should have caught it skipped first.

Failures are now classified by exception type instead of lumped
together:

| Outcome | Verdict |
|---|---|
| `BackendCoverageGap` — an arch in `backend.CPP_UNPORTED_ARCHES`, the
one sanctioned gap taxonomy | collect and continue, report as skip |
| `BackendError` — the engine is not importable at all | whole-run skip;
an absent engine cannot appear mid-loop |
| anything else | propagates — the engine is present and will not lower
a case, which is a defect |

`assert cases` is also tightened into a per-family presence check. A
renamed or dropped family would still leave a non-empty list, and
harness drift must not be reported as an engine gap.

## 2. Stale `parity_unified_attention` module path

The harness moved under `attention/prefill/`, but three call sites still
resolve `builders.gfx950.attention.parity_unified_attention`. Two are
hard failures on `develop` today:

- `test_attention_builds.py::TestAttentionHarnessTimers` (2 tests) —
`find_spec` returns `None`, so `.origin` raises `AttributeError`
- `test_gfx950_smoke_attention.py` — `python -m` on a module that does
not exist

The other two are stale copy-paste targets in docstrings.

## Verification

On `develop` tip (`1781f84e4b`), CPU lane:

| | before | after |
|---|---|---|
| `pytest library/tests` | 2 failed, 502 passed | **504 passed**, 0
failed |
| `pytest platform/tests` | 685 passed | 685 passed |

`black` 25.12.0 clean. No new `ruff` findings. No golden re-bless — this
touches no emission path.

## Scope

Test-lane only. No kernel, spec, lever, or emission change; no C++
mirror needed.

A related follow-up covers ~15 remaining references to a dead
`examples/gfx950/attention/parity_unified_attention.py` documentation
path across 8 doc files — kept out of here to stay reviewable.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AnaghaRaoAMD
AnaghaRaoAMD enabled auto-merge (squash) August 17, 2026 18:47
@AnaghaRaoAMD
AnaghaRaoAMD requested review from a team and AviralGoelAMD August 17, 2026 22:32
@AviralGoelAMD
AviralGoelAMD requested a review from a team August 17, 2026 22:47
@AnaghaRaoAMD
AnaghaRaoAMD merged commit f20838a into develop Aug 17, 2026
36 checks passed
@AnaghaRaoAMD
AnaghaRaoAMD deleted the users/anarao/fix-attention-arch-patch-seam branch August 17, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

organization: ROCm project: hip-kernel-provider rocke: platform Touches rocKE platform rocke-lib: attention Touches rocKE library attention/FMHA kernels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants