Skip to content

fix(tensilelite): Do not overwrite logic-file tiles with a zero custom-kernel macrotile - #12599

Merged
dsaffars merged 1 commit into
developfrom
users/dsaffars/algomethod_floatingpoint_exception
Sep 28, 2026
Merged

dsaffars merged 1 commit into
developfrom
users/dsaffars/algomethod_floatingpoint_exception

Conversation

@dsaffars

Copy link
Copy Markdown
Contributor

JIRA ID : AIHPBLAS-4871

Motivation

Dense solution enumeration (hipblaslt-bench --algo_method 1 / geko search) dies with Floating point exception when the walked library contains a handwritten custom kernel whose macrotile cannot be inferred. Pinned-index (algo_method 2) and heuristic (algo_method 0) paths work because they never cost the zero-tile custom solution.

The crash is host-side integer divide-by-zero in ContractionProblemGemm::getNumTiles():

numWG.x = CeilDivide(numWG.x, sizeMapping.macroTile.x);

CustomKernels.py derives macrotile from MatrixInstruction / MIWaveTile in custom.config, then from an MTxxx token in the kernel name. Any custom kernel that has neither infers zeros, and _assignCustomKernelParameters then copied that over the tile already present on the solution from the logic YAML. The built device library stored sizeMapping.macroTile: [0, 0, 1] (or [0, 0, DepthU] when only DepthU is in custom.config).

Technical Details

In Solution._assignCustomKernelParameters, keep using CustomKernel.macrotile when every component is positive. If a component is missing or <= 0, keep the value already on state from the consuming logic file. Write the resolved [MacroTile0, MacroTile1, DepthU] back into both:

  • state["MacroTile0" / "MacroTile1" / "DepthU"] — what SizeMapping.ReadOriginalMacroTile serializes
  • state["CustomKernel"]["macrotile"] — what the C++ runtime reads in calculateTiles / workspace sizing

If neither source supplies a positive integer, raise at library-build time rather than emit a solution that divides by zero at selection time. Logic-file default DepthU: -1 is rejected as a tile.

Kernels that already declare a real macrotile (BBS / HHS / MX Stream-K custom kernels) are unchanged: a positive inferred tile still wins over the logic file.

This is a host-library-generation fix. Existing device libraries must be rebuilt for the affected custom solutions to pick up non-zero tiles.

Test Plan

  • PYTHONPATH=$PWD python3 -m pytest Tensile/Tests/unit/test_custom_kernel_host.py --noconftest in the dsgemm container (39 passed, including the four new cases below).
  • Rebuild Tensile device library after the change.
  • Re-run the crashing geko / hipblaslt-bench --yaml F8BS config (transA: T, transB: N, M: 21504, N: 4720, K: 5376, a_type/b_type: f8_r, c_type/d_type: bf16_r, algo_method: 1) — enumeration completes.

New unit coverage:

  • inferred [0,0,0] falls back to logic-file 256×256×128 and writes that back into CustomKernel.macrotile
  • a positive inferred macrotile still wins over the logic file
  • neither source supplying a tile raises RuntimeError
  • logic-file DepthU: -1 is not accepted as a tile

Test Result

Unit suite: 39 passed, 0 failed.

After device-lib rebuild, F8BS dense enumeration no longer SIGFPEs (same config that previously dumped core).

@therock-pr-bot

therock-pr-bot Bot commented Sep 25, 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 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

❌ Your project check has failed because the head coverage (48.80%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop   #12599   +/-   ##
========================================
  Coverage    69.94%   69.94%           
========================================
  Files         2933     2933           
  Lines       482194   482200    +6     
  Branches     69514    69516    +2     
========================================
+ Hits        337261   337272   +11     
+ Misses      120723   120720    -3     
+ Partials     24210    24208    -2     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 48.80% <ø> (+0.03%) ⬆️
TensileLite-Unit 77.57% <100.00%> (+<0.01%) ⬆️
hipBLAS 90.62% <ø> (ø) Carriedforward from f6be1af
hipBLASLt 35.27% <ø> (ø) Carriedforward from f6be1af
hipCUB 82.68% <ø> (ø) Carriedforward from f6be1af
hipDNN 86.95% <ø> (ø) Carriedforward from f6be1af
hipFFT 54.32% <ø> (ø) Carriedforward from f6be1af
hipRAND 76.12% <ø> (ø) Carriedforward from f6be1af
hipSOLVER 68.96% <ø> (ø) Carriedforward from f6be1af
hipSPARSE 86.99% <ø> (ø) Carriedforward from f6be1af
rocBLAS 48.31% <ø> (ø) Carriedforward from f6be1af
rocFFT 47.89% <ø> (ø) Carriedforward from f6be1af
rocRAND 57.42% <ø> (ø) Carriedforward from f6be1af
rocSOLVER 76.83% <ø> (ø) Carriedforward from f6be1af
rocSPARSE 74.76% <ø> (ø) Carriedforward from f6be1af
rocThrust 78.34% <ø> (ø) Carriedforward from f6be1af

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

Files with missing lines Coverage Δ
...lt/tensilelite/Tensile/SolutionStructs/Solution.py 61.39% <100.00%> (+0.06%) ⬆️

... and 1 file 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.

@talumbau talumbau left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding test coverage LGTM 👍

@dsaffars
dsaffars merged commit 2ab4cc9 into develop Sep 28, 2026
466 of 485 checks passed
@dsaffars
dsaffars deleted the users/dsaffars/algomethod_floatingpoint_exception branch September 28, 2026 09:17
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

jinchen62 added a commit that referenced this pull request Oct 7, 2026
#12797)

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
#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 #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 #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 |

- The fp16 GSU 16, 18 and 19 results are bit-identical to the pre-#11155
launch path, and `shortname0` at 8192×16×65536 matches the pre-#11155
library exactly (1.51036e-05).
- Enumeration and the 4096-solution heuristic request complete. Against
a `develop` library, this host returns `HIPBLAS_STATUS_INTERNAL_ERROR`
and logs `... runs with GSU 16 but its custom-kernel grid [TilesX,
TilesY, Batch] launches one GSU slice per tile`.

### 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-#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 #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

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

---------

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