Skip to content

fix(tensilelite): refuse kernel launches whose grid does not fit in 32 bits - #12283

Merged
tony-davis merged 14 commits into
developfrom
users/todavis_amdeng/rocm-31290-streamk-grid-overflow
Sep 28, 2026
Merged

tony-davis merged 14 commits into
developfrom
users/todavis_amdeng/rocm-31290-streamk-grid-overflow

Conversation

@tony-davis

@tony-davis tony-davis commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

JIRA ID : ROCM-31290

Motivation

hipBLASLt launches each GEMM kernel with a grid, the number of parallel work items the hardware should run. KernelInvocation holds that grid in a 64-bit type, but both launch entry points take 32-bit grid parameters. hipExtModuleLaunchKernel declares its globalWorkSize arguments as unsigned int, and the cluster path's gridDim fields are the same width. A grid too large to express was narrowed at the call rather than refused.

That produces two outcomes. A work-item count of exactly 2^32 narrows to zero, and HIP rejects the launch with an error. A count above 2^32 narrows to its low 32 bits, a small non-zero number, so the launch succeeds while only a fraction of the intended workgroups run. The output tiles the missing workgroups would have written keep whatever the buffer held before, and hipblasLtMatmul returns success.

Stream-K kernels reach this most easily. They normally size their grid to the compute-unit count, but several paths hand them one workgroup per output tile instead, and solution selection's LaunchLimits check exempts Stream-K.

Technical Details

SolutionAdapter::launchKernel now validates the grid and returns hipErrorInvalidValue when it does not fit, with a diagnostic naming the offending dimensions. The check runs first, before the code object is looked up, since that is the last point where the 64-bit values are still intact.

The two launch paths narrow different quantities, so each is bounded against the one it actually passes. hipDrvLaunchKernelEx takes the grid in workgroups and is bounded on numWorkGroups. hipExtModuleLaunchKernel takes work items and is bounded on numWorkItems. A single shared check would wrongly reject a cluster launch whose workgroup count fits even though the work-item product does not.

#12204 already fixed the most common way to get there, the tree-fixup fallback in getSKGridImpl(), which now launches cuCount * occupancy once the tile count reaches the thread-aware cap. The paths that remain include the insufficient-workspace fallback in resolveStreamKSettings(), K == 0, the data-parallel debug knob, and the skFixedGrid / skGridMultiplier overrides. The guard turns all of them from silent truncation into a loud error.

Earlier revisions of this PR also added a selection-time predicate, StreamKWorkgroupNumberCheck. It was removed in review. After #12204 it rejected Stream-K solutions that now run correctly, and a check that resolves the grid that will actually launch is a better fit for the selection side. That is left to a follow-up.

This changes host-side launch validation only. No kernel code or solution selection changes.

Test Plan

  • tensilelite-tests, tensilelite/tests/LaunchDimGuard_test.cpp. Six cases that drive SolutionAdapter::launchKernel directly with a synthetic KernelInvocation, so no GPU work is dispatched and no kernel needs to exist. They cover the 2^32 boundary, the silent case above it, the largest grid that must still pass, an oversized y dimension, and both directions of the cluster path's workgroups-versus-work-items distinction.
  • Full tensilelite-tests suite on gfx950 to check nothing else moved.

Not covered: rocblaslt's rocRoller custom kernels call hipExtModuleLaunchKernel directly and never reach SolutionAdapter. That path builds its grid from uint32_t tile counts, so its failure mode is arithmetic overflow rather than narrowing, which is tracked separately as ROCM-31230.

Test Result

tensilelite-tests on an MI355X (gfx950), MessagePack configuration: 500 ran, 497 passed, 3 skipped. The three skips are unrelated and also skip on develop: RangeLibraryTest.SpecificSizes/0, MXDataGenFP4Test.ZeroFrequencyWithinBounds/3, and DataInitializationReferenceRecompute.Gfx1250ReusesPreswizzledMXAcrossSolutions.

For context on impact, a hardware sweep against the TheRock 10.1.0a20260822 library (which predates #12204) found 39 Stream-K algorithms at M = 19,398,656, N = 256 that returned success with part of the output never written, 22 of them leaving exactly 2^32 elements unwritten. #12204 fixes most of those at that shape. The sweep was not rerun against a current library.

Submission Checklist

Risk level

Low (2/5). The guard is a few integer comparisons at the top of the launch path, and it only changes behavior for grids that could not have run correctly. Solution selection is unchanged.

tony-davis and others added 2 commits September 18, 2026 00:35
KernelInvocation carries its launch grid in dim3, which is vector3<size_t>,
but both launch APIs take 32-bit grid parameters: hipExtModuleLaunchKernel's
globalWorkSize arguments are unsigned int, and HIP_LAUNCH_CONFIG's gridDim
fields are too. A grid that does not fit was narrowed at the call rather than
refused.

That gives two outcomes. A work-item count of exactly 2^32 narrows to zero,
which HIP rejects, so the call fails loudly. A count above 2^32 narrows to the
low 32 bits, so the launch succeeds, a fraction of the intended workgroups
run, and every tile the missing workgroups would have written is left holding
whatever was there before. Measured on gfx950 at 256 threads, 2^24 + 16
workgroups wrapped to a 16-workgroup launch and left the other 16,777,216
workgroups' worth of D unwritten with no error raised.

Validate the grid in SolutionAdapter::launchKernel and return
hipErrorInvalidValue when it cannot be expressed. The check runs before the
code object is looked up, since a grid that cannot be expressed is impossible
whether or not the kernel loads, and that is the last point where the 64-bit
values are still intact.

The two launch paths narrow different quantities, so each is bounded against
the one it actually passes: hipDrvLaunchKernelEx enumerates the grid in
workgroups, so it is bounded on numWorkGroups, while hipExtModuleLaunchKernel
takes work items, so it is bounded on numWorkItems. A single shared check
would wrongly reject cluster launches whose workgroup count fits even though
the work-item product does not.

LaunchDimGuard_test drives launchKernel directly with a synthetic
KernelInvocation, so it needs no GPU work and no kernel to exist. It covers
the 2^32 boundary, the silent 2^24 + 16 case, the largest grid that must still
pass, an oversized y dimension, and both directions of the cluster path's
workgroups-versus-work-items distinction. That last pair needs to know how the
library was compiled, so the tests target picks up the cached
HIP_HAS_CLUSTER_LAUNCH result rather than repeating the feature test.

ROCM-31290

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

Stream-K normally sizes its grid to the compute-unit count, which stays small.
But getSKGridImpl() has launch-time paths that hand it one workgroup per output
tile instead, the dominant one being the tree-fixup bound:

    if(itersPerTile >= 65536 || itersPerWG >= 65536
       || (tiles * itersPerTile) >= 16777216)
        skGrid = tiles;

Once the tile count reaches 2^24 the third clause holds for any itersPerTile of
1 or more, so it fires for exactly the shapes that cannot survive it.

WorkgroupNumberCheck already bounds a tile-scaled grid for ordinary kernels,
but it exempts Stream-K unconditionally, on the reasoning that a Stream-K grid
is not tile-scaled. That reasoning does not cover these fallbacks, so nothing
bounded the grid at all, and the launch narrowed it instead.

Add StreamKWorkgroupNumberCheck, which bounds
ceil(freeSizeA/MacroTile0) * ceil(freeSizeB/MacroTile1) * batchSize at
UINT32_MAX / 256, so a 256-thread kernel's largest admitted grid is 2^32 - 256
work items. It is named apart from MAX_WORKGROUP_NUMBER despite the equal
magnitude, because that constant guards an unrelated fp32 conversion in the
hand-written custom kernels. The tile count uses integer ceiling division: a
float ceil() loses precision above 2^24 and would undercount tiles at the
boundary this exists to guard.

The bound assumes 256 threads, the dominant Stream-K configuration on gfx950
with 290 distinct macro tiles. It is exact there, conservative at 64 and 128
threads, and too permissive at 512. Across all 2,796 shipped logic files one
family binds tighter, MacroTile 448x128 at 512 threads, and reaching its wrap
point needs roughly 896 GiB of D. The launch guard added in the preceding
commit makes that case a loud failure rather than a silent one.

The predicate is checked for every Stream-K solution rather than only when a
fallback is predicted, because the reduction strategy that decides which path
a launch takes is not available at predicate time.

Contractions.py attaches it on the isStreamK arm that already skips the
ordinary WorkgroupNumberCheck. That arm has no test of its own; the coverage
here is the predicate's behavior, in Predicates_test.cpp.

ROCM-31290

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

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

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #12283      +/-   ##
===========================================
+ Coverage    70.43%   71.68%   +1.25%     
===========================================
  Files         2933     2849      -84     
  Lines       482485   468310   -14175     
  Branches     69520    69188     -332     
===========================================
- Hits        339819   335694    -4125     
+ Misses      118818   108992    -9826     
+ Partials     23848    23624     -224     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 48.80% <ø> (ø)
TensileLite-Unit 81.27% <ø> (ø)
hipBLAS 90.62% <ø> (ø) Carriedforward from 67c9d08
hipBLASLt 35.28% <ø> (+0.01%) ⬆️ Carriedforward from 67c9d08
hipCUB 82.68% <ø> (ø) Carriedforward from 67c9d08
hipDNN 86.98% <ø> (+0.33%) ⬆️ Carriedforward from 67c9d08
hipFFT 43.25% <ø> (-11.07%) ⬇️ Carriedforward from 67c9d08
hipRAND 76.12% <ø> (ø) Carriedforward from 67c9d08
hipSOLVER 68.96% <ø> (ø) Carriedforward from 67c9d08
hipSPARSE 86.99% <ø> (ø) Carriedforward from 67c9d08
rocBLAS 48.31% <ø> (+0.08%) ⬆️ Carriedforward from 67c9d08
rocFFT 51.59% <ø> (+3.71%) ⬆️ Carriedforward from 67c9d08
rocRAND 57.42% <ø> (ø) Carriedforward from 67c9d08
rocSOLVER 76.83% <ø> (ø) Carriedforward from 67c9d08
rocSPARSE 74.60% <ø> (-0.17%) ⬇️ Carriedforward from 67c9d08
rocThrust 93.15% <ø> (+14.81%) ⬆️ Carriedforward from 67c9d08

*This pull request uses carry forward flags. Click here to find out more.
see 206 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.

tony-davis and others added 2 commits September 18, 2026 19:23
The preceding commit noted that the isStreamK arm has no test of its own and
that the coverage there is the predicate's behavior in Predicates_test.cpp.
This closes that gap: the arm itself is now covered.

Add a real gfx950 Stream-K fixture chain to test_contractions_char.py,
reusing solution_pool_gfx950.yaml, which is already vendored for the
BenchmarkProblems solution-pool tests, so
test_problem_predicate_compound_streamk exercises CompoundPredicates()'s
isStreamK branch against a real derived solution state rather than a
hand-synthesized one.

The test asserts both directions of the branch: StreamKWorkgroupNumberCheck
is attached, and the ordinary WorkgroupNumberCheck is not. Both assertions
were checked by mutation. Removing the emission fails the first; removing the
isStreamK guard so both predicates attach fails the second.

Log this as DECISIONS.md D24: the first arm of the "Contractions matrix"
slice ADR 0007 deferred, landed via an already-vendored fixture instead of
the two alternatives that ADR explicitly rejected, vendoring a new fixture or
hand-synthesizing derived state.

ROCM-31290

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

The comment claimed the helper mirrors
ContractionProblemGemm::getNumTiles(sizeMapping, 1). It does not. getNumTiles
multiplies over every free and batch index and, when packBatchDims is set,
folds batch into the M or N extent before dividing by the macro tile. The
helper uses index 0 of each and multiplies batch in afterwards.

The two agree for the single-free-index, unpacked-batch case, which is what
reaches this predicate today, and WorkgroupNumberCheck makes the same
simplification, so the two predicates agree with each other on every shape.
That is the property worth recording, because a reader who trusts the word
"mirrors" would not re-derive it before changing either one.

Comment only; no behavior change. Also makes enableCluster const on the
cluster-launch path, matching the definition already used on the other side
of the #ifdef.

ROCM-31290

Co-authored-by: Cursor <cursoragent@cursor.com>
@tony-davis
tony-davis marked this pull request as ready for review September 18, 2026 19:24
@tony-davis
tony-davis requested review from a team as code owners September 18, 2026 19:24
Copilot AI lite review requested due to automatic review settings September 18, 2026 19:24
…rid-overflow

Resolves a DECISIONS.md conflict: the Gemm-From-Anywhere custom kernel
framework (#11155, 1833a35) landed D24 and D25 on develop while this
branch was adding its own D24. Renumbered this branch's entry to D26 and
placed it after develop's two, which are taken unchanged.

Only the numbering collided; the entries are unrelated.

Co-authored-by: Cursor <cursoragent@cursor.com>
@tony-davis tony-davis changed the title fix(tensilelite): bound Stream-K grids by the 32-bit work-item limit (ROCM-31290) fix(tensilelite): bound Stream-K grids by the 32-bit work-item limit Sep 18, 2026

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.

🟡 Changes recommended

Unresolved direct launch coverage, validation ordering, and multi-batch tile-count issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds Stream-K grid bounds to prevent 32-bit HIP launch overflow through launch validation and solution-selection predicates.

Changes:

  • Adds launch-dimension validation for regular and cluster paths.
  • Adds and serializes StreamKWorkgroupNumberCheck.
  • Adds unit, characterization, and decision-record coverage.
File summaries
File Reviewed changes
projects/hipblaslt/tensilelite/tests/Predicates_test.cpp Tests Stream-K predicate boundaries.
projects/hipblaslt/tensilelite/tests/LaunchDimGuard_test.cpp Tests launch-dimension guards.
projects/hipblaslt/tensilelite/tests/CMakeLists.txt Registers new tests.
projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/DECISIONS.md Documents characterization decisions.
projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/Contractions/test_contractions_char.py Tests predicate attachment.
projects/hipblaslt/tensilelite/Tensile/Contractions.py Attaches the Stream-K predicate.
projects/hipblaslt/tensilelite/src/hip/HipSolutionAdapter.cpp Validates launch dimensions.
projects/hipblaslt/tensilelite/include/Tensile/Serialization/ContractionPredicates.hpp Adds predicate serialization support.
projects/hipblaslt/tensilelite/include/Tensile/ContractionProblemPredicates.hpp Implements Stream-K tile bounds.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

… count

Three findings from the Copilot review of #12283, all verified against the
source before acting.

Move the launch-dimension check ahead of FindCodeObject. The comment claimed
the check ran "before the code object is looked up", but FindCodeObject() was
several lines above it; only the getKernel() function lookup came after. The
check is now the first thing in launchKernel, so an unexpressible grid costs
no module load. This also places it ahead of the m_debugSkipLaunch early
return, so that debug knob now reports the invalid grid instead of returning
success.

Count every batch index in StreamKWorkgroupNumberCheck::tiles. getNumTiles()
multiplies over the whole m_batchIndices loop while this took batchSize(0),
matching WorkgroupNumberCheck and the LeadingFree*SizesGreaterOrEqual
predicates. Those carry an assert(batchIndices().size() <= 1) that compiles
out of a release build, and undercounting admits a grid that then wraps,
which is the failure this predicate exists to prevent. Accumulating can only
raise the count, so it can only reject more. Covered by
StreamKWorkgroupNumberCheck_EveryBatchIndexCounted, built on a two-batch-index
problem: all batch indices give 2^26 tiles and reject, batchSize(0) alone
gives 2^20 and would admit.

Narrow the claim about hand-written custom kernels. rocblaslt's rocRoller
custom kernels call hipExtModuleLaunchKernel directly from
library/src/amd_detail/rocblaslt/src/rocroller/custom_kernels.cpp and never
reach SolutionAdapter, so this guard does not make that path loud. That path
builds its grid from uint32_t tile counts rather than narrowing a 64-bit
dim3, so its failure mode differs from the one guarded here; the comment now
says so rather than overclaiming coverage.

ROCM-31290

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

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.

🟡 Changes recommended

One or more issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/Contractions/test_contractions_char.py:157

  • The Test Plan says the Contractions.py isStreamK arm is not covered, but this newly added test explicitly exercises that arm and verifies the predicate swap. Please update the PR description so the stated coverage and review risk match the actual tests.
def test_problem_predicate_compound_streamk(problem_type_streamk, solution_state_streamk):
    # test_problem_predicate_compound above never exercises
    # CompoundPredicates()'s isStreamK branch (Contractions.py): the vendored
    # gfx942 fixture's solution has no StreamK field. This drives the same
    # call from a real Stream-K solution's derived state instead, which must
    # skip the ordinary WorkgroupNumberCheck and attach
    # StreamKWorkgroupNumberCheck in its place.
    tags = {
        p.tag
        for p in C.ProblemPredicate.CompoundPredicates(solution_state_streamk, problem_type_streamk)
    }
    assert "StreamKWorkgroupNumberCheck" in tags

projects/hipblaslt/tensilelite/tests/LaunchDimGuard_test.cpp:20

  • The statement that SolutionAdapter::launchKernel is where every launch passes through is inaccurate: this repository also has direct hipExtModuleLaunchKernel calls for custom kernels, as the implementation's caveat notes. Please limit this test comment to the ordinary SolutionAdapter path so it does not imply coverage of those unguarded launches.
// SolutionAdapter::launchKernel is the one place every launch passes through,
// so that is where the refusal lives.
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

@tony-davis
tony-davis marked this pull request as draft September 18, 2026 21:38
Copilot review follow-up on #12283, and a correction to f86cab9.

getNumTiles() multiplies every free size before dividing by the macro tile
(the m_freeIndicesA / m_freeIndicesB loops in ContractionProblem.cpp), while
tiles() divided freeSizeA(0) alone. The product of the free sizes is larger
than any one of them, so this under-counted, which is the unsafe direction:
the predicate admitted grids that then wrap.

Measured on a two-free-index contraction with freeSizeA {4096, 4096} and
freeSizeB 256 at a 16x16 macro tile. The real count is 16,777,216 tiles, one
past the bound, and the predicate computed 4,096 and accepted it.

f86cab9 fixed the same mistake for batch indices but asserted this
divergence could only over-count. That was wrong, and the comment said so in
the source. Both the code and the comment are corrected here: tiles() now
accumulates every free and batch index, leaving packBatchDims as the only
divergence from getNumTiles, which does over-count and so only rejects more.

Covered by StreamKWorkgroupNumberCheck_EveryFreeIndexCounted, sized to land
exactly one tile past the bound so it pins the boundary as well as the
accumulation. Mutation-confirmed: reverting to freeSizeA(0)/freeSizeB(0)
fails it and no other test.

ROCM-31290

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

Copilot review follow-up on #12283. The comment said the packBatchDims
divergence from getNumTiles could only over-count. That holds for modes 1 and
2, where batch folds into one extent before the macro-tile divide, but not for
mode 3, where getNumTiles folds batch into both M and N and so scales by batch
squared while this predicate scales by batch. One batch of 2 at a 16x16 tile
gives getNumTiles 1024 against 512 here, an under-count.

No code change. The predicate is serialized with MacroTile0/MacroTile1 only
and never sees sizeMapping, so it cannot read the packing mode, and
"conservatively reject mode 3" is not available to it either. Mirroring
getNumTiles means carrying the mode in the predicate's serialized value,
across the Python emission, the C++ struct and its serialization.

That is not worth doing for a mode that does not occur: packBatchDims is 0 in
all 77,421 solutions of the shipped gfx950 library. The launch guard added in
this PR is the backstop, since it bounds the grid it is actually handed rather
than a predicted one, so an under-count here surfaces as hipErrorInvalidValue
instead of silent truncation.

The comment now enumerates all four modes and says which one is unsafe, rather
than making a blanket claim. This is the second time that blanket claim has
been wrong, after the free-index case in 55c37f9.

ROCM-31290

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

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.

Copilot review overview

🔵 Needs a closer look

Add shared CI coverage for the affected gfx950 launch-dimension regression test before merging.

Review effort: Lite
Findings: None

Resolved since last review (1)

@tony-davis
tony-davis marked this pull request as ready for review September 18, 2026 23:23
@tony-davis
tony-davis requested a review from newling September 24, 2026 21:49
@tony-davis

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

…ng/rocm-31290-streamk-grid-overflow

# Conflicts:
#	projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/DECISIONS.md

Co-authored-by: tony-davis <7938412+tony-davis@users.noreply.github.com>

Copilot AI commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merged origin/develop into this branch and resolved the merge conflict in projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/DECISIONS.md. Fix is in commit 826828aa.

@tony-davis

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

…ng/rocm-31290-streamk-grid-overflow

# Conflicts:
#	projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/DECISIONS.md

Co-authored-by: tony-davis <7938412+tony-davis@users.noreply.github.com>

Copilot AI commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merged origin/develop again and resolved the merge conflict in projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/DECISIONS.md. Fix is in commit 67c9d086.

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

Nice find. I had a discussion with an AI about this, and learned quite a lot in the process.

While discussing with the AI, I had 2 ideas. The first was, isn't the best safety net position where the 64-bit integer is cast to a 32-bit integer? That resulted in this branch, which might be complementary to this: https://github.com/newling/rocm-libraries/tree/users/newling/launch-grid-narrowing-checks

The second was, the logic for stream-k in the case where workspace is too small and it falls back to 1-tile per workgroup, we might be able to unify the logic with the non-stream-k case. This resulted in this: https://github.com/newling/rocm-libraries/tree/users/newling/streamk-launch-limits which looks slightly simpler.

I will generate an AI review now.

(my summary of the PR, not part of review per se) Essentially the constraint imposed by the hip APIs is that the number of threads (or workgroups, depending on the API) in each dimension of a grid must be less then 2**32. Which when considered there are more than just 1 (x) grid is really ample! Unfortunately tensilielite squeezes all threads into the x dimension, and so the constraint becomes more than just theoretical. A further waste is that we know the number of threads will be divided by the number of threads per workgroup, so we're self-imposing a constraint on the GEMM sizes we can run that is narrower than it needs to be.... but, that is too much to try and fix here. This PR is (correctly) about making sure we fit within the constraints.

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

AI review: https://github.com/newling/agent-public-reviews/blob/main/rocm-libraries/review_pr12283.md

The only request in the above is for an additional test, which I think is worth looking at. Seems like something a mutation test might catch...

I'm approving this as is as it fixes a real issue without adding too much complexity. But I've left a lot of loose threads in my review, so am also happy to re-review this if you decide to deviate significantly from the current design @tony-davis

@tony-davis

Copy link
Copy Markdown
Contributor Author

Thanks James! I really appreciate you digging into this. Your summary is spot on too; squeezing everything into x makes the limit a lot more real than it needs to be.

On your first idea, I think that's the same check this PR already has, just in a different spot. I put it at the top of launchKernel mostly so LaunchDimGuard_test can drive it with a synthetic KernelInvocation. At the cast site it sits behind getKernel() and the debug-skip return, so the test would need a real kernel to reach it. I'd lean toward leaving it where it is.

On your second idea, you're right, and it made me realize I missed that LaunchLimits already existed and just skipped Stream-K. My StreamKWorkgroupNumberCheck basically re-derives a grid that calculateGrid already computes. Review found three counting bugs in it, which I think is the tell.

That said, your branch feels like more than this fix needs. So here's what I'd propose for this PR: drop StreamKWorkgroupNumberCheck entirely and just remove the Stream-K exemption from LaunchLimits. That's the if in Contractions.py and the streamK == 0 check in the C++ predicate. For Stream-K, calculateGrid gives the one-workgroup-per-tile grid, which is exactly the fallback that overflows. So it's the same conservative bound my predicate had. It uses the real thread count instead of assuming 256, it handles packBatchDims, and it adds no new predicate type or serialization. I think it's about ten lines plus a test.

Your full rework would then make a great follow-up. It's the more precise version, since it only rejects a Stream-K solution when the fallback actually happens. That wins back the tuned candidates the conservative bound gives up. I'd want to see a timing check on it, though. It adds a second resolveStreamKSettings() per Stream-K solution during selection, on top of the one WorkspaceCheck already does through requiredWorkspaceSize(). Would you want to take that one, since you've already written it?

Does that split sound OK to you?

@tony-davis

Copy link
Copy Markdown
Contributor Author

Quick update, and a correction to my last comment. Until I read your AI review, I hadn't caught that #12204 (merged Sep 18) already fixed the tree-fixup fallback. When the tile count hits the thread-aware cap, it now launches cuCount * occupancy instead of one workgroup per tile. So both my predicate and the LaunchLimits change I proposed above would now reject Stream-K solutions at 2^24+ tiles that actually work whenever there's enough workspace. Before #12204 that rejection was free. Now it throws away good candidates.

So I think the right shape for this PR is smaller still: just the launch guard. The remaining ways to get an oversized grid (the insufficient-workspace fallback, skFixedGrid, skGridMultiplier) then fail loudly with hipErrorInvalidValue instead of silently leaving output unwritten, which is the actual ROCM-31290 bug. I'll drop StreamKWorkgroupNumberCheck and its tests, and update the description, including the stale tree-fixup claim your review flagged. That makes the precision-test finding moot, but thanks for catching it. You were right that it never crossed the boundary.

That leaves your streamk-launch-limits branch as the real selection-side fix, since it resolves the grid that will actually launch, workspace fallback included. I'd still love for you to take it as the follow-up. Since this is a real change from what you approved, I'll re-request your review once it's pushed.

…nch guard

#12204, merged after this PR was opened, changed the tree-fixup fallback in
getSKGridImpl(). Once the tile count reaches (UINT32_MAX + 1) /
threadsPerWorkGroup, it now launches cuCount * occupancy instead of one
workgroup per tile. That was the main path StreamKWorkgroupNumberCheck existed
to keep selection away from. With it fixed, the predicate rejects every
Stream-K solution at or above that tile count, including ones that now run
correctly whenever the workspace fits, so it throws away working candidates.

Remove the predicate, its serialization and Python emission, its gtests, the
characterization test for the isStreamK arm, and the DECISIONS.md entry. This
also drops the float-precision test that review found never crossed the
boundary.

The launch guard in SolutionAdapter::launchKernel stays and is now the whole
fix. Paths that still hand Stream-K one workgroup per output tile (the
insufficient-workspace fallback in resolveStreamKSettings(), K == 0, the
data-parallel debug knob) and the skFixedGrid / skGridMultiplier overrides
fail with hipErrorInvalidValue instead of launching a truncated grid. Its
comments now say that instead of pointing at the removed predicate.

A selection-time check that resolves the grid that will actually launch,
workspace fallback included, is the better fix on the selection side. It is
left to a follow-up; see the review discussion on #12283.

tensilelite-tests on gfx950 (MI355X): 500 ran, 497 passed, 3 skipped (the
same three noted in the PR description).

ROCM-31290

Co-authored-by: Cursor <cursoragent@cursor.com>
@tony-davis tony-davis changed the title fix(tensilelite): bound Stream-K grids by the 32-bit work-item limit fix(tensilelite): refuse kernel launches whose grid does not fit in 32 bits Sep 25, 2026
@tony-davis
tony-davis requested a review from newling September 25, 2026 21:04
@tony-davis
tony-davis merged commit 3d5094c into develop Sep 28, 2026
164 of 172 checks passed
@tony-davis
tony-davis deleted the users/todavis_amdeng/rocm-31290-streamk-grid-overflow branch September 28, 2026 21:49
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

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.

5 participants