Skip to content

feat(stinkytofu): per-window WMMA hide budget analysis - #11301

Merged
KKyang merged 1 commit into
developfrom
users/kkyang/wmma-hide-budget-prescan
Sep 1, 2026
Merged

feat(stinkytofu): per-window WMMA hide budget analysis#11301
KKyang merged 1 commit into
developfrom
users/kkyang/wmma-hide-budget-prescan

Conversation

@KKyang

@KKyang KKyang commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Groundwork for teaching the CDNA5 scheduler which WMMA windows can afford to give
up an issue slot and which cannot. This lands the measurement; nothing gates on it
yet, and it does not run unless asked for.

No behaviour change. The pre-scan is off by default, and with it on the scheduling
output is byte-identical -- it measures and reports, it does not decide.

Why a per-window budget

"Can this region hide its work" is the wrong question, because the work is not
interchangeable. A ds_load feeding WMMA 6 has to be issued before WMMA 6 whether
or not a shadow has room for it; an independent SALU can wait forever. So the
question each window answers is "may I issue more than my slot?".

analyzeWmmaHideBudget() reads the region DAG:

  1. Number the matrix ops in program order -- those are the windows. For each,
    capacityCycles is every window cycle after its own issue slot that
    blockedScaleMask does not reserve, and capacityValu the subset coIssueWindow
    also names.
  2. Give every node the index of the earliest WMMA that transitively depends on
    it -- its deadline. RegionDAG ids are program indices and its edges run
    strictly forward, so one reverse sweep settles every node, O(V+E).
  3. Charge the issue cycles of each filler to its deadline. Nodes no WMMA depends
    on are floating: they still compete for window space at pick time, but they
    can always be deferred, so they never force anything.
  4. Walk the deadlines in order. Where the shadow before WMMA i falls short, the
    shortfall is granted to window i-1, the latest window that can still meet the
    deadline, as extraIssue. Granting late is the tightest answer; a consumer may
    spread the same total earlier, which issues the loads sooner, but not later.

Demand is a deliberate lower bound: VALU is counted at its issueCycles even though
a VALU inside a window can cost more (computeValuAdvanceCycles walks to the next
co-issue bit), so an overrun is only ever demanded where one is owed beyond doubt.
Barriers are excluded -- they are not window fillers, and placing them is the
barrier-threshold work, not this.

The knob

dagFeatures.enableWmmaHideBudgetPrescan, default false, reachable only through
stinkytofu-opt --enable-wmma-hide-budget-prescan. There is deliberately no
ModuleOptions mirror, so TensileLite cannot turn it on -- same treatment as
mergeBarrierThreshold. The budget is a pure measurement so far, so a production
compile would pay for a verdict nobody reads.

Layout

The analysis is its own unit under analysis/asm/, next to the other asm analyses,
rather than more free functions in CDNA5.hpp -- which is already 2000 lines of
scheduler. The ready queue keeps only the gated call: the budget is a local in
onInitRegion, reported and dropped, because nothing in the pick paths reads it yet.
The follow-up that calls extraIssueFor() from pickOne() is what promotes it to
per-region state -- until then a member would be state nobody reads, holding
StinkyInstruction* past the region boundary. Its isBlockedCycle() now delegates to
isBlockedWindowCycle() in the analysis header, so the end-anchored-mask convention
has one definition instead of two; that helper is inline because advanceTime(),
computeValuAdvanceCycles() and freeCoIssueSpace() call it once per window cycle.

The queue needs the region DAG to answer "which loads does this WMMA wait on".
The pass already builds that graph and was dropping it on the floor; handing it
over means region pre-scans reason about the same graph the scheduler drains
instead of rebuilding a weaker view of it from def-use chains, which carry RAW
edges only and are function- rather than region-scoped. It is passed before the
region's policy edges are merged in, so a pre-scan sees real data dependencies
rather than heuristic orderings.

Rather than make that a fifth positional parameter, onInitRegion now takes a
RegionDependencies: the dependency edges the region already has (the DAG, in) and
the ones the queue would like added (requestedConstraints, out, previously its own
parameter). Both are edges of the same graph, which is what makes them one
argument rather than a bag. It also retires a hazard the old signature carried --
three of the five parameters were IRList::iterator, so transposing two of them
compiled silently. One override, one call site; RegionDAG stays forward-declared
in ReadyQueue.hpp to keep the include one-way.

Observability

  • --remarks reports only windows that owe an overrun, naming them.
  • --debug-pass StinkyDAGSchedulerPass dumps capacity/extraIssue per window.

Test plan

  • New dag_wmma_hide_budget_prescan.stir, four regions:
    • 20 ds_loads all feeding WMMA Change shared libs name #3, plain FP8: 3 windows x 7 = 21 cycles of
      shadow, they fit, region stays quiet.
    • Identical dependency structure on the scale pair: 3 x 6 = 18, they no longer
      fit, and window [hipDNN] Enablement #2 is told to overrun by 2. Same IR, only the opcode differs
      -- that isolates what the LD_SCALE blocked-cycle model costs when the work has
      a deadline.
    • tensor_load -> ds_load -> WMMA: the deadline reaches through LDS, not just
      registers (StinkyBuildImplicitDependencyPass materialises memory tokens as
      RegType::LDS pseudo-registers, so the DAG carries the edge), so the
      tensor_loads count as deadlined rather than floating.
    • v_wmma_f32_16x16x4_f32, which carries coIssueWindow 0x0000: capacityValu is 0
      there, but nothing was blocked, so the LD_SCALE remark must stay silent.
  • ctest 1130/1130, no failures (112 FileCheck tests included, -Werror build).
  • Verified off by default: without the flag the region emits no remarks, and
    --print-output is byte-identical with the flag on and off.
  • No existing test changed: the analysis gates nothing.

Notes for reviewers

  • The "lost every VALU co-issue slot" remark counts only windows that HAD co-issue
    slots and lost them to blockedScaleMask. capacityValu == 0 alone does not mean
    LD_SCALE took anything: v_wmma_f32_16x16x4_f32 carries coIssueWindow 0x0000, so it
    never had a VALU slot and its window still offers 15 issue cycles to SALU and
    memory. Pinned by the last function in the FileCheck test.

  • RegionDAG.hpp is private to the scheduler (it lives under src/, not the
    installed include tree), so the analysis reaches it the same way
    StinkyDAGSchedulerPass.cpp does: relatively. Only the .cpp needs it; the header
    forward-declares.

  • Unrelated but adjacent, and not addressed here: InsertCoexecHazardPass reads the
    unresolved getHwInstDesc()->coIssueWindow while the DAG uses the
    matrix-format-resolved inst->coIssueWindow. On gfx1250 FP4/FP4 those are 0x00C8
    and 0x0008, so it inserts two surplus v_nop per dependent WMMA pair.
    Pre-existing, worth its own fix.

A follow-up wires the budget into the pick paths and decides per window.

🤖 Generated with Claude Code

@KKyang KKyang changed the title feat(stinkytofu): per-window WMMA hide budget, and the LD_SCALE default back on feat(stinkytofu): per-window WMMA hide budget pre-scan Aug 26, 2026
@KKyang
KKyang marked this pull request as ready for review August 26, 2026 13:20
@KKyang
KKyang requested a review from a team as a code owner August 26, 2026 13:20
@KKyang
KKyang force-pushed the users/kkyang/wmma-hide-budget-prescan branch from e71a6d3 to 2154251 Compare August 26, 2026 13:20
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

❌ Your project status has failed because the head coverage (35.22%) 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   #11301      +/-   ##
===========================================
- Coverage    69.32%   69.32%   -0.00%     
===========================================
  Files         2810     2810              
  Lines       465520   465534      +14     
  Branches     68685    68687       +2     
===========================================
  Hits        322706   322706              
- Misses      119250   119264      +14     
  Partials     23564    23564              
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 38.33% <ø> (ø)
TensileLite-Unit 76.13% <ø> (-0.02%) ⬇️
hipBLAS 90.62% <ø> (ø) Carriedforward from b4f07c6
hipBLASLt 35.22% <ø> (ø)
hipCUB 82.68% <ø> (ø) Carriedforward from b4f07c6
hipDNN 86.85% <ø> (ø) Carriedforward from b4f07c6
hipFFT 43.78% <ø> (ø) Carriedforward from b4f07c6
hipRAND 76.12% <ø> (ø) Carriedforward from b4f07c6
hipSOLVER 68.92% <ø> (ø) Carriedforward from b4f07c6
hipSPARSE 86.99% <ø> (ø) Carriedforward from b4f07c6
rocBLAS 48.29% <ø> (ø) Carriedforward from b4f07c6
rocFFT 46.60% <ø> (ø) Carriedforward from b4f07c6
rocRAND 56.91% <ø> (ø) Carriedforward from b4f07c6
rocSOLVER 76.83% <ø> (ø) Carriedforward from b4f07c6
rocSPARSE 74.60% <ø> (ø) Carriedforward from b4f07c6
rocThrust 91.60% <ø> (ø) Carriedforward from b4f07c6

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

@KKyang
KKyang force-pushed the users/kkyang/wmma-hide-budget-prescan branch from 2154251 to 96d541e Compare August 27, 2026 00:04
@KKyang KKyang changed the title feat(stinkytofu): per-window WMMA hide budget pre-scan feat(stinkytofu): per-window WMMA hide budget analysis Aug 27, 2026
@KKyang
KKyang force-pushed the users/kkyang/wmma-hide-budget-prescan branch from 96d541e to 6b545d2 Compare August 31, 2026 07:53
@KKyang
KKyang requested review from a team as code owners August 31, 2026 07:53
@therock-pr-bot

therock-pr-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass
Forbidden Files ✅ Pass
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: shared/stinkytofu/include/stinkytofu/analysis/asm/WmmaHideBudgetAnalysis.hpp, shared/stinkytofu/include/stinkytofu/core/Types.hpp, shared/stinkytofu/src/analysis/asm/WmmaHideBudgetAnalysis.cpp, shared/stinkytofu/src/transforms/asm/StinkyDAGSchedulerPass.cpp, shared/stinkytofu/src/transforms/asm/dag/CDNA5.hpp (+2 more); no test file found
🔎 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?

@KKyang
KKyang removed request for a team September 1, 2026 00:55
@therock-pr-bot

therock-pr-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

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

Groundwork for teaching the CDNA5 scheduler which WMMA windows can afford to give
up an issue slot and which cannot. This lands the measurement; nothing gates on it
yet, and it does not run unless asked for.

No behaviour change. The pre-scan is off by default, and with it on the scheduling
output is byte-identical -- it measures and reports, it does not decide.

## Why a per-window budget

"Can this region hide its work" is the wrong question, because the work is not
interchangeable. A ds_load feeding WMMA 6 has to be issued before WMMA 6 whether
or not a shadow has room for it; an independent SALU can wait forever. So the
question each window answers is "may I issue more than my slot?".

analyzeWmmaHideBudget() reads the region DAG:

1. Number the matrix ops in program order -- those are the windows. For each,
   capacityCycles is every window cycle after its own issue slot that
   blockedScaleMask does not reserve, and capacityValu the subset coIssueWindow
   also names.
2. Give every node the index of the earliest WMMA that transitively depends on
   it -- its deadline. RegionDAG ids are program indices and its edges run
   strictly forward, so one reverse sweep settles every node, O(V+E).
3. Charge the issue cycles of each filler to its deadline. Nodes no WMMA depends
   on are floating: they still compete for window space at pick time, but they
   can always be deferred, so they never force anything.
4. Walk the deadlines in order. Where the shadow before WMMA i falls short, the
   shortfall is granted to window i-1, the latest window that can still meet the
   deadline, as extraIssue. Granting late is the tightest answer; a consumer may
   spread the same total earlier, which issues the loads sooner, but not later.

Demand is a deliberate lower bound: VALU is counted at its issueCycles even though
a VALU inside a window can cost more (computeValuAdvanceCycles walks to the next
co-issue bit), so an overrun is only ever demanded where one is owed beyond doubt.
Barriers are excluded -- they are not window fillers, and placing them is the
barrier-threshold work, not this.

## The knob

dagFeatures.enableWmmaHideBudgetPrescan, default false, reachable only through
stinkytofu-opt --enable-wmma-hide-budget-prescan. There is deliberately no
ModuleOptions mirror, so TensileLite cannot turn it on -- same treatment as
mergeBarrierThreshold. The budget is a pure measurement so far, so a production
compile would pay for a verdict nobody reads.

## Layout

The analysis is its own unit under analysis/asm/, next to the other asm analyses,
rather than more free functions in CDNA5.hpp -- which is already 2000 lines of
scheduler. The ready queue keeps only the gated call: the budget is a local in
onInitRegion, reported and dropped, because nothing in the pick paths reads it yet.
The follow-up that calls extraIssueFor() from pickOne() is what promotes it to
per-region state -- until then a member would be state nobody reads, holding
StinkyInstruction* past the region boundary. Its isBlockedCycle() now delegates to
isBlockedWindowCycle() in the analysis header, so the end-anchored-mask convention
has one definition instead of two; that helper is inline because advanceTime(),
computeValuAdvanceCycles() and freeCoIssueSpace() call it once per window cycle.

The queue needs the region DAG to answer "which loads does this WMMA wait on".
The pass already builds that graph and was dropping it on the floor; handing it
over means region pre-scans reason about the same graph the scheduler drains
instead of rebuilding a weaker view of it from def-use chains, which carry RAW
edges only and are function- rather than region-scoped. It is passed before the
region's policy edges are merged in, so a pre-scan sees real data dependencies
rather than heuristic orderings.

Rather than make that a fifth positional parameter, onInitRegion now takes a
RegionDependencies: the dependency edges the region already has (the DAG, in) and
the ones the queue would like added (requestedConstraints, out, previously its own
parameter). Both are edges of the same graph, which is what makes them one
argument rather than a bag. It also retires a hazard the old signature carried --
three of the five parameters were IRList::iterator, so transposing two of them
compiled silently. One override, one call site; RegionDAG stays forward-declared
in ReadyQueue.hpp to keep the include one-way.

## Observability

- --remarks reports only windows that owe an overrun, naming them.
- --debug-pass StinkyDAGSchedulerPass dumps capacity/extraIssue per window.

## Test plan

- New dag_wmma_hide_budget_prescan.stir, four regions:
  - 20 ds_loads all feeding WMMA #3, plain FP8: 3 windows x 7 = 21 cycles of
    shadow, they fit, region stays quiet.
  - Identical dependency structure on the scale pair: 3 x 6 = 18, they no longer
    fit, and window #2 is told to overrun by 2. Same IR, only the opcode differs
    -- that isolates what the LD_SCALE blocked-cycle model costs when the work has
    a deadline.
  - tensor_load -> ds_load -> WMMA: the deadline reaches through LDS, not just
    registers (StinkyBuildImplicitDependencyPass materialises memory tokens as
    RegType::LDS pseudo-registers, so the DAG carries the edge), so the
    tensor_loads count as deadlined rather than floating.
  - v_wmma_f32_16x16x4_f32, which carries coIssueWindow 0x0000: capacityValu is 0
    there, but nothing was blocked, so the LD_SCALE remark must stay silent.
- ctest 1130/1130, no failures (112 FileCheck tests included, -Werror build).
- Verified off by default: without the flag the region emits no remarks, and
  --print-output is byte-identical with the flag on and off.
- No existing test changed: the analysis gates nothing.

## Notes for reviewers

- The "lost every VALU co-issue slot" remark counts only windows that HAD co-issue
  slots and lost them to blockedScaleMask. capacityValu == 0 alone does not mean
  LD_SCALE took anything: v_wmma_f32_16x16x4_f32 carries coIssueWindow 0x0000, so it
  never had a VALU slot and its window still offers 15 issue cycles to SALU and
  memory. Pinned by the last function in the FileCheck test.

- RegionDAG.hpp is private to the scheduler (it lives under src/, not the
  installed include tree), so the analysis reaches it the same way
  StinkyDAGSchedulerPass.cpp does: relatively. Only the .cpp needs it; the header
  forward-declares.
- Unrelated but adjacent, and not addressed here: InsertCoexecHazardPass reads the
  *unresolved* getHwInstDesc()->coIssueWindow while the DAG uses the
  matrix-format-resolved inst->coIssueWindow. On gfx1250 FP4/FP4 those are 0x00C8
  and 0x0008, so it inserts two surplus v_nop per dependent WMMA pair.
  Pre-existing, worth its own fix.

A follow-up wires the budget into the pick paths and decides per window.
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