Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/Tensile/Components/Subtile/SubtileLREmit.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

782 lines
37 KiB
Python

# Copyright Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
################################################################################
# LR (local read) emit and alloc dispatch.
#
# singledispatch over LR tag sentinels (LRTag_1x2, LRTag_TLU1, etc.).
# ABLRTile calls these via self.config.tag as the dispatch key.
#
# Structure:
# 1. Dispatch bases — @singledispatch declarations
# 2. Implementations — logic functions decorated with @register
################################################################################
from functools import singledispatch
from math import prod
from rocisa.code import Module
from rocisa.container import DSModifiers, EXEC, vgpr, sgpr
from rocisa.enum import RegisterType
from rocisa.instruction import (
DSLoadB128,
SMovB32, SMovB64,
VAddU32, VAndB32, VMovB32, VXorB32,
VLShiftLeftB32, VLShiftRightB32,
VMulLOU32, VPermlane16SwapB32,
)
from .SubtileGeometry import (
LRTag_1x1, LRTag_1x2, LRTag_TLU1,
)
from .SubtileScaleEmit import emitScaleLRLDSSwap
################################################################################
# 1. Dispatch bases
################################################################################
@singledispatch
def _emitLocalReadOffset(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"emitLocalReadOffset not implemented for {type(tag).__name__}")
@singledispatch
def _emitLocalRead(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"emitLocalRead not implemented for {type(tag).__name__}")
@singledispatch
def _allocLROffsetRegisters(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"allocLROffsetRegisters not implemented for {type(tag).__name__}")
@singledispatch
def _deallocLROffsetRegisters(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"deallocLROffsetRegisters not implemented for {type(tag).__name__}")
@singledispatch
def _emitLRDTLInit(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"emitLRDTLInit not implemented for {type(tag).__name__}")
@singledispatch
def _emitLRLDSBufferSwap(tag, tile, ti, writer, kernel):
raise NotImplementedError(f"emitLRLDSBufferSwap not implemented for {type(tag).__name__}")
# Stubs for tags not yet implemented.
_stub = lambda tag, tile, ti, writer, kernel: None
_emitLocalReadOffset.register(LRTag_TLU1)(_stub)
_emitLocalRead.register(LRTag_TLU1)(_stub)
_allocLROffsetRegisters.register(LRTag_TLU1)(_stub)
_deallocLROffsetRegisters.register(LRTag_TLU1)(_stub)
_emitLRDTLInit.register(LRTag_TLU1)(_stub)
_emitLRLDSBufferSwap.register(LRTag_TLU1)(_stub)
################################################################################
# Helpers
################################################################################
def _setExecMask(module, writer, maskLo, maskHi):
"""Set EXEC mask to a 64-bit immediate value."""
tmpSgpr = writer.sgprPool.checkOutAligned(2, 2, "setExecMask tmpSgpr", False)
module.add(SMovB32(dst=sgpr(tmpSgpr), src=hex(maskLo), comment="exec mask lo"))
module.add(SMovB32(dst=sgpr(tmpSgpr+1), src=hex(maskHi), comment="exec mask hi"))
module.add(SMovB64(dst=EXEC(), src=sgpr(tmpSgpr, 2), comment="Set exec mask"))
writer.sgprPool.checkIn(tmpSgpr)
setExecMask = _setExecMask
################################################################################
# 2. Implementations
################################################################################
# --- LR offset emit (TLU=0) --------------------------------------------------
@_emitLocalReadOffset.register(LRTag_1x1)
@_emitLocalReadOffset.register(LRTag_1x2)
def _emitLROffset_TLU0(tag, tile, ti, writer, kernel):
"""LR offset for row-major (TLU=0) subtile with swizzling.
Ported from legacy lraTileAssignment + _computeLROffset + _applyWavePartitionLROffset.
Operates on a single tensor component (A or B).
The LDS read layout uses MFMA register mapping:
lane16 = laneId % instM (M row within MMA tile)
lane16Group = laneId // instM (K column group)
Steps:
1. Compute lane16 and lane16Group from Serial.
2. Apply rotation and swizzling to colOffset (de-swizzle to match GR's LDS layout).
3. Compute rowOffset = lane16 * subIterKBytes.
4. For each ds_read within the subtile: offset = (colOffset + advance) % blockSize * loadWidth + rowOffset.
5. Apply wave partition offset (shift LR offsets by wave's LDS region).
"""
return Module(f"LR Offset 1x2 ({ti.tc})") # STUB
module = Module(f"LR Offset 1x2 ({ti.tc})")
tc = ti.tc
wavesize = kernel["WavefrontSize"]
subIterKBytes = ti.subIterKBytes
loadWidth = ti.loadWidthLR
mi_m = ti.mmaTileShape[0]
ldsRowBankSize = writer.states.archCaps["LDSBankCount"] * writer.states.archCaps["LDSBankWidth"]
numRowsPerLDSBanks = ldsRowBankSize // subIterKBytes
blockSize = subIterKBytes // loadWidth
numMFMACols = int(ti.mmaTileShape[1] * ti.bpe) // loadWidth
wg_m = ti.waveGroupSize
numWaves = ti.numWaves
waves_coop = numWaves // wg_m
tmpVgpr = writer.vgprPool.checkOut(5, tag="_emitLROffset_TLU0_tmpVgpr")
lane16 = tmpVgpr
lane16Group = tmpVgpr + 1
rotation = tmpVgpr + 2
rowOffset = tmpVgpr + 3
colOffset = tmpVgpr + 4
# --- 1. lane16 and lane16Group from Serial ---
module.add(VAndB32(dst=vgpr(lane16Group), src0=vgpr("Serial"), src1=wavesize-1,
comment=f"{tc}: laneId"))
module.add(VLShiftRightB32(dst=vgpr(lane16Group), shiftHex=hex(mi_m.bit_length()-1),
src=vgpr(lane16Group), comment=f"{tc}: lane16Group = laneId // {mi_m}"))
module.add(VAndB32(dst=vgpr(lane16), src0=vgpr("Serial"), src1=mi_m-1,
comment=f"{tc}: lane16 = laneId %% {mi_m}"))
# --- 2. Swizzling: rotation + permlane16 de-swizzle ---
module.addComment0(f"{tc}: LR swizzling")
# ldsRowId = lane16 // numRowsPerLDSBanks
module.add(VLShiftRightB32(dst=vgpr(rotation), shiftHex=hex(numRowsPerLDSBanks.bit_length()-1),
src=vgpr(lane16), comment=f"{tc}: lds_row_id"))
# rotation = (ldsRowId // 2) * 2
module.add(VLShiftRightB32(dst=vgpr(rotation), shiftHex=hex(1),
src=vgpr(rotation), comment=f"{tc}: ldsRowId // 2"))
module.add(VLShiftLeftB32(dst=vgpr(rotation), shiftHex=hex(1),
src=vgpr(rotation), comment=f"{tc}: (ldsRowId // 2) * 2"))
# colOffset = (rotation + lane16Group) % blockSize
module.add(VAddU32(dst=vgpr(colOffset), src0=vgpr(rotation), src1=vgpr(lane16Group),
comment=f"{tc}: rotation + lane16Group"))
module.add(VAndB32(dst=vgpr(colOffset), src0=vgpr(colOffset), src1=hex(blockSize-1),
comment=f"{tc}: %% blockSize"))
# Permlane16 swap to match GR's quad_perm swizzle pattern
_setExecMask(module, writer, 0x33333333, 0x33333333)
module.add(VPermlane16SwapB32(dst=vgpr(colOffset), src=vgpr(colOffset),
comment=f"{tc}: de-swizzle"))
_setExecMask(module, writer, -1, -1)
# --- 3. rowOffset = lane16 * subIterKBytes ---
module.add(VLShiftLeftB32(dst=vgpr(rowOffset), shiftHex=hex(subIterKBytes.bit_length()-1),
src=vgpr(lane16), comment=f"{tc}: row = lane16 * {subIterKBytes}"))
# --- 4. Compute LR offsets for each ds_read within the subtile ---
# offset[0] = colOffset * loadWidth + rowOffset
# offset[i] = ((colOffset + i * numMFMACols) % blockSize) * loadWidth + rowOffset
module.add(VMovB32(dst=vgpr(tile.sharedVgprLROffset[0]), src=vgpr(colOffset),
comment=f"{tc}: LR offset 0 col"))
for i in range(1, ti.numLRPerSubtile):
module.add(VAddU32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i-1]), src1=hex(numMFMACols),
comment=f"{tc}: advance col for MFMA {i}"))
module.add(VAndB32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i]), src1=hex(blockSize-1),
comment=f"{tc}: col %% blockSize"))
for i in range(ti.numLRPerSubtile):
module.add(VLShiftLeftB32(dst=vgpr(tile.sharedVgprLROffset[i]),
shiftHex=hex(loadWidth.bit_length()-1), src=vgpr(tile.sharedVgprLROffset[i]),
comment=f"{tc}: col * {loadWidth}"))
module.add(VAddU32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i]), src1=vgpr(rowOffset),
comment=f"{tc}: row + col"))
writer.vgprPool.checkIn(tmpVgpr)
# --- 5. Wave partition: shift LR offsets by wave's LDS region ---
# Each wave reads from a different partition of LDS along the tc's own wave-group axis.
# Guard: wg_m > 1 ensures tc's own axis has multiple waves (for A: wg_m, for B: wg_n).
# Without this guard, a 1x4 WG would wrongly treat A's 4 N-waves as M-partitions.
if waves_coop > 1 and wg_m > 1:
# Each wave reads from a different M partition. The A LDS region has size
# MT * subIterKBytes, split into wg_m partitions (one per M-direction wave).
# B uses the same stride since B partition also maps 1:1 to M-direction waves.
MT = ti.globalMMATileGrid[0] * ti.mmaTileShape[0]
sInterval = MT * subIterKBytes // wg_m
waveId = writer.vgprPool.checkOut(1, tag="_emitLROffset_TLU0_waveId")
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(wavesize.bit_length()-1),
src=vgpr("Serial"), comment=f"{tc}: waveId"))
if tc == 'A':
module.add(VAndB32(dst=vgpr(waveId), src0=hex(waves_coop - 1), src1=vgpr(waveId),
comment=f"{tc}: waveId %% {waves_coop}"))
else:
module.add(VLShiftRightB32(dst=vgpr(waveId),
shiftHex=hex(waves_coop.bit_length()-1), src=vgpr(waveId),
comment=f"{tc}: waveId // {waves_coop}"))
tmpSgpr = writer.sgprPool.checkOut(1, tag="_emitLROffset_TLU0_tmpSgpr")
module.add(SMovB32(dst=sgpr(tmpSgpr), src=hex(sInterval),
comment=f"{tc}: LR partition stride"))
module.add(VMulLOU32(dst=vgpr(waveId), src1=vgpr(waveId), src0=sgpr(tmpSgpr)))
for i in range(ti.numLRPerSubtile):
module.add(VAddU32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i]), src1=vgpr(waveId),
comment=f"{tc}: + wave partition"))
writer.vgprPool.checkIn(waveId)
writer.sgprPool.checkIn(tmpSgpr)
elif wg_m > 1:
# waves_coop == 1 but wg_m > 1: each wave owns a separate LDS region
MT = ti.globalMMATileGrid[0] * ti.mmaTileShape[0]
sInterval = MT * subIterKBytes // (numWaves)
waveId = writer.vgprPool.checkOut(1, tag="_emitLROffset_TLU0_waveId")
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(wavesize.bit_length()-1),
src=vgpr("Serial"), comment=f"{tc}: waveId"))
tmpSgpr = writer.sgprPool.checkOut(1, tag="_emitLROffset_TLU0_tmpSgpr")
module.add(SMovB32(dst=sgpr(tmpSgpr), src=hex(sInterval),
comment=f"{tc}: LR partition stride"))
module.add(VMulLOU32(dst=vgpr(waveId), src1=vgpr(waveId), src0=sgpr(tmpSgpr)))
for i in range(ti.numLRPerSubtile):
module.add(VAddU32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i]), src1=vgpr(waveId),
comment=f"{tc}: + wave partition"))
writer.vgprPool.checkIn(waveId)
writer.sgprPool.checkIn(tmpSgpr)
# --- 6. Add global LDS start offset for B (B data follows A in LDS) ---
ldsStartOffset = getattr(writer, f'ldsStartOffset{tc}', 0)
if ldsStartOffset:
stmp = writer.sgprPool.checkOut(1, tag="_emitLROffset_TLU0_stmp")
module.add(SMovB32(dst=sgpr(stmp), src=ldsStartOffset,
comment=f"{tc}: ldsStartOffset"))
for i in range(ti.numLRPerSubtile):
module.add(VAddU32(dst=vgpr(tile.sharedVgprLROffset[i]),
src0=vgpr(tile.sharedVgprLROffset[i]), src1=sgpr(stmp),
comment=f"{tc}: + LDS offset"))
writer.sgprPool.checkIn(stmp)
return module
# --- LR alloc/dealloc (LRTag_1x2) -------------------------------------------
@_allocLROffsetRegisters.register(LRTag_1x1)
@_allocLROffsetRegisters.register(LRTag_1x2)
def _allocLROffsetRegs_1x2(tag, tile, ti, writer, kernel):
"""Allocate LR offset registers for row-major (TLU=0) 1x2 subtile shape.
Two register groups are allocated:
1. sharedVgprLROffset[]: one VGPR per ds_read within a subtile.
numLRPerSubtile = ceil(lrSubtileSize / (loadWidthLR * waveSize)).
Each VGPR holds the per-lane byte offset into LDS for one ds_read_b128.
2. sharedVgprLROffsetSwap[]: same count, used for double-buffering.
While one set is in use for the current iteration's LR, the other
holds pre-computed offsets for the next iteration.
"""
tile.sharedVgprLROffset = []
tile.sharedVgprLROffsetSwap = []
for i in range(ti.numLRPerSubtile):
tile.sharedVgprLROffset.append(writer.vgprPool.checkOut(1, tag="_allocLROffsetRegs_1x2_sharedVgprLROffset"))
tile.sharedVgprLROffsetSwap.append(writer.vgprPool.checkOut(1, tag="_allocLROffsetRegs_1x2_sharedVgprLROffsetSwap"))
@_deallocLROffsetRegisters.register(LRTag_1x1)
@_deallocLROffsetRegisters.register(LRTag_1x2)
def _deallocLROffsetRegs_1x2(tag, tile, ti, writer, kernel):
"""Deallocate LR offset registers."""
if isinstance(tile.sharedVgprLROffset, list):
for voff in tile.sharedVgprLROffset:
writer.vgprPool.checkIn(voff)
tile.sharedVgprLROffset = []
if isinstance(tile.sharedVgprLROffsetSwap, list):
for voff in tile.sharedVgprLROffsetSwap:
writer.vgprPool.checkIn(voff)
tile.sharedVgprLROffsetSwap = []
# --- LR load emit (LRTag_1x2) -----------------------------------------------
@_emitLocalRead.register(LRTag_1x1)
@_emitLocalRead.register(LRTag_1x2)
def _emitLR_1x2(tag, tile, ti, writer, kernel):
return Module(f"LR Load 1x2 ({ti.tc})") # STUB
"""Emit ds_read_b128 for all subtiles in the local grid.
For each subtile (sId0, sId1), for each MMA tile in K (subtileShape[1]):
- addrVgpr = sharedVgprLROffset[mfmaId] (per-lane LDS byte offset)
- ds_offset = subtile position in LDS (constant immediate)
- dst = vgprTiles[tileIdx] (destination register tile)
The tile index mapping: for subtile at linearId with numLRPerSubtile reads,
tileIdx = linearId * numLRPerSubtile + mfmaId
This assumes non-interleaved layout (subtileShape[0]=1 for 1x2).
"""
module = Module(f"LR Load 1x2 ({ti.tc})")
tc = ti.tc
# TODO: Remove legacy TileInfo dependency after full migration.
# Uses legacy's grid/sizes/vgprTiles because TileInfo's expanded subtileShape
# doesn't match the LDS layout computed from legacy values.
legacyTi = getattr(writer.states, tc.lower()).tileInfo
subtileSize = int(legacyTi.subtileSize)
for i in range(int(legacyTi.localSubtileGrid[0])):
for j in range(int(legacyTi.localSubtileGrid[1])):
for du in range(int(legacyTi.subtileShape[1])):
mfmaId = du
addrVgpr = tile.sharedVgprLROffset[mfmaId]
# DS offset: subtile position in LDS
offset = i * subtileSize + j * int(legacyTi.globalSubtileGrid[0]) * subtileSize
# Destination tile register
tileIdx = ti.lrTileIndexForSubtile(i, j, mfmaId)
dstTile = ti.vgprTiles[tileIdx]
dstVgpr = dstTile.regList.indices[0]
numRegs = len(dstTile.regList.indices)
module.add(DSLoadB128(
dst=vgpr(dstVgpr, numRegs),
src=vgpr(addrVgpr),
ds=DSModifiers(offset=offset),
comment=f"LR {tc}[{i},{j}] k={du}")
)
return module
# --- LR DTL init (LRTag_1x2) ------------------------------------------------
@_emitLRDTLInit.register(LRTag_1x1)
@_emitLRDTLInit.register(LRTag_1x2)
def _emitLRDTLInit_1x2(tag, tile, ti, writer, kernel):
return Module(f"LR DTL Init ({ti.tc})") # STUB
"""Compute swap VGPRs for LR double-buffering.
For each sharedVgprLROffset[i], computes the corresponding swap offset:
swap[i] = XOR(offset[i], offset[i] + ldsTotalSize)
This mask toggles the LR read between the two LDS buffer halves.
"""
module = Module(f"LR DTL Init ({ti.tc})")
stmp = writer.sgprPool.checkOut(1, tag="_emitLRDTLInit_1x2_stmp")
module.add(SMovB32(dst=sgpr(stmp), src=writer.ldsTotalSize,
comment=f"{ti.tc}: ldsTotalSize for swap"))
for i in range(len(tile.sharedVgprLROffset)):
vOff = tile.sharedVgprLROffset[i]
vSwap = tile.sharedVgprLROffsetSwap[i]
module.add(VAddU32(dst=vgpr(vSwap), src0=vgpr(vOff), src1=sgpr(stmp),
comment=f"{ti.tc}: offset + ldsTotalSize"))
module.add(VXorB32(dst=vgpr(vSwap), src0=vgpr(vOff), src1=vgpr(vSwap),
comment=f"{ti.tc}: swap mask = XOR"))
writer.sgprPool.checkIn(stmp)
return module
# --- LR LDS buffer swap (LRTag_1x2) -----------------------------------------
@_emitLRLDSBufferSwap.register(LRTag_1x1)
@_emitLRLDSBufferSwap.register(LRTag_1x2)
def _emitLRLDSSwap_1x2(tag, tile, ti, writer, kernel):
"""Toggle LR read offsets between double-buffer halves.
XOR each sharedVgprLROffset with its swap mask to flip to the other buffer.
"""
module = Module()
module.addComment0("Emit code to swap %s LR vgpr offsets"%ti.tc)
for i in range(len(tile.sharedVgprLROffset)):
vOff = tile.sharedVgprLROffset[i]
vSwap = tile.sharedVgprLROffsetSwap[i]
module.add(VXorB32(dst=vgpr(vOff), src0=vgpr(vOff), src1=vgpr(vSwap), comment=""))
return module
################################################################################
# Legacy LR emit functions (moved from SubtileBasedKernel.py)
################################################################################
def _computeLROffset(module, tileInfo, colOffset, rowOffset, swizzled):
tc = tileInfo.tc
subIterKBytes = tileInfo.subIterKBytes
loadWidth = tileInfo.loadWidthLR
numMFMACols = int(tileInfo.mmaTileShape[1] * tileInfo.bpe) // loadWidth # TN case only
# Without LDS swizzling (e.g. TDM), the full DepthU tile is contiguous in LDS,
# so the K-row is depthUBytes wide. With swizzling, GR writes individual
# subtile K-groups, so the effective K-row is subIterKBytes.
ldsKBytes = subIterKBytes if swizzled else tileInfo.depthUBytes
blockSize = ldsKBytes // loadWidth
# Each ds_load_b128 fills REGS_PER_DS_READ VGPRs. Tiles with more VGPRs
# (e.g. 8-VGPR wave32 BF16 or wave64 FP8) need multiple reads. Consecutive
# LR offset entries advance by colsPerRead = numMFMACols / numReadsForTile
# so entries within the same MMA tile cover equal K sub-portions.
REGS_PER_DS_READ = loadWidth // 4
numReadsForTile = tileInfo.geometry.lr.mmaLayout.vgprs // REGS_PER_DS_READ
colsPerRead = numMFMACols // numReadsForTile
module.add(VMovB32(dst=vgpr(tileInfo.sharedVgprLROffset[0]), src=vgpr(colOffset), comment="%s: laneId"%tc))
for vgprId in range(1, len(tileInfo.sharedVgprLROffset)):
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src0=vgpr(tileInfo.sharedVgprLROffset[vgprId-1]), src1=hex(colsPerRead), comment="%s: colOffset for read %u"%(tc, vgprId)))
module.add(VAndB32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src0=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src1=hex(blockSize-1), comment="%s: colOffset = colOffset %% block_size"%tc))
for vgprId in range(0, len(tileInfo.sharedVgprLROffset)):
module.add(VLShiftLeftB32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), shiftHex=hex(loadWidth.bit_length()-1), src=vgpr(tileInfo.sharedVgprLROffset[vgprId]), comment="%s: colOffset*loadWidth"%tc))
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src0=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src1=vgpr(rowOffset), comment="%s: row + col"%tc))
def _applyWavePartitionLROffset(module, writer, kernel, tileInfo):
"""Apply wave-based partition offset to LR offsets.
loadRatioGR >= 2.0: no partition needed, contiguous subtiles (1x4 for A , 4x1 for B)
loadRatioGR == 1.0: 2x2 config, each wave loads half of the subtile
loadRatioGR == 0.5: 4x1 for A , 1x4 for B. Split in 4 subtiles groups
"""
tc = tileInfo.tc
# TDM handles wave partitioning via descriptors
# For single-wave, TDM puts all data at the wave's LDS base -- no partition needed.
# For multi-wave, each wave's TDM writes to a different LDS region, so LR
# offsets must include a per-wave partition offset.
if kernel.get("enableTDM%s" % tc, False):
numWaves = prod(kernel["MIWaveGroup"])
if numWaves == 1:
return
# Multi-wave TDM: add per-wave LDS offset based on axis position
wgM, wgN = kernel["MIWaveGroup"]
numWavesThisAxis = wgM if tc == 'A' else wgN
if numWavesThisAxis <= 1:
return # this tensor's axis is not split
wavesize = kernel["WavefrontSize"]
du = kernel["DepthU"]
mt = kernel["MacroTile0"] if tc == 'A' else kernel["MacroTile1"]
bpe = tileInfo.bpe
waveId = writer.vgprPool.checkOut(1)
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(wavesize.bit_length()-1), src=vgpr("Serial"), comment="waveId"))
# Decompose to axis component
if tc == 'A' and wgN > 1:
module.add(VAndB32(dst=vgpr(waveId), src0=hex(wgM - 1), src1=vgpr(waveId), comment="waveIdM = waveId %% %d" % wgM))
elif tc == 'B' and wgM > 1:
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(wgM.bit_length()-1), src=vgpr(waveId), comment="waveIdN = waveId / %d" % wgM))
# LDS offset per wave = waveId_axis * (mt / numWavesThisAxis * (du*bpe + pad))
rowBytes = int(du * bpe) + int(getattr(tileInfo, "ldsRowPadBytes", 0))
ldsPerWave = int(mt // numWavesThisAxis) * rowBytes
tmpSgpr = writer.sgprPool.checkOut(1)
module.add(SMovB32(dst=sgpr(tmpSgpr), src=hex(ldsPerWave), comment="LDS bytes per wave for %s" % tc))
module.add(VMulLOU32(dst=vgpr(waveId), src1=vgpr(waveId), src0=sgpr(tmpSgpr), comment="waveOffset"))
for vgprId in range(len(tileInfo.sharedVgprLROffset)):
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src0=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src1=vgpr(waveId), comment="%s: TDM wave partition LR offset" % tc))
writer.vgprPool.checkIn(waveId)
writer.sgprPool.checkIn(tmpSgpr)
return
if tileInfo.loadRatioGR >= 2.0:
return
wavesize = kernel["WavefrontSize"]
subIterKBytes = tileInfo.subIterKBytes
loadWidth = tileInfo.loadWidthGR
waveId = writer.vgprPool.checkOut(1, tag="_applyWavePartitionLROffset_waveId")
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(wavesize.bit_length()-1), src=vgpr("Serial"), comment="waveId"))
partitionOffset = tileInfo.mmaTileShape[0] * tileInfo.localSubtileGrid[0]
numRowsPerWave = wavesize // (subIterKBytes // loadWidth)
if tileInfo.loadRatioGR == 1.0:
mWaves = kernel["MIWaveGroup"][0]
if tc == 'A':
module.add(VAndB32(dst=vgpr(waveId), src0=hex(mWaves - 1), src1=vgpr(waveId), comment="%s: waveId %% %d"%(tc, mWaves)))
else:
module.add(VLShiftRightB32(dst=vgpr(waveId), shiftHex=hex(mWaves.bit_length()-1), src=vgpr(waveId), comment="%s: waveId / %d"%(tc, mWaves)))
sInterval = partitionOffset * subIterKBytes
elif tileInfo.loadRatioGR == 0.5:
sInterval = partitionOffset * subIterKBytes
else:
raise NotImplementedError("Unsupported loadRatioGR for wave partition: %s"%str(tileInfo.loadRatioGR))
if sInterval == 0:
writer.vgprPool.checkIn(waveId)
return
tmpSgpr = writer.sgprPool.checkOut(1, tag="_applyWavePartitionLROffset_tmpSgpr")
module.add(SMovB32(dst=sgpr(tmpSgpr), src=hex(sInterval), comment="%s: interleave stride"%tc))
module.add(VMulLOU32(dst=vgpr(waveId), src1=vgpr(waveId), src0=sgpr(tmpSgpr), comment=""))
for vgprId in range(len(tileInfo.sharedVgprLROffset)):
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src0=vgpr(tileInfo.sharedVgprLROffset[vgprId]), src1=vgpr(waveId), comment="%s: wave partition LR offset"%tc))
writer.vgprPool.checkIn(waveId)
writer.sgprPool.checkIn(tmpSgpr)
##################################################
# Subroutine to generate LR offset calculation code
#
def lraTileAssignment(writer, kernel):
return _lraTileAssignment_legacy(writer, kernel)
def _lraWavePartitioning_legacy(module, writer, kernel):
tileInfoA = writer.states.a.tileInfo
tileInfoB = writer.states.b.tileInfo
_applyWavePartitionLROffset(module, writer, kernel, tileInfoA)
_applyWavePartitionLROffset(module, writer, kernel, tileInfoB)
def _lraTileAssignment_fp8_legacy(writer, kernel, module):
"""FP8 LR offset: block-swap + wave de-rotation for MFMA 16x16x128.
Two ds_read_b128 per MFMA (numLRPerSubtile=2), using complementary block
assignments to achieve zero LDS bank conflicts:
finalColId = (lane16Group + 2*(lane16 >> 3)) % 4 [undo GR wave rotation]
colOffset_0 = finalColId + swap_bit * 4
colOffset_1 = colOffset_0 ^ 4
where:
swap_bit = (lane16 >> 1) & 1
The rotation 2*(lane16>>3) undoes the GR step 2 wave K_group rotation:
waves with waveId&1==1 (M-rows 8..15) wrote with rotation=2; lane16>=8
reads them back with de-rotation=2. Together they achieve zero bank conflicts.
"""
tileInfoA = writer.states.a.tileInfo
tileInfoB = writer.states.b.tileInfo
subIterKBytes = tileInfoA.subIterKBytes
wavesize = kernel["WavefrontSize"]
mi_m = tileInfoA.mmaTileShape[0]
loadWidth = tileInfoA.loadWidthLR
tmpVgpr = writer.vgprPool.checkOut(6, tag="_lraTileAssignment_fp8_legacy_tmpVgpr")
lane16, lane16Group, scratch, rowOffset, colOffset0, colOffset1 = range(tmpVgpr, tmpVgpr + 6)
module.add(VAndB32(dst=vgpr(lane16), src0=vgpr("Serial"), src1=mi_m-1, comment="lane16 = laneId % 16"))
module.add(VAndB32(dst=vgpr(lane16Group), src0=vgpr("Serial"), src1=wavesize-1, comment="laneId"))
module.add(VLShiftRightB32(dst=vgpr(lane16Group), shiftHex=hex(mi_m.bit_length()-1), src=vgpr(lane16Group), comment="lane16Group = laneId // 16"))
module.add(VLShiftRightB32(dst=vgpr(scratch), shiftHex=hex(3), src=vgpr(lane16), comment="lane16 >> 3 (1 if M-row >= 8)"))
module.add(VLShiftLeftB32(dst=vgpr(scratch), shiftHex=hex(1), src=vgpr(scratch), comment="rotation = 2 * (lane16 >> 3)"))
module.add(VAddU32(dst=vgpr(colOffset0), src0=vgpr(lane16Group), src1=vgpr(scratch), comment="lane16Group + rotation"))
module.add(VAndB32(dst=vgpr(colOffset0), src0=vgpr(colOffset0), src1=hex(3), comment="finalColId = (lane16Group + rotation) % 4"))
module.add(VLShiftRightB32(dst=vgpr(scratch), shiftHex=hex(1), src=vgpr(lane16), comment="lane16 >> 1"))
module.add(VAndB32(dst=vgpr(scratch), src0=vgpr(scratch), src1=hex(1), comment="swap_bit"))
module.add(VLShiftLeftB32(dst=vgpr(scratch), shiftHex=hex(2), src=vgpr(scratch), comment="swap_val = swap_bit * 4"))
module.add(VAddU32(dst=vgpr(colOffset0), src0=vgpr(colOffset0), src1=vgpr(scratch), comment="colOffset_0 = finalColId + swap_val"))
module.add(VXorB32(dst=vgpr(colOffset1), src0=vgpr(colOffset0), src1=hex(4), comment="colOffset_1 = colOffset_0 ^ 4"))
module.add(VLShiftLeftB32(dst=vgpr(rowOffset), shiftHex=hex(subIterKBytes.bit_length()-1), src=vgpr(lane16), comment=f"rowOffset = lane16 * {subIterKBytes}"))
for tileInfo in [tileInfoA, tileInfoB]:
module.add(VLShiftLeftB32(dst=vgpr(tileInfo.sharedVgprLROffset[0]),
shiftHex=hex(loadWidth.bit_length()-1), src=vgpr(colOffset0),
comment=f"{tileInfo.tc}: col0 * {loadWidth}"))
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[0]),
src0=vgpr(tileInfo.sharedVgprLROffset[0]), src1=vgpr(rowOffset),
comment=f"{tileInfo.tc}: offset[0]"))
if len(tileInfo.sharedVgprLROffset) > 1:
module.add(VLShiftLeftB32(dst=vgpr(tileInfo.sharedVgprLROffset[1]),
shiftHex=hex(loadWidth.bit_length()-1), src=vgpr(colOffset1),
comment=f"{tileInfo.tc}: col1 * {loadWidth}"))
module.add(VAddU32(dst=vgpr(tileInfo.sharedVgprLROffset[1]),
src0=vgpr(tileInfo.sharedVgprLROffset[1]), src1=vgpr(rowOffset),
comment=f"{tileInfo.tc}: offset[1]"))
writer.vgprPool.checkIn(tmpVgpr)
_lraWavePartitioning_legacy(module, writer, kernel)
stmp = writer.sgprPool.checkOut(1, tag="_lraTileAssignment_legacy_stmp")
module.add(SMovB32(dst=sgpr(stmp), src=writer.ldsStartOffsetB, comment="ldsStartOffsetB"))
for vgprId in range(len(tileInfoB.sharedVgprLROffset)):
module.add(VAddU32(dst=vgpr(tileInfoB.sharedVgprLROffset[vgprId]),
src0=sgpr(stmp),
src1=vgpr(tileInfoB.sharedVgprLROffset[vgprId]),
comment="B matrix offset in LDS"))
writer.sgprPool.checkIn(stmp)
return module
def _lraTileAssignment_legacy(writer, kernel):
module = Module()
module.addComment0("LR Offset Calculation for Subtile Based Tiling")
tileInfoA = writer.states.a.tileInfo
tileInfoB = writer.states.b.tileInfo
if tileInfoA.bpe == 1: # FP8: block-swap swizzle, no VPermlane16Swap
return _lraTileAssignment_fp8_legacy(writer, kernel, module)
subIterKBytes = tileInfoA.subIterKBytes
wavesize = kernel["WavefrontSize"]
mi_m = tileInfoA.mmaTileShape[0]
loadWidth = tileInfoA.loadWidthLR
ldsRowBankSize = writer.states.archCaps["LDSBankCount"] * writer.states.archCaps["LDSBankWidth"]
# With LDS swizzling (gfx950), K-row is one subtile group; without, full DepthU.
ldsKBytes = subIterKBytes if writer.states.subtileLdsSwizzle else tileInfoA.depthUBytes
padBytes = int(getattr(tileInfoA, "ldsRowPadBytes", 0))
ldsRowStride = ldsKBytes + padBytes
numRowsPerLDSBanks = ldsRowBankSize // ldsKBytes
blockSize = ldsKBytes // loadWidth
tmpVgpr = writer.vgprPool.checkOut(6, tag="_lraTileAssignment_legacy_tmpVgpr")
lane16, lane16Group, rotation, rowOffset, colOffset = range(tmpVgpr, tmpVgpr + 5)
module.add(VAndB32(dst=vgpr(lane16Group), src0=vgpr("Serial"), src1=wavesize-1, comment="laneId"))
module.add(VLShiftRightB32(dst=vgpr(lane16Group), shiftHex=hex(mi_m.bit_length()-1), src=vgpr(lane16Group), comment="lane16Group"))
module.add(VAndB32(dst=vgpr(lane16), src0=vgpr("Serial"), src1=mi_m-1, comment="laneId %% 16"))
module.add(VMovB32(dst=vgpr(colOffset), src=vgpr(lane16Group), comment="colOffset = lane16Group"))
if writer.states.subtileLdsSwizzle:
module.add(VLShiftRightB32(dst=vgpr(rotation), shiftHex=hex(numRowsPerLDSBanks.bit_length()-1), src=vgpr(lane16), comment="lds_row_id"))
module.add(VLShiftRightB32(dst=vgpr(rotation), shiftHex=hex(1), src=vgpr(rotation), comment="(lds_row_id //2 )"))
module.add(VLShiftLeftB32(dst=vgpr(rotation), shiftHex=hex(1), src=vgpr(rotation), comment="rotation=(lds_row_id //2) * 2"))
module.add(VAddU32(dst=vgpr(colOffset), src0=vgpr(rotation), src1=vgpr(lane16Group), comment="colOffset = rotation + lane16Group"))
setExecMask(module, writer, 0x33333333, 0x33333333)
module.add(VPermlane16SwapB32(dst=vgpr(colOffset), src=vgpr(colOffset), comment="apply swizzling"))
setExecMask(module, writer, -1, -1)
module.add(VAndB32(dst=vgpr(colOffset), src0=vgpr(colOffset), src1=hex(blockSize-1), comment="colOffset = colOffset %% blockSize"))
# Without swizzling, the LDS M-row stride is depthUBytes (contiguous K row).
# With swizzling, GR writes individual subtile K-groups, so subIterKBytes applies.
# TDM pad adds 16B per row, breaking pow2; fall back to VMul when padded.
if padBytes == 0:
module.add(VLShiftLeftB32(dst=vgpr(rowOffset), shiftHex=hex(ldsRowStride.bit_length()-1), src=vgpr(lane16), comment="offsetRow = %d*lane16" % ldsRowStride))
else:
module.add(VMulLOU32(dst=vgpr(rowOffset), src0=hex(ldsRowStride), src1=vgpr(lane16), comment="offsetRow = %d*lane16" % ldsRowStride))
_computeLROffset(module, tileInfoA, colOffset, rowOffset, writer.states.subtileLdsSwizzle)
_computeLROffset(module, tileInfoB, colOffset, rowOffset, writer.states.subtileLdsSwizzle)
writer.vgprPool.checkIn(tmpVgpr)
_lraWavePartitioning_legacy(module, writer, kernel)
for vgprId in range(len(tileInfoB.sharedVgprLROffset)):
module.add(VAddU32(dst=vgpr(tileInfoB.sharedVgprLROffset[vgprId]), src0=writer.ldsStartOffsetB, src1=vgpr(tileInfoB.sharedVgprLROffset[vgprId]), comment="B matrix offset in LDS"))
return module
def localReadResetOffsetsSubtile(writer, kernel):
module = Module()
module.addComment0("REMOVE WHEN IMPLEMNTED: Placeholder for subtile based LR offset reset code")
for i in range(8):
module.addComment("")
return module
def emitSingleDsRead(tileInfo, sId0, sId1, subIterK, dstTile, swizzled=True):
"""Emit DSLoadB128 instruction(s) for one MMA tile within a subtile.
For wave32 tiles with 8 VGPRs, emits two DSLoadB128 instructions
(each loading 4 VGPRs) since ds_load_b256 is not available.
Args:
tileInfo: TileInfo (for subtileSize, loadRatioGR, sharedVgprLROffset, tc)
sId0: Subtile row index (used for offset computation)
subIterK: subIterK index within the subtile (maps to mfmaC; subtileShape[0]=1 so mfmaR=0)
dstTile: RegisterTileInfo \u2014 destination vgpr tile for the load
swizzled: If True, LDS uses swizzled subtile layout; if False, contiguous K-row layout
Returns a Module. For tiles with numRegs > 4 (e.g. FP8 8-VGPR tiles), emits
multiple ds_read_b128 instructions (one per 4 VGPRs), each using the next
sharedVgprLROffset entry.
"""
REGS_PER_DS_READ = tileInfo.loadWidthLR // 4 # load width in bytes / 4 bytes per VGPR
# du maps to mfmaC, mfmaR is always 0 (subtileShape[0]=1)
mfmaId = tileInfo.getSubtileShapeLinearId(subIterK, 0)
if swizzled:
# Swizzled: GR writes individual subtile K-groups into LDS.
offsetStride = int(tileInfo.subtileSize)
offset = sId0 * offsetStride + sId1 * int(tileInfo.globalSubtileGrid[0]) * offsetStride
else:
# Non-swizzled: full DepthU tile is contiguous in LDS with K as the fast
# dimension. Each M-row is depthUBytes wide. A subtile row covers
# subtileShape[0] * instM M-rows, so stride = that * depthUBytes.
instM = int(tileInfo.mmaTileShape[0])
instK = int(tileInfo.mmaTileShape[1])
subtileShapeM = int(tileInfo.subtileShape[0])
subtileShapeK = int(tileInfo.subtileShape[1])
depthUBytes = int(tileInfo.depthUBytes)
# Add padding
rowPadBytes = getattr(tileInfo, "ldsRowPadBytes", 0)
rowStride = depthUBytes + rowPadBytes
offsetStride = subtileShapeM * instM * rowStride
offset = sId0 * offsetStride + sId1 * subtileShapeK * instK * int(tileInfo.bpe)
dstVgpr = dstTile.regList.indices[0]
numRegs = len(dstTile.regList.indices)
numReadsForTile = numRegs // REGS_PER_DS_READ
module = Module()
for readIdx in range(numReadsForTile):
addrVgpr = tileInfo.sharedVgprLROffset[mfmaId * numReadsForTile + readIdx]
module.add(DSLoadB128(
dst=vgpr(dstVgpr + readIdx * REGS_PER_DS_READ, REGS_PER_DS_READ),
src=vgpr(addrVgpr),
ds=DSModifiers(offset=offset),
comment="Subtile%s[%u, %u] subIterK=%u read=%u" % (tileInfo.tc, sId0, sId1, subIterK, readIdx)))
return module
def emitSubtileDsRead(writer, kernel, tileInfo, subtileId):
module = Module()
sId0 = subtileId[0]
sId1 = subtileId[1]
REGS_PER_DS_READ = tileInfo.loadWidthLR // 4 # load width in bytes / 4 bytes per VGPR
offsetStride = int(tileInfo.subtileSize)
offset = sId0 * offsetStride + sId1 * int(tileInfo.globalSubtileGrid[0]) * offsetStride
lrOffsetIdx = 0
for du in range(tileInfo.subtileShape[1]):
mfmaId = tileInfo.getSubtileShapeLinearId(du, 0)
tileIdx = tileInfo.lrTileIndexForSubtile(sId0, sId1, mfmaId)
dstTile = tileInfo.vgprTiles[tileIdx]
dstVgpr = dstTile.regList.indices[0]
numRegs = len(dstTile.regList.indices)
# Each tile may need multiple ds_read_b128 when numRegs > 4 (e.g. FP8 8-vgpr tiles).
# Each read uses the next sharedVgprLROffset entry.
numReadsForTile = numRegs // REGS_PER_DS_READ
for readIdx in range(numReadsForTile):
addrVgpr = tileInfo.sharedVgprLROffset[lrOffsetIdx]
module.add(DSLoadB128(
dst=vgpr(dstVgpr + readIdx * REGS_PER_DS_READ, REGS_PER_DS_READ),
src=vgpr(addrVgpr),
ds=DSModifiers(offset=offset),
comment="Subtile%s[%u, %u] subIterK=%u read=%u" % (tileInfo.tc, sId0, sId1, du, readIdx)))
lrOffsetIdx += 1
return module
##################################################
# Subroutine to generate LR load code
# Initial idea: maybe store asm in modules in a separate obj?
#
def localReadDoSubtile(tc, writer, kernel):
module = Module()
tileInfo = writer.states.a.tileInfo if tc == 'A' else writer.states.b.tileInfo
for i in range(tileInfo.localSubtileGrid[0]):
for j in range(tileInfo.localSubtileGrid[1]):
module.add(emitSubtileDsRead(writer, kernel, tileInfo, [i, j]))
return module
def localReadDTLInitCommonSwapVgpr(writer, kernel):
module = Module()
atile = writer.states.a.tileInfo
btile = writer.states.b.tileInfo
stmp = writer.sgprPool.checkOut(1, tag="_localReadDTLInitCommonSwapVgpr_stmp")
module.add(SMovB32(dst=sgpr(stmp), src=writer.ldsTotalSize, comment="Store Total Lds Size for one buffer"))
for i in range(len(atile.sharedVgprLROffset)):
vgprId = atile.sharedVgprLROffset[i]
vgprSwapId = atile.sharedVgprLROffsetSwap[i]
module.add(VAddU32(dst=vgpr(vgprSwapId), src0=vgpr(vgprId), src1=sgpr(stmp), comment=""))
module.add(VXorB32(dst=vgpr(vgprSwapId), src0=vgpr(vgprId), src1=vgpr(vgprSwapId), comment=""))
for i in range(len(btile.sharedVgprLROffset)):
vgprId = btile.sharedVgprLROffset[i]
vgprSwapId = btile.sharedVgprLROffsetSwap[i]
module.add(VAddU32(dst=vgpr(vgprSwapId), src0=vgpr(vgprId), src1=sgpr(stmp), comment=""))
module.add(VXorB32(dst=vgpr(vgprSwapId), src0=vgpr(vgprId), src1=vgpr(vgprSwapId), comment=""))
writer.sgprPool.checkIn(stmp)
return module
##################################################
# Subroutine to generate DTL M0 LDS buffer swap
#
def localReadLDSBufferSwap(tc, writer, kernel):
if tc in ['A', 'B']:
ti_ = writer.states.a.tileInfo if tc == 'A' else writer.states.b.tileInfo
return ti_.emitLRLDSBufferSwap(writer, kernel)
else:
ti_ = writer.states.mxsa.tileInfo if tc == 'MXSA' else writer.states.mxsb.tileInfo
return emitScaleLRLDSSwap(ti_, writer, kernel)