Skip to content

fix(hipblaslt): launch every GSU slice of hand-written split-K kernels - #12797

Merged
jinchen62 merged 4 commits into
developfrom
users/jinchen62/rocm-31692-gsu-grid
Oct 7, 2026
Merged

jinchen62 merged 4 commits into
developfrom
users/jinchen62/rocm-31692-gsu-grid

Conversation

@jinchen62

@jinchen62 jinchen62 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

JIRA ID : ROCM-31692

Motivation

Since #11155, hand-written custom kernels launch through generateCustomCall. When a kernel's custom.config has no CustomKernel: block, CustomKernels.py infers one, and for kernels that take no NumWorkGroups argument it inferred the grid [TilesX, TilesY, Batch]. generateSingleCall, which launched these kernels before #11155, multiplies Y by GSU. The inferred grid does not, so every tile launched only one of its GSU slices.

This affects 11 gfx942 kernels (198 solutions): the nine CustomGSUs_* kernels (FreeSize logic, GSU 2–32) and Custom_Cijk_Ailk_Bljk_F8NH_HHS_BH_Bias_AS_SAB_SAV_shortname0/1_gfx942 (Equality logic, GSU 16 and 2). Both failure modes return HIPBLAS_STATUS_SUCCESS:

  • The CustomGSUs_* kernels use MultipleBufferSingleKernel, where the last workgroup to reach a tile reduces into D. With one slice launched that never happens, so D keeps whatever it held (norm_error 1, or NaN).
  • shortname0/shortname1 use MultipleBuffer, where the PostGSU kernel sums every workspace slice, but only one was written. Since fix(tensilelite): Do not overwrite logic-file tiles with a zero custom-kernel macrotile #12599 fixed their zero macro tile, the default heuristic selects them at 8192×16×65536 and 16384×32×4096 (fp8 × fp16 with bias or scale A/B) and returns a wrong D (norm_error ≈ 0.96 and ≈ 0.7) where it used to SIGFPE.

With TENSILE_DB=0x40, a GSU 16 fp16 kernel at 4096×16×2048 receives GSU 16 in internalArgs but launches Num work groups: 64, 1, 1 where 64, 16, 1 is needed.

Technical Details

  • Grid: Add CustomGridSize::TilesYGSU, which launches tiles.y × GSU work groups using the runtime-effective GSU (the same value encoded in internalArgs and the SplitK argument). CustomKernels.py now infers [TilesX, TilesYGSU, Batch] for kernels without a NumWorkGroups argument. That is the grid generateSingleCall gave them before feat(hipblaslt): Gemm-From-Anywhere custom kernel framework V1 #11155, and it is identical to TilesY at GSU 1.
  • Build-time guard: Solution._assignCustomKernelParameters fails library generation when a hand-written kernel can run with a GlobalSplitU other than 0 or 1 (including -1, auto) but its grid is sized from tile counts alone (no GSU or persistent term), the same rule generateCustomCall applies at launch. No hipBLASLt or hipSPARSELt logic file trips it. For such grids it also clears SupportUserGSU, so checkInternalArgumentsSupport rejects a runtime GSU override during solution selection. This only affects the explicit-grid external kernels (aiter, CK, RRGEMM, Triton, wave), none of which is referenced by shipped logic.
  • Runtime guard: generateCustomCall throws when the runtime GSU is above 1 and the grid is sized from tile counts alone, so a library built before this change returns HIPBLAS_STATUS_INTERNAL_ERROR instead of success.
  • Logging: The generic std::exception handlers in runContractionProblem and makeArgument now log_error the message (shown with HIPBLASLT_LOG_LEVEL=1). They previously discarded it.

Device libraries must be rebuilt to pick up the new grid, as with #12599. A host without TilesYGSU fails to load a library that uses it (the msgpack reader reports "Enum not found") rather than misreading it.

Test Plan

  • TensileLite Python unit tests for custom kernels, and the full tensilelite-tests suite. New CustomKernelTest cases cover TilesYGSU and the runtime guard.
  • MI300X (gfx942), hipBLASLt built with HIPBLASLT_LIBLOGIC_PATH limited to the five affected logic files:
    • every CustomGSUs_* solution by index, each in its own process with CPU verification (-v): fp16 → fp32 with bias at 4096×16×2048, and fp8 × fp16 at 4096×16×8192 with and without bias
    • default heuristic at both tuned fp8 × fp16 shapes, with bias and with --scaleA 1 --scaleB 1
    • --algo_method all at 4096×16×8192, and a 4096-solution heuristic request at 4096³
    • this host against a library built from develop
  • The same fp16 solutions on a library from before feat(hipblaslt): Gemm-From-Anywhere custom kernel framework V1 #11155, which still launches them through generateSingleCall.

Test Result

  • Python: 1002 passed, 3 skipped, 26 snapshots unchanged. tensilelite-tests: 503 passed, 8 skipped (existing skips).
  • The rebuilt library serializes all 198 split-K solutions as [TilesX, TilesYGSU, Batch], with no zero macro tile and user GSU still enabled. The launch grid is now 64×16×1 for the fp16 GSU 16 kernel and 16×16×1 for shortname0.
Case develop This PR
fp8 × fp16 CustomGSUs_* by index (188) all fail (norm_error 1 or NaN) all pass
fp16 → fp32 CustomGSUs_* by index (8) all fail (norm_error 1) 6 pass; the two GSU 17 entries fail (see below)
Default heuristic, 8192×16×65536, bias / scale A/B (shortname0) 0.963 / 0.966 1.51e-05 / 1.62e-05
Default heuristic, 16384×32×4096, bias / scale A/B (shortname1) 0.682 / 0.706 1.07e-05 / 1.43e-05

Not addressed here

  • GSU 17 kernel bug (ROCM-32355): The GSU 17 entries of the two fp16 CustomGSUs_* kernels give a different wrong answer on each run, and do the same on the pre-feat(hipblaslt): Gemm-From-Anywhere custom kernel framework V1 #11155 launch path. They still fail when K divides evenly into 17 slices and pass with a single tile, which points at the kernel. ROCM-31692's first acceptance criterion depends on that separate fix.
  • MultipleBufferSingleKernel workspace with a large ldd (ROCM-32356): AddressD points at the workspace while StrideD0 stays ldd, and the workspace is sized from tiles × GSU. Once ldd exceeds M, the partial-sum stores run past the end of the workspace: a fault if that memory is unmapped, silent corruption of whatever follows it otherwise. This predates feat(hipblaslt): Gemm-From-Anywhere custom kernel framework V1 #11155 and is reachable again now that every slice launches.
  • Bench harness (ROCM-32357): hipblaslt-bench --algo_method all reuses D across algorithms, which hid this regression.

Submission Checklist

Since #11155, hand-written custom kernels launch through generateCustomCall,
and for those without an explicit CustomKernel block the grid is inferred.
Kernels that take no NumWorkGroups argument were given
[TilesX, TilesY, Batch], which drops the GSU factor generateSingleCall
multiplies into Y, so each tile launched one GSU slice. On gfx942 this hits
the nine CustomGSUs_* kernels and the two fp8 x fp16 Equality shortname
kernels: MultipleBufferSingleKernel kernels never reach their last-workgroup
reduction and leave D unwritten, and MultipleBuffer kernels reduce a single
valid slice into D. Both return HIPBLAS_STATUS_SUCCESS. Since #12599 fixed
the zero macro tile, the default heuristic reaches this at the two tuned
fp8 x fp16 shapes instead of crashing.

- Add CustomGridSize::TilesYGSU (tiles.y * GSU) and infer
  [TilesX, TilesYGSU, Batch] for these kernels.
- Fail library generation when a hand-written kernel can run with a
  GlobalSplitU other than 0 or 1 but its grid has no GSU term, and clear
  SupportUserGSU for such grids so a runtime GSU override is rejected during
  solution selection.
- Throw from generateCustomCall when a split-K launch would use a grid with
  no GSU term, and log exceptions caught in runContractionProblem and
  makeArgument, so a library built before this fix returns an error instead
  of success.

Tested on MI300X with a library built from the five affected gfx942 logic
files. All 188 fp8 CustomGSUs_* solutions and 6 of the 8 fp16 ones pass CPU
verification when run by index, and the default heuristic passes at both
tuned fp8 x fp16 shapes. The two fp16 GSU 17 entries fail the same way on
the pre-#11155 launch path; that is a separate kernel issue. TensileLite
Python unit tests and tensilelite-tests pass.

JIRA ID: ROCM-31692

Co-authored-by: Cursor <cursoragent@cursor.com>
@therock-pr-bot

therock-pr-bot Bot commented Sep 30, 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

Copy link
Copy Markdown

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

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rary/src/amd_detail/rocblaslt/src/tensile_host.cpp 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #12797      +/-   ##
===========================================
- Coverage    71.93%   70.92%   -1.01%     
===========================================
  Files         2861     2944      +83     
  Lines       472441   485487   +13046     
  Branches     69773    70098     +325     
===========================================
+ Hits        339832   344316    +4484     
- Misses      109043   117231    +8188     
- Partials     23566    23940     +374     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 49.03% <ø> (ø)
TensileLite-Unit 83.28% <100.00%> (+<0.01%) ⬆️
hipBLAS 90.62% <ø> (ø) Carriedforward from 08f2e01
hipBLASLt 39.04% <0.00%> (-<0.01%) ⬇️
hipCUB 82.68% <ø> (ø) Carriedforward from 08f2e01
hipDNN 86.64% <ø> (+0.01%) ⬆️ Carriedforward from 08f2e01
hipFFT 54.32% <ø> (+10.89%) ⬆️ Carriedforward from 08f2e01
hipRAND 76.12% <ø> (ø) Carriedforward from 08f2e01
hipSOLVER 68.96% <ø> (ø) Carriedforward from 08f2e01
hipSPARSE 86.99% <ø> (ø) Carriedforward from 08f2e01
rocBLAS 48.25% <ø> (-<0.01%) ⬇️ Carriedforward from 08f2e01
rocFFT 47.11% <ø> (-0.51%) ⬇️ Carriedforward from 08f2e01
rocRAND 57.42% <ø> (-0.03%) ⬇️ Carriedforward from 08f2e01
rocSOLVER 76.83% <ø> (+<0.01%) ⬆️ Carriedforward from 08f2e01
rocSPARSE 74.75% <ø> (+0.12%) ⬆️ Carriedforward from 08f2e01
rocThrust 78.45% <ø> (-14.70%) ⬇️ Carriedforward from 08f2e01

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...cts/hipblaslt/tensilelite/Tensile/CustomKernels.py 97.42% <100.00%> (ø)
...lt/tensilelite/Tensile/SolutionStructs/Solution.py 64.39% <100.00%> (+0.06%) ⬆️
...rary/src/amd_detail/rocblaslt/src/tensile_host.cpp 43.46% <0.00%> (-0.02%) ⬇️

... and 173 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

jinchen62 and others added 2 commits October 5, 2026 14:45
Resolve the CustomGridSize conflicts with the persistent grid values added
on develop (PersistentGrid, PersistentWithBatch, PersistentNoBatch), keeping
their enum positions and appending TilesYGSU after them.

Adapt both GSU guards to develop's execution-policy model. They now fire
only for grids sized from tile counts alone, so persistent grids never trip
them, and the build-time guard detects persistent kernels with
isPersistent() instead of the legacy StreamK key.

Co-authored-by: Cursor <cursoragent@cursor.com>
…unch

The build-time GSU guard also exempted persistent kernels by execution
policy, while generateCustomCall looks only at the grid, so a persistent
kernel declared with a tile-count grid and a GlobalSplitU above 1 would
build and then fail at launch. Every persistent grid already fails the
tile-count test, so both guards now judge the grid alone.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jinchen62
jinchen62 requested review from newling and talumbau October 5, 2026 15:23

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

Hi Jinchen! This looks great to me. The fix lines up with the old generateSingleCall grid, the two guards use the same rule, and the tests would actually fail if someone reverted any piece of it. I also checked the red CI lanes: the ASAN gfx90a hang, the FFM gfx1250 failures, and the codecov project checks show up on other open PRs too, so I don't think they're yours. The rocSOLVER SYEVD_64 NaN on gfx950 is probably worth one rerun, just to be sure.

I'm approving, but I'd like two small things before merge (no need to rework the description):

  1. Trackers for the "Not addressed here" items. Could you file tickets for the GSU 17 kernel bug, the MultipleBufferSingleKernel workspace with a large ldd, and the bench harness reusing D, and link them in that section? The GSU 17 entries are still in the shipped FreeSize logic, so I'd like them to have a home. For the ldd one, my worry is that it could be an out-of-bounds write into the caller's workspace, not just a wrong answer, so it'd be good to capture that in the ticket (or tell me I'm wrong!).

  2. One sentence about gfx950. The inferred grid change also reaches 36 gfx950 custom-kernel solutions (the Custom_Cijk_Alik_Bljk_*_shortname*_gfx950 kernels). They're compiled with GSU 1 and advertise SupportUserGSU, so with a runtime GSU override they now launch GSU slices along Y, like they did before #11155. I bet that's a fix there too. Something like this in Technical Details would cover it: "On gfx950, 36 custom-kernel solutions compiled with GSU 1 advertise SupportUserGSU; with a runtime GSU override they now launch GSU slices along Y, as before #11155 (not verified on gfx950)." If you do get a quick gfx950 run with --gsu 4 -v, even better.

Thanks for the really thorough writeup and test evidence. It made this easy to review.

@jinchen62

Copy link
Copy Markdown
Contributor Author

Thanks @tony-davis !

  1. Filed ROCM-32355 (GSU 17), ROCM-32356 (MultipleBufferSingleKernel with a large ldd) and ROCM-32357 (bench D reuse), and linked them under "Not addressed here". You're right about the ldd one: with MultipleBufferSingleKernel and GSU > 1 the host points AddressD at the workspace but passes D's strides, while the workspace is sized from tiles × GSU. Once ldd exceeds M, the partial-sum stores run past the end of the workspace: a fault if that memory is unmapped, silent corruption of whatever follows otherwise. ROCM-32356 says so.

  2. gfx950: I checked those 36 solutions, and this PR doesn't change them. They all take a numWG argument (KernArgsVersion 2), so CustomKernels.py infers TilesXYBatchGSU for them, on develop and here; this PR only changes the branch for kernels without numWG. With a GSU override they already launch tiles × GSU work groups, and neither guard fires for them. The other nine are Stream-K and keep StreamKWithBatch. So I'd rather not add that sentence.

  3. rocSOLVER: the same SYEVD_64.strided_batched__float/53 NaN hit feat(stinkytofu): model frame-aware LDS ring dependencies #12643 on Oct 5, and the shard passed on JIRA ID : LCOMPILER-876 : Remove calls to hipconfig in all of rocm-libraries: read ROCm version from .info/version #7235 and fix(tensilelite): fix VGPR occupancy calculation on gfx11 #9708, so it looks intermittent. I'll re-run it.

@jinchen62
jinchen62 merged commit 6505db3 into develop Oct 7, 2026
143 of 149 checks passed
@jinchen62
jinchen62 deleted the users/jinchen62/rocm-31692-gsu-grid branch October 7, 2026 16:09
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

jinchen62 added a commit that referenced this pull request Oct 29, 2026
…client

hipblaslt-bench -v launches every candidate solution into the same D and checks
each one in turn, and so does the hipblaslt-test loop outside fast_check, but D
was only restored between solutions under --c_equal_d. A solution that never
stores D was therefore checked against the previous solution's result and
reported that solution's error. On gfx942 before #12797, three CustomGSUs
split-K kernels (indices 57 to 59) passed --algo_method all -v with the
1.8065e-05 norm_error of the generated solution checked before them, and failed
with norm_error 1 when run alone.

Fill D with all ones, NaN in the IEEE floating types, before each solution is
launched whenever a result check is on and D is not C: in the candidate-search
prepare hook and in the hipblaslt-test loop. fast_check keeps its own sentinel,
and --c_equal_d keeps restoring C from the host.

JIRA ID: ROCM-32357

Co-authored-by: Cursor <cursoragent@cursor.com>
Jinp800125 added a commit that referenced this pull request Oct 30, 2026
…14743)

ROCM-32357

## Motivation

All solutions in one hipblaslt-bench run write the same D buffer. D was
filled with a sentinel between solutions only under fast_check, and
restored only under c_equal_d. With -v (norm_check) and separate C and
D, a solution that leaves D unwritten was checked against the previous
solution's result and could pass; this hid the CustomGSUs_* kernels of
ROCM-31692 under --algo_method all.

## Technical Details

Fill D with the fast_check sentinel before every solution whenever
unit_check, norm_check, allclose_check or fast_check is on, in both the
verification loop and the timing path's prepareCandidate. Fill every D
buffer over its full size, so pointer-array batches and rotating-buffer
blocks are covered, and fall back to all-ones bytes for D types the
sentinel helper does not support instead of aborting the run.

## Test Plan

CI, tox and check before #12797

## Test Result

pass and as expected

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

Co-authored-by: victorwu <victor.wu2@amd.com>
jinchen62 added a commit that referenced this pull request Oct 31, 2026
…t never launches (#14647)

JIRA ID : ROCM-32357

## Motivation

`hipblaslt-bench` and `hipblaslt-test` share one test harness,
`testing_matmul.hpp`. It can run several solutions (kernel
configurations) for one matrix multiply and compare each result, the
output matrix D, with a CPU reference. All solutions write into the same
D buffer, so a solution that never stored D used to be checked against
the previous solution's result and pass. On gfx942 before #12797,
`hipblaslt-bench --algo_method all -v` passed three hand-written
`CustomGSUs_*` kernels this way. #14743 now fills D with a NaN sentinel
before each solution, but no test fails if that fill stops working.

## Technical Details

- A new test argument, `skip_launch_solution`, leaves the solution at
that position in the list unlaunched (C interface with strided batches
only), so that solution never writes D. After each solution's check, the
harness fails the case if the allclose check reported a solution other
than the unlaunched one or missed the unlaunched one, and also if the
unlaunched solution was never checked.
- The new per-PR (`pre_checkin`) case
`matmul_check_reports_unlaunched_solution` sets it for f32 and bf16 at
131×67×200, in the `hipblaslt-test` loop and in the bench's loop over
candidate solutions (`timing: 1`). It uses allclose alone because
`unit_check` and `norm_check` fail a case outright when D holds NaN.
Cases with `timing: 1` now get a `_TIMING` name suffix.
- Only test code changes; the library, its kernels and the clients'
behavior without the new argument do not. `Arguments`, the clients'
parameter struct, gains one `int32_t` field, added at the same position
in `hipblaslt_common.yaml`, from which `hipblaslt_gtest.data` is
generated at build time.
- This branch first carried its own fill of D. #14743 landed an
equivalent fill on develop, and the merge of develop keeps develop's
`fillDWithSentinel`, so this pull request now adds only the self-test.

## Test Plan

On MI300X (gfx942), with hipBLASLt built from this branch after the
merge of develop and `HIPBLASLT_LIBLOGIC_PATH` limited to six logic
files: fp8 × fp16 → f16, f16 → f32, grouped GEMM, aux output, and the
f32 and bf16 N/T grid-based files cut to the 8 solutions nearest
131×67×200.

- `matmul_check_reports_unlaunched_solution` with develop's fill and
with the fill disabled, plus the existing `fast_check_inject` self-test.
- Every solution with `-v`, to check that the fill passes correct
kernels: fp8 × fp16 at 4096×16×8192 with `--verify_strict`,
`--c_equal_d`, `--rotating 256` with timed iterations, `--api_method
mix` and `--HMM`; f16 → f32 at 4096×16×2048 with the C and C++
interfaces; f16 → f32 with pointer-array batches; grouped GEMM; aux
output.

## Test Result

| Case | Fill disabled | Develop's fill |
|---|---|---|
| `matmul_check_reports_unlaunched_solution`, 4 cases | 4 fail:
"solution 1 of 4 … was never launched, but D passed its checks" | 4
pass, 134 ms in total |

- With every solution launched, every check passes with develop's fill:
53 fp8 × fp16 solutions in each bench configuration (`--verify_strict`
exits 0), 39 f16 → f32 solutions with each interface, 37 with
pointer-array batches, and the single grouped-GEMM and aux-output
solutions. The `fast_check_inject` self-test passes.
- On the head before the merge, all four new cases ran and passed in
TheRock's gfx942 and gfx110X hipBLASLt lanes.
- Automated GitHub checks for the merged head are pending.

### Not addressed here

- Under `unit_check` and `norm_check`, the harness still does not reset
E (the aux output), the bias gradient or amaxD between solutions; only
`fast_check` fills them.
[ROCM-33792](https://amd-hub.atlassian.net/browse/ROCM-33792) tracks
this.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.


[ROCM-33792]:
https://amd-hub.atlassian.net/browse/ROCM-33792?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants