Repository navigation
Enable HalfPLR for MXF8 in gfx1250 - #7453
Conversation
67a134b to
2116105
Compare
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (77.83%) 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 #7453 +/- ##
===========================================
- Coverage 61.76% 61.69% -0.07%
===========================================
Files 2082 2083 +1
Lines 356042 356740 +698
Branches 53628 53812 +184
===========================================
+ Hits 219883 220072 +189
- Misses 117417 117917 +500
- Partials 18742 18751 +9
*This pull request uses carry forward flags. Click here to find out more.
🚀 New features to boost your workflow:
|
2116105 to
ea8a37f
Compare
ea8a37f to
d072f12
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces a new HalfPLR option for TensileLite (hipBLASLt) kernels on gfx1250 MXF8, aiming to reduce VGPR pressure when PrefetchLocalRead=1 by using a 3-group “half-sized” rotating VGPR buffer scheme and a split sub-iteration schedule.
Changes:
- Add the
HalfPLRtuning parameter (and derivedHalfPLRA/HalfPLRBflags) with validity constraints and defaults. - Update kernel generation (VGPR allocation, macro aliases, MFMA/local-read operand selection, and tail/no-load-loop handling) to support HalfPLR scheduling and address masking.
- Extend gfx12 MXF8 test YAML to exercise HalfPLR and additional small-K sizes / PGR settings.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| projects/hipblaslt/tensilelite/Tensile/Tests/common/gemm/gfx12/mxf8_gfx1250.yaml | Adds HalfPLR and additional MI/PGR/problem sizes to test gfx1250 MXF8 kernels. |
| projects/hipblaslt/tensilelite/Tensile/SolutionStructs/Solution.py | Decodes HalfPLR into A/B flags and rejects unsupported configurations. |
| projects/hipblaslt/tensilelite/Tensile/KernelWriterAssembly.py | Adds HalfPLR VGPR alias macros, HalfPLR MFMA source selection, and tail-loop / GR increment masking logic. |
| projects/hipblaslt/tensilelite/Tensile/KernelWriter.py | Implements HalfPLR-aware scheduling, local-read splitting, VGPR allocation adjustments, and loop copy changes. |
| projects/hipblaslt/tensilelite/Tensile/Components/LocalRead.py | Routes LDS transpose local-read destinations through HalfPLR VGPR group selection when enabled. |
| projects/hipblaslt/tensilelite/Tensile/Common/ValidParameters.py | Registers HalfPLR as a valid tuning parameter. |
| projects/hipblaslt/tensilelite/Tensile/Common/RequiredParameters.py | Includes HalfPLR in the minimal required parameter set. |
| projects/hipblaslt/tensilelite/Tensile/Common/GlobalParameters.py | Adds the default HalfPLR: 0 setting to solution defaults. |
Comments suppressed due to low confidence (3)
projects/hipblaslt/tensilelite/Tensile/KernelWriter.py:4090
halfCntis computed ascountLocalRead(localReadCodeB) / 2, yielding a float. If the local-read count is odd,halfCnt == 0will never be reached and the split between first/second-half reads will be wrong. Use integer division and validate the read count is even before splitting.
localReadCodeB, packCodeB, packPreB = self.localReadDo(kernel, bufferIdx, iui*self.states.numReadsIterCoalescedB, 0, tensorParametersB)
if kernel["HalfPLRB"]:
halfCnt = countLocalRead(localReadCodeB) / 2
readItems = localReadCodeB.flatitems()
while readItems:
item = readItems.pop(0)
localReads.add(item)
readsThisItem = countLocalRead(item)
if readsThisItem:
assert readsThisItem==1, "Scheduler assumes 1 read per item"
halfCnt -= 1
if halfCnt == 0:
break
projects/hipblaslt/tensilelite/Tensile/SolutionStructs/Solution.py:2182
- Grammar in rejection reason: "does not supports" should be "does not support" (user-facing message).
if (state["HalfPLRA"] and not (state["UnrollMajorLDSA"] or state["enableLDSTrA"])) or \
(state["HalfPLRB"] and not (state["UnrollMajorLDSB"] or state["enableLDSTrB"])):
reject(state, printRejectionReason, "Currently HalfPLR does not supports packing (need UnrollMajorLDS or use LDSTrInst)")
return
projects/hipblaslt/tensilelite/Tensile/SolutionStructs/Solution.py:2188
- Grammar in rejection reason: "does not supports" should be "does not support" (user-facing message).
return
if state["ConvertAfterDS"]:
reject(state, printRejectionReason, "Currently HalfPLR does not supports ConvertAfterDS")
return
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ea66243 to
f0257b5
Compare
f0257b5 to
654c4fe
Compare
654c4fe to
eb7e58f
Compare
## Summary This small change makes tensor-DMA load and store operations honor a zero descriptor count as a disabled transfer. The gap was found while reducing the stock gfx1250 TensileLite TDM workload tracked by #9935. Generated TensileLite code explicitly writes zero to the descriptor Group 0 count field when disabling TDM loads in the final prefetch iterations, but rocJITsu previously ignored that field and executed the transfer. ## Related contract evidence - ROCm/rocm-libraries#7453 introduced the HalfPLR TDM disable convention by conditionally clearing descriptor Group 0 and restoring it before the tail loop. - #7361 established the shared TDM wait-counter and atomic barrier sequencing that this PR preserves inside the common load/store executor. ## Failure before this fix A descriptor that generated code had disabled could still copy global data into LDS or store LDS data to global memory. If the descriptor requested an atomic-barrier arrival, that side effect was also performed. ## Fix - Parse the two-bit count field from descriptor Group 0. - Represent descriptor activity as part of the parsed descriptor contract. - Route load and store through one common tensor-DMA executor, which performs no copy or barrier side effect for an inactive descriptor. ## Test coverage The focused regression initializes global memory, LDS, and an atomic barrier with distinct values, executes decoded load and store instructions, and verifies that both operations leave all three unchanged. Existing tensor-DMA execution tests continue to pass. ## Workload status This PR fixes one independent descriptor contract. It does not claim to resolve the complete TensileLite reproducer or the remaining tail-loop semantic question. ## Issue tracking Progress toward #9935.
## Summary This small change makes tensor-DMA load and store operations honor a zero descriptor count as a disabled transfer. The gap was found while reducing the stock gfx1250 TensileLite TDM workload tracked by #9935. Generated TensileLite code explicitly writes zero to the descriptor Group 0 count field when disabling TDM loads in the final prefetch iterations, but rocJITsu previously ignored that field and executed the transfer. ## Related contract evidence - ROCm/rocm-libraries#7453 introduced the HalfPLR TDM disable convention by conditionally clearing descriptor Group 0 and restoring it before the tail loop. - #7361 established the shared TDM wait-counter and atomic barrier sequencing that this PR preserves inside the common load/store executor. ## Failure before this fix A descriptor that generated code had disabled could still copy global data into LDS or store LDS data to global memory. If the descriptor requested an atomic-barrier arrival, that side effect was also performed. ## Fix - Parse the two-bit count field from descriptor Group 0. - Represent descriptor activity as part of the parsed descriptor contract. - Route load and store through one common tensor-DMA executor, which performs no copy or barrier side effect for an inactive descriptor. ## Test coverage The focused regression initializes global memory, LDS, and an atomic barrier with distinct values, executes decoded load and store instructions, and verifies that both operations leave all three unchanged. Existing tensor-DMA execution tests continue to pass. ## Workload status This PR fixes one independent descriptor contract. It does not claim to resolve the complete TensileLite reproducer or the remaining tail-loop semantic question. ## Issue tracking Progress toward #9935. (cherry picked from commit 00aa1e2)
AIHPBLAS-1332
Motivation
In gfx1250, we cannot generate mxf8 kernel with MT=256x256x256 with PLR=1 due to vgpr limitation. By enabling HalfPLR, we can use less vgprs to achieve local data prefetch, with the price of additional scheduling constraint.
Technical Details
Concept
With
PrefetchLocalRead = 1, each operand normally needs 2 VGPR buffers (current + prefetch). HalfPLR splits each K-iteration's local reads and MFMAs into two halves and uses a rotating 3-group buffer of half the per-iter size (i.e. 1.5× buffer instead of 2×), trading scheduling complexity for ~25% lower VGPR pressure on the selected operand.New parameter —
HalfPLRBitmask in
{0, 1, 2}(Common/ValidParameters.py,GlobalParameters.py,RequiredParameters.py).Solution.pydecodes it into:HalfPLRA = HalfPLR & 0x1HalfPLRB = HalfPLR & 0x2VGPR layout —
KernelWriter.py/KernelWriterAssembly.pyvaluBlocksA/B = 1.5(wasnumVgprBufferA/B) when HalfPLRA/B is on.vgprValu{A,B}_G0/G1/G2over the same base VGPR range, eachnumVgprValuPerBlock // 2wide.Group rotation
Each sub-iter uses two of the three groups;
getHalfPLRValuStr(groups, offset, tc)picksG[gIdx]fromoffset // (numVgprValuPerBlock//2). AllValu{A,B}_X*_I*destination strings inComponents/LocalRead.pyand all source strings ingenerateSrcStrForMFMA/generateSrcStrForMFMAshiftK(now plumbed withlc=unrollLoopIdx) are rewritten through this helper when HalfPLR is active.Sub-iter schedule —
_makeSubIterSchedule(SIA = 0)New
localReadCodeSecondHalfarg. New ordering whenHalfPLR:localReadDooutput for the HalfPLR'd operand is split by countingcountLocalReadand assigning the firstN/2tolocalReads, the rest tolocalReadsSecondHalf. MFMAs are split symmetrically viacountInstruction(macIterCode) / 2. The reason is that the 2nd half of localRead use the same vgpr group as the first half of MFMAs.Loop copies
loopCopies = 3 if kernel["HalfPLR"] else 2 if expand else 1— three unroll copies are required because the 3-group rotation has period 3 overlc. NGLL & NLL will not be generated to reduce code size, soendCounterfor unrolled loop is 0 .Tail-loop / unrolled loop GR handling —
KernelWriterAssembly.pygraIncrementMask(kernel, tPA, tPB): inglobalReadIncrementatprefetchIndex == 0, ifloopCounter <= 1, conditionally zeroestdmABIncs/tdmMXSAMXSBIncs(multi-wavegroup) or per-tensorGlobalReadIncs{A,B,MXSA,MXSB}(single wavegroup) so the last two PGR iters don't advance addresses.globalReadDoheader (mode=1, tPA,SuppressNoLoadLoop): ifHalfPLR, whenloopCounter <= PrefetchGlobalRead, conditionally clears the TDM enable bits (tdmAGroup0,tdmBGroup0,tdmMXSAGroup0,tdmMXSBGroup0) instead of the originalSrdA/B+2 = 0path.SuppressNoLoadLoop): re-enables TDM and callstdmSwapLdsOffseton A/B (and MXS where applicable) to realign LDS, terminated bys_waitcnt dscnt(0)+s_barrier("prevent dummy ds_load from polluting LDS buffer").Outer/inner MFMA loop order
generateSrcStrForMFMAforceouter=0, loopSwap=TruewhenHalfPLRAso the A index is the outer loop (reuses the rotating group selection across the B sweep).Bug fix folded in
Fix wrong VgprG2L allocation condition for tensor B: should not allocate VgprG2L if TDM is enabled.

SCBranchSCC1(labelName=lastIterEnd, ...)->SCBranchSCC1(labelName=lastIterEnd.getLabelName(), ...)(was passing aLabelobject instead of its name string).Test Plan
Add HalfPLR kernels in tensilelite/Tensile/Tests/common/gemm/gfx12/mxf8_gfx1250.yaml:
[16,16,128,1,1,8,8,2,2]for the targeted MT 256x256x256.HalfPLR: [0, 1, 2].PrefetchGlobalRead: [1, 2].256x256x1x{128,384,640,896}to exercise small-K behavior.Test Result
Passed
Submission Checklist