Repository navigation
perf(rocke): prepare saved paged decode bindings once - #13700
tenpercent wants to merge 31 commits into
Conversation
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
There was a problem hiding this comment.
🟡 Changes recommended
gfx1250 rebinding encounters a scope-only restriction, and the shared graph-runtime changes lack current validation for that target.
2 open findings
What changed in this PR
Reduces repeated split-KV decode host overhead through fixed-binding replay scopes without changing kernel emission.
Changes:
- Adds scoped replay and rebinding with explicit stream and resource ownership.
- Optimizes ordinary stream setup and graph-binding checks.
- Updates benchmarks, lifetime tests and usage documentation.
Paths below are relative to dnn-providers/hip-kernel-provider/rocke/.
| File | Description |
|---|---|
platform/dsl_docs/optimization/optimization_runbook.md |
Adds host-overhead diagnosis guidance. |
library/tests/test_strided_kv_decode_numeric.py |
Covers replay updates and storage rebinding numerically. |
library/tests/test_attention_workspace.py |
Tests redundant-context avoidance. |
library/tests/test_attention_3d_replay_scope.py |
Tests scope ownership, rebinding and cleanup. |
library/tests/test_attention_3d_replay_bindings.py |
Tests binding checks and graph-cache lifetime. |
library/kernels/common/attention_unified.py |
Implements scoped replay and runtime optimizations. |
library/builders/gfx950/attention/README.md |
Links the host-overhead case study. |
library/builders/gfx950/attention/CASE_STUDY_DECODE_HOST_OVERHEAD.md |
Documents comparison methodology and results. |
library/benchmarks/gfx950/attention/decode/benchmark_decode_live.py |
Times the scoped public callable. |
library/benchmarks/gfx942/attention/decode/benchmark_decode_live.py |
Times the scoped public callable. |
docs/strided-kv-decode.md |
Documents replay, rebinding and completion contracts. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🎉 All checks passed! This PR is ready for review. |
Keep ordinary gfx1250 rebinding independent of scope-only admission and retain replacement bindings until completion. Confine context and resident-key shortcuts to gfx942/gfx950 while preserving prior gfx1250 graph behavior. Add host and numerical regression coverage for AICK-2566.
There was a problem hiding this comment.
🔵 Needs a closer look
Reusable GPU scratch across stream, capture and asynchronous-lifetime boundaries warrants final human review of the lifecycle guarantees and reported hardware validation.
No open issues 🎉
1 resolved since last review
Review effort: Balanced
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #13700 — prepare saved paged decode bindings once — 🔴 Follow-up: blocking — the narrowed change is sound on its own base, but merged with today's develop it breaks #13955's LSE binding: a CPU-lane test fails in every CI shard, and the second call of an LSE-emitting binding raises
Utility: 🟢 Positive — the narrowed design keeps all reuse inside one saved binding, with private scratch and the ordinary retention path, and roughly halves host enqueue time on gfx942 / gfx950 by the description's ratios. It needs the LSE path reconciled before landing. 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 a file-level comparison with the base and the threads against the code. They also included the new binding test with torch on a merge with today's develop, the |
Merge develop and exclude LSE-enabled problems from private decode preparation. Preserve repeated LSE buffer and stream forwarding with a regression for both affected architectures.
|
I reviewed this PR again. The following review threads are still unresolved, or comments have no author reply:
If you have already fixed these, please reply on each comment to say so and resolve the threads. |
|
Following up on the October 26 LSE finding, verified at merged head N1 remains addressed by The merged-head focused binding/LSE/predicate cohort passed 55 host tests. The earlier Torch-free cohort passed 83 tests, and removing only the LSE guard failed the regression for both architectures; those earlier checks are identified separately in the description. Fresh merged-head numerical confirmation now passed on both gfx942 and gfx950. Each target passed the existing pinned split-KV case and four launches of one saved binding, with BF16 inputs and the existing FP32-reference tolerance. Both outputs were poisoned before each saved-binding launch, the LSE-buffer override was exercised, and the unused buffer remained untouched. The adapter rejected private preparation. The PR description records the source/environment provenance and validation scope. gfx942 LSE confirmation is complete. Assessment and reply prepared with Codex. |
|
Following up on the October 23 review summary, at merged head
The gfx942 default-graph measurement request is complete, but it revealed a consistent regression. The description keeps its fix/revalidation open and records a focused plan to measure wrapper overhead, test early graph-policy delegation while preserving stream/capture/live-policy contracts, extend existing tests and repeat the same pairs. This plan is not an implemented or validated remedy. The historical host wall-clock enqueue percentages remain a separate open reproduction request because the existing CLI measures HIP-event intervals. Per-invocation rebinding and rotating maximum-length cases remain outside the measured scope. Fresh LSE numerics passed on both targets. No new repository benchmark or performance fix was introduced in this evidence update. Assessment and reply prepared with Codex. |
|
Following up on the October 24 review summary, verified at merged head The runtime-coordinator and eager-cache-clear findings remain addressed by withdrawal. The requested gfx942 develop-to-head comparison with default internal graphs is now complete. The existing The default-graph measurements revealed a consistent regression, so the performance issue remains open. The binding wrapper resolves the stream, checks thread/owner state and queries capture before checking graph policy and delegating. Those additional probes are a candidate source of overhead, not a proven root cause. The follow-up plan is to measure wrapper cost, test early graph-policy delegation that avoids eager-only probes, extend existing graph-mode tests and repeat the same default-graph pairs and numerics. The change must preserve current/zero/explicit streams, policy changes after binding, capture, LSE exclusion and private-owner safety. No remedy has been implemented or validated yet. The historical host wall-clock enqueue metric is also still a separate open reproduction request. Assessment and reply prepared with Codex. |
|
Following up on the latest reminder: the three linked summaries have separate author replies, now updated for merged head The LSE semantic conflict remains fixed, and fresh repeated-binding LSE numerics passed on both gfx942 and gfx950. The coordinator/cache-clear findings concern withdrawn code. The description separates merged-head host counts and matching-hardware skips from earlier installed Python validation. All 28 inline threads are resolved and have author replies. Fresh paired base/head timing completed on gfx942 (eager and default graphs) and gfx950 (eager), with all 36 CLI numerical checks passing and equivalent executable specs. The PR description records verified relative results, current revisions, environment and the existing runnable command. The gfx942 default-graph comparison revealed a consistent regression. Its fix and repeat measurements remain open, with a focused wrapper-overhead plan in the description. Historical host wall-clock enqueue reproduction also remains open. Resolving the older inline threads does not mean those follow-ups are complete. No new repository benchmark or performance fix was added in this evidence update. Assessment and reply prepared with Codex. |
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #13700 — prepare saved paged decode bindings once (tenpercent, AICK-2566) — 🟠 Follow-up: LSE fixed, gfx942 default slower —
|
| Prior item | Status |
|---|---|
| Inline threads (10-15…10-24), all 28: Copilot ×9, AviralGoelAMD ×15, AnaghaRaoAMD ×4, incl. the items behind our 10-21 🟠/🟡 (cross-device retention key, stream-handle collisions, drain races, single-host-thread contract) | 🟢 N/A or FIXED — all resolved with author replies. Most target withdrawn code: the net diff now touches only the binding, its two tests, the case study, two README links and CMake, so hip_module.py, launcher.py and attention_unified.py match develop (ran: git diff). Foreign-thread and reentrant calls take the public runner (bindings.py:403-406). The doc and test asks are met by the new case study (no PR or job IDs, one sampling recipe) and by test_warm_binding_reuses_private_scratch_and_context, which also enters through the candidate's bound_torch (ran) |
Our 10-19 review: CHANGES_REQUESTED at 931a48eb (timing-test stubs; the PR's tests in no CI lane) |
🟡 Stale, still standing — it is still the PR's review decision. Its reasons look addressed: the timing tests left the PR with the redesign, and the new test_attention_decode_binding.py is in the CPU attention lane (platform/CMakeLists.txt:637) and passes 13 in every CI shard. No approvals yet; reviews are re-requested from us, AnaghaRaoAMD, AviralGoelAMD and the rocke-lib / rocke-platform teams (ran: gh) |
| Our 10-21 🟠: no gfx942 hardware run | 🟢 ADDRESSED — the description reports fresh merged-head gfx942 runs: eager and default-graph pairs with numerics checks before timing, and repeated-binding LSE numerics. We have no gfx942 here to repeat them (read) |
| AviralGoelAMD (10-23) review: skip labels and counts disagree; no reproducible develop→head ratio; no gfx942, per-call-rebinding or graph-default measurement; silent cross-thread misuse | 🟢 Mostly ADDRESSED — the description separates merged-head counts from earlier ones, gives a runnable attention_combo_sweep command with paired ratios, and measures gfx942; cross-thread calls fall back (row 1). 🟡 DEFERRED, acceptable: per-call rebinding and the historical host-enqueue metric ("outside the measured scope"), since the claim is scoped to saved bindings. The graph-default result is the 🟠 row below (read) |
| AnaghaRaoAMD (10-24): add the gfx942 develop→head ratio with internal graphs at their default, which is on for these decode shapes | 🟠 OPEN, valid — measured on 10-28. The description reports gfx942's default-graph path slower than develop for both shapes, in every paired trial, and leaves the fix as an unchecked checklist item with a plan. By reading, each call resolves the stream, checks thread and lock, imports torch and queries capture (bindings.py:402-410) before the graph-policy check at :411 passes it to the runner, which resolves the stream again (attention_unified.py:4415). The committed case study still shows only graphs-disabled rows, measured at branch commit b106aeda → check the graph policy first, as planned, re-measure, and put the default-graph result in the case study (read) |
Our 10-26 N1 🔴: merged with develop, #13955's LSE binding broke: a CPU-lane test failed without torch, and warm LSE calls lacked lse_ptr |
🟢 FIXED bc79b921 — the decode gate now requires not problem.emit_lse (bindings.py:585), so LSE bindings never build the warm path or import torch. Every launch goes to the public runner, with live lse and stream overrides. test_binding_forwards_the_lse_buffer (three launches; preparation patched to fail) passes on the merge with develop f22bed43, 31 of 31 in its file (ran), and in all four CI shards at head (read) |
| CongMa13 (10-28): our 10-26 comment and the 10-23 / 10-24 review summaries had no author reply | 🟢 ADDRESSED — the author answered all three on 10-28 (read) |
| Bots / CI | 🟢 At 0feeb33b: the policy bot passes; 45 checks passed, 51 skipped, none failed. Pre-commit, clang-tidy, the Multi-Arch and Component CI summaries, Multi-Arch ASAN and both hipDNN superbuilds pass; Math CI reports success but says it skipped. Multi-Arch CI tested gfx94X (Linux) and gfx110X (Windows): all four Test hipkernelprovider shards pass, each with test_unified_lse_dispatch.py 31 passed (both test_binding_forwards_the_lse_buffer cases) and test_attention_decode_binding.py 13 passed, without torch. gfx950 and gfx125X were built, but their test jobs were skipped. GitHub: MERGEABLE, merge state BLOCKED by the review decision (read) |
| Our checks | 🟢 • Merge: clean with develop f22bed43 (22 behind, 31 ahead) (ran). Since the PR's base, develop changed only 2D sliding-window routing (#14150) in the attention runtime; the 3D split-KV launch the warm path reuses is untouched (ran: git diff). • Interdiff since 0db55f1e: the PR's own change sits in merge bc79b921: the emit_lse gate and its docstring, the stronger LSE binding test, and case-study text. The develop merges 7b37892b and 0feeb33b add no manual change (ran: git show --remerge-diff). • Build: the native build and 33 ctests are OK (1 skipped) (ran). • Goldens: the differential golden (ll at llvm20 / 22 / 23 / 24, and canonical) has no unblessed change and nothing missing; the PR changes no emitter code (ran). • Tests: the PR's two test files pass 44 on the merge; test_unified_lse_dispatch.py alone, 31 passed (ran). • Rules 1–3 clean, and the case study gives ratios only (ran) |
| # | Sev | file:line | Finding |
|---|---|---|---|
| N1 | 🟡 | dispatch/attention/bindings.py:441, :477-497 |
The warm path skips the runner's per-call LSE and bounds handling. • At head: a stray lse= override on an LSE-off saved binding is silently dropped on warm calls, so the caller's buffer is never written. The cold call, and develop on every call, raise lse requires problem.emit_lse=True (_lse_store.py:363) (ran: a probe on the merge, built on the PR's own test fixture). • With #13128 (open, same author): bindings.py conflicts in 2 hunks. The LSE test file and CMake auto-merge, and the two PRs edit different tests in that file (ran: git merge-tree). #13128's attention_paged_decode_t32 candidate (gfx942 / gfx950, page size 1 by default) always uses metadata containment and binds through this function. Containment adds a fourth workspace tensor, so the three-name unpack at :441 would raise on every launch, after its work is queued. The warm dicts also lack the containment arguments and per-call bounds. The PR's tests fake the pipeline, so CI would not notice (read). → Route a non- None lse to the public runner now. Whichever PR lands second sends contained specs to the runner, or ports those arguments. |
Utility: 🟡 Mixed — faster eager saved bindings on gfx950; gfx942's default graph path is slower.
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 an interdiff of the PR's own change since the head we reviewed (0db55f1e); a merge with develop f22bed43 with the native build and ctests, the differential goldens, the PR's two test files, and a probe of the warm path built on the PR's own test fixture; a merge simulation with #13128; and the threads, reviews and 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.
|
I reviewed this PR again. The following review threads are still unresolved, or comments have no author reply: If you have already fixed these, please reply on each comment to say so and resolve the threads. |


Summary
Repeated launches from a saved paged decode binding redo scratch allocation and fixed launch preparation. This PR prepares that work once for gfx942/gfx950 non-FP8, LSE-off one-token split-KV attention. Compatible subsequent calls reuse the compiled pipeline, launch geometry, stream wrapper and private scratch, while reading live tensor pointers and scalar overrides and packing fresh kernel arguments on every launch.
LSE-enabled bindings use the public runner on every invocation, preserving output validation, buffer overrides and fresh reducer arguments.
ISSUE ID : AICK-2566
Risk Assessment
Level 4: this changes a performance-sensitive host submission path, and fresh paired measurements found an unresolved gfx942 default-graph regression. The eager benefit is bounded to the measured saved bindings; it does not establish a general speedup. Incompatible calls retain the public runner. Focused tests cover scratch ownership, live overrides, stream/capture fallback and failure recovery. There are no emitter, kernel-signature or native-provider changes.
ASIC Coverage
The optimized path serves gfx942 and gfx950. Fresh merged-head validation completed on both targets: gfx942 eager and default-graph comparisons, gfx950 eager comparisons, and LSE numerical checks with repeated saved-binding launches. All measured CLI arms passed independent numerical checks. The gfx942 default-graph performance regression still needs a fix and repeat measurements. Native-provider and gfx1250 hardware validation are outside this binding change.
Testing Summary
Develop
3eeb4c446556b6717a37866479fae3b581bfed16was merged into this branch without conflicts and pushed as0feeb33b12feb4fc5991706443111c25fc7b4226. Both overlapping CMake test registrations survived. The merged binding implementation and shared runtime are unchanged from the previous PR head.test_attention_decode_binding.py,dispatch/attention/test_unified_lse_dispatch.pyandtest_attention_spec_predicate_parity.py.status="ok", with independent FP32 reference checks on BF16 inputs before timing atatol=0.03and equivalent executable specs across pairs.0.04tolerance. Each target passed the pinned split-KV case and four launches of one saved binding. Both outputs were poisoned before every saved-binding launch; the checks covered an LSE-buffer override and verified that the unused buffer remained untouched. Private preparation was rejected by the adapter.Host checks used the Python engine and LLVM23. Fresh GPU runs used Python 3.12, the Python engine, LLVM23 emission,
ubuntu-24androcm-nightly/10.3.0a20261027, with matching Torch/SDK wheels. The existing timing CLI compiled normally in-process; the separate LSE adapter compiled in Torch-free subprocesses. No new broad native/parity or installed replay result is claimed.Testing Checklist
CHANGES_REQUESTED.Technical Changes
Preparation happens on the first ordinary eager launch; creating a binding submits no GPU work. Each binding owns its scratch independently, while existing immutable compiled caches can still be shared. Ordinary runtime retention keeps submitted tensors and scratch alive until queued work completes, including when the binding is discarded.
For eligible prepared bindings,
Noneselects Torch's current stream, and explicit integer stream handles remain supported. LSE bindings retain the existing integer-stream interface and default0. A different device, stream or tensor layout, a foreign or reentrant host-thread call, internal graphs, or external capture uses the public runner. Explicit stream contexts are restored after each call.The implementation is confined to the attention binding layer, with focused regressions, installed CPU test registration and documentation. Shared attention kernels and the generic HIP runtime are unchanged relative to develop. The change adds no global scratch cache or general concurrency guarantee. Unfenced callers still need their existing completion boundary before reading outputs.
Performance evidence
The saved decode binding case study records historical paired gfx942/gfx950 measurements against the original per-call binding path. Those measurements predate the LSE admission repair and this develop merge. They apply to saved non-FP8, LSE-off bindings at their recorded configurations and are distinct from the fresh comparison below. HIP-event intervals include host pacing and do not establish a device-kernel speedup.
Runnable saved-binding HIP-event comparison
The existing
benchmarks.common.attention_combo_sweepcommand creates one saved binding and repeatedly launches it. Run this fromdnn-providers/hip-kernel-provider/rockein separate checkouts of base3eeb4c446556b6717a37866479fae3b581bfed16and merged head0feeb33b12feb4fc5991706443111c25fc7b4226, on the same gfx950 GPU and with the same release-matched ROCm/Torch environment:This pins B=1/KV=2048, Hq/Hkv=32/4, D128, page-size-16 BF16 split-KV attention. For the other measured shape, use
--batch 4 --seqlen-k 4096. For gfx942, change both architecture/candidate identifiers to gfx942 and useHIPDNN_GFX942_3D_GRAPH=0for the eager diagnostic.For gfx942's default internal-graph policy, first run
unset HIPDNN_GFX942_3D_GRAPH, then omit theHIPDNN_GFX942_3D_GRAPH=0assignment from the gfx942 command. Keep outer capture disabled. Internal graphs are on by default for the measured gfx942 shapes; gfx950 is eager by default. The eager gfx942 rows are diagnostics, not its ordinary default behavior.Compare matching rows only when both report
status="ok", with equivalent executable specs and geometry. Compute each paired reduction from the row's HIP-eventmsstatistic as100 * (1 - head.ms / base.ms). Repeat paired runs on the same GPU with alternating arm order and record revisions and environment. The CLI's independent FP32 attention reference check remains enabled, using quantized BF16 inputs and default absolute tolerance0.03.Fresh merged-head results
On October 28, 2026, three paired trials per shape and policy alternated base/head order on one exclusively allocated GPU per target. All 24 gfx942 and 12 gfx950 CLI runs exited successfully and passed their before-timing independent numerical checks. Executable spec hashes, tuning knobs, kernel names and selected paths matched across each pair. The CLI does not add a poisoned post-timing check; the separate repeated LSE checks described above are a different validation lane.
The CLI takes seven HIP-event samples, each with 20 warmups and 100 timed executions, discards the first sample and selects the upper middle of the remaining six. The table reports the median paired reduction over three trials. The range describes those trials, not a confidence interval. A negative reduction means the head took longer than develop.
The gfx942 default-graph intervals were about 7.9% longer for both shapes, consistently across all three paired trials. This regression is unresolved. Eager savings do not establish a benefit for gfx942's default graph policy. These results apply only to the saved Python binding boundary for the measured synthetic non-FP8, LSE-off configurations, with outer capture disabled. They do not reproduce historical host wall-clock enqueue percentages, rotating maximum-length cases or callers that rebind every invocation.
Provenance: gfx942 Slurm job
68202040ondefqand gfx950 job68202130onrockeboth completed with exit0:0. Environment: Python 3.12, Python engine, LLVM23,ubuntu-24+rocm-nightly/10.3.0a20261027, Torch2.16.0a0+rocm10.3.0a20261027, HIP7.17.26431, NumPy2.2.6, and matchingrocm-sdk-core,rocm-sdk-librariesand target-specific device wheels version10.3.0a20261027. Compilation and launch used matching SDK-wheel COMGR/HIP libraries. A fresh gfx950 wheel download could not obtain the pinned device wheel, so that job used a task-local copy of a previously verified environment on the same target; package versions, runtime mappings and device operations were freshly verified. The source and CLI were the pinned merged revisions. Raw source-tar SHA-256 values:cf2337fd6eafb90e4887cedf8dad225438631f3075325029acec84f0c8184235(base) and809a1c29edf8cfe8b061b18d2275c00cf0d1a65e5422213639647ddbf437a7b2(head). Private raw records retain exact commands, adapter hashes and numerical summaries.Open follow-up plan
The requested gfx942 default-graph ratio is now measured, but it revealed a regression. In
_bind_paged_decode_launch, calls resolve the stream, check thread/owner state and query capture before checking the pure graph policy and delegating to the public runner. Those additional wrapper probes are a candidate source of overhead; the exact cause and remedy have not been proven.The focused follow-up is to measure the wrapper cost, test an early graph-policy delegation that avoids eager-only probes, and extend the existing graph-mode regression tests. It must preserve current/zero/explicit stream semantics, policy changes after binding, external capture, LSE exclusion and private-owner safety. Then repeat the same default-graph pairs and numerics, and check that eager behavior remains correct. No runtime fix or new repository benchmark is included in this evidence update.