Skip to content

feat: add non-TN support for gfx950 mxfp4 with subtile impl - #11369

Open
WorldofKerry wants to merge 147 commits into
ROCm:developfrom
WorldofKerry:gfx950-transposed
Open

WorldofKerry wants to merge 147 commits into
ROCm:developfrom
WorldofKerry:gfx950-transposed

Conversation

@WorldofKerry

@WorldofKerry WorldofKerry commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

JIRA ID AIHPBLAS-4765

Motivation

Support non-TN mxfp4 GEMM for gfx950.

Technical Details

  • Add new subtile geometries for non-TN cases
  • Uses ds_read_b64_tr_b4 to transpose out of LDS
  • Avoids bank conflicts be either xor swizzle (for wg 1x1), or rearranging GR col to thread assignment (in other cases)
  • For uneven MT sizes (e.g. MT96, MT224, MT192), round up to nearest power-of-two MT size for GR to LDS (improves GR cache hits)
  • Update client to support non-TN scale data / pre-swizzle

Test Plan

Include test yamls:

  • Tensile/Tests/common/gemm/gfx950/subtile_mxfp4.yaml for coverage
  • Run MT256x256x256 for MAF perf.

Test Result

  • Tensile/Tests/common/gemm/gfx950/subtile_mxfp4.yaml passes
  • Good perf on MT256x256x256 on 4k by 4k by 32k problem.

@therock-pr-bot

therock-pr-bot Bot commented Aug 27, 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

therock-pr-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

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

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.98296% with 235 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...lelite/Tensile/Components/Subtile/SubtileGREmit.py 78.11% 82 Missing and 29 partials ⚠️
...lelite/Tensile/Components/Subtile/SubtileLREmit.py 85.49% 19 Missing and 9 partials ⚠️
...aslt/tensilelite/client/src/DataInitialization.cpp 0.00% 24 Missing ⚠️
...lt/tensilelite/Tensile/SolutionStructs/Solution.py 31.25% 18 Missing and 4 partials ⚠️
...blaslt/tensilelite/Tensile/KernelWriterAssembly.py 25.00% 18 Missing and 3 partials ⚠️
...lite/Tensile/SolutionStructs/Validators/Subtile.py 85.00% 7 Missing and 5 partials ⚠️
...lite/Tensile/Components/Subtile/SubtileGeometry.py 83.33% 4 Missing and 4 partials ⚠️
...ite/Tensile/Components/Subtile/SubtileLdsLayout.py 94.81% 2 Missing and 2 partials ⚠️
...te/Tensile/Components/Subtile/SubtileTLUSwizzle.py 97.96% 2 Missing ⚠️
...ects/hipblaslt/tensilelite/Tensile/KernelWriter.py 87.50% 1 Missing and 1 partial ⚠️
... and 1 more

❌ Your project check has failed because the head coverage (48.72%) 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   #11369       +/-   ##
============================================
- Coverage    70.90%   70.38%    -0.51%     
============================================
  Files         2847      218     -2629     
  Lines       466762    87239   -379523     
  Branches     68829    15919    -52910     
============================================
- Hits        330914    61400   -269514     
+ Misses      111922    21753    -90169     
+ Partials     23926     4086    -19840     
Flag Coverage Δ
TensileLite-CPP 48.72% <0.00%> (-0.04%) ⬇️
TensileLite-Unit 76.58% <81.65%> (+0.12%) ⬆️
hipBLAS ?
hipBLASLt ?
hipCUB ?
hipDNN ?
hipFFT ?
hipRAND ?
hipSOLVER ?
hipSPARSE ?
rocBLAS ?
rocFFT ?
rocRAND ?
rocSOLVER ?
rocSPARSE ?
rocThrust ?
Files with missing lines Coverage Δ
...ipblaslt/tensilelite/Tensile/Components/StreamK.py 80.94% <100.00%> (+0.03%) ⬆️
...e/Tensile/Components/Subtile/InstructionEmitter.py 90.34% <100.00%> (+0.03%) ⬆️
...ite/Tensile/Components/Subtile/SubtileScaleEmit.py 77.01% <100.00%> (+0.12%) ⬆️
...t/tensilelite/Tensile/Components/Subtile/Kernel.py 79.57% <98.18%> (+1.19%) ⬆️
...te/Tensile/Components/Subtile/SubtileTLUSwizzle.py 97.96% <97.96%> (ø)
...ects/hipblaslt/tensilelite/Tensile/KernelWriter.py 73.32% <87.50%> (-0.17%) ⬇️
...ite/Tensile/Components/Subtile/SubtileLdsLayout.py 94.81% <94.81%> (ø)
...lite/Tensile/Components/Subtile/SubtileGeometry.py 93.39% <83.33%> (+5.63%) ⬆️
...lite/Tensile/SolutionStructs/Validators/Subtile.py 85.00% <85.00%> (ø)
...blaslt/tensilelite/Tensile/KernelWriterAssembly.py 70.19% <25.00%> (-0.10%) ⬇️
... and 4 more

... and 2632 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.

@WorldofKerry WorldofKerry changed the title Gfx950 transposed feat: add non-TN support for gfx950 mxfp4 with subtile impl Aug 31, 2026
@WorldofKerry WorldofKerry changed the title feat: add non-TN support for gfx950 mxfp4 with subtile impl feat: add non-TN support for gfx950 fp4 with subtile impl Aug 31, 2026
@WorldofKerry WorldofKerry changed the title feat: add non-TN support for gfx950 fp4 with subtile impl feat: add non-TN support for gfx950 mxfp4 with subtile impl Aug 31, 2026
@WorldofKerry
WorldofKerry force-pushed the gfx950-transposed branch 3 times, most recently from 3ddf2e6 to 1015150 Compare September 15, 2026 22:27
@WorldofKerry
WorldofKerry requested a balanced review from Copilot September 16, 2026 22:11

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

The emitted transpose-load mnemonic is invalid and non-TN client references consume swizzled scales as though they were canonical.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Extends gfx950 MXFP4 subtile GEMM support from TN to NT, NN, and TT layouts.

Changes:

  • Adds TLU=1 geometry planning, LDS layouts, cooperative reads, and transpose loads.
  • Updates scale pre-swizzling and StreamK addressing.
  • Adds broad unit, characterization, and gfx950 correctness coverage.

Review assessment: The test breadth is strong, but unsupported ISA mnemonics and incorrect non-TN client reference scaling are blocking. The PR description also names the wrong test YAML and lacks reproducible test/performance evidence and work-item tracking.

Suggested PR Description Updates

  • Replace subtile_mxfp4_tlu1.yaml with subtile_mxfp4.yaml.
  • Add the exact gfx950 test commands and device results.
  • Include baseline versus updated MT256 performance measurements.
  • Link the corresponding work item and document the cross-architecture instruction risk.
File summaries
File Description
projects/hipblaslt/clients/common/src/mxDataGen.cpp Adds orientation-aware gfx950 scale pre-swizzling.
projects/hipblaslt/tensilelite/client/src/DataInitialization.cpp Allocates padded scale staging storage.
projects/hipblaslt/tensilelite/Tensile/Components/StreamK.py Updates swizzled-scale StreamK offsets.
projects/hipblaslt/tensilelite/Tensile/KernelWriter.py Applies centralized subtile LDS layouts.
projects/hipblaslt/tensilelite/Tensile/SolutionStructs/Solution.py Selects and validates TLU=1 stack geometry.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/Kernel.py Integrates cooperative TLU=1 planning.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileGeometry.py Adds cooperative-read geometry planning.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileGREmit.py Implements TLU=1 global-read emission.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileLREmit.py Implements transposed LDS reads.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileLdsLayout.py Centralizes operand and scale LDS allocation.
projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileTLUSwizzle.py Adds bank-conflict-avoiding layouts.
projects/hipblaslt/tensilelite/Tensile/Tests/common/gemm/gfx950/subtile_mxfp4.yaml Expands gfx950 layout and geometry coverage.
projects/hipblaslt/tensilelite/Tensile/Tests/unit/test_gr_coop_spread.py Tests cooperative-read planning.
projects/hipblaslt/tensilelite/Tensile/Tests/unit/test_subtile_stack_for_tile.py Tests stack selection and fallback.
projects/hipblaslt/tensilelite/Tensile/Tests/unit/characterization/_codegen/test_r5_subtile_kern_char.py Characterizes implemented DTL dispatch.
projects/hipblaslt/tensilelite/rocisa/rocisa/include/instruction/mem.hpp Defines the transpose-load instruction.
projects/hipblaslt/tensilelite/rocisa_stinkytofu_adaptor/rocisa_stinkytofu_adaptor/instruction.py Mirrors the instruction in the Python backend.
Review details
  • Files reviewed: 20/20 changed files
  • Comments generated: 3
  • Review effort level: Balanced

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

Comment thread projects/hipblaslt/clients/common/src/mxDataGen.cpp Outdated

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

The characterization golden update lacks the repository-required ADR documenting the intended behavior change.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 27/27 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Add the TLU=1 (NT) path for UseSubtileImpl kernels, where both operands
are free-dim contiguous in global memory. Global reads become a per-lane
K-ramp with a single wide buffer_load spanning the free dim, and
transpose local reads use ds_load_b64_tr_b4.

Also drop the StinkyTofu MatrixInstructionNaming pre-check (module,
validator wiring, and its tests) that this path supersedes.

Adds nt_fp4_bringup.yaml (gfx950 NT MXFP4 subtile bring-up).
The TLU=1 (NT) subtile global-read pointer update advanced the SRD by
DepthU * bpe bytes per main-loop iteration. For NT the free dim is
unit-stride and the K/unroll dim is strided, so one DepthU K-window is
DepthU * strideK * bpe bytes. The missing strideK factor only cancels
when the main loop runs once (K == DepthU), so any K > DepthU produced
incorrect results.

Scale the increment by the runtime K stride in the TLU=1 branch, keeping
a unit-stride fast path for the non-transposed operand.

Rename nt_fp4_bringup.yaml to f4_nt.yaml and sweep sizes covering
single/multi K-iteration and single/multi workgroup.
The NT (TLU=1) fp4 subtile path wrote each operand free-dim contiguous,
K-major, into LDS. The ds_read_b64_tr_b4 transpose reads then hit a 2-way
LDS bank conflict: the 32 lanes of each read half map onto only 32 of the
64 banks (see format.md analysis).

Apply a chunk-index XOR swizzle (chunk[6] ^= chunk[5]) plus an 8-byte pad
inserted between load-blocks. The XOR is an involution, so the GR write
(which global K-row each lane fetches) and the LR read (which chunk each
lane addresses) apply the same transform and the LDS image round-trips A
exactly. The transform is centralized in SubtileTLUSwizzle and keyed by
stack size (subtileShape[0]); only the 2x1 fp4 stack is wired, all others
keep baseline addressing.

Measured on gfx950 at 4096x4096x4096: SQ_LDS_BANK_CONFLICT drops from
8388608 to 0 and SQ_LDS_IDX_ACTIVE halves, with validation still PASSED.
The NT (TLU=1) fp4 subtile path only worked at MT 32x32 (a single 32x128
subtile strip per operand). Larger macro tiles crashed because the GR emit
indexed its soffset register list with a scheduler tileId in MMA-tile
units, overrunning the per-subtile-row list, and neither the GR write base
nor the LR transpose-read base applied a per-strip LDS offset.

Map the scheduler tileId to (subtileRow, mTileWithinStrip): the GR write
and LR read now place strip subtileRow at subtileRow*stripStride in LDS and
select the M-tile within the strip. Fill the per-subtile-row global-M
soffset for the TLU path (free dim is unit-stride, so stride is
subtileM*bpe). Centralize the pad-aware stripStride in SubtileTLUSwizzle so
GR write, LR read, and the LDS size computation agree, and fold the swizzle
pad into the LDS footprint.

Single-wave macro tiles from 32x32 up to 128x128 (2x2 and 4x4 subtile
grids, including non-power-of-2 like 96x96) now build and validate, with
LDS scaling accordingly (8KB -> 48KB). Multi-wave (MIWaveGroup>1) partition
offsets remain a separate follow-up.

f4_nt.yaml gains a second problem block exercising MT 64x64 and 128x128.
Extend the NT (TLU=1) fp4 subtile path to MIWaveGroup>1 so wg2x2 (4 waves)
and single-axis wg2x1/1x2 macro tiles work. Waves partition the free dim
(M for A via MIWaveGroup[0], N for B via MIWaveGroup[1]) into disjoint
subtile strips.

Three coupled per-wave offsets are added, all keyed on the wave's axis id
(waveId%mWaves for A, waveId//mWaves for B): the GR global read starts at
the wave's first strip (axisId*localSub0*subtileM elements, unit-stride
free dim); the GR DTL LDS write base and the LR transpose-read base target
the wave's region of the full-macro-tile LDS image
(axisId*localSub0*stripStride bytes).

Also fix the cooperative-load math: bytesPerLoad folds in numWaves for
TLU=0 (waves share a strip's K), but TLU waves partition free-dim strips
and load their own K independently. Using numWaves there halved
numGRPerSubtile and undersized the soffset register list, so drive both the
load-ratio and localGRGranularity from a single-wave grLoadWaves for TLU.
Derive the swizzle strip pad from DepthU/wavesize so it is identical across
all register-allocation snapshots.

Verified on gfx950: wg2x1, wg1x2, and wg2x2 from MT64 up to MT256x256
build and validate, single-wave regression stays green, and the LDS bank
conflict counter stays 0 for the multi-wave kernel. f4_nt.yaml gains a
wg2x2 (4-wave) MatrixInstruction variant.
NT (TLU=1) subtile GEMM stacked one MFMA K-window per DepthU.  Extend it to
DepthU that spans multiple K-windows (e.g. DepthU 256 = 2 windows of instK
128) so each strip holds several K-windows in LDS.

Global read: for NT the free (M/N) dim is unit-stride and K is strided, so
stepping to K-window sId1 must advance the GLOBAL address by
sId1*instK*strideK*bpe bytes.  The prior code encoded this as a DTL offset12
immediate, which only shifts the LDS m0 write and left window sId1>0 fetching
window 0's global rows.  Move the K-window step onto soffset (scaled by the
runtime K stride) and drop the offset12 trick on the TLU=1 path.

Local read: transpose read adds sId1 * globalSubtileGrid[0] * stripStride to
reach the window's LDS strip, matching the GR m0 placement.

Swizzle pad: a strip spans exactly one K-window, so swizzlePadPerStrip counts
load-blocks per K-window (instK/waveSize) instead of per full DepthU; this
keeps stripStride correct when DepthU > instK.

f4_nt.yaml gains DepthU 256 coverage across MT32x32, MT64x64, and MT128x128
wg2x2.  Bank conflict stays 0 (verified via rocprofv3 PMC).
Exercise DepthU 256 combined with the combined-subtile macro tiles up to
MT256x256 wg2x2.  The 256x256x256 tile fits gfx950 resources (144KB LDS of
160KB, 496 VGPR) and keeps the LDS bank conflict at 0 (verified via rocprofv3
PMC).  f4_nt.yaml now covers 26 problems across DepthU 128/256 and MT 32x32
through 256x256.
WorldofKerry and others added 24 commits September 18, 2026 19:58
The span is roundUp(ceil(K/mxBlock), 8) * 32 on both layouts, but only the
TLU=1 branch computed it.  The TLU=0 branch shifted Strides<tc>+0 left by 5,
which reaches the same number only because that register happens to hold
paddedKBlocks when the free dim is slow and the host did pad.  That is an
assumption about the host layout the kernel cannot check, and the register it
reads is the same one three separate attempts at a StreamK bug reached for by
mistake.

Derive it from K on both.  Verified the two forms agree at K = 256, 288, 1024
and 4096 before making the change, and subtile_mxfp8 -- nine TN groups, the
layout that previously took the shift -- still reports 107 passed / 0 failed.
Costs four SALU in the prologue, once per kernel.

The padding and the grouping are now named, SWIZZLE_GROUP_ROWS and
K_BLOCK_PAD, rather than appearing as 5, 7, 3 and 8 across five lines where
they are easy to mistake for each other.

Both numbers come from preSwizzleScalesGFX950, so the calculation is
swizzle-specific by construction.  Making that explicit is what lets the next
commit refuse NoSwizzle rather than leave it silently wrong.

7408 unit tests, 764 snapshots unchanged, subtile_mxfp4 9953 / 0 / 131.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The subtile scale group span is derived from the swizzled layout -- the K-block
padding and the 32-row grouping are both properties of
preSwizzleScalesGFX950 -- so a canonical scale buffer is read with the wrong
stride.  Nothing rejected the combination, so it produced faulting or
wrong-answer kernels instead of refusing.

Measured on gfx950, MXScaleFormat=NoSwizzle with UseSubtileImpl=1:

  TN   K=256, StreamK 0 and 3   FAILED, 5 illegal memory accesses
  NN                            aborts during kernel generation
  NT   K=256, 288               passes, the one case the padding happens to suit

StreamK=0 failing rules out the partial-tile K stride fixed earlier in this
branch; this is the scale addressing itself.

Nothing selects it: MXScaleFormat=Auto resolves to HostPreSwizzle on gfx950 and
InMemorySwizzle under TDM, and all 30 MXScaleFormat settings in the gfx950
suites are 1.  So refuse it, rather than implement a canonical-layout scale path
nobody asked for.  The TN probe that produced 5 illegal accesses now yields 0
valid solutions and 0 faults, with the reason printed.

state.get for UseSubtileImpl because this runs during MX format derivation,
before the key is necessarily present -- test_MXScaleLayoutDerivation calls it
with a minimal state, and absent means not subtile.

7408 unit tests, 764 snapshots unchanged; subtile_mxfp8 107 / 0 / 9 and
subtile_mxfp4 9953 / 0 / 131, neither of which touches NoSwizzle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tile K unit

numSubIterK answered scales with a post-hoc clamp bolted onto the side of a
per-operand rule. Scales are a per-operand property, so answer them where every
other property is answered and let the max do the rest.

Equivalent for all 64 input combinations: subIterKFor returns only 1 or 2, so
the old clamp yielded 2 whenever either operand had scales, which is what
max(2, other) now yields. 764 codegen snapshots unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AB_B16_TLU1 is selected for bf16/half whenever TLU=1, but nothing gives the
free dim the element multiple its 16B chunk needs, the way the fp4 branch sets
AssertFree{0,1}ElementMultiple to 32. An NN bf16 UseSubtileImpl config therefore
cleared SolutionStructs (300 of 372 solutions) and then died in kernelBodySubtile
on a bare AssertionError rather than failing cleanly.

Reject the combination, matching the NoSwizzle precedent. Enabling it instead
would generate kernels on an untested path and needs its own coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
generateData swizzled scaleBytes in place, but the reference path indexes scales
canonically: getAlignedFloat uses scale_id = kBlock * M + m. The early return
covers transposed A and non-transposed B, so the layouts that fall through -- the
TLU=1 ones this branch enables -- would build their reference from unrelated
scale entries and reject a correct GPU result.

Swizzle a copy for the device buffer and leave scaleBytes in generator order.
The bytes written to the device are unchanged in every case, so configurations
covered today (all preswizzled gtests are transA T / transB N, which return
early) are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ldsRowBankSize was recomputed inline at three sites in SubtileGREmit, and the
row-major split into loads and bank rows, with its two asserts, was duplicated
when _graTileAssignment_rowMajorSingle was factored out for NN/TT. Give both a
name and call them.

Also spell out the writer attribute each LDS region publishes instead of
building it with a format string, so the names the emit paths read are greppable
to their source, and drop SubtileLdsLayout.offsetOf, which has no callers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…unner

The TLU=1 work is exercised only by the gfx950 common suites, which are filtered
out on a runner without that GPU, so the coverage job saw SubtileGREmit drop to
52% and SubtileLREmit to 63% as the files roughly doubled in size.

Add one curated NN logic file to the codegen characterization set, which drives
the real emitter with no GPU. NN is the shape that pairs a TLU=1 operand with a
row-major one, so a single file reaches both the col_scatter stack and the
row-major pairing path: SubtileGREmit 52.76 -> 73.18%, SubtileLREmit
63.58 -> 91.22%, the latter above its pre-branch baseline.

Measured against the alternatives before settling on one file: NT covers TLU=1
on both sides but never the pairing path, TT mirrors NN, and a StreamK variant
was byte-identical in coverage to plain NT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… paths

The one NN logic file left SubtileGREmit at 73.18%, under its 85.92 floor in
coverage-baseline.json, which the ratchet enforces with a 1pp tolerance.

Add unit tests driving the TLU=1 global-read offset helpers directly. Which
split a geometry turns on is chosen by Solution.py from the tile shape, so a
curated logic file reaches one combination at a time; calling the helpers lets
each branch be stated as a case. They assert on the emitted stream, the returned
flags and register balance, not on address values, which the gfx950 suites cover.

Add the short-stack NT file as well: NN selects a 16x1 stack and so always takes
the col_scatter path, leaving the XOR-swizzle branch of _graTileAssignment_tlu
unreached by any curated file.

SubtileGREmit 73.18 -> 86.12%, SubtileLREmit 91.22 -> 94.96%, both now above
their floors without relying on the tolerance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
findConfigs walks the whole Tests tree, so the characterization data files are
offered to configMarks, which guards against them by testing for a top-level
sequence. The LibraryLogic step emits a mapping today, so the two TLU=1 files
captured from it passed that guard and crashed on doc["BenchmarkProblems"],
taking out collection for test_config, test_config_build and test_config_run.

Convert both to the sequence form every other per-arch data file uses, and test
for the BenchmarkProblems key rather than the document shape so a future capture
in either form is skipped instead of crashing.

The conversion is lossless: rawLibraryLogic returns an equal tuple before and
after, and the emit digests are unchanged, so the goldens need no update.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SubtileGeometry gained the strided-subtile mapping on this branch, which no unit
test reached, dropping the file to 83.55% against its 86.89 floor.

subtileForMmaTile is pure geometry, so test it directly: the contiguous case,
the zero-stride fallback, the strided case where a subtile's rows interleave
with its neighbours', and a self-consistency sweep that every row lands in a
subtile containing it. The interleaved case is the one worth pinning, since
getting it wrong still returns a plausible contiguous answer.

SubtileGeometry 83.55 -> 90.60%, above its floor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gfx950 emit golden for MX_Subtile_TLU1_NN.yaml was generated locally, and CI
computes a different kernel basename for the same logic file. Running
--snapshot-update locally is a no-op, since it can only re-pin the local value,
so take the digest CI reports.

The divergence itself is unexplained: CUCount, the LdsPad solution keys and the
HasLDSTrB64B4 assembler probe were each tested and ruled out, and the basename
is stable across runs on either side. Only this file diverges; the twelve
pre-existing gfx950 files and the sibling NT_Stack2 file agree.

This unblocks the coverage lane, which runs the characterization suite first and
so never reached coverage.json while the golden mismatched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
col_scatter had no test of its own. Its derivation ran only incidentally, via
the K-slot tests, and its emit side only through one characterization file, so a
wrong thread-bit placement would not fail to build -- it would build a kernel
that reads the wrong LDS bytes.

Test the properties the layout exists to provide rather than a table of numbers:
bit 3 carries col_group[gdBit] (the bank-pair separation), the thread bits are a
collision-free permutation, every lane decodes to a distinct (m_chunk,
col_group), and the K step is a non-zero per-lane constant -- the degenerate
case the N=32 guard exists for. The stack-16 step is anchored to the offset:256
the shipped kernels actually emit.

Also drop the 'verified against the bank model' claims: that verification is not
checked in, so the comments now say what the code does, not what was measured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Syrupy reports only the first differing entry, so pinning NN surfaced the same
divergence on the sibling NT_Stack2 file. Pin it the same way.

The cause looks to be the toolchain, not the test: the harness builds its
capability map by probing the local assembler (makeIsaInfoMap(SUPPORTED_ISA,
cxx)), and CI runs clang 24.0.0 against clang 22.0.0git here. Python is ruled
out -- 3.12.3 and 3.14.6 agree locally, while CI's 3.10.8 and 3.12.10 agree with
each other, so the split is by machine. The twelve pre-existing gfx950 goldens
match on both toolchains, so only the fp4 subtile kernels are sensitive to
whichever capability differs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comments and docstrings that narrated the change rather than the code they sit
on, plus function specs longer than the bodies they document.

Two docstrings explained themselves against a previous implementation: the
s_waitcnt builder claimed to replace "the old comment-marker scheme", and the
LDS token back-edge map called the identity carry "the classic behaviour".
Both now state the property that holds. Two subtile_mxfp4 configs justified
their K values by a crash they once reproduced; they now name the path they
cover. The s_waitcnt docstring also carried an em dash.

Trimmed eight docstrings on functions this branch adds where the spec outweighed
or duplicated the body -- _b128ChunkTiling restated a decomposition its caller
already comments, and _allocLROffsetRegs_tlu spent eight lines on a four-line
body. The offset formulas on the large emitters stay: those bodies emit
instructions rather than perform the arithmetic, so the equation is the only
statement of the result.

Comments only. No executable line changes, verified by filtering the diff for
assignments, statements and braces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matching a mnemonic string was the wrong fix for the CI failure. The root cause
is that VAddU32 has two spellings: native rocisa renders it v_add_u32 on gfx9,
while rocisa_stinkytofu_adaptor/instruction.py:1379 defines it as v_add_nc_u32
unconditionally. Which one appears depends on the active backend, so any string
assertion is a coin flip that happened to land differently on CI.

Check isinstance against the instruction class instead, the convention already
used in test_r6_subtile3_char.py, and apply it to every mnemonic assertion in
the file rather than only the two that failed. Text matching is kept for the
one operand check, since register names are stable across backends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The scale GR thread split divides Serial by the scale K-subtile count. That
count is lrSubtileShape[0] * mmaTileSize * (DepthU // instK) / loadWidth, and
every factor except DepthU // instK is a power of two, so a DepthU that is
merely a multiple of 2 * MatrixInstK is not enough: DepthU 768 over instK 128
gives 6, where int(math.log2(6)) truncates to 2 and the mask 6-1 is not a
modulo.

Reject that at solution validation instead of widening the split to cover it.
The emit goes back to a shift and a mask, dropping the reciprocal-multiply
fallback and its two scratch VGPRs. Emitted assembly is unchanged for every
DepthU that still validates: the gfx950 characterization digest is identical
with and without this change.

subtile_mxfp4 loses its DepthU 768 group and drops 768 from the DepthU fork,
since both are now rejected. No other MX config in Tensile/Tests uses a
non-power-of-two DepthU.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the two curated characterization logic files with unit tests that drive
the same emitters directly.

The goldens for those files were not reproducible: they depend on the assembler
version (clang 22 here, 24 on CI), so their digests had to be pinned to CI's
values and were red for anyone on a different ROCm. CUCount, the LdsPad solution
keys, the HasLDSTrB64B4 probe and the Python version were each tested and ruled
out; the cause is still unknown. A golden nobody can regenerate does not do the
job a golden exists for, so drop both files and restore the .ambr untouched.

The tests build a real TileInfo from the shipped geometry table and stub only
the writer, so the subtile shape, swizzle selection and register allocation are
genuine. The stack height picks the layout, which is how one fixture reaches
both: 2x1 takes the chunk-index XOR, 16x1 column-scatter, and a shared strip
reroutes even the short stack. They assert branch selection, register
allocation and that temporaries are returned -- not assembled output, which the
gfx950 hardware suites establish.

SubtileGREmit 67.90 -> 86.12%, SubtileLREmit 64.33 -> 92.36%, SubtileGeometry
90.60%, all above their floors without relying on the ratchet tolerance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The coverage gate measured SubtileLREmit at 86.67% on CI against 87.08 with
tolerance, while the same commit measures 92.36% here. CI runs a much smaller
selection -- its last report showed 139 skipped and 143 deselected against 29
skipped locally -- so the coverage other test files contribute on a developer
box is not there, and this file has to stand on its own.

Cover _lraTileAssignment_fp8_legacy, which pairs both operands off the writer's
tile state. That lifts this file's standalone LREmit coverage from 50.41 to
58.37%, 41 lines that no longer depend on another test happening to run.

Also stub the LDS start offsets applyLdsLayout publishes, which that path reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two docstrings belonging to pre-existing code were trimmed while tidying the
subtile comments. Neither function's code is touched by this branch, so the
edits drop context we did not write and cannot vouch for:

_make_swaitcnt lost the note that the current scheme replaced comment-marker
plus assembly-text post-processing, which left a combined s_waitcnt that
waitReconstruction could not rebuild and perturbed scheduling.

_ldsTokenBackEdgeMap lost that an empty map is the classic behaviour the binary
0<->1 toggle already amounts to, and that _nextLdsToken is the same primitive
codegen swaps with.

Audited the rest of the branch for the same thing: no other function has
unchanged code with an edited docstring, no Python file is a comment-only
change, and the two remaining comment-only hunks are a comment above code this
branch does change and a newly added one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The file had no header at the develop base; the block was added when the TLU=1
groups were folded in and grew from there. Most of it restated what the config
already shows: which groups cover which layout, and that MT is 16 * stack *
waveGroup when the MacroTiles are in the fork parameters.

The copyright/SPDX lines go too: test yamls here do not carry one (1 of the 48
pre-existing files in this directory does, 41 of 414 across common), and this
file did not have one before this branch.

Parsed document is unchanged -- 130 groups, identical when loaded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Solution.py carried 178 lines of subtile layout rules -- strip heights, wave
sharing, the GR K-partition bug check -- interleaved with the rest of solution
validation. Move them to SolutionStructs/Validators/Subtile.py, next to the
MatrixInstruction, MXScaleFormat and WorkGroup validators, and import the three
entry points Solution.py actually calls.

Validators is the right home rather than Components/Subtile: it is the
established place for Solution-level validation (MXScaleFormat.py says so in its
own docstring), and it imports cleanly, where Components/Subtile at Solution.py
module scope deadlocks the package load and needs lazy imports.

The three externally called helpers lose their leading underscore to match the
validateX convention; the rest stay private to the new module. Two unit tests
import the moved names and follow.

No behaviour change: 764 codegen snapshots unchanged, 7497 unit tests pass, and
subtile_mxfp4 is 9937 passed / 0 failed on gfx950.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guard was widened to skip mapping-format library logic yamls, because the
two curated files added to data/gfx950 were captured in the form the
LibraryLogic step emits today and crashed collection on doc[BenchmarkProblems].
Those files were later converted, then dropped entirely when the subtile
coverage moved to unit tests, so nothing in the tree needs the wider guard: every
logic yaml under Tests parses as a sequence, which the original check catches.

Leaves the file untouched by this branch. test_config, test_config_build and
test_config_run each collect 414 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule rejected any non-power-of-two DepthU for a subtile MX solution, and
took TN's DepthU 768 out of subtile_mxfp4.yaml with it. The requirement comes
from the scale GR thread split, which divides Serial by numThreadsPerGroup with
a shift and a mask; that count's other factors are powers of two only when an
operand is free-dim contiguous, so the requirement lands on DepthU for NT, NN
and TT and not for TN.

Gate it on TLUA or TLUB, which is exactly non-TN (TLUA = !transA, TLUB =
transB), and put TN's DepthU list back to its develop form so this branch does
not touch TN config at all. All 12 base TN groups are now present unchanged.

DepthU 768 still generates nothing on TN, there and on develop alike: the
pre-existing LDS pad rule rejects it (LdsBlockSizePerPadA 1024 % 768 x 0.5 != 0),
verified by running the base yaml against the base commit. That is unrelated to
the scale split and is not this branch's to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WorldofKerry
WorldofKerry marked this pull request as ready for review September 18, 2026 20:15
@WorldofKerry
WorldofKerry requested a review from a team as a code owner September 18, 2026 20:15
: 0;
size_t const scaleCols = static_cast<size_t>(sizes[1]);

bool const kIsRows = (isMatrixA && isTranspose) || (!isMatrixA && !isTranspose);

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.

Why can't we put these changes in case MXScaleLayout::GFX950:?

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.

3 participants