Port sliding-window attention follow-up fixes - #2450
Conversation
5475de2 to
391ffcb
Compare
There was a problem hiding this comment.
Pull request overview
Ports sliding-window attention fixes across matching, tuning reconstruction, documentation, and boundary testing.
Changes:
- Reconciles KV-cache and sliding-window masks while preserving clip semantics.
- Defaults omitted runtime sequence positions to full-cache values.
- Adds inclusive-window documentation and multi-type GPU boundary coverage.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
mlir/utils/performance/perfRunner.py |
Documents tuning reconstruction defaults. |
mlir/tools/rocmlir-gen/rocmlir-gen.cpp |
Adds defaults, validation, and clarified options. |
mlir/test/rocmlir-gen/problem-key.mlir |
Tests tuning-key reconstruction. |
mlir/test/rocmlir-gen/options.mlir |
Updates option validation tests. |
mlir/test/rocmlir-gen/attention-sliding-window.mlir |
Tests full-cache defaults. |
mlir/test/e2e/PrAttentionI8.toml |
Adds I8 boundary coverage. |
mlir/test/e2e/PrAttentionF32.toml |
Adds F32 boundary coverage. |
mlir/test/e2e/PrAttentionF16.toml |
Adds F16 boundary coverage. |
mlir/test/e2e/PrAttentionBF16.toml |
Adds BF16 boundary coverage. |
mlir/test/Conversion/TosaToRock/tosa-to-rock-attention-sliding-window-neg.mlir |
Verifies lower-only masks remain explicit. |
mlir/lib/Conversion/TosaToRock/TosaToRock.cpp |
Reworks mask, broadcast, and clip matching. |
mlir/include/mlir/Dialect/Rock/IR/RockOps.td |
Documents inclusive window semantics. |
Suppressed comments (1)
mlir/lib/Conversion/TosaToRock/TosaToRock.cpp:2393
- The sliding-window path has the same invalid-clip fallthrough: a negative-bound clip makes
tryClipPatternfail, butgetValueSkippingbelow still skipsminimum/maximumviaseqLenSkip. If the KV-cache mask uses the same clip, both paths report the same unclipped block argument and empty bounds, allowing the masks to fold into unsigned Rock masking with changed semantics. Treat a recognized-but-unsupported clip as a match failure instead of continuing.
auto maybeClip = tryClipPattern(seqLenCandidate);
if (succeeded(maybeClip)) {
seqLenCandidate = maybeClip->input;
clipMin = maybeClip->clipMin;
clipMax = maybeClip->clipMax;
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Verdict: COMMENT · Findings: 5 (0 Critical, 1 Major, 4 Minor)
Scope
Ports six sliding-window attention follow-up fixes: reconciling clip-bound detection with the KV-cache matcher in TosaToRock.cpp, peeling nested currentSeqLen broadcasts, requiring an independent KV-cache mask before folding a sliding-window lower bound, defaulting current_seq_len to the full-cache position in rocmlir-gen, clarifying the inclusive [max(0, P-W), P] / W + 1 window semantics in RockOps.td, and adding F16/BF16/F32/I8 E2E boundary coverage.
Findings
One Major and four Minor. The Major (mlir/lib/Conversion/TosaToRock/TosaToRock.cpp:2253) is that the new negative-clip-bound rejection does not actually reject anything: because both callers treat tryClipPattern failure as "no clip present" and then resolve through seqLenSkip, which itself contains tosa::MaximumOp/tosa::MinimumOp, the clamp is skipped over and the mask still folds with the clamp silently discarded. The Minors are a duplicated broadcast-peel loop (TosaToRock.cpp:2282), a dead sequenceLengthK > 0 && sub-condition (mlir/tools/rocmlir-gen/rocmlir-gen.cpp:1365), and two comments that describe the wrong adjacent code (rocmlir-gen.cpp:1346, mlir/utils/performance/perfRunner.py:1751).
None of these are regressions relative to develop; the Major is a latent gap in newly added defensive code, so the verdict is COMMENT rather than REQUEST_CHANGES.
Notes
- Spot-checked the
RockOps.tddoc change against the matcher:greater(col, seqLen)andgreater(seqLen - W, col)both fold to-inf, so the attended range really is the inclusive[max(0, P-W), P], i.e.W + 1positions. The wording is correct. - Requiring an independent KV-cache mask (
TosaToRock.cpp:2683) also means a prefix-causal mask that co-occurs with a sliding-window mask but no KV-cache mask now fails to fold at all, rather than falling back to the oldseqLen = slidingWindowSeqLenpath. Returningfailure()is the safe choice here since the peel loop has already advanced the input, so this looks intentional — just noting the coverage consequence. - The new E2E configs check out:
159 - 64 = 95, which lands inside the[64, 128)N block forNPerBlock = 64, and theattn:v2perf configs match ones already used elsewhere in the same TOML files. tosa-to-rock-attention-sliding-window-neg.mlircovers mismatched seq-len and mismatched (both non-negative) clip bounds, but has no case with a negative clip bound — see the Major finding.
CI status
No checks are in the fail or cancel bucket. Python performance script tests and Python format/lint checks pass; Jenkins, C/C++ premerge checks, Build and Test, MIGraphX, Code coverage, and the parameter sweeps are still pending at review time.
Clip matching returned bounds while sequence-length recovery independently skipped min/max operations. That could detach a clip from its underlying currentSeqLen value or fold negative signed bounds into Rock's unsigned masking semantics. Return the unclipped input together with validated bounds, accept constants on either side of commutative min/max operations, and use the same clip reconstruction for KV-cache and sliding-window masks. Include the matcher declarations used for constant recognition. (cherry picked from commit ba1aab52f6a8903455d5a127869d72284a8fbced) Co-authored-by: Cursor <cursoragent@cursor.com>
MIGraphX may broadcast currentSeqLen through multiple multiply-by-one layers. Peeling only the outer broadcast left an intermediate tensor instead of the original block argument, so matching could fail or rebuild the wrong head shape. Peel every broadcast-only multiplication before resolving currentSeqLen, then reconstruct the required head broadcast from the validated block argument. (cherry picked from commit 3642fef9e4dc9f8a69e980047714bdb713e59e48) Co-authored-by: Cursor <cursoragent@cursor.com>
A sliding-window lower bound does not prove that keys after currentSeqLen are invalid. Folding that mask alone by adopting its operand as currentSeqLen introduced a KV-cache upper mask that was absent from the source IR. Require an independently matched KV-cache upper mask before folding the window. Otherwise retain the lower-bound select in the elementwise region, preserving the original semantics. Cover this behavior in the negative matcher test. (cherry picked from commit 105932404a1b7b264640f2664ac9798d8b151828) Co-authored-by: Cursor <cursoragent@cursor.com>
currentSeqLen is runtime data and is intentionally omitted from attention tuning keys, but rocmlir-gen required it whenever sliding_window_size was set. Reconstructing a serialized tuning problem therefore failed before generating valid attention IR. When the runtime position is absent, synthesize seq_len_k - 1 for every group. This represents a full cache without changing tuning identity. Update option, problem-key, bufferized generation, and performance-runner coverage accordingly. (cherry picked from commit a57d63d24488d94b9338ff61f4cb171224d02483) Co-authored-by: Cursor <cursoragent@cursor.com>
The existing wording described currentSeqLen as a sequence length and slidingWindowSize as a count, which obscured the inclusive endpoint and invited off-by-one interpretations. Document currentSeqLen as the last valid zero-based KV-cache position and W as the maximum look-back distance. The resulting inclusive range contains up to W + 1 key positions. (cherry picked from commit c7aed69fc75f39ed98bd4f36cb0534c36d21a1d7) Co-authored-by: Cursor <cursoragent@cursor.com>
Existing attention cases did not force the sliding lower bound to cross an N-block boundary, so regressions in the N-loop start adjustment could pass while the window mask remained inactive or inside the first block. Add random-data F16, BF16, F32, and I8 cases where currentSeqLen - windowSize is 95, inside the [64, 128) block. Use supported attn:v2 configurations so each case reaches GPU validation instead of failing during lowering. (cherry picked from commit b4b4f360426a55618ff4ecef6159ec383e2f9145) Co-authored-by: Cursor <cursoragent@cursor.com>
391ffcb to
9a80cd9
Compare
| return failure(); | ||
|
|
||
| KVCacheResult result; | ||
| Value seqLenCandidate = *maybeNonOne; |
There was a problem hiding this comment.
Dead store: seqLenCandidate is initialized from *maybeNonOne here and then unconditionally overwritten four lines below by seqLenCandidate = peelBroadcasts(seqLenCandidate);, so the initializer never reaches a read. Fold the two into one statement -- drop this line and write Value seqLenCandidate = peelBroadcasts(*maybeNonOne); at line 2287, keeping the explanatory comment directly above it. This also matches how trySlidingWindowPattern already does it at line 2384. (Checklist: Major / dead-code bullet -- flagged Minor because it is a single redundant assignment with no behavioral effect.)
There was a problem hiding this comment.
Verdict: COMMENT · New findings: 1 (0 Critical, 0 Major, 1 Minor)
Scope
Re-review of the sliding-window attention follow-up port at head 9a80cd9. Six commits touch the TOSA→Rock attention matcher (TosaToRock.cpp), the rock.attention op documentation (RockOps.td), rocmlir-gen tuning-key reconstruction, perfRunner.py config identity, and lit/E2E test coverage.
Findings
All five findings from the previous review are addressed at this revision:
- Tri-state clip handling:
tryClipPatternnow returnsFailureOr<ClipResult>and both callers reject negative bounds with a hardfailure()rather than silently dropping them; covered by the newsliding_window_kvcache_negative_clipcase. - Duplicated broadcast-peel loop: factored into
peelBroadcastsand used from bothtryKVCachePatternandtrySlidingWindowPattern. - Dead sub-condition
sequenceLengthK > 0 &&removed; the required-arg loop already rejects non-positive values. - The KV-cache-position rationale comment moved onto the
currentSeqLen.empty()default, with a separate rationale for the negativity check. - The
perfRunner.pycomment no longer mislabelssliding_window_sizeas runtime-only.
One new Minor issue: a dead store at mlir/lib/Conversion/TosaToRock/TosaToRock.cpp:2283.
Notes
Spot-checks that came back clean:
- The
W + 1window documentation inRockOps.tdmatches thegreater(P - W, col)lowering and theslidingWindowSize > seq_len_krejection inrocmlir-gen. currentSeqLenandslidingWindowSizeprint beforeqk = elementwisein the op'sassemblyFormat, so the newCHECK-NOTassertions in the negative lit tests genuinely constrain the attribute region.- The default-
current_seq_lenlit test is self-consistent:-g 2withseq_len_k 64yieldsmemref<2xi32>andarith.constant 63 : i32. - Hardcoded
-perf_config attn:v2:...in the E2E TOML files is an established pattern in these suites, not new.
The four-line negative-clip guard is now duplicated verbatim in both callers. That is below the threshold where a helper pays for itself, so no change requested.
Behavioral note worth recording: getSeqLenMask returning failure() for a sliding-window-only mask also forgoes folding an otherwise-recognizable prefixOffset from the same select chain. This is conservative and semantically correct, but the fusion-coverage cost is slightly wider than the sliding-window mask alone.
Pre-existing issues (out of scope)
tryClipPattern still conflates "no clip present" with "a clip is present but not in the recognized min(max(x, lo), hi) shape". Because seqLenSkip includes tosa::MaximumOp and tosa::MinimumOp, an unrecognized one-sided clamp such as tosa.minimum(seqLen, cap) is skipped through rather than rejected. The pre-PR code had the identical fall-through, so this is not introduced here — noting it only as a possible follow-up.
CI status
No checks in /tmp/pr/checks.json are in the fail or cancel bucket. "C/C++ premerge checks", "Python format and lint checks", and "Python performance script tests" are still in progress at review time.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2450 +/- ##
===========================================
+ Coverage 82.57% 83.55% +0.99%
===========================================
Files 120 121 +1
Lines 42852 43177 +325
Branches 7110 7185 +75
===========================================
+ Hits 35381 36076 +695
+ Misses 4815 4513 -302
+ Partials 2656 2588 -68
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Note to reviewers :
Review commit by commit.
Summary
current_seq_lendefault and document the inclusive position /W + 1window semanticsCommit breakdown
Reconcile sliding-window clipping with the KV-cache matcher
currentSeqLen, so the matcher could detach a clip from its operand or fold negative signed bounds into unsigned Rock masking.Fix sliding-window sequence-length reconstruction
currentSeqLenthrough multiple multiply-by-one layers, while the matcher peeled only the outer layer.Preserve sliding-window-only mask semantics
currentSeqLenthat did not exist in the source IR.Default sliding-window tuning to full cache
current_seq_lenis runtime-only and omitted from tuning keys, butrocmlir-genrequired it when reconstructing a problem withsliding_window_size.seq_len_k - 1per group represents a full cache and makes serialized tuning problems reproducible without changing tuning identity.Clarify attention window semantics
currentSeqLenlike a length andslidingWindowSizelike a count, leaving the inclusive endpoint ambiguous.currentSeqLenas the last valid zero-based position andWas look-back distance makes the effectiveW + 1range explicit.Add active sliding-window boundary coverage
[64, 128)block and use supportedattn:v2configurations to reach GPU validation.Behavioral note
A sliding-window lower mask without a separate KV-cache upper mask still folds into
rock.attention, but the lower-bound select remains in the QK elementwise region instead of becoming aslidingWindowSizeattribute. This conservative path preserves source semantics, though it may be less optimized for such models.Compatibility and scope
This ports the applicable review follow-ups from rocmlirTriton PR #356. rocMLIR already samples valid sliding-window KV-cache configurations in its attention sweep tooling, so the equivalent rocmlirTriton-specific sweep changes are not ported.
Test plan
ninja -C build check-rock-e2e: 636 passed, 8 unsupportedninja -C build check-rocmlir: 1,652 passed, 35 unsupported, 4 expected failures