Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/Tensile/Components/GlobalWriteBatch.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

3509 lines
204 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
################################################################################
#
# Copyright (C) 2022-2026 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cop-
# ies of the Software, and to permit persons to whom the Software is furnished
# to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM-
# PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNE-
# CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
################################################################################
from rocisa.code import Label, Module, RegSet, TextBlock
from rocisa.container import SMEMModifiers, VOP3PModifiers, MUBUFModifiers, \
SDWAModifiers, replaceHolder, EXEC, VCC, vgpr, sgpr, ContinuousRegister, mgpr
from rocisa.enum import CvtType, HighBitSel, RoundType, SaturateCastType, SelectBit
from rocisa.instruction import BufferAtomicAddF32, BufferAtomicCmpswapB32, \
BufferAtomicCmpswapB64, BufferStoreB16, BufferStoreB32, BufferStoreB64, BufferStoreB128, DSBPermuteB32, FlatAtomicCmpswapB32, \
SAddCU32, SAddU32, SAndB32, \
SAndB64, SAtomicDec, SBarrier, SBranch, SCBranchExecNZ, SCBranchExecZ, \
SCBranchSCC0, SCBranchSCC1, SCmpGtU32, SCmpKGtU32, SCSelectB32, SCmpEQI32, SCmpEQU32, SCmpGtI32, SCmpLeI32, SMinU32, SEndpgm, \
SLShiftLeftB32, SLShiftLeftB64, SLShiftRightB32, SLShiftRightB64, SMovB32, SMovB64, SMulI32, \
SNop, SOrB32, SOrB64, SOrSaveExecB32, SOrSaveExecB64, SSleep, SSubI32, SSubU32, \
SSwapPCB64, SWaitCnt, SWaitAlu, VAShiftRightI32, VAddCCOU32, VAddCOU32, VAddF32, VAddF64, \
VAddI32, VAddPKF16, VAddPKF32, VAddU32, VBfeI32, VCmpEQU32, VCmpGEI32, VCmpGtU32, \
VCmpNeU32, VCmpNeU64, VCndMaskB32, VCvtBF8toF32, VCvtF16toF32, VCvtF32toF16, VCvtF32toI32, \
VCvtFP8toF32, VCvtI32toF32, VCvtPkBF8toF32, VCvtPkF32toBF16, VCvtPkF32toFP16, VCvtPkFP8toF32, \
VFmaF32, VFmaF64, VFmaMixF32, VAndB32, VLShiftLeftB32, VPermlane16SwapB32, VPermlane32SwapB32, \
VLShiftRightB32, VMacF32, VMadMixF32, VMaxF32, VMovB32, VMovB64, VMulF32, VMulF64, \
VMulLOU32, VMulPKF16, VMulPKF32, VPackF16toB32, VReadfirstlaneB32, VRndneF32, VCvtBF16toFP32
from rocisa.functions import vectorStaticMultiply
from ..Common import DataDirection, SemanticVersion, isSubtileMultiDU
from ..Common.DataType import DataType
from ..Component import GlobalWriteComponents
from ..Component import Component
from ..SolutionStructs import Solution
from ..Activation import ActivationModule
from ..AsmStoreState import StoreState
from ..AsmAddressCalculation import AddrCalculation
from ..Components.PackData import formatting, PackData_F16, PackData_BF16, PackData_FLOAT8, PackData_FLOAT8_fnuz
from rocisa.instruction import ECvtF16toF32, ECvtPkFP8toF32, ECvtPkBF8toF32
from ..KernelWriterModules import hasSequentialValuC
from math import ceil, log2
def _scmpGtU32(writer, src, imm, comment=""):
"""ISA-aware scalar compare: s_cmpk_gt_u32 when available, else s_cmp_gt_u32 via temp SGPR."""
if writer.states.asmCaps["HasSCMPK"]:
return SCmpKGtU32(src=src, simm16=imm, comment=comment)
else:
module = Module("scmpGtU32")
tmpSgpr = writer.sgprPool.checkOut(1, preventOverflow=False)
module.add(SMovB32(dst=sgpr(tmpSgpr), src=imm))
module.add(SCmpGtU32(src0=src, src1=sgpr(tmpSgpr), comment=comment))
writer.sgprPool.checkIn(tmpSgpr)
return module
class GlobalWriteBatchComponent(GlobalWriteComponents):
kernel = {"ProblemType": {"OperationType": "GEMM" }}
def __call__(self, kernel: Solution, tPA, tPB, activation: ActivationModule, ss: StoreState, \
batchIdx, applyAlpha, beta, edge, atomic, gwvw, atomicW, \
batchElements, addrE, addrD, addrC, addrBias, addrScaleAVec, addrScaleBVec, addrScaleAlphaVec, isLocalBarrierInit: bool, \
tmpVgpr, tmpVgprDynamic, cvtVgprStruct, activationSetPCStruct, activationTypeStr, batchElementSgprs, tmpSgpr, codeAccVgprRead, \
codeMulAlpha, packdata, parentWriter, factorDim, amdClangVersion: SemanticVersion, numBatches: int,
inter_iter_rowInc: int = 0, direct_next_rowInc: int = 0) -> Module:
return GlobalWriteBatchWriter(kernel, tPA, tPB, activation, ss, batchIdx, applyAlpha, \
beta, edge, atomic, gwvw, atomicW, \
batchElements, addrE, addrD, addrC, addrBias, addrScaleAVec, addrScaleBVec, addrScaleAlphaVec, isLocalBarrierInit, \
tmpVgpr, tmpVgprDynamic, cvtVgprStruct, activationSetPCStruct, activationTypeStr, batchElementSgprs, tmpSgpr, \
codeAccVgprRead, codeMulAlpha, packdata, parentWriter, factorDim, amdClangVersion, numBatches,
inter_iter_rowInc, direct_next_rowInc).emit()
class GlobalWriteBatchWriter:
def __init__(self, kernel: Solution, tPA, tPB, activation: ActivationModule, ss: StoreState, \
batchIdx, applyAlpha, beta, edge, atomic, gwvw, atomicW, \
batchElements, addrE, addrD, addrC, addrBias, addrScaleAVec, addrScaleBVec, addrScaleAlphaVec, isLocalBarrierInit: bool, \
tmpVgpr, tmpVgprDynamic, cvtVgprStruct, activationSetPCStruct, activationTypeStr, batchElementSgprs, tmpSgpr, codeAccVgprRead, \
codeMulAlpha, packdata, parentWriter, factorDim, amdClangVersion: SemanticVersion, numBatches: int,
inter_iter_rowInc: int = 0, direct_next_rowInc: int = 0):
self.kernel = kernel
self.tPA = tPA
self.tPB = tPB
self.activation = activation
self.ss = ss
self.batchIdx = batchIdx
self.applyAlpha = applyAlpha
self.beta = beta
self.edge = edge
self.atomic = atomic
self.gwvw = gwvw
self.atomicW = atomicW
self.batchElements = batchElements
self.addrE = addrE
self.addrD = addrD
self.addrC = addrC
self.addrBias = addrBias
self.addrScaleAVec = addrScaleAVec
self.addrScaleBVec = addrScaleBVec
self.addrScaleAlphaVec = addrScaleAlphaVec
self.isLocalBarrierInit = isLocalBarrierInit
self.activationSetPCStruct = activationSetPCStruct
self.activationTypeStr = activationTypeStr
self.tmpVgpr = tmpVgpr.idx
self.tmpVgprSize = tmpVgpr.size
self.tmpVgprDynamic = None
if tmpVgprDynamic:
self.tmpVgprDynamic = tmpVgprDynamic.idx
self.tmpVgprDynamicSize = tmpVgprDynamic.size
self.cvtVgprStruct = cvtVgprStruct
self.batchElementSgprs = batchElementSgprs
self.tmpSgpr = tmpSgpr
self.codeAccVgprRead = codeAccVgprRead
self.codeMulAlpha = codeMulAlpha
self.packdata = packdata
self.parentWriter = parentWriter
self.storesIssued = 0
self.factorDim = factorDim
self.amdClangVersion = amdClangVersion
# Stateful tracking for N-group OOB guard deduplication (_emitSubtileOobGuard).
# The outer loop iterates N-outer / M-inner, so all M elements within a fixed N
# group share the same N guard result. We emit the N s_cmp/s_cbranch only once
# per N group and skip it for subsequent M elements in the same group.
self._subtilePrevBlockIdxN = -1 # sentinel: no group seen yet
self._subtileNGroupSkipLabel = None # end-of-N-group label (M cbranch target)
self._subtileAllStoresEndLabel = None # end-of-all-stores label (N cbranch target)
self._subtileCloadPrevD1 = -1 # sentinel: last d1 group seen in C load guard
self._subtilePendingSrdDInc = None # deferred SrdD incToNextRow (emitted after N-group label)
self._align8NMaskBlockIdxN = -1 # last blockIdxN for which N mask was computed
self.numBatches = numBatches
# CompactLoopStore: stash the "next batch's first elt rowInc" passed in via
# `inter_iter_rowInc`. Used as the look-ahead fall-through value at the
# LAST emitting elt of the batch (cross-batch transition), where the
# intra-batch forward scan finds no further emitting elt.
# 0 means no override (non-CLS / last batch / atomic).
self.inter_iter_rowInc = inter_iter_rowInc
self.direct_next_rowInc = direct_next_rowInc
# Internal state for GlobalWriteBatch
# 0 for None, 1 for WorkGroupReduction = False, 2 for WorkGroupReduction = True
self.storeBiasD = 0
if self.parentWriter.states.useBias == DataDirection.WRITE and \
(not self.kernel["WorkGroupReduction"]) and \
self.kernel["ProblemType"]["BiasSrc"] == "D":
self.storeBiasD = 1
@property
def needsAccumToDestConversion(self) -> bool:
"""
Check if accumulation values need to be converted to destination type:
1. HighPrecisionAccumulate is enabled (accumulator precision > output precision)
e.g., F32 accumulator -> FP16/BF16/FP8/BF8/I32/I8 output
2. _GlobalAccumulation is not 'MultipleBuffer'
When True, the pack/convert module will be generated to perform:
- F32 -> FP16/BF16 packing
- F32 -> FP8/BF8 conversion (with optional stochastic rounding)
- F32 -> I32/I8 conversion and packing
"""
return self.kernel["ProblemType"]["HighPrecisionAccumulate"] and \
(self.kernel["_GlobalAccumulation"] != 'MultipleBuffer')
@property
def skipRearrangement(self) -> bool:
"""
Check if we can skip v_mov_b32 rearrangement and use WMMA output registers directly.
skipRearrangement changes store to read from (elementSumIdx - elementSumIdx[0]),
but other operations (bias, activation, alpha) work on (elementSumIdx - startVgprValu).
These positions are only the same when no operations modify the data.
Safe to skip rearrangement only when:
1. MIArchVgpr is True (MFMA/WMMA writes directly to arch VGPRs)
2. hasSequentialValuC is True (WMMA output is already sequential)
3. needsAccumToDestConversion is False (no pack module to do the rearrangement)
4. Not a Beta path (Beta paths need separate alpha multiply + beta*C)
5. Not complex (real/imag are interleaved in acc VGPRs, so the relative
offset elementSumIdx[i]-elementSumIdx[0] does not locate the imag half)
6. Single output tile (MIWaveTile == [1,1] and VectorWidth == 1). The
i*gwvw offset only matches the arch-VGPR layout for one contiguous block;
with multiple wave-tiles the acc registers are grouped per tile (e.g. TN +
2x2 read the wrong registers).
7. CompactLoopStore is off. CLS-loop store reads acc VGPRs via
v_movrelsd_2_b32 indexing, which relies on the rearrangement layout.
"""
if self.parentWriter.states.useBias == DataDirection.READ or \
self.kernel.get("CompactLoopStore", False) or \
self.kernel.get("ActivationFuncCall", False) or \
self.applyAlpha or \
self.kernel["ProblemType"].get("UseScaleAlphaVec", 0) or \
self.kernel["ProblemType"].get("UseScaleAB", "") == "Vector" or \
self.kernel["ProblemType"].get("UseScaleCD", False) or \
self.kernel["ProblemType"]["DataType"].isComplex():
return False
miWaveTile = self.kernel.get("MIWaveTile", [1, 1])
if miWaveTile[0] != 1 or miWaveTile[1] != 1 or \
self.kernel.get("VectorWidthA", 1) != 1 or self.kernel.get("VectorWidthB", 1) != 1:
return False
return self.kernel["MIArchVgpr"] and \
hasSequentialValuC(self.kernel) and \
not self.needsAccumToDestConversion and \
not self.beta
@property
def wavelen(self) -> int:
return self.kernel["WavefrontSize"]
@property
def laneSGPRC(self) -> int:
return self.parentWriter.states.laneSGPRCount
@property
def tmpS01(self):
return self.tmpSgpr
@property
def tmpS23(self):
return self.tmpS01 + self.laneSGPRC
@property
def debugConfig(self):
return self.parentWriter.db
@property
def computeDataType(self) -> DataType:
return self.kernel["ProblemType"]["ComputeDataType"]
@property
def destDataType(self) -> DataType:
return self.kernel["ProblemType"]["DestDataType"]
@property
def moduleName(self):
return "globalWriteBatch (Atomic)" if self.atomic else "globalWriteBatch (Non atomic)"
def getEdgeMovInstType(self):
return SMovB32 if self.wavelen == 32 else SMovB64
def getEdgeOrInstType(self):
return SOrB32 if self.wavelen == 32 else SOrB64
def getEdgeAndInstType(self):
return SAndB32 if self.wavelen == 32 else SAndB64
def getSOrSaveExecType(self):
return SOrSaveExecB32 if self.wavelen == 32 else SOrSaveExecB64
@staticmethod
def computeCLSLayout(kernel, numBatches: int):
"""Single source of truth for the CLS loop layout math.
Returns (batchesPerCLSBody, iterCount, m0Step), all derived from ONE shared
read of the MI output layout so the loop trip count and the M0 src stride
can never drift apart:
- batchesPerCLSBody: how many batches one CLS loop body covers. The body
must align to the SrdD-advance period, else the loop would run s_add
SrdD too many times.
- iterCount: numBatches / batchesPerCLSBody (>= 1).
- m0Step: stride of the CLS iter dim in the v_movrelsd_2_b32 src formula
(added to M0 each iteration).
Which dim is the CLS iter dim depends on the output layout:
(a) outerTT1 > 1, VW1 == 1 : iter = wgIdx1, step = OPM*BM*BN*VW0*outerTT0*VW1
(b) outerTT1 == 1, VW1 > 1, SS=False : iter = vw1, step = OPM*BM*BN*VW0*outerTT0
(c) outerTT1 == 1, VW1 == 1, SS=True : iter = tIdx, step = NEPBS / inner_dims
Store paths not covered by the CLS loop (non-MI, StoreRemap, StreamK) and
non-divisible / non-regular layouts fall back to a single iteration
(batchesPerCLSBody = numBatches), where m0Step is dead.
"""
batchesPerBody = numBatches
m0Step = 1
if not kernel["EnableMatrixInstruction"]:
return batchesPerBody, 1, m0Step
VW0 = kernel["VectorWidthA"]
VW1 = kernel["VectorWidthB"]
outerTT0 = kernel["MIWaveTile"][0] // VW0
outerTT1 = kernel["MIWaveTile"][1] // VW1
miT = min(kernel["MatrixInstM"], kernel["MatrixInstN"])
miM_ = (kernel["MatrixInstM"] * kernel["MatrixInstBM"]) if (kernel["MatrixInstM"] == 4) else miT
miN_ = (kernel["MatrixInstN"] * kernel["MatrixInstBN"]) if (kernel["MatrixInstN"] == 4) else miT
matrixInstBM = 1 if (kernel["MatrixInstM"] == 4) else kernel["MatrixInstBM"]
matrixInstBN = 1 if (kernel["MatrixInstN"] == 4) else kernel["MatrixInstBN"]
OPM = miM_ * miN_ // kernel["WavefrontSize"]
NEPBS = kernel["NumElementsPerBatchStore"]
if outerTT1 > 1 and VW1 == 1:
m0Step = OPM * matrixInstBM * matrixInstBN * VW0 * outerTT0 * VW1
if numBatches % outerTT1 == 0:
batchesPerBody = max(1, numBatches // outerTT1)
elif outerTT1 == 1 and VW1 > 1 and not kernel["SourceSwap"]:
m0Step = OPM * matrixInstBM * matrixInstBN * VW0 * outerTT0
if numBatches % VW1 == 0:
batchesPerBody = max(1, numBatches // VW1)
elif outerTT1 == 1 and VW1 == 1 and kernel["SourceSwap"]:
inner_dims = VW0 * outerTT0 * matrixInstBM * matrixInstBN
if NEPBS % inner_dims == 0:
m0Step = max(1, NEPBS // inner_dims)
batchesPerBody = max(1, ceil(numBatches / NEPBS))
# StoreRemap / StreamK store paths are not covered by the CLS loop: force a
# single iteration (body = all batches). m0Step is left as derived above to
# preserve the original emitted SrdD step (it is dead when iterCount == 1).
if kernel.get("StoreRemapVectorWidth", 0) != 0 or kernel.get("StreamK", 0) != 0:
batchesPerBody = numBatches
iterCount = max(1, numBatches // batchesPerBody)
return batchesPerBody, iterCount, m0Step
@staticmethod
def computeBatchesPerCLSBody(kernel, numBatches: int) -> int:
return GlobalWriteBatchWriter.computeCLSLayout(kernel, numBatches)[0]
def _computeBatchesPerCLSBody(self) -> int:
return GlobalWriteBatchWriter.computeCLSLayout(self.kernel, self.numBatches)[0]
@staticmethod
def computeCLSIterCount(kernel, numBatches: int) -> int:
"""CLS loop iter count = numBatches / batchesPerCLSBody. Minimum 1."""
return GlobalWriteBatchWriter.computeCLSLayout(kernel, numBatches)[1]
def _computeCLSIterCount(self) -> int:
return GlobalWriteBatchWriter.computeCLSLayout(self.kernel, self.numBatches)[1]
def _computeCLSLayout(self):
return GlobalWriteBatchWriter.computeCLSLayout(self.kernel, self.numBatches)
def emit(self) -> Module:
assert self._checkAtomicPreconditions()
module = Module(self.moduleName)
self._prolog(module)
# The bias/SAV drain barrier ordering is a multi-DU-only hardening that
# prevents cross-wave LDS corruption from ds_bpermute. Multi-DU emits the
# drain+barrier before the _emitAdd subtile stores; non-multi-DU emits
# _emitAdd first.
isMultiDU = isSubtileMultiDU(self.kernel)
drainBiasSav = self.kernel.get("UseSubtileImpl") and \
(self.parentWriter.states.useBias != DataDirection.NONE or \
self.kernel["ProblemType"].get("UseScaleAlphaVec", 0))
if not isMultiDU:
self._emitAdd(module)
if drainBiasSav:
module.add(SWaitCnt(dscnt=0, comment="drain bias/SAV LDS reads"))
module.add(SBarrier(comment="sync waves before subtile paired stores"))
if isMultiDU:
self._emitAdd(module)
self._epilog(module)
# CompactLoopStore CLS countdown tail: emit countdown + branch + s_endpgm at
# END of the CLS-loop body (= last batch of batchesPerCLSBody). Gated by
# CompactLoopStore so non-CLS .s matches baseline (no CLS tail emit).
if self.kernel["CompactLoopStore"]:
# updateCoord1 = (edge or multi-packed)
if self.direct_next_rowInc != 0 and self.ss.elementAddr:
kw = self.parentWriter
rowInc = self.direct_next_rowInc
bufferStore = self.kernel["BufferStore"]
updateCoord1 = self.edge or len(self.kernel["PackedC1IndicesX"]) > 1
emit_coord1 = (not bufferStore) or updateCoord1
emit_rowptr = (not self.ss.optSrdIncForRow) and bufferStore
if emit_coord1 or emit_rowptr:
module.addComment0("CLS look-ahead: next batch's row advance at end of this batch")
# Reuse the canonical per-element advance helpers (emitCoord1Advance +
# emitRowPtrAdvance) on a representative AddrCalculation instead of
# re-implementing the coord1 / rowPtr advance inline. The look-ahead is just
# the next batch's emitAddressSetupCode row-advance, produced from the same
# source. Same spirit as the delayed-primer reuse of incrementToNextRow.
addrCalc = self.ss.elementAddr[0]
if emit_coord1:
module.add(addrCalc.emitCoord1Advance(rowInc, self.tmpS01,
comment="coord1.la: coord1Vgpr += rowInc (look-ahead)",
scomment="rowInc look-ahead"))
if emit_rowptr:
# Reuse the canonical row-pointer advance (the SAME helper the per-element
# advance uses in emitAddressSetupCode) instead of re-implementing the
# cinRowPtr/coutRowPtrD adds inline. This also makes the look-ahead cover
# coutRowPtrE / coutRowPtrBias / packed-C1 for free (same conditions).
module.add(addrCalc.emitRowPtrAdvance(self.kernel, self.ss, self.tmpS01, rowInc, lookahead=True))
clsLabel = getattr(self.ss, "_clsLoopLabel", None)
if clsLabel is not None and (self._computeBatchesPerCLSBody() - 1 == self.batchIdx) and self.ss.elementAddr:
module.add(SSubI32(dst=sgpr("CLSLoopCounter"), src0=sgpr("CLSLoopCounter"), src1=1))
module.add(SCmpEQU32(src0=sgpr("CLSLoopCounter"), src1=0))
module.add(SCBranchSCC0(clsLabel.getLabelName(), "loop while counter != 0"))
# if not self.kernel["StreamK"] == 3:
# module.add(SEndpgm(comment="stop here after CLS loop"))
self.ss._clsLoopLabel = None
return module
def globalStoreWait(self, elementIdx, waitCnter, vlcntTotalIssued, dscntTotalIssued, interleaveStoreVmcnt: bool):
vlcnt = -1
dscnt = -1
vscnt = -1
isSingleKernel = ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel") or self.kernel["StreamK"] > 0
if interleaveStoreVmcnt:
waitLocalLoadCnt = 0
waitLocalLoadCntStrList = []
waitLoadCnt = 0
waitLoadCntStrList = []
# Calculate global loads
if self.beta:
waitLoadCnt += self.betaLoadIssued[elementIdx]
waitLoadCntStrList.append("%d (beta)"%self.betaLoadIssued[elementIdx])
if self.loadE:
waitLoadCnt += self.eLoadIssued[elementIdx]
waitLoadCntStrList.append("%d (load E)"%self.eLoadIssued[elementIdx])
if self.parentWriter.states.useGateResidual and elementIdx < len(self.gateLoadIssued):
waitLoadCnt += self.gateLoadIssued[elementIdx]
waitLoadCntStrList.append("%d (load Gate)"%self.gateLoadIssued[elementIdx])
# Calculate local loads
# UseSubtileImpl with bias/SAV: skip bias/SAV LDS loads from interleaved
# waitcnt and rely on the batch-start barrier for LDS synchronization.
subtileBarrierDrains = self.kernel.get("UseSubtileImpl") and \
(self.parentWriter.states.useBias != DataDirection.NONE or \
self.kernel["ProblemType"].get("UseScaleAlphaVec", 0))
if self.parentWriter.states.useBias == DataDirection.READ and not subtileBarrierDrains:
waitLocalLoadCnt += self.biasLoadIssued[elementIdx]
waitLocalLoadCntStrList.append("%d (bias)"%self.biasLoadIssued[elementIdx])
if (self.kernel["ProblemType"]["UseScaleAB"] == "Vector") and isSingleKernel:
waitLocalLoadCnt += self.scaleAVecLoadIssued[elementIdx]
waitLocalLoadCntStrList.append("%d (scaleAVec)"%self.scaleAVecLoadIssued[elementIdx])
waitLocalLoadCnt += self.scaleBVecLoadIssued[elementIdx]
waitLocalLoadCntStrList.append("%d (scaleBVec)"%self.scaleBVecLoadIssued[elementIdx])
# Skip scaleAlphaVec when subtileBarrierDrains
if self.kernel["ProblemType"]["UseScaleAlphaVec"] and isSingleKernel and not subtileBarrierDrains:
waitLocalLoadCnt += self.scaleAlphaVecLoadIssued[elementIdx]
waitLocalLoadCntStrList.append("%d (scaleAlphaVec)"%self.scaleAlphaVecLoadIssued[elementIdx])
# Get vlcnt and dscnt
vlcnt = vlcntTotalIssued - waitLoadCnt
if waitCnter[0] > 0 or vlcnt != waitCnter[0] : # Check if global load issued > 0
if waitCnter[0] == vlcnt: # No need to wait if the global load cnt doesn't change
vlcnt = -1
else:
waitCnter[0] = vlcnt
else:
vlcnt = -1
dscnt = dscntTotalIssued - waitLocalLoadCnt
if waitCnter[1] > 0 or dscnt != waitCnter[1]: # Check if local load issued > 0
if waitCnter[1] == dscnt: # No need to wait if the local load cnt doesn't change
dscnt = -1
else:
waitCnter[1] = dscnt
else:
dscnt = -1
# Get vscnt
if vlcnt != -1 and not (self.parentWriter.states.asmCaps["SeparateVscnt"] or self.parentWriter.states.asmCaps["SeparateVMcnt"]):
if self.kernel.get("UseSubtileImpl") and not self.kernel["GroupLoadStore"]:
vscnt = 0
else:
vscnt = self.storesIssued if not self.kernel["GroupLoadStore"] else 0
if (vlcnt != -1) or (vscnt != -1) or (dscnt != -1):
# Get comment
comment = ""
if vlcnt != -1:
tmp = ""
for cntStr in waitLoadCntStrList:
tmp += " - %s"%cntStr
comment = "vlcnt(%s) = %d%s"%(vlcnt, vlcntTotalIssued, tmp)
if vscnt != -1:
comment = comment + (" " if comment else "") + "vscnt(%s)"%(vscnt)
if dscnt != -1:
tmp = ""
for cntStr in waitLocalLoadCntStrList:
tmp += " - %s"%cntStr
comment = comment + (" " if comment else "") + "dscnt(%d) = %d%s"%(dscnt, dscntTotalIssued, tmp)
# if not self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
return SWaitCnt(dscnt=dscnt, vlcnt=vlcnt, vscnt=vscnt, comment="%s (interleaved)"%comment)
else:
commentList = []
# Global read wait
if self.beta:
vlcnt = 0
commentList.append("Beta")
if self.loadE:
vlcnt = 0
commentList.append("E")
if self.parentWriter.states.useGateResidual:
vlcnt = 0
commentList.append("Gate")
# Local read wait
if self.parentWriter.states.useBias == DataDirection.READ:
dscnt = 0
commentList.append("Bias LDS")
if (self.kernel["ProblemType"]["UseScaleAB"] == "Vector") and isSingleKernel:
dscnt = 0
commentList.append("ScaleABVec")
if self.kernel["ProblemType"]["UseScaleAlphaVec"] and isSingleKernel:
dscnt = 0
commentList.append("ScaleAlphaVec")
if (vlcnt != -1) or (dscnt != -1):
# Get comment
comment = "wait for " + commentList[0]
for c in commentList[1:]:
comment += ", %s"%c
return SWaitCnt(dscnt=dscnt, vlcnt=vlcnt, vscnt=vscnt, comment=comment)
return None
##############################################################################
# choose the ADD instruction for combining external C with internal C
# used in atomic=1 case to compute expected external data
##############################################################################
def _chooseAddForAtomic(self, kernel, dst, src0, src1, comment):
module = Module("chooseAddForAtomic")
if kernel["ProblemType"]["DataType"].isBFloat16():
if kernel["_GlobalAccumulation"]:
module.add(VAddF32(dst, src0, src1, comment=comment))
elif kernel["ProblemType"]["DataType"].isHalf():
if kernel["_GlobalAccumulation"]:
module.add(VAddF32(dst, src0, src1, comment=comment))
elif kernel["ProblemType"]["HighPrecisionAccumulate"]:
if self.parentWriter.states.asmCaps["v_fma_mix_f32"]:
module.add(VFmaMixF32(dst, src0, 1, src1, comment=comment))
elif self.parentWriter.states.asmCaps["v_mad_mix_f32"]:
module.add(VMadMixF32(dst, src0, 1, src1, comment=comment))
else:
assert False, "No valid v_mad_mix_f32 equivalent"
else:
module.add(VAddPKF16(dst, src0, src1, comment))
elif kernel["ProblemType"]["DataType"].isInt8x4() or kernel["ProblemType"]["DataType"].isInt8():
# assume v_add_i32 can be used in place of v_add_f32
# need to add saturation directive to v_add_i32 instruction to clamp integer arithmetic
module.add(VAddI32(dst, src0, src1, comment=comment))
elif kernel["ProblemType"]["DataType"].isSingle():
module.add(VAddF32(dst, src0, src1, comment=comment))
else:
#support for double
module.add(VAddF64(dst, src0, src1, comment=comment))
return module
def _emitLdsBarrierIfNeeded(self, targetModule: Module, isSingleKernel: bool):
"""Emit the LDS write barrier once per batch; idempotent via
self.isLocalBarrierInit so repeat callers (preamble/body) are no-ops.
"""
if isSingleKernel and (not self.isLocalBarrierInit):
targetModule.add(SWaitCnt(dscnt=0, comment="Wait for LDS write"))
targetModule.add(SBarrier(comment="LDS write barrier"))
self.isLocalBarrierInit = True
def _emitElt0EpilogueLoads(self, module: Module, addrCalc: 'AddrCalculation',
mask, elementIdx: int, bufferOOB,
loadInputCode: Module, factor_gwvw: int,
preamble: bool = False):
"""Emit one element's epilogue LDS loads (Bias -> ScaleAlphaVec ->
ScaleAVec/ScaleBVec, then reorder). Shared by the CLS preamble
(preamble=True: address compute only) and the per-element loop
(preamble=False: also ds_load). xxxLoadIssued lists are appended in the
body only, so the preamble adds no extra entry.
"""
addrBiasVgpr = addrCalc.addrBiasVgpr
addrScaleAVecVgpr = addrCalc.addrScaleAVecVgpr
addrScaleBVecVgpr = addrCalc.addrScaleBVecVgpr
addrScaleAlphaVecVgpr = addrCalc.addrScaleAlphaVecVgpr
dataBias = self.ss.elementDataBias[elementIdx]
dataScaleAVec = self.ss.elementDataScaleAVec[elementIdx]
dataScaleBVec = self.ss.elementDataScaleBVec[elementIdx]
dataScaleAlphaVec = self.ss.elementDataScaleAlphaVec[elementIdx]
skipLoad = True if self.factorDim else False
isSingleKernel = ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel") or self.kernel["StreamK"] > 0
def addEpilogueLoad(modGwvw, ldName: str, addrVecVgpr, addrVec, dataVec, loadedDataVec,
vecOffset, gwvw, referenceVgpr, dim, referenceDim,
skipLoad: bool = False, comment: str = "") -> int:
"""One vector's epilogue load: emitLdChange (address compute) always runs;
captured `preamble` gates ONLY the ds_load (preamble=True = address only).
"""
loadsIssued = 0
tmpInrSgpr = self._epilogScratchSgpr(1)
module.add(addrCalc.emitLdChange(self.kernel, self.ss, ldName, self.edge, self.beta, mask, bufferOOB, (elementIdx == 0), self.tmpVgpr, tmpInrSgpr, addrVecVgpr, addrVec, dim))
self._epilogScratchFree(tmpInrSgpr)
ldsAddrVgpr = referenceVgpr if (referenceVgpr and (dim == referenceDim)) else addrVecVgpr
if dataVec not in loadedDataVec:
# GroupLoadStore routes barrier+ds_load into `loadInputCode` so they
# are grouped with the C input; otherwise both go into `module`.
targetModule = loadInputCode if self.kernel["GroupLoadStore"] else module
self._emitLdsBarrierIfNeeded(targetModule, isSingleKernel)
if not preamble:
targetModule.add(self.parentWriter.addLdsLoad(self.kernel["ProblemType"]["ComputeDataType"], dataVec, ldsAddrVgpr, vecOffset, gwvw, comment=comment))
loadedDataVec[dataVec] = ceil(self.kernel["ProblemType"]["ComputeDataType"].numBytes() * gwvw / 16)
loadsIssued = ceil(self.kernel["ProblemType"]["ComputeDataType"].numBytes() * gwvw / 16)
if (self.ss.cfg.gwvw != gwvw) and (not skipLoad):
remain_load = self.ss.cfg.gwvw - 1
#For below ds_read instruction do not add bias issued , because of all ds_load instructions need to be completed at the same time in this batch.
for r in range(remain_load):
modGwvw.add(self.parentWriter.addLdsLoad(self.kernel["ProblemType"]["ComputeDataType"], dataVec, ldsAddrVgpr, vecOffset, factor_gwvw, comment=comment))
return loadsIssued
modGwvwScale = []
localReferenceVgpr = None
if self.parentWriter.states.useBias == DataDirection.READ:
modGwvwBias = Module("GwvwBias")
self.localLoadsBiasIssued += addEpilogueLoad(modGwvwBias, 'Bias', addrBiasVgpr, self.addrBias, dataBias, self.loadedDataBias, addrCalc.biasOffset[self.factorDim], factor_gwvw, localReferenceVgpr, self.factorDim, self.factorDim, skipLoad=skipLoad, comment="load Bias")
localReferenceVgpr = addrBiasVgpr
modGwvwScale.append(modGwvwBias)
if not preamble:
self.biasLoadIssued.append(len(self.loadedDataBias) * ceil(self.kernel["ProblemType"]["ComputeDataType"].numBytes() * factor_gwvw / 16))
if self.kernel["ProblemType"]["UseScaleAlphaVec"] and isSingleKernel:
modGwvwScaleAlpha = Module("GwvwScaleAlpha")
# For multi-DU, the subtile ScaleAlphaVec epilogue load passes None as the
# LDS reference vgpr; non-multi-DU uses localReferenceVgpr.
savIsMultiDU = isSubtileMultiDU(self.kernel)
if savIsMultiDU:
savLdsRefVgpr = None if (self.kernel.get("UseSubtileImpl") and addrScaleAlphaVecVgpr is not None) else localReferenceVgpr
else:
savLdsRefVgpr = localReferenceVgpr
self.loadsScaleAlphaVecIssued += addEpilogueLoad(modGwvwScaleAlpha, "ScaleAlphaVec", addrScaleAlphaVecVgpr, self.addrScaleAlphaVec, dataScaleAlphaVec, self.loadedDataScaleAlphaVec, addrCalc.scaleAlphaVecOffset[self.factorDim], factor_gwvw, savLdsRefVgpr, self.factorDim, self.factorDim, skipLoad=skipLoad, comment="load scaleAlpha")
if localReferenceVgpr == None:
localReferenceVgpr = addrScaleAlphaVecVgpr
modGwvwScale.append(modGwvwScaleAlpha)
if not preamble:
self.scaleAlphaVecLoadIssued.append(len(self.loadedDataScaleAlphaVec) if self.factorDim else len(self.loadedDataScaleAlphaVec) * ceil(self.kernel["ProblemType"]["ComputeDataType"].numBytes() * factor_gwvw / 16))
if (self.kernel["ProblemType"]["UseScaleAB"] == "Vector") and isSingleKernel:
modGwvwScaleA = Module("GwvwScaleA")
modGwvwScaleB = Module("GwvwScaleB")
self.loadsScaleAVecIssued += addEpilogueLoad(modGwvwScaleA, "ScaleAVec", addrScaleAVecVgpr, self.addrScaleAVec, dataScaleAVec, self.loadedDataScaleAVec, addrCalc.scaleAVecOffset, self.ss.cfg.gwvw, localReferenceVgpr, 0, self.factorDim, comment="load scaleA")
self.loadsScaleBVecIssued += addEpilogueLoad(modGwvwScaleB, "ScaleBVec", addrScaleBVecVgpr, self.addrScaleBVec, dataScaleBVec, self.loadedDataScaleBVec, addrCalc.scaleBVecOffset, 1, localReferenceVgpr, 1, self.factorDim, skipLoad=True, comment="load scaleB")
if localReferenceVgpr == None:
localReferenceVgpr = addrScaleAVecVgpr if self.factorDim == 0 else addrScaleBVecVgpr
modGwvwScale.append(modGwvwScaleA)
modGwvwScale.append(modGwvwScaleB)
if not preamble:
self.scaleAVecLoadIssued.append(len(self.loadedDataScaleAVec) * ceil(self.kernel["ProblemType"]["ComputeDataType"].numBytes() * self.ss.cfg.gwvw / 16))
self.scaleBVecLoadIssued.append(len(self.loadedDataScaleBVec))
# Reorder scale
length = 0
for mod in modGwvwScale:
length = max(length, len(mod.items()))
for index in range(0, length):
for mod in modGwvwScale:
if len(mod.items()) > index:
module.add(mod.items()[index])
def _emitElt0LdsPreambleBeforeBanner(self, module: Module, bufferOOB,
loadInputCode: Module, factor_gwvw: int):
"""CompactLoopStore preamble (batch 0): hoist elt-0 LDS setup out of the
per-element loop so the CLS countdown loop need not re-emit it each iter. The
body dedups via state flags (ss.singleCol*AddrUpdated, isLocalBarrierInit).
Per-section gating (addr compute only if optSingleColVgpr; D scaleToBpe /
sgpr-offset init / MSB prewarm NonEdge-only) is documented inline below.
"""
elementIdx = 0
addrCalc: AddrCalculation = self.ss.elementAddr[elementIdx]
addrBiasVgpr = addrCalc.addrBiasVgpr
addrCVgpr = addrCalc.addrCVgpr
addrDVgpr = addrCalc.addrDVgpr
addrScaleAlphaVecVgpr = addrCalc.addrScaleAlphaVecVgpr
addrScaleAVecVgpr = addrCalc.addrScaleAVecVgpr
addrScaleBVecVgpr = addrCalc.addrScaleBVecVgpr
mask = self.ss.elementMask[elementIdx]
bufferOOB = None
isSingleKernel = ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or \
self.kernel["GlobalSplitUAlgorithm"] == "MultipleBufferSingleKernel") or \
self.kernel["StreamK"] > 0
# emitAddressSetupCode for elt 0 is safe: coordOffset0=0 and rowInc=0, so it
# only emits the (d1,vc1,d0,vc0) comment + sets coord0Vgpr state -- no real
# instructions. The body re-runs it for elt 0 (just re-emits the comment).
tmpInrSgpr = self._epilogScratchSgpr(1)
module.add(addrCalc.emitAddressSetupCode(self.kernel, self.tPB, self.ss, self.tmpVgpr, \
tmpInrSgpr, self.edge, self.beta, self.atomic, elementIdx, addrDVgpr))
self._epilogScratchFree(tmpInrSgpr)
# elt-0 epilogue hoist: optSingleColVgpr -> hoist the address compute
# (preamble=True; body folds it via ss.singleCol*AddrUpdated). Else (Edge /
# non-optSingleCol) -> hoist only the LDS barrier.
if self.ss.optSingleColVgpr:
self._emitElt0EpilogueLoads(module, addrCalc, mask, elementIdx, bufferOOB,
loadInputCode, factor_gwvw,
True)
else:
targetModule = loadInputCode if self.kernel["GroupLoadStore"] else module
self._emitLdsBarrierIfNeeded(targetModule, isSingleKernel)
# D scaleToBpe -- NonEdge only (Edge has its own per-elt path that is not
# safe to hoist).
if not self.edge:
tmpInrSgpr = self._epilogScratchSgpr(1)
if self.kernel["GlobalSplitU"] == 1 or (self.kernel["GlobalSplitUAlgorithm"] != "MultipleBufferSingleKernel"):
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'D', self.edge, self.beta, \
mask, bufferOOB, True, self.tmpVgpr, tmpInrSgpr, addrDVgpr, self.addrD, 0))
if self.beta:
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'C', self.edge, self.beta, \
mask, bufferOOB, True, self.tmpVgpr, tmpInrSgpr, addrCVgpr, self.addrC, 0))
self._epilogScratchFree(tmpInrSgpr)
# Init sgpr offset -- NonEdge only. Needed for the delayed-pattern
# incrementToNextRow when optSrdIncForRow=1 (NonEdge). Edge path
# (optSrdIncForRow=0) can skips the delayed pattern entirely so these inits
# might not needed.
module.add(SMovB32(dst=sgpr(self.tmpS01), src=0, comment="Init sgpr offset"))
module.add(SMovB32(dst=sgpr(self.tmpS01+1), src=0, comment="Init sgpr offset"))
# Prewarm VGPR MSB bank -- NonEdge only. Forces the upper-bank toggle
# for the upcoming ds_load to settle ahead of time so the loop body
# avoids a stall on first use.
prewarmVgpr = None
if self.parentWriter.states.useBias == DataDirection.READ and addrBiasVgpr is not None:
prewarmVgpr = addrBiasVgpr
elif self.kernel["ProblemType"]["UseScaleAlphaVec"] and addrScaleAlphaVecVgpr is not None:
prewarmVgpr = addrScaleAlphaVecVgpr
elif (self.kernel["ProblemType"]["UseScaleAB"] == "Vector") and addrScaleAVecVgpr is not None:
prewarmVgpr = addrScaleAVecVgpr
if prewarmVgpr is not None:
module.add(VMovB32(dst=vgpr(prewarmVgpr), src=vgpr(prewarmVgpr),
comment="prewarm VGPR MSB bank for upcoming ds_load"))
def _lookaheadRowInc(self, elementIdx: int) -> int:
"""CompactLoopStore look-ahead: the rowInc that the NEXT EMITTING elt's
s_add will consume, so this elt's delayed AFTER-primer can encode it
(removes the off-by-one in the CLS primer chain). Forward-scan from
elementIdx+1 for the first elt with rowInc != 0; if none remain in this
batch, fall through to self.inter_iter_rowInc (the cross-batch advance,
precomputed by KernelWriterAssembly; 0 on the last batch). Returns 0 when
there is no override (non-CLS, no elementAddr, or no further advance).
"""
if not (self.kernel["CompactLoopStore"] and self.ss.elementAddr):
return 0
for _j in range(elementIdx + 1, len(self.batchElements)):
_ri = self.ss.elementAddr[_j].rowInc
if _ri != 0:
return _ri
return self.inter_iter_rowInc
def _epilogScratchSgpr(self, n: int = 1):
"""Scratch sgpr for epilogue address math.
CompactLoopStore: borrow `n` aligned sgpr(s) from the pool so the address
math does NOT clobber the row-increment primer chain (which lives in
tmpS01 / tmpS01+1). Non-CLS: reuse self.tmpSgpr -- this is the baseline, so
CLS=0 codegen is bit-for-bit unchanged. Pair with _epilogScratchFree.
"""
if self.kernel["CompactLoopStore"]:
return self.parentWriter.sgprPool.checkOutAligned(n, 1)
return self.tmpSgpr
def _epilogScratchFree(self, sgprIdx):
"""Release a scratch sgpr from _epilogScratchSgpr (no-op for non-CLS, which
reused self.tmpSgpr and owns nothing).
"""
if self.kernel["CompactLoopStore"]:
self.parentWriter.sgprPool.checkIn(sgprIdx)
def _prolog(self, module: Module):
module.addComment0("optSingleColVgpr=%u optSharedColVgpr=%u optSGPRUsage=%s optSrdIncForRow=%u factorDim=%u" % \
(self.ss.optSingleColVgpr, self.ss.optSharedColVgpr, self.ss.optSGPRUsage, self.ss.optSrdIncForRow, self.factorDim))
if self.kernel["StoreSyncOpt"]:
self._storeSyncOpt(module)
# comment tt1, tt0, vc1, vc0
# tt = thread tile, vc=vector component
commentStr = "Global Write%s%s Batch #%u (d1,d0,vc1,vc0) =\n " \
% (" Beta" if self.beta else "", " Edge" if self.edge else "", self.batchIdx)
commentStr = ''.join([commentStr] \
+ ["(%u,%u,%u,%u:vw%u%s)%s" % \
(element[0], element[1], element[2], element[3], self.gwvw,
":vaw:%u"%self.atomicW if self.atomic else "",
"" if idx == len(self.batchElements) -1 else "; ")
for idx, element in enumerate(self.batchElements)])
# setupStoreElementsForBatch must run before the CLS preamble call so
# ss.elementAddr[0] is populated. For non-CLS the batch banner is emitted
# right after in the else branch (matches baseline ordering); for CLS the
# banner is moved into the CLS header block below (after sgpr setup, before
# the CLS label) so the per-iter store body is visually a single labelled
# unit.
if self.kernel["_GlobalAccumulation"] != "MultipleBufferSingleKernel":
self.ss.setupStoreElementsForBatch(self.kernel, self.gwvw, self.batchElements, self.batchElementSgprs, isOptNLL=False, factorDim=self.factorDim)
else:
self.ss.setupStoreElementsForBatch(self.kernel, self.gwvw, self.batchElements, self.batchElementSgprs, isOptNLL=True, factorDim=self.factorDim)
self.localLoadsBiasIssued = 0
self.storesIssued = 0
self.loadsBetaIssued = 0
self.loadsEIssued = 0
self.loadsGateIssued = 0
self.loadsScaleAVecIssued = 0
self.loadsScaleBVecIssued = 0
self.loadsScaleAlphaVecIssued = 0
########################################
# calculate addr and masks
# On input, coord0 and coord1 are VGPRs computed in the pre-batch code, based
# on the thread and tid number. These are ELEMENT offsets from start of tensor C
# for the top-left corner this thread will write. These are not changed
# across all the store loop iters.
if self.debugConfig["ConservativeWaitCnt"] & 0x10:
module.add(SBarrier(comment="debug"))
module.add(SWaitCnt(vlcnt=0, vscnt=0, comment="ConservativeWaitCnt"))
module.add(SBarrier(comment="debug"))
if not self.edge and self.debugConfig["ForceEdgeStores"] >= 2:
module.add(self.parentWriter.getBomb()) # should not get here
if self.edge and self.debugConfig["AssertNoEdge"]:
module.add(self.parentWriter.getBomb()) # should not get here
########################################
# rC *= alpha
if not self.kernel["InterleaveAlpha"] and self.applyAlpha and self.parentWriter.alphaBeforeLoadC:
module.addComment1("rC *= alpha batchElements=%s"%self.batchElements)
if self.codeMulAlpha is None:
elementIdx = 0
while elementIdx < len(self.batchElements):
isEnd = (elementIdx == len(self.batchElements) - 1)
if not isEnd and (self.ss.elementSumIdx[elementIdx] + 1 == self.ss.elementSumIdx[elementIdx + 1]) and (self.ss.elementSumIdx[elementIdx] % 2 == 0):
module.add(self._applyAlpha(self.kernel, self.gwvw, self.ss.elementSumIdx, elementIdx, self.tmpS01, usePK=True))
elementIdx += 2
else:
module.add(self._applyAlpha(self.kernel, self.gwvw, self.ss.elementSumIdx, elementIdx, self.tmpS01))
elementIdx += 1
else:
regsPerScalar = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr # register per scalar
for elementIdx in range(len(self.batchElements)):
for vi in range(self.gwvw):
rh = replaceHolder(self.codeMulAlpha.popFirstItem(), self.ss.elementSumIdx[elementIdx]*regsPerScalar + regsPerScalar*vi)
if (self.kernel["GlobalSplitU"] == 1) and (self.kernel["ProblemType"]["ComputeDataType"].isSingle() and self.kernel["ProblemType"]["DataType"].isInt8()):
srcRegName = rh.getParams()[2].getCompleteRegName()
module.add(VCvtI32toF32(dst=vgpr(srcRegName), src=vgpr(srcRegName), comment="Convert MI out reg to fp32"))
module.add(rh)
loadInputCode = Module("loadInputCode")
self.betaLoadIssued = []
self.eLoadIssued = []
self.gateLoadIssued = []
self.biasLoadIssued = []
self.scaleAVecLoadIssued = []
self.scaleBVecLoadIssued = []
self.scaleAlphaVecLoadIssued = []
self.loadedDataBeta = {}
self.loadedDataE = {}
self.loadedDataGate = {}
self.loadedDataBias = {}
self.loadedDataScaleAVec = {}
self.loadedDataScaleBVec = {}
self.loadedDataScaleAlphaVec = {}
#when factorDim = 1 the bias's gwvw is alwasy be 1.
factor_gwvw = 1 if self.factorDim else self.ss.cfg.gwvw
# CompactLoopStore CLS header (batch 0 only): preamble + sgpr setup + CLS
# label + M0 assignment + M0 step. Wrapped in `if CompactLoopStore` as a
# unit so non-CLS .s emits ONLY the original module.addComment2(commentStr)
# in the else branch (matches baseline)
if self.kernel["CompactLoopStore"] and self.batchIdx == 0:
self._emitElt0LdsPreambleBeforeBanner(module, None, #bufferOOB,
loadInputCode,
factor_gwvw)
module.add(SMovB32(dst=sgpr("CLSm0Base"), src=hex(0x0), comment="CLS M0 base = 0"))
module.add(SMovB32(dst=sgpr("CLSLoopCounter"), src=hex(self._computeCLSIterCount()), comment="CLS loop iter count"))
module.addComment2(commentStr)
self.ss._clsLoopLabel = Label(self.parentWriter.labels.getNameInc("CLS"), "")
module.add(self.ss._clsLoopLabel)
module.add(SMovB32(dst=mgpr(0), src=sgpr("CLSm0Base"),
comment="LDS clamp at sgpr(CLSm0Base)"))
# CLS M0 step: M0 indirectly offsets the src VGPR index of v_movrelsd_2_b32.
# Step = the stride of the CLS iter dim in the SRC formula; taken from the
# same layout derivation as the loop iter count (computeCLSLayout) so the
# two can never drift apart.
_, _, cls_m0_step = self._computeCLSLayout()
module.add(SAddU32(dst=sgpr("CLSm0Base"), src0=sgpr("CLSm0Base"), src1=cls_m0_step,
comment="CLS M0 step (src coef of CLS iter dim)"))
else:
module.addComment2(commentStr)
module.addComment1("calc coords, apply mask, and issue loads (if necessary)")
if self.kernel["BufferStore"] and (self.edge or (self.kernel["NumWaveSplitK"] > 1)):
bufferOOB = self.tmpVgpr + self.tmpVgprSize - 1
module.add(VMovB32(dst=vgpr(bufferOOB), src="BufferOOB"))
else:
bufferOOB = None
for elementIdx, element in enumerate(self.batchElements):
addrCalc: AddrCalculation = self.ss.elementAddr[elementIdx]
addrCVgpr = addrCalc.addrCVgpr
addrDVgpr = addrCalc.addrDVgpr
addrEVgpr = addrCalc.addrEVgpr
addrBiasVgpr = addrCalc.addrBiasVgpr
addrScaleAVecVgpr = addrCalc.addrScaleAVecVgpr
addrScaleBVecVgpr = addrCalc.addrScaleBVecVgpr
addrScaleAlphaVecVgpr = addrCalc.addrScaleAlphaVecVgpr
data = self.ss.elementData[elementIdx]
dataBeta = self.ss.elementData[elementIdx]
dataE = self.ss.elementDataE[elementIdx]
dataGate = self.ss.elementDataGate[elementIdx] if self.parentWriter.states.useGateResidual else 0
dataBias = self.ss.elementDataBias[elementIdx]
dataScaleAVec = self.ss.elementDataScaleAVec[elementIdx]
dataScaleBVec = self.ss.elementDataScaleBVec[elementIdx]
dataScaleAlphaVec = self.ss.elementDataScaleAlphaVec[elementIdx]
mask = self.ss.elementMask[elementIdx]
vc0 = element[3]
sumIdxGSUSYNC = self.ss.elementSumIdx[elementIdx]
# CompactLoopStore look-ahead override: this elt's delayed AFTER-primer
# encodes the NEXT EMITTING elt's rowInc (see _lookaheadRowInc).
_emitOverrideRows = self._lookaheadRowInc(elementIdx)
tmpInrSgpr = self._epilogScratchSgpr(1)
_skipCrossBatchAdv = (self.kernel["CompactLoopStore"] and elementIdx == 0 and self.batchIdx > 0)
module.add(addrCalc.emitAddressSetupCode(self.kernel, self.tPB, self.ss, self.tmpVgpr, tmpInrSgpr, self.edge, self.beta, self.atomic, elementIdx, addrDVgpr,
skipCrossBatchAdvance=_skipCrossBatchAdv))
self._epilogScratchFree(tmpInrSgpr)
if self.edge:
tmpInrSgpr = self._epilogScratchSgpr(2)
module.add(addrCalc.edgeProtectCode(self.kernel, self.edge, self.beta, self.atomic, mask, tmpInrSgpr))
self._epilogScratchFree(tmpInrSgpr)
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
module.addComment1("edge Protect")
# create code Module to push mov vgpr,acc instructions
if self.beta:
tmpInrSgpr = self._epilogScratchSgpr(1)
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'C', self.edge, self.beta, mask, bufferOOB, (elementIdx == 0), self.tmpVgpr, tmpInrSgpr, addrCVgpr, self.addrC, 0))
self._epilogScratchFree(tmpInrSgpr)
if dataBeta not in self.loadedDataBeta:
# In the UseSubtileImpl NonEdge path the workgroup-level edge check is relaxed
# (subtile-aligned remainder is allowed into NonEdge), so individual waves may
# own rows/columns beyond the valid output region. Gate each C load by writing
# SrdC+2 (num_records): BufferOOB → normal load, 0 → hardware returns zero.
#
# element loop is N(d1)-outer / M(d0)-inner.
# d1 (N) check: emitted once per d1 group — sets SrdC+2 = BufferOOB if N valid, else 0.
# d0 (M) check: emitted per element — overwrites SrdC+2 = SrdC+2 if M valid, else 0.
# (AND semantics: SrdC+2 = BufferOOB only when both M and N are valid.)
# d0 is monotone within each d1 group: once OOB, remaining d0s are also OOB.
mGuardSgpr = self.parentWriter.states.subtileM32ValidBlocksSgpr
nGuardSgpr = self.parentWriter.states.subtileN16ValidBlocksSgpr
if not self.edge and (mGuardSgpr is not None or nGuardSgpr is not None):
d1, d0 = element[0], element[1]
# N guard: emit once per d1 group.
if nGuardSgpr is not None and d1 != self._subtileCloadPrevD1:
d1Cmp = d1 * 16 if self.parentWriter.states.storeAlign8 else d1
module.add(_scmpGtU32(self.parentWriter, sgpr("SubtileNGuard"), d1Cmp,
comment="subtile C load: clamped > %d?" % d1Cmp))
module.add(SCSelectB32(dst=sgpr("SrdC+2"), src0="BufferOOB", src1=0,
comment="SrdC+2 = BufferOOB if N valid, else 0"))
self._subtileCloadPrevD1 = d1
# M guard: emit per element, AND into SrdC+2.
if mGuardSgpr is not None:
module.add(_scmpGtU32(self.parentWriter, sgpr("SubtileMGuard"), d0,
comment="subtile C load: numMBlocks > d0=%d?" % d0))
if nGuardSgpr is not None:
module.add(SCSelectB32(dst=sgpr("SrdC+2"), src0=sgpr("SrdC+2"), src1=0,
comment="SrdC+2 = SrdC+2 if M valid, else 0 (AND with N result)"))
else:
module.add(SCSelectB32(dst=sgpr("SrdC+2"), src0="BufferOOB", src1=0,
comment="SrdC+2 = BufferOOB if M valid, else 0"))
# Pass `elementIdx` so the readInput's incrementToNextRow gate can
# open the CompactLoopStore chain-seed emit for elt-0. Pass
# `overrideAfterPrimerRows=_emitOverrideRows` so the AFTER-primer
# encodes the NEXT EMITTING elt's rowInc (look-ahead). Non-CLS:
# `elementIdx` is unused and `_emitOverrideRows`==0 means no override.
if self.kernel["GroupLoadStore"]:
loadInputCode.add(self.parentWriter.readInput(self.kernel, self.ss, 'C', self.kernel["ProblemType"]["DestDataType"], addrCalc, vc0, data, self.gwvw, addrCVgpr, self.tmpS01, elementIdx, self.batchIdx,
overrideAfterPrimerRows=_emitOverrideRows))
else:
module.add(self.parentWriter.readInput(self.kernel, self.ss, 'C', self.kernel["ProblemType"]["DestDataType"], addrCalc, vc0, data, self.gwvw, addrCVgpr, self.tmpS01, elementIdx, self.batchIdx,
overrideAfterPrimerRows=_emitOverrideRows))
self.loadedDataBeta[dataBeta] = ceil(self.kernel["ProblemType"]["DestDataType"].numBytes() * self.ss.cfg.gwvw / 16)
self.loadsBetaIssued += ceil(self.kernel["ProblemType"]["DestDataType"].numBytes() * self.gwvw / 16)
self.betaLoadIssued.append(len(self.loadedDataBeta) * ceil(self.kernel["ProblemType"]["DestDataType"].numBytes() * self.ss.cfg.gwvw / 16))
if (self.kernel["ProblemType"]["UseE"] and self.kernel["ProblemType"]["Gradient"] and self.kernel["ProblemType"]["ActivationType"] != 'none') and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
tmpInrSgpr = self._epilogScratchSgpr(1)
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'E', self.edge, self.beta, mask, bufferOOB, (elementIdx == 0), self.tmpVgpr, tmpInrSgpr, addrEVgpr, self.addrE, 0))
self._epilogScratchFree(tmpInrSgpr)
if dataE not in self.loadedDataE:
loadOffset = int((self.kernel["ProblemType"]["ComputeDataType"].numRegisters() - self.kernel["ProblemType"]["DataTypeE"].numRegisters()) * self.ss.cfg.gwvw)
if self.kernel["GroupLoadStore"]:
loadInputCode.add(self.parentWriter.readInput(self.kernel, self.ss, 'E', self.kernel["ProblemType"]["DataTypeE"], addrCalc, vc0, dataE + loadOffset, self.gwvw, addrEVgpr, self.tmpS01))
else:
module.add(self.parentWriter.readInput(self.kernel, self.ss, 'E', self.kernel["ProblemType"]["DataTypeE"], addrCalc, vc0, dataE + loadOffset, self.gwvw, addrEVgpr, self.tmpS01))
self.loadedDataE[dataE] = ceil(self.kernel["ProblemType"]["DataTypeE"].numBytes() * self.ss.cfg.gwvw / 16)
self.loadsEIssued += ceil(self.kernel["ProblemType"]["DataTypeE"].numBytes() * self.gwvw / 16)
self.loadE = True
else:
self.loadE = False
self.eLoadIssued.append(len(self.loadedDataE) * ceil(self.kernel["ProblemType"]["DataTypeE"].numBytes() * self.ss.cfg.gwvw / 16))
# Per-element epilogue LDS loads (same helper the CLS preamble used for
# elt 0); if the preamble already loaded it, the ds_load is skipped here.
self._emitElt0EpilogueLoads(module, addrCalc, mask, elementIdx, bufferOOB,
loadInputCode, factor_gwvw,
False)
tmpInrSgpr = self._epilogScratchSgpr(1)
if (self.kernel["ProblemType"]["UseE"] and not self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'E', self.edge, self.beta, mask, bufferOOB, (elementIdx == len(self.batchElements) - 1), self.tmpVgpr, tmpInrSgpr, addrEVgpr, self.addrE, 0))
if self.storeBiasD == 1:
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'Bias', self.edge, self.beta, mask, bufferOOB, (elementIdx == len(self.batchElements) - 1), self.tmpVgpr, tmpInrSgpr, addrBiasVgpr, self.addrBias, self.factorDim))
if self.parentWriter.states.useGateResidual and \
(self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1 or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel"):
_gateList = self.kernel["ProblemType"].get("GateResidualDataTypeList", [])
_destDtype = self.kernel["ProblemType"]["DestDataType"]
if not _gateList:
_prologLoadDtype = self.kernel["ProblemType"]["DestDataType"]
elif len(_gateList) == 1:
_prologLoadDtype = _gateList[0]
else:
_prologLoadDtype = _destDtype if _destDtype in _gateList else _gateList[0]
_multiDtypeGate = (_gateList and len(_gateList) > 1)
addrGateVgpr = addrCalc.addrGateVgpr if addrCalc.addrGateVgpr != None else addrDVgpr
if not _multiDtypeGate and not self.ss.optSingleColVgpr:
module.add(addrCalc.emitLdChange(
self.kernel, self.ss, 'Gate', self.edge, self.beta, mask, bufferOOB,
(elementIdx == 0), self.tmpVgpr, tmpInrSgpr, addrGateVgpr, self.addrD, 0))
if dataGate not in self.loadedDataGate:
if self.ss.optSingleColVgpr:
# opt (single + multi dtype): hoisted to ONE branch after the prolog loop
# (see _emitHoistedGateLoadPhase).
pass
elif not _multiDtypeGate:
# no-opt single-dtype: unconditional load.
# Null gate -> SRD num_records==0 -> buffer_load returns 0;
_glTgt = loadInputCode if self.kernel["GroupLoadStore"] else module
gateLoadMod = self.parentWriter.readInput(
self.kernel, self.ss, 'Gate',
_prologLoadDtype,
addrCalc, vc0, dataGate, self.gwvw, addrGateVgpr, self.tmpS01)
_glTgt.add(gateLoadMod)
else:
# no-opt (edge) multi-dtype: per-dtype dispatcher per element (gate
# borrows D's per-element addr, so it must stay interleaved here).
# all using this element's fresh mask. The dispatcher tail-branches
# to a common "GateLoad_End" label per element.
labels = self.parentWriter.labels
gateLoadEndLabel = Label(
labels.getNameInc("GateLoad_End_%u"%elementIdx), "")
gateLoadTypeLabels = [
Label(labels.getNameInc(
"GateLoad_%s_%u"%(g.toNameAbbrev(), elementIdx)), "")
for g in _gateList]
gateLoadTypeLabels.append(gateLoadEndLabel)
# Snapshot mutables we'll restore after the dispatcher chain.
_savedBpeGate = self.parentWriter.states.bpeGate
_savedGlobalOffsetGate = addrCalc.globalOffsetGate
# Per-element skip when gate pointer is null (no-gate problem): jump past
# the whole dtype dispatcher straight to GateLoad_End for this element.
module.add(self.parentWriter.getSCMPKInstruction(
"EQU32", "SrdGate+2", 0, comment="gate disabled? (SrdGate num_records==0)"))
module.add(SCBranchSCC1(gateLoadEndLabel.getLabelName(), "skip gate load if disabled (null gate)"))
for i, nextLabel in enumerate(gateLoadTypeLabels[1:]):
typeValue = _gateList[i].value
gDtype = _gateList[i]
module.add(gateLoadTypeLabels[i])
module.add(self.parentWriter.getSCMPKInstruction(
"LGU32", "GateType", typeValue,
comment="GateType != %u"%typeValue))
module.add(SCBranchSCC1(nextLabel.getLabelName(),
"Branch if true (try next gate dtype)"))
# per-dtype bpe. no-opt path only (opt is handled by the hoisted phase),
# so gate uses per-element runtime addr -> globalOffsetGate fixed 0.
perBranchBpe = int(self.parentWriter.states.bpr * gDtype.numRegisters())
perBranchBpe = max(1, perBranchBpe)
self.parentWriter.states.bpeGate = perBranchBpe
addrCalc.globalOffsetGate = 0
# Force fresh per-dtype gate address setup.
self.ss.singleColGateAddrUpdated = False
module.add(addrCalc.emitLdChange(
self.kernel, self.ss, 'Gate', self.edge, self.beta, mask, bufferOOB,
(elementIdx == 0), self.tmpVgpr, tmpInrSgpr, addrGateVgpr, self.addrD, 0))
module.add(self.parentWriter.readInput(
self.kernel, self.ss, 'Gate', gDtype,
addrCalc, vc0, dataGate, self.gwvw, addrGateVgpr, self.tmpS01))
# Restore bpe/offset for the next branch in this elem.
self.parentWriter.states.bpeGate = _savedBpeGate
addrCalc.globalOffsetGate = _savedGlobalOffsetGate
module.add(SBranch(labelName=gateLoadEndLabel.getLabelName(),
comment="Branch to GateLoad end"))
module.add(gateLoadEndLabel)
gateBytes = ceil(_prologLoadDtype.numBytes() * self.ss.cfg.gwvw / 16)
self.loadedDataGate[dataGate] = gateBytes
self.loadsGateIssued += gateBytes
self.gateLoadIssued.append(len(self.loadedDataGate) * ceil(_prologLoadDtype.numBytes() * self.ss.cfg.gwvw / 16))
else:
self.gateLoadIssued.append(0)
if self.kernel["GlobalSplitU"] == 1 or (self.kernel["_GlobalAccumulation"] != "MultipleBufferSingleKernel"): # "SingleBuffer" or "MultipleBuffer"
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'D', self.edge, self.beta, mask, bufferOOB, (elementIdx == len(self.batchElements) - 1), self.tmpVgpr, tmpInrSgpr, addrDVgpr, self.addrD, 0))
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
module.add(addrCalc.emitLdChange(self.kernel, self.ss, 'TD', self.edge, self.beta, mask, bufferOOB, (elementIdx == len(self.batchElements) - 1), self.tmpVgpr, tmpInrSgpr, addrCalc.addrGSUSyncVgprs, self.addrD, 0))
self._epilogScratchFree(tmpInrSgpr)
if self.atomic and (not self.parentWriter.states.useAtomicAdd):
# load c into data+1 because of CAS structure
# TODO - Fix for double here, would need bigger load
# FIXME
# gwvw is the number of elements in the batch
# iterate over number of atomic operations to perform, each of width atomicW
for avi in range(self.gwvw // self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi*self.ss.cfg.numVgprsPerDataPerVI)
bpm = self.parentWriter.states.bpeCexternal * self.atomicW
useBuffer = self.kernel["BufferStore"]
if self.kernel["BufferStore"]: # yes, BufferStore here - use same addressing regs for this load
addr0 = vgpr(addrDVgpr)
addr1 = sgpr("SrdD", 4)
else:
addr0 = vgpr(addrDVgpr, 2)
addr1 = ""
# Calculate vgpr Index for 32-bit/64-bit instruction
# DGEMM use SRCS[2] register
vgprIdx = bpm // 4
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bpm, dataV + vgprIdx, \
addr0, addr1, soffset=0, offset=addrCalc.globalOffset,
comment="load D (atomic) bpm=%u vaw=%u"%(bpm,self.atomicW)))
if self.kernel["InterleaveAlpha"] and self.applyAlpha:
module.add(self._applyAlpha(self.kernel, self.gwvw, self.ss.elementSumIdx, elementIdx, self.tmpS01))
if not self.kernel["BufferStore"]:
# emitLdChange for 'D' (above) already computed addrDVgpr = addrD + offset
# Only need to build the address here if emitLdChange for 'D' was NOT called
if not (self.kernel["GlobalSplitU"] == 1 or (self.kernel["GlobalSplitUAlgorithm"] != "MultipleBufferSingleKernel")):
offsetSrc = (self.tmpVgpr + 2) if self.beta else addrDVgpr
module.add(VAddCOU32(vgpr(addrDVgpr+0), VCC(), vgpr(self.addrD+0), \
vgpr(offsetSrc+0), "addrDVgpr = D + index*bytes (lo)"))
module.add(VAddCCOU32(vgpr(addrDVgpr+1), VCC(), vgpr(self.addrD+1), \
vgpr(offsetSrc+1), VCC(), "addrDVgpr = D + index*bytes (hi)"))
# restore full exec mask for calculating addr of next element
if self.edge and (self.beta or self.loadE or self.atomic):
module.add(self.getEdgeMovInstType()(EXEC(), -1, "full mask -1 -> exec"))
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
if self.ss.optSrdIncForRow and (addrCalc.rowInc or (self.kernel["CompactLoopStore"] and elementIdx == 0 and self.batchIdx == 0)) and self.kernel["StoreRemapVectorWidth"] > 0:
module.addComment1("StoreRemap: shift coord1 address MultipleBufferSingleKernel")
if self.kernel["ProblemType"]["UseE"] and (self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1):
# TODO Check if works with StreamK
printExit("Use E does not support StoreRemapVectorWidth if GSU == 1.")
# module.add(addrCalc.incrementToNextRow(self.kernel, "E", self.ss, self.tmpS01, isCompute=True))
module.add(addrCalc.incrementToNextRow(self.kernel, "D", self.ss, self.tmpS01, forceinitrow0=1, overrideAfterPrimerRows=_emitOverrideRows))
module.add(VMovB32(vgpr(self.tmpVgpr), addrCalc.rowInc, comment="set shift rows"))
module.add(VAddU32(vgpr(self.parentWriter.vgprs.storeRemapCoord1), vgpr(self.parentWriter.vgprs.storeRemapCoord1), vgpr(self.tmpVgpr), "shift storeRemap coord1"))
module.add(loadInputCode)
# opt + multi-dtype gate: emit the gate prolog load as ONE GateType dispatch
# (the per-element loop above skipped it on the opt path).
self._emitHoistedGateLoadPhase(module, bufferOOB)
# Restore SrdC+2 = BufferOOB after subtile NonEdge C-load OOB gating (which may have set it to 0).
if self.beta and not self.edge:
mGuardSgpr = self.parentWriter.states.subtileM32ValidBlocksSgpr
nGuardSgpr = self.parentWriter.states.subtileN16ValidBlocksSgpr
if mGuardSgpr is not None or nGuardSgpr is not None:
module.add(SMovB32(dst=sgpr("SrdC+2"), src="BufferOOB",
comment="restore SrdC+2 after subtile NonEdge C-load OOB gating"))
if self.beta and self.kernel["StoreSyncOpt"]:
self._storeSyncOpt(module)
########################################
# AccVgpr read
if self.codeAccVgprRead is not None and (self.kernel["LocalSplitU"] == 1 or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel"):
regsPerScalar = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr # register per scalar
#TODOBS: Need to change this, for LSU>1 + subtile impl case
if self.kernel["MIArchVgpr"] and self.kernel["LocalSplitU"] > 1:
tmpStartVgprValuC = self.parentWriter.states.c.startVgprValu
self.parentWriter.states.c.startVgprValu = 0
module.add(RegSet("v", "vgprValuC", 0))
# loop over store instructions within one batch
for elementIdx in range(len(self.batchElements)):
# loop over scalars within one store instruction
for vi in range(self.gwvw):
# loop over registers within one scalar
for rIdx in range(0, regsPerScalar):
module.add(replaceHolder(self.codeAccVgprRead.popFirstItem(), self.ss.elementSumIdx[elementIdx]*regsPerScalar + regsPerScalar*vi + rIdx - self.parentWriter.states.c.startVgprValu))
if self.kernel["MIArchVgpr"] and self.kernel["LocalSplitU"] > 1:
self.parentWriter.states.c.startVgprValu = tmpStartVgprValuC
module.add(RegSet("v", "vgprValuC", tmpStartVgprValuC))
elif self.kernel["LocalSplitU"] > 1:
# read from LSU VGPRs
regsPerScalar = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr # register per scalar
if self.ss.lsuStartVgprOffset > 0:
for elementIdx in range(len(self.batchElements)):
for vi in range(self.gwvw):
for rIdx in range(0, regsPerScalar):
idx = self.ss.elementSumIdx[elementIdx]*regsPerScalar + regsPerScalar*vi + rIdx - self.parentWriter.states.c.startVgprValu
module.add(VMovB32(vgpr("ValuC+%u"%(idx)), vgpr("ValuC+%u"%(idx + self.ss.lsuStartVgprOffset)), comment="load from "+str(idx + self.ss.lsuStartVgprOffset)+" to "+str(idx) ))
self.ss.lsuStartVgprOffset += len(self.batchElements) * self.gwvw * regsPerScalar
if not self.kernel["MIArchVgpr"]:
module.add(SNop(1, "2 wait states required before reading vgpr"))
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
module.addComment1("store after Acc, "+"GSU: "+str(self.kernel["GlobalSplitU"]))
storeCodeGSUSK = Module("GroupLoadStore")
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":#GSUGSU
storeWidth = self.kernel["StoreVectorWidth"]
for elementIdx in range(0, len(self.batchElements)):
addrCalc: AddrCalculation = self.ss.elementAddr[elementIdx]
if self.batchIdx == 0 and elementIdx == 0:
addrDVgpr = addrCalc.addrDVgpr
storeCodeGSUSK.add(vectorStaticMultiply(vgpr(addrDVgpr), vgpr("Serial"), storeWidth * self.parentWriter.states.bpeCinternal, ContinuousRegister(self.tmpS01, 1)))
storeCodeGSUSK.add(SMovB32(dst=sgpr(self.tmpS01), src=0, comment="Init sgpr offset"))
storeCodeGSUSK.addSpaceLine()
if (self.kernel["ProblemType"]["UseE"] and not self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
vgprIdx = self.ss.elementSumIdx[elementIdx] - self.parentWriter.states.c.startVgprValu
vgprDst = self.activationSetPCStruct.vgprActCopy if mergeActFuncCall else "ValuC+%d"%vgprIdx
module.add(self.parentWriter.addStore(self.kernel, self.ss, 'E', addrCalc, vgprDst, self.tmpS01, self.edge, comment="store E"))
sumIdx = self.ss.elementSumIdx[elementIdx]
if self.kernel["StoreRemapVectorWidth"]:
rpe = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr
module.add(self.parentWriter.storeRemapAddLocalWrite(self.kernel, self.ss, addrCalc, sumIdx*rpe))
# Column Block Shape has been written to LDS
# Now read back and write out to global memory
# module.add(storeCodeGSUSK)
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel" and self.kernel["StoreRemapVectorWidth"]:
if self.parentWriter.StoreRemapLastBatch == 1:
module.addComment1("Handle local read and global write")
tmpInrSgpr = self._epilogScratchSgpr(2)
storeModule, numNewStores = self.parentWriter.storeRemapAddStore(self.kernel, self.tmpVgpr, tmpInrSgpr, self.edge, self.parentWriter.StoreRemapLastBatch)
self._epilogScratchFree(tmpInrSgpr)
module.add(storeModule)
self.storesIssued += numNewStores
gsuComponent = Component.GSU.find(self.parentWriter)
module.add(gsuComponent.globalWriteBatchProlog(self.parentWriter, self.kernel, self.tmpVgpr, self.tmpVgprSize, self.tmpVgprDynamic, \
self.batchIdx, self.ss, self.gwvw, self.batchElements, \
self.beta, self.edge, sumIdxGSUSYNC, addrCalc))
# rC *= alpha
if not self.kernel["InterleaveAlpha"] and self.applyAlpha and not self.parentWriter.alphaBeforeLoadC:
module.addComment1("rC *= alpha batchElements=%s"%self.batchElements)
if self.codeMulAlpha is None:
elementIdx = 0
while elementIdx < len(self.batchElements):
isEnd = (elementIdx == len(self.batchElements) - 1)
if not isEnd and (self.ss.elementSumIdx[elementIdx] + 1 == self.ss.elementSumIdx[elementIdx + 1]) and (self.ss.elementSumIdx[elementIdx] % 2 == 0):
module.add(self._applyAlpha(self.kernel, self.gwvw, self.ss.elementSumIdx, elementIdx, self.tmpS01, usePK=True))
elementIdx += 2
else:
module.add(self._applyAlpha(self.kernel, self.gwvw, self.ss.elementSumIdx, elementIdx, self.tmpS01))
elementIdx += 1
else:
regsPerScalar = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr # register per scalar
for elementIdx in range(len(self.batchElements)):
for vi in range(self.gwvw):
rh = replaceHolder(self.codeMulAlpha.popFirstItem(), self.ss.elementSumIdx[elementIdx]*regsPerScalar + regsPerScalar*vi - self.parentWriter.states.c.startVgprValu)
if (self.kernel["GlobalSplitU"] == 1) and (self.kernel["ProblemType"]["ComputeDataType"].isSingle() and self.kernel["ProblemType"]["DataType"].isInt8()):
srcRegName = rh.getParams()[2].getCompleteRegName()
module.add(VCvtI32toF32(dst=vgpr(srcRegName), src=vgpr(srcRegName), comment="Convert MI out reg to fp32"))
module.add(rh)
def _epilog(self, module: Module):
# return registers to pool:
lastDataD = -1
lastDataE = -1
checkedDataBias = {}
checkedDataGate = {}
checkedDataScaleAVec = {}
checkedDataScaleBVec = {}
checkedDataScaleAlphaVec = {}
for elementIdx in range(len(self.batchElements)):
sumIdxGSUSYNC = self.ss.elementSumIdx[elementIdx]
if not self.ss.sharedColDVgprs:
addrCalc: AddrCalculation = self.ss.elementAddr[elementIdx]
addrEVgpr = addrCalc.addrEVgpr
addrDVgpr = addrCalc.addrDVgpr
addrGSUSyncVgprs = addrCalc.addrGSUSyncVgprs
addrCVgpr = addrCalc.addrCVgpr
addrBiasVgpr = addrCalc.addrBiasVgpr
addrGateVgpr = addrCalc.addrGateVgpr
addrScaleAVecVgpr = addrCalc.addrScaleAVecVgpr
addrScaleBVecVgpr = addrCalc.addrScaleBVecVgpr
addrScaleAlphaVecVgpr = addrCalc.addrScaleAlphaVecVgpr
if addrEVgpr != None:
self.parentWriter.vgprPool.checkIn(addrEVgpr)
self.parentWriter.vgprPool.checkIn(addrDVgpr)
if addrCVgpr != addrDVgpr:
self.parentWriter.vgprPool.checkIn(addrCVgpr)
if addrGSUSyncVgprs != None:
self.parentWriter.vgprPool.checkIn(addrGSUSyncVgprs)
if addrBiasVgpr != None:
self.parentWriter.vgprPool.checkIn(addrBiasVgpr)
# No-opt path: addrGateVgpr aliases addrDVgpr
if addrGateVgpr != None and addrGateVgpr != addrDVgpr:
self.parentWriter.vgprPool.checkIn(addrGateVgpr)
if addrScaleAVecVgpr != None:
self.parentWriter.vgprPool.checkIn(addrScaleAVecVgpr)
if addrScaleBVecVgpr != None:
self.parentWriter.vgprPool.checkIn(addrScaleBVecVgpr)
if addrScaleAlphaVecVgpr != None:
self.parentWriter.vgprPool.checkIn(addrScaleAlphaVecVgpr)
data = self.ss.elementData[elementIdx]
if data != 0:
if data != lastDataD:
self.parentWriter.vgprPool.checkIn(data)
lastDataD = data
dataBias = self.ss.elementDataBias[elementIdx]
if dataBias != 0:
if dataBias not in checkedDataBias:
self.parentWriter.vgprPool.checkIn(dataBias)
checkedDataBias[dataBias] = 1
dataE = self.ss.elementDataE[elementIdx]
if dataE != 0:
if dataE != lastDataE:
self.parentWriter.vgprPool.checkIn(dataE)
lastDataE = dataE
if self.parentWriter.states.useGateResidual:
dataGate = self.ss.elementDataGate[elementIdx]
if dataGate != 0:
if dataGate not in checkedDataGate:
self.parentWriter.vgprPool.checkIn(dataGate)
checkedDataGate[dataGate] = 1
def checkScaleVec(dataScaleVec, checkedDataScaleVec):
if dataScaleVec != 0:
if dataScaleVec not in checkedDataScaleVec:
self.parentWriter.vgprPool.checkIn(dataScaleVec)
checkedDataScaleVec[dataScaleVec] = 1
checkScaleVec(self.ss.elementDataScaleAVec[elementIdx], checkedDataScaleAVec)
checkScaleVec(self.ss.elementDataScaleBVec[elementIdx], checkedDataScaleBVec)
checkScaleVec(self.ss.elementDataScaleAlphaVec[elementIdx], checkedDataScaleAlphaVec)
self.ss.firstBatch = False
self.ss.checkInTempVgprC()
if self.kernel["_GlobalAccumulation"] != "MultipleBufferSingleKernel" and self.kernel["StoreRemapVectorWidth"]:
if self.parentWriter.StoreRemapLastBatch == 1:
module.addComment1("Handle local read and global write")
# this seems buggy? it's possible to issue more than one stores for SR
# module.add(self.storeRemapAddStore(kernel, tmpVgpr, tmpS01, edge))
# storesIssued += 1
tmpInrSgpr = self._epilogScratchSgpr(2)
storeModule, numNewStores = self.parentWriter.storeRemapAddStore(self.kernel, self.tmpVgpr, tmpInrSgpr, self.edge, self.parentWriter.StoreRemapLastBatch)
self._epilogScratchFree(tmpInrSgpr)
module.add(storeModule)
self.storesIssued += numNewStores
if self.parentWriter.states.serializedStore:
module.add(SNop(0, "1 wait state required when next inst writes vgprs held by previous dwordx4 store inst"))
if self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":
module.addCommentAlign("GW end") #GSUSYNC
def _emitAdd(self, module: Module):
if self.atomic:
del self.tmpVgpr # catch bugs
if self.parentWriter.states.useAtomicAdd:
self._emitAtomicAdd(module)
else:
self._emitCasAdd(module)
else:
self._emitNonatomicAdd(module)
def _emitGateCvt(self, dataGate, gateDtype):
cvtMod = Module("GateCvt_%s" % gateDtype.toNameAbbrev())
if gateDtype.isSingle():
pass
elif gateDtype.isHalf():
for vi in range(self.gwvw - 1, -1, -1):
sel = HighBitSel.LOW if (vi % 2) == 0 else HighBitSel.HIGH
cvtMod.add(ECvtF16toF32(dst=vgpr(dataGate + vi), src=vgpr(dataGate + vi // 2), sel=sel,
comment="GateCvt[h]: f16 -> f32 (vi=%d)"%vi))
elif gateDtype.isBFloat16():
# bf16 = top 16 bits of an f32; lo half shifts up, hi half masks.
for vi in range(self.gwvw - 1, -1, -1):
if (vi % 2) == 0:
cvtMod.add(VLShiftLeftB32(dst=vgpr(dataGate + vi), shiftHex=16, src=vgpr(dataGate + vi // 2),
comment="GateCvt[bf16]: lo bf16 -> f32 (vi=%d)"%vi))
else:
cvtMod.add(VAndB32(dst=vgpr(dataGate + vi), src0=hex(0xffff0000), src1=vgpr(dataGate + vi // 2),
comment="GateCvt[bf16]: hi bf16 -> f32 (vi=%d)"%vi))
elif gateDtype.isAnyFloat8() or gateDtype.isAnyBFloat8():
# PK-cvt 2 elements per instruction into a tmp, then move to dataGate.
isFP8 = gateDtype.isAnyFloat8()
CvtPk = ECvtPkFP8toF32 if isFP8 else ECvtPkBF8toF32
cvtTmp = self.tmpVgpr
for vi in range(((self.gwvw - 1) // 2) * 2, -1, -2):
sel = HighBitSel.LOW if (vi % 4) == 0 else HighBitSel.HIGH
cvtMod.add(CvtPk(dst=vgpr(cvtTmp, 2), src=vgpr(dataGate + vi // 4), sel=sel,
comment="GateCvt[%s]: 2x8bit -> 2xf32 (vi=%d)"%(gateDtype.toNameAbbrev(), vi)))
for j in range(2):
if (vi + j) < self.gwvw:
cvtMod.add(VMovB32(dst=vgpr(dataGate + vi + j), src=vgpr(cvtTmp + j),
comment="GateCvt[%s]: store f32 (vi=%d)"%(gateDtype.toNameAbbrev(), vi + j)))
elif gateDtype.isInt8():
# int8 = 4 signed bytes/dword; extract+sign-extend byte (vi%4) to i32, then -> f32.
for vi in range(self.gwvw - 1, -1, -1):
dst = dataGate + vi
if (vi % 4) != 3:
cvtMod.add(VMovB32(dst=vgpr(self.tmpVgpr), src=hex((vi % 4) * 8), comment="byte offset"))
cvtMod.add(VBfeI32(dst=vgpr(dst), src0=vgpr(dataGate + vi // 4), src1=vgpr(self.tmpVgpr), src2=8,
comment="GateCvt[i8]: int8 -> int32 (vi=%d)"%vi))
else:
cvtMod.add(VAShiftRightI32(dst=vgpr(dst), shiftHex=24, src=vgpr(dataGate + vi // 4),
comment="GateCvt[i8]: int8(byte3) -> int32 (vi=%d)"%vi))
cvtMod.add(VCvtI32toF32(dst=vgpr(dst), src=vgpr(dst), comment="GateCvt[i8]: int32 -> f32 (vi=%d)"%vi))
elif gateDtype.isInt32():
# int32 = 1 value/dword; cvt in place to f32.
for vi in range(self.gwvw - 1, -1, -1):
cvtMod.add(VCvtI32toF32(dst=vgpr(dataGate + vi), src=vgpr(dataGate + vi),
comment="GateCvt[i32]: int32 -> f32 (vi=%d)"%vi))
else:
raise RuntimeError(
"GateResidual: unsupported gate dtype %s" % str(gateDtype))
return cvtMod
def _emitHoistedGateLoadPhase(self, module: Module, bufferOOB):
"""opt path: emit the gate prolog load wrapped in ONE null-gate skip branch."""
if not (self.parentWriter.states.useGateResidual and
(self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1 or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel")):
return
if not self.ss.optSingleColVgpr:
return # no-opt path keeps the per-element load in the prolog loop
gateList = list(self.kernel["ProblemType"].get("GateResidualDataTypeList", []))
if not gateList:
return
multi = len(gateList) > 1
labels = self.parentWriter.labels
endLabel = Label(labels.getNameInc("GateLoadAll_End"), "")
savedBpe = self.parentWriter.states.bpeGate
def _emitLoadsFor(gDtype):
bpe = max(1, int(self.parentWriter.states.bpr * gDtype.numRegisters()))
self.parentWriter.states.bpeGate = bpe
self.ss.singleColGateAddrUpdated = False
seen = set()
for ei, element in enumerate(self.batchElements):
addrCalc = self.ss.elementAddr[ei]
dataGate = self.ss.elementDataGate[ei]
if dataGate == 0 or dataGate in seen:
continue
seen.add(dataGate)
addrGateVgpr = addrCalc.addrGateVgpr # opt: gate's own sharedColGateVgprs
addrCalc.globalOffsetGate = addrCalc.coordOffset0 * bpe
module.add(addrCalc.emitLdChange(
self.kernel, self.ss, 'Gate', self.edge, self.beta, self.ss.elementMask[ei],
bufferOOB, (ei == 0), self.tmpVgpr, self.tmpSgpr, addrGateVgpr, self.addrD, 0))
module.add(self.parentWriter.readInput(
self.kernel, self.ss, 'Gate', gDtype, addrCalc, element[3], dataGate,
self.gwvw, addrGateVgpr, self.tmpS01))
if not multi:
# single-dtype: no GateType dispatch needed, just the loads.
_emitLoadsFor(gateList[0])
else:
typeLabels = [Label(labels.getNameInc("GateLoadAll_%s"%g.toNameAbbrev()), "") for g in gateList]
typeLabels.append(endLabel)
for i, nextLabel in enumerate(typeLabels[1:]):
gDtype = gateList[i]
module.add(typeLabels[i])
module.add(self.parentWriter.getSCMPKInstruction(
"LGU32", "GateType", gDtype.value, comment="GateType != %u"%gDtype.value))
module.add(SCBranchSCC1(nextLabel.getLabelName(), "Branch if true (try next gate dtype)"))
_emitLoadsFor(gDtype)
module.add(SBranch(labelName=endLabel.getLabelName(), comment="Branch to GateLoadAll end"))
module.add(endLabel)
self.parentWriter.states.bpeGate = savedBpe
def _emitGateCvtAllPhase(self, module: Module):
"""Multi-dtype Gate: convert ALL loaded gate values to f32 in one GateType
dispatch."""
if not (self.parentWriter.states.useGateResidual and
(self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1 or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel")):
return
gateList = list(self.kernel["ProblemType"].get("GateResidualDataTypeList", []))
if len(gateList) <= 1:
if self.ss.optSingleColVgpr:
module.add(SWaitCnt(vlcnt=0, comment="GateResidual: wait for hoisted single-dtype gate loads"))
return
labels = self.parentWriter.labels
cvtEndLabel = Label(labels.getNameInc("GateCvtAll_End"), "")
cvtTypeLabels = [Label(labels.getNameInc("GateCvtAll_%s"%g.toNameAbbrev()), "") for g in gateList]
cvtTypeLabels.append(cvtEndLabel)
# Skip the whole gate cvt phase when the gate pointer is null (no-gate problem).
module.add(self.parentWriter.getSCMPKInstruction(
"EQU32", "SrdGate+2", 0, comment="gate disabled? (SrdGate num_records==0)"))
module.add(SCBranchSCC1(cvtEndLabel.getLabelName(), "skip gate cvt if disabled (null gate)"))
# One wait for all prolog gate loads (multi-dtype already paid a blunt wait
# per element previously; this hoists it to a single wait).
module.add(SWaitCnt(vlcnt=0, comment="GateResidual: wait for prolog gate loads (branch-once cvt)"))
for i, nextLabel in enumerate(cvtTypeLabels[1:]):
gDtype = gateList[i]
module.add(cvtTypeLabels[i])
module.add(self.parentWriter.getSCMPKInstruction(
"LGU32", "GateType", gDtype.value, comment="GateType != %u"%gDtype.value))
module.add(SCBranchSCC1(nextLabel.getLabelName(), "Branch if true (try next gate dtype)"))
seen = set()
for ei in range(len(self.batchElements)):
dg = self.ss.elementDataGate[ei]
if dg != 0 and dg not in seen:
seen.add(dg)
module.add(self._emitGateCvt(dg, gDtype))
module.add(SBranch(labelName=cvtEndLabel.getLabelName(), comment="Branch to GateCvtAll end"))
module.add(cvtEndLabel)
def _emitNonatomicAdd(self, module: Module):
########################################
# Not Atomic
########################################
# edge has v_cndmask so loads or stores may not issue, hard to track vmcnt:
interleaveStoreVmcnt = self.parentWriter.states.interleaveStoreVmcnt and not self.edge
for elementIdx in range(len(self.batchElements)):
for vi in range(self.gwvw):
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
# covers sgemm, gemm_ex(HHS/HSS/BBS/BSS (HPA=T)), int8 (int8x4?)
if self.kernel["ProblemType"]["ComputeDataType"].isInt32() or \
self.kernel["ProblemType"]["ComputeDataType"].isSingle(): # covers sgemm/gemm_ex(HHS/HSS/BBS/BSS)
if self.debugConfig["ForceExpectedValue"]:
module.add(VMovB32(vgpr("ValuC+%u"%newSumIdxV), self.debugConfig["ValueCExpectedValue"], "force expected value" ))
if self.parentWriter.db["ForceVSerial"]:
module.add(VMovB32(vgpr("ValuC+%u"%newSumIdxV), vgpr("Serial"), "force expected value to serial" ))
if self.parentWriter.db["CheckValueC"]:
module.add(SMovB32(sgpr(self.tmpS01), self.debugConfig["ValueCExpectedValue"], "Move expected value"))
module.add(self.parentWriter.getCmpAssert(self.parentWriter.asmAssert.eq, vgpr("ValuC+%u"%newSumIdxV), sgpr(self.tmpS01)))
########################################
# wait for batched load
# Here we wait all
if not interleaveStoreVmcnt:
waitcntInst = self.globalStoreWait(0, [], 0, 0, False)
if waitcntInst:
module.add(waitcntInst)
if self.kernel["ProblemType"]["StochasticRounding"]:
if self.parentWriter.states.asmCaps["v_prng_b32"]:
vgprRND = self.parentWriter.vgprPool.checkOut(1, tag="_emitNonatomicAdd_vgprRND")
else:
# legacy PRNG approach needs extra 2 VGPRs
# Ref.: Module("StochasticRoundingCvt")
vgprRND = self.parentWriter.vgprPool.checkOut(3, tag="_emitNonatomicAdd_vgprRND2")
# Multi-dtype Gate: branch-once cvt of all loaded gate values to f32.
self._emitGateCvtAllPhase(module)
module.addComment1("apply mask, calc new C and issue writes")
# module.add(self.getBomb()) # can see store addresses just before the store inst
activationCDataType = self.kernel["ProblemType"]["ActivationComputeDataType"]
if self.kernel["_GlobalAccumulation"] != 'MultipleBuffer':
if self.kernel["ProblemType"]["DestDataType"].isBFloat16() and self.kernel["ProblemType"]["HighPrecisionAccumulate"]:
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprBf16Mask), "0xffff0000", comment="mask for pack two bfloat16 element to 32bit" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp32Nan), "0x7fff0000", comment="fp32 Nan" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprBf16Inc), "0x7fff", comment="rounding bias for bfloat16" ))
# is16bitSubtile: controls partner-lane address setup for dwordx4 paired-subtile stores.
# Must match is16bitSubtilePaired (per-element store dispatch) exactly.
# Excluded for "MultipleBufferSingleKernel" and "MultipleBuffer" (StreamK partial-tile
# workspace path) — both write float32 to workspace, not 16bit to D output.
isSubtileNonEdge = (
self.kernel.get("UseSubtileImpl") and not self.edge
and self.kernel["_GlobalAccumulation"] not in ("MultipleBufferSingleKernel", "MultipleBuffer")
)
is16bitSubtile = (
isSubtileNonEdge
and (self.kernel["ProblemType"]["DestDataType"].isBFloat16() or
self.kernel["ProblemType"]["DestDataType"].isHalf())
and self.kernel["ProblemType"]["HighPrecisionAccumulate"]
and self.kernel["WavefrontSize"] != 32 # wave32: skip permute-based packed store (uses wave64-only ops)
)
if is16bitSubtile:
assert self.kernel["BufferStore"], \
"UseSubtileImpl 16bit optimized store requires BufferStore=1"
# Compute ds_permute partner-lane address for 16bit dwordx4 paired-subtile stores.
# vtmp1 = lane_id, vtmp2 = partner_lane_id (for ds_permute forward scatter)
# After v_permlane32_swap + v_permlane16_swap + exec masking:
# each lane ends up with the lane_id of the partner that will scatter data to it.
# vPermAddr = partner_lane_id * 4 (byte address for ds_permute_b32)
vPermAddr = self.cvtVgprStruct.vgprPermAddr
vTmp = self.cvtVgprStruct.vgprBf16Temp # reuse scratch temp before it's used for mask init
module.addComment1("16bit dwordx4 UseSubtileImpl: compute ds_permute partner-lane address")
module.add(VAndB32(dst=vgpr(vTmp), src0=self.kernel["WavefrontSize"]-1, src1=vgpr("Serial"), comment="lane_id & (WS-1)"))
module.add(VAndB32(dst=vgpr(vPermAddr), src0=self.kernel["WavefrontSize"]-1, src1=vgpr("Serial"), comment="copy of lane_id"))
module.add(VPermlane32SwapB32(dst=vgpr(vTmp), src=vgpr(vTmp), comment="lane XOR 32 swap"))
module.add(SNop(waitState=0, comment="delay after v_permlane32_swap"))
module.add(VPermlane16SwapB32(dst=vgpr(vTmp), src=vgpr(vTmp), comment="lane XOR 16 swap"))
# Exec mask: lanes where both XOR swaps changed the value (i.e., the 'first' half of each pair)
# selects lanes 0-15 and 32-47 within the wave.
stmp = self.parentWriter.sgprPool.checkOutAligned(2,2, tag="_emitNonatomicAdd_stmp")
module.add(SMovB32(dst=sgpr(stmp), src="0x0000ffff", comment="select lanes 0-15, 32-47"))
module.add(SMovB32(dst=sgpr(stmp+1), src="0xffff0000"))
module.add(VCndMaskB32(dst=vgpr(vTmp), src0=vgpr(vTmp), src1=vgpr(vPermAddr), src2=sgpr(stmp,2), comment="restore original lane_id for selected lanes"))
self.parentWriter.sgprPool.checkIn(stmp)
module.add(VLShiftLeftB32(dst=vgpr(vPermAddr), shiftHex=2, src=vgpr(vTmp), comment="partner_lane * 4 = ds_permute byte addr"))
# Pre-compute lane_group*8 once; reused as the row-byte address correction in every
# paired dwordx4 store (addrDVgpr encodes lane_group*8 but we need lane_group*16).
vLGDelta = self.cvtVgprStruct.vgprLaneGroupDelta
module.addComment1("16bit dwordx4: pre-compute lane_group*8 row-byte correction")
module.add(VAndB32(dst=vgpr(vLGDelta), src0=self.kernel["WavefrontSize"]-1, src1=vgpr("Serial"),
comment="lane_id = Serial & (WS-1)"))
module.add(VLShiftRightB32(dst=vgpr(vLGDelta), shiftHex=4, src=vgpr(vLGDelta),
comment="lane_group = lane_id >> 4"))
module.add(VLShiftLeftB32(dst=vgpr(vLGDelta), shiftHex=3, src=vgpr(vLGDelta),
comment="vgprLaneGroupDelta = lane_group * 8"))
# Compute bpe scale shift once (compile-time constant); used inside
# _emit16bitSubtilePairedStore to adjust addrDVgpr inline without
# modifying it in place, so no restore loop is needed after the stores.
elif self.kernel["_GlobalAccumulation"] != 'MultipleBuffer':
if self.kernel["ProblemType"]["DestDataType"].isFloat8_fnuz() and self.kernel["ProblemType"]["HighPrecisionAccumulate"]:
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8NanInf), "0x207", comment="Nan and +/- inf" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8Max), "0x43700000", comment="Fp8 Max value 240 as float32" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8Min), "0xc3700000", comment="Fp8 Min value -240 as float32" ))
elif self.kernel["ProblemType"]["DestDataType"].isFloat8() and self.kernel["ProblemType"]["HighPrecisionAccumulate"]:
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8NanInf), "0x207", comment="Nan and +/- inf" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8Max), "0x43E00000", comment="Fp8 Max value 448 as float32" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprFp8Min), "0xc3E00000", comment="Fp8 Min value -448 as float32" ))
elif self.kernel["ProblemType"]["DestDataType"].isAnyBFloat8() and self.kernel["ProblemType"]["HighPrecisionAccumulate"]:
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprBF8NanInf), "0x207", comment="Nan and +/- inf" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprBF8Max), "0x47600000", comment="BF8 Max value 57344 as float32" ))
module.add(VMovB32(vgpr(self.cvtVgprStruct.vgprBF8Min), "0xc7600000", comment="BF8 Min value -57344 as float32" ))
storeCode = Module("GroupLoadStore")
vlcntTotalIssued = self.loadsBetaIssued + self.loadsEIssued + self.loadsGateIssued
dscntTotalIssued = self.localLoadsBiasIssued + self.loadsScaleAVecIssued + self.loadsScaleBVecIssued + self.loadsScaleAlphaVecIssued
waitCnter = [vlcntTotalIssued, dscntTotalIssued]
for elementIdx in range(0, len(self.batchElements)):
element = self.batchElements[elementIdx]
addrCalc: AddrCalculation = self.ss.elementAddr[elementIdx]
addr = addrCalc.addrDVgpr
dataE = self.ss.elementDataE[elementIdx]
dataGate = self.ss.elementDataGate[elementIdx] if self.parentWriter.states.useGateResidual else 0
dataBias = self.ss.elementDataBias[elementIdx]
dataScaleAVec = self.ss.elementDataScaleAVec[elementIdx]
dataScaleBVec = self.ss.elementDataScaleBVec[elementIdx]
dataScaleAlphaVec = self.ss.elementDataScaleAlphaVec[elementIdx]
mask = self.ss.elementMask[elementIdx]
vc0 = element[3]
# When skipRearrangement is True:
# - Data is at WMMA output (v[0:N]), not at elementSumIdx (v[144:N])
# - Store should read from WMMA output directly
# Note: Beta paths need the rearrangement because they load C and compute rC = alpha*rC + beta*C
#
# skipRearrangement requires elementSumIdx to be contiguous so that
# (elementSumIdx[i] - elementSumIdx[0]) == i. When checkOutAligned
# returns non-contiguous blocks (gaps in the pool), the relative offset
# no longer matches the sequential MFMA output layout, causing stores
# to read from wrong VGPRs.
esIdx = self.ss.elementSumIdx
contiguous = all(esIdx[i] - esIdx[0] == i for i in range(len(esIdx)))
if self.skipRearrangement and contiguous:
sumIdx = esIdx[elementIdx] - esIdx[0]
else:
sumIdx = self.ss.elementSumIdx[elementIdx]
# CompactLoopStore look-ahead override for this store loop (see
# _lookaheadRowInc). Separate for-loop pass from the _prolog one, so it is
# recomputed here.
_emitOverrideRows = self._lookaheadRowInc(elementIdx)
# print(str(element)+" rowInc="+str(addrCalc.rowInc))
# Already write wave column block into LDS
# Now read lds data back to registers and write to global memroy
if self.kernel["_GlobalAccumulation"] != "MultipleBufferSingleKernel":
if self.ss.optSrdIncForRow and (addrCalc.rowInc or (self.kernel["CompactLoopStore"] and elementIdx == 0 and self.batchIdx == 0)) and self.kernel["StoreRemapVectorWidth"] > 0:
module.addComment1("StoreRemap: shift coord1 address")
if self.kernel["ProblemType"]["UseE"] and (self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1):
# TODO Check if works with StreamK
printExit("Use E does not support StoreRemapVectorWidth if GSU == 1.")
# module.add(addrCalc.incrementToNextRow(self.kernel, "E", self.ss, self.tmpS01, isCompute=True))
module.add(addrCalc.incrementToNextRow(self.kernel, "D", self.ss, self.tmpS01, forceinitrow0=1, overrideAfterPrimerRows=_emitOverrideRows))
module.add(VMovB32(vgpr(self.tmpVgpr), addrCalc.rowInc, comment="set shift rows"))
module.add(VAddU32(vgpr(self.parentWriter.vgprs.storeRemapCoord1), vgpr(self.parentWriter.vgprs.storeRemapCoord1), vgpr(self.tmpVgpr), "shift storeRemap coord1"))
# When stores are interleaved (GLS=0) with subtile NonEdge guards, the
# M-guard branch for the last store in N-group K targets the N-group end
# label. That label must be placed BEFORE the beta*C fmacs for N-group K+1,
# otherwise the M-guard branch skips the fmacs and the next N-group stores zeros.
if isSubtileNonEdge and not self.kernel["GroupLoadStore"]:
blockIdxN = element[0]
if blockIdxN != self._subtilePrevBlockIdxN and self._subtileNGroupSkipLabel is not None:
module.add(self._subtileNGroupSkipLabel)
self._subtileNGroupSkipLabel = None
if self._subtilePendingSrdDInc is not None:
module.add(self._subtilePendingSrdDInc)
self._subtilePendingSrdDInc = None
# apply in-bounds exec mask
if self.edge and not self.kernel["BufferStore"]:
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask, self.laneSGPRC), "sgprs -> exec"))
if interleaveStoreVmcnt:
waitcntInst = self.globalStoreWait(elementIdx, waitCnter, vlcntTotalIssued, dscntTotalIssued, True)
if waitcntInst:
module.addSpaceLine()
module.add(waitcntInst)
def applyScaleVec(vecModule, addressStr, dataScaleVec, factorDim, isGlobal=True):
if not self.beta and not self.applyAlpha: # case for beta-0 and alpha == 1,(OptNLL)
if (self.kernel["ProblemType"]["DestDataType"].isInt8() or self.kernel["ProblemType"]["DestDataType"].isInt32() or \
(self.kernel["ProblemType"]["DataType"].isInt8() and self.kernel["ProblemType"]["DestDataType"].isHalf()) or \
(self.kernel["ProblemType"]["DataType"].isInt8() and self.kernel["ProblemType"]["DestDataType"].isBFloat16())) and \
self.kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(convertData(self.gwvw, self.ss.elementSumIdx[elementIdx], cvtType=CvtType.CVT_I32_to_F32, \
inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu))
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
maskConst = 1.0
elif self.kernel["ProblemType"]["ComputeDataType"].isInt32():
maskConst = 1
gwvw = 1 if factorDim else self.gwvw
if isGlobal:
vecModule.add(VCmpGtU32(dst=sgpr("Address%s"%addressStr, self.parentWriter.states.laneSGPRCount), src0=sgpr("Srd%s+2"%addressStr), src1=0, comment=" == 0 ?"))
for vi2 in range(0, gwvw):
vecModule.add(VCndMaskB32(
dst=vgpr(dataScaleVec + vi2), \
src1=vgpr(dataScaleVec + vi2), \
src0=maskConst, \
src2=sgpr("Address%s"%addressStr, self.parentWriter.states.laneSGPRCount), \
comment="1. mul 1 if 0"))
if factorDim and self.gwvw > 1:
vecModule.add(VMovB32(dst=vgpr(dataScaleVec+1), src=vgpr(dataScaleVec), comment="copy data%s to data%s+1"%(addressStr, addressStr)))
for vi in range(0, self.gwvw):
inputScaleVecVgpr = dataScaleVec + (0 if factorDim else vi)
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single f32 code if edge is detected.
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
vecModule.add(VMulF32(dst=vgpr("ValuC+%d"%vgprIdx), src0=vgpr(inputScaleVecVgpr), src1=vgpr("ValuC+%d"%vgprIdx), comment="*= %sVMul"%addressStr ))
# Original packed route
elif vi%2 == 1:
assert (self.gwvw % 2 == 0)
else:
vecModule.add(VMulPKF32(dst=vgpr("ValuC+%d"%vgprIdx, 2), src0=vgpr(inputScaleVecVgpr, 2), src1=vgpr("ValuC+%d"%vgprIdx, 2), comment="*= %sVMulPK(%d)(%d)"%(addressStr, dataScaleVec,vi)))
elif self.kernel["ProblemType"]["ComputeDataType"].isInt32():
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single i32 code if edge is detected.
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
vecModule.add(VMulLOU32(dst=vgpr("ValuC+%d"%vgprIdx), src0=vgpr(inputScaleVecVgpr), src1=vgpr("ValuC+%d"%vgprIdx), comment="*= %sVMul"%addressStr ))
elif vi%2 == 1:
assert (self.gwvw % 2 == 0)
else:
vecModule.add(VMulLOU32(dst=vgpr("ValuC+%d"%vgprIdx), src0=vgpr(inputScaleVecVgpr), src1=vgpr("ValuC+%d"%vgprIdx), comment="*= %sVMulPK(%d)(%d)"%(addressStr, dataScaleAlphaVec,vi)))
vecModule.add(VMulLOU32(dst=vgpr("ValuC+%d"%(vgprIdx+1)), src0=vgpr(inputScaleVecVgpr+1), src1=vgpr("ValuC+%d"%(vgprIdx+1)), comment="*= %sVMulPK(%d)(%d)"%(addressStr, dataScaleAlphaVec,vi)))
else:
raise RuntimeError("Unsupported %s compute data type %s."%(addressStr, str(self.kernel["ProblemType"]["ComputeDataType"])))
isSingleKernel = ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel") or self.kernel["StreamK"] > 0
scaleAVecModule = Module("ScaleAVecModule")
scaleBVecModule = Module("ScaleBVecModule")
if (self.kernel["ProblemType"]["UseScaleAB"] == "Vector") and isSingleKernel:
applyScaleVec(scaleAVecModule, "ScaleA", dataScaleAVec, 0, isGlobal=False)
applyScaleVec(scaleBVecModule, "ScaleB", dataScaleBVec, 1, isGlobal=False)
module.add(scaleAVecModule)
module.add(scaleBVecModule)
scaleAlphaVecModule = Module("scaleAlphaVecModule")
if self.kernel["ProblemType"]["UseScaleAlphaVec"] and isSingleKernel:
applyScaleVec(scaleAlphaVecModule, "ScaleAlphaVec", dataScaleAlphaVec, self.factorDim, isGlobal=False)
module.add(scaleAlphaVecModule)
if self.beta:
module.add(self._addSumAlphaWithCBeta(self.kernel, self.ss, self.gwvw, elementIdx, vc0, self.tmpVgpr, self.cvtVgprStruct))
elif ((self.parentWriter.states.useBias == DataDirection.READ) or self.kernel["ActivationFuncCall"]) and not self.applyAlpha \
and not ( self.kernel["ProblemType"]["UseScaleAlphaVec"] and isSingleKernel): # case of alpha=1 and beta=0
if (self.kernel["ProblemType"]["DestDataType"].isInt8() or self.kernel["ProblemType"]["DestDataType"].isInt32() or \
(self.kernel["ProblemType"]["DataType"].isInt8() and self.kernel["ProblemType"]["DestDataType"].isHalf()) or \
(self.kernel["ProblemType"]["DataType"].isInt8() and self.kernel["ProblemType"]["DestDataType"].isBFloat16())) and \
self.kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(convertData(self.gwvw, self.ss.elementSumIdx[elementIdx], cvtType=CvtType.CVT_I32_to_F32, \
inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu))
# Add bias
mergeActFuncCall = False
if self.parentWriter.states.useBias == DataDirection.READ:
if activationCDataType == self.kernel["ProblemType"]["ComputeDataType"] and self.kernel["ActivationFuncCall"]:
mergeActFuncCall = True
if (self.kernel["ProblemType"]["Gradient"] and self.kernel["ProblemType"]["ActivationType"] != 'none' and self.kernel["ProblemType"]["UseE"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
mergeActFuncCall = False
if self.factorDim and self.gwvw > 1:
module.add(VMovB32(dst=vgpr(dataBias+1), src=vgpr(dataBias), comment="copy dataBias to dataBIas+1"))
for vi in range(0, self.gwvw):
inputVgpr = dataBias + + (0 if self.factorDim else vi)
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
vgprDst = (self.activationSetPCStruct.vgprActCopy + vi) if mergeActFuncCall else "ValuC+%d"%vgprIdx
# Generate single f32 code if edge is detected.
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
module.add(VAddF32(dst=vgpr(vgprDst), src0=vgpr(inputVgpr), src1=vgpr("ValuC+%d"%vgprIdx), \
comment="C += bias"))
# Original packed route
elif vi%2 == 1:
assert (self.gwvw % 2 == 0)
else:
module.add(VAddPKF32(dst=vgpr(vgprDst, 2), src0=vgpr(inputVgpr, 2), \
src1=vgpr("ValuC+%d"%vgprIdx, 2), comment="C += bias"))
else:
raise RuntimeError("Unsupported bias compute data type %s."%str(self.kernel["ProblemType"]["ComputeDataType"]))
# Gate Residual: acc = gate*acc + gate. Built here but DEFERRED — added after
# activation+scaleD (see module.add(gateModule) below), on the f32 ValuC.
gateModule = Module("Empty GateResidual")
if self.parentWriter.states.useGateResidual and \
(self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1 or self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel"):
# TODO: I8 input (int32 compute) — needs int32 acc -> cvt f32 -> FMA ->
# saturate-cast. Not supported yet; reject below.
if not self.kernel["ProblemType"]["ComputeDataType"].isSingle():
raise RuntimeError(
"GateResidual currently requires ComputeDataType=f32 (HPA); got %s" %
str(self.kernel["ProblemType"]["ComputeDataType"]))
gateList = list(self.kernel["ProblemType"].get("GateResidualDataTypeList", []))
if not gateList:
# Default fallback: use DestDataType as the gate dtype.
gateList = [self.kernel["ProblemType"]["DestDataType"]]
def _emit_gate_fma():
"""Identity (branchless null): ValuC = (gate+s)*ValuC + gate.
GateNullOne (s) = 1.0 if gate null else 0.0 -> null:(0+1)*acc+0=acc; real:gate*acc+gate."""
fmaMod = Module("GateFMA")
for vi in range(0, self.gwvw):
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
fmaMod.add(VAddF32(dst=vgpr(self.tmpVgpr), src0=vgpr(dataGate + vi),
src1=sgpr("GateNullOne"), comment="gate + s (vi=%d)"%vi))
fmaMod.add(VFmaF32(
dst=vgpr("ValuC+%d"%vgprIdx),
src0=vgpr(self.tmpVgpr),
src1=vgpr("ValuC+%d"%vgprIdx),
src2=vgpr(dataGate + vi),
comment="GateResidual identity: acc = (gate+s)*acc + gate (vi=%d)"%vi))
return fmaMod
if len(gateList) == 1:
gateModule.add(self._emitGateCvt(dataGate, gateList[0]))
gateModule.add(_emit_gate_fma())
else:
gateModule.add(_emit_gate_fma())
if (self.kernel["ProblemType"]["UseE"] and not self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
vgprIdx = self.ss.elementSumIdx[elementIdx] - self.parentWriter.states.c.startVgprValu
vgprDst = self.activationSetPCStruct.vgprActCopy if mergeActFuncCall else vgprIdx
prefixStr = "" if mergeActFuncCall else "ValuC+"
prefixOffset = 0 if mergeActFuncCall else self.parentWriter.states.c.startVgprValu
# Packdata if needed
tmpVgpr = self.tmpVgpr
if mergeActFuncCall:
tmpVgpr += self.gwvw * self.kernel["ProblemType"]["ComputeDataType"].numRegisters()
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
if self.kernel["ProblemType"]["DataTypeE"].isHalf():
packdata = PackData_F16()
module.add(packdata(self.gwvw, tmpVgpr, vgprDst, tmpVgpr=tmpVgpr, inputPrefix=prefixStr, prefixOffset=prefixOffset))
vgprDst = tmpVgpr
elif self.kernel["ProblemType"]["DataTypeE"].isBFloat16():
packdata = PackData_BF16()
module.add(packdata(self.gwvw, tmpVgpr, vgprDst, self.cvtVgprStruct, self.tmpS01, self.laneSGPRC,
tmpVgpr=tmpVgpr, inputPrefix=prefixStr, prefixOffset=prefixOffset))
vgprDst = tmpVgpr
elif self.kernel["ProblemType"]["DataTypeE"].isSingle():
if not mergeActFuncCall:
vgprDst = "ValuC+%d" % vgprDst
elif self.kernel["ProblemType"]["DataTypeE"].isFloat8():
packdata = PackData_FLOAT8()
module.add(packdata(self.gwvw, tmpVgpr, vgprDst, self.cvtVgprStruct, self.tmpS01, self.laneSGPRC,
inputPrefix=prefixStr, prefixOffset=prefixOffset))
vgprDst = tmpVgpr
elif self.kernel["ProblemType"]["DataTypeE"].isFloat8_fnuz():
packdata = PackData_FLOAT8_fnuz()
module.add(packdata(self.gwvw, tmpVgpr, vgprDst, self.cvtVgprStruct, self.tmpS01, self.laneSGPRC,
inputPrefix=prefixStr, prefixOffset=prefixOffset))
vgprDst = tmpVgpr
else:
printExit("Unsupport type for E output. (%s)"%self.kernel["ProblemType"]["DataTypeE"].toEnum())
else:
printExit("Unsupport compute type for E output. (%s)"%self.kernel["ProblemType"]["ComputeDataType"].toEnum())
module.add(self.parentWriter.addStore(self.kernel, self.ss, 'E', addrCalc, vgprDst, self.tmpS01, self.edge, comment="store E"))
SaturateTypeInt8 = SaturateCastType.NORMAL
gradientCvtModule = Module("gradientCvtModule")
if (self.kernel["ProblemType"]["UseE"] and self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
loadOffset = int((self.kernel["ProblemType"]["ComputeDataType"].numRegisters() - self.kernel["ProblemType"]["DataTypeE"].numRegisters()) * self.ss.cfg.gwvw)
if activationCDataType != self.kernel["ProblemType"]["DataTypeE"]:
if activationCDataType.isSingle() and self.kernel["ProblemType"]["DataTypeE"].isHalf():
for vi in range(0, self.gwvw):
dataEV = dataE + vi
dataEV2 = dataE + vi // 2
selectbit = HighBitSel.LOW if (self.gwvw != 1 and vi % 2 == 0) or (self.gwvw == 1 and elementIdx % 2 == 0) else HighBitSel.HIGH
gradientCvtModule.add(ECvtF16toF32(dst=vgpr(dataEV), src=vgpr(dataEV2+loadOffset), sel=selectbit, comment="gwvw %d, elementIdx %d"%(self.gwvw, elementIdx)))
elif activationCDataType.isSingle() and self.kernel["ProblemType"]["DataTypeE"].isBFloat16():
for vi in range(0, self.gwvw):
dataEV = dataE + vi
dataEV2 = dataE + vi // 2
# Consider bf16 without packing (gwvw==1)
# TODO: check correctness for gfx950
selectWord = 0 if (self.gwvw != 1 and vi % 2 == 0) or (self.gwvw == 1) else 1
module.add(VCvtBF16toFP32(dst=vgpr(dataEV), src=vgpr(dataEV2+loadOffset), vgprMask=vgpr(self.cvtVgprStruct.vgprBf16Mask), vi=(selectWord), comment="gwvw %d, elementIdx %d"%(self.gwvw, elementIdx)))
else:
printExit("[Gradient input] Unsupported conversion.")
# Activation
activationModule = None
isActivationInsertAfter = False
if self.kernel["ProblemType"]["Gradient"] and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
gradientInput = dataE
enableValuC = False
else:
gradientInput = self.ss.elementSumIdx[elementIdx]
enableValuC = True
if self.kernel["LocalSplitU"] > 1:
# When LSU > 1, the VGPRs are from LSU output.
# the elementSumIdx has indicated the VGPRs from LSU.
# Don't use the ValuC prefix here.
enableValuC = False
# gradientInput is an absolute vgpr index (= elementSumIdx). The
# ActivationFuncCall/copyData and getActivationDestDataType consumers below
# address it directly as an absolute vgpr, so keep it absolute here. Only
# getActivationActivationComputeType() uses the "ValuC+N" relative namespace
# (when enableValuC is set); that startVgprValu offset is applied at its
# call site below.
if self.kernel["ActivationFuncCall"]:
if (activationCDataType == self.kernel["ProblemType"]["DestDataType"]) and \
(activationCDataType != self.kernel["ProblemType"]["ComputeDataType"]) and ((self.kernel["ProblemType"]["UseScaleCD"] == False) or (self.kernel["ProblemType"]["UseScaleAlphaVec"] == False)):
isActivationInsertAfter = True
activationModule = Module("ActivationFuncCall")
if (not mergeActFuncCall) and (not isActivationInsertAfter):
activationModule.appendModule (copyData(activationCDataType, gradientInput, self.gwvw, \
self.activationSetPCStruct.vgprActCopy))
swappc = SSwapPCB64(dst=sgpr(self.activationSetPCStruct.sgprOffsetBack, 2), \
src=sgpr(self.activationSetPCStruct.sgprOffsetActivation, 2))
calleeLabelsByGwvw = getattr(self.activationSetPCStruct, "calleeLabelsByGwvw", None)
if calleeLabelsByGwvw:
calleeFuncs = list(calleeLabelsByGwvw.get(self.gwvw, ()))
if calleeFuncs:
swappc.calleeFuncs = calleeFuncs
activationModule.add(swappc)
activationModule.appendModule (copyData(activationCDataType, gradientInput, self.gwvw, \
self.activationSetPCStruct.vgprActCopy, 1))
elif self.parentWriter.insertActivationAfterPacked(self.kernel, self.activationTypeStr) and (self.kernel["ProblemType"]["UseScaleAlphaVec"] == False):
isActivationInsertAfter = True
activationModule = self.parentWriter.getActivationDestDataType(self.kernel, self.activation, \
self.activationTypeStr, self.gwvw, gradientInput , gradientInput, self.tmpVgpr, self.tmpSgpr)
else:
satInt8 = False
if self.kernel["ProblemType"]["DestDataType"].isInt8():
if (self.activationTypeStr == 'abs') or (self.activationTypeStr == 'relu'):
SaturateTypeInt8 = SaturateCastType.DO_NOTHING
satInt8 = True
# getActivationActivationComputeType emits on the "ValuC+N" relative
# register namespace when enableValuC is set, so make its input index
# relative to startVgprValu. The offset is scoped to this consumer only;
# the absolute-vgpr consumers above are left untouched.
actComputeInput = gradientInput
if enableValuC:
actComputeInput = gradientInput - self.parentWriter.states.c.startVgprValu
activationModule = self.parentWriter.getActivationActivationComputeType(self.kernel, self.activation, \
self.activationTypeStr, self.gwvw, actComputeInput, actComputeInput, self.tmpVgpr, self.tmpSgpr, satInt8, enableValuC)
# Add C *= GradientAct
if self.kernel["ProblemType"]["ActivationType"] != 'none' and self.kernel["ProblemType"]["Gradient"] and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
if isActivationInsertAfter:
assert 0, "Gradient does not support isActivationInsertAfter."
for vi in range(0, self.gwvw):
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
dataEV = dataE + vi
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single f32 code if edge is detected.
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
activationModule.add(VMulF32(dst=vgpr("ValuC+%d"%vgprIdx), src0=vgpr("ValuC+%d"%vgprIdx), src1=vgpr(dataEV), comment="C *= GradAct"))
# Original packed route
elif vi%2 == 1:
assert (self.gwvw % 2 == 0)
else:
activationModule.add(VMulPKF32(dst=vgpr("ValuC+%d"%vgprIdx, 2), src0=vgpr("ValuC+%d"%vgprIdx, 2), src1=vgpr(dataEV, 2), comment="C *= GradAct"))
else:
assert 0, "Unsupported gradient type"
scaleDModule = Module("Empty scaleDModule")
if self.kernel["ProblemType"]["UseScaleCD"] and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
for vi in range(0, self.gwvw):
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
if self.kernel["ProblemType"]["ComputeDataType"].isSingle():
vgprIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single f32 code if edge is detected.
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
if self.kernel["ProblemType"]["OutputAmaxD"]:
if self.edge:
activationModule.add(VCmpEQU32(dst=VCC(), src0="BufferOOB", src1=(vgpr(addrCalc.addrDVgpr)), comment =""))
activationModule.add(VCndMaskB32(dst=vgpr("AmaxOutB"), src0=vgpr("ValuC+%d"%vgprIdx), src1=0, src2=VCC(), comment="Check If OOB, put zero if OOB"))
activationModule.add(VMaxF32(dst=vgpr("AmaxOut"), src0=vgpr("AmaxOut"), src1=vgpr("AmaxOutB", isAbs=True), comment="absmax"))
else:
activationModule.add(VMaxF32(dst=vgpr("AmaxOut"), src0=vgpr("AmaxOut"), src1=vgpr("ValuC+%d"%vgprIdx, isAbs=True), comment="absmax"))
activationModule.add(VMulF32(dst=vgpr("ValuC+%d"%vgprIdx), src0=vgpr("ValuC+%d"%vgprIdx), src1=sgpr("ScaleD"), comment="result *= ScaleD"))
# Original packed route
elif vi%2 == 1:
assert (self.gwvw % 2 == 0)
else:
activationModule.add(VMulPKF32(dst=vgpr("ValuC+%d"%vgprIdx, 2), src0=vgpr("ValuC+%d"%vgprIdx, 2), src1=sgpr("ScaleD", 2), vop3=VOP3PModifiers(op_sel_hi=[1,0,1]), comment="result *= ScaleD"))
else:
assert 0, "Unsupported scaleD type"
# pack stores, beta and non-beta reach here:
packModule = Module("Empty pack module")
convertModule = Module("Empty convert module")
if self.needsAccumToDestConversion:
if self.kernel["ActivationFuncCall"] and activationCDataType == self.kernel["ProblemType"]["DestDataType"]:
destIdx = self.activationSetPCStruct.vgprActCopy
else:
destIdx = self.ss.elementSumIdx[elementIdx]
packTmpS01 = self._epilogScratchSgpr(self.laneSGPRC)
if self.kernel["ProblemType"]["DestDataType"].isHalf():
# For UseSubtileImpl non-edge: paired dwordx4 path handles packing in _emit16bitSubtilePairedStore.
if not is16bitSubtile:
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
elif self.kernel["ProblemType"]["DestDataType"].isBFloat16():
# For UseSubtileImpl non-edge: paired dwordx4 path handles packing in _emit16bitSubtilePairedStore.
if not is16bitSubtile:
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], bf16CVTVgprStruct=self.cvtVgprStruct,
tmpS01=packTmpS01, laneSGPRC=self.laneSGPRC, inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
elif self.kernel["ProblemType"]["DestDataType"].isAnyFloat8():
if self.kernel["ProblemType"]["StochasticRounding"]:
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], fp8CVTVgprStruct=self.cvtVgprStruct, \
tmpS01=packTmpS01, laneSGPRC=self.laneSGPRC, vgprTmp=vgprRND, inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu, alphaScale=1.0)
else:
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], fp8CVTVgprStruct=self.cvtVgprStruct, \
tmpS01=packTmpS01, laneSGPRC=self.laneSGPRC, inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
elif self.kernel["ProblemType"]["DestDataType"].isAnyBFloat8():
# TODO: BF8 stochastic rounding is not yet supported here.
# VCvtSRF32toBF8 instruction exists but stochasticRoundingCvt() only emits VCvtSRF32toFP8.
# To support BF8 SR: add SR branch here, generalize stochasticRoundingCvt() to accept bf8CVTVgprStruct,
# and select VCvtSRF32toBF8 based on DestDataType.
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], bf8CVTVgprStruct=self.cvtVgprStruct, \
tmpS01=packTmpS01, laneSGPRC=self.laneSGPRC, inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
elif self.kernel["ProblemType"]["DestDataType"].isInt32():
if self.kernel["ProblemType"]["ComputeDataType"].isSingle() and ((self.parentWriter.states.useBias == DataDirection.READ) or self.kernel["ActivationFuncCall"] or self.applyAlpha or self.beta):
convertModule = convertData(self.gwvw, self.ss.elementSumIdx[elementIdx], cvtType=CvtType.CVT_F32_to_I32, \
inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
elif self.kernel["ProblemType"]["DestDataType"].isInt8():
if self.kernel["ProblemType"]["ComputeDataType"].isSingle() and ((self.parentWriter.states.useBias == DataDirection.READ) or self.kernel["ActivationFuncCall"] or self.applyAlpha or self.beta):
convertModule = convertData(self.gwvw, self.ss.elementSumIdx[elementIdx], cvtType=CvtType.CVT_F32_to_I32, roundType=RoundType.ROUND_TO_NEAREST_EVEN, \
inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
packModule = self.packdata(self.gwvw, destIdx, self.ss.elementSumIdx[elementIdx], self.cvtVgprStruct, self.tmpS01,
SaturateTypeInt8=SaturateTypeInt8, inputPrefix="ValuC+", prefixOffset=self.parentWriter.states.c.startVgprValu)
self._epilogScratchFree(packTmpS01)
if self.parentWriter.states.asmCaps["HasWMMA_V1"] and self.kernel["EnableMatrixInstruction"] and self.kernel["ProblemType"]["DestDataType"].isHalf() and (not self.kernel["ProblemType"]["HighPrecisionAccumulate"]):
for vi in range(0, self.gwvw):
sumIdxV = self.ss.elementSumIdx[elementIdx] + vi
if vi%2 == 1:
formatVgpr = formatting(sumIdxV, "ValuC+", self.parentWriter.states.c.startVgprValu)
d = self.ss.elementSumIdx[elementIdx] + vi//2
dVgpr = formatting(d, "ValuC+", self.parentWriter.states.c.startVgprValu)
packModule.add(VPackF16toB32(dst=vgpr(dVgpr), src0=vgpr(formatting(sumIdxV-1, "ValuC+", self.parentWriter.states.c.startVgprValu)), src1=vgpr(formatVgpr), \
comment="Pack with neighbor"))
if self.kernel["ExpertSchedulingMode"] > 0:
packModule.add(SWaitAlu(va_vdst=0, comment="wait for writes to complete"))
biasReductionModule = Module("biasReductionModule")
if self.storeBiasD == 1:
vgprIdx = self.ss.elementSumIdx[elementIdx] - self.parentWriter.states.c.startVgprValu
biasReductionModule.add(self.parentWriter.addStore(self.kernel, self.ss, 'Bias', addrCalc, "ValuC+%d"%vgprIdx, self.tmpS01, self.edge, comment="store Bias"))
if isActivationInsertAfter:
if self.parentWriter.states.useGateResidual and \
(self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1):
raise RuntimeError(
"GateResidual is not supported with isActivationInsertAfter "
"(activation applied after pack on dest-dtype value).")
module.add(convertModule)
module.add(packModule)
module.add(gradientCvtModule)
module.add(activationModule)
else:
module.add(gradientCvtModule)
module.add(activationModule)
module.add(scaleDModule)
module.add(biasReductionModule)
module.add(gateModule)
module.add(convertModule)
module.add(packModule)
if not self.kernel["StoreRemapVectorWidth"]:
# 16bit UseSubtileImpl non-edge: emit paired dwordx4 stores combining sba=0
# with sba=1 subtile data into one buffer_store_dwordx4. Works for both
# bf16 and fp16 HPA output types.
#
# UseSubtileImpl splits MIWaveTile[0] into two subtile groups:
# sba=0 owns even tt0 values (0, 2, 4, ...)
# sba=1 owns odd tt0 values (1, 3, 5, ...)
# The element list interleaves them as consecutive (even, odd) tt0 pairs:
# element 0: tt0=0 (sba=0)
# element 1: tt0=1 (sba=1) <- pair with element 0
# element 2: tt0=2 (sba=0) (if MIWaveTile[0]>2)
# ...
# Pairing key: tt0 % 2 — even tt0 is sba=0, odd tt0 is sba=1.
storeCodeModule = storeCode if self.kernel["GroupLoadStore"] else module
if is16bitSubtile:
tt0 = element[1] # d0: thread-tile index along M
# Epilogue (bias/activation) is applied per-element in iteration order.
# The paired store must be emitted AFTER both sba=0 and sba=1 elements have
# had their epilogue applied, so we defer it to the sba=1 (odd tt0) iteration.
if tt0 % 2 == 1:
# sba=1 element (odd tt0): both sba=0 and sba=1 epilogues are done — emit paired store.
# Find the sba=0 partner: the immediately preceding element with tt0-1.
partnerElementIdx = elementIdx - 1
partnerExists = (partnerElementIdx >= 0 and
self.batchElements[partnerElementIdx][1] == tt0 - 1)
if partnerExists:
# Paired dwordx4 store for (sba=0 at tt0-1, sba=1 at tt0).
partnerAddrCalc: AddrCalculation = self.ss.elementAddr[partnerElementIdx]
sumIdx0 = self.ss.elementSumIdx[partnerElementIdx]
sumIdx1 = self.ss.elementSumIdx[elementIdx]
prefixOffset = self.parentWriter.states.c.startVgprValu
blockIdxN = element[0]
# Guard with tt0-1 (lower block): skip if even the lower M-block is OOB.
# This also handles N-group transitions.
blockIdxM = tt0 - 1
skipLabel = self._emitSubtileOobGuard(storeCodeModule, blockIdxM, blockIdxN,
labelPrefix="subtile_skip_store")
# Additional check: paired store needs BOTH blocks valid (MGuard > tt0).
# When only the lower block is valid, fall through to a scalar fallback.
guardMSgpr = self.parentWriter.states.subtileM32ValidBlocksSgpr
if guardMSgpr is not None:
afterPairedLabel = Label(self.parentWriter.labels.getNameInc("subtile_after_paired"),
f"after paired/fallback store tt0={tt0}")
fallbackLabelName = self.parentWriter.labels.getNameInc("subtile_scalar_fallback")
fallbackLabel = Label(fallbackLabelName,
f"scalar fallback for d0={tt0-1} when d0={tt0} is OOB")
storeCodeModule.add(_scmpGtU32(self.parentWriter, sgpr("SubtileMGuard"), tt0,
comment=f"paired store: both M-blocks valid? (MGuard > {tt0})"))
storeCodeModule.add(SCBranchSCC0(labelName=fallbackLabel.getLabelName(),
comment=f"only d0={tt0-1} valid -> scalar fallback"))
tmpStoreCode = self._emit16bitSubtilePairedStore(partnerAddrCalc, sumIdx0, sumIdx1, prefixOffset, tt0 - 1, blockIdxM=blockIdxM, blockIdxN=blockIdxN)
storeCodeModule.add(tmpStoreCode)
storeCodeModule.add(SBranch(labelName=afterPairedLabel.getLabelName(),
comment="skip scalar fallback"))
storeCodeModule.add(fallbackLabel)
tmpFallbackCode = self._emit16bitSubtileScalarStore(partnerAddrCalc, sumIdx0, prefixOffset, tt0 - 1, blockIdxM=blockIdxM, blockIdxN=blockIdxN)
storeCodeModule.add(tmpFallbackCode)
storeCodeModule.add(afterPairedLabel)
else:
tmpStoreCode = self._emit16bitSubtilePairedStore(partnerAddrCalc, sumIdx0, sumIdx1, prefixOffset, tt0 - 1, blockIdxM=blockIdxM, blockIdxN=blockIdxN)
storeCodeModule.add(tmpStoreCode)
if skipLabel is not None:
storeCodeModule.add(skipLabel)
self.storesIssued += 1
else:
# sba=1 orphan (no sba=0 partner in this batch — split by batch boundary).
blockIdxM = tt0
blockIdxN = element[0]
orphanSkipLabel = self._emitSubtileOobGuard(storeCodeModule, blockIdxM, blockIdxN,
labelPrefix="subtile_skip_orphan")
sumIdx0 = self.ss.elementSumIdx[elementIdx]
prefixOffset = self.parentWriter.states.c.startVgprValu
tmpStoreCode = self._emit16bitSubtileScalarStore(addrCalc, sumIdx0, prefixOffset, tt0, blockIdxM=blockIdxM, blockIdxN=blockIdxN)
storeCodeModule.add(tmpStoreCode)
if orphanSkipLabel is not None:
storeCodeModule.add(orphanSkipLabel)
self.storesIssued += 1
else:
# sba=0 element (even tt0): defer SRD row increment until after N-group label.
if self.ss.optSrdIncForRow and addrCalc.rowInc:
self._subtilePendingSrdDInc = addrCalc.incrementToNextRow(self.kernel, "D", self.ss, self.tmpS01)
partnerElementIdx = elementIdx + 1
partnerExists = (partnerElementIdx < len(self.batchElements) and
self.batchElements[partnerElementIdx][1] == tt0 + 1)
if not partnerExists:
# Orphan element (no sba=1 partner in this batch): scalar 16bit store now.
# Guard against OOB wave groups (same as paired store path).
blockIdxM = tt0
blockIdxN = element[0]
# Early exit: skip this orphan scalar store if the wave group is outside the valid M/N tile bounds.
orphanSkipLabel = self._emitSubtileOobGuard(storeCodeModule, blockIdxM, blockIdxN,
labelPrefix="subtile_skip_orphan")
sumIdx0 = self.ss.elementSumIdx[elementIdx]
prefixOffset = self.parentWriter.states.c.startVgprValu
tmpStoreCode = self._emit16bitSubtileScalarStore(addrCalc, sumIdx0, prefixOffset, tt0, blockIdxM=blockIdxM, blockIdxN=blockIdxN)
storeCodeModule.add(tmpStoreCode)
if orphanSkipLabel is not None:
storeCodeModule.add(orphanSkipLabel)
self.storesIssued += 1
elif self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":#GSUGSU
tmpStoreCode = self.parentWriter.addStore(self.kernel, self.ss, 'TD', addrCalc, sumIdx, self.tmpS01, self.edge, comment="store TD not StoreRemapVectorWidth")
storeCodeModule.add(tmpStoreCode)
self.storesIssued += 1
else:
# Regular store path. If UseSubtileImpl NonEdge, guard against OOB wave groups.
skipLabel = None
if isSubtileNonEdge:
tt0 = element[1]
blockIdxM = tt0 # each tt0 maps to one mBlockSize-row block
blockIdxN = element[0]
# Early exit: skip this store if the wave group is outside the valid M/N tile bounds.
skipLabel = self._emitSubtileOobGuard(storeCodeModule, blockIdxM, blockIdxN,
labelPrefix="subtile_skip_store")
# Apply exec mask for partial M/N blocks (regular fp32 store path)
if self.parentWriter.states.storeAlign8 and isSubtileNonEdge:
self._emitAlign8ExecMask(storeCodeModule, self.tmpS01, self.tmpS23, blockIdxM, blockIdxN,
mGuardOffset=1, rowScaleShift=2)
storeCodeModule.add(self.getEdgeMovInstType()(EXEC(), sgpr(self.tmpS01, self.laneSGPRC), "apply exec mask"))
# _emitOverrideRows reused from the top of this store loop (see _lookaheadRowInc).
tmpStoreCode = self.parentWriter.addStore(self.kernel, self.ss, 'D', addrCalc, sumIdx, self.tmpS01, self.edge, elementIdx, self.batchIdx,
overrideAfterPrimerRows=_emitOverrideRows, comment="store D")
storeCodeModule.add(tmpStoreCode)
if self.parentWriter.states.storeAlign8 and isSubtileNonEdge:
storeCodeModule.add(self.getEdgeMovInstType()(EXEC(), -1, "restore exec"))
if skipLabel is not None:
storeCodeModule.add(skipLabel)
self.storesIssued += 1
if (self.kernel["ProblemType"]["UseE"] and not self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
self.storesIssued += 1
if self.storeBiasD == 1:
self.storesIssued += 1
else:
if not self.kernel["_GlobalAccumulation"] == "MultipleBufferSingleKernel":#GSUGSU
rpe = self.parentWriter.states.bpeCinternal // self.parentWriter.states.bpr
module.add(self.parentWriter.storeRemapAddLocalWrite(self.kernel, self.ss, addrCalc, sumIdx*rpe))
# Column Block Shape has been written to LDS
# Now read back and write out to global memory
else:
tmpStoreCode = self.parentWriter.addStore(self.kernel, self.ss, 'TD', addrCalc, sumIdx, self.tmpS01, self.edge, comment="store TD StoreRemapVectorWidth")
if self.kernel["GroupLoadStore"]:
storeCode.add(tmpStoreCode)
module.add(tmpStoreCode)
self.storesIssued += 1
if (self.kernel["ProblemType"]["UseE"] and not self.kernel["ProblemType"]["Gradient"]) and ((self.kernel["GlobalSplitU"] == 1 or self.kernel["GlobalSplitU"] == -1) or self.kernel["StreamK"] > 0):
self.storesIssued += 1
if self.storeBiasD == 1:
self.storesIssued += 1
# Close the last N-group OOB skip label (if any) opened by _emitSubtileOobGuard.
self._finalizeSubtileOobGuards(storeCode if self.kernel["GroupLoadStore"] else module)
if self.kernel["ProblemType"]["StochasticRounding"]:
self.parentWriter.vgprPool.checkIn(vgprRND)
module.add(storeCode)
if self.parentWriter.db["CheckStoreC"]>=0:
useBuffer = self.kernel["BufferStore"]
# Note - CheckStoreC won't work for EDGE store cases since they load 0 for OOB, would need more sophisticated check
# Note - TODO- CheckStoreC also won't work for StoreRemap
module.add(SWaitCnt(vscnt=0, comment="CheckStoreC, wait for stores to complete"))
for elementIdx in range(0, len(self.batchElements)):
addr = self.ss.elementAddr[elementIdx].addrDVgpr
sumIdx = self.ss.elementSumIdx[elementIdx]
bps = self.kernel["ProblemType"]["DestDataType"].numBytes() * self.gwvw
if self.kernel["BufferStore"]:
addr0 = vgpr(addr)
addr1 = sgpr("SrdC", 4)
else:
addr0 = vgpr(addr,2)
addr1 = ""
if self.kernel["ProblemType"]["DestDataType"].isHalf() or self.kernel["ProblemType"]["DestDataType"].isBFloat16():
if not self.kernel["ProblemType"]["HighPrecisionAccumulate"]:
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bps, sumIdx//2, \
addr0, addr1, soffset=0, offset=0, hi16=sumIdx%2))
else:
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bps, sumIdx, \
addr0, addr1, soffset=0, offset=0, hi16=0))
elif self.kernel["ProblemType"]["DestDataType"].isInt32() or self.kernel["ProblemType"]["DestDataType"].isSingle():
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bps, sumIdx, \
addr0, addr1, soffset=0, offset=0))
elif self.kernel["ProblemType"]["DestDataType"].isDouble() or self.kernel["ProblemType"]["DestDataType"].isSingleComplex() :
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bps, sumIdx*2, \
addr0, addr1, soffset=0, offset=0))
elif self.kernel["ProblemType"]["DestDataType"].isDoubleComplex():
module.add(self.parentWriter.chooseGlobalRead(useBuffer, bps, sumIdx*4, \
addr0, addr1, soffset=0, offset=0))
module.add(SWaitCnt(vscnt=0, comment="CheckStoreC, wait for stores to complete"))
# Add checks for expected values:
module.add(SMovB32(sgpr(self.tmpS01), self.parentWriter.db["CheckStoreC"], "expected value"))
for elementIdx in range(0, len(self.batchElements)):
sumIdx = self.ss.elementSumIdx[elementIdx]
# Need to fix for other types:
assert (self.kernel["ProblemType"]["DestDataType"].isSingle() or self.kernel["ProblemType"]["DestDataType"].isInt32())
module.add(self.parentWriter.getCmpAssert(self.parentWriter.asmAssert.eq, vgpr(sumIdx), sgpr(self.tmpS01)))
if self.edge and (self.atomic or not self.kernel["BufferStore"]):
# subsequent batch must start with full exec mask
# BufferStore doesn't need exec since it used buffer range checking when
# possible
module.add(self.getEdgeMovInstType()(EXEC(), -1, "full mask -> exec"))
if self.parentWriter.db["ConservativeWaitCnt"] & 0x40:
module.add(SBarrier(comment="debug"))
module.add(SWaitCnt(vscnt=0, comment="ConservativeWaitCnt"))
module.add(SBarrier(comment="debug"))
def _emitSubtilePackedPermute(self, vPack: int, vPermAddr: int, addrWhilePermuting=None) -> Module:
"""Shuffle four packed dwords across wave halves for a subtile dwordx4 store.
After the caller packs 8 f32 accumulator values into four 16bit dwords
(vPack+0..+3), this routine performs the two-step permute that assembles
eight consecutive M-rows owned by a pair of lane-groups into a contiguous
dwordx4 payload:
Step 1 — ds_bpermute (in-place, 4×): each lane fetches vPack+k from its
partner lane l' (= the lane at LG±1 distance, pre-encoded as a
byte address in vPermAddr). The LDS pipe latches vPermAddr at
issue time, so vPermAddr can be repurposed as soon as all four
ds_bpermute instructions are issued.
Step 2 — v_permlane32_swap_b32 (2×): exchange (vPack+0 ↔ vPack+2) and
(vPack+1 ↔ vPack+3) across the 32-lane boundary so that lanes
0-31 end up with rows LG*8+0..LG*8+7 in ascending order.
The caller may supply an optional `addrWhilePermuting` callable that adds
address-preparation instructions to the same module *between* the four
ds_bpermute issues and the SWaitCnt. This overlaps address arithmetic
with the LDS round-trip latency at no extra cost.
Args:
vPack: Base VGPR index of the four packed dwords (must be
2-aligned to satisfy dwordx4 store alignment).
vPermAddr: VGPR holding the partner-lane byte address (pre-computed
once per batch in the vgprPermAddr slot).
addrWhilePermuting: Optional callable() that appends address instructions
to `module` while the ds_bpermute results are in-flight.
Returns:
Module containing ds_bpermute × 4, optional address code, SWaitCnt,
and v_permlane32_swap_b32 × 2. Leaves vPack+0..+3 holding the
correctly ordered dwords ready for buffer_store_dwordx4.
"""
module = Module("SubtilePackedPermute")
module.addComment1("ds_bpermute in-place: gather packed dwords from partner lane-group")
for k in range(4):
module.add(DSBPermuteB32(dst=vgpr(vPack+k), src0=vgpr(vPermAddr), src1=vgpr(vPack+k),
comment=f"perm dword {k}"))
if addrWhilePermuting is not None:
addrWhilePermuting(module)
module.add(SWaitCnt(dscnt=0, comment="wait for ds_bpermute (lgkmcnt=0)"))
module.addComment1("v_permlane32_swap_b32: swap across lane-32 boundary")
module.add(VPermlane32SwapB32(dst=vgpr(vPack+0), src=vgpr(vPack+2), comment="swap dwords 0↔2"))
module.add(VPermlane32SwapB32(dst=vgpr(vPack+1), src=vgpr(vPack+3), comment="swap dwords 1↔3"))
return module
def _emitSubtileOobGuard(self, targetModule, blockIdxM: int, blockIdxN: int, labelPrefix: str = "subtile_skip_store"):
"""Emit M/N OOB guard branches for UseSubtileImpl NonEdge stores.
Background
----------
UseSubtileImpl assigns each wave a fixed subtile region of the output matrix.
In the NonEdge path the macro-tile fits entirely within the output bounds, but
individual wave groups within the macro-tile may still be out-of-bounds when
the problem size is not a multiple of the macro-tile. The SGPRs
subtileM32ValidBlocksSgpr and subtileN16ValidBlocksSgpr count how many M/N
blocks (in units of mBlockSize rows / nBlockSize columns) belong to valid
output for this wave, and are set to None when no guard is needed (edge path
or problem is tile-aligned).
Logic
-----
For a store at (blockIdxM, blockIdxN):
- If numValidNBlocks <= blockIdxN → N OOB: jump past ALL remaining stores.
Valid because N is monotone: subsequent N groups (blockIdxN+1, ...) are also OOB.
- If numValidMBlocks <= blockIdxM → M OOB: jump to the end of the current N group.
Valid because M is monotone: remaining M elements in this N group are also OOB.
The N guard is emitted ONCE per N group (when blockIdxN changes). It branches to
_subtileAllStoresEndLabel (past all stores). _subtileNGroupSkipLabel marks the
boundary between N groups; M guards branch there to skip the rest of the current
N group without re-testing the remaining M elements.
Both labels are placed by _finalizeSubtileOobGuards (called after the element loop).
Returns a per-element skip Label only when there is no N guard (M-only case); the
caller must add it after the store. Returns None in all other cases.
"""
guardMSgpr = self.parentWriter.states.subtileM32ValidBlocksSgpr
guardNSgpr = self.parentWriter.states.subtileN16ValidBlocksSgpr
# No guard SGPRs means the store is always in-bounds for this path; nothing to emit.
if guardMSgpr is None and guardNSgpr is None:
return None
# --- N-group guard (emitted once per unique blockIdxN) ---
# Branches to _subtileAllStoresEndLabel when N OOB, skipping all remaining stores.
# Because N is monotone (blockIdxN increases each group), if this group is OOB
# then every subsequent group is also OOB — no need to test them.
if guardNSgpr is not None and blockIdxN != self._subtilePrevBlockIdxN:
# Place the previous N group's end label before starting a new group.
if self._subtileNGroupSkipLabel is not None:
targetModule.add(self._subtileNGroupSkipLabel)
self._subtileNGroupSkipLabel = None
if self._subtilePendingSrdDInc is not None:
targetModule.add(self._subtilePendingSrdDInc)
self._subtilePendingSrdDInc = None
# Create the single end-of-all-stores label on the first N group.
if self._subtileAllStoresEndLabel is None:
endLabelName = self.parentWriter.labels.getNameInc("subtile_all_stores_end")
self._subtileAllStoresEndLabel = Label(endLabelName, "end of all subtile NonEdge D stores")
nGroupEndLabelName = self.parentWriter.labels.getNameInc(
f"{labelPrefix}_N{blockIdxN}_end")
nGroupEndLabel = Label(nGroupEndLabelName,
f"end of N group blockIdxN={blockIdxN} (M cbranch target)")
nGuardCmp = blockIdxN * 16 if self.parentWriter.states.storeAlign8 else blockIdxN
targetModule.add(_scmpGtU32(self.parentWriter, sgpr("SubtileNGuard"), nGuardCmp,
comment=f"quick-exit: clamped > {nGuardCmp}? (OOB -> skip all stores)"))
targetModule.add(SCBranchSCC0(labelName=self._subtileAllStoresEndLabel.getLabelName(),
comment=f"quick-exit: N OOB at blockIdxN={blockIdxN}, skip all remaining stores"))
self._subtileNGroupSkipLabel = nGroupEndLabel
self._subtilePrevBlockIdxN = blockIdxN
# --- M guard (emitted per element) ---
# Branches to end of current N group when M OOB, skipping remaining M elements.
# Because M is monotone (blockIdxM increases within the N group), if this element
# is OOB then all subsequent M elements in this N group are also OOB.
if guardMSgpr is None:
return None
targetModule.add(_scmpGtU32(self.parentWriter, sgpr("SubtileMGuard"), blockIdxM,
comment=f"quick-exit: numValidMBlocks > {blockIdxM}? (OOB -> skip N group)"))
if guardNSgpr is not None and self._subtileNGroupSkipLabel is not None:
# M OOB → jump to end of this N group (no per-element label needed).
targetModule.add(SCBranchSCC0(labelName=self._subtileNGroupSkipLabel.getLabelName(),
comment=f"quick-exit: M OOB at blockIdxM={blockIdxM}, skip rest of N group"))
return None
else:
# No N guard → fall back to a per-element skip label (caller places it after the store).
skipLabelName = self.parentWriter.labels.getNameInc(
f"{labelPrefix}_M{blockIdxM}_N{blockIdxN}")
skipLabel = Label(skipLabelName,
f"skip OOB store blockIdxM={blockIdxM} blockIdxN={blockIdxN}")
targetModule.add(SCBranchSCC0(labelName=skipLabel.getLabelName(),
comment=f"quick-exit: M OOB at blockIdxM={blockIdxM}, skip store"))
return skipLabel
def _finalizeSubtileOobGuards(self, targetModule):
"""Place the pending N-group end label and end-of-all-stores label after the element loop.
Must be called once after all elements have been emitted to close out the last
N group and anchor the N-cbranch target past all stores.
"""
if self._subtileNGroupSkipLabel is not None:
targetModule.add(self._subtileNGroupSkipLabel)
self._subtileNGroupSkipLabel = None
if self._subtilePendingSrdDInc is not None:
targetModule.add(self._subtilePendingSrdDInc)
self._subtilePendingSrdDInc = None
if self._subtileAllStoresEndLabel is not None:
targetModule.add(self._subtileAllStoresEndLabel)
self._subtileAllStoresEndLabel = None
def _emitAlign8ExecMask(self, module, tmpS, tmpS2, blockIdxM, blockIdxN, mGuardOffset, rowScaleShift):
"""Emit exec mask for partial M/N blocks in the NonEdge store path.
Computes a 64-bit exec mask that disables OOB lanes for partial M and N
blocks at the tile boundary. The mask is the AND of an M component and
an N component, each computed independently.
MMA output layout (MI16x16, wavefront=64):
64 lanes = 4 lane-groups (LGs) of 16 lanes each.
Each LG owns consecutive M-rows:
- Paired bf16 store: 8 M-rows per LG (32 rows total, 2 MMA tiles)
- Scalar/fp32 store: 4 M-rows per LG (16 rows total, 1 MMA tile)
Within each LG, lane_id % 16 selects the N-column (0..15).
M mask algorithm:
Given validRows (number of valid M-rows in this block), the number of
active lane-groups is validRows / rowsPerLG. Each LG occupies 16
consecutive lanes, so the mask is the bottom (numValidLGs * 16) bits of
a 64-bit word. This is computed as:
shiftAmt = 64 - validRows * (16 / rowsPerLG)
mask = (uint64_t)-1 >> shiftAmt
validM_wave is precomputed once per tile in _emitSubtileGuards.
N mask algorithm:
SubtileNGuard holds the clamped valid-N-column count for this wave.
partialN = SubtileNGuard % 16 gives the number of valid columns within
the last 16-column MMA tile. The N mask disables columns >= partialN:
nMask = (1 << partialN) - 1 e.g. partialN=5 -> 0x001F
nMask = nMask | (nMask << 16) replicate across both LG halves
This 32-bit word is ANDed into both lo and hi halves of the exec mask.
This sequence is designed to fit within the ds_bpermute latency window
(~88 cycles at 4 cyc/issue) for zero-overhead execution on interior blocks.
Args:
module: Target module to emit instructions into.
tmpS: SGPR index for mask result (2 consecutive, even-aligned).
tmpS2: SGPR index for scratch (2 consecutive).
blockIdxM: M block index for this store element.
blockIdxN: N block index for this store element.
mGuardOffset: Number of MMA tiles this store spans (2=paired, 1=single).
rowScaleShift: Left-shift to convert validRows to lane-count
(1 for paired/8-rows-per-LG, 2 for scalar-fp32/4-rows-per-LG).
"""
miM = self.kernel["MatrixInstM"]
blockStartRow = blockIdxM * miM
validMWaveSgpr = self.parentWriter.states.subtileTotalMOffsetSgpr
mMaskDone = Label(self.parentWriter.labels.getNameInc("align8_m_done"), "")
# --- M mask: per-lane-group selection via right-shift ---
isWave32 = self.wavelen == 32
if isWave32:
module.add(SMovB32(dst=sgpr(tmpS), src=-1, comment="mask = full"))
else:
module.add(SMovB64(dst=sgpr(tmpS, 2), src=-1, comment="mask = full"))
module.add(_scmpGtU32(self.parentWriter, sgpr("SubtileMGuard"), blockIdxM + mGuardOffset,
comment=f"SubtileMGuard > {blockIdxM + mGuardOffset}? (block fully interior)"))
module.add(SCBranchSCC1(labelName=mMaskDone.getLabelName(),
comment="interior M block -> mask stays -1"))
if blockStartRow > 0:
module.add(SSubU32(dst=sgpr(tmpS2), src0=sgpr(validMWaveSgpr), src1=blockStartRow,
comment=f"validRows = validM_wave - {blockStartRow}"))
module.add(SCSelectB32(dst=sgpr(tmpS2), src0=0, src1=sgpr(tmpS2), comment="clamp to 0"))
module.add(SLShiftLeftB32(dst=sgpr(tmpS2), src=sgpr(tmpS2), shiftHex=rowScaleShift,
comment=f"validRows * {1 << rowScaleShift}"))
else:
module.add(SLShiftLeftB32(dst=sgpr(tmpS2), src=sgpr(validMWaveSgpr), shiftHex=rowScaleShift,
comment=f"validM_wave * {1 << rowScaleShift}"))
module.add(SSubU32(dst=sgpr(tmpS2), src0=self.wavelen, src1=sgpr(tmpS2),
comment=f"shiftAmt = {self.wavelen} - validRows * scale"))
if isWave32:
module.add(SLShiftRightB32(dst=sgpr(tmpS), src=-1, shiftHex=sgpr(tmpS2),
comment="M mask = -1 >> shiftAmt"))
else:
module.add(SLShiftRightB64(dst=sgpr(tmpS, 2), src=-1, shiftHex=sgpr(tmpS2),
comment="M mask = -1 >> shiftAmt"))
module.add(mMaskDone)
# --- N mask: per-column bit-mask for arbitrary partial N ---
nCmpVal = (blockIdxN + 1) * 16
nMaskDone = Label(self.parentWriter.labels.getNameInc("align8_n_done"), "")
module.add(_scmpGtU32(self.parentWriter, sgpr("SubtileNGuard"), nCmpVal,
comment=f"clamped > {nCmpVal}? (not last N block)"))
module.add(SCBranchSCC1(labelName=nMaskDone.getLabelName(),
comment="interior N block -> mask unchanged"))
module.add(SAndB32(dst=sgpr(tmpS2), src0=sgpr("SubtileNGuard"), src1=0xF,
comment="partialN = clamped %% 16, SCC=1 if non-zero"))
nFullLabel = Label(self.parentWriter.labels.getNameInc("align8_n_full"), "")
module.add(SCBranchSCC0(labelName=nFullLabel.getLabelName(),
comment="partialN==0 -> full 16-col block"))
module.add(SLShiftLeftB32(dst=sgpr(tmpS2), src=1, shiftHex=sgpr(tmpS2),
comment="1 << partialN"))
module.add(SSubU32(dst=sgpr(tmpS2), src0=sgpr(tmpS2), src1=1,
comment="(1 << partialN) - 1"))
if isWave32:
# wave32: replicate lo16 to both halves without extra scratch SGPR
module.add(SMulI32(dst=sgpr(tmpS2), src0=sgpr(tmpS2), src1=hex(0x10001),
comment="replicate lo16 to both halves"))
module.add(SAndB32(dst=sgpr(tmpS), src0=sgpr(tmpS), src1=sgpr(tmpS2),
comment="mask &= N mask"))
else:
module.add(SLShiftLeftB32(dst=sgpr(tmpS2+1), src=sgpr(tmpS2), shiftHex=16,
comment="replicate to hi16"))
module.add(SOrB32(dst=sgpr(tmpS2), src0=sgpr(tmpS2), src1=sgpr(tmpS2+1),
comment="N mask word = lo16 | hi16"))
module.add(SAndB32(dst=sgpr(tmpS), src0=sgpr(tmpS), src1=sgpr(tmpS2),
comment="mask_lo &= N mask"))
module.add(SAndB32(dst=sgpr(tmpS+1), src0=sgpr(tmpS+1), src1=sgpr(tmpS2),
comment="mask_hi &= N mask"))
module.add(nFullLabel)
module.add(nMaskDone)
def _emit16bitSubtilePairedStore(self, addrCalc, sumIdx0: int, sumIdx1: int, prefixOffset: int, tt0: int = 0, blockIdxM: int = 0, blockIdxN: int = 0) -> Module:
"""Emit a paired 16bit store combining sba=0 and sba=1 subtile data.
Works for both bf16 and fp16 HPA output types.
sba = subtile block index along A (M dimension). UseSubtileImpl iterates over
two subtile groups (sba=0, sba=1) that share the same (tt1, tt0) element
coordinates but draw from different accumulator registers. The element list
therefore contains consecutive pairs with identical (tt1, tt0): sba=0 first
(even elementIdx), sba=1 second (odd elementIdx).
Converts 8 f32 accvgprs (4 from sba=0, 4 from sba=1) to 16bit, shuffles them
across wave halves via ds_bpermute + v_permlane32_swap_b32, then issues
1 × buffer_store_dwordx4 at the sba=0 element's address. The cvtVgpr block
is 2-aligned (64-bit) in KWA so vgprBf16Temp satisfies the dwordx4 alignment.
Args:
addrCalc: AddrCalculation for the sba=0 element.
sumIdx0: elementSumIdx for the sba=0 element.
sumIdx1: elementSumIdx for the sba=1 element.
prefixOffset: parentWriter.states.c.startVgprValu (offset into ValuC).
tt0: thread-tile M index (same for both sba=0 and sba=1).
"""
module = Module("16bitSubtilePairedStore")
isFp16 = self.kernel["ProblemType"]["DestDataType"].isHalf()
ntd = self.kernel["NonTemporalD"]
isGlc = bool(ntd & 0x1)
isSlc = bool(ntd & 0x2)
isNT = bool(ntd & 0x4)
# Reuse cvtVgprStruct.vgprBf16Temp..vgprBf16Inc (+0..+3) as 4 scratch vgprs.
# The cvtVgpr block is allocated with 2-alignment (64-bit aligned) in KWA so that
# vgprBf16Temp is at an even VGPR index, satisfying buffer_store_dwordx4's
# alignment requirement. The +0..+3 slots are safely overwritten here as pack/perm
# staging for each pair.
vPack = self.cvtVgprStruct.vgprBf16Temp # +0..3: packed 16bit dwords, 2-aligned
vPermAddr = self.cvtVgprStruct.vgprPermAddr
vLGDelta = self.cvtVgprStruct.vgprLaneGroupDelta
vAddrScratch = self.cvtVgprStruct.vgprAddrScratch
addrDVgpr = addrCalc.addrDVgpr
typeStr = "fp16" if isFp16 else "bf16"
VCvtPkF32to16 = VCvtPkF32toFP16 if isFp16 else VCvtPkF32toBF16
module.addComment1(f"{typeStr} paired dwordx4 store tt0={tt0} (sba=0+sba=1): pack 8 f32 accvgprs -> 4 {typeStr} dwords")
# Pack sba=0 subtile: ValuC+sumIdx0+{0,1} → vPack+0; ValuC+sumIdx0+{2,3} → vPack+1
# Pack sba=1 subtile: ValuC+sumIdx1+{0,1} → vPack+2; ValuC+sumIdx1+{2,3} → vPack+3
def vc(sumIdx, vi):
idx = sumIdx + vi - prefixOffset
return vgpr("ValuC+" + str(idx))
def packF32pair(dst, src0, src1, comment):
"""Pack two f32 VGPRs into one dword of two 16bit values."""
module.add(VCvtPkF32to16(dst=vgpr(dst), src0=src0, src1=src1, comment=f"{comment} -> {typeStr}"))
packF32pair(vPack+0, vc(sumIdx0, 0), vc(sumIdx0, 1), f"sba=0 tt0={tt0}[0:1]")
packF32pair(vPack+1, vc(sumIdx0, 2), vc(sumIdx0, 3), f"sba=0 tt0={tt0}[2:3]")
packF32pair(vPack+2, vc(sumIdx1, 0), vc(sumIdx1, 1), f"sba=1 tt0={tt0}[0:1]")
packF32pair(vPack+3, vc(sumIdx1, 2), vc(sumIdx1, 3), f"sba=1 tt0={tt0}[2:3]")
# Compute adjusted D address into vgprAddrScratch while ds_bpermute results are in-flight.
# addrDVgpr holds the M-byte offset in bpeCexternal units; scale to bpeCexternalGSU1
# (16bit=2 bytes) then add lane_group*8 so the dwordx4 store lands at the correct row.
# addrDVgpr and vgprPermAddr are left unchanged — vgprAddrScratch is dedicated scratch
# for this purpose so no restore is needed.
bpeCurr = self.parentWriter.states.bpeCexternal
bpeDest = self.parentWriter.states.bpeCexternalGSU1
globalOffset = addrCalc.globalOffset * bpeDest // bpeCurr
addrScaleShift = int(log2(bpeCurr // bpeDest)) if bpeCurr > bpeDest else 0
useAlign8 = self.parentWriter.states.storeAlign8
def emitAddrWhilePermuting(module):
"""Callback emitted between ds_bpermute and s_waitcnt lgkmcnt(0).
The ds_bpermute has ~88 cycles of LDS latency. We overlap SALU/VALU
work in this window to hide the cost:
1. Compute the adjusted D store address (VALU).
2. Compute the exec mask for partial M/N blocks (SALU) — this
suppresses OOB lanes at tile boundaries without adding any
latency to the critical path.
`module` is the permute Module, so instructions emitted here land
between the ds_bpermute issue and the s_waitcnt that consumes results."""
if addrScaleShift:
module.add(VLShiftRightB32(dst=vgpr(vAddrScratch), shiftHex=addrScaleShift,
src=vgpr(addrDVgpr), comment=f"scale addrDVgpr bpe {bpeCurr}->{bpeDest}"))
module.add(VAddU32(dst=vgpr(vAddrScratch), src0=vgpr(vAddrScratch), src1=vgpr(vLGDelta),
comment="adjusted D addr = scaled addrDVgpr + lane_group*8"))
else:
module.add(VAddU32(dst=vgpr(vAddrScratch), src0=vgpr(addrDVgpr), src1=vgpr(vLGDelta),
comment="adjusted D addr = addrDVgpr + lane_group*8"))
if useAlign8:
self._emitAlign8ExecMask(module, self.tmpS01, self.tmpS23, blockIdxM, blockIdxN,
mGuardOffset=2, rowScaleShift=1)
module.add(self._emitSubtilePackedPermute(vPack, vPermAddr, addrWhilePermuting=emitAddrWhilePermuting))
if useAlign8:
tmpS = self.tmpS01
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(tmpS, self.laneSGPRC), "apply exec mask"))
module.addComment1("buffer_store_dwordx4: write 8 16bit values (4 dwords, 2-aligned src)")
module.add(BufferStoreB128(
src=vgpr(vPack, 4),
vaddr=vgpr(vAddrScratch),
saddr=sgpr("SrdD", 4),
soffset=0,
mubuf=MUBUFModifiers(offen=True, offset12=globalOffset, glc=isGlc, slc=isSlc, nt=isNT),
comment=f"16bit paired dwordx4 store tt0={tt0},{tt0+1}"
))
if useAlign8:
module.add(self.getEdgeMovInstType()(EXEC(), -1, "restore exec"))
# WAR hazard: buffer_store_dwordx4 reads vPack[0:3] as source operands.
# The next paired store's v_cvt_pk_bf16_f32 will overwrite vPack.
# Insert nop to ensure the store has latched its source VGPRs.
module.add(SNop(waitState=0, comment="1 wait state: WAR hazard between store src and next pack dst"))
return module
def _emit16bitSubtileScalarStore(self, addrCalc, sumIdx0: int, prefixOffset: int, tt0: int = 0, blockIdxM: int = 0, blockIdxN: int = 0) -> Module:
"""Emit a 16bit store for an orphan subtile element with no partner.
Used when MIWaveTile[0] is odd and the last sba=0 element has no sba=1
partner, or when batch boundaries split an (sba=0, sba=1) pair.
The layout below is specific to the mfma instruction used here: lane l = LG*16 + r
owns 4 output values at M-rows (LG*4 + 0..3) and a single N-column
(l % 16 = r = lane_id & 15). In column-major (row-first in memory) layout
these 4 values ARE contiguous
in memory (consecutive M-rows at fixed N-col), so we use 2x buffer_store_dwordx2
after packing all 4 16bit values into 2 dwords.
The per-lane vaddr encodes:
vaddr = (lane_id & 15) * StrideD1J * bpe [N-col byte offset within wave tile]
+ vLGDelta [LG*4 M-rows * bpe = LG*8 bytes]
+ wg0*MT0*bpe [workgroup M byte base]
+ waveId0 * waveM_stride * bpe [M-wave byte offset within WG]
+ waveId1 * waveN_stride * StrideD1J * bpe [N-wave byte offset]
and a constant offset12 = globalOffset (encodes d0 M-tile position within wave).
The SRD base encodes only wg1*MT1*StrideD1J*bpe (N workgroup offset).
The M workgroup offset (wg0*MT0*bpe) and wave-within-WG offsets must be
included in the vaddr explicitly.
Args:
addrCalc: AddrCalculation for the element.
sumIdx0: elementSumIdx for the element.
prefixOffset: parentWriter.states.c.startVgprValu (offset into ValuC).
"""
module = Module("16bitSubtileScalarStore")
isFp16 = self.kernel["ProblemType"]["DestDataType"].isHalf()
ntd = self.kernel["NonTemporalD"]
isGlc = bool(ntd & 0x1)
isSlc = bool(ntd & 0x2)
isNT = bool(ntd & 0x4)
# Scratch vgprs from the cvtVgprStruct block (overwritten each call):
# vPack+0 : 16bit packed dword (vc=0,1)
# vPack+1 : wave ID scratch / 16bit packed dword (vc=2,3)
# vPack+2 : per-lane vaddr (N-col byte offset + M offsets)
# vPack+3 : temp for N-col byte offset computation
vPack = self.cvtVgprStruct.vgprBf16Temp
vLGDelta = self.cvtVgprStruct.vgprLaneGroupDelta # LG*4*bpe = LG*8 bytes (pre-computed)
# addrCalc.globalOffset was computed with bpeCexternal (may be 4 for _GlobalAccumulation kernels),
# but the 16bit orphan store always targets the final 16bit output (bpeCexternalGSU1=2).
bpeCurr = self.parentWriter.states.bpeCexternal
bpe = self.parentWriter.states.bpeCexternalGSU1 # always 2 for 16bit dest
globalOffset = addrCalc.globalOffset * bpe // bpeCurr
def vc(vi):
idx = sumIdx0 + vi - prefixOffset
return vgpr("ValuC+" + str(idx))
# Derive the D-stride sgpr name (e.g. "StrideDJ") the same way incrementToNextRow does.
packedC1 = self.kernel["PackedC1IndicesX"]
indexChar = self.parentWriter.states.indexChars[packedC1[0]]
strideD1J = "StrideD%s" % indexChar
ws = self.kernel["WavefrontSize"]
miwg0 = self.kernel["MIWaveGroup"][0]
miwg1 = self.kernel["MIWaveGroup"][1]
matM = self.kernel["MatrixInstM"]
matN = self.kernel["MatrixInstN"]
typeStr = "fp16" if isFp16 else "bf16"
VCvtPkF32to16 = VCvtPkF32toFP16 if isFp16 else VCvtPkF32toBF16
module.addComment1(f"{typeStr} orphan subtile tt0={tt0}: pack 4 M-rows (vc=0..3) at fixed N-col, store as 2x dwordx2")
# Build per-lane vaddr:
# vaddr = (lane_id & 15) * StrideD1J * bpe [N-col]
# + vLGDelta [LG*4 M-rows = LG*8 bytes]
# + wg0*MT0*bpe [M-WG base]
# + waveId0*waveM_stride*bpe [M-wave offset, if miwg0>1]
# + waveId1*waveN_stride*StrideD1J*bpe [N-wave offset, if miwg1>1]
# The SRD already encodes wg1*MT1*StrideD1J*bpe (N-WG offset).
tmpS = self.tmpS01
mt0bpe = self.kernel["MacroTile0"] * bpe
module.addComment1("compute per-lane orphan vaddr = N_col_off + LG_M_off + wg0_M_off [+ wave offsets]")
# N-col byte offset: (lane_id & 15) * StrideD1J * bpe
module.add(VAndB32(dst=vgpr(vPack+2), src0=15, src1=vgpr("Serial"),
comment="col_in_wave = lane_id & 15 (N-column index)"))
module.add(VMulLOU32(dst=vgpr(vPack+3), src0=vgpr(vPack+2), src1=sgpr(strideD1J),
comment="col_in_wave * StrideD1J"))
if bpe == 2:
module.add(VLShiftLeftB32(dst=vgpr(vPack+2), shiftHex=1, src=vgpr(vPack+3),
comment="N_col_off = col_in_wave * StrideD1J * 2"))
else:
module.add(VMulLOU32(dst=vgpr(vPack+2), src0=vgpr(vPack+3), src1=bpe,
comment="N_col_off = col_in_wave * StrideD1J * bpe"))
# Add LG M-row offset: vLGDelta = LG*4*bpe = LG*8 bytes (pre-computed at batch start)
module.add(VAddU32(dst=vgpr(vPack+2), src0=vgpr(vPack+2), src1=vgpr(vLGDelta),
comment="vaddr += LG_M_off (= vLGDelta = LG*4*bpe)"))
# Add M-WG offset: wg0 * MT0 * bpe
module.add(SMulI32(dst=sgpr(tmpS), src0=sgpr("WorkGroup0"), src1=mt0bpe,
comment="wg0_M_off = WorkGroup0 * MT0 * bpe"))
module.add(VAddU32(dst=vgpr(vPack+2), src0=vgpr(vPack+2), src1=sgpr(tmpS),
comment="vaddr += wg0_M_off"))
# Add M-wave offset: waveId0 * MIWaveTile[0] * matM * bpe.
if miwg0 > 1:
wsLog2 = int(log2(ws))
waveM_stride_bpe = self.kernel["MIWaveTile"][0] * matM * bpe
module.add(VLShiftRightB32(dst=vgpr(vPack+3), shiftHex=wsLog2, src=vgpr("Serial"),
comment=f"waveId = Serial >> {wsLog2}"))
if miwg0 & (miwg0 - 1) == 0: # power of 2 — use AND mask
module.add(VAndB32(dst=vgpr(vPack+3), src0=miwg0 - 1, src1=vgpr(vPack+3),
comment=f"waveId0 = waveId & {miwg0-1}"))
else:
raise NotImplementedError(f"Non-power-of-2 MIWaveGroup[0]={miwg0} not supported in orphan store")
module.add(SMovB32(dst=sgpr(tmpS), src=waveM_stride_bpe,
comment=f"waveM_stride_bpe={waveM_stride_bpe}"))
module.add(VMulLOU32(dst=vgpr(vPack+3), src0=vgpr(vPack+3), src1=sgpr(tmpS),
comment=f"wave_M_off = waveId0 * {waveM_stride_bpe}"))
module.add(VAddU32(dst=vgpr(vPack+2), src0=vgpr(vPack+2), src1=vgpr(vPack+3),
comment="vaddr += wave_M_off"))
# Add N-wave offset: waveId1 * MIWaveTile[1] * matN * StrideD1J * bpe.
if miwg1 > 1:
wsLog2 = int(log2(ws))
waveN_stride_bpe = self.kernel["MIWaveTile"][1] * matN * bpe
module.add(VLShiftRightB32(dst=vgpr(vPack+3), shiftHex=wsLog2, src=vgpr("Serial"),
comment=f"waveId = Serial >> {wsLog2}"))
if miwg0 & (miwg0 - 1) == 0: # miwg0 is power of 2
module.add(VLShiftRightB32(dst=vgpr(vPack+3), shiftHex=int(log2(miwg0)),
src=vgpr(vPack+3),
comment=f"waveId1 = waveId / {miwg0}"))
else:
raise NotImplementedError(f"Non-power-of-2 MIWaveGroup[0]={miwg0} not supported in orphan store")
module.add(SMovB32(dst=sgpr(tmpS), src=waveN_stride_bpe,
comment=f"waveN_stride_bpe={waveN_stride_bpe}"))
module.add(VMulLOU32(dst=vgpr(vPack+3), src0=vgpr(vPack+3), src1=sgpr(tmpS),
comment=f"waveId1 * {waveN_stride_bpe}"))
module.add(VMulLOU32(dst=vgpr(vPack+3), src0=vgpr(vPack+3), src1=sgpr(strideD1J),
comment=f"wave_N_off = waveId1 * {waveN_stride_bpe} * StrideD1J"))
module.add(VAddU32(dst=vgpr(vPack+2), src0=vgpr(vPack+2), src1=vgpr(vPack+3),
comment="vaddr += wave_N_off"))
# Pack all 4 16bit values (consecutive M-rows at fixed N-col) into 2 dwords.
# vc=0 → M-row+0 (lo16 of dword0), vc=1 → M-row+1 (hi16 of dword0)
# vc=2 → M-row+2 (lo16 of dword1), vc=3 → M-row+3 (hi16 of dword1)
#
module.add(VCvtPkF32to16(dst=vgpr(vPack+0), src0=vc(0), src1=vc(1), comment=f"M-row+0/+1 -> {typeStr}"))
module.add(VCvtPkF32to16(dst=vgpr(vPack+1), src0=vc(2), src1=vc(3), comment=f"M-row+2/+3 -> {typeStr}"))
module.add(SNop(waitState=0, comment=f"delay after pk_{typeStr}"))
useAlign8 = self.parentWriter.states.storeAlign8
if useAlign8:
self._emitAlign8ExecMask(module, self.tmpS01, self.tmpS23, blockIdxM, blockIdxN,
mGuardOffset=1, rowScaleShift=2)
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(self.tmpS01, self.laneSGPRC), "apply exec mask"))
module.addComment1(f"buffer_store_b64: write 4 {typeStr} M-rows at fixed N-col (orphan subtile)")
module.add(BufferStoreB64(
src=vgpr(vPack+0, 2),
vaddr=vgpr(vPack+2),
saddr=sgpr("SrdD", 4),
soffset=0,
mubuf=MUBUFModifiers(offen=True, offset12=globalOffset, glc=isGlc, slc=isSlc, nt=isNT),
comment=f"orphan tt0={tt0} vc=0..3: 4 consecutive M-rows at fixed N-col"
))
if useAlign8:
module.add(self.getEdgeMovInstType()(EXEC(), -1, "restore exec"))
return module
def _emitAtomicAdd(self, module: Module):
########################################
# first attempt write
module.addComment1("issue first atomic writes")
for elementIdx in range(len(self.batchElements)):
addrCalc = self.ss.elementAddr[elementIdx]
mask = self.ss.elementMask[elementIdx]
# apply in-bounds exec mask
if self.edge:
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask, self.laneSGPRC), "sgprs -> exec (before atomic)"))
for avi in range(0, self.gwvw // self.atomicW):
sumIdxV = self.ss.elementSumIdx[elementIdx] + avi
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
if self.parentWriter.do["GlobalWrite"]:
if self.kernel["BufferStore"]:
module.add(BufferAtomicAddF32(vgpr("ValuC+%u"%newSumIdxV), \
vgpr(addrCalc.addrDVgpr,1), \
sgpr("SrdD", 4), \
0,
MUBUFModifiers(offen=True, offset12=addrCalc.globalOffset),
"attempt write avi=%u" % (avi)))
else:
pass # TODO:
if self.edge:
module.add(self.getEdgeMovInstType()(EXEC(), -1, "full mask -> exec"))
def _emitCasAdd(self, module: Module):
# TODO for atomic GWVW:
# - Use vi to compute addresses, sumIdx.
# - Need a solution for the mask. Can move to all buffer or can fix?
element = self.batchElements[0]
d1 = element[0]
d0 = element[1]
vc1 = element[2]
vc0 = element[3]
labels = self.parentWriter.labels
labelString = "Global_Write%s%s_%u_%u_%u_%u" % ("_Beta" if self.beta else "", "_Edge" if self.edge else "", vc0, vc1, d0, d1 )
labelComment = "Global_Write (Beta) (Edge) vc0 vc1 d0 d1"
label = Label(labels.getName(labelString), labelComment)
labelString += "_EarlyExit"
labelAfterAtomicLoop = Label(labels.getName(labelString), labelComment)
########################################
# wait for batched load
# TODO - we are always atomic here?
module.add(SWaitCnt(vlcnt=0, vscnt=0, comment="wait C (atomic)"))
########################################
# first attempt write
module.addComment1("issue first atomic writes")
for elementIdx, element in enumerate(self.batchElements):
addrCalc = self.ss.elementAddr[elementIdx]
mask = self.ss.elementMask[elementIdx]
# apply in-bounds exec mask
if self.edge:
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask, self.parentWriter.states.laneSGPRCount), "sgprs -> exec (before atomic)"))
for avi in range(0, self.gwvw//self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi*self.ss.cfg.numVgprsPerDataPerVI)
sumIdxV = self.ss.elementSumIdx[elementIdx] + avi
## number of src[s]/dst[s] register for DGEMM / SGEMM HGEMM
vgprCnt = 2 if self.kernel["ProblemType"]["DestDataType"].isDouble() else 1
if self.kernel["ProblemType"]["DestDataType"].numRegisters() < 1 and not self.kernel["_GlobalAccumulation"]:
sumIdxV //= 2
if self.kernel["ProblemType"]["DestDataType"].isDouble(): sumIdxV = sumIdxV * 2
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
bpm = self.parentWriter.states.bpeCexternal * self.atomicW
# Calculate vgpr Index for 32-bit/64-bit instruction
# DGEMM use SRCS[2] register
vgprIdx = 1*(bpm//4)
# for atomic, data[1] = original c, data[0] = new c
module.add(self._chooseAddForAtomic(self.kernel, \
vgpr(dataV+0,vgprCnt), vgpr(dataV+1*vgprIdx,vgprCnt), vgpr("ValuC+%u"%newSumIdxV,vgprCnt), \
"desired value avi=%u"%avi))
# attempt write
atomicDestVgpr = dataV if self.kernel["BufferStore"] else dataV+2
if self.parentWriter.do["GlobalWrite"]:
if self.kernel["BufferStore"]:
# use cmpswap_x2 for DGEMM in CAS loop
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(BufferAtomicCmpswapB64(vgpr(dataV,4), \
vgpr(addrCalc.addrDVgpr,1), \
sgpr("SrdD", 4), \
0,
MUBUFModifiers(offen=True, offset12=addrCalc.globalOffset, glc=True),
"attempt write avi=%u"%(avi)))
else:
# use cmpswap for SGEMM in CAS loop
module.add(BufferAtomicCmpswapB32(vgpr(dataV,2), \
vgpr(addrCalc.addrDVgpr,1), \
sgpr("SrdD", 4), \
0, \
MUBUFModifiers(offen=True, offset12=addrCalc.globalOffset, glc=True), \
"attempt write avi=%u"%(avi)))
else:
module.add(FlatAtomicCmpswapB32(vgpr(atomicDestVgpr), \
vgpr(addrCalc.addrDVgpr,2), \
vgpr(dataV,2),
FLATModifiers(glc=True),
"attempt write"))
else:
# Fake successful CAS swap
module.add(VMovB32(vgpr(atomicDestVgpr), vgpr(dataV+1), "Fake successful CAS" ))
########################################
# wait for first attempt write
module.add(SWaitCnt(vlcnt=0, vscnt=0, comment="wait for atomic writes"))
########################################
# check first attempt
module.addComment1("check success of writes, update masks")
for elementIdx, element in enumerate(self.batchElements):
mask = self.ss.elementMask[elementIdx]
# calculate new masks
if self.edge:
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask, self.laneSGPRC), "sgprs -> exec"))
for avi in range(0, self.gwvw // self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi * self.ss.cfg.numVgprsPerDataPerVI)
atomicDestVgpr = dataV if self.kernel["BufferStore"] else dataV+2
# need to apply element mask before comparison
# so that all valid lanes are doing the cmp
if avi == 0:
# use u64 for DGEMM
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(VCmpNeU64(sgpr(self.tmpS01, self.laneSGPRC), vgpr(atomicDestVgpr,2), \
vgpr(dataV+2,2), comment="c read during atomic == c read during prior load (avi=%u, first)"%avi))
else:
module.add(VCmpNeU32(sgpr(self.tmpS01, self.laneSGPRC), vgpr(atomicDestVgpr), \
vgpr(dataV+1), comment="c read during atomic == c read during prior load (avi=%u, first)"%avi))
else:
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(VCmpNeU64(sgpr(self.tmpS23, self.laneSGPRC), vgpr(atomicDestVgpr,2), \
vgpr(dataV+2,2), comment="c read during atomic != c read during prior load"))
else:
module.add(VCmpNeU32(sgpr(self.tmpS23, self.laneSGPRC), vgpr(atomicDestVgpr), \
vgpr(dataV+1), comment="c read during atomic == c read during prior load (avi=%u)"%avi))
module.add(self.getEdgeOrInstType()(sgpr(self.tmpS01, self.laneSGPRC), \
sgpr(self.tmpS01, self.laneSGPRC), sgpr(self.tmpS23, self.laneSGPRC), "combine with tmp mask"))
module.add(self.getEdgeAndInstType()(sgpr(mask, self.laneSGPRC), sgpr(self.tmpS01, self.laneSGPRC), sgpr(mask,self.laneSGPRC), "inBounds & must try again"))
else:
for avi in range(0, self.gwvw//self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi*self.ss.cfg.numVgprsPerDataPerVI)
atomicDestVgpr = dataV if self.kernel["BufferStore"] else dataV+2
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(VCmpNeU64(sgpr(mask, self.laneSGPRC), vgpr(atomicDestVgpr,2), \
vgpr(dataV+2,2), comment="c read during atomic != c read during prior load"))
else:
module.add(VCmpNeU32(sgpr(mask, self.laneSGPRC), vgpr(atomicDestVgpr), \
vgpr(dataV+1), comment="c read during atomic != c read during prior load"))
# or masks together to check early exit
module.addComment1("or masks to check for exit")
module.add(self.getEdgeMovInstType()(sgpr(self.tmpS01, self.laneSGPRC), 0, "empty mask"))
for elementIdx in range(0, len(self.batchElements)):
mask = self.ss.elementMask[elementIdx]
module.add(self.getEdgeOrInstType()(sgpr(self.tmpS01, self.laneSGPRC), sgpr(mask, self.laneSGPRC), sgpr(self.tmpS01, self.laneSGPRC), "or to add threads"))
module.add(self.getSOrSaveExecType()(sgpr(self.tmpS23,self.laneSGPRC), sgpr(self.tmpS01,self.laneSGPRC), "apply combined mask"))
module.add(SCBranchExecZ(labelAfterAtomicLoop.getLabelName(), "if exec is zero skip loop"))
# begin atomic loop
module.addComment1("atomic CAS loop")
module.add(label)
module.addComment1("apply updated masks and issue writes again")
for elementIdx in range(0, len(self.batchElements)):
addrCalc = self.ss.elementAddr[elementIdx]
addr = addrCalc.addrDVgpr
mask = self.ss.elementMask[elementIdx]
vgprCnt = 2 if self.kernel["ProblemType"]["DestDataType"].isDouble() else 1 # number of registers for f32/f64
bpm = self.parentWriter.states.bpeCexternal * self.atomicW
vgprIdx = 1*(bpm//4) # index register
for avi in range(0, self.gwvw//self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi*self.ss.cfg.numVgprsPerDataPerVI)
atomicDestVgpr = dataV if self.kernel["BufferStore"] else dataV+2
sumIdxV = self.ss.elementSumIdx[elementIdx] + avi
if self.kernel["ProblemType"]["DestDataType"].numRegisters() < 1 and not self.kernel["_GlobalAccumulation"]:
sumIdxV //= 2
if self.kernel["ProblemType"]["DestDataType"].isDouble():
sumIdxV = sumIdxV * 2
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
# apply mask for element
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask,self.laneSGPRC), "must try again"))
if self.kernel["ProblemType"]["DestDataType"].isDouble():
#64-bit C val move by 2 32-bit instructions
module.add(VMovB32(vgpr(dataV+2), vgpr(atomicDestVgpr), "dataV+2 = tmp (new original C)" ))
module.add(VMovB32(vgpr(dataV+3), vgpr(atomicDestVgpr+1), "dataV+3 = tmp (new original C)" ))
else:
module.add(VMovB32(dst=vgpr(dataV+1), src=vgpr(atomicDestVgpr), comment="dataV+1 = tmp (new original C)" ))
module.add(self._chooseAddForAtomic(self.kernel, \
vgpr(dataV+0,vgprCnt), vgpr(dataV+1*vgprIdx,vgprCnt), vgpr("ValuC+%u"%newSumIdxV,vgprCnt), \
"newC = rC + originalC"))
if self.parentWriter.do["GlobalWrite"]:
if self.kernel["BufferStore"]:
# Using no-ret version here?
# cmpswap_x2 for DGEMM
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(BufferAtomicCmpswapB64(vgpr(dataV,4), \
vgpr(addr,1), \
sgpr("SrdD", 4), \
0,
MUBUFModifiers(offen=True, offset12=addrCalc.globalOffset, glc=True,),
"try again"))
else:
module.add(BufferAtomicCmpswapB32(
vgpr(dataV,2), \
vgpr(addr,1), \
sgpr("SrdD", 4), \
0,
MUBUFModifiers(offen=True, offset12=addrCalc.globalOffset, glc=True),
"try again"))
else:
module.add(FlatAtomicCmpswapB32(vgpr(atomicDestVgpr), \
vgpr(addr,2), \
vgpr(dataV,2), \
FLATModifiers(glc=True), \
"try again"))
# wait for batched write
module.add(SWaitCnt(vlcnt=0, vscnt=0, comment="wait for atomic writes"))
# check batched write success
module.addComment1("apply masks and check for success")
for elementIdx in range(0, len(self.batchElements)):
data = self.ss.elementData[elementIdx]
mask = self.ss.elementMask[elementIdx]
for avi in range(0, self.gwvw//self.atomicW):
dataV = self.ss.elementData[elementIdx] + int(avi*self.ss.cfg.numVgprsPerDataPerVI)
atomicDestVgpr = dataV if self.kernel["BufferStore"] else dataV+2
# apply mask for element
module.add(self.getEdgeMovInstType()(EXEC(), sgpr(mask,self.laneSGPRC), "must try again"))
# compare success
if self.kernel["ProblemType"]["DestDataType"].isDouble():
module.add(VCmpNeU64(sgpr(self.tmpS01,self.laneSGPRC), vgpr(data+2,2), vgpr(atomicDestVgpr,2), \
comment="c read during atomic != c read during prior load"))
else:
module.add(VCmpNeU32(sgpr(self.tmpS01,self.laneSGPRC), vgpr(data+1), vgpr(atomicDestVgpr), \
comment="c read during atomic == c read during prior load"))
# update element mask
module.add(self.getEdgeAndInstType()(sgpr(mask,self.laneSGPRC), sgpr(self.tmpS01,self.laneSGPRC), sgpr(mask,self.laneSGPRC), "inBounds & must try again"))
# or masks together
module.addComment1("or masks to check for exit")
module.add(self.getEdgeMovInstType()(sgpr(self.tmpS01,self.laneSGPRC), 0, "empty mask"))
for elementIdx in range(0, len(self.batchElements)):
mask = self.ss.elementMask[elementIdx]
module.add(self.getEdgeOrInstType()(sgpr(self.tmpS01,self.laneSGPRC), sgpr(mask,self.laneSGPRC), sgpr(self.tmpS01,self.laneSGPRC), "or to add threads"))
# apply combined masks and exit
module.add(self.getSOrSaveExecType()(sgpr(self.tmpS23, self.laneSGPRC), sgpr(self.tmpS01,self.laneSGPRC), "apply combined mask"))
module.add(SCBranchExecNZ(label.getLabelName(), "try again if not complete"))
module.add(labelAfterAtomicLoop)
module.add(self.getEdgeMovInstType()(EXEC(), -1, "full mask -> exec"))
def _checkAtomicPreconditions(self) -> bool:
if self.atomic:
# all kinds of code relies on this assumption:
if self.atomicW > self.gwvw:
return False
if (self.kernel["ProblemType"]["DataType"].isHalf() or self.kernel["ProblemType"]["DataType"].isBFloat16()) \
and not self.kernel["_GlobalAccumulation"]:
return self.atomicW >= 2
return True
def _storeSyncOpt(self, module: Module):
module.add(SSleep(self.kernel["StoreSyncOpt"] - 1, "optimization: sync and wait"))
module.add(SBarrier())
def _applyAlpha(self, kernel, gwvw, elementSumIdx, elementIdx, tmpS01, usePK=False):
module = Module("applyAlpha")
if kernel["_GlobalAccumulation"] == 'MultipleBuffer':
return module
if self.parentWriter.do["ApplyAlpha"]:
for vi in range(0, gwvw):
sumIdxV = elementSumIdx[elementIdx] + vi
if kernel["ProblemType"]["ComputeDataType"].isHalf() and not kernel["ProblemType"]["HighPrecisionAccumulate"]:
# (h,h,h,h,h,h), internal alpha is f16 (2-16bits)
if sumIdxV%2:
newSumIdx = sumIdxV // 2 - self.parentWriter.states.c.startVgprValu
module.add(VMulPKF16(dst=vgpr("ValuC+%u"%(newSumIdx)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx)), comment="*= alpha sumIdx=%u vi=%u"%(elementSumIdx[elementIdx], vi)))
# Int8 (TODO- Int8x4 not checked, but should be OK)
elif kernel["ProblemType"]["ComputeDataType"].isInt32():
newSumIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# below assume we use v_mul_lo_u32. Could also use v_mul_i32_i24.
# module.add(VMulI32I24(dst=vgpr("ValuC+%u"%newSumIdx), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%newSumIdx), comment="*= alpha" )_
module.add(VMulLOU32(dst=vgpr("ValuC+%u"%newSumIdx), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%newSumIdx), comment="*= alpha" ))
if usePK:
module.add(VMulLOU32(dst=vgpr("ValuC+%u"%(newSumIdx+1)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx+1)), comment="*= alpha" ))
if self.parentWriter.db["ForceExpectedValue"]:
module.add(VMovB32(dst=vgpr("ValuC+%u"%newSumIdx), src=self.parentWriter.db["ValueCExpectedValue"], comment="force expected value" ))
if self.parentWriter.db["CheckValueC"]:
module.add(SMovB32(dst=sgpr(tmpS01), src=self.parentWriter.db["ValueCExpectedValue"], comment="Move expected value"))
module.add(self.parentWriter.getCmpAssert(self.parentWriter.asmAssert.eq, vgpr("ValuC+%u"%newSumIdx), sgpr(tmpS01)))
# sgemm, HPA-bfgemm(b,b,b,b,s,s), and HPA-hgemm(h,h,h,h,s,s)
# (h,h,h,h,h,h) + HPA (will be converted to (h,h,h,h,s,s)), internal alpha is single
elif kernel["ProblemType"]["ComputeDataType"].isSingle() or (kernel["ProblemType"]["ComputeDataType"].isHalf() and kernel["ProblemType"]["HighPrecisionAccumulate"]):
if kernel["ProblemType"]["DataType"].isInt8() and kernel["ProblemType"]["HighPrecisionAccumulate"]:
if usePK or gwvw > 1:
if vi % 2 == 0:
module.add(VCvtI32toF32(dst=vgpr("ValuC+%u"%sumIdxV), src=vgpr("ValuC+%u"%sumIdxV), comment="convert to fp32" ))
module.add(VCvtI32toF32(dst=vgpr("ValuC+%u"%(sumIdxV+1)), src=vgpr("ValuC+%u"%(sumIdxV+1)), comment="convert to fp32" ))
else:
module.add(VCvtI32toF32(dst=vgpr("ValuC+%u"%sumIdxV), src=vgpr("ValuC+%u"%sumIdxV), comment="convert to fp32" ))
newSumIdx = sumIdxV - self.parentWriter.states.c.startVgprValu
# Use pk if possible
if usePK or gwvw > 1:
if newSumIdx % 2 == 0:
module.add(VMulPKF32(dst=vgpr("ValuC+%u"%newSumIdx, 2), src0=sgpr("Alpha",2), src1=vgpr("ValuC+%u"%newSumIdx,2), vop3=VOP3PModifiers(op_sel_hi=[0,1,1]), comment="*= alpha (pk)"))
else:
module.add(VMulF32(dst=vgpr("ValuC+%u"%newSumIdx), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%newSumIdx), comment="*= alpha" ))
if self.parentWriter.db["ForceExpectedValue"]:
module.add(VMovB32(dst=vgpr("ValuC+%u"%newSumIdx), src=self.parentWriter.db["ValueCExpectedValue"], comment="force expected value" ))
if self.parentWriter.db["ForceVSerial"]:
module.add(VMovB32(dst=vgpr("ValuC+%u"%newSumIdx), src=vgpr("Serial"), comment="force expected value to serial" ))
if self.parentWriter.db["CheckValueC"]:
module.add(SMovB32(dst=sgpr(tmpS01), src=self.parentWriter.db["ValueCExpectedValue"], comment="Move expected value"))
module.add(self.parentWriter.getCmpAssert(self.parentWriter.asmAssert.eq, vgpr("ValuC+%u"%newSumIdx), sgpr(tmpS01)))
# dgemm
elif kernel["ProblemType"]["ComputeDataType"].isDouble():
newSumIdx = sumIdxV * 2 - self.parentWriter.states.c.startVgprValu
module.add(VMulF64(dst=vgpr("ValuC+%u"%(newSumIdx),2), src0=sgpr("Alpha",2), src1=vgpr("ValuC+%u"%(newSumIdx),2), comment="*= alpha"))
if usePK:
module.add(VMulF64(dst=vgpr("ValuC+%u"%(newSumIdx+2),2), src0=sgpr("Alpha",2), src1=vgpr("ValuC+%u"%(newSumIdx+2),2), comment="*= alpha"))
# single precision complex
elif kernel["ProblemType"]["ComputeDataType"].isSingleComplex():
newSumIdx = sumIdxV * 2 - self.parentWriter.states.c.startVgprValu
tmpVgpr = self.parentWriter.vgprPool.checkOut(1, tag="_applyAlpha_tmpVgpr")
module.add(VMovB32(dst=vgpr(tmpVgpr), src=vgpr("ValuC+%u"%(newSumIdx)), comment="store Cr"))
module.add(VMulF32(dst=vgpr("ValuC+%u"%(newSumIdx)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx)), comment="*= alpha ( Cr = Ar * Cr)"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdx)), src0=(sgpr("Alpha+1").getMinus()), src1=vgpr("ValuC+%u"%(newSumIdx+1)), comment="*= alpha ( Cr += -Ai * Ci )"))
module.add(VMulF32(dst=vgpr("ValuC+%u"%(newSumIdx+1)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx+1)), comment="*= alpha ( Ci = Ar * Ci)"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdx+1)), src0=sgpr("Alpha+1"), src1=vgpr(tmpVgpr), comment="*= alpha ( Ci += Ai * Cr_backup )"))
if usePK:
newSumIdx2 = newSumIdx + 2
module.add(VMovB32(dst=vgpr(tmpVgpr), src=vgpr("ValuC+%u"%(newSumIdx2)), comment="store Cr"))
module.add(VMulF32(dst=vgpr("ValuC+%u"%(newSumIdx2)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx2)), comment="*= alpha ( Cr = Ar * Cr)"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdx2)), src0=(sgpr("Alpha+1").getMinus()), src1=vgpr("ValuC+%u"%(newSumIdx2+1)), comment="*= alpha ( Cr += -Ai * Ci )"))
module.add(VMulF32(dst=vgpr("ValuC+%u"%(newSumIdx2+1)), src0=sgpr("Alpha"), src1=vgpr("ValuC+%u"%(newSumIdx2+1)), comment="*= alpha ( Ci = Ar * Ci)"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdx2+1)), src0=sgpr("Alpha+1"), src1=vgpr(tmpVgpr), comment="*= alpha ( Ci += Ai * Cr_backup )"))
self.parentWriter.vgprPool.checkIn(tmpVgpr)
# double precision complex
elif kernel["ProblemType"]["ComputeDataType"].isDoubleComplex():
newSumIdx = sumIdxV * 4 - self.parentWriter.states.c.startVgprValu
vtmp1 = self.parentWriter.vgprPool.checkOutAligned(2, 2, tag="_applyAlpha_vtmp1")
vtmp2 = self.parentWriter.vgprPool.checkOutAligned(2, 2, tag="_applyAlpha_vtmp2")
# tmp1 = a.real * b.real (t1 = Ar*Cr)
module.add(VMulF64(dst=vgpr(vtmp1,2), src0=sgpr("Alpha+0",2), src1=vgpr("ValuC+%u"%(newSumIdx+0),2)))
# tmp2 = a.imag * b.real (t2 = Ai*Cr)
module.add(VMulF64(dst=vgpr(vtmp2,2), src0=sgpr("Alpha+2",2), src1=vgpr("ValuC+%u"%(newSumIdx+0),2)))
# c.real = a.real * b.real - a.imag * b.imag = tmp1 - a.imag * b.imag (Cr = -Ai*Ci + t1).
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdx+0),2), src0=sgpr("Alpha+2",2), src1=vgpr("ValuC+%u"%(newSumIdx+2),2).getMinus(), src2=vgpr(vtmp1,2)))
# c.imag = a.real * b.imag + a.imag * b.real = a.real * b.imag + tmp2 (Ci = Ar*Ci + t2).
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdx+2),2), src0=sgpr("Alpha+0",2), src1=vgpr("ValuC+%u"%(newSumIdx+2),2), src2=vgpr(vtmp2,2)))
if usePK:
newSumIdx2 = newSumIdx + 4
# tmp1 = a.real * b.real
module.add(VMulF64(dst=vgpr(vtmp1,2), src0=sgpr("Alpha+0",2), src1=vgpr("ValuC+%u"%(newSumIdx2+0),2)))
# tmp2 = a.imag * b.real
module.add(VMulF64(dst=vgpr(vtmp2,2), src0=sgpr("Alpha+2",2), src1=vgpr("ValuC+%u"%(newSumIdx2+0),2)))
# c.real = a.real * b.real - a.imag * b.imag = tmp1 - a.imag * b.imag
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdx2+0),2), src0=sgpr("Alpha+2",2), src1=vgpr("ValuC+%u"%(newSumIdx2+2),2).getMinus(), src2=vgpr(vtmp1,2)))
# c.imag = a.real * b.imag + a.imag * b.real = a.real * b.imag + tmp2
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdx2+2),2), src0=sgpr("Alpha+0",2), src1=vgpr("ValuC+%u"%(newSumIdx2+2),2), src2=vgpr(vtmp2,2)))
self.parentWriter.vgprPool.checkIn(vtmp1)
self.parentWriter.vgprPool.checkIn(vtmp2)
return module
def _addSumAlphaWithCBeta(self, kernel, ss, gwvw, elementIdx, vc0, tmpVgpr, cvtVgprStruct):
module = Module("addSumAlphaWithCBeta #elementIdx%u, vc0 %u"%(elementIdx, vc0))
for vi in range(0, gwvw):
dataV = ss.elementData[elementIdx] + int(vi*ss.cfg.numVgprsPerDataPerVI)
sumIdxV = ss.elementSumIdx[elementIdx] + vi
if kernel["ProblemType"]["DestDataType"].isHalf():
if not kernel["ProblemType"]["HighPrecisionAccumulate"]:
if self.parentWriter.states.asmCaps["HasWMMA_V1"] and kernel["EnableMatrixInstruction"]:
dataV = ss.elementData[elementIdx] + int(vi / 2 * ss.cfg.numVgprsPerDataPerVI)
if (vi % 2) == 0:
module.add(VMulPKF16(dst=vgpr(dataV), src0=sgpr("Beta"), src1=vgpr(dataV+0), \
comment="%s = C*beta ei=%u vi=%u"%(vgpr(dataV),elementIdx, vi)))
else:
module.add(VLShiftRightB32(dst=vgpr(dataV), shiftHex=16, src=vgpr(dataV), \
comment="shift 16bit to get next half of packed ValueC"))
# dataV+0 = new c = old c*beta + rC
module.add(VAddPKF16(dst=vgpr("ValuC+%u"%(sumIdxV)), src0=vgpr(dataV), src1=vgpr("ValuC+%u"%(sumIdxV)), \
comment="sum*alpha + C*beta"))
elif sumIdxV%2==0 or (not ss.cfg.halfDataRegPerVI and gwvw==1):
newSumIdxV = sumIdxV // 2 - self.parentWriter.states.c.startVgprValu
# dataV+0 = new c = old c*beta
module.add(VMulPKF16(dst=vgpr(dataV), src0=sgpr("Beta"), src1=vgpr(dataV+0), \
comment="%s = C*beta ei=%u vi=%u"%(vgpr(dataV),elementIdx, vi)))
# dataV+0 = new c = old c*beta + rC
module.add(VAddPKF16(dst=vgpr("ValuC+%u"%(newSumIdxV)), src0=vgpr(dataV), src1=vgpr("ValuC+%u"%(newSumIdxV)), \
comment="sum*alpha + C*beta"))
else:
pass # add will have been done previously
else: # HPA
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
# dataV+0 = new c = old c*beta + rC
# src0 = beta = f32 = opsel 00
# src1 = dataV = f16.lo = opsel 10 or 11 depending on even/odd
# src2 = sumIdxV = f32 = opsel 00
dataCExternal = ss.elementData[elementIdx] + vi//2
hi16 = (vi + gwvw*vc0) % 2
module.add(self.parentWriter.states.mixinst(dst=vgpr("ValuC+%u"%newSumIdxV), src0=sgpr("Beta"), \
src1=vgpr(dataCExternal), src2=vgpr("ValuC+%u"%newSumIdxV), \
vop3=VOP3PModifiers(op_sel=[0,hi16,0], op_sel_hi=[0,1,0]),
comment="//C*=beta"))
elif kernel["ProblemType"]["DestDataType"].isBFloat16():
if kernel["ProblemType"]["HighPrecisionAccumulate"]:
# dataV+0 = new c = old c*beta + rC
# src0 = beta = f32 = opsel 00
# src1 = dataV = f16.lo = opsel 10 or 11 depending on even/odd
# src2 = sumIdxV = f32 = opsel 00
dataCExternal = ss.elementData[elementIdx] + vi//2
module.add(VCvtBF16toFP32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), vgprMask=vgpr(cvtVgprStruct.vgprBf16Mask), vi=(vi)))
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(tmpVgpr), src1=sgpr("Beta"), \
comment="finalSum = sum*alpha + C*beta"))
elif kernel["ProblemType"]["DestDataType"].isSingle():
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(dataV+0), src1=sgpr("Beta"), \
comment="finalSum = sum*alpha + C*beta"))
elif kernel["ProblemType"]["DestDataType"].isInt8():
if kernel["ProblemType"]["HighPrecisionAccumulate"]:
dataCExternal = ss.elementData[elementIdx] + (vi // 4)
byteIdx = vi % 4
if (vi%4) != 3:
module.add(VMovB32(dst=vgpr(tmpVgpr+1), src=hex(byteIdx * 8), comment="value = %u"%(byteIdx * 8)))
module.add(VBfeI32(dst=vgpr(tmpVgpr), src0=vgpr(dataCExternal), src1=vgpr(tmpVgpr+1), src2=8, comment="int8 to int32"))
else:
module.add(VAShiftRightI32(dst=vgpr(tmpVgpr), shiftHex=24, src=vgpr(dataCExternal), comment="int8 to int32"))
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
if kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(VCvtI32toF32(dst=vgpr(tmpVgpr), src=vgpr(tmpVgpr), comment="convert to fp32" ))
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(tmpVgpr), src1=sgpr("Beta"), \
comment="finalSum = sum*alpha + C*beta"))
else:
module.add(VMulLOU32(dst=vgpr(tmpVgpr), src0=sgpr("Beta"), src1=vgpr(tmpVgpr), comment="C = C*beta"))
module.add(VAddU32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(tmpVgpr), src1=vgpr("ValuC+%u"%newSumIdxV), comment="finalSum = sum*alpha + C*beta"))
elif kernel["ProblemType"]["DestDataType"].isInt32():
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
if kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(VCvtI32toF32(dst=vgpr(dataV+0), src=vgpr(dataV+0), comment="convert to fp32" ))
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(dataV+0), src1=sgpr("Beta"), comment="finalSum = sum*alpha + C*beta"))
else:
# assume we will need to replace v_mac_f32 with v_add_u32 and s_mul_lo_i32
# v_mad_i32_i24
# module.add(VMadI32I24(dst=vgpr("ValuC+%u"%sumIdxV), src0=vgpr(dataV+0), src1=sgpr("Beta"), src2=vgpr("ValuC+%u"%sumIdxV), \
# comment="finalSum = sum*alpha + C*beta"))
module.add(VMulLOU32(dst=vgpr(dataV+0), src0=sgpr("Beta"), src1=vgpr(dataV+0), comment="C = C*beta"))
module.add(VAddU32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(dataV+0), src1=vgpr("ValuC+%u"%newSumIdxV), comment="finalSum = sum*alpha + C*beta"))
elif kernel["ProblemType"]["DestDataType"].isDouble():
newSumIdxV = sumIdxV * 2 - self.parentWriter.states.c.startVgprValu
# dataV+0 = new c = old c*beta
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdxV),2), src0=vgpr(dataV+0,2), src1=sgpr("Beta",2), src2=vgpr("ValuC+%u"%(newSumIdxV),2), \
comment="finalSum = sum*alpha + C*beta"))
# single precision complex
elif kernel["ProblemType"]["DestDataType"].isSingleComplex():
newSumIdxV = sumIdxV * 2 - self.parentWriter.states.c.startVgprValu
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV)), src0=vgpr(dataV+0), src1=sgpr("Beta"), comment="finalSum Cr += old Cr * Br"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV)), src0=vgpr(dataV+1), src1=sgpr("Beta+1").getMinus(), comment="finalSum Cr += old Ci * -Bi"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV+1)), src0=vgpr(dataV+1), src1=sgpr("Beta"), comment="finalSum Ci += old Ci * Br"))
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV+1)), src0=vgpr(dataV+0), src1=sgpr("Beta+1"), comment="finalSum Ci += old Cr * Bi"))
# double precision complex
elif kernel["ProblemType"]["DestDataType"].isDoubleComplex():
newSumIdxV = sumIdxV * 4 - self.parentWriter.states.c.startVgprValu
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdxV+0),2), src0=vgpr(dataV+0,2), src1=sgpr("Beta+0",2), src2=vgpr("ValuC+%u"%(newSumIdxV+0),2), comment="c.real += a.real * b.real"))
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdxV+0),2), src0=vgpr(dataV+2,2), src1=sgpr("Beta+2",2).getMinus(), src2=vgpr("ValuC+%u"%(newSumIdxV+0),2), comment="c.real -= a.imag * b.imag"))
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdxV+2),2), src0=vgpr(dataV+0,2), src1=sgpr("Beta+2",2), src2=vgpr("ValuC+%u"%(newSumIdxV+2),2), comment="c.imag += a.real * b.imag"))
module.add(VFmaF64(dst=vgpr("ValuC+%u"%(newSumIdxV+2),2), src0=vgpr(dataV+2,2), src1=sgpr("Beta+0",2), src2=vgpr("ValuC+%u"%(newSumIdxV+2),2), comment="c.imag += a.imag * b.real"))
# float8 precision
elif kernel["ProblemType"]["DestDataType"].isAnyFloat8():
if kernel["ProblemType"]["HighPrecisionAccumulate"]:
dataCExternal = ss.elementData[elementIdx] + (vi // 4)
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single f32 code if edge is detected.
isPK = False
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
if self.parentWriter.states.archCaps["VOP3ByteSel"]:
sb = 0 if self.gwvw == 1 else 1
if not self.amdClangVersion.major >= 19:
module.add(VCvtFP8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), vop3=VOP3PModifiers(op_sel=[0,sb])))
else:
module.add(VCvtFP8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), vop3=VOP3PModifiers(byte_sel=[sb])))
else:
sb = SelectBit.BYTE_0 if self.gwvw == 1 else SelectBit.BYTE_2
module.add(VCvtFP8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), sdwa=SDWAModifiers(src0_sel=sb)))
# Original packed route
elif vi%2 == 1:
continue
else:
isPK = True
module.add(ECvtPkFP8toF32(dst=vgpr(tmpVgpr, 2), src=vgpr(dataCExternal), sel=HighBitSel.LOW if vi%4 == 0 else HighBitSel.HIGH))
module.add(SNop(waitState=0))
if kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(tmpVgpr), src1=sgpr("Beta"), comment="finalSum = sum*alpha + C*beta"))
if isPK:
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV+1)), src0=vgpr(tmpVgpr+1), src1=sgpr("Beta"), comment="finalSum = sum*alpha + C*beta (PK)"))
# bfloat8 precision
elif kernel["ProblemType"]["DestDataType"].isAnyBFloat8():
if kernel["ProblemType"]["HighPrecisionAccumulate"]:
dataCExternal = ss.elementData[elementIdx] + (vi // 4)
newSumIdxV = sumIdxV - self.parentWriter.states.c.startVgprValu
# Generate single f32 code if edge is detected.
isPK = False
if ((vi + 1) == self.gwvw) and ((self.gwvw % 2) == 1):
if self.parentWriter.states.archCaps["VOP3ByteSel"]:
sb = 0 if self.gwvw == 1 else 1
if not self.amdClangVersion.major >= 19:
module.add(VCvtBF8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), vop3=VOP3PModifiers(op_sel=[0,sb])))
else:
module.add(VCvtBF8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), vop3=VOP3PModifiers(byte_sel=[sb])))
else:
sb = SelectBit.BYTE_0 if self.gwvw == 1 else SelectBit.BYTE_2
module.add(VCvtBF8toF32(dst=vgpr(tmpVgpr), src=vgpr(dataCExternal), sdwa=SDWAModifiers(src0_sel=sb)))
# Original packed route
elif vi%2 == 1:
continue
else:
isPK = True
module.add(ECvtPkBF8toF32(dst=vgpr(tmpVgpr, 2), src=vgpr(dataCExternal), sel=HighBitSel.LOW if vi%4 == 0 else HighBitSel.HIGH))
module.add(SNop(waitState=0))
if kernel["ProblemType"]["ComputeDataType"].isSingle():
module.add(VMacF32(dst=vgpr("ValuC+%u"%newSumIdxV), src0=vgpr(tmpVgpr), src1=sgpr("Beta"), comment="finalSum = sum*alpha + C*beta"))
if isPK:
module.add(VMacF32(dst=vgpr("ValuC+%u"%(newSumIdxV+1)), src0=vgpr(tmpVgpr+1), src1=sgpr("Beta"), comment="finalSum = sum*alpha + C*beta (PK)"))
return module
def copyData(computeDataType, elementSumIdx, gwvw, vgprStart, direction=0):
module = Module("Copy Data")
vi = 0
while vi < gwvw:
sumIdxV = elementSumIdx + vi
if computeDataType.isHalf() or computeDataType.isBFloat16():
if (sumIdxV % 2 != 0):
vi += 1
continue
vgprIdx = elementSumIdx + vi // 2
if (vi + 1 < gwvw) and ((vgprStart + (vi // 2)) % 2 == 0) and (vgprIdx % 2 == 0):
module.add(VMovB64(dst=vgpr(vgprStart + (vi // 2), 2), src=vgpr(vgprIdx, 2)))
vi += 2
else:
module.add(VMovB32(dst=vgpr(vgprStart + (vi // 2)), src=vgpr(vgprIdx)))
vi += 1
elif computeDataType.isSingle() or computeDataType.isInt32():
vgprIdx = sumIdxV
if (vi + 1 < gwvw) and ((vgprStart + vi) % 2 == 0) and (vgprIdx % 2 == 0):
module.add(VMovB64(dst=vgpr(vgprStart + vi, 2), src=vgpr(vgprIdx, 2)))
vi += 2
else:
module.add(VMovB32(dst=vgpr(vgprStart + vi), src=vgpr(vgprIdx)))
vi += 1
elif computeDataType.isDouble():
vgprIdx = elementSumIdx + vi * 2
module.add(VMovB64(dst=vgpr(vgprStart + vi * 2, 2), src=vgpr(vgprIdx, 2)))
vi += 1
else:
assert 0
if direction == 1:
for i in module.items():
srcs = i.srcs
tmp = srcs[0]
srcs[0] = i.dst
i.dst = tmp
i.srcs = srcs
return module
def convertData(gwvw, elementSumIdx, cvtType: CvtType, roundType: RoundType = RoundType.ROUND_UP, inputPrefix="", prefixOffset=0):
module = Module("ConvertData")
for vi in range(0, gwvw):
sumIdxV = elementSumIdx + vi
formatVgpr = formatting(sumIdxV, inputPrefix, prefixOffset)
if cvtType == CvtType.CVT_F32_to_I32:
if roundType == RoundType.ROUND_TO_NEAREST_EVEN:
module.add(VRndneF32(dst=vgpr(formatVgpr), src=vgpr(formatVgpr), comment=" round to even"))
module.add(VCvtF32toI32(dst=vgpr(formatVgpr), src=vgpr(formatVgpr), comment=" convert fp32 to i32"))
elif cvtType == CvtType.CVT_I32_to_F32:
module.add(VCvtI32toF32(dst=vgpr(formatVgpr), src=vgpr(formatVgpr), comment=" convert to fp32"))
else:
#TODO add other convert types here.
assert 0
return module