Skip to content

feat(rocke): fix and tune attention sink decode - #9391

Merged
arai713 merged 4 commits into
developfrom
users/arai/attention_sink_decode
Jul 30, 2026
Merged

arai713 merged 4 commits into
developfrom
users/arai/attention_sink_decode

Conversation

@arai713

@arai713 arai713 commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

gpt-oss attention-sink decode (seqlen_q=1) on gfx942 had two problems. First, it was broken: bf16 sliding-window (SWA-128) sink decode crashed when a windowed config fell into the fp8-loader code path and failed an assert (fp8 total chunks must be divisible by THREADS), even though gfx942 has no fp8 K/V support. Second, the D64 decode path ran at a suboptimal num_warps, leaving significant decode performance on the table. AOTriton has no attention-sink kernel, so there is no competing flash path for gpt-oss sink attention on gfx942, the goal is rocKE-only correctness plus a competitive roofline. This PR fixes the SWA-sink decode crash and tunes decode performance.

Technical Details

  • Dispatch fix: guard the gfx942 2D fp8-loader assert and fp8_chunks_per_thread behind KV_FP8, matching the 3D builder. bf16 windowed decode no longer enters the fp8 path.
  • Perf tune: _select_2d_num_warps now returns num_warps=1 for gfx942 D64 decode (all_decode), leaving prefill at nw4. Decode is memory-bandwidth-bound, so 4 warps over-subscribe and contend for KV bandwidth with too little parallel work to hide it, a single warp per workgroup streams KV without that overhead. The change is gated to all_decode, so prefill, which is compute-bound, is untouched.
  • Benchmark: add attention-sink + sliding-window support to the decode benchmark plus a gpt-oss sink shapes file (gpt_oss_sink_shapes.json).

Performance

Both arches: bf16, D64, GQA 64/8, decode Sq=1. Median of 3 runs, DSL kernel, best across the num_sms sweep. Measured via the Python-lowerer path (C++ engine not built on the bench box), so absolute latencies are a floor.

gfx942 (MI300X)

Same-run nw4 to nw1, 500 iterations, run-to-run variance under 0.1%.

Shape nw4 (shipped) nw1 (tuned) Speedup
full_sink B64 kv2048 558 us 295 us 1.89x
full_sink B128 kv2048 1116 us 334 us 3.34x
full_sink B64 kv8192 2204 us 1158 us 1.90x
swa_sink B128 kv2048 92 us 46 us 2.01x

full_sink decode is the primary win (about 1.9x to 3.3x). SWA decode already sits at a ~46 us floor and is unchanged except at B128. full_sink B1 routes to the 3D path and is unchanged. The nw4 to nw1 ratio is same-session and device-consistent.

gfx950 (MI350X)

No kernel or selector change was needed: the gfx950 selector already picks num_warps=1 for D64 decode, the same config the gfx942 change introduces, so gfx950 is already at the tuned optimum. Latency, 500 iterations, run-to-run variance under 2%.

Shape Latency Path
full_sink B1 kv2048 44 us 3d
full_sink B64 kv2048 59 us 3d
full_sink B128 kv2048 119 us 2d
full_sink B64 kv8192 238 us 3d
swa_sink B64 kv2048 30 us 2d
swa_sink B128 kv2048 31 us 2d
swa_sink B64 kv8192 31 us 2d

SWA decode is flat (~30 us) across batch and KV length, confirming window-skipping. AOTriton has no sink kernel, so there is no comparison column.

Test Plan

  • Codegen regression test (CPU, no GPU) which verifies the bf16 SWA-decode config no longer trips the fp8 assert.
  • Selector unit check where _select_2d_num_warps returns 1 for Sq=1 and 4 for prefill (Sq=512/2048), this confirms decode-only gating.
  • Correctness: ran the 8 gpt-oss sink shapes through the decode benchmark, comparing output against the fp32 reference within bf16 tolerance.

Test Result

  • Regression + selector checks pass.
  • Correctness: all shapes match the fp32 reference, max_abs <= 1.2e-4 (bf16 tol 4e-2), full and SWA, on MI300X.

Related

JIRA ID : AICK-1498

Submission Checklist

Changes are behind the feature flag HIPKERNELPROVIDER_ENABLE_ROCKE which is defaulted to OFF.

@therock-pr-bot

therock-pr-bot Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Title/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.

@therock-pr-bot

therock-pr-bot Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

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

@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch from 765dc19 to 4da77dc Compare July 16, 2026 00:51
@arai713 arai713 changed the title feat (rocke): fix and tune attention sink decode feat(rocke): fix and tune attention sink decode Jul 16, 2026
@therock-pr-bot

Copy link
Copy Markdown

Pre-commit check failed

⛔ pre-commit failed

Please run locally:

  • python -m pip install pre-commit
  • pre-commit install
  • pre-commit run --all-files --show-diff-on-failure

This repo uses .pre-commit-config.yaml.

@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch 2 times, most recently from 74b171c to 388ea06 Compare July 21, 2026 21:10
@arai713
arai713 marked this pull request as ready for review July 21, 2026 21:21
@arai713
arai713 requested review from a team as code owners July 21, 2026 21:21
@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch from 388ea06 to 68d6774 Compare July 22, 2026 17:50

@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.

Ideally, this

if problem.all_decode:
            return 1

change should be also done on the cpp counterpart that is present at platform/cpp/instances/common/attention_unified_selectors.cpp so that both CPP and PY translators translate down to identical IR.

(I am not sure if we are strictly enforcing that policy just yet but given that its only a two line change on python side, we can do it quickly on cpp as well).

Rest of the PR LGTM.

@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.

.

Copilot AI 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.

Pull request overview

This PR fixes correctness and performance issues in rocKE’s attention-sink decode path (notably for gfx942 seqlen_q=1) and extends the decode benchmarking harness to cover attention sinks and sliding-window (SWA) shapes.

Changes:

  • Fixes a gfx942 bf16 SWA decode crash by guarding the 2D fp8-loader chunk-count assert behind KV_FP8.
  • Tunes gfx942 D64 decode warp selection to prefer num_warps=1 for decode-only (seqlen_q==1 / all_decode), preserving prefill behavior.
  • Adds attention-sink + sliding-window coverage to the decode benchmarks and introduces gpt-oss sink shape JSONs; adds a regression test to prevent the prior crash.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
dnn-providers/hip-kernel-provider/rocke/platform/cpp/instances/common/attention_unified_selectors.cpp Adjusts gfx942 D64 decode warp selection (Sq=1 -> nw1).
dnn-providers/hip-kernel-provider/rocke/library/tests/test_attention_builds.py Adds regression test ensuring bf16 SWA decode no longer trips the fp8-loader assert.
dnn-providers/hip-kernel-provider/rocke/library/kernels/gfx942/attention_tiled_2d.py Guards fp8 chunk-count assert / chunk computation behind KV_FP8.
dnn-providers/hip-kernel-provider/rocke/library/kernels/common/attention_unified.py Tunes gfx942 2D warp selection to return nw1 for decode-only problems.
dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx950/attention/decode/gpt_oss_sink_shapes.json Adds gpt-oss sink + SWA decode benchmark shapes for gfx950.
dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx950/attention/decode/benchmark_decode_live.py Extends gfx950 decode benchmark to plumb sinks + sliding_window into the DSL path and shapes loader.
dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx942/attention/decode/gpt_oss_sink_shapes.json Adds gpt-oss sink + SWA decode benchmark shapes for gfx942.
dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx942/attention/decode/benchmark_decode_live.py Extends gfx942 decode benchmark to plumb sinks + sliding_window into the DSL path and shapes loader; skips Triton when sinks are active.
Comments suppressed due to low confidence (2)

dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx942/attention/decode/benchmark_decode_live.py:228

  • sliding_window is now a first-class shape field, but the AITER Triton baseline still hard-codes window_size=(-1, -1). For any shape with sliding_window>0 and use_sinks=False, Triton will benchmark the wrong math (full-causal) and the DSL-vs-Triton comparison becomes invalid.
    except ImportError:

dnn-providers/hip-kernel-provider/rocke/library/benchmarks/gfx950/attention/decode/benchmark_decode_live.py:215

  • sliding_window is now loaded and passed to the DSL path, but the AITER Triton baseline still hard-codes window_size=(-1, -1). For any shape with sliding_window>0 and use_sinks=False, Triton will benchmark the wrong math (full-causal) and the DSL-vs-Triton comparison becomes invalid.
    except ImportError:
        return None

@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch 2 times, most recently from 0530cd6 to ed72ae8 Compare July 27, 2026 21:57

@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.

Read through this end to end. The fp8 guard is right and the C++ mirror matches (all_decode is defined as max_seqlen_q == 1 in attention_unified_entry.cpp#L125, so the two selectors agree). Four comments inline; these six are on lines outside the diff.

1. --dtype / --batch silently drop the new shape fields (gfx950)

benchmark_decode_live.py#L658-L686 rebuilds DecodeShape field by field, and neither override forwards use_sinks or sliding_window. So this:

python -m benchmarks.gfx950.attention.decode.benchmark_decode_live \
    --shapes .../gpt_oss_sink_shapes.json --batch 64

benchmarks plain full-causal attention under rows still labelled full_sink_* / swa_sink_*. A batch sweep is the most likely way these shapes get run.

shapes = [dataclasses.replace(s, dtype=args.dtype) for s in shapes]
shapes = [dataclasses.replace(s, batch=args.batch) for s in shapes]

immune to the next field someone adds. (gfx942 is safe only because it has neither flag.)

2. AOTriton isn't skipped for sinks or windows

gfx942#L403 / gfx950#L389 — use_bias covers softcap, alibi and qq_bias but not the two new fields, so all 8 shapes in gpt_oss_sink_shapes.json get timed against scaled_dot_product_attention(is_causal=False): no sink logit, no window. That number becomes best_speedup_vs_aoTriton and lands in the printed geomean.

use_bias = (
    alibi_slopes is not None
    or data["qq_bias"] is not None
    or softcap
    or shape.use_sinks
    or shape.sliding_window > 0
)

3. FlyDSL has no gate at all

gfx942#L498-L503 / gfx950#L480-L485 — _flydsl_supported checks head_size and dtype only, and FlyDSL is called with causal=True and no sink or window operand, so flydsl_ms and best_speedup_vs_flydsl are wrong math for these shapes. The prefill sibling already gets this right (benchmark_prefill2d_live.py#L190-L193):

if shape.use_sinks:
    return False, "sinks unsupported"
if shape.sliding_window:
    return False, "sliding_window unsupported"

Between this, #2 and the inline comment on _run_triton, the same two shape fields get three different policies across three backends — one hard skip, one stale predicate, one nothing. A single _backend_supports(shape) helper would keep them honest.

4. window_size is still hardcoded

gfx942#L251 / gfx950#L237 — window_size=(-1, -1), while sliding_window is now a first-class field. Only the sinks flag short-circuits, so a sliding_window>0, use_sinks=False shape (newly expressible because of this PR) times full-causal Triton against a windowed DSL kernel. Copilot flagged this earlier; still open.

5. Same unguarded assert survives in the gfx950 2D builder

gfx950/attention_tiled_2d.py#L2378-L2382 has the identical unconditional fp8_total_chunks % THREADS == 0. With this PR, gfx942 2D, gfx942 3D and gfx950 3D all guard it and gfx950 2D is the last one that doesn't — and this PR adds gfx950 sink-decode shapes that drive that builder. It survives today only because the gfx950 selector happens to land on nw≤2; any retune to nw=4 trips it. Same if KV_FP8: wrap closes it.

6. The correctness result isn't reproducible from this PR

The description reports max_abs <= 1.2e-4 vs an fp32 reference, but neither benchmark computes a reference, compares outputs, or reports an error norm — out is allocated, written and discarded (gfx942#L699-L781). For a change that alters kernel geometry that's the one number worth being able to re-run. Either an opt-in --check that runs a small fp32 sink reference on the first shape, or a pointer to the test that produced those numbers, would do it.


Minor, take or leave: the results record (L783-L817) mirrors every other DecodeShape field as its own JSON key but not the two new ones, so downstream tooling has to reverse-parse signature to tell a sink run from a plain one. And the gfx950 file got the code without the comments the gfx942 twin got (sinks tensor and the skip at L214) — they're maintained as clones, so the next person to touch one will wonder.

@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch from ed72ae8 to 7d1b60e Compare July 28, 2026 16:44
@arai713
arai713 requested a review from a team as a code owner July 28, 2026 16:44
@github-actions github-actions Bot added the rocke-lib: attention Touches rocKE library attention/FMHA kernels label Jul 28, 2026
arai713 added 2 commits July 29, 2026 15:21
Guard the fp8 chunk-count assert and fp8_chunks_per_thread with KV_FP8,
to match the 3D builder. Add a codegen regression test covering the bf16
SWA-decode config (t=16, hd=64, threads=256).
Decode is memory-bound; num_warps=1 beats the shipped nw4 on gfx942 D64
with identical output. Apply only to decode (all_decode) so prefill keeps
nw4. Add sink and sliding-window support to the decode benchmark plus a
gpt-oss sink shapes file
arai713 added 2 commits July 29, 2026 15:21
Add attention-sink and sliding-window support to the gfx950 decode
benchmark plus a gpt-oss sink shapes file, mirroring the setup for
gfx942.
- gate the gfx942 D64 decode num_warps=1 selector on `not use_fp8`
  in Python + C++ selector.
- guard the gfx950 2D fp8-loader chunk-count assert behind KV_FP8,
  matching the gfx942 fix.
- decode benchmark backend gating (gfx942 + gfx950):
  * pass sinks + sliding-window through to AITER Triton instead of
    skipping sink shapes.
  * skip aoTriton and FlyDSL for sink / sliding-window shapes.
  * derive window_size from sliding_window instead of hardcoding (-1,-1).
- use dataclasses.replace for --batch/--dtype overrides so use_sinks and
  sliding_window are preserved.
- add selector unit test and harden the fp8-loader-assert regression
  test.
- results JSON records use_sinks/sliding_window as keys
- DecodeShape signature distinguishes sink and windowed shapes.
@arai713
arai713 force-pushed the users/arai/attention_sink_decode branch from 7d1b60e to baab3ab Compare July 29, 2026 19:26
AviralGoelAMD added a commit that referenced this pull request Jul 29, 2026
…fx942 (#9742)

Child of #9739 (the gfx942 `num_sms` device-parameter resolver +
split-KV segment bound).

## Why this exists (the accurate reason)
`num_sms` in #9739 is a **device-parameter selection**: it is the device
CU count, and it sets `target = num_sms*4`, the boundary where a decode
shape stops fitting the serial 2D path and should split across the
device (3D split-KV). So for any given shape, **whether it should route
2D or 3D is a function of the device parameter** — and it is both
**batch-dependent** and **device-dependent**:
- `num_2d <= target` → 3D (needs splitting to fill the device),
- `num_2d > target` → 2D (batch alone already fills the grid).

The existing decode corpus is **batch=1**, which is the hardest/most
latency-bound case (least parallelism) and the right stress case for the
kernel body — **but it is already routed 3D even at the stale
`num_sms=120`** (`num_2d≈8 ≪ 480`). So the corpus is a **no-op for the
routing change** and cannot exercise the 2D↔3D transition #9739 actually
moves. This is not a "realism gap" — it's that a device-parameter fix
can only act in the transition zone, which batch=1 is not in.

## Routing map (llama8b 32/8 D128, MI300X)
| batch | num_2d | @sms=120 | @sms=304 | effect of #9739 |
|---|---|---|---|---|
| 1–48 | ≤480 | 3D | 3D | already 3D — fix no-op on routing |
| 56–~120 | 481–1216 | **2D** | **3D** | **FLIP — the win (mis-routed on
a 304-CU device)** |
| 128+ | >1216 | 2D | 2D | grid already full — no-op |

## What this adds
`decode_batch_sweep_shapes.json` (gfx942) — **70 shapes**: `batch ∈
{8,16,32,64,128,256}` + a per-config flip-window batch, across 5 GQA
configs (Llama-3-8B/70B, Llama-3.1-405B, Qwen3-30B, Qwen3-235B),
D64/D128, kv {2048, 8192}, `seqlen_q=1`. This spans the whole routing
spectrum so the resolved parameter is validated end-to-end: shapes that
**flip** (the win), shapes **already 3D** (must exercise the segment
bound → stay flat), and shapes that **stay 2D** (controls).

## Coverage (verified with the real dispatcher, MI300X = 304 CU, sms
120→304)
- **24 flip 2D→3D** — the transition #9739 moves,
- **30 already-3D** — exercise the split-KV segment bound (must not
over-split → flat),
- **16 stay 2D** — controls (must be unaffected).

Dual-device sanity check (MI300X 304 + MI300A 228) confirmed the D128
flips win on **both** parts (10–21× decode, 1.1–2.4× q>1), with routing
correctly shifting with the device's CU count — i.e. the parameter
selection is right per device.

## Caveats (from review)
- **Benchmark input (vault P3):** `benchmark_decode_live` uses
`block_table = randint` (with replacement) → some fake L2 reuse. The
2D-vs-3D **speedup ratios** are unaffected (same inputs both sides), but
absolute HBM BW read from these shapes is understated. Use
distinct/disjoint pages for a true working-set BW measurement.
- **D64 shapes need #9391 on develop:** the `qwen3_30b_*` (D64)
mid-batch decode shapes hit the unguarded 2D fp8-loader assert
(`attention_tiled_2d.py:3150`, `fp8_total_chunks % THREADS`) that #9391
guards behind `KV_FP8`. On develop (without #9391) those flip shapes
crash in the 2D baseline; the D128 shapes are develop-clean.

## Notes
- **gfx942 only**, matching #9739's scope (gfx950/gfx1250 → AICK-1726).
Data-only change (a benchmark shapes list); no kernel/dispatch code.

Changes are behind the feature flag HIPKERNELPROVIDER_ENABLE_ROCKE which
is defaulted to OFF.

@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.

LGTM.

@arai713
arai713 merged commit 016ae70 into develop Jul 30, 2026
25 checks passed
@arai713
arai713 deleted the users/arai/attention_sink_decode branch July 30, 2026 19:40
yraparti pushed a commit that referenced this pull request Aug 4, 2026
…-batch decode (#9739)

## What was happening before (plain language)

Attention dispatch has a knob called **`num_sms`** — it tells the
dispatcher **how many compute units (CUs) the GPU has**. That number
decides two things:
1. **Which kernel to run** — a serial **2D** kernel, or a **3D
split‑KV** kernel that splits work across the whole GPU.
2. **How finely** the 3D kernel splits the KV cache.

`num_sms` was **hard‑coded to a stale `120`**, but an **MI300X (gfx942)
has 304 CUs**. So the dispatcher thought the GPU was less than half its
real size, and **medium/large‑batch decode that should have been split
across the device stayed stuck in the slow 2D kernel**, leaving most of
the GPU idle. Long‑context decode also under‑split.

## What we changed

The `num_sms` device resolver, a direct `target_ctas` knob, and a
segment-count bound:
1. **Resolve `num_sms` from the real device — only when verified
on-box.** For a gfx942 request built on a gfx942 box, read the live CU
count (304 MI300X / 228 MI300A) → the reroute win. Everywhere else —
non-gfx942 arches, or a gfx942 kernel built off-box / with no visible
gfx942 device — keep the legacy **120** (matches develop), so a
cross-compile never bakes the wrong device's count into the kernel.
*(Per review: the earlier hardcoded `304` off-box fallback was removed —
304 now comes only from the live query — and the `ROCKE_NUM_SMS` env pin
was removed; pin reproducibility via an explicit `num_sms` or the
`target_ctas` knob.)*
2. **Add a direct `target_ctas` spec knob.** A single
device-subscription target (concurrent workgroups) that drives 2D↔3D
routing and 3D segmentation. Default `0` → `num_sms * 4` (byte-identical
to before); set `>0` to pin the target directly, so tuners / benchmarks
/ hipDNN can select the fast path **without** a device CU count.
Consolidated behind one accessor (`_effective_target_ctas`) so routing
and segmentation share one source of truth.
3. **Bound the split‑KV segment count.** Raising `num_sms` alone made
the 3D kernel **over‑split** already‑3D shapes; we clamp the segment
count to the **pre‑bump baseline** where it over‑splits (gfx942 D128
decode ≤16384, and all q>1), leaving the cases that genuinely benefit
(D128 kv≥32768, D64, D256) uncapped.

## How it acts as an optimization
- The true CU count lets under‑filled decode grids **reroute 2D → 3D**,
filling all 304 CUs → large speedups.
- The clamp keeps that win **without** an over‑split penalty on
already‑3D shapes.
- **Correctness is untouched** — `num_sms` changes only launch geometry,
never math. Every result below was fp32‑gated.

---

## Benchmarks — **GPU: AMD Instinct MI300X (gfx942, 304 CUs)**,
bf16/fp16, before/after = `num_sms` 120 → 304, fp32‑gated

> **Measurement provenance (please read):** all on‑device numbers were
measured on the **#9391 (`users/arai/attention_sink_decode`) branch
base**, where this work was developed; this PR is now rebased onto
**develop**. Practical implications for a reviewer on develop:
> - The **Llama / Qwen** shapes are reproducible on develop (from
`decode_shapes.json`, `qwen3_30b_a3b_shapes.json`, and the batch sweep
in child PR #9742). The 2D "before" baseline on develop lacks #9391's
gfx942 2D `num_warps` tune, so the reroute ratios should be
**equal‑or‑larger** there.
> - The **gpt‑oss sink** rows depend on #9391's sink feature + shapes
file, which are **NOT on develop** — they are marked below and are
reproducible only on the #9391 base.

### Headline: mid‑batch decode flips — Llama / Qwen
(develop‑reproducible; child PR #9742 shapes)
These were stuck in the slow 2D kernel before; the resolver reroutes
them to 3D:
| shape | config | speedup |
|---|---|---|
| llama8b_dec_b80_kv8192 | 32/8 D128 | **14.56×** |
| qwen235b_dec_b100_kv8192 | 64/4 D128 | **13.49×** |
| llama8b_dec_b80_kv2048 | 32/8 D128 | **13.49×** |
| llama405b_dec_b50_kv8192 | 128/8 D128 | **13.47×** |
| llama405b_dec_b50_kv2048 | 128/8 D128 | **11.80×** |
| qwen235b_dec_b100_kv2048 | 64/4 D128 | **11.74×** |
| llama70b_dec_b67_kv8192 | 64/8 D128 | **11.20×** |
| llama70b_dec_b67_kv2048 | 64/8 D128 | **10.25×** |
| qwen30b_dec_b133_kv8192 | 32/4 D64 | **2.65×** |
| qwen30b_dec_b133_kv2048 | 32/4 D64 | **2.59×** |

**Mechanism (profiled on MI300X, `llama8b_dec_b80_kv8192`, distinct
block_table):**
| num_sms | path | grid | occ/CU | VALUBusy | HBM/launch | achieved BW |
GPU time |
|---|---|---|---|---|---|---|---|
| 120 | 2D | 512 | 0.70 | 0.8% | 2573 MiB | 0.16 TB/s (**3%** of 5.3
peak) | 17357 µs |
| 304 | 3D (seg16) | 6400 | 2.73 | 13.2% | 2612 MiB | 2.31 TB/s (**44%**
of peak) | 1187 µs |

The 14.6x is **pure device-fill, not fewer bytes** (~2.6 GiB moved
either way): the 2D grid of 512 leaves the 304 CUs almost idle (occ
0.70, HBM 3% utilized); the reroute to 3D fills the device (grid 6400,
occ 2.73, HBM 44%). `seg16` confirms the segment clamp is active (not
the naive 128). rocprofv3, one representative mid-batch D128 shape —
mechanism measured, not inferred.

### Prefill / spec‑decode flips (q>1, causal‑fp32‑gated; Llama/Qwen,
develop‑reproducible)
| shape | q | config | speedup |
|---|---|---|---|
| qwen30b_q4_b67 | 4 | 32/4 D64 | **2.06×** |
| llama405b_q2_b33 | 2 | 128/8 D128 | **1.95×** |
| qwen235b_q2_b67 | 2 | 64/4 D128 | **1.93×** |
| llama70b_q4_b33 | 4 | 64/8 D128 | **1.92×** |
| llama8b_q2_b67 | 2 | 32/8 D128 | **1.82×** |
| qwen235b_q4_b40 | 4 | 64/4 D128 | **1.70×** |
| llama405b_q4_b20 | 4 | 128/8 D128 | **1.68×** |
| qwen30b_q2_b100 | 2 | 32/4 D64 | **1.61×** |
| llama8b_q4_b50 | 4 | 32/8 D128 | **1.44×** |
| llama70b_q2_b50 | 2 | 64/8 D128 | **1.39×** |

### Existing corpus — 38 affected decode shapes (batch 1–2)
**Long‑kv / D64 re‑splits (Llama/Qwen, develop‑reproducible):**
| shape | speedup |
|---|---|
| decode_kv32768 (Qwen‑30B D64) | **1.26×** |
| decode_kv16384 (Qwen‑30B D64) | **1.21×** |
| llama405b_b1_kv32768 (D128) | **1.18×** |
| llama405b_fp16_b1_kv32768 | **1.06×** |
| decode_kv4096 / kv8192 (D64) | 1.01× / 1.00× |
| llama8b/70b/qwen235b_b1_kv32768 (bf16+fp16, ×6) | 0.97–0.99× (noise) |

**Neutralized by the clamp (kernel byte‑identical → flat, no regression)
— 24 shapes:** Llama‑8B/70B/405B & Qwen‑235B, D128, kv4096/8192/16384
(bf16+fp16), all 0.97–1.08× (noise on identical kernels).

**gpt‑oss sink flips — REQUIRES #9391 (not on develop), measured on the
#9391 base:**
| shape | speedup |
|---|---|
| full_sink_b64_kv2048 | 1.94× |
| full_sink_b64_kv8192 | 1.93× |

---

## Regressions
- **None after the fix** across **38 corpus + 28 mid‑batch/prefill = 66
shapes on MI300X** — zero real regressions, zero correctness failures.
- The *naive* version (bump `num_sms` **without** the clamp) regressed
**21 of 38** shapes, worst **0.50× (2× slower)** — exactly the
over‑split the clamp fixes.
- The `0.97–0.99×` readings are **measurement noise (±~3%)**: they sit
on **byte‑identical (clamped) kernels**, bf16/fp16 twins disagree
run‑to‑run, and a 3× re‑run of the suspect shape read 1.010 / 1.010 /
1.013×.

## Tests
Adds `tests/dispatch/attention/test_num_sms_resolver.py` — **9 CPU
tests**: resolution order (explicit / gfx942 device / MI300A=228 /
other-arch=120), routing scales with `num_sms`, segments stay bounded,
and `target_ctas` precedence (overrides `num_sms*4`, flips 2D→3D at
fixed `num_sms`, threads through `_problem`). Re-verified on **MI300X**:
byte-identity GREEN (66 families), installed-golden PASS, and a
same-node A/B confirming the `target_ctas` path is perf-neutral vs
pre-change (0 routing changes, 0.11% median timing diff, mid-batch win
intact); the knob validated on silicon (2D→3D at fixed `num_sms`, output
matches the 2D default, 14.1×).

## Scope / notes
- Validated on **MI300X** via the Python dispatch path. Auto-resolution
is **gfx942-only** (other archs keep 120 — future scope).
- **No environment variables**: the `ROCKE_NUM_SMS` pin was removed per
review; pin reproducibility via an explicit `num_sms` or the
`target_ctas` knob.
- The production **C++ C-ABI engine** hardcodes `num_sms=120` and has no
`target_ctas` field today (routing decided in Python); mirroring the
device resolver + `target_ctas` there is tracked as a companion
follow-up (**AICK-1856**).
- The split-KV clamp ceiling (`_PRE_BUMP_SMS=120`) is kept hardcoded
here; parametrizing it as a spec knob (reviewer request) is tracked as
**AICK-1857**.
- Rebased onto **develop**; the mid/large‑batch coverage is extended by
child PR **#9742**.

Changes are behind the feature flag HIPKERNELPROVIDER_ENABLE_ROCKE which
is defaulted to OFF.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants