[Dialect][Perf] Don't merge mixed static/runtime offsets on LDS pointers - #914
Open
Arist12 wants to merge 1 commit into
Open
[Dialect][Perf] Don't merge mixed static/runtime offsets on LDS pointers#914Arist12 wants to merge 1 commit into
Arist12 wants to merge 1 commit into
Conversation
Contributor
Author
|
Additional evidence, in case it is useful for review: since the headline numbers in the description are all register/spill counters, here is the rest of the emitted ISA for the same kernel (N=8192, plain
The MFMA and |
The nested-add_offset merge in MemrefLowering.td folds a compile-time offset into a runtime one before the getelementptr is formed. On a shared (LDS) pointer that hides the constant inside a dynamic index, so the AMDGPU backend can no longer sink it into the ds_read offset: immediate and every access has to materialize its own base address. In a kernel with many live LDS reads that register scatter becomes spilling. Guard the merge so that on a shared pointer it only fires when both offsets are of the same kind: constant with constant is pure constant folding, runtime with runtime loses no constant. A mixed chain is left nested, in either operand order, which keeps the compile-time part a separate getelementptr index the backend can still fold. Pointers in other address spaces are unaffected. On the GQA sliding-window attention kernel from the issue (MI355X, gfx950), the plain add_offset form goes from 44 VGPR spills and 71 scratch operations to none, from 38 distinct ds_read base registers to 3, and from 256 to 242 VGPRs. It now matches the hand-inserted recast_iter workaround on every counter, so that workaround is no longer needed: N plain before plain after 4096 0.0916 ms 352 TF 0.0741 ms 436 TF 8192 0.1752 ms 381 TF 0.1376 ms 485 TF 16384 0.3500 ms 387 TF 0.2780 ms 488 TF Fixes ROCm#898 Signed-off-by: Arist12 <ykzhang@cs.wisc.edu>
Arist12
force-pushed
the
fix/898-lds-offset-merge
branch
from
July 28, 2026 04:40
6f13532 to
87424b7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The nested-
add_offsetmerge inMemrefLowering.tdfolds a compile-time offset into a runtime one before thegetelementptris formed. On a shared (LDS) pointer that hides the constant inside a dynamic index, so the AMDGPU backend can no longer sink it into theds_read ... offset:immediate and every access has to materialize its own base address. In a kernel with many live LDS reads that register scatter becomes spilling.This adds a single guard to that pattern: on a shared pointer, merge only when both offsets are of the same kind — constant with constant (pure constant folding) or runtime with runtime (nothing constant is lost). A mixed chain is left nested, in either operand order. Pointers in other address spaces are unaffected.
Fixes #898.
Motivation
Reported in #898 with a GQA sliding-window attention kernel. Written the natural way it spills 44 VGPRs and issues 71 scratch operations in its inner loop; inserting an
fx.recast_iterbetween the twoadd_offsetcalls — a no-op for the address arithmetic that happens to block the merge — removes all of it and runs ~26% faster. Kernel authors should not need to know about that workaround.Changes
include/flydsl/Dialect/Fly/IR/FlyOps.td: newFly_MergeableOffsetChainconstraint — the pointer is not shared/LDS, or both offsets have the same staticness.include/flydsl/Dialect/Fly/Transforms/MemrefLowering.td: the existing nested-offset pattern gains that constraint. The rewrite itself is unchanged and no new pattern is added, so the diff is one guard plus a comment.tests/mlir/Transforms/layout_lowering.mlir: merge-vs-leave-nested at theflylevel, for both operand orders and for global pointers.tests/mlir/Conversion/add_offset_reassociation.mlir(new): the resulting address formation after--convert-fly-to-rocdl, i.e. the shape the backend actually sees.tests/kernels/test_lds_offset_chain.py(new): device test running both mixed orderings plus the already-merged control, checking the values read against a torch reference.Performance (MI355X, gfx950, ROCm 7.2)
Reproducer from the issue, B=1, window (512,0), D=128, H=32, H_KV=16, bf16. "plain" is the natural nested
add_offsetform; "recast" is therecast_iterworkaround.Across shapes, plain form before → after: 4096
0.0916 → 0.0741 ms(352 → 436 TFLOPS), 81920.1752 → 0.1376 ms(381 → 485 TFLOPS), 163840.3500 → 0.2780 ms(387 → 488 TFLOPS). The plain form now matches the workaround on every counter.Testing
layout_lowering.mlir, 5 in the new conversion test, 3 device cases intest_lds_offset_chain.py. MLIR FileCheck suite 36 → 37 files, all passing. The new conversion test was confirmed to fail against an unpatchedfly-opt, and the device test to fail if an address component is dropped, so neither passes vacuously.RUN_TESTS_FULL=1 scripts/run_tests.shon an unpatched tree and on this branch, same machine: 2248 passed / 0 failed and 2251 passed / 0 failed respectively, the difference being exactly the three new device cases. (Multi-GPU tests excluded; they need more than one visible device.)Dependencies
Breaking Changes
None. The guard only declines a merge; it introduces no new rewrite, and non-shared address spaces are untouched.
Made with Cursor