Files
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

5021 lines
229 KiB
Python

################################################################################
#
# Copyright (C) 2016-2024 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
# copies 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
# IMPLIED, 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 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################
from .Common import assignParameterRequired, assignParameterWithDefault, \
defaultProblemType, defaultSolution, \
globalParameters, \
isGfx12, \
tPrint, printExit, printWarning, \
validActivationFormats, validConvolutionConfig, \
validMFMA, validWMMA, validParameters, validWeightFormats, \
validGEMMTypes, HPATypes, parameterNameAbbreviations
from .DataType import DataType
from .Utils import roundUpToNearestMultiple
from .KernelWriterStreamKInit import KernelWriterStreamKInit
from .KernelWriterBetaOnly import KernelWriterBetaOnly
from .KernelWriterConversion import KernelWriterConversion
from .CustomKernels import isCustomKernelConfig
from collections import namedtuple,OrderedDict
from collections.abc import Mapping
from copy import deepcopy
from enum import Enum
from functools import reduce
import collections
import math
import operator
import sys
########################################
# Print a reject message :
def reject(state, *args):
if state and "NoReject" in state and state["NoReject"]:
return
if globalParameters["PrintSolutionRejectionReason"]:
sys.stdout.write("\nreject: ")
for a in args:
print(a)
#traceback.print_stack(None, 2)
solutionIndex = state["SolutionIndex"] if (state != None and "SolutionIndex" in state) else -1
if solutionIndex != -1:
# If we have valid solutionIndex, this means we are during TensileCreateLibrary stage
# In this stage, all solutions in the logic should be valid
# So if any rejection happens, print the warning for further check
# This will be done only when --global-parameters=PrintSolutionRejectionReason=True
solutionNameMin = state["SolutionNameMin"] if ("SolutionNameMin" in state) else None
# if we don't have SolutionNameMin, we simply use the problemTypeName
solutionNameMin = str(state["ProblemType"]) if (solutionNameMin == None) else solutionNameMin
print("!! Warning: Any rejection of a LibraryLogic is not expected, please check. \
SolutionIndex: %d (or SolutionName/ProblemType: %s)"%(solutionIndex, solutionNameMin))
if state != None:
state["Valid"] = False
def disablePreloadKernelArguments(state):
state["PreloadKernelArguments"] = 0
if "DelayRemainingArguments" in state:
state["DelayRemainingArguments"] = False
# print a labled variable
def pvar(state, field):
return field + "=" + str(state[field])
def roundupRatio(dividend, divisor):
return int(math.ceil(float(dividend) / float(divisor)))
class Fbs(Enum):
Free=0 # Expect to be free dimension
Batch=1 # Expect to be batch dimension
Sum=2 # Expect to be summation dimension
################################################################################
RegDim=namedtuple("RegDim", ["idx", "fbs", "dim"])
class ConvolutionConfig:
def __init__(self, fil=None, stride=None, dilation=None, \
spatial=None, groupCount=1, \
padStart=None, padEnd=None):
self.fil = fil
self.stride = stride
self.dilation = dilation
self.spatial = spatial
self.groupCount = groupCount
self.padStart = padStart
self.padEnd = padEnd
@staticmethod
def copyField(tag, selfValues, refValues):
""" Use selfValues if specified (after validating they match cc), or conv config values if not"""
if selfValues:
if refValues:
assert(len(selfValues) == len(refValues))
for (i,(selfVal, refVal)) in enumerate(zip(selfValues, refValues)):
if selfVal == -1:
selfValues[i] = refVal
if selfVal != -1 and refVal != -1 and refVal != selfVal:
raise RuntimeError("Mismatch between ConvolutionConfig value (%d) and ConvProblem value (%d) for %s[%d]." %
(refVal, selfVal, tag, i))
return selfValues
else:
return refValues
def copyFromRef(self, ref):
"""
For all fields which are -1 in self, copy from reference implementation.
For any fields that are specified in self (not -1), ensure they match reference
"""
self.fil = self.copyField("filter", self.fil, ref.fil)
self.stride = self.copyField("stride", self.stride, ref.stride)
self.dilation = self.copyField("dilation", self.dilation, ref.dilation)
self.spatial = self.copyField("spatial", self.spatial, ref.spatial)
self.padStart = self.copyField("padStart", self.padStart, ref.padStart)
self.padEnd = self.copyField("padEnd", self.padEnd, ref.padEnd)
if self.groupCount == -1:
self.groupCount = ref.groupCount
def checkFullySpecified(self, ref):
"""
Throw exception if the config is not fully specified.
"""
for field in ('fil', 'stride', 'dilation', 'spatial', 'groupCount', 'padStart', 'padEnd'):
val = getattr(self,field)
if val==None:
raise RuntimeError("ConvolutionConfig field '%s' == None and must be specified'" % field)
elif isinstance(val,int) and val==-1 or type(val) in (tuple,list) and -1 in val:
raise RuntimeError("ConvolutionConfig field '%s' == %s must be fully specified.'" % (field,val))
def __str__ (self):
return("filter:%s stride:%s dilation:%s spatial:%s group:%d padStart:%s padEnd:%s" \
% (str(self.fil) if self.fil else "tbd",
self.stride, self.dilation, \
str(self.spatial) if self.spatial else "tbd", \
self.groupCount, self.padStart, self.padEnd))
class Convolution:
class Dimension:
"""
A description of the dimension - short char, usage, and const strides
Dimensions are later assigned tensile indices and assigned to A/B
based on the desired formats.
"""
# stride=-1 indicates TBD stride; >=0 indicates a compile-time constant
def __init__(self, shortChar, description, size=-1, strideA=-1, strideB=-1):
self.shortChar = shortChar
self.description = description
self.size=size
self.strideA=strideA
self.strideB=strideB
def __str__(self):
s = "%5s : %s" % ("'%s'"%self.shortChar, self.description)
if self.size != -1:
s+=" [size:%d]"%self.size
if self.strideA != -1:
s+=" [strideA:%d]"%self.strideA
if self.strideB != -1:
s+=" [strideB:%d]"%self.strideB
return s
def __repr__(self):
return self.shortChar
SummaryProblemProperties=[\
'OperationType','DestDataType','DataType','HighPrecisionAccumulate',\
'TensorAFormat','TensorBFormat','TensorDFormat',\
'Filter', 'Stride','Dilation','PadStart','PadEnd','GroupCount',\
'NumIndicesC', 'IndexAssignmentsA','IndexAssignmentsB',\
'IndicesFree', 'IndicesBatch', 'IndicesSummation',\
'SetConstStrideA', 'SetConstStrideB', 'ZeroPadA', \
'UseBeta', 'UseInitialStridesAB', "AllowNoFreeDims", \
]
SummarySolutionProperties=[\
'AssertSizeEqual', 'AssertStrideAEqual', 'AssertStrideBEqual',\
'AssertSizeGreaterThan', 'AssertSizeLessThan', "AssertSizeMultiple"
]
# valid lowest filter dimensions, these we can attach compile-time constant strides:
ValidLowestFilterDim= ('X','XY', 'XYZ', 'W', 'HW', 'DHW')
def initForwardConvolution(self, problemTypeOut, config, \
formatA, formatB, formatD,
ndim, cdim, kdim, sdims, fdims, \
fil, stride, dilation):
"""
Output : registerA and registerB
"""
# Make index assignments following standard Tensile Index assignment rules (see Common.py)
# - Indices < NumCindices are batch or free indices and are present in TensorD
# - Indices >= NumCindices are summation indices. cidx is cin / summation so must be after nidx
# - Memory order for TensorD is NumCindices...0, with 0 the fastest-moving dim.
# The index assignment is captured in the RegDim.idx parm. This idx is in global space.
# Specific assignments to A and B (and associated impact on memory order of those tensors) is
# specified by order of parms to registerA and registerB below.
# Control output space dimension order:S
# Note:
# - 'C' in output format refers to output channels, ie 'K'
# - backward-weight formats swap (C,K)
# - fastest moving in format is rightmost and should have idx=0, then
# increase index moving left through the format.
#print ("formatA=", formatA, "formatB=", formatB, "formatD=", formatD)
self.normalizedCC = ConvolutionConfig(fil=fil, stride=stride, dilation=dilation, \
spatial=None)
if formatD in ('NCHW','NCDHW'):
sidx = 0
i = len(sdims)
kidx = i ; i+=1
nidx = i ; i+=1
sumIdx = i
elif formatD in ('NHWC','NDHWC'):
i = 0
kidx = i ; i+=1
sidx = i ; i+=len(sdims)
nidx = i ; i+=1
sumIdx = i
elif formatD in ("CNHW", "CNDHW"):
sidx = 0
# re-order batch dim to control memory order in output space
i = len(sdims)
nidx = i ; i+=1
kidx = i ; i+=1
sumIdx = i
elif formatD in ("CHWN", "CDHWN"):
i = 0
nidx = i ; i+=1
sidx = i ; i+=len(sdims)
kidx = i ; i+=1
sumIdx = i
else:
raise RuntimeError ("unknown formatD '%s'"%formatD)
if not self.unrollOnChannel:
# place cidx at lowest summation then filters -> filters are unroll
cidx = sumIdx
sumIdx = sumIdx+1
self.filterRegDims = []
for filterDim in fdims:
self.filterRegDims.append( RegDim(sumIdx, Fbs.Sum, filterDim) )
sumIdx = sumIdx+1
if self.unrollOnChannel:
# place cidx at highest summation, after filters
cidx = sumIdx
self.spatialRegDims = []
# reverse dims so can pass spatialRegDims to register functions in 'convolution' order
for si,sdim in enumerate(sdims):
self.spatialRegDims.insert(0, RegDim(sidx+si, Fbs.Free, sdim))
chinRegDim = [RegDim(cidx,Fbs.Sum,cdim)]
choutRegDim= [RegDim(kidx,Fbs.Free,kdim)]
if formatA in ("NCHW", "NCDHW"):
self.registerA( [RegDim(nidx,Fbs.Batch,ndim)] + chinRegDim + self.spatialRegDims + self.filterRegDims )
elif formatA in ("NHWC", "NDHWC"):
self.registerA( [RegDim(nidx,Fbs.Batch,ndim)] + self.spatialRegDims + self.filterRegDims + chinRegDim )
elif formatA in ("CNHW", "CNDHW"):
self.registerA( chinRegDim + [RegDim(nidx,Fbs.Batch,ndim)] + self.spatialRegDims + self.filterRegDims )
elif formatA in ("CHWN", "CDHWN"):
self.registerA( chinRegDim + self.spatialRegDims + self.filterRegDims + [RegDim(nidx,Fbs.Batch,ndim)] )
else:
raise RuntimeError ("unknown formatA '%s'"%formatA)
ndim.strideB = 0
if formatB in ("KCYX",'KCZYX') :
self.registerB( [RegDim(nidx,Fbs.Batch,ndim)] + choutRegDim + chinRegDim + self.filterRegDims )
elif formatB in ("CKYX",'CKZYX'):
self.registerB( [RegDim(nidx,Fbs.Batch,ndim)] + chinRegDim + choutRegDim + self.filterRegDims )
elif formatB in ("CYXK",'CZYXK'):
self.registerB( [RegDim(nidx,Fbs.Batch,ndim)] + chinRegDim + self.filterRegDims + choutRegDim )
elif formatB in ("KYXC",'KZYXC'):
self.registerB( [RegDim(nidx,Fbs.Batch,ndim)] + choutRegDim + self.filterRegDims + chinRegDim )
else:
raise RuntimeError ("unknown formatB '%s'"%formatB)
problemTypeOut["NumIndicesC"] = 2+len(self.spatialRegDims)
problemTypeOut["ZeroPadA"] = self.makeZeroPadConvProblemType(self.cc.padStart, self.cc.padEnd)
# Attach constant strides to A, if possible:
nonFilterDims = [dim for dim in self.regDimsA if dim not in self.filterRegDims]
setStride=False
if sdims and nonFilterDims[-1].dim == sdims[0]:
setStride = True
sdims[0].strideA = self.cc.stride[0]
elif nonFilterDims[-1].dim==cdim:
setStride = True
cdim.strideA = 1
elif nonFilterDims[-1].dim==ndim:
setStride = True
ndim.strideA = 1
if self.filterRegDims and self.regDimsA[-1].dim == self.filterRegDims[-1].dim:
if self.filterRegDims[-1].dim.shortChar in self.ValidLowestFilterDim:
self.filterRegDims[-1].dim.strideA = self.cc.dilation[0]
elif not setStride:
raise RuntimeError ("unexpected lowest dimension in tensorAFormat(%s)"%self.tensorAFormat)
# Attach constant strides to B, if possible:
if self.regDimsB[-1].dim == cdim:
cdim.strideB=1
elif self.regDimsB[-1].dim == kdim:
kdim.strideB=1
elif self.filterRegDims and self.regDimsB[-1].dim == self.filterRegDims[-1].dim:
if self.filterRegDims[-1].dim.shortChar in self.ValidLowestFilterDim:
self.filterRegDims[-1].dim.strideB = 1
else:
raise RuntimeError ("unexpected lowest dimension in tensorBFormat(%s)"%self.tensorAFormat)
@staticmethod
def swap(targetStr, replStr1, replStr2):
tmp = '$'
assert(tmp not in targetStr)
for (char1,char2) in zip(replStr1,replStr2):
if char1==',':
continue
targetStr = targetStr.replace(char1, tmp).replace(char2, char1).replace(tmp, char2)
return targetStr
def makeZeroPadConvProblemType(self, padStart, padEnd):
"""
Convert padStart/padEnd into the format expected by ProblemType ZeroPad*
Tensile drops any compile-time padding info here; this must be provided with each problem.
"""
rv = []
spatialChars='WHD'
filterChars='XYZ'
for i in range(self.numSpatialDims):
if padStart[i] or padEnd[i]:
anchorIdx = self.convolutionDims[spatialChars[i]].idx
sumIdx = self.convolutionDims[filterChars[i]].idx
rv.append([anchorIdx, sumIdx, -1, -1])
return rv
def makeZeroPadProblemType(self, zps, padStart, padEnd, c, cc):
""" Convert padStart/padEnd into the format expected by ProblemType ZeroPad* """
rv = []
ss = c if self.regDimsA[0].dim.shortChar == 'C' else 1
for (i,zp) in enumerate(zps):
(anchorIdx, sumIdx) = zp[:2]
rv.append([anchorIdx, sumIdx, padStart[i]*ss, padEnd[i]*ss])
assert(cc.spatial[i] != -1)
ss *= cc.spatial[i]
return rv
def __init__(self, problemTypeOut, convolutionType, config):
"""
problemTypeOut contains problem type parms created by this constructor.
"""
self.convolutionDims={};
self.convolutionType = convolutionType
self.config = config # input configuration
self.problemTypeOut = problemTypeOut
self.cc = ConvolutionConfig() # parsed configuration
for k in config:
if k not in validConvolutionConfig:
raise RuntimeError ("unknown convolution config field '%s'"%k)
self.tensorAFormat = config.get("TensorAFormat", "NCHW")
assert self.tensorAFormat in validActivationFormats
self.formatNumSpatialDims = len(self.tensorAFormat)-2
assert (self.formatNumSpatialDims>=2 and self.formatNumSpatialDims<=3)
if convolutionType in ('ConvolutionForward', 'ConvolutionBackwardData'):
defaultFormatB = "KCYX" if self.formatNumSpatialDims==2 else 'KCZYX'
defaultFormatD = self.tensorAFormat
elif convolutionType == 'ConvolutionBackwardWeights':
defaultFormatB = "NCHW" if self.formatNumSpatialDims==2 else 'NCDHW'
defaultFormatD = "KCYX" if self.formatNumSpatialDims==2 else 'KCZYX'
self.tensorBFormat = config.get("TensorBFormat", defaultFormatB)
self.tensorDFormat = config.get("TensorDFormat", defaultFormatD)
if convolutionType in ('ConvolutionForward', 'ConvolutionBackwardData'):
assert self.tensorBFormat in validWeightFormats
assert self.tensorDFormat in validActivationFormats
elif convolutionType in ('ConvolutionBackwardWeights'):
assert self.tensorBFormat in validActivationFormats
assert self.tensorDFormat in validWeightFormats
if self.tensorDFormat == 0:
self.tensorDFormat = self.tensorAFormat
assert len(self.tensorAFormat) == len(self.tensorBFormat) == len(self.tensorDFormat)
# index 0,1,2 = W,H,D = X,Y,Z
if config.get("Spatial",None):
self.cc.spatial = self.dimxParm(config, "Spatial",-1)
else:
self.cc.spatial = None
self.cc.fil = self.dimxParm(config, "Filter",1)
self.cc.stride = self.dimxParm(config, "Stride",1)
self.cc.dilation = self.dimxParm(config, "Dilation",1)
self.cc.padStart = self.dimxParm(config, "PadStart",0)
self.cc.padEnd = self.dimxParm(config, "PadEnd",0)
self.packedSpatialDims = config.get("PackedSpatialDims", 1)
self.packedFilterDims = config.get("PackedFilterDims", 1)
self.unrollOnChannel = config.get("UnrollOnChannel", 1)
assert(type(self.packedFilterDims) == int)
assert(type(self.packedSpatialDims) == int)
assert(type(self.unrollOnChannel) == int)
if not all(i==1 for i in self.cc.dilation[1:]) and not all (i==1 for i in self.cc.fil) :
self.packedFilterDims = 0
if not (\
all(i==1 for i in self.cc.stride[1:]) and \
all(i==0 for i in self.cc.padStart) and \
all(i==0 for i in self.cc.padEnd) \
):
self.packedSpatialDims = 0
assert (len(self.cc.fil)==len(self.cc.stride)==len(self.cc.dilation) \
==len(self.cc.padStart)==len(self.cc.padEnd))
self.groupCount = config.get("GroupCount", 1)
self.indexAssignments = []
# Index assignment have fastest-moving first
ndim = Convolution.Dimension('N', 'Minibatch dimension. size#T=N.')
kdim = Convolution.Dimension('K', 'Cout. size#T=Cout.')
cdim = Convolution.Dimension('C', 'Cin. size#T=Cin.')
if self.packedSpatialDims:
if self.formatNumSpatialDims==2:
sdims = [Convolution.Dimension('HW', \
'Spatially packed HW. size#T=H_o*W_o. strideA#T=strideW(#S0).')]
elif self.formatNumSpatialDims==3:
sdims = [Convolution.Dimension('DHW', \
'Spatially packed DHW. size#T=D_o*H_o*W_o. strideA#T=strideW(#S0).')]
else:
raise RuntimeError ("unsupported formatNumSpatialDims")
else:
sdims = []
schars = [1,'W','H','D']
# sdims[0] is W
for si in range(self.formatNumSpatialDims):
sc=schars[si+1]
if si==0:
strideMsg = "stride%s(#S0)"%sc
else:
strideMsg = "%s_in*stride%s(#S%d)"%(schars[si],sc,si)
sdims.append(Convolution.Dimension(sc, \
'Spatial %s. size#T=%s_o strideA#T=%s.'%(sc,sc,strideMsg)))
# dims actually used in the tensor.
self.numSpatialDims = len(sdims)
if self.packedSpatialDims:
assert (self.numSpatialDims <= self.formatNumSpatialDims)
else:
assert (self.numSpatialDims == self.formatNumSpatialDims)
fdims = []
for (rfi,filterValue) in enumerate(self.cc.fil[::-1]):
if not self.packedFilterDims or filterValue != 1:
fi = self.formatNumSpatialDims - rfi - 1 # forward filter index, 0...
filterChar = chr(ord('X')+fi)
filterValueStr = "TBD" if filterValue==-1 else str(filterValue)
prevChar = ['1', 'W', 'W*H']
# TODO - stride setconst maybe applies only for NCHW/CNHW format not NHWC
# can modify message here based on format or position of indices?
filterMsg = "Filter%s. size#T=Filter%s(%s). strideA#T=Dilation%s(#D%d)*%s." \
% (filterChar, filterChar, filterValueStr, filterChar, fi, \
prevChar[fi])
fdims.append(Convolution.Dimension(filterChar, filterMsg, size=filterValue))
# Create summation dimensions for non-unit filters and assign summation indices
assert(len(self.cc.fil)) == self.formatNumSpatialDims
# Output format: C->K -> doesn't matter
# what about if both C and K present in output (ie weights) CK
if convolutionType in ("ConvolutionForward"):
self.initForwardConvolution(problemTypeOut, config, \
self.tensorAFormat, self.tensorBFormat, self.tensorDFormat,
ndim=ndim, cdim=cdim, kdim=kdim, sdims=sdims, fdims=fdims, \
fil=self.cc.fil, stride=self.cc.stride, dilation=self.cc.dilation)
elif convolutionType in ("ConvolutionBackwardData"):
# swaps cdim and kdim
formatB=self.swap(self.tensorBFormat, 'C', 'K')
self.initForwardConvolution(problemTypeOut, config, \
self.tensorAFormat, formatB, self.tensorDFormat,
ndim=ndim, cdim=kdim, kdim=cdim, sdims=sdims, fdims=fdims, \
fil=self.cc.fil, stride=self.cc.dilation, dilation=self.cc.stride)
elif convolutionType in ("ConvolutionBackwardWeights"):
# swaps ndim and cdim; filter and spatial
formatA=self.swap(self.tensorBFormat, 'C', 'N')
# convert activation->weight format, ie NCHW->KCYX
formatB=self.swap(self.tensorBFormat, 'WHD', 'XYZ').replace('C','K').replace('N','C')
formatD=self.swap(self.tensorDFormat, 'C,K,XYZ', 'N,C,WHD') # ie CKYX -> NCHW
self.initForwardConvolution(problemTypeOut, config, \
formatA, formatB, formatD,
ndim=cdim, cdim=ndim, kdim=kdim, sdims=list(reversed(fdims)), \
fdims=list(reversed(sdims)), \
fil=self.cc.spatial, stride=self.cc.dilation, \
dilation=self.cc.stride)
# fdims (filter dims) become the free dims for backward-weights.
# if these dims have size==1 and stride==default they may be collapsed to empty list.
# set AllowNoFreeDims to tell Tensile to use the batch dim as a virtual free dims
# this forces PackBatchDims and sets Index* appropriately.
if not fdims:
problemTypeOut["AllowNoFreeDims"] = True
# convert from convolution order to tensor order:
self.regDimsA.reverse()
self.regDimsB.reverse()
problemTypeOut["IndexAssignmentsA"] = [x[0] for x in self.regDimsA]
problemTypeOut["IndexAssignmentsB"] = [x[0] for x in self.regDimsB]
problemTypeOut["UseBeta"] = False # MI kernels don't use beta
self.solutionParms = {}
stridea=[]
for (idx,fbs,dim) in self.regDimsA:
if dim.strideA != -1:
stridea.append([idx, dim.strideA])
stridea.sort()
self.solutionParms["AssertStrideAEqual"] = \
{problemTypeOut["IndexAssignmentsA"].index(s[0]) : s[1] for s in stridea}
problemTypeOut["SetConstStrideA"] = stridea
strideb=[]
for (idx,fbs,dim) in self.regDimsB:
if dim.strideB != -1:
strideb.append([idx,dim.strideB])
strideb.sort()
self.solutionParms["AssertStrideBEqual"] = \
{problemTypeOut["IndexAssignmentsB"].index(s[0]) : s[1] for s in strideb}
problemTypeOut["SetConstStrideB"] = strideb
self.solutionParms["AssertSizeEqual"] = {regDim.idx:regDim.dim.size for regDim in self.indexAssignments if regDim.dim.size != -1}
if self.solutionParms["AssertStrideAEqual"].get(0,-1) == 1 and \
self.solutionParms["AssertStrideBEqual"].get(0,-1) == 1:
# optimize if no initial stride needed in A or B
# allow yaml to override this for testing UseInitialStridesAB
if "UseInitialStridesAB" not in problemTypeOut:
problemTypeOut["UseInitialStridesAB"] = False
else:
problemTypeOut["UseInitialStridesAB"] = True
iaa = problemTypeOut["IndexAssignmentsA"]
iab = problemTypeOut["IndexAssignmentsB"]
allIndices = list(range(len(self.indexAssignments)))
self.sumIndices = list(range(problemTypeOut["NumIndicesC"], len(self.indexAssignments)))
self.batchIndices = [idx for idx in allIndices \
if idx in iaa and idx in iab and idx not in self.sumIndices]
self.freeIndices = [idx for idx in allIndices \
if idx not in self.sumIndices and idx not in self.batchIndices]
self.checkDims(self.freeIndices, self.batchIndices, self.sumIndices)
def dimIdx(self, convolutionChar):
return self.convolutionDims[convolutionChar].idx
def convolutionChar(self, dimIdx):
return self.indexAssignments[dimIdx].dim.shortChar
def markedConvolutionChar(self, dimIdx, tc):
regDim = self.indexAssignments[dimIdx]
assert tc in ('A','B')
if tc=='A' and regDim.fbs==Fbs.Sum and regDim.dim.shortChar not in ['C','K','N']:
return "_" + regDim.dim.shortChar
else:
return regDim.dim.shortChar
def makeProblem(self, n, c, k, pcc):
"""
Generate valid problem dims for specified convolution
pcc is a ConvolutionConfig class with specified values for this problem.
The function will attempt to initialize TBD values in pcc from the convolution base class,
and will then check to ensure the problem is fully specified.
Return [ [sizes], [stridesA] ]
"""
numDims = 1 + max(max([x[0] for x in self.regDimsA]), max([x[0] for x in self.regDimsB]))
sizes = [-1]*numDims
astrides = [-1]*numDims
bstrides = [-1]*numDims
pcc.copyFromRef(self.cc)
pcc.checkFullySpecified(self.cc)
sizes[self.convolutionDims['N'].idx]=n
sizes[self.convolutionDims['C'].idx]=c
sizes[self.convolutionDims['K'].idx]=k
xIndexOfA = [index for index in range(len(self.regDimsA)) if self.regDimsA[index].dim.shortChar == 'W']
cIndexOfA = [index for index in range(len(self.regDimsA)) if self.regDimsA[index].dim.shortChar == 'C']
if xIndexOfA < cIndexOfA:
astrides[self.convolutionDims['C'].idx] = reduce((lambda x, y: x * y), pcc.spatial)
astrides[self.convolutionDims['N'].idx] = reduce((lambda x, y: x * y), pcc.spatial) * c
bstrides[self.convolutionDims['N'].idx] = 0 # broadcast b matrix
if len(pcc.spatial) != self.formatNumSpatialDims:
raise RuntimeError ("len(pcc.spatial=", pcc.spatial, ") must match formatNumSpatialDims(%d)"%self.formatNumSpatialDims)
# convert to Output dimensions:
spatialOut=[0]*len(pcc.spatial)
for i in range(self.formatNumSpatialDims):
spatialOut[i] = int((pcc.spatial[i] + pcc.padStart[i] + pcc.padEnd[i] - ((pcc.fil[i]-1) * pcc.dilation[i] + 1)) / pcc.stride[i]) + 1
#print ("spatialOut=", spatialOut, "padStart=", pcc.padStart, "padEnd=", pcc.padEnd)
cScalar = c if self.regDimsA[0].dim.shortChar == 'C' else 1
for fi,filterValue in enumerate(pcc.fil):
try:
pos = self.convolutionDims[chr(ord('X')+fi)].idx
sizes[pos] = filterValue
astrides[pos] = pcc.dilation[0]*cScalar if fi==0 else pcc.spatial[fi-1]*pcc.dilation[fi]*cScalar
except KeyError:
None
if self.numSpatialDims==1:
spatialName="DHW"[3-self.formatNumSpatialDims:]
pos=self.convolutionDims[spatialName].idx
sizes[pos] = reduce((lambda x, y: x * y), spatialOut) # product of all spatial dimes
astrides[pos] = pcc.stride[0]*cScalar
else:
for si,sout in enumerate(spatialOut):
spatialChars=['W','H','D']
pos = self.convolutionDims[spatialChars[si]].idx
sizes[pos] = sout
astrides[pos]=pcc.stride[0]*cScalar if si==0 else pcc.spatial[si-1]*pcc.stride[si]*cScalar
assert all(i!=-1 for i in sizes)
# translate to strides for A tensor in IndexAssignmentsA order:
orderedStridesA = []
orderedStridesB = []
for (idx,fbs,dim) in self.regDimsA:
orderedStridesA.append(astrides[idx])
for (idx,fbs,dim) in self.regDimsB:
orderedStridesB.append(bstrides[idx])
#print("ordered=A", orderedStridesA, "b=", orderedStridesB)
return (sizes, orderedStridesA, orderedStridesB)
def registerA(self, regDimList):
"""
Provide a list of indices in convolution order - these will be reversed when assigned to IndexAssignmentsAB
The order of items in the list determines the IndexAssignment order.
Each tuple in the list is a RegDim class.
- idx is the tensor index
- fbs indicates if the tensor is expected to be Free, Sum, or Batch. This is used for later check.
- dim is Convolution.Dimension class that describes the dimension (for Usage info)
"""
for regDim in regDimList:
try:
self.indexAssignments[regDim.idx]
except IndexError:
self.indexAssignments.extend([None]*(1+regDim.idx-len(self.indexAssignments)))
assert(self.indexAssignments[regDim.idx] == None or \
self.indexAssignments[regDim.idx] == regDim)
self.indexAssignments[regDim.idx] = regDim
self.convolutionDims[regDim.dim.shortChar] = regDim
self.regDimsA = regDimList
def registerB(self, regDimList):
"""
See registerA
"""
for regDim in regDimList:
try:
self.indexAssignments[regDim.idx]
except IndexError:
self.indexAssignments.extend([None]*(1+regDim.idx-len(self.indexAssignments)))
assert(self.indexAssignments[regDim.idx] == None or \
self.indexAssignments[regDim.idx] == regDim)
self.indexAssignments[regDim.idx] = regDim
self.convolutionDims[regDim.dim.shortChar] = regDim
self.regDimsB = regDimList
def dimxParm(self, config, parmName, default):
parm =config.get(parmName)
if not parm:
rv = [default ] * self.formatNumSpatialDims
else:
rv=[]
for x in parm.split('x'):
if x.upper()=='N':
rv.append(-1)
else:
rv.append(int(x))
rv.reverse() # rightmost number is 0
if len(rv) != self.formatNumSpatialDims:
raise RuntimeError ("%s parm '%s' must have %d spatial dims'"%(parmName, parm, self.formatNumSpatialDims))
return rv
def printUsage(self, problemType, details=False):
print()
print("Tensor Formats: A:%s B:%s D:%s\n" % (self.tensorAFormat, self.tensorBFormat, self.tensorDFormat))
print("Input Conv: %s packedFilter:%d packedSpatiol:%d unrollOnChannel:%d\n" % \
(str(self.cc), self.packedFilterDims, self.packedSpatialDims, \
self.unrollOnChannel))
print("Normalized Conv: %s unrollOnChannel:%d\n" % (str(self.normalizedCC), self.unrollOnChannel))
print("Tensile Index Assignments and Usage:")
print(" Tensile : ConvChar: Explanation/Usage")
for (idx,regDim) in enumerate(self.indexAssignments):
tensileChar = globalParameters['IndexChars'][idx]
usage = str(regDim.dim)
usage = usage.replace('#T', tensileChar)
for i in range(len(self.cc.stride)):
usage = usage.replace('#S%d'%i, str(self.cc.stride[i]) if self.cc.stride[i]>=0 else 'TBD')
for i in range(len(self.cc.dilation)):
usage = usage.replace('#D%d'%i, str(self.cc.dilation[i]) if self.cc.dilation[i]>=0 else 'TBD')
print(" %d('%c') %-5s: %s" % (idx, tensileChar, str(regDim.fbs).split('.')[1], usage))
if 0:
print ()
print (" FreeIndices:", ','.join([str(x) for x in self.freeIndices]))
print (" BatchIndices:", ','.join([str(x) for x in self.batchIndices]))
print (" SumIndices:", ','.join([str(x) for x in self.sumIndices]))
if details:
print ()
print ("- Spatial sizes D_i, H_i, W_i refer to size of INPUT dimension.")
print ("- Spatial sizes D_o, H_o, W_o refer to size of OUTPUT dimension.")
print (" For example W_o = (W_i - X - padStart - padEnd + 1)/stride")
print ("- (TBD)' indicates the parm is flexible and must be specified at runtime.")
print ("- (i)' where i is an integer constant, indicates the parm is hard-coded at compile time.")
print (" The runtime value must match the compile-time value.")
print ("- Unspecified strides use default stride value:")
print (" stride[i] = (stride[i-1]*size[i]) for i>0 ; 1 for i==0.")
print ("- [stride*,size*] in brackets list required values to run the generated solutions.")
print ("- Tensile IndexAssignments list the fastest-moving (in memory) index first.")
print ("- Dimension collapsing:")
print (" - spatial dims with default strides and no zero-pad are collapsed with adjacent dims.")
print (" - Nx1 filter with dilationY=1 collapse into a single filter.")
print (" - 1xN filter with dilationX=1 collapse into a single filter.")
print (" - PackSpatialDims=0 / PackFilterDims=0 forcibly disables collapsing.")
print ("- Overlapping / Hidden summation dimensions shown below with leading '_'.")
print ()
if problemType:
print ("ProblemType Definition:")
for k in Convolution.SummaryProblemProperties:
try:
if k in ['IndexAssignmentsA', 'IndexAssignmentsB']:
comment = "# [" + ",".join([self.markedConvolutionChar(idx,k[-1]) for idx in problemType[k]]) + "]"
elif k == 'NumIndicesC':
comment = "# [" + ",".join([self.convolutionChar(idx) for idx in range(0,problemType[k])] ) + "]"
else:
comment = ""
print (" ", k, ":", problemType[k], comment)
except KeyError:
pass
print ()
print ("Solution Assertions:")
for k in Convolution.SummarySolutionProperties:
try:
print (" ", k, ":", self.solutionParms[k])
except KeyError:
pass
def checkDims(self, freeIndices, batchIndices, sumIndices):
for dimList in (self.regDimsA, self.regDimsB):
for (idx,fbs,dim) in dimList:
if fbs==Fbs.Free and idx not in freeIndices:
raise RuntimeError ("dimension %d('%s') expected to be free dimension" % (idx, dim.shortChar))
elif fbs==Fbs.Batch and idx not in batchIndices:
raise RuntimeError ("dimension %d('%s') expected to be batch dimension" % (idx, dim.shortChar))
elif fbs==Fbs.Sum and idx not in sumIndices:
raise RuntimeError ("dimension %d('%s') expected to be summation dimension" % (idx, dim.shortChar))
def identifier(self, problem = None):
if problem == None:
id = self.convolutionType
id += "_" + self.tensorAFormat
id += "_" + self.tensorBFormat
id += "_" + self.tensorDFormat
id += "_spatialDims:" + str(self.numSpatialDims)
id += "_indices:" + '.'.join([x.dim.shortChar for x in self.indexAssignments])
else:
id = ''
problemCC = problem.convConfig
id += ",".join([str(x) for x in problemCC.spatial])
id += "," + ",".join([str(x) for x in problemCC.fil])
id += "," + ",".join([str(x) for x in problemCC.stride])
id += "," + ",".join([str(x) for x in problemCC.dilation])
id += "," + ",".join([str(x) for x in problemCC.padStart])
id += "," + ",".join([str(x) for x in problemCC.padEnd])
return id
################################################################################
# ProblemType
# name of solution should begin with name of problemType, and arguments can be listed out explicitly
class ProblemType(Mapping):
########################################
def __init__(self, config):
self.state = {}
for key in defaultProblemType:
assignParameterWithDefault(self.state, key, config, defaultProblemType)
# adjusting all data types
if "DataType" in config:
self["DataType"] = DataType(config["DataType"])
else:
printExit("NO data type specified")
self["DataType"] = DataType(0)
if "DestDataType" in config:
self["DestDataType"] = DataType(config["DestDataType"])
else:
if "DataType" in config:
self["DestDataType"] = DataType(config["DataType"])
else:
printExit("NO dest data type or data type specified")
self["DataType"] = DataType(0)
if "ComputeDataType" in config:
self["ComputeDataType"] = DataType(config["ComputeDataType"])
else:
if "DestDataType" in config:
self["ComputeDataType"] = DataType(config["DestDataType"])
else:
if "DataType" in config:
self["ComputeDataType"] = DataType(config["DataType"])
else:
printExit("NO compute data type, or dest data type, or data type specified")
self["DataType"] = DataType(0)
if "F32XdlMathOp" in config:
self["F32XdlMathOp"] = DataType(config["F32XdlMathOp"])
else:
self["F32XdlMathOp"] = DataType(0)
# Modifying ComputeDataType for HHH+HPA: if (HHH+HPA), convert it to HHS_BH by setting ComputeDataType to S.
if self["ComputeDataType"].isHalf() and self["DataType"].isHalf() and self["HighPrecisionAccumulate"]:
printWarning("Inconsistent DataTypes: DataType == f16, DestType == f16, ComputeDataType == f16, but HPA == True (HHH+HPA, no such a type); Converting HHH+HPA to HHS_BH by setting compute data type to f32.")
self["ComputeDataType"] = DataType('s')
# Modifying ComputeDataType for BBB+HPA: if (BBB+HPA), convert it to BBS_BH by setting ComputeDataType to S.
if self["ComputeDataType"].isBFloat16() and self["DataType"].isBFloat16() and self["HighPrecisionAccumulate"]:
printWarning("Inconsistent DataTypes: DataType == bf16, DestType == bf16, ComputeDataType == bf16, but HPA == True (BBB+HPA, no such a type); Converting BBB+HPA to BBS_BH by setting compute data type to f32.")
self["ComputeDataType"] = DataType('s')
self.convolution = None
if self["OperationType"] == "GEMM":
self.checkIfSupportedGEMMType()
self.initGEMM()
elif self["OperationType"] == "TensorContraction":
self.initTensorContraction(self.state)
elif self["OperationType"] in ("ConvolutionForward", "ConvolutionBackwardData", "ConvolutionBackwardWeights"):
self.initConvolution(config, self["OperationType"])
else:
printExit("Unsupported OperationType = %s" % self["OperationType"])
self.state["AssignedDerivedParameters"] = False
ProblemType.assignDerivedParameters(self.state)
if self.convolution:
if globalParameters["PrintConvolutionUsage"] & 0x3 :
print()
self.convolution.printUsage(self, globalParameters["PrintConvolutionUsage"]&0x2)
print()
self.convolution.checkDims(self.state["IndicesFree"], self.state["IndicesBatch"], self.state["IndicesSummation"])
for tc in ('A', 'B'):
freeDims={}
sumDims={}
for zp in self["ZeroPad%s"%tc] :
(freeDim, sumDim, leading, trailing) = zp
if freeDim not in self.state["IndicesFree"]:
printExit("ZeroPad%s=%s dim=%u is not a free index"%(tc, zp, freeDim))
if freeDim not in self.state["IndexAssignments%s"%tc]:
printExit("ZeroPad%s=%s dim=%u is not in IndexAssignments%s"%(tc, zp, freeDim, tc))
if sumDim not in self.state["IndicesSummation"]:
printExit("ZeroPad%s=%s dim=%u is not a summation index"%(tc, zp, sumDim))
if freeDim in freeDims:
printExit("ZeroPad%s=%s freeDim=%u occurs in more than one tuple (prev:%s)"%(tc, zp, freeDim,freeDims[freeDim]))
freeDims[freeDim] = zp
if sumDim in sumDims:
printExit("ZeroPad%s=%s sumDim=%u occurs in more than one tuple"%(tc, zp, sumDim))
sumDims[sumDim] = zp
for tc in ('A', 'B'):
for sc in self["SetConstStride%s"%tc] :
(anchorDim, stride) = sc[:2]
if anchorDim not in self.state["IndexAssignments%s"%tc]:
printExit("SetConstStride%s=%s anchorDim=%u is not in IndexAssignments%s"%(tc, sc, anchorDim, tc))
################################################################################
# Function checkIfSupportedGEMMType:
# Assures 3 data-types are valid, supported and well-assigned
# See the discussion on Common.py for validGEMMTypes
################################################################################
def checkIfSupportedGEMMType(self):
inType = self["DataType"]
outType = self["DestDataType"]
computeType = self["ComputeDataType"]
gemmType = ( inType.toChar(), outType.toChar(), computeType.toChar() )
if gemmType not in validGEMMTypes:
printExit("This typed-GEMM (Ti, To, Tc) = (%s, %s, %s) is not supported yet."%(gemmType[0],gemmType[1],gemmType[2]))
########################################
def initGEMM(self):
sumIdx = 3 if self["Batched"] else 2
self["IndexAssignmentsA"] = [0, sumIdx] # N
self["IndexAssignmentsB"] = [sumIdx, 1] # N
if self["TransposeA"]:
self["IndexAssignmentsA"] = [sumIdx, 0] # T
if self["TransposeB"]:
self["IndexAssignmentsB"] = [1, sumIdx] # T
if self["Batched"]:
self["IndexAssignmentsA"].append(2)
self["IndexAssignmentsB"].append(2)
self["NumIndicesC"] = 3
else:
self["NumIndicesC"] = 2
self["NumIndicesLD"] = 4
self["IndexAssignmentsLD"][0] = self["NumIndicesC"] + 1
for i in range(1, len(self["IndexAssignmentsLD"])):
self["IndexAssignmentsLD"][i] = self["IndexAssignmentsLD"][i-1] + 1
########################################
def initTensorContraction(self, config):
assignParameterRequired(self.state, "NumIndicesC", config)
assignParameterRequired(self.state, "IndexAssignmentsA", config)
assignParameterRequired(self.state, "IndexAssignmentsB", config)
self["NumIndicesLD"] = 0
########################################
def initConvolution(self, config, convolutionType):
convolutionConfig = {}
try:
if config['ConvolutionConfig'] != None:
for dict in config['ConvolutionConfig']:
for k,v in dict.items():
convolutionConfig[k] = v
except KeyError:
raise RuntimeError ("OperationType %s must include ConvolutionConfig section in ProblemType"%convolutionType)
self.convolution = Convolution(self, convolutionType, convolutionConfig)
self["NumIndicesLD"] = 0
# For Conv with filter 1x1, unit-stride, no padding case, we can let UseBeta = True
self["UseBeta"] = ("UseBeta" in config and config["UseBeta"] == True) and (self.canExpressedAsGEMM() == True)
########################################
def canExpressedAsGEMM(self):
rv = self.convolution != None and \
self.convolution.cc != None and \
self.convolution.cc.fil == [1,1] and \
self.convolution.cc.stride == [1,1] and \
self.convolution.cc.dilation == [1,1] and \
self.convolution.cc.padStart == [0,0] and \
self.convolution.cc.padEnd == [0,0]
return rv
########################################
def isGEMM(self):
return self.operationType == 0
########################################
def isTensorContraction(self):
return self.operationType == 1
########################################
# determine d0, d1, dU
@staticmethod
def assignDerivedParameters(state):
if "AssignedDerivedParameters" in state:
if state["AssignedDerivedParameters"]:
return
state["AssignedDerivedParameters"] = False
state["TotalIndices"] = max(max(state["IndexAssignmentsA"])+1, \
max(state["IndexAssignmentsB"])+1)
# determine num free, batch
state["IndicesFree"] = []
state["IndicesBatch"] = []
state["IndicesSummation"] = []
for i in range(0, state["NumIndicesC"]):
inA = i in state["IndexAssignmentsA"]
inB = i in state["IndexAssignmentsB"]
if inA and inB:
state["IndicesBatch"].append(i)
elif inA or inB:
state["IndicesFree"].append(i)
else:
printExit("invalid index %u (inC but not (inA or inB))" % i)
# determine num summation
for i in range(state["NumIndicesC"], state["TotalIndices"]):
inA = i in state["IndexAssignmentsA"]
inB = i in state["IndexAssignmentsB"]
if inA and inB:
state["IndicesSummation"].append(i)
else:
printExit("invalid index %u (expected summation but not (inA and inB))" % i)
# print index assignments
if globalParameters["PrintIndexAssignments"]:
print("IndicesFree: %s" % state["IndicesFree"])
print("IndicesBatch: %s" % state["IndicesBatch"])
print("IndicesSum: %s" % state["IndicesSummation"])
print("IndexAssignmentsA: %s" % state["IndexAssignmentsA"])
print("IndexAssignmentsB: %s" % state["IndexAssignmentsB"])
print("NumIndicesC: %s" % state["NumIndicesC"])
for k in ('IndexAssignmentsA','IndexAssignmentsB'):
if len(state[k]) != len(set(state[k])):
printExit("duplicate index in %s=%s"% (k,state[k]))
state["NumIndicesFree"] = len(state["IndicesFree"])
state["NumIndicesBatch"] = len(state["IndicesBatch"])
state["NumIndicesSummation"] = len(state["IndicesSummation"])
if not state["AllowNoFreeDims"] and state["NumIndicesFree"] < 2 :
printExit("Tensile requires >= 2 free indices or set AllowNoFreeDims; FreeIndices=%s."% state["IndicesFree"])
# by default, unroll index will be the last/inner summation index
state["IndexUnroll"] = state["IndicesSummation"][len(state["IndicesSummation"])-1]
for i in range(0, len(state["IndexAssignmentsA"])):
if state["IndexAssignmentsA"][i] == state["IndexUnroll"]:
state["IndexUnrollA"] = i
break
for i in range(0, len(state["IndexAssignmentsB"])):
if state["IndexAssignmentsB"][i] == state["IndexUnroll"]:
state["IndexUnrollB"] = i
break
#tPrint(3, "IndexUnrollA: %u" % state["IndexUnrollA"])
#tPrint(3, "IndexUnrollB: %u" % state["IndexUnrollB"])
# assign d0, d1
if state["AllowNoFreeDims"]:
dimList = state["IndicesFree"] + state["IndicesBatch"]
else:
dimList = state["IndicesFree"]
state["Index01A"] = [i for i in state["IndexAssignmentsA"] if i in dimList][0]
state["Index01B"] = [i for i in state["IndexAssignmentsB"] if i in dimList][0]
#tPrint(3, "Index01A: %u" % state["Index01A"])
#tPrint(3, "Index01B: %u" % state["Index01B"])
# Store code is optimized for 0 as the fastest-moving in memory
# whichever has lower stride in C (lower value), is 0, other is 1
if state["Index01A"] < state["Index01B"]:
state["Index0"] = state["Index01A"]
state["Index1"] = state["Index01B"]
state["Tensor0"] = 0
state["Tensor1"] = 1
state["TileA"] = 0
state["TileB"] = 1
else:
state["Index0"] = state["Index01B"]
state["Index1"] = state["Index01A"]
state["Tensor0"] = 1
state["Tensor1"] = 0
state["TileA"] = 1
state["TileB"] = 0
# generalize transpose
strideIdxA = state["IndexAssignmentsA"].index(state["Index01A"])
strideIdxB = state["IndexAssignmentsB"].index(state["Index01B"])
unrollIdxA = state["IndexAssignmentsA"].index(state["IndexUnroll"])
unrollIdxB = state["IndexAssignmentsB"].index(state["IndexUnroll"])
state["TLUA"] = strideIdxA < unrollIdxA
state["TLUB"] = strideIdxB < unrollIdxB
#state["TLUB"] = True # hack
if globalParameters["PrintIndexAssignments"]:
print("TLUA: %s (stridePosA(%d) <? unrollIdxA(%d)" % \
(state["TLUA"], strideIdxA, unrollIdxA))
print("TLUB: %s (stridePosB(%d) <? unrollIdxB(%d)" % \
(state["TLUB"], strideIdxB, unrollIdxB))
print("Index01A: %s" % state["Index01A"])
print("Index01B: %s" % state["Index01B"])
#unrollDimStrideGreaterThanTileDimStrideA = TLUA = !transA = fast
#!unrollDimStrideLessThanTileDimStrideB = TLUB = transB = fast
state["AssignedDerivedParameters"] = True
########################################
def __str__(self):
indexChars = globalParameters["IndexChars"]
# C dimensions
name = "C"
for i in range(0, self["NumIndicesC"]):
name += indexChars[i].lower()
# A dimensions
name += "_A"
for i in self["IndexAssignmentsA"]:
name += indexChars[i] if i in self["MirrorDimsA"] else indexChars[i].lower()
if self["ComplexConjugateA"]:
name += "C"
# B dimensions
name += "_B"
for i in self["IndexAssignmentsB"]:
name += indexChars[i] if i in self["MirrorDimsB"] else indexChars[i].lower()
if self["ComplexConjugateB"]:
name += "C"
# DataTypes
name += "_"
name += self["DataType"].toChar() # Type of A/B
# Special condition for some newly supported kernels:
# HHS, HSS, BSS and I8II kernels, use a clearer naming _TiToTc_
# TODO: Distinguish all kernels by _TiToTc_ to be more consistent with rocblas
gemmType = (self["DataType"].toChar(),self["DestDataType"].toChar(),self["ComputeDataType"].toChar() )
if gemmType in HPATypes:
name += self["DestDataType"].toChar() # Type of C/D
name += self["ComputeDataType"].toChar() # Type of Alpha/Beta
name += "_"
# Other
if self["UseBeta"]: name += "B"
if self["HighPrecisionAccumulate"] and not self["SilentHighPrecisionAccumulate"]: name += "H"
if self["Fp16AltImpl"]:
if self["Fp16AltImplRound"]: name += "RZ"
else: name += "R"
if self["UseInitialStridesAB"]: name += "I"
if self["UseInitialStridesCD"]: name += "Ic"
# precision and other
# name += "_SB" if self["StridedBatched"] else "_GB"
name += "" if self["StridedBatched"] else "_GB" # legacy
if not self["F32XdlMathOp"].isSingle() and self["DataType"].isSingle():
name += "_M"
name += self["F32XdlMathOp"].toChar()
# Rounding mode: IEEE vs SR
if self["StochasticRounding"]: name += "_SR"
return name
def keys(self):
return list(self.state.keys())
def __len__(self):
return len(self.state)
def __iter__(self):
return iter(self.state)
def __getitem__(self, key):
return self.state[key]
def __setitem__(self, key, value):
self.state[key] = value
def __repr__(self):
return self.__str__()
def getAttributes(self):
return self.state
def __hash__(self):
return hash(str(self))
def __eq__(self, other):
return isinstance(other, ProblemType) and self.getAttributes() == other.getAttributes()
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return result
return not result
def get(self, key, default=None):
try:
return self.state[key]
except:
return default
################################################################################
# ProblemSizeRange
################################################################################
class ProblemSizeRange:
########################################
def __init__(self, problemType, config):
self.totalIndices = 1+max(problemType["IndexAssignmentsA"]) + problemType["NumIndicesLD"]
if len(config) < self.totalIndices:
for i in range(len(config), self.totalIndices):
if i < self.totalIndices - problemType["NumIndicesLD"]:
config.append(0)
else:
config.append([0])
self.indexMax = []
self.indexIsSized = []
self.indicesSized = []
self.indicesMapped = []
for i in range(0, self.totalIndices):
dim = deepcopy(config[i])
if isinstance(dim, list):
if len(dim) == 1:
self.indicesSized.append([dim[0], 1, 0, dim[0]])
elif len(dim) == 2:
self.indicesSized.append([dim[0], dim[0], 0, dim[1]])
elif len(dim) == 3:
self.indicesSized.append([dim[0], dim[1], 0, dim[2]])
elif len(dim) == 4:
self.indicesSized.append([dim[0], dim[1], dim[2], dim[3]])
else:
printExit("dimension[%u] config (%s) has %u descriptors rather than 1-4."
% ( i, dim, len(dim) ))
self.indexIsSized.append(True)
self.indexMax.append(self.indicesSized[len(self.indicesSized)-1][3])
elif isinstance(dim, int):
self.indicesMapped.append(dim)
self.indexIsSized.append(False)
self.indexMax.append(self.indicesSized[self.indicesMapped[ \
len(self.indicesMapped)-1]][3])
# max num elements in each tensor
self.maxNumElements = [ 1, 1, 1 ]
for i in range(0, problemType["NumIndicesC"]):
self.maxNumElements[0] *= self.indexMax[i]
for i in problemType["IndexAssignmentsA"]:
self.maxNumElements[1] *= self.indexMax[i]
for i in problemType["IndexAssignmentsB"]:
self.maxNumElements[2] *= self.indexMax[i]
self.totalProblemSizes = 1
self.numProblemSizes = [] # per index
self.problemSizeToIndex = []
self.problemIndexToSize = []
sizedIdx = 0
for i in range(0, len(self.indexIsSized)):
self.problemSizeToIndex.append({})
self.problemIndexToSize.append({})
if self.indexIsSized[i]:
self.numProblemSizes.append(0)
index = self.indicesSized[sizedIdx]
sizedIdx += 1
currentSize = index[0]
currentIncrement = index[1]
while currentSize <= index[3]:
currentSize += currentIncrement
currentIncrement += index[2]
self.numProblemSizes[i] += 1
else:
self.numProblemSizes.append(1)
self.totalProblemSizes *= self.numProblemSizes[i]
########################################
# enumerate problem sizes
currentSizedIndexSizes = []
currentSizedIndexIncrements = []
for i in range(0, len(self.indicesSized)):
currentSizedIndexSizes.append(self.indicesSized[i][0])
currentSizedIndexIncrements.append(self.indicesSized[i][1])
# iterate over all problem sizes
self.problemSizes = []
moreProblemSizes = True
problemIdx = 0
problemSize = [0]*self.totalIndices
while moreProblemSizes:
#/ convert current sized and mapped indices to full sizes
currentSizedIdx = 0
currentMappedIdx = 0
for i in range(0, self.totalIndices):
if self.indexIsSized[i]:
problemSize[i] = currentSizedIndexSizes[currentSizedIdx]
currentSizedIdx+=1
else:
problemSize[i] = problemSize[self.indicesMapped[currentMappedIdx]]
currentMappedIdx+=1
self.problemSizes.append(tuple(problemSize))
#/ increment sizes for next benchmark
currentSizedIndexSizes[0] += currentSizedIndexIncrements[0]
currentSizedIndexIncrements[0] += self.indicesSized[0][2]
for i in range(1, len(self.indicesSized)+1):
# if prior index past max, reset to min and increment next index
if currentSizedIndexSizes[i-1] > self.indicesSized[i-1][3]:
#/ reset prior index
currentSizedIndexSizes[i-1] = self.indicesSized[i-1][0]
currentSizedIndexIncrements[i-1] = self.indicesSized[i-1][1]
# increment next index
if i >= len(self.indicesSized):
moreProblemSizes = False
else:
currentSizedIndexSizes[i] += currentSizedIndexIncrements[i]
currentSizedIndexIncrements[i] += self.indicesSized[i][2]
problemIdx+=1
########################################
# YAML format
def __str__(self):
state = "[ "
sizedIdx = 0
mappedIdx = 0
for i in range(0, len(self.indexIsSized)):
if self.indexIsSized[i]:
indices = self.indicesSized[sizedIdx]
state += "[ %u, %u, %u, %u ]" \
% (indices[0], indices[1], indices[2], indices[3])
sizedIdx += 1
else:
indices = self.indicesSized[self.indicesMapped[mappedIdx]]
state += str(self.indicesMapped[mappedIdx])
mappedIdx += 1
if i < len(self.indexIsSized)-1:
state += ", "
state += " ]"
return state
class Problem:
""" Problem sizes, strides, padding and other info"""
def __init__(self, sizes=None, stridesA=None, stridesB=None, stridesC=None, stridesD=None, zeroPadA=None, zeroPadB=None, count=None):
self.sizes = tuple(sizes) if sizes else None
self.stridesA = tuple(stridesA) if stridesA else None
self.stridesB = tuple(stridesB) if stridesB else None
self.stridesC = tuple(stridesC) if stridesC else None
self.stridesD = tuple(stridesD) if stridesD else None
self.zeroPadA = zeroPadA
self.zeroPadB = zeroPadB
self.count = count
def __str__(self):
rv= "{ sizes:" + str(list(self.sizes))
if self.stridesA:
rv += ", stridesA:" + str(list(self.stridesA))
if self.stridesB:
rv += ", stridesB:" + str(list(self.stridesB))
if self.stridesC:
rv += ", stridesC:" + str(list(self.stridesC))
if self.stridesD:
rv += ", stridesD:" + str(list(self.stridesD))
rv += " }"
return rv
class ConvProblem(Problem):
ConvField = namedtuple ("ConvField", ('shortChar', 'descrip', 'default'))
AllowedConvFields = [ ConvField('n', 'Batch Count', None),
ConvField('c', 'Channel In', None),
ConvField('k', 'Channel Out', None),
ConvField('d', 'Spatial Depth', -1),
ConvField('h', 'Spatial Height',-1),
ConvField('w', 'Spatial Width', -1),
ConvField('z', 'Filter Z', -1),
ConvField('y', 'Filter Y', -1),
ConvField('x', 'Filter X', -1),
ConvField('#', 'Stride for Depth', -1),
ConvField('u', 'Stride for Height', -1),
ConvField('v', 'Stride for Width', -1),
ConvField('^', 'Dilation for filter Depth Z', -1),
ConvField('l', 'Dilation for filter Height Y', -1),
ConvField('j', 'Dilation for filter Width X', -1),
ConvField('$', 'Pad for Depth', -1),
ConvField('p', 'Pad for Height', -1),
ConvField('q', 'Pad for WidthX', -1),
ConvField('$_', 'Pad End for Depth (overrides $ for end)', -1),
ConvField('p_', 'Pad End for Height (overrides p for end)', -1),
ConvField('q_', 'Pad End for Width (overrides q for end)', -1),
ConvField('g', 'Group Count', 1),
ConvField('count', 'Layer execution Count', -1),
]
AllowedConfFieldsDict = {field.shortChar : field for field in AllowedConvFields}
@staticmethod
def initParm(e, chars, skipFields):
fields = []
for s in (chars):
if s not in skipFields:
fields.append(e[s])
return fields
def __init__(self, e, convolution):
self.inputConfig = deepcopy(e)
if convolution.formatNumSpatialDims==2:
skipFields = ('d', 'z', '#', '^', '$')
else:
skipFields = ()
if not isinstance(e,dict):
raise RuntimeError ("ConvProblem must be a dictionary, for example '{n: 64, ...}' not '[n: 64, ...]'")
for k in e:
if k not in ConvProblem.AllowedConfFieldsDict:
# TODO - detect and print message for common error n:32 w/o space
raise RuntimeError ("unknown ConvProblem field '%s'"%k)
for (k,field) in ConvProblem.AllowedConfFieldsDict.items():
if k not in e and k not in skipFields:
if field.default == None:
raise RuntimeError ("required ConvProblem field '%s' not present in ConvProblem:%s"%(k,e))
elif isinstance(field.default, int):
e[k] = field.default
padStart = self.initParm(e, ('q','p','$'), skipFields)
padEnd = self.initParm(e, ('q_','p_','$_'), skipFields)
padEnd = [ps if pe==-1 else pe for (ps,pe) in zip(padStart,padEnd) ] # use padStart as default
self.convConfig = ConvolutionConfig(
fil = self.initParm(e, ('x','y','z'), skipFields),
stride = self.initParm(e, ('v','u','#'), skipFields),
dilation = self.initParm(e, ('j','l','^'), skipFields),
spatial = self.initParm(e, ('w','h','d'), skipFields),
padStart = padStart,
padEnd = padEnd,
groupCount = e['g']
)
(sizes, stridesA, stridesB) = convolution.makeProblem(e['n'], e['c'], e['k'], self.convConfig)
zeroPadA = convolution.makeZeroPadProblemType(convolution.problemTypeOut["ZeroPadA"],
self.convConfig.padStart, self.convConfig.padEnd, e['c'], self.convConfig)
Problem.__init__(self, sizes, stridesA, stridesB=stridesB, zeroPadA=zeroPadA, count=e['count'])
#print ("sizes=", self.sizes, "stridesA=", self.stridesA, "stridesB=", self.stridesB, "zeroPadA=", self.zeroPadA)
def toExactDict(self):
""" Return a dict with ExactDict fields, after converting the ConvProblem to tensor sizes and strides"""
padStartA = [zp[2] for zp in self.zeroPadA]
padEndA = [zp[3] for zp in self.zeroPadA]
exactFields = OrderedDict()
exactFields['count'] = self.count
exactFields['sizes'] = list(self.sizes)
exactFields['stridesA'] = list(self.stridesA)
if padStartA:
exactFields['padStartA'] = padStartA
if padEndA:
exactFields['padEndA'] = padEndA
return exactFields
class ExactList(Problem):
def __init__(self, e, problemType):
if len(e) == problemType["TotalIndices"]:
if -1 in e:
printExit("ExactSize %s contains -1" % (e))
if problemType["OperationType"] == "GEMM":
e += [-1, -1, -1, -1]
e = ExactList.convertLeadingDims(problemType, tuple(e))
sizes=e
elif len(e) == (problemType["TotalIndices"] + problemType["NumIndicesLD"]):
sizes = ExactList.convertLeadingDims(problemType, tuple(e))
else:
printExit("ExactSize %s doesn't match indices of ProblemType %s, totalIndices=%d" \
% (e, problemType, problemType["TotalIndices"]) )
# TODO- pass strides here, remove calls to convertLeadingDims
Problem.__init__(self, sizes=sizes, zeroPadA=problemType["ZeroPadA"], zeroPadB=problemType["ZeroPadB"])
def __str__(self):
return str(list(self.sizes))
@staticmethod
def convertLeadingDims(problemType, problemSize, stridesA = None, stridesB = None, stridesC = None, stridesD = None):
# FIXME-problem: refactor to eliminate max, pass strides in strideB parm rather than hacked
# onto the end of the sizes list
predStridesD = stridesD is not None and stridesD[1] != -1
predStridesC = stridesC is not None and stridesC[1] != -1
predStridesA = stridesA is not None and stridesA[1] != -1
predStridesB = stridesB is not None and stridesB[1] != -1
return problemSize[:problemType["NumIndicesC"]+1] + \
(max(problemSize[0], problemSize[problemType["IndexAssignmentsLD"][0]]) if not predStridesD else stridesD[1], ) + \
(max(problemSize[0], problemSize[problemType["IndexAssignmentsLD"][1]]) if not predStridesC else stridesC[1], ) + \
(max(problemSize[problemType["IndexAssignmentsLD"][2]],
problemSize[problemType["IndexAssignmentsA"][0]]) if not predStridesA else stridesA[1], ) + \
(max(problemSize[problemType["IndexAssignmentsLD"][3]],
problemSize[problemType["IndexAssignmentsB"][0]]) if not predStridesB else stridesB[1], )
class ExactDict(Problem):
# padStartA is list of pad starts for A dimension in order of ZeroPadA list.
# padEndA is list of pad ends for A dimension in order of ZeroPadA list.
AllowedFields = [ 'count', 'sizes', 'stridesA', 'stridesB', 'stridesC', 'stridesD', 'padStartA', 'padEndA', 'padStartB', 'padEndB']
def __init__(self, e, problemType):
Problem.__init__(self)
for f in e:
if f in ExactDict.AllowedFields:
setattr(self, f, e[f])
else:
raise RuntimeError ("specified field '%s' is not a valid Exact dict field"%f)
if problemType:
if "OperationType" in problemType and problemType["OperationType"] == "GEMM":
sizesTuple = tuple(self.sizes + [-1, -1, -1, -1])
self.sizes = ExactList.convertLeadingDims(problemType, sizesTuple, self.stridesA, self.stridesB, self.stridesC, self.stridesD)
zp={}
zp['A'] = deepcopy(problemType["ZeroPadA"])
zp['B'] = deepcopy(problemType["ZeroPadB"])
for (tc, padName, zpField) in (
("A", "padStartA",2), ("A", "padEndA", 3),
("B", "padStartB",2), ("B", "padEndB", 3) ):
try:
problemPad = getattr(self, padName)
if len(problemPad) != len (zp[tc]):
raise RuntimeError ("problem-specified %s==%s does not match length of problem-type pad==%s." % (padName, problemPad, zp[tc]))
for (i,p) in enumerate(problemPad):
if not (zp[tc][i][zpField] == -1 or zp[tc][i][zpField] == p):
raise RuntimeError ("problem-specified %s==%d does not match problem-type==%d." % (padName, p, zp[tc][i][zpField]))
zp[tc][i][zpField] = p
except AttributeError:
None
for (tc) in ("A", "B"):
for p in zp[tc]:
if p[2] == -1 or p[3]==-1:
raise RuntimeError ("padStart/padEnd for %s must be specified in problem-type or problem - can't be left -1/TBD" % zp[tc])
self.zeroPadA = zp['A']
self.zeroPadB = zp['B']
else:
self.zeroPadA = self.zeroPadB = []
if problemType:
if "OperationType" in problemType and problemType["OperationType"] == "GEMM":
if len(self.sizes) != (problemType["TotalIndices"] + problemType["NumIndicesLD"]):
# FIXME-ExactDict size descriptor still (but preferrably not so) uses 8-tuple for GEMM problems
raise RuntimeError ("specified size=%s does not have enough indices for problem (expected %d, got %d)" \
% (self.sizes, problemType["TotalIndices"]+problemType["NumIndicesLD"], len(self.sizes)))
elif len(self.sizes) != problemType["TotalIndices"]:
raise RuntimeError ("specified size=%s does not have enough indices for problem (expected %d, got %d)" \
% (self.sizes, problemType["TotalIndices"], len(self.sizes)))
################################################################################
# ProblemSizes
################################################################################
"""
Adapter class for class `ProblemSizes`. It satisfies the implicit usage requirement
of ClientWriter.writeClientConfig() by converting ExactLogic to list of `Problem` objects
"""
class ProblemSizesMock:
def __init__(self, exactLogic):
self.problems = [Problem(problem) for problem, solution in exactLogic]
class ProblemSizes:
########################################
def __init__(self, problemType, config):
self.problemType = problemType
self.ranges = []
self.exacts = []
self.minStrides = None
if config:
for dictionary in config:
for sizeTypeKey in dictionary:
#print ("PROBLEM parsed:", sizeTypeKey, dictionary[sizeTypeKey])
if sizeTypeKey == "Range":
psr = ProblemSizeRange(problemType, dictionary[sizeTypeKey])
self.ranges.append( psr )
elif sizeTypeKey == "Exact":
e= dictionary[sizeTypeKey]
if isinstance(e,list):
self.exacts.append(ExactList(e, problemType))
elif isinstance(e,dict):
self.exacts.append(ExactDict(e, problemType))
else:
printExit("Unsupported Exact type==%s"%type(e))
elif sizeTypeKey == "Conv":
if problemType.convolution == None:
printExit("ConvProblem requires OperationType==Convolution*")
else:
self.exacts.append(ConvProblem(dictionary[sizeTypeKey], problemType.convolution))
elif sizeTypeKey == "MinStride":
e = dictionary[sizeTypeKey]
if len(e) != problemType["TotalIndices"]:
printExit("MinStride %s doesn't match indices of ProblemType %s" \
% (e, problemType) )
if self.minStrides:
printExit("Only one MinStride command is allowed in a ProblemsSizes definition. Previous minStrides:%s, New minstride:%s" \
% (self.minStrides, e) )
self.minStrides=(tuple(e))
else:
printExit("ProblemSize Type %s not supported"%sizeTypeKey)
if not self.minStrides:
# set harmless default mins of 0
self.minStrides = ([0]* problemType["TotalIndices"])
# not the ideal spot, but convert leading dims that are below the minimum size
if problemType["OperationType"] == "GEMM":
for i in range(0, len(self.ranges)):
self.ranges[i].problemSizes[:] = \
[ExactList.convertLeadingDims(self.problemType, problemSize) for problemSize in self.ranges[i].problemSizes]
self.problems = OrderedDict()
for sizeRange in self.ranges:
for rangeSize in sizeRange.problemSizes:
self.problems.update({Problem(rangeSize, zeroPadA=problemType["ZeroPadA"]) : 1 })
for e in self.exacts:
self.problems.update({e : 1})
if globalParameters["SortProblems"]:
self.problems = sorted(list( self.problems.keys()), key=operator.attrgetter("sizes"))
else:
self.problems = list(self.problems.keys())
self.totalProblemSizes = len(self.problems)
# max sizes
self.maxD = 0
self.maxC = 0
self.maxA = 0
self.maxB = 0
for problem in self.problems:
problemSize = problem.sizes # FIXME-problem. This should use problem.strides*
sizeLdd = problemSize[self.problemType["IndexAssignmentsLD"][0]] if problemType["OperationType"] == "GEMM" else problemSize[0]
sizeD = max(self.minStrides[0], sizeLdd)
for i in range(1, problemType["NumIndicesC"]):
sizeD *= max(self.minStrides[i], problemSize[i])
sizeLdc = problemSize[self.problemType["IndexAssignmentsLD"][1]] if problemType["OperationType"] == "GEMM" else problemSize[0]
sizeC = max(self.minStrides[0], sizeLdc)
for i in range(1, problemType["NumIndicesC"]):
sizeC *= max(self.minStrides[i], problemSize[i])
sizeLda = problemSize[self.problemType["IndexAssignmentsLD"][2]] \
if problemType["OperationType"] == "GEMM" \
else problemSize[self.problemType["IndexAssignmentsA"][0]]
sizeA = max(self.minStrides[self.problemType["IndexAssignmentsA"][0]], sizeLda)
for i in self.problemType["IndexAssignmentsA"][1:]:
sizeA *= max(self.minStrides[i], problemSize[i])
sizeLdb = problemSize[self.problemType["IndexAssignmentsLD"][3]] \
if problemType["OperationType"] == "GEMM" \
else problemSize[self.problemType["IndexAssignmentsB"][0]]
sizeB = max(self.minStrides[self.problemType["IndexAssignmentsB"][0]], sizeLdb)
for i in self.problemType["IndexAssignmentsB"][1:]:
sizeB *= max(self.minStrides[i], problemSize[i])
self.maxD = max(self.maxD, sizeD)
self.maxC = max(self.maxC, sizeC)
self.maxA = max(self.maxA, sizeA)
self.maxB = max(self.maxB, sizeB)
if globalParameters["PrintConvolutionUsage"] & 0x4:
for problem in self.problems:
if isinstance(problem, ConvProblem):
print (problem.inputConfig, '->\n ', ", ".join(["%s: %s"%(k,v) for (k,v) in problem.toExactDict().items()]))
def __str__(self):
s = "ProblemSizes\n"
for sizeRange in self.ranges:
s += " %s" % sizeRange
return s
# kds is class Solution or class Kernel
# If PackFreeDims=1 then all free dims are packed ; else only 1 free dim/matrix is supported
# PackBatchDims can pack batches into A or B (has stride==0 requirements for non-packed tensor);
# batchMask controls which bit in PackBatchDims detects batch index
def isPackedIndex(ks, index, batchMask=0x3):
problemType = ks["ProblemType"]
return index in problemType["IndicesFree"] and ks["PackFreeDims"] or \
index in problemType["IndicesBatch"] and (ks["PackBatchDims"] & batchMask)
def isExtractableIndex(ks, index, tc='x'):
xA = index in ks['PackedC0IndicesX'][:-1]
xB = index in ks['PackedC1IndicesX'][:-1]
if tc=='A':
return xA
elif tc=='B':
return xB
else:
return xA or xB
################################################################################
# Solution
################################################################################
class Solution(collections.abc.Mapping):
########################################
def __init__(self, config):
self._name = None
state = {}
# problem type
if "ProblemType" in config:
state["ProblemType"] = ProblemType(config["ProblemType"])
else:
state["ProblemType"] = ProblemType(defaultProblemType)
# assign parameters with defaults
for key in defaultSolution:
assignParameterWithDefault(state, key, config, defaultSolution)
if 'ISA' not in state:
if 'ISA' in config:
state['ISA'] = config['ISA']
elif config['KernelLanguage'] == 'Source':
state['ISA'] = [0,0,0]
else:
raise ValueError(f"ISA not found in state {state} or config {config}. This is required for assembly kernels.")
if "CodeObjectVersion" not in state:
if "CodeObjectVersion" in config:
state["CodeObjectVersion"] = config["CodeObjectVersion"]
else:
state["CodeObjectVersion"] = globalParameters["CodeObjectVersion"]
# assign parameters without defaults
for key in config:
if key != "ProblemType" and key not in state:
state[key] = config[key]
state["Valid"] = True
# this could prevent OriginalSolution from re-assigning the parameters, save lots of time
if "AssignedProblemIndependentDerivedParameters" not in state:
state["AssignedProblemIndependentDerivedParameters"] = False
if "AssignedDerivedParameters" not in state:
state["AssignedDerivedParameters"] = False
if state["ProblemType"].convolution:
for (key,value) in state["ProblemType"].convolution.solutionParms.items():
state[key]=value
Solution.assignDerivedParameters(state)
self._name = config["CustomKernelName"] if isCustomKernelConfig(config) else None
self._state = state
self.initHelperKernelObjects()
# these keys are copied from ProblemType to internal that may be overridden
InternalKeys = ["UseSgprForGRO","VectorStore"]
########################################
# get a list of kernel parameters for this solution
def getKernels(self):
self["Kernel"] = True
return self
########################################
# create Helper Kernels
def initHelperKernelObjects(self):
self.initStreamKInitKernelObjects()
self.initBetaOnlyKernelObjects()
self.initConversionKernelObjects()
########################################
# create StreamKInit Kernels
def initStreamKInitKernelObjects(self):
self.streamKInitKernelObjects = []
if self["StreamK"] > 0 and self["StreamKAtomic"] == 0:
state = {}
state["ProblemType"] = deepcopy(self["ProblemType"])
state["KernelLanguage"] = "Source"
state["_GlobalAccumulation"] = self["_GlobalAccumulation"]
self.streamKInitKernelObjects.append(KernelWriterStreamKInit(state))
########################################
# create BetaOnly Kernels
def initBetaOnlyKernelObjects(self):
self.betaOnlyKernelObjects = []
if self["GlobalSplitU"] > 1 or self["StreamK"] > 0 and self["StreamKAtomic"] == 1:
state = {}
state["ProblemType"] = deepcopy(self["ProblemType"])
state["KernelLanguage"] = "Source"
state["_GlobalAccumulation"] = self["_GlobalAccumulation"]
self.betaOnlyKernelObjects.append(KernelWriterBetaOnly(state))
########################################
# create Conversion Kernels
def initConversionKernelObjects(self):
self.conversionKernelObjects = []
gsu = self["GlobalSplitU"]
if (gsu > 1) and self["_GlobalAccumulation"]:
# wider load for GSU is single compute type only
supportedTypeForVWopt = self["ProblemType"]["ComputeDataType"].isSingle() or self["ProblemType"]["ComputeDataType"].isDouble()
vwMax = 1
if (supportedTypeForVWopt):
vwMax = 2
# reduction for GSU is single compute type + gus = power of 2 only
supportedTypeForReductionOpt = self["ProblemType"]["ComputeDataType"].isSingle() or self["ProblemType"]["ComputeDataType"].isDouble()
maxReduction = 1
maxReductionConst = 4 # this must match the value in client code (ContractionSolution.cpp)
minGSUperReduction = 32; # Minimum GSU=128 for Reduction=4, GSU=64 for Reduction2
applicableReduction = max(1, gsu // minGSUperReduction)
if (supportedTypeForReductionOpt and ((gsu & (gsu - 1)) == 0) and self["_GlobalAccumulation"] == "MultipleBuffer"):
maxReduction = min(applicableReduction, maxReductionConst) # not exceeding reductionThreshold
# loop unroll opt for postGSU
supportedTypeForUnrollOpt = self["ProblemType"]["ComputeDataType"].isSingle() or self["ProblemType"]["ComputeDataType"].isDouble()
vw = 1
while vw <= vwMax:
reduction = 1
while reduction <= maxReduction:
# so far, reduction=2 does not perform well. Skip 2
if reduction == 2 and self["ProblemType"]["ComputeDataType"].isSingle():
reduction *= 2
continue
state = {}
state["ProblemType"] = deepcopy(self["ProblemType"])
state["KernelLanguage"] = "Source"
state["_GlobalAccumulation"] = self["_GlobalAccumulation"]
state["GlobalSplitU"] = self["GlobalSplitU"]
state["VectorWidth"] = vw
state["Reduction"] = reduction
# number of unroll for large GSU (must match client code)
state["GSUUnrollUnit"] = 16 * state["Reduction"] if supportedTypeForUnrollOpt and self["_GlobalAccumulation"] == "MultipleBuffer" else 1
self.conversionKernelObjects.append(KernelWriterConversion(state))
reduction *= 2
vw *= 2
########################################
# get Helper Kernels
def getHelperKernelObjects(self):
return self.streamKInitKernelObjects + self.betaOnlyKernelObjects + self.conversionKernelObjects
########################################
# get Helper Kernels
def getKernelStreamKInitObjects(self):
return self.streamKInitKernelObjects
########################################
# get Helper Kernels
def getKernelBetaOnlyObjects(self):
return self.betaOnlyKernelObjects
########################################
# get Helper Kernels
def getKernelConversionObjects(self):
return self.conversionKernelObjects
@staticmethod
def getMIOutputInfo(state):
outputVectorWidth = 4
RegsPerOut = 1
isa = tuple(state["ISA"])
if globalParameters["AsmCaps"][isa]['HasMFMA']:
if state["ProblemType"]["DataType"].MIOutputTypeNameAbbrev() == 'f64':
outputVectorWidth, RegsPerOut = 1, 2
else:
outputVectorWidth, RegsPerOut = 4, 1
elif globalParameters["AsmCaps"][isa]['HasWMMA']:
outputVectorWidth, RegsPerOut = 1, 1
else:
print("WARNING: unexpected code flow")
return outputVectorWidth, RegsPerOut
########################################
# assign tile sizes
@staticmethod
def assignProblemIndependentDerivedParameters(state):
if "AssignedProblemIndependentDerivedParameters" in state:
if state["AssignedProblemIndependentDerivedParameters"]:
return
state["AssignedProblemIndependentDerivedParameters"] = False
if "Valid" not in state:
state["Valid"] = True
if (not state["ProblemType"]["StridedBatched"]) and (not state["ProblemType"]['Batched']):
reject(state, "General Batched GEMM only support Batched Problem")
if (not state["ProblemType"]["StridedBatched"]) and (state["ProblemType"]["OperationType"] != 'GEMM'):
reject(state, "General Batched GEMM only support GEMM OperationType")
EnableMatrixInstruction = state["EnableMatrixInstruction"] if "EnableMatrixInstruction" in state else None
if EnableMatrixInstruction == None:
if ("MIBlock" in state and len(state["MIBlock"]) == 6) \
and ("MIWaveGroup" in state and len(state["MIWaveGroup"]) == 2) \
and ("MIWaveTile" in state and len(state["MIWaveTile"]) == 2):
EnableMatrixInstruction = True
elif ("WorkGroup" in state and len(state["WorkGroup"]) == 3) \
and ("ThreadTile" in state and len(state["ThreadTile"]) == 2) :
EnableMatrixInstruction = False
else:
reject(state, "EnableMatrixInstruction undetermined")
if EnableMatrixInstruction == True:
state["MatrixInstM"] = state["MIBlock"][0]
state["MatrixInstN"] = state["MIBlock"][1]
state["MatrixInstK"] = state["MIBlock"][2]
state["MatrixInstB"] = state["MIBlock"][3]
state["MatrixInstBM"] = state["MIBlock"][4]
state["MatrixInstBN"] = state["MIBlock"][5]
state["MIOutputVectorWidth"], state["MIRegPerOut"] = Solution.getMIOutputInfo(state)
if state["MatrixInstM"] == 4:
state["ThreadTile0"] = state["MIWaveTile"][0] * state["MIOutputVectorWidth"]
state["ThreadTile1"] = state["MIWaveTile"][1]
state["SubGroup0"] = state["MIWaveGroup"][0] * state["MatrixInstM"] * state["MatrixInstBM"] // state["MIOutputVectorWidth"]
state["SubGroup1"] = state["MIWaveGroup"][1] * state["MatrixInstN"] * state["MatrixInstBN"]
else:
state["ThreadTile0"] = state["MatrixInstBM"] * state["MIWaveTile"][0] * (state["MatrixInstM"] * state["MatrixInstN"] // state["WavefrontSize"])
state["ThreadTile1"] = state["MatrixInstBN"] * state["MIWaveTile"][1]
state["SubGroup0"] = state["MIWaveGroup"][0] * (state["WavefrontSize"] // state["MatrixInstN"])
state["SubGroup1"] = state["MIWaveGroup"][1] * state["MatrixInstN"]
elif EnableMatrixInstruction == False:
state["ThreadTile0"] = state["ThreadTile"][0]
state["ThreadTile1"] = state["ThreadTile"][1]
state["SubGroup0"] = state["WorkGroup"][0]
state["SubGroup1"] = state["WorkGroup"][1]
state["LocalSplitU"] = state["WorkGroup"][2]
# enable MatrixInstruction store only for LSU=1
# LSU>1 case, use non-MI store instead
state["EnableMatrixInstructionStore"] = EnableMatrixInstruction and state["LocalSplitU"]==1
if "SubGroup0" in state and "SubGroup1" in state and "LocalSplitU" in state:
state["NumThreads"] = state["SubGroup0"] * state["SubGroup1"] * state["LocalSplitU"]
if (state["NumThreads"] % state['WavefrontSize']) != 0:
reject(state, f"size of WorkGroup {state['NumThreads']} should be multiple of WavefrontSize {state['WavefrontSize']}")
if EnableMatrixInstruction == True:
if ((state["SubGroup0"] * state["SubGroup1"]) % state['WavefrontSize']) != 0:
reject(state, f"SubGroup0 {state['SubGroup0']} * SubGroup1 {state['SubGroup1']}should be multiple of WavefrontSize {state['WavefrontSize']}")
if (state["LocalSplitU"] > 4):
reject(state, f"LocalSplitU {state['LocalSplitU']} should not be larger than 4 with MatrixInstruction")
# macro tile sizes
if "SubGroup0" in state and "ThreadTile0" in state:
state["MacroTile0"] = state["SubGroup0"]*state["ThreadTile0"]
if "SubGroup1" in state and "ThreadTile1" in state:
state["MacroTile1"] = state["SubGroup1"]*state["ThreadTile1"]
if "MacroTile" in state:
if state["MacroTile0"] != state["MacroTile"][0] \
or state["MacroTile1"] != state["MacroTile"][1]:
reject(state, "MacroTile mismatch")
if state["Valid"] and "MacroTileShapeMax" in state \
and "MacroTileShapeMin" in state:
macroTileShape = max(state["MacroTile0"]//state["MacroTile1"], \
state["MacroTile1"]//state["MacroTile0"])
if macroTileShape > state["MacroTileShapeMax"] \
or macroTileShape < state["MacroTileShapeMin"]:
reject(state, "rejecting MacroTile Shape %u:%u for Min:Max %u:%u" \
% (state["MacroTile0"], state["MacroTile1"], \
state["MacroTileShapeMin"], state["MacroTileShapeMax"]))
if "WorkGroupMappingType" in state:
if state["WorkGroupMappingType"] == "Z":
if abs(state["WorkGroupMapping"]) > 2:
reject(state, "WorkGroupMappingType=Z only supports WorkGroupMapping=1, 2")
# done
state["AssignedProblemIndependentDerivedParameters"] = True
########################################
# This is the "classic" algorithm which requires that each threads load the same number of bytes
# Called with tc=A and then with tc=B
# totalVectors is totalElements/GRVW, this is #vectors loaded by the LoadTile
# Reduces the GlobalLoadVectorWidth if necessary if each thread has a small amount of work to do.
# Output from this function:
# state[GlobalLoadVectorWidth*]
# state[NumLoads*] # only used in SolutionStructs, with classic alg
@staticmethod
def setGlobalLoadVectorWidth(state, tc, totalElements, grvw):
validDepthU = True
numThreadGrvw = int(state["NumThreads"] * grvw)
if totalElements < numThreadGrvw:
# Try to reduce size of vector so every thread has a load to do
pv = numThreadGrvw //totalElements
if not state["FractionalLoad"]:
if numThreadGrvw % totalElements != 0:
reject(None, "(NumThreads * grvw) %u %% totalElements %u != 0" \
% (numThreadGrvw, totalElements))
validDepthU = False
if pv * totalElements != numThreadGrvw:
reject(None, "pv %u * totalElements %u != (NumThreads * grvw) %u " \
% (pv, totalElements, numThreadGrvw))
validDepthU = False
if grvw < 1 or grvw % pv != 0:
reject(None, "GlobalLoadVectorWidth%s %u %% pv %u != 0" \
% (tc, grvw, pv))
validDepthU = False
grvw = grvw//pv
numThreadGrvw = numThreadGrvw//pv
else:
pv = 1 # no partial vector required
if totalElements % numThreadGrvw != 0:
if not state["FractionalLoad"]:
reject(None, "totalElements %u %% (NumThreads * grvw) %u != 0" \
% (totalElements, numThreadGrvw))
validDepthU = False
state["GlobalLoadVectorWidth%s"%tc] = grvw
# NumLoads is NOT used on the fractional path
# NumLoads is number of vector loads per-thread
state["NumLoads%s"%tc] = totalElements // numThreadGrvw
#print "result: ", pvar(state, "GlobalLoadVectorWidth%s"%tc), \
# pvar(state, "NumLoads%s"%tc)
return validDepthU
########################################
# Sets the Global Read Tile dims (para, perp)
# This information controls which threads read which addresses from global mem)
# Output from this function:
# state[NumLoadsCoalescedA]
# state[NumLoadsPerpendicularA]
# state[LSCA]
# state[LSPA]
@staticmethod
def setGlobalLoadTileDimClassic(state, tc, numLoads, totalVectorsCoalesced, totalElementsPerp):
if state["WaveSeparateGlobalRead%s"%tc]:
totalElementsPerp = roundupRatio(totalElementsPerp, state["NumThreads"] // state["WavefrontSize"])
# nlc = 1
if state["NumLoadsCoalesced%s"%tc] == 1 :
foundValid = False
nlcStart = 1
if state["DirectToVgpr%s"%tc]:
# adjust nlc for DirectToVgpr
if state["ProblemType"]["TLU%s"%tc]:
nlcStart = roundupRatio(state["MIWaveTile%s"%tc], state["GlobalLoadVectorWidth%s"%tc])
else:
nlcStart = roundupRatio(state["DepthU"], state["MatrixInstK"] * state["GlobalLoadVectorWidth%s"%tc] * state["LocalSplitU"] // state["MIInputPerThread"])
for nlc in range(nlcStart, int(state["NumLoads%s"%tc]+1)):
nlp = state["NumLoads%s"%tc] // nlc
if state["NumLoads%s"%tc] % nlc == 0 \
and totalVectorsCoalesced % nlc == 0 \
and totalElementsPerp % nlp == 0:
state["NumLoadsCoalesced%s"%tc] = nlc
state["NumLoadsPerpendicular%s"%tc] = nlp
#print("NumLoadsCoalesced",state["NumLoadsCoalesced%s"%tc])
#print("NumLoadsPerpendicular",state["NumLoadsPerpendicular%s"%tc])
foundValid = True
break
if not foundValid:
reject(state, "%s: No NumLoadsCoalesced=1 found"%tc)
return False
# nlc = -1
elif state["NumLoadsCoalesced%s"%tc] == -1:
foundValid = False
for nlc in range(state["NumLoads%s"%tc], 0, -1):
nlp = state["NumLoads%s"%tc] // nlc
if state["NumLoads%s"%tc] % nlc == 0 \
and totalVectorsCoalesced % nlc == 0 \
and totalElementsPerp % nlp == 0:
state["NumLoadsCoalesced%s"%tc] = nlc
state["NumLoadsPerpendicular%s"%tc] = nlp
foundValid = True
break
if not foundValid:
reject(state, "%s: No NumLoadsCoalesced=-1 found"%tc)
return False
# nlc = other
else:
if state["NumLoadsCoalesced%s"%tc] > state["NumLoads%s"%tc]:
reject(state, "%s nlc > numLoads"%tc)
return False
state["NumLoadsPerpendicular%s"%tc] = state["NumLoads%s"%tc] \
// state["NumLoadsCoalesced%s"%tc]
if state["NumLoads%s"%tc] % state["NumLoadsCoalesced%s"%tc] != 0:
reject(state, "%s: numLoads %u %% numLoadsCoalesced %u != 0" \
% (tc, state["NumLoads%s"%tc], state["NumLoadsCoalesced%s"%tc]))
return False
if totalVectorsCoalesced % state["NumLoadsCoalesced%s"%tc] != 0 :
reject(state, "%s: totalVectorsCoalesced %u %% numLoadsPara %u != 0" \
% (tc, totalVectorsCoalesced, state["NumLoadsCoalesced%s"%tc]))
return False
if totalElementsPerp % state["NumLoadsPerpendicular%s"%tc] != 0:
reject(state, "%s: totalElementsPerp %u %% numLoadsPerp %u != 0" \
% (tc, totalElementsPerp, state["NumLoadsPerpendicular%s"%tc]))
return False
if state["ProblemType"]["TLU%s"%tc]:
state["LSC%s"%tc] = state["MacroTile%s"%tc] // state["NumLoadsCoalesced%s"%tc]
state["LSP%s"%tc] = int(math.ceil(float(state["DepthU"]) / state["NumLoadsPerpendicular%s"%tc]))
else:
state["LSC%s"%tc] = int(math.ceil(float(state["DepthU"]) / state["NumLoadsCoalesced%s"%tc]))
state["LSP%s"%tc] = state["MacroTile%s"%tc] // state["NumLoadsPerpendicular%s"%tc]
if state["WaveSeparateGlobalRead%s"%tc]:
state["LSP%s"%tc] = roundupRatio(state["LSP%s"%tc], state["NumThreads"] // state["WavefrontSize"])
return True
########################################
# Sets the Global Read Tile dims (para, perp)
# This information controls which threads read which addresses from global mem)
# Output from this function:
# state[NumLoadsCoalesced*]
# state[NumLoadsPerpendicular*]
# state[LSC*]
# state[LSP*]
# state[GlobalLoadVectorWidthA/B]
#
# LSC and LSP define the shape of the PerLoadTile, measured in elements.
# LSC*LSP is the elements loaded by a single instruction across all
# threads in the group.
# LSC is the number of elements loaded in the para(coalesced) dimension
# LSP is the number of elements loaded in the perp(noncoalesced) dimension
# PerLoadTile is always rectangular.
# When BufferLoad=1, the area (LSC*LSP) can be larger than NumThreads.
# In this case, some threads will generate a dummy OOB GRO.
# Related fields:
# LVC = LSC/GRVW (LVCA = LSCA/GLVWA)
# LVP = LSP/GRVW (LVPA = LSPA/GLVWA)
#
# NumLoadsCoalesced and NumLoadsPerpendicular define the number of times the
# PerLoadTile is loaded in each dimension to fetch the LoadTile
# LoadTile = (LSC * NumLoadsCoalesced) * (LSP * NumLoadsPerpendicular).
# For Fractional, the LoadTile can be larger than the MacroTile. Buffer
# loads will clip any OOB references to 0 and will also avoid writing these
# into LDS.
# Fractional load algorithm:
# - Each load instruction loads one or more (complete) rows of the load tile.
# - Each row is LSC elements wide
# - Rows are complete and do not wrap. This allows a single base GRO VGPR
# to be used for all loads in the tile.
# - Some work-items in the load may not perform useful work. These WI will
# set their GRO to a large OOB number so as to do no harm
# - Some G2L registers space may be unused as well.
# - The 'used' message at the bottom of this routine computes and prints the
# wasted register space.
# - The wasted space is removed when the data is written to LDS- the LWO
# for work-items beyond the valid ones are set to safely write to OOB locations.
# - In cases where each load is loading multiple rows (multiple lines of lsc
# elements), the last load is allowed to load fewer lines than the others.
# The KernelWriterAssembly will modify the LWO for the last load. This allows
# flexibility in the unroll factors for example.
@staticmethod
def setGlobalLoadTileDimFractional(state, tc, depthU, glvwOrig):
isa = tuple(state["ISA"])
assert(depthU > 0)
dbFract = 0
# parDim, perpDim define the LoadTile and are measured in elements
if state["ProblemType"]["TLU%s"%tc]:
parDim = state["MacroTile%s"%tc]
perpDim = depthU
else:
parDim = depthU
perpDim = state["MacroTile%s"%tc]
if dbFract:
print("\ninfo: %s Fractional MT%u_%u_%u Par=%u Perp=%u WG%02u_%02u_%02u NumThreads=%u GRWV=%u" \
% (tc, state["MacroTile0"], state["MacroTile1"], depthU, \
parDim, perpDim, \
state["WorkGroup"][0], state["WorkGroup"][1], state["LocalSplitU"], \
state["NumThreads"], glvwOrig))
# Try to find a GRVW which is smaller than the LSC and also does not force
# the LSC to wrap - both of these conditions can be tested with lsc % grvw ==0.
# Each iteration divides GRWV by 2 which provides finer granularity
# and a possible opportunity to handle the lsc
grvw = glvwOrig
minGrvw = 2 if state["ProblemType"]["DataType"].isHalf() and \
globalParameters["ArchCaps"][isa]["HasEccHalf"] else 1
# TODO- check this for int8 and fractional load
# minGrvw = 4 if state["ProblemType"]["DataType"].isInt8() and \
# globalParameters["ArchCaps"][isa]["HasEccHalf"] else 1
bestVw = -1
while grvw >= minGrvw:
# Per instruction across the entire group:
elementsLoadedPerInst = state["NumThreads"]*grvw
mik = 1
if (state["DirectToVgpr%s"%tc] and state["ProblemType"]["TLU%s"%tc]):
mik = state["MatrixInstK"] * state["LocalSplitU"] // state["MIInputPerThread"]
elementsLoadedPerInst //= mik
# LSC, LSP - #elements loaded along specified dim with each load
if parDim >= elementsLoadedPerInst:
# entire work-group can work on (part) of the same row
# DirectToVgpr case, LSC is limited to elementsLoadedPerInst // (state["MatrixInstK"] * state["LocalSplitU"])
state["LSC%s"%tc] = elementsLoadedPerInst
state["LSP%s"%tc] = mik
state["NumLoadsCoalesced%s"%tc] = roundupRatio(parDim , state["LSC%s"%tc])
state["NumLoadsPerpendicular%s"%tc] = 1
else:
# work-group exceeds read dimension so wraps to multiple rows
state["LSC%s"%tc] = parDim
state["LSP%s"%tc] = min(perpDim, elementsLoadedPerInst // parDim)
state["NumLoadsCoalesced%s"%tc] = 1
state["NumLoadsPerpendicular%s"%tc] = roundupRatio(perpDim , state["LSP%s"%tc])
# Vector loads can't wrap to next P dim, so LSC must be divisible by vector elements;
if dbFract:
print(" lsc search : lsc(%u) %% grvw(%u) = %u (?0)" % (state["LSC%s"%tc], grvw, state["LSC%s"%tc] % grvw))
if state["LSC%s"%tc] % grvw == 0:
bestVw = grvw
# Try to shrink GRVW if possible while keeping same LSC and LSP:
# For example, avoid cases where we use a GRVW=4 with many empty addresses
# when a GRVW=1 will do instead.
validElementsLoadedPerInst = state["LSC%s"%tc] * state["LSP%s"%tc]
grvw //= 2
while grvw >= minGrvw:
elementsLoadedPerInst = state["NumThreads"]*grvw//mik
if elementsLoadedPerInst < validElementsLoadedPerInst:
break # Went too far, not enough load elements at this VW
if state["LSC%s"%tc] % grvw == 0:
if dbFract:
print(" stepdown success (valid)elementsLoadedPerInst=", validElementsLoadedPerInst, "/", elementsLoadedPerInst, "grvw=", grvw, "lsc=", state["LSC%s"%tc])
bestVw = grvw
grvw //= 2
break
# TODO - could have this generate dwordx3 loads in addition, step down by 1 instead of div2
# Would need to change asm code gen to generate x3
grvw //= 2
# end-- while loop
if bestVw == -1:
if dbFract:
print ("reject fractional - no acceptable tile dim? GlobalLoadVectorWidthA/B", \
glvwOrig)
return False # could not find a solution, perhaps only possible for half ?
state["GlobalLoadVectorWidth%s"%tc] = bestVw
if bestVw != glvwOrig:
if dbFract:
print(" reducing GlobalLoadVectorWidth%s from %u to %u" \
% (tc, glvwOrig, bestVw))
# How many loads per threads in each dimension.
# threads which are outside the global read tile bounds will be clipped
# in the assembly code generator.
# Multiply the LSC*GRVW
state["NumLoadsCoalesced%s"%tc] = roundupRatio(parDim, state["LSC%s"%tc])
state["NumLoadsPerpendicular%s"%tc] = roundupRatio(perpDim , state["LSP%s"%tc])
nlc = state["NumLoadsCoalesced%s"%tc]
nlp = state["NumLoadsPerpendicular%s"%tc]
# LoadTile must at least cover the MacroTile:
assert(nlc*state["LSC%s"%tc] >= parDim)
assert(nlp*state["LSP%s"%tc] >= perpDim)
perpOverhang = perpDim % state["LSP%s"%tc]
state["fractionalPerpOverhang%s"%tc] = perpOverhang
if dbFract:
# how many threads compute Global Read Offsets (GRO) that are not used
print(" PerLoadTile=%ux%u elements Loads/WI=%ux%u LoadTile/WI=%ux%u (MT=%ux%u), %u/%u = %.1f%% WI GRO used %s" \
% (state["LSC%s"%tc], state["LSP%s"%tc], \
nlc, nlp, \
nlc*state["LSC%s"%tc], nlp*state["LSP%s"%tc], \
parDim, perpDim, \
parDim*perpDim, \
nlc*nlp*state["NumThreads"]*state["GlobalLoadVectorWidth%s"%tc], \
float(parDim*perpDim), \
float(nlc*nlp*state["NumThreads"]*state["GlobalLoadVectorWidth%s"%tc]) * 100.0) \
)
for p in range(0,nlp):
elementWidth = 4
if p != nlp-1:
perp = state["LSP%s"%tc]
else:
perp = perpOverhang if perpOverhang else state["LSP%s"%tc]
validElements = state["LSC%s"%tc] * perp
print(" buffer_load_element_x%u %ux%ux%u bytes, %u/%u valid GRO" %\
(state["GlobalLoadVectorWidth%s"%tc], \
state["LSC%s"%tc], perp, \
elementWidth, \
validElements//state["GlobalLoadVectorWidth%s"%tc],
state["NumThreads"]))
return True
@staticmethod
def parameterWrapper(state):
isa = tuple(state["ISA"])
if len(state["MatrixInstruction"]) == 9:
waves = state["MatrixInstruction"][7]* state["MatrixInstruction"][8]
state["ThreadTile"][0] = state["MatrixInstruction"][5]
state["ThreadTile"][1] = state["MatrixInstruction"][6] * state["MatrixInstruction"][1]
state["WorkGroup"][0] = state["MatrixInstruction"][4] * state["MatrixInstruction"][0] * state["MatrixInstruction"][7]
state["WorkGroup"][1] = waves*state["WavefrontSize"] // state["WorkGroup"][0]
#print("9-tuple: ", state["MatrixInstruction"], " TT=", state["ThreadTile"], " WG=", state["WorkGroup"])
if state["MatrixInstruction"]:
state["MatrixInstruction"] = [state["MatrixInstruction"][0],state["MatrixInstruction"][1],state["MatrixInstruction"][2],state["MatrixInstruction"][3]]
if state["MatrixInstruction"] != [] and len(state["MatrixInstruction"]) == 4:
state["MFMA_BF16_1K"] = False
if globalParameters["AsmCaps"][isa]["HasMFMA"]:
miDataType = state["ProblemType"]["DataType"] if (not state["EnableF32XdlMathOp"]) else state["ProblemType"]["F32XdlMathOp"]
# check if requested MFMA instruction is not in the list of valid instructions
if not (state["ProblemType"]["DataType"].toChar() in validMFMA and \
state["MatrixInstruction"] in validMFMA[miDataType.toChar()]):
# check separate list for B1k instructions
if miDataType.isBFloat16() and \
state["MatrixInstruction"] in validMFMA["B1k"]:
state["MFMA_BF16_1K"] = True
else:
reject(state, "MatrixInstruction %s not valid for DataType %s" % (state["MatrixInstruction"], miDataType))
# check if requested instruction is available on current architecture
if state["ProblemType"]["DataType"].toChar() == 'B':
if state["MatrixInstruction"] in validMFMA["B"] and not globalParameters["AsmCaps"][isa]["HasMFMA_bf16_original"]:
reject(state, "MatrixInstruction %s not available on %s" % (state["MatrixInstruction"], isa))
if state["MatrixInstruction"] in validMFMA["B1k"] and not globalParameters["AsmCaps"][isa]["HasMFMA_bf16_1k"]:
reject(state, "MatrixInstruction %s not available on %s" % (state["MatrixInstruction"], isa))
elif state["ProblemType"]["DataType"].toChar() == 'I8':
if globalParameters["AsmCaps"][isa]["HasMFMA_i8_908"] and state["MatrixInstruction"] not in validMFMA["I8_908"]:
reject(state, "MatrixInstruction %s not available on %s" % (state["MatrixInstruction"], isa))
if globalParameters["AsmCaps"][isa]["HasMFMA_i8_940"] and state["MatrixInstruction"] not in validMFMA["I8_940"]:
reject(state, "MatrixInstruction %s not available on %s" % (state["MatrixInstruction"], isa))
elif globalParameters["AsmCaps"][isa]["HasWMMA"]:
if state["MatrixInstruction"] not in validWMMA:
reject(state, "MatrixInstruction %s not valid for DataType %s" % (state["MatrixInstruction"], state["ProblemType"]["DataType"]))
if (state["ThreadTile"][1] % state["MatrixInstruction"][0]) != 0:
reject(state, "invalid ThreadTile1 %u for MatrixInstM %u" % (state["ThreadTile"][1], state["MatrixInstruction"][0]))
# set EnableMatrixInstruction
state["EnableMatrixInstruction"] = True
# set MIBlock
miwg0 = state["MatrixInstruction"][0] if (state["WorkGroup"][0] < state["MatrixInstruction"][0]) else state["WorkGroup"][0]
MIBlock_BM = miwg0 // state["MatrixInstruction"][0]
MIBlock_BM = min(MIBlock_BM, state["MatrixInstruction"][3])
MIBlock_BN = state["MatrixInstruction"][3] // MIBlock_BM
state["MIBlock"] = [32, 32, 2, 1, 1, 1]
state["MIBlock"][0] = state["MatrixInstruction"][0]
state["MIBlock"][1] = state["MatrixInstruction"][1]
state["MIBlock"][2] = state["MatrixInstruction"][2]
state["MIBlock"][3] = state["MatrixInstruction"][3]
state["MIBlock"][4] = MIBlock_BM
state["MIBlock"][5] = MIBlock_BN
# set MIWaveGroup
numOfWave = max((state["WorkGroup"][0] * state["WorkGroup"][1]) // state["WavefrontSize"], 1) # should be >0
state['MIWaveGroup'] = [1, 1]
state['MIWaveGroup'][0] = min((miwg0 // state["MatrixInstruction"][0]) // MIBlock_BM, numOfWave)
state['MIWaveGroup'][1] = numOfWave // state['MIWaveGroup'][0]
# set MIWaveTIle
state['MIWaveTile'] = [1, 1]
state['MIWaveTile'][0] = state["ThreadTile"][0]
state['MIWaveTile'][1] = state["ThreadTile"][1] // state["MatrixInstruction"][1]
# set MIInputPerThread
isa = tuple(state["ISA"])
state['MIInputPerThread'] = state["MatrixInstruction"][0] * state["MatrixInstruction"][2] * state["MatrixInstruction"][3] // state["WavefrontSize"]
if (not globalParameters["AsmCaps"][isa]['HasMFMA']) and globalParameters["AsmCaps"][isa]['HasWMMA']:
# gfx12 WMMA uses the generic MI formula above. Older WMMA paths keep
# the historical full-K input packing.
if not isGfx12(isa):
state['MIInputPerThread'] = state["MatrixInstruction"][2]
else:
state["EnableMatrixInstruction"] = False
##############################################
# check and calculate Wave Separate Global Read
@staticmethod
def checkAndAssignWaveSeparateGlobalRead(state, tc):
# check can we use WaveSeparateGlobalRead
numOfWaves = state["NumThreads"] // state["WavefrontSize"]
if state["WaveSeparateGlobalRead%s"%tc]:
if state["FractionalLoad"] != 0:
reject(state, "didn't support WaveSeparateGlobalRead with FractionalLoad(%u) != 0" % state["FractionalLoad"])
if state["LocalDotLayout"]>1:
reject(state, "didn't support WaveSeparateGlobalRead when LocalDotLayout(%u) > 1" % state["LocalDotLayout"])
if state["ProblemType"]["TLU%s"%tc] and (state["DepthU"] > 0) and (state["DepthU"] % numOfWaves != 0):
reject(state, "didn't support WaveSeparateGlobalRead when DepthU is not multiple of wave %u in TLU%s" % (state["DepthU"], tc))
if not state["ProblemType"]["TLU%s"%tc] and (state["MacroTile%s" % tc] % numOfWaves != 0):
reject(state, "didn't support WaveSeparateGlobalRead when MacroTile is not multiple of wave %u in TLU%s" % (state["MacroTile%s"%tc], tc))
########################################
# determine can we use VgprForLocalReadPacking
@staticmethod
def isVgprForLocalReadPackingDoable(state):
isa = tuple(state["ISA"])
rejectComment = ""
doable = True
# MatrixInstruction only
if not state["EnableMatrixInstruction"]:
rejectComment = "VgprForLocalReadPacking is for MatrixInstruction only"
doable = False
# only for HasEccHalf
if not globalParameters["ArchCaps"][isa]["HasEccHalf"]:
rejectComment = "VgprForLocalReadPacking is for EccHalf only"
doable = False
# only for SIA=3 + PLR>=1
if not (state["ScheduleIterAlg"] == 3 and state["PrefetchLocalRead"] >= 1):
rejectComment = "VgprForLocalReadPacking is effective only fof SIA=3 and PLR>=1"
doable = False
# only for 1 or 2 byte input (numRegister < 1) + UnrollMajorLDSA or B is False
if not (state["ProblemType"]["DataType"].numRegisters() < 1 and (state["UnrollMajorLDSA"] == False or state["UnrollMajorLDSB"] == False)):
rejectComment = "VgprForLocalReadPacking is effective only fof 1 or 2 byte input + UnrollMajorLDSA or B =false"
doable = False
return doable, rejectComment
########################################
# determine if current datatype can support DirectToVgpr
@staticmethod
def isDirectToVgprSupportDataType(state):
return (state["ProblemType"]["DataType"].isSingle() or state["ProblemType"]["DataType"].isDouble() or state["ProblemType"]["DataType"].isComplex() or \
state["ProblemType"]["DataType"].isHalf() or state["ProblemType"]["DataType"].isBFloat16() or state["ProblemType"]["DataType"].isInt8()) or \
state["ProblemType"]["DataType"].is8bitFloat()
########################################
# determine can we use DirectToVgpr
@staticmethod
def isDirectToVgprDoable(state, tc):
MIindex = 0 if tc == 'A' else 1
numBytes = state["ProblemType"]["DataType"].numBytes()
# Does not support DirectToVgprA+DirectToVgprB+PrefetchGlobalRead=2
# Need more than double-vgpr buffers to avoid overwritting loaded data on vgpr
if state["DirectToVgprA"] and state["DirectToVgprB"] and state["PrefetchGlobalRead"]==2:
reject(state, "DirectToVgprA + DirectToVgprB + PrefetchGlobalRead=2 is not supported")
return False
# With MatrixInstruction only (tentative)
if not state["EnableMatrixInstruction"] :
reject(state, "DirectToVgpr is for MatrixInstruction only")
return False
# check if the DataType can support DirectToVgpr
if not Solution.isDirectToVgprSupportDataType(state):
reject(state, "so far, DirectToVgpr is for single, double or double complex only")
return False
# Does not work with TLU = False and PrefetchLocalRead = 0
if (not state["ProblemType"]["TLU%c"%tc]) and state["PrefetchLocalRead"] == 0:
reject(state, "DirectToVgpr%c does not supports TLU%c = False and PrefetchLocalRead = 0"%(tc, tc))
return False
# Does not work with TLU = False and SGEMM/CGEMM (not supported)
if (not state["ProblemType"]["TLU%c"%tc]) and (state["ProblemType"]["DataType"].isSingle() or state["ProblemType"]["DataType"].isSingleComplex()):
reject(state, "DirectToVgpr%c does not supports TLU%c = False + SGEMM/CGEMM"%(tc, tc))
return False
# numBytes < 4 case
if numBytes < 4:
# Does not work with TLU = True and numBytes < 4 (not supported)
if state["ProblemType"]["TLU%c"%tc]:
doable, _ = Solution.isVgprForLocalReadPackingDoable(state)
if numBytes * state["VectorWidth%s"%tc] >= 4 and doable:
# use pack logic (with v_perm) same as local read (only if VgprForLocalReadPacking is doable)
# numBytes * VW should be 4 or larger
# force VgprForLocalReadPacking + ClusterLocalRead
state["VgprForLocalReadPacking"] = True
state["ClusterLocalRead"] = True
else:
reject(state, "DirectToVgpr%c does not supports TLU%c = True + numByte < 4"%(tc, tc))
return False
# MIWaveGroup, MatrixInstBM,BN check
# for A, MIWaveGroup[1] and MatrixInstBN should be 1
# for B, MIWaveGroup[0] and MatrixInstBM should be 1
# This is to limit the number of Vgpr
if tc == 'A' and not (state['MIWaveGroup'][1] == 1 and state['MatrixInstBN'] == 1):
reject(state, "MIWaveGroup[1] and MatrixInstBN should be 1 for DirectToVgprA. Current value is [%d, %d]"%(state['MIWaveGroup'][1], state['MatrixInstBN']))
return False
if tc == 'B' and not (state['MIWaveGroup'][0] == 1 and state['MatrixInstBM'] == 1):
reject(state, "MIWaveGroup[0] and MatrixInstBM should be 1 for DirectToVgprB. Current value is [%d, %d]"%(state['MIWaveGroup'][0], state['MatrixInstBM']))
return False
# Does not work with WaveSeparateGlobalRead
if state["WaveSeparateGlobalRead%c"%tc]:
reject(state, "DirectToVgpr%c does not supports WaveSeparateGlobalRead%c"%(tc, tc))
return False
# Does not work with TLU and NumLoadsCoalesced != MIWaveTile / GlobalLoadVectorWidth
# (only for FractionalLoad = False)
if state["FractionalLoad"] == False:
if state["ProblemType"]["TLU%s"%tc] and state["NumLoadsCoalesced%c"%tc] != state['MIWaveTile'][MIindex] / state["GlobalLoadVectorWidth%c"%tc]:
reject(state, "DirectToVgpr%c does not supports NumLoadsCoalesced%c(=%u) != MIWaveTile[%u](=%u) / GlobalLoadVectorWidth%c(=%u)"\
%(tc, tc, state["NumLoadsCoalesced%c"%tc], MIindex, state['MIWaveTile'][MIindex], tc, state["GlobalLoadVectorWidth%c"%tc]))
return False
# Does not work with MIWaveTile < VectorWidth
if state['MIWaveTile'][MIindex] < state["VectorWidth%s"%tc]:
reject(state, "DirectToVgpr%c does not supports MIWaveTile[%u](=%u) < VectorWidth%s(=%u)"\
%(tc, MIindex, state['MIWaveTile'][MIindex], tc, state["VectorWidth%s"%tc]))
return False
# Does not work with ExpandPointerSwap = False
if not state["ExpandPointerSwap"]:
reject(state, "DirectToVgpr%c does not supports ExpandPointerSwap = False"%(tc))
return False
# Does not work with TLU + VectorWidth != GlobalLoadVectorWidth (VW = 2 + GRVW = 1 or VW = 1 + GRVW = 2 does not work)
if state["ProblemType"]["TLU%c"%tc] and state["VectorWidth%s"%tc] != state["GlobalLoadVectorWidth%c"%tc]:
reject(state, "DirectToVgpr%c does not supports TLU + VectorWidth%s(=%u) != GlobalLoadVectorWidth%c(%u)"%(tc, tc, state["VectorWidth%s"%tc], tc, state["GlobalLoadVectorWidth%c"%tc]))
return False
# Does not work with FractionalLoad and (not TLU)
if state["FractionalLoad"] and (not state["ProblemType"]["TLU%c"%tc]):
reject(state, "DirectToVgpr%c does not supports FractionalLoad + TLU=False"%(tc))
return False
# Does not work with TLU=False and NumLoadsCoalesced != DepthU//(MatrixInstK*GRVW*LSU//MIInputPerThread)
if (not state["ProblemType"]["TLU%c"%tc]) and \
state["NumLoadsCoalesced%c"%tc] != state["DepthU"] // (state["MatrixInstK"] * state["GlobalLoadVectorWidth%c"%tc] * state["LocalSplitU"] // state["MIInputPerThread"]):
reject(state, "DirectToVgpr%c does not supports TLU=False and NumLoadsCoalesced%c != DepthU//(MatrixInstK*GlobalLoadVectorWidth*LocalSplitU//MIInputPerThread(=%u))"%(tc, tc, state["MIInputPerThread"]))
return False
# TLU=False case, need GlobalLoadVectorWidth == LocalReadVectorWidth
if (not state["ProblemType"]["TLU%c"%tc]) and \
state["GlobalLoadVectorWidth%c"%tc] != state["LocalReadVectorWidth"]:
reject(state, "DirectToVgpr%c does not supports TLU=False GlobalLoadVectorWidth%c(%u) != LocalReadVectorWidth(%u)"%(tc, tc, state["GlobalLoadVectorWidth%c"%tc], state["LocalReadVectorWidth"]))
return False
# Does not work with TLU=False and PrefetchLocalRead=1 and VectorWidth>1
if (not state["ProblemType"]["TLU%c"%tc]) and state["PrefetchLocalRead"] == 1 and state["VectorWidth%s"%tc] > 1:
reject(state, "DirectToVgpr%c does not supports TLU=False and PrefetchLocalRead=1 and VectorWidth%s>1)"%(tc, tc))
return False
# Does not work with SIA<3 and PGR=2
if state["ScheduleIterAlg"] < 3 and state["PrefetchGlobalRead"] == 2:
reject(state, "DirectToVgpr%c does not supports ScheduleIterAlg < 3 and PrefetchGlobalRead==2"%(tc))
return False
# Does not work with InnerUnroll>1
if state["InnerUnroll"]>1:
reject(state, "DirectToVgpr%c does not supports InnerUnroll>1"%(tc))
return False
# Does not work with ThreadSeparateGlobalRead
if state["ThreadSeparateGlobalRead%s"%tc]:
reject(state, "DirectToVgpr%c does not supports ThreadSeparateGlobalRead%c"%(tc,tc))
return False
# Reject TLU = UnrollMajorLDS (B only)
if tc == 'B' and (state["ProblemType"]["TLUA"] == state["UnrollMajorLDSA"] or state["ProblemType"]["TLUB"] == state["UnrollMajorLDSB"]):
reject(state, "DirectToVgpr%c does not supports TLU = UnrollMajorLDS"%(tc))
return False
# force GlobalReadCoalesceGroup == TLU in DTV case
state["GlobalReadCoalesceGroup%s"%tc] = state["ProblemType"]["TLU%c"%tc]
# Does not work with DirectToLDS
# -> this will be checked after DirectToLDS doable check is done
return True
########################################
# determine can we use DirectToLds
@staticmethod
def isDirectToLdsDoable(state, tc):
isa = tuple(state["ISA"])
numBytes = state["ProblemType"]["DataType"].numBytes()
asem = state["AssertSummationElementMultiple"]
gsu = state["GlobalSplitU"]
# x2/x4 support for directToLds (no longer supported)
# numelements_perlane = 4/numBytes
# TN with transposeLDS feature should work as long as state["AssertSummationElementMultiple"] % (numelements_perlane*2) = 0
# state["AssertSummationElementMultiple"] % (numelements_perlane*4) = 0
#NT
# use only for all precisions (except for bpe > 4)
#TN
# use for all precisions (except for bpe > 4) with TransposeLDS=1
numBytes = state["ProblemType"]["DataType"].numBytes()
numBytesPerLoad = int(state["GlobalLoadVectorWidth%c"%tc] * numBytes)
# x2 (b64/dwordx2) DirectToLds has no valid hardware instruction: the lds
# modifier is only legal on the x1 (dword) and x4 (dwordx4) buffer loads.
# Reject so the kernel falls back to the non-DTL load path.
if numBytesPerLoad == 8:
reject(state, "DirectToLds not supported for b64 (x2) buffer loads")
return False
# x4 (b128) DirectToLds only where supported; x1 (4 bytes) always ok
if not globalParameters["ArchCaps"][isa]["HasDTLx4"]:
if numBytesPerLoad != 4:
reject(state, "DirectToLds can only be used with buffer loads requiring 1 register")
return False
if numBytes < 4:
# numBytes < 4 and TLU=false case
# need AssertSummationElementMultiple
if state["ProblemType"]["TLU%c"%tc] == False and \
((asem % gsu != 0) or ((asem//gsu) % (state["GlobalLoadVectorWidth%c"%tc]) != 0)):
reject(state, "can't use DirectToLds for numBytes < 4 with TLU%c=False and AssertSummationElementMultiple=%u and GlobalSplitU=%u" % (tc, asem, gsu))
return False
# numBytes < 4 and TLU=true case
# need AssertSummationElementMultiple
afem = "AssertFree0ElementMultiple" if tc == "A" else "AssertFree1ElementMultiple"
if state["ProblemType"]["TLU%c"%tc] and \
(state[afem] % state["GlobalLoadVectorWidth%c"%tc] != 0):
reject(state, "can't use DirectToLds for numBytes < 4 with TLU%c=True and %s%%GlobalLoadVectorWidth!=0" % (tc, afem))
return False
if state["NumThreads"] % state["WavefrontSize"] != 0:
reject(state, "can't use DirectToLds for NumThreads % WavefrontSize != 0")
return False
if state["ProblemType"]["TLU%c"%tc] == state["UnrollMajorLDS%c" % tc]:
reject(state, "can't use DirectToLds for TLU%c == UnrollMajorLDS%c"%(tc, tc))
return False
# avoid picking x2&x4 for precisions < f32/f64 in [ProblemType][TLU] == TRUE
if not state["EnableMatrixInstruction"]:
if numBytesPerLoad * state["WavefrontSize"] > 256:
reject(state, "can't use DirectToLds for not EnableMatrixInstruction and GlobalLoadVectorWidth%c * bpe * WavefrontSize > 256"%tc)
return False
if state["WaveSeparateGlobalRead%c" % tc]:
if state["LSC%c"%tc] * state["LSP%c"%tc] * numBytes != state["WavefrontSize"] * numBytesPerLoad:
reject(state, "can't use DirectToLds for LSC%c and LSP%c * bpe!= WavefrontSize * GlobalLoadVectorWidth%c * bpe > 4"%(tc, tc, tc))
return False
else:
if state["LSC%c"%tc] * state["LSP%c"%tc] * numBytes != state["NumThreads"] * numBytesPerLoad:
reject(state, "can't use DirectToLds for LSC%c and LSP%c * bpe != NumThreads * GlobalLoadVectorWidth%c * bpe > 4"%(tc, tc, tc))
return False
if (state["LdsBlockSizePerPad%c"%tc] == 0) \
and (state["LdsPad%c"%tc] != 0):
# and ((state["LSC%c"%tc] * numBytes) != (state["NumThreads"] * 4)): // TODO:
# and ((state["LSC%c"%tc] * numBytes) % (state["WavefrontSize"] * 4) != 0):
reject(state, "can't use DirectToLds for LdsBlockSizePerPad%c == 0 and LdsPad%c != 0"%(tc, tc))
return False
if (state["LdsBlockSizePerPad%c"%tc] != 0) \
and (state["LdsPad%c"%tc] != 0) \
and (state["LdsBlockSizePerPad%c"%tc] != state["WavefrontSize"] * numBytesPerLoad):
# and (state["LdsBlockSizePerPad%tc"] % (state["WavefrontSize"] * 4) != 0): // TODO:
reject(state, "can't use DirectToLds for LdsBlockSizePerPad%c != 0 and LdsPad%c != 0 and \
LdsBlockSizePerPad%c != WavefrontSize * GlobalLoadVectorWidth%c * bpe"%(tc, tc, tc, tc))
return False
# so far, DirectToLds does not work well with PGR=2
# performance is not good and a lot of ds_read for DTL can cause scheduling issue(need fix)
# limit this reject condition for numBytes >= 8
if numBytes >= 8 and state["PrefetchGlobalRead"] == 2 and not (state["DirectToVgprA"] or state["DirectToVgprB"]):
reject(state, "can't use DirectToLds for PrefetchGlobalRead == 2 without DirectToVgpr")
return False
if state["NumLoadsCoalesced%c"%tc] > 1:
# NumLoadsCoalesced > 1 not working with TLU=False
if (not state["ProblemType"]["TLU%c"%tc]):
reject(state, "Can't use NumLoadsCoalesced > 1 with DirectToLds + TLU=False")
return False
# Does not work with (NumLoadsCoalesced>1 and UseInstOffsetForGRO) + DGEMM
if state["ProblemType"]["DataType"].isDouble() and state["UseInstOffsetForGRO"]:
reject(state, "DirectToLds%c does not supports NumLoadsCoalesced%c > 1 and UseInstOffsetForGRO for dgemm"%(tc, tc))
return False
# Does not work with PAPMode 1 and (AssertSummationElementMultiple/GlobalSplitU) % (DepthU * 2) != 0
# This is because DirectToLds use second LDS buffer after Odd exit. It does not match local read at the beginning of PK loop.
if state["PrefetchAcrossPersistentMode"] == 1 and ((asem%gsu != 0) or ((asem//gsu) % (state["DepthU"] * 2) != 0)):
reject(state, "DirectToLds%c does not work with PAPMode 1 and (AssertSummationElementMultiple//GlobalSplitU) is not multiple of (DepthU * 2)"%(tc))
return False
# Does not work with PrefetchGlobalRead=2 and PrefetchLocalRead=1 (cannot schedule DTL global read after local read)
if state["PrefetchGlobalRead"] == 2 and state["PrefetchLocalRead"] == 1:
reject(state, "DirectToLds%c does not work with PrefetchGlobalRead=2 and PrefetchLocalRead=1"%(tc))
return False
# Does not work with PrefetchGlobalRead=2 and numBytes < 4 and TLU=True
# local reads for high portion can be scheduled after DirectToLds load
if state["PrefetchGlobalRead"] == 2 and numBytes < 4 and state["ProblemType"]["TLU%c"%tc]:
reject(state, "DirectToLds%c does not work with PrefetchGlobalRead=2 and numBytes < 4 and TLU"%(tc))
return False
# Does not work with PrefetchGlobalRead=2 and MatrixInstB > 1
if state["PrefetchGlobalRead"] == 2 and state["MatrixInstB"] > 1:
reject(state, "DirectToLds%c does not work with PrefetchGlobalRead=2 and MatrixInstB > 1"%(tc))
return False
# DirectToLds does not work if MacroTile is not power of 2
# LDS offset swap/rotate logic works only when MacroTile is power of 2
mt = state["MacroTile%c"%tc]
if mt & (mt - 1) != 0 and state["NumLoadsCoalesced%c"%tc] > 1:
reject(state, "can't use DirectToLds if MacroTile%s is not power of 2 and NumLoadsCoalesced%s > 1"%(tc,tc))
return False
# check for DirectToLds + ThreadSeparateGlobalRead
if state["ThreadSeparateGlobalRead%c"%tc]:
if numBytes > 4:
reject(state, "ThreadSeparateGlobalRead%c + DTL does not work if numBytes(%d) > 4"%(tc, numBytes))
if state["ProblemType"]["TLU%c"%tc]:
reject(state, "ThreadSeparateGlobalRead%c does not work with DTL%c + TLU%c"%(tc, tc, tc))
if state["NumLoadsCoalesced%c"%tc] > 1:
reject(state, "ThreadSeparateGlobalRead%c does not work with DirectToLds + NumLoadsCoalesced > 1."%(tc))
if int(state["WavefrontSize"] * state["GlobalLoadVectorWidth%c"%tc]) < state["_DepthULds"] * state["VectorWidth%s"%tc]:
reject(state, "ThreadSeparateGlobalRead%c does not work with WavefrontSize * GlobalLoadVectorWidth%c < _DepthULds * VectorWidth%s."%(tc, tc, tc))
# Does not work with LocalSplitU
if state["LocalSplitU"] > 1:
reject(state, "DirectToLds%c does not work with LocalSplitU>1"%(tc))
return False
return True
@staticmethod
def getDivisorName(state, tC):
if state["GlobalReadCoalesceGroup{}".format(tC)]:
if state["GlobalReadCoalesceVector{}".format(tC)]:
divisorName = "LVC{}".format(tC)
else:
# Fractional load use the more accurate lsc, multiply by VW later
divisorName = "LSC{}".format(tC)
else:
if state["GlobalReadCoalesceVector{}".format(tC)]:
divisorName = "LSP{}".format(tC)
else:
divisorName = "LVP{}".format(tC)
return divisorName
########################################
# get number of elements in LDS for each tc (A or B)
@staticmethod
def getLdsNumElements(state, tc):
bpeAB = int(4*state["ProblemType"]["DataType"].numRegisters())
if state["LdsBlockSizePerPad%s"%tc]:
padInterval = state["LdsBlockSizePerPad%s"%tc] // bpeAB
else:
if state["UnrollMajorLDS%s"%tc]:
padInterval = state["_DepthULds"]
else:
padInterval = state["MacroTile%s"%tc]
numElements = state["MacroTile%s"%tc] * state["_DepthULds"]
ldsNumElements = numElements + (numElements // padInterval) * (state["LdsPad%s"%tc])
if state["DirectToVgpr%s"%tc]:
# DirectToVgpr does not use LDS. Set to 0.
ldsNumElements = 0
return ldsNumElements
########################################
# get number of aligned elements in LDS for each tc (A or B)
@staticmethod
def getLdsNumElementsAligned(state, tc):
ldsAlign = int(64 / state["ProblemType"]["DataType"].numRegisters())
ldsNumElements = Solution.getLdsNumElements(state, tc)
ldsNumElementsAligned = roundUpToNearestMultiple(ldsNumElements, ldsAlign)
return ldsNumElementsAligned
########################################
# determine auto LdsPad and LdsBlockSizePerPad
@staticmethod
def ldsPaddingAuto(state, isa):
# LDS padding
# Resolve -1 before isDirectToLdsDoable check
numBytes = state["ProblemType"]["DataType"].numBytes()
optPad = state["LocalReadVectorWidth"]
readRegs = state["LocalReadVectorWidth"]*numBytes//4
if (not globalParameters["AsmCaps"][isa]['HasWMMA']) and readRegs > 4:
reject(state, "LocalReadVectorWidth=%u results in attemping to read LDS larger than b128, reject")
autoAdjusted = {"LdsPadA": False, "LdsPadB": False, "LdsBlockSizePerPadA": False, "LdsBlockSizePerPadB": False}
for tc in ('A','B'):
# set pad as readRegs to avoid unaligned read
idx01 = 0 if tc == 'A' else 1
charMN = 'M' if tc == 'A' else 'N'
numBank = 32
vw = state["VectorWidth%s"%tc]
# LdsBlockSizePerPad and LdsPad setting
autoCalcLBSPP = False
if state["LdsBlockSizePerPad%s"%tc] == -1:
state["LdsBlockSizePerPad%s"%tc] = 0
autoCalcLBSPP = True
autoAdjusted["LdsBlockSizePerPad%s"%tc] = autoCalcLBSPP
autoCalcLP = False
if state["LdsPad%s"%tc] == -1:
autoCalcLP = True
if state["ProblemType"]["TLU%s"%tc] and (not state["UnrollMajorLDS%s"%tc]):
state["LdsPad%s"%tc] = 0
else:
state["LdsPad%s"%tc] = vw
autoAdjusted["LdsPad%s"%tc] = autoCalcLP
if state["EnableMatrixInstruction"]:
# MI case
LRstrideLine = 0 # for LBSPP value check
LRstride = 0
comment = ""
depthU = state["_DepthULds"]
if state["UnrollMajorLDS%s"%tc]:
LRstrideLine = depthU
comment = "DepthULds"
LRstride = LRstrideLine * vw
else:
LRstrideLine = state["MacroTile%d"%idx01]
comment = "MT0"
if state["MIInputPerThread"] > 1:
# MIInputPerThread > 1 case, we still need padding to mitigate bank conflict even for non-UnrollMajorLDS case
LRstride = LRstrideLine * state["LocalReadVectorWidth"]
# if LRstrideLine is not power of 2, adjust ldsPad at each line (keep LRstride = 0)
if LRstrideLine <= 0 or (LRstrideLine & (LRstrideLine - 1)) != 0:
LRstride = 0
state["LdsBlockSizePerPad%s"%tc] = 0 # force LdsBlockSizePerPad to 0
# auto calc for LBSPP
if autoCalcLBSPP and LRstride > 0:
state["LdsBlockSizePerPad%s"%tc] = max(int(2**(math.ceil(math.log(LRstride * numBytes, 2)))), 128)
# value check
if state["LdsBlockSizePerPad%s"%tc]:
if state["LdsBlockSizePerPad%s"%tc] < LRstrideLine:
reject(state, "reject: %s %u x bpe > LdsBlockSizePerPad%s %u" % (comment, LRstrideLine, tc, state["LdsBlockSizePerPad%s"%tc]))
# auto calc for LdsPad
if autoCalcLP:
miWidth = state["MatrixInst%s"%charMN] * state["MatrixInstB%s"%charMN]
if tc == 'B' and state["MatrixInstBM"] > 1:
# B and state["MatrixInstBM"] > 1 case, BN is not continuous. Use MatrixInstN only
miWidth = state["MatrixInstN"]
if (not state["UnrollMajorLDS%s"%tc]):
extra = 0
if miWidth < numBank and miWidth * vw < 128 // numBytes:
extra = (miWidth * vw) % (128 // numBytes)
if extra:
divisor = 128//numBytes
mod = LRstride % divisor
state["LdsPad%s"%tc] = (divisor + extra - mod) % divisor
else:
optPadMN = optPad
# for readRegs = 1 or 4, we need to double pad for MI16x16xNx1 to avoid bank conflict.
if miWidth < numBank and (readRegs == 4 or readRegs == 1):
# UnrollMajorLds and miWidth < 32 case, same M or N location (with K+1(,2,...) is accessed
# We need to offset (32//miWidth) times for the next M or N
# ex.) MI16x16, miWidth=16. To avoid Bank conflict, we need to double padding for M1.
# [M0,K0]. [M0,K1]
# offset K0 and K1 -> [M1,K0]. [M1,K1]
# offset K0 and K1 -> [M2,K0]. [M2,K1]
# offset K0 and K1 -> [M3,K0]. [M3,K1]
optPadMN = optPad * (numBank//miWidth)
optPadMN = max(state["GlobalLoadVectorWidth%s"%tc], optPadMN)
# if depthU is not power of 2, adjust ldsPad to make depthU part multiple of 128/numBytes
if depthU > 0 and (depthU & (depthU - 1)) != 0:
divisor = 128//numBytes
ratio = roundupRatio(128//numBytes, depthU)
extraPadDU = (ratio * depthU) % (128//numBytes)
optPadMN_Plus = (extraPadDU + optPadMN) % (128//numBytes)
optPadMN_Minus = (extraPadDU - optPadMN) % (128//numBytes)
optPadMN = min(optPadMN_Plus, optPadMN_Minus)
state["LdsPad%s"%tc] = optPadMN
## turn-off padding for directToLds
if state["DirectToLds%s"%tc]:
state["LdsPad%s"%tc] = 0
else:
# non MI case
if state["UnrollMajorLDS%s"%tc]:
reject(state, "didn't support UnrollMajorLDS in VALU mode yet")
if state["LdsBlockSizePerPad%s"%tc] != 0:
reject(state, "didn't support LdsBlockSizePerPad in VALU mode yet")
assert(state["LdsPad%s"%tc] >= 0)
# DirectToVgpr case, set LdsPad to 0
if state["DirectToVgpr%s"%tc]:
state["LdsPad%s"%tc] = 0
# set LdsBlockSizePerPad = 0 if LdsPad is 0
if state["LdsPad%s"%tc] == 0:
state["LdsBlockSizePerPad%s"%tc] = 0
# LDS size check for auto adjustment
# if LDS size is over the limit, change LdsPad to 0
# check UnrollMajorLDS=False for A, B first, then true
for umlds in [False, True]:
for tc in ('A','B'):
# A side (aligned) + B side (not aligned)
ldsNumElementsAB = Solution.getLdsNumElementsAligned(state, 'A') + \
Solution.getLdsNumElements(state, 'B')
if state["1LDSBuffer"] != 1:
# not 1LDSBuffer case, double num element
ldsNumElementsAB *= 2
if autoAdjusted["LdsPad%s"%tc] and (ldsNumElementsAB * numBytes) > globalParameters["MaxLDS"]:
# auto adjusted LdsPad and LDS overflow
if state["UnrollMajorLDS%s"%tc] == umlds:
# change LdsPad and LdsBlockSizePerPad to 0
state["LdsPad%s"%tc] = 0
state["LdsBlockSizePerPad%s"%tc] = 0
########################################
# assign all derived parameters
@staticmethod
def assignDerivedParameters(state):
isa = tuple(state["ISA"])
state["EnableF32XdlMathOp"] = False #ignore the F32 xDL MathOp by default.
#enable F32 xDL MathOp only when the input type is f32.
if "F32XdlMathOp" in state["ProblemType"] \
and (not state["ProblemType"]["F32XdlMathOp"].isSingle()) \
and (state["ProblemType"]["DataType"].isSingle()):
state["EnableF32XdlMathOp"] = True
Solution.parameterWrapper(state)
Solution.assignProblemIndependentDerivedParameters(state)
if "AssignedDerivedParameters" in state:
if state["AssignedDerivedParameters"]:
return
state["AssignedDerivedParameters"] = False
for s in Solution.InternalKeys:
state['_'+s] = state[s]
#del state[s]
if ("_GlobalAccumulation" not in state) or ("_WorkspaceSizePerElemC" not in state):
state["_GlobalAccumulation"] = None
state["_WorkspaceSizePerElemC"] = 0
if state["StreamK"] > 0 and state["StreamKAtomic"] == 0:
# StreamK Workspace size
computeBytes = state["ProblemType"]["ComputeDataType"].numBytes()
state["_GlobalAccumulation"] = 'PartialsBuffer'
state["_WorkspaceSizePerElemC"] = computeBytes
if state["GlobalSplitU"] > 1:
computeName = state["ProblemType"]["ComputeDataType"].toName()
computeBytes = state["ProblemType"]["ComputeDataType"].numBytes()
if state["GlobalSplitUAlgorithm"] == 'SingleBuffer':
# For SingleBuffer algorithm, _GA and _WorkspaceSizePerElemC is updated only if the gemm function is HPA (excluding int8).
# The workspace is used to convert the final output from ComputeDataType to DestDataType.
# If ComputeDataType and DestDataType are the same ,the _GA and _Workspace remain unchanged.
# for HPA cases with ComputeDataType!=DestDataType : HHS/BBS (_GA should be singlebuffer for these types)
if (computeName != state["ProblemType"]["DestDataType"].toName()):
state["_GlobalAccumulation"] = 'SingleBuffer'
state["_WorkspaceSizePerElemC"] = computeBytes
elif state["GlobalSplitUAlgorithm"] == 'MultipleBuffer':
state["_GlobalAccumulation"] = 'MultipleBuffer'
state["_WorkspaceSizePerElemC"] = computeBytes * state["GlobalSplitU"]
if state["StreamK"] != 0:
if state["MIWaveGroup"][0] * state["MIWaveGroup"][1] != 4:
reject(state, "Stream-K requries MIWaveGroup0*MIWaveGroup1=4")
if state["EnableMatrixInstruction"] and globalParameters["AsmCaps"][isa]["HasWMMA"]:
reject(state, "Stream-K untested with WMMA")
if state["GlobalSplitU"] > 1:
reject(state, "Cannot enable both Stream-K and GSU")
if state["PersistentKernel"]:
reject(state, "Cannot enable both Stream-K and PersistentKernel")
if not state["ProblemType"]["StridedBatched"]:
reject(state, "General batch not supported with Stream-K")
if state["StreamKXCCMapping"] > 0:
if isa != (9,4,2):
reject(state, "XCC mapping currently only on gfx942")
if state["StreamKAtomic"] == 1:
if not state["ProblemType"]["DataType"].isSingle():
reject(state, "Atomic Stream-K currently only tested for SGEMM")
if not state["BufferStore"]:
reject(state, "Atomic Stream-K requires BufferStore")
if state["LocalSplitU"] > 1:
reject(state, "Atomic Stream-K not working with LocalSplitU")
else:
# If not using StreamK, set StreamKAtomic to 0 to avoid possibility of duplicate kernels
state["StreamKAtomic"] = 0
state["StreamKXCCMapping"] = 0
# If not using StreamK, clear debug modes to avoid duplicate kernels
state["DebugStreamK"] = 0
if state["KernelLanguage"] == "Assembly" and not state["BufferLoad"]:
# StaggerU only works with source kernels, or with BufferLoad.
# Since StaggerU defaults to 32, override it to 0 if not supported.
state["StaggerU"] = 0
def supportsPreloadKernelArguments():
if not globalParameters["AsmCaps"][isa]["KernargPreloading"]:
# force to disable preloading (instead of rejecting kernel)
state["PreloadKernelArguments"] = 0
return False, f"{isa} doesn't support preloading."
if not state["ProblemType"]["StridedBatched"]:
return False, "Preloading only supported in StridedBatched mode."
dt = state["ProblemType"]["DataType"]
if not (dt.isSingle() or dt.isHalf() or dt.isBFloat16()):
return False, f"Preloading not supported for data type {dt}."
if not state["ProblemType"]["UseBeta"]:
return False, "Preloading only supported with UseBeta."
if state["ProblemType"]["UseInitialStridesAB"]:
return False, "Preloading not supported with initial strides AB."
if abs(state["WorkGroupMapping"]) > 1:
return False, "Preloading not supported with WorkGroupMapping."
if not state["PrefetchGlobalRead"]:
return False, "Preloading must use PrefetchGlobalRead."
if state["EdgeType"] == "ShiftPtr":
return False, "Preloading not compatible with ShiftPtr."
if state["StaggerU"] != 0:
return False, "Preloading not compatible with StaggerU."
return True, ""
pkaRequested = state["PreloadKernelArguments"]
pkaSupported, pkaMsg = supportsPreloadKernelArguments()
pkaForcedOff = False
if pkaRequested == -1:
if pkaSupported:
state["PreloadKernelArguments"] = 1
else:
state["PreloadKernelArguments"] = 0
elif pkaRequested == 1:
if not pkaSupported:
if not globalParameters["AsmCaps"][isa]["KernargPreloading"]:
# The hardware cannot preload kernargs; fall back to the normal s_load
# path and disable the dependent delayed-argument schedule.
disablePreloadKernelArguments(state)
pkaForcedOff = True
else:
reject(state, pkaMsg)
if "DelayRemainingArguments" in state:
if state["DelayRemainingArguments"] and state["PreloadKernelArguments"] != 1:
if pkaForcedOff:
state["DelayRemainingArguments"] = False
else:
reject(state, "Delayed kernel arguments only supported when preloading.")
else:
state["DelayRemainingArguments"] = False
if state["VectorStore"] == -1:
state["_VectorStore"] = 1 # default, may be changed if needed to generate a valid kernel
ProblemType.assignDerivedParameters(state["ProblemType"])
if not state["Valid"]:
tPrint(3, "in assignDerivedParameters, state['Valid'] = False")
return
atomic = ((state["GlobalSplitU"] > 1) and (state["_GlobalAccumulation"] != 'MultipleBuffer')) or state["AtomicAddC"] or (state["StreamK"] > 0 and state["StreamKAtomic"] == 1)
if atomic and globalParameters["DebugSkipAtomic"]:
reject(state, "DEBUG: DebugSkipAtomic enabled, rejecting atomic kernel")
if not atomic and globalParameters["DebugSkipNonAtomic"]:
reject(state, "DEBUG: DebugSkipNonAtomic enabled, rejecting non-atomic kernel")
# Init LoopIters parameter in case of early exit
# For backwards compatibility with older yaml files
state["LoopIters"] = 0
if "LoopUnroll" in state:
state["LoopIters"] = state["LoopUnroll"]
if state["ScheduleIterAlg"] == 2:
state["InnerUnroll"] = state["DepthU"] // state["MatrixInstK"]
state["PrefetchLocalRead"] = 1
state["ExpandPointerSwap"] = 1
state["1LDSBuffer"] = 1
tPrint(3, "\nSet SIA=2, force PrefetchLocalRead=1, ExpandPointerSwap=1, 1LDSBuffer=1")
if "MemoryModifierFormat" not in state or state["MemoryModifierFormat"] not in validParameters["MemoryModifierFormat"]:
if globalParameters["AsmCaps"][isa]["HasGLCModifier"]:
state["MemoryModifierFormat"] = "GLC"
else:
state["MemoryModifierFormat"] = "SC0"
if ("ForceStoreSC1" not in state) or (state["ForceStoreSC1"] == "Auto") or \
(state["ForceStoreSC1"] not in validParameters["ForceStoreSC1"]):
state["ForceStoreSC1"] = globalParameters["ArchCaps"][isa]["ForceStoreSC1"]
if not globalParameters["AsmCaps"][isa]["HasNTModifier"]:
# force to disable nt flag if it is not supported by arch
for ch in ["A", "B", "C", "D"]:
if state["NonTemporal%s"%ch] >= 4:
state["NonTemporal%s"%ch] -= 4
if state["WavefrontSize"] == 32 and not globalParameters["ArchCaps"][isa]["HasWave32"]:
reject(state, "WavefrontSize=32 not supported for ISA {}".format(isa))
if state["WavefrontSize"] == 32 and state["KernelLanguage"] == "Source":
reject(state, "WavefrontSize=32 not yet supported for source kernels.")
if state["EnableMatrixInstruction"]:
if not (globalParameters["AsmCaps"][isa]["HasMFMA"] or globalParameters["AsmCaps"][isa]["HasWMMA"]):
reject(state, f"isa {isa} doesn't support matrix instruction")
return
if not (state["ProblemType"]["DataType"].isSingle() \
or state["ProblemType"]["DataType"].isDouble() \
or state["ProblemType"]["DataType"].isBFloat16() \
or state["ProblemType"]["DataType"].isHalf() \
or state["ProblemType"]["DataType"].isComplex() \
or state["ProblemType"]["DataType"].is8bitFloat() \
or state["ProblemType"]["DataType"].isInt8()):
reject(state, "didn't support Matrix Instruction with type %s" % str(state["ProblemType"]["DataType"]))
return
if (not globalParameters["AsmCaps"][isa]["HasMFMA"] and globalParameters["AsmCaps"][isa]["HasWMMA"] and (state["WavefrontSize"] == 64)):
reject(state, "WMMA only suppport on WGP mode, wave size = 32")
return
if not state["MIBlock"] or len(state["MIBlock"]) != 6:
reject(state, "invalid MIBlock")
return
if not state["MIWaveGroup"] or len(state["MIWaveGroup"]) != 2:
reject(state, "invalid MIWaveGroup")
return
if not state["MIWaveTile"] or len(state["MIWaveTile"]) != 2:
reject(state, "invalid MIWaveTile")
return
if globalParameters["AsmCaps"][isa]["HasMFMA"]:
if not state["ProblemType"]["HighPrecisionAccumulate"] \
and state["ProblemType"]["DataType"].numRegisters() < 1 :
reject(state, "Matrix instructions for half, bf16, f8, b8 (or i8) types are natively accumulated" + \
" in fp32 (or i32) precision. Please add the following config:" + \
"\n - HighPrecisionAccumulate: True")
return
if globalParameters["AsmCaps"][isa]["HasWMMA"]:
if state["ProblemType"]["DataType"].numRegisters() >=1:
reject(state, "WMMA only supports half, bf16 and i8 types")
return
if state["InterleaveAlpha"]:
reject(state, "Matrix instruction does not support InterleaveAlpha")
return
else:
if not state["ProblemType"]["HighPrecisionAccumulate"] \
and state["ProblemType"]["ComputeDataType"].numRegisters() > state["ProblemType"]["DataType"].numRegisters():
reject(state, "For non-MI Kernel, if sizeof(ComputeDataType) > sizeof(DataType), " + \
"Please add the following config:" + \
"\n - HighPrecisionAccumulate: True")
if state["ThreadTile0"] > 16 or state["ThreadTile1"] > 16:
reject(state, "Invalid value for ThreadTile")
if state["ScheduleIterAlg"] == 2 or state["ScheduleIterAlg"] == 3:
reject(state, "SIA2 and SIA3 only support MatrixInstruction")
if state["ProblemType"]["Tensor0"]==0:
state["ThreadTileA"] = state["ThreadTile0"]
state["ThreadTileB"] = state["ThreadTile1"]
state["SubGroupA"] = state["SubGroup0"]
state["SubGroupB"] = state["SubGroup1"]
state["MacroTileA"] = state["MacroTile0"]
state["MacroTileB"] = state["MacroTile1"]
if state["EnableMatrixInstruction"]:
state["MIWaveTileA"] = state["MIWaveTile"][0]
state["MIWaveTileB"] = state["MIWaveTile"][1]
else:
state["ThreadTileB"] = state["ThreadTile0"]
state["ThreadTileA"] = state["ThreadTile1"]
state["SubGroupB"] = state["SubGroup0"]
state["SubGroupA"] = state["SubGroup1"]
state["MacroTileB"] = state["MacroTile0"]
state["MacroTileA"] = state["MacroTile1"]
if state["EnableMatrixInstruction"]:
state["MIWaveTileA"] = state["MIWaveTile"][1]
state["MIWaveTileB"] = state["MIWaveTile"][0]
Solution.checkAndAssignWaveSeparateGlobalRead(state, 'A')
Solution.checkAndAssignWaveSeparateGlobalRead(state, 'B')
# Init vars early since there are early-exit return statements below
state["LocalWriteUseSgprA"] = False
state["LocalWriteUseSgprB"] = False
if state["KernelLanguage"] != "Assembly" or state["WorkGroupMappingType"] == "Z":
# WGM < 0 is only for Asm + WorkGroupMappingType==B
# convert to > 0
state["WorkGroupMapping" ] = abs(state["WorkGroupMapping"])
if state["WorkGroupMapping" ] == -1:
# -1 code is same as 1. Convert to 1.
state["WorkGroupMapping" ] = 1
# avoid bug somehow related to GlobalSplitU + Persistent
# avoid bug related to WGM<0
# General Batch doesn't support PersistentKernel
if state["PersistentKernel"] and (\
(state["KernelLanguage"] == "Assembly" and state["GlobalSplitU"] != 1) or \
(state["KernelLanguage"] == "Assembly" and state["WorkGroupMapping"] < 0)):
state["PersistentKernel"] = 0
if state["PersistentKernelAlongBatch"] and (\
(state["PersistentKernel"] == 0) or \
(state["KernelLanguage"] == "Source" and state["GlobalSplitU"] != 1)):
tPrint(3, "PersistentKernelAlongBatch requires PersistentKernel != 0, forcing PersistentKernelAlongBatch = False")
tPrint(3, "PersistentKernelAlongBatch not support GSU on HIP, forcing PersistentKernelAlongBatch = False")
state["PersistentKernelAlongBatch"] = False
if state["PrefetchAcrossPersistent"]:
if state["KernelLanguage"] == "Source" or \
state["PersistentKernel"] == 0 or \
state["PrefetchGlobalRead"] == 0 or \
state["SuppressNoLoadLoop"]:
tPrint(3, "PAP requires Assembly, PK!=0, PGR!=0, SuppressNoLoadLoop=True, forcing PAP=False")
state["PrefetchAcrossPersistent"] = False
state["PrefetchAcrossPersistentMode"] = False # PAPM should be 0 here to avoid getting rejected later with a logic file
if state["PrefetchAcrossPersistentMode"] == 0 and state["PrefetchGlobalRead"] == 2:
reject(state, "PAPMode 0 does not support PGR=2")
return
else:
if state["PrefetchAcrossPersistentMode"] != 0:
reject(state, "PAPMode requires PrefetchAcrossPersistent enabled")
return
problemType = state["ProblemType"]
if not problemType["UseInitialStridesAB"]:
for (tc) in ('A','B'):
state["AssertStride%sEqual"%tc][0]=1
if not problemType["UseInitialStridesCD"]:
for (tc) in ('C','D'):
state["AssertStride%sEqual"%tc][0]=1
# Add AssertStride*Equal for PackBatchDims, if needed
for (mask, tc) in ((0x1,'B'), (0x2,'A')):
if state["PackBatchDims"] & mask:
for bi in problemType["IndicesBatch"]:
state["AssertStride%sEqual"%tc][problemType["IndexAssignments%s"%tc].index(bi)] = 0
for (tc,batchMask) in (('A', 0x1), ('B', 0x2)):
freeDims = [i for i in problemType["IndexAssignments%s"%tc] if i in problemType["IndicesFree"]]
if not freeDims and (not problemType["AllowNoFreeDims"] or not (state["PackBatchDims"] & batchMask)):
reject(state, "tensor%s contains no free indices. Set AllowNoFreeDims and PackBatchDims&%s" % (tc, batchMask))
return False
# Determine which indices will be packed together as this impacts several different parms (sizes, magic numbers, etc)
# The order in PackedC*Indices also determines the order that dimensions are packed - the first elements in
# the list are the fastest-moving elements.
# The store code optimizes for C0 being the coalesced dimension and C1 the perp dimension.
# C0/C1 indices can come from IndexAssignmentsA or IndexAssignmentsB
# grid size [0,1]
state["PackedC0IdxChars"] = []
state["PackedC0IndicesX"] = []
indexChars = globalParameters["IndexChars"]
# Pack all the dimensions (batch and free) of A into grid[0]
if problemType["Index0"] in problemType["IndexAssignmentsA"]:
tc0 = 'A'
tc1 = 'B'
batch0Mask = 0x1
batch1Mask = 0x2
else:
tc0 = 'B'
tc1 = 'A'
batch0Mask = 0x2
batch1Mask = 0x1
assert(isPackedIndex(state, problemType["Index01A"], 0x1))
assert(isPackedIndex(state, problemType["Index01B"], 0x2))
# Pack all the dimensions (batch and free) of A into grid[0]
for idx in problemType["IndexAssignments%s"%tc0]:
if isPackedIndex(state, idx, batch0Mask):
assert (idx < problemType["NumIndicesC"])
state["PackedC0IdxChars"].append("%s" % indexChars[idx])
state["PackedC0IndicesX"].append(idx)
state["PackedC1IdxChars"] = []
state["PackedC1IndicesX"] = []
for idx in problemType["IndexAssignments%s"%tc1]:
if isPackedIndex(state, idx, batch1Mask):
assert (idx < problemType["NumIndicesC"])
state["PackedC1IdxChars"].append("%s" % indexChars[idx])
state["PackedC1IndicesX"].append(idx)
# If dims are packed, then need to ensure a global vector load isn't split by a tensor dim
# (since this could result in non-contiguous addresses)
# Current implementation ensures that the vector load is not partial across the Free* boundary:
# GlobalLoadVectorWidth=1 will always meet this requirement.
# (TODO - could make this more sophisticated if dims use default strides and are thus contiguous)
packedC0 = len(state["PackedC0IdxChars"])>1
packedC1 = len(state["PackedC1IdxChars"])>1
bufferLoad = state["BufferLoad"] and state["KernelLanguage"] == "Assembly"
if not bufferLoad:
state["DirectToLdsA"] = False
state["DirectToLdsB"] = False
state["_UseSgprForGRO"] = False
state["FractionalLoad"] = False
#These modes only work under certain conditions, apply them here:
# - The "NoLoad" loop is only generated if PrefetchGlobalRead>0
# - And Suppress does not work if GSU>1 for some reason
if state["SuppressNoLoadLoop"] == 1:
if not (bufferLoad and state["PrefetchGlobalRead"] == 1 and (state["GlobalSplitU"]==1)):
state["SuppressNoLoadLoop"] = 0
if state["ExpandPointerSwap"] == 1:
# Pointer swap only used if PGR==1 or (PGR>1 and double/double complex) - so set ExpandPointerSwap=0 here
# So far, EPS=1 and PGR>1 works only with double/double complex.
# DirectToVgpr case, bufferLoad=False can work with ExpandPointerSwap=1
#if not (bufferLoad and state["PrefetchGlobalRead"] == 1):
if not ((bufferLoad or state["DirectToVgprA"] or state["DirectToVgprB"]) and ( state["PrefetchGlobalRead"] == 1 \
or (state["PrefetchGlobalRead"] > 1 and Solution.isDirectToVgprSupportDataType(state)))):
state["ExpandPointerSwap"] = 0
# EPS not supported with SplitLDS yet
if state["DepthULdsDivisor"] > 1:
state["ExpandPointerSwap"] = 0
# Can optimize preLoop LW Vmcnt only when PAP
# TODO- less restriction? Haven't tested for not BufferLoad
state["OptPreLoopVmcnt"] = state["OptPreLoopVmcnt"] and \
state["PrefetchAcrossPersistent"]
#print("PackedC0IdxChars", state["PackedC0IdxChars"])
#print("PackedC1IdxChars", state["PackedC1IdxChars"])
if state["StaggerUStride"] == 0 and state["StaggerU"] != 0:
reject(state, "StaggerUStride={} is only valid if StaggerU is 0 (StaggerU={})".format(state["StaggerUStride"], state["StaggerU"]))
return
# Set up stagger shift:
bpeAB = int(4*state["ProblemType"]["DataType"].numRegisters())
# (1<<staggerStrideShift) is number of loop iterations to traverse the stride
if state["StaggerU"] == 0:
state["StaggerUMapping"] = 0
state["StaggerUStride"] = 0
try:
staggerStrideShift = (int)(math.ceil(math.log(state["StaggerUStride"] / \
(state["DepthU"] * bpeAB), 2)))
except ValueError: # i.e., StaggerUStride == 0
staggerStrideShift = 0
if staggerStrideShift < 0:
reject(state, "StaggerUStride=%u is less than size of DepthU=%u * BytesPerElement=%u" \
% (state["StaggerUStride"], state["DepthU"], bpeAB))
return
#print "staggerStrideShift=", staggerStrideShift, "depthu=", state["DepthU"]
state["_staggerStrideShift"] = staggerStrideShift
# Use GlobalReadVectorWidth if it is not -1
if state["GlobalReadVectorWidth"] != -1:
state["GlobalLoadVectorWidthA"] = state["GlobalReadVectorWidth"]
state["GlobalLoadVectorWidthB"] = state["GlobalReadVectorWidth"]
if state["EnableMatrixInstruction"]:
# MI case
# VectorWidth default handling
if state["VectorWidth"] < 1:
regPerElem = state["ProblemType"]["DataType"].numRegisters()
# half: regPE=0.5, vw=2 / int8: regPE=0.25, vw=4
state["VectorWidth"] = int(1//regPerElem) if (regPerElem < 1) else 1
if state["VectorWidthB"] < 1:
regPerElem = state["ProblemType"]["DataType"].numRegisters()
# half: regPE=0.5, vw=2 / int8: regPE=0.25, vw=4
state["VectorWidthB"] = int(1//regPerElem) if (regPerElem < 1) else 1
# state["VectorWidthB"] = 1 # use 1 to avoid existing kernels getting rejected (backward compatibility)
# DirectToVgpr special case
# allow LocalReadVectorWidthB > 1 for TLUB + MatrixInstruction (this is applicable for B only)
# some more limitations necessary to make this logic work
# - MatrixInstruction
# - TLUB and not UnrollMajorLDSB
# - MIInputPerThread == 1
# - SourceSwap
# - DirectToVgprB or DirectToVgprA
# - MIWaveTile1 must be multiple of VectorWidthB
# need state["LocalReadVectorWidth"] != -1 for DTVA
# need state["GlobalLoadVectorWidthB"] != -1 for DTVB
if state["EnableMatrixInstruction"] and \
state["ProblemType"]["TLUB"] and (not state["UnrollMajorLDSB"]) and \
state["MIInputPerThread"] == 1 and state["SourceSwap"]:
if state["DirectToVgprB"] and state["GlobalLoadVectorWidthB"] != -1:
state["VectorWidthB"] = state["GlobalLoadVectorWidthB"]
elif state["DirectToVgprA"] and state["ProblemType"]["TLUA"] and state["LocalReadVectorWidth"] != -1:
state["VectorWidthB"] = state["LocalReadVectorWidth"]
# TT0,1 both must be multiples of VW, b/c of rC, rA, rB
if ((state["MIWaveTile"][0] % state["VectorWidth"]) != 0):
if not state["SourceSwap"]:
# non SourceSwap case, change VectorWidth to 1 for backward compatibility (before SourceSwap + VectorWidth support)
state["VectorWidth"] = 1
else:
reject(state, "MIWaveTile0(%u) should be multiple of VectorWidth(%u)" % (state["MIWaveTile"][0], state["VectorWidth"]))
return
if (state["MIWaveTile"][1] % state["VectorWidthB"]) != 0:
#reject(state, "MIWaveTile1(%u) should be multiple of VectorWidthB(%u)" % (state["MIWaveTile"][1], state["VectorWidthB"]))
#return
# change VectorWidthB to 1 for backward compatibility (before VectorWidthB support)
state["VectorWidthB"] = 1
if state["StoreRemapVectorWidth"]:
# change VectorWidth and VectorWidthB to 1 for backward compatibility (before SourceSwap + VectorWidth support)
state["VectorWidth"] = 1
state["VectorWidthB"] = 1
else:
# non MI case
# VectorWidth default handling
if state["VectorWidth"] < 1:
state["VectorWidth"] = int(4 / state["ProblemType"]["DataType"].numRegisters())
while state["ThreadTile0"] % state["VectorWidth"] != 0 \
or state["ThreadTile1"] % state["VectorWidth"] != 0:
state["VectorWidth"] //= 2
# TT0,1 both must be multiples of VW, b/c of rC, rA, rB
if state["ThreadTile0"] % state["VectorWidth"] != 0 \
or state["ThreadTile1"] % state["VectorWidth"] != 0:
reject(state, "ThreadTile0 %u or ThreadTile1 %u not a multiple of VectorWidth %u" \
% (state["ThreadTile0"], state["ThreadTile1"], \
state["VectorWidth"]))
return
# use VectorWidthB=VectorWidth for non MI case
state["VectorWidthB"] = state["VectorWidth"]
# use VectorWidthA internally
state["VectorWidthA"] = state["VectorWidth"]
if len(problemType["IndicesSummation"]) > 1:
# not supported with multiple summations, bug is maybe something with
# how stagger iteration is wrapped when unroll loop exits
state["StaggerU"] = 0
# Some restrictions for half:
if state["KernelLanguage"] == "Assembly" \
and state["ProblemType"]["DataType"].isHalf():
# Vector-width must be at least 2 for Half (since unroll loop uses packed operations?)
if (not state["EnableMatrixInstruction"]) and state["VectorWidth"] < 2:
reject(state, "VectorWidth must be >= 2 for half")
if globalParameters["ArchCaps"][isa]["HasEccHalf"]:
if not state["ProblemType"]["HighPrecisionAccumulate"] and state["AssertFree0ElementMultiple"] % 2 != 0:
# beta-on-edge has AF0EM requirement except for HPA kernels
reject(state, "Archs with HasEccHalf require AF0EM%2==0 except for HPA kernels")
# Some restrictions for int8 and fp8 or bf8:
if state["KernelLanguage"] == "Assembly" \
and (state["ProblemType"]["DataType"].isInt8() or state["ProblemType"]["DataType"].is8bitFloat()):
if (not state["EnableMatrixInstruction"]) and state["VectorWidth"] < 4:
reject(state, "VectorWidth must be >= 4 for Int8 or 8bitFloat")
#if state["KernelLanguage"] == "Assembly" and state["PackSummationDims"]:
# reject(state, "PackSummationDims does not yet support assembly")
# Default GlobalLoadVectorWidthA, B
for tc in ('A','B'):
if state["GlobalLoadVectorWidth%s"%tc] == -1:
state["GlobalLoadVectorWidth%s"%tc] = state["VectorWidth"]
if state["KernelLanguage"] == "Assembly":
# asm kernel case, GlobalLoadVectorWidth needs to be at least numElemPerReg
numElemPerReg = int(math.ceil(1 / state["ProblemType"]["DataType"].numRegisters()))
state["GlobalLoadVectorWidth%s"%tc] = max(state["VectorWidth"], numElemPerReg)
# reject - GLVWA/B too big
if (state["GlobalLoadVectorWidth%s"%tc] * state["ProblemType"]["DataType"].numBytes()) > 16:
reject(state, "GlobalLoadVectorWidth%s * DataType.numBytes() > 16"%tc)
return
# Default GlobalStoreVectorWidth
if state["StoreVectorWidth"] == -1:
#TODO : re-enable later after running testlists
#state["StoreVectorWidth"] = state["VectorWidth"]
# use wider store for best store optimization
if state["SourceSwap"]:
state["StoreVectorWidth"] = state["VectorWidth"]
elif state["ProblemType"]["DataType"].numRegisters() <= 1:
state["StoreVectorWidth"] = 4
else:
state["StoreVectorWidth"] = 4//state["ProblemType"]["DataType"].numRegisters()
if state["EnableMatrixInstruction"]:
if state["SourceSwap"]:
if ((state["VectorWidthA"] % state["StoreVectorWidth"]) != 0):
reject(state, "MFMA SourceSwap mode doesn't support vw(%u) with svw(%u)" % (state["VectorWidthA"], state["StoreVectorWidth"]))
return
else:
if ((state["MIOutputVectorWidth"] % state["StoreVectorWidth"]) != 0):
reject(state, "MFMA non-SourceSwap mode doesn't support miovw(%u) with svw(%u)" % (state["MIOutputVectorWidth"], state["StoreVectorWidth"]))
return
# reject - VW too big
if (state["VectorWidthA"] * state["ProblemType"]["DataType"].numBytes()) > 16:
reject(state, "VWA * DataType.numBytes() > 16")
return
if (state["VectorWidthB"] * state["ProblemType"]["DataType"].numBytes()) > 16:
reject(state, "VWB * DataType.numBytes() > 16")
return
# LocalSplitU too large?
numElementsPerWorkGroup = state["MacroTile0"]*state["MacroTile1"]
if numElementsPerWorkGroup < state["NumThreads"]:
reject(state, "NumElementsPerWorkGroup %u < NumThreads %u; reduce LocalSplitU" \
% (numElementsPerWorkGroup, state["NumThreads"]))
return
state["NumElementsPerThread"] = numElementsPerWorkGroup // state["NumThreads"]
state["GlobalWriteVectorWidth"] = min(state["VectorWidthA"], state["NumElementsPerThread"] )
if state["NumElementsPerThread"] % state["GlobalWriteVectorWidth"] != 0:
reject(state, "LSU NumElementsPerThread %u not divisible into GWVW %u" \
% (state["NumElementsPerThread"], state["GlobalWriteVectorWidth"]))
return
state["NumGlobalWriteVectorsPerThread"] = state["NumElementsPerThread"] \
// state["GlobalWriteVectorWidth"]
# LocalSplitU but can't NumThreads%MacroTile doesn't support sideways store
if state["LocalSplitU"] > 1:
if state["NumThreads"] % state["MacroTile0"] != 0:
reject(state, "LocalSplitU but NumThreads=%u not divisible by MT0=%u for sideways store" \
% (state["NumThreads"], state["MacroTile0"]))
return
if state["MacroTile0"]*state["MacroTile1"] % state["NumThreads"] != 0:
reject(state, "LocalSplitU but MT0*MT1=%u elements doesn't divide into NumThreads=%u" \
% (state["MacroTile0"]*state["MacroTile1"], state["NumThreads"]))
return
if (not state["EnableMatrixInstruction"]) and state["ProblemType"]["DataType"].isInt8():
reject(state, "int8 doesn't support non-MFMA + LocalSplitU")
return
if (not state["EnableMatrixInstruction"]) and state["ProblemType"]["DataType"].is8bitFloat():
reject(state, "Float8 or BFloat8 doesn't support non-MFMA + LocalSplitU")
return
# to eliminate identical/duplicate kernels when GSU=1
if state["GlobalSplitU"] == 1:
state["MinKForGSU"] = 256
state["GlobalSplitUAlgorithm"] = 'SingleBuffer' # force to use SingleBuffer for GSU=1 (instead of rejecting it)
# GlobalSplitUAtomicAdd is True
if state["GlobalSplitUAtomicAdd"]:
reject(state, " GlobalSplitU=1 and GlobalSplitUAtomicAdd=True. Rejecting to avoid duplicate kernels.")
# GlobalSplitU doesn't work with some other things:
if state["GlobalSplitU"] > 1:
if not state["GlobalSplitUSummationAssignmentRoundRobin"] and state["LoopTail"]:
reject(state, "GlobalSplitU and LoopTail require SummationAssignmentRoundRobin=True since strongly breaks Tensile kernel architecture")
return
supported = \
(state["ProblemType"]["DataType"].isSingle()) or \
(state["ProblemType"]["DataType"].is8bitFloat()) or \
(state["ProblemType"]["DataType"].isDouble() and state["BufferStore"]) or \
(state["ProblemType"]["DestDataType"].isInt32()) or \
(state["KernelLanguage"] == "Assembly" and state["ProblemType"]["ComputeDataType"].isHalf()) or \
(state["_GlobalAccumulation"]) or \
(state["EnableMatrixInstruction"]) # MFMA case, support all data types with BufferStore=0 or 1
if not supported:
reject(state, "GlobalSplitU only compatible with single, or asm and (half or mixed) precision, or EnableMatrixInstruction")
return
if state["GlobalSplitUAtomicAdd"]:
# use atomic_add for SingleBuffer algorithm
# limit to f32 + BufferStore + VAW=1 only
if not globalParameters["AsmCaps"][isa]["HasAtomicAdd"]:
reject(state, "GlobalSplitUAtomicAdd is not supported by this arch")
if state["GlobalSplitUAlgorithm"] != 'SingleBuffer':
reject(state, "GlobalSplitUAtomicAdd only compatible with SingleBuffer aloghrithm")
if not state["ProblemType"]["ComputeDataType"].isSingle():
reject(state, "GlobalSplitUAtomicAdd only compatible with single precision ComputeDataType")
if not state["BufferStore"]:
reject(state, "GlobalSplitUAtomicAdd only compatible with BufferStore")
if state["VectorAtomicWidth"] != 1:
reject(state, "GlobalSplitUAtomicAdd only compatible with VectorAtomicWidth=1")
# print warning message if GlobalSplitUAtomicAdd is enabled
printWarning("Using GlobalSplitUAtomicAdd is not recommended")
# set minimum and maximum of VectorAtomicWidth
minVectorAtomicWidth = 2 if (state["ProblemType"]["ComputeDataType"].numBytes() == 2) else 1
if state["GlobalSplitUAtomicAdd"]:
maxVectorAtomicWidth = minVectorAtomicWidth
else:
# cmpswap_b64 is applicable only for bpe>4 data types due to alignment restriction
# atomicAdd case, Wdth=1 only.
# TODO: add VectorAtomicWidth=2 support for smaller data types by introducing alignment assertion
# maximum is b64 (8 byte)
#computeBytes = state["ProblemType"]["ComputeDataType"].numBytes()
#maxVectorAtomicWidth = (8 // computeBytes) if computeBytes <= 8 else 1
maxVectorAtomicWidth = minVectorAtomicWidth
useAtomic = state["GlobalSplitU"] > 1 and state["GlobalSplitUAlgorithm"] == 'SingleBuffer'
if state["VectorAtomicWidth"] == -1:
if useAtomic:
# atomic case, use max
state["VectorAtomicWidth"] = maxVectorAtomicWidth
else:
# not atomic case, this is not used. Set min
state["VectorAtomicWidth"] = minVectorAtomicWidth
if state["VectorAtomicWidth"] < minVectorAtomicWidth:
reject(state, "VectorAtomicWidth should not be smaller than min(=%u)"%minVectorAtomicWidth)
if state["VectorAtomicWidth"] > maxVectorAtomicWidth:
reject(state, "VectorAtomicWidth should not be larger than max(=%u)"%maxVectorAtomicWidth)
if (not useAtomic) and state["VectorAtomicWidth"] > minVectorAtomicWidth:
reject(state, "Rejecting (GlobalSplitU=1 or MultipleBuffer) and VectorAtomicWidth>min(=%u) to avoid duplicate kernels."%minVectorAtomicWidth)
if useAtomic and state["VectorAtomicWidth"] > state["GlobalWriteVectorWidth"]:
reject (state, "GSU + SingleBuffer + VectorAtomicWidth(%u) > GlobalWriteVectorWidth(%u) not supported"%(state["VectorAtomicWidth"], state["GlobalWriteVectorWidth"]))
if state["ProblemType"]["DataType"].isHalf() and state["KernelLanguage"] == "Assembly":
if (not state["EnableMatrixInstruction"]) and state["VectorWidth"] < 2:
reject(state, "Assembly half requires VectorWidth >= 2 for non-MFMA mode")
if state["GlobalSplitU"] > 1 and (not state["_GlobalAccumulation"]):
if state["VectorAtomicWidth"] < 2:
reject(state, "Assembly GSU half requires VectorWidth >= 2 (for 32-bit CAS)")
if state["AssertFree0ElementMultiple"] < 2:
reject(state, "Assembly GSU half requires AF0EM>=2 (for atomics on edge tiles)")
if state["EnableMatrixInstruction"] and globalParameters["AsmCaps"][isa]['HasWMMA']:
reject(state, "Half WMMA doesn't support single buffer GSU")
return
########################################
# Initial DepthU
########################################
userDepthU = state["DepthU"]
# DepthU == -1 means glvw=1
if state["DepthU"] == -1:
if state["MacroTile0"] != state["MacroTile1"]:
reject(state, "DepthU=0 requires square MacroTile")
return
if userDepthU < 0:
depthU = 2
depthULds = 2
maxDepthU = globalParameters["MaxDepthU"]
numOfWaves = state["NumThreads"] // state["WavefrontSize"]
if state["ProblemType"]["TLUA"] and state["WaveSeparateGlobalReadA"]:
depthU = max(depthU, numOfWaves)
if state["ProblemType"]["TLUB"] and state["WaveSeparateGlobalReadB"]:
depthU = max(depthU, numOfWaves)
else:
depthU = userDepthU
depthULds = userDepthU//state["DepthULdsDivisor"]
maxDepthU = userDepthU
state["_DepthULds"] = state["DepthU"]//state["DepthULdsDivisor"] # internal
# Default LocalReadVectorWidth
if state["LocalReadVectorWidth"] == -1:
if state["EnableMatrixInstruction"]:
state["LocalReadVectorWidth"] = state["MIInputPerThread"]
else:
state["LocalReadVectorWidth"] = state["VectorWidth"]
# reject if FractionalLoad and depthU is not power of 2 (does not work)
if state["FractionalLoad"] and (depthU & (depthU - 1)) != 0:
reject(state, "FractionalLoad requires DepthU = power of 2")
########################################
# Search DepthU
# Inputs:
# - depthU, userDepthU, state["LocalSplitU"], state["InnerUnroll"], state["MacroTile0/1"], state["GlobalLoadVectorWidthA/B"]
# - state["MatrixInstK"], ...
# Outputs:
# - totalVectorsCoalescedA, totalVectorsCoalescedB, totalElementsPerpA, totalElementsPerpB, state["DepthU"]
#######################################
GlobalLoadVectorWidthAorig = state["GlobalLoadVectorWidthA"] # keep original value
GlobalLoadVectorWidthBorig = state["GlobalLoadVectorWidthB"] # keep original value
while True: # exit criteria at end
validDepthU = True
# peek LoopIters
loopIters = (depthULds // state["LocalSplitU"]) // state["InnerUnroll"]
if "MatrixInstK" in state:
loopIters //= state["MatrixInstK"]
if loopIters < 1:
reject(state, "LoopIters need to greater than 0")
return
# Make sure the prefetch VGPR index plr[x] can be aligned for each loop
# for example, if PLR3 result in 4 VGPR:
# PGR - pre : plr[0], plr[1], plr[2]
# loop - iter0: plr[3], iter1: plr[0], iter2: plr[1], iter3: plr[2] -> restart LOOP (from plr[3]...) -> OK
#
# but if PLR2 result in 3 VGPR:
# PGR - pre : plr[0], plr[1]
# loop - iter0: plr[2], iter1: plr[0], iter2: plr[1], iter3: plr[2] -> restart LOOP (from plr[2]...) -> !!
if (depthULds % ((state["PrefetchLocalRead"]%loopIters)+1)) != 0:
validDepthU = False
# how many elements to load
if state["ProblemType"]["TLUA"]:
totalElementsCoalescedA = state["MacroTileA"]
totalElementsPerpA = depthU
else:
totalElementsCoalescedA = depthU
totalElementsPerpA = state["MacroTileA"]
if state["ProblemType"]["TLUB"]:
totalElementsCoalescedB = state["MacroTileB"]
totalElementsPerpB = depthU
else:
totalElementsCoalescedB = depthU
totalElementsPerpB = state["MacroTileB"]
totalElementsA = totalElementsCoalescedA * totalElementsPerpA
totalElementsB = totalElementsCoalescedB * totalElementsPerpB
if state["FractionalLoad"]:
if not Solution.setGlobalLoadTileDimFractional(state, "A", depthU, GlobalLoadVectorWidthAorig):
validDepthU = False
if not Solution.setGlobalLoadTileDimFractional(state, "B", depthU, GlobalLoadVectorWidthBorig):
validDepthU = False
else:
GlobalLoadVectorWidthA = GlobalLoadVectorWidthAorig
# TODO: Enable DTLx2 and DTLx4 for gfx950
#if not globalParameters["ArchCaps"][isa]["HasDTLx4"]:
if state["DirectToLdsA"] and (bpeAB * GlobalLoadVectorWidthA) > 4:
# bpe * grvw must be <= 4 for DirectToLds (lds flag only for <= 32bit load)
GlobalLoadVectorWidthA = 4 / bpeAB
# use float only for <1. Otherwise, convert to int
if GlobalLoadVectorWidthA >= 1:
GlobalLoadVectorWidthA = int(GlobalLoadVectorWidthA)
if not Solution.setGlobalLoadVectorWidth(state, "A", totalElementsA, GlobalLoadVectorWidthA):
validDepthU = False
GlobalLoadVectorWidthB = GlobalLoadVectorWidthBorig
# TODO: Enable DTLx2 and DTLx4 for gfx950
#if not globalParameters["ArchCaps"][isa]["HasDTLx4"]:
if (not state["DirectToVgprB"]) and state["DirectToLdsB"] and (bpeAB * GlobalLoadVectorWidthB) > 4:
# bpe * grvw must be <= 4 for DirectToLds
GlobalLoadVectorWidthB = 4 / bpeAB
# use float only for <1. Otherwise, convert to int
if GlobalLoadVectorWidthB >= 1:
GlobalLoadVectorWidthB = int(GlobalLoadVectorWidthB)
if not Solution.setGlobalLoadVectorWidth(state, "B", totalElementsB, GlobalLoadVectorWidthB):
validDepthU = False
if validDepthU and state["KernelLanguage"] == "Assembly" \
and (state["ProblemType"]["DataType"].isHalf() \
or state["ProblemType"]["DataType"].isBFloat16()):
if globalParameters["ArchCaps"][isa]["HasEccHalf"]:
if state["GlobalLoadVectorWidthA"] <= 1 or state["GlobalLoadVectorWidthB"] <= 1:
reject(state, "HalfEcc requires GLVWA > 1")
# TODO- Need this restrict ?
if validDepthU and state["KernelLanguage"] == "Assembly" \
and (state["ProblemType"]["DataType"].isInt8() or state["ProblemType"]["DataType"].is8bitFloat()):
if state["GlobalLoadVectorWidthA"] < 4:
reject(state, "Int8 requires GLVWA >= 4, current is %u"%state["GlobalLoadVectorWidthA"])
if state["GlobalLoadVectorWidthB"] < 4:
reject(state, "Int8 requires GLVWB >= 4, current is %u"%state["GlobalLoadVectorWidthB"])
# Now convert elements to vectors based on GlobalLoadVectorWidth
GlobalLoadVectorWidthA = GlobalLoadVectorWidthAorig if state["GlobalLoadVectorWidthA"] == 0 else state["GlobalLoadVectorWidthA"]
GlobalLoadVectorWidthB = GlobalLoadVectorWidthBorig if state["GlobalLoadVectorWidthB"] == 0 else state["GlobalLoadVectorWidthB"]
totalVectorsCoalescedA = int(totalElementsCoalescedA / GlobalLoadVectorWidthA)
totalVectorsCoalescedB = int(totalElementsCoalescedB / GlobalLoadVectorWidthB)
totalVectorsA = int(totalElementsA / GlobalLoadVectorWidthA)
totalVectorsB = int(totalElementsB / GlobalLoadVectorWidthB)
if 0:
print("info:", pvar(state, "NumThreads"), pvar(state, "DepthU"), pvar(state, "DepthULdsDivisor"),
"TT=%ux%u" % (state["ThreadTile0"], state["ThreadTile1"]),
"WG=%ux%u" % (state["WorkGroup"][0], state["WorkGroup"][1]),
"MT=%ux%u" % (state["MacroTile0"], state["MacroTile1"]))
print("info: totalElementsCoalescedA=", totalElementsCoalescedA,
" totalVectorsCoalescedA=", totalVectorsCoalescedA, " totalVectorsA=", totalVectorsA)
print("info: totalElementsCoalescedB=", totalElementsCoalescedB,
" totalVectorsCoalescedB=", totalVectorsCoalescedB, " totalVectorsB=", totalVectorsB)
print("info", pvar(state, "VectorWidth")
, pvar(state, "GlobalLoadVectorWidthA"), pvar(state, "GlobalLoadVectorWidthB"))
#if state["ProblemType"]["DataType"].isHalf() \
# and (state["GlobalLoadVectorWidthA"] == 1 \
# or state["GlobalLoadVectorWidthB"] == 1):
# validDepthU = False
if not state["FractionalLoad"]:
if userDepthU == -1: # no vectors
if state["GlobalLoadVectorWidthA"] != 1 \
or state["GlobalLoadVectorWidthB"] != 1:
validDepthU = False
elif userDepthU == -2:
if state["GlobalLoadVectorWidthA"] < GlobalLoadVectorWidthAorig and \
state["GlobalLoadVectorWidthB"] < GlobalLoadVectorWidthBorig:
validDepthU = False
elif userDepthU <= -3:
if state["GlobalLoadVectorWidthA"] < GlobalLoadVectorWidthAorig or \
state["GlobalLoadVectorWidthB"] < GlobalLoadVectorWidthBorig:
validDepthU = False
if validDepthU:
if not state["ProblemType"]["TLUA"]:
if depthU < state["GlobalLoadVectorWidthA"]:
validDepthU = False
if not state["ProblemType"]["TLUB"]:
if depthU < state["GlobalLoadVectorWidthB"]:
validDepthU = False
if validDepthU:
# check depthU and ThreadSeparateGlobalReadA==1 depthU*bpe <= 64 bytes reject ThreadSeparateGlobalRead =1 (TLU=0 only)
# reject depthU for cases requiring < minimum lanes per fragment. depthU * bpe must be multiple of cache-line sizes(l2)
# minimum lane is 4//bpe for bpe < 4, 1 for bpe >= 4
minLanes = max( 4 // state["ProblemType"]["DataType"].numBytes(), 1)
depthULds = depthU // state["DepthULdsDivisor"]
if state["ThreadSeparateGlobalReadA"] and (not state["ProblemType"]["TLUA"]):
#if state["ThreadSeparateGlobalReadA"] and (((depthU//state["GlobalLoadVectorWidthA"])// (2 * state["ThreadSeparateGlobalReadA"])) < 2):
if (depthU < minLanes * state["GlobalLoadVectorWidthA"] * (2 * state["ThreadSeparateGlobalReadA"])):
validDepthU= False
# reject if KelementsPerMFrag (= (_DepthULds // (ThreadSeparateGlobalRead * 2))) < inputPerThread = max(lrvwA,lrvwB)
if (depthULds // (2 * state["ThreadSeparateGlobalReadA"])) < state["LocalReadVectorWidth"]:
validDepthU= False
if state["ThreadSeparateGlobalReadB"] and (not state["ProblemType"]["TLUB"]):
#if state["ThreadSeparateGlobalReadB"] and (((depthU//state["GlobalLoadVectorWidthB"])// (2 * state["ThreadSeparateGlobalReadB"])) < 2):
if (depthU < minLanes * state["GlobalLoadVectorWidthB"] * (2 * state["ThreadSeparateGlobalReadB"])):
validDepthU= False
# reject if NblockSizePerLoad (= (waveWidth * GlobalLoadVectorWidthB // depthULds)) > MatrixInstN
if (state["WavefrontSize"] * state["GlobalLoadVectorWidthB"] // depthULds > state["MatrixInstN"]):
validDepthU= False
# reject if KelementsPerMFrag (= (_DepthULds // (ThreadSeparateGlobalRead * 2))) < inputPerThread = max(lrvwA,lrvwB)
if (depthULds // (2 * state["ThreadSeparateGlobalReadB"])) < state["LocalReadVectorWidth"]:
validDepthU= False
# this depthU is valid, done unless user wants to double (for TN)
if validDepthU:
if userDepthU < -3: # for every int below -3, use next doubled value
userDepthU += 1
depthU *= 2
depthULds = 2
continue
else: # use this found value
state["DepthU"] = depthU
state["_DepthULds"] = depthU//state["DepthULdsDivisor"]
break
# this depthU not valid
else:
# keep looking
if depthU < maxDepthU:
depthU += 2
depthULds = depthU//state["DepthULdsDivisor"]
continue
# give up
else:
reject(state, "No valid DepthU found")
return
########################################
# end DepthU loop
########################################
assert(state["DepthU"]> 0)
if state["UnrollIncIsDepthU"] or state["PackSummationDims"] == 1 \
or bool(problemType["ZeroPadA"]) or bool(problemType["ZeroPadB"]):
# unrollIncIsDepthU does not support tail loop, so add asem requirement to reject
# problems that require tail loop.
if state["DepthU"] % state["AssertSummationElementMultiple"] != 0:
reject(state, "PackSummationDims=1 requires DepthU is integer multiple of ASEM")
else:
state["AssertSummationElementMultiple"] = state["DepthU"]
# not supported with PSD, has some interaction with iter
state["StaggerU"] = 0
if not state["FractionalLoad"]:
if not Solution.setGlobalLoadTileDimClassic(state, "A", state["NumLoadsA"], \
totalVectorsCoalescedA, totalElementsPerpA):
return
if not Solution.setGlobalLoadTileDimClassic(state, "B", state["NumLoadsB"], \
totalVectorsCoalescedB, totalElementsPerpB):
return
# set UnrollMajorLDSA,B before isDirectToVgprDoable
state["UnrollMajorLDSA"] = (state["TransposeLDS"] and (not state["ProblemType"]["TLUA"])) or state["UnrollMajorLDSA"]
state["UnrollMajorLDSB"] = (state["TransposeLDS"] and (not state["ProblemType"]["TLUB"])) or state["UnrollMajorLDSB"]
# TODO
if (0 and state["LSCA"] % state["GlobalLoadVectorWidthA"] != 0):
reject(state, "lsca % grvw != 0")
return
if (0 and state["LSPA"] % state["GlobalLoadVectorWidthA"] != 0):
reject(state, "lspa % grvw != 0")
return
if (0 and state["LSCB"] % state["GlobalLoadVectorWidthB"] != 0):
reject(state, "lscb % grvw != 0")
return
if (0 and state["LSPB"] % state["GlobalLoadVectorWidthB"] != 0):
reject(state, "lspb % grvw != 0")
return
state["LVCA"] = roundupRatio(state["LSCA"] , state["GlobalLoadVectorWidthA"])
state["LVPA"] = roundupRatio(state["LSPA"] , state["GlobalLoadVectorWidthA"])
state["LVCB"] = roundupRatio(state["LSCB"] , state["GlobalLoadVectorWidthB"])
state["LVPB"] = roundupRatio(state["LSPB"] , state["GlobalLoadVectorWidthB"])
for tc in ('A','B'):
if problemType["TLU%s"%tc]:
pos = problemType["IndexAssignments%s"%tc].index(problemType["Index01%s"%tc])
else:
pos = problemType["IndexAssignments%s"%tc].index(problemType["IndexUnroll"])
unitStride = False
stride = -1
stride = state["AssertStride%sEqual"%tc].get(pos,-1)
if stride==1:
unitStride = True
if not unitStride and state["GlobalLoadVectorWidth%s"%tc] != 1:
reject(state,
"Non-unit stride(%s) for coalesced dimension (index=%d) requires GlobalLoadVectorWidth%s==1" \
% ("TBD" if stride==-1 else str(stride), \
problemType["IndexAssignments%s"%tc][pos], tc))
for p in state["AssertStride%sEqual"%tc].keys():
if p>len(problemType["IndexAssignments%s"%tc]):
raise RuntimeError ("AssertStride%sEqual index position %d is > len(IndexAssignments%s)" % \
tc, p, tc)
maxIndex = max(problemType["IndexAssignmentsA"] + problemType["IndexAssignmentsB"])
for p in state["AssertSizeEqual"]:
if p>maxIndex:
raise RuntimeError ("AssertSize index position=%d is > maxIndex=%d" % (p, maxIndex))
# Some of these might become 0?
if 0:
print("info: ", pvar(state, "LVCA"), pvar(state, "LVPA"), \
pvar(state, "LVCB"), pvar(state, "LVPB"))
# lds buffer size for A, B
if state["KernelLanguage"] == "Source":
# source kernel
# use 0 for auto adjust (-1)
for tc in ('A','B'):
if state["LdsBlockSizePerPad%s"%tc] == -1:
state["LdsBlockSizePerPad%s"%tc] = 0
if state["LdsPad%s"%tc] == -1:
state["LdsPad%s"%tc] = 0
if state["LdsPadA"] != state["LdsPadB"]:
reject(state, "Source KernelLanguage only supports LdsPadA == LdsPadB")
return
########################################
# LDS
########################################
# allow LocalReadVectorWidthB > 1 for TLUB + MatrixInstruction (this is applicable for B only)
# some more limitations necessary to make this logic work
# - MatrixInstruction
# - TLUB and not UnrollMajorLDSB
# - MIInputPerThread == 1
# - SourceSwap
# - DirectToVgprA
# need to check after state["LocalReadVectorWidth"] = -1 is resolved
wideLRVWBforTLUBallowed = False
if state["EnableMatrixInstruction"] and \
state["ProblemType"]["TLUB"] and (not state["UnrollMajorLDSB"]) and \
state["MIInputPerThread"] == 1 and state["SourceSwap"]:
if state["DirectToVgprA"] and state["ProblemType"]["TLUA"] and state["VectorWidthB"] == state["LocalReadVectorWidth"]:
wideLRVWBforTLUBallowed = True
# LocalReadVectorWidth check
if state["EnableMatrixInstruction"]:
if state["LocalReadVectorWidth"] < state["MIInputPerThread"]:
reject(state, "LocalReadVectorWidth < %u" %(state["MIInputPerThread"]))
if state["LocalReadVectorWidth"] > state["MIInputPerThread"] and not (state["UnrollMajorLDSA"] or state["UnrollMajorLDSB"]) \
and not (state["DirectToVgprA"] and state["LocalReadVectorWidth"] == state["VectorWidthB"] and wideLRVWBforTLUBallowed):
reject(state, "LocalReadVectorWidth require Transpose LDS")
if state["LocalReadVectorWidth"] > state["MIInputPerThread"] and \
(state["UnrollMajorLDSA"] and (not state["UnrollMajorLDSB"])) and \
state["DirectToVgprA"]:
reject(state, "LocalReadVectorWidth + DirectToVgprA + does not work for TT")
else:
if state["LocalReadVectorWidth"] != state["VectorWidth"]:
reject(state, "LocalReadVectorWidth must equal VectorWidth for non MI kernels")
# Determine if we can load directly-to-Vgpr
# need to check after state["LocalReadVectorWidth"] = -1 is resolved
if state["DirectToVgprA"]:
if not Solution.isDirectToVgprDoable(state, 'A'):
return # rejected
# disable DTL
state["DirectToLdsA"] = False
if state["DirectToVgprB"]:
if not Solution.isDirectToVgprDoable(state, 'B'):
return # rejected
# disable DTL
state["DirectToLdsB"] = False
# LDS padding
# Resolve -1 before isDirectToLdsDoable check
Solution.ldsPaddingAuto(state, isa)
# Determine if we can load directly-to-LDS.
# Transpose requires a trip through registers to perform the transpose so can't use DirectToLdsA
# LDS loads always write 4 bytes apart so can use only 4-byte operations
# The matrix must not require transposing since that is done by reading to VGPR and writing in different order
# The LSC (load size coalesced) must load some multiple of 256 bytes since that is what each DirectToLds load provides
# Note for these matrices LSC is same as MacroTile dim
# MatrixInstruction rules:
# DirectToLDS is supported for TLU=0 (make sure transposeLDS=1)
# LDS (load size coalesced) * LSPA must load some multiple of 256 bytes.
# No longer support loadX2/loadx4 .
# need to check after DirectToVgpr check
for tc in ('A','B'):
if state["DirectToLds%s"%tc]:
if Solution.isDirectToLdsDoable(state, tc):
state["LocalWriteUseSgpr%s"%tc] = True
else:
return # rejected
if state["1LDSBuffer"] == -1 and (state["DirectToLdsA"] or state["DirectToLdsB"]):
#1LDS buffer must be 0 for DirectToLdsA
state["1LDSBuffer"] = 0
# set NoLdsWriteCode if both A and B use DirectToLds or DirectToVgpr
state["NoLdsWriteCode"] = False
if (state["DirectToVgprA"] or state["DirectToLdsA"]) and (state["DirectToVgprB"] or state["DirectToLdsB"]):
state["NoLdsWriteCode"] = True
# noTailLoop condition check
# need to check after DirectToVgpr and DirectToLds check
# no tail loop optimization setting
# noTailLoop=1: remove TailLoop
# noTailLoop=2: remove TailLoop and generate TailLoop in NoLoadLoop with early exit
# noTailLoop=3: remove TailLoop and generate TailLoop in NoLoadLoop without early exit
# here, only check condition 1
# Reject the following cases if noTailLoop is not enabled
# - PrefetchAcrossPersistent and PrefetchAcrossPersistentMode
# PrefetchAcrossPersistentMode does not support TailLoop (TLU is necessary for NoTailLoop)
# - DirectToLds + TLU + NumLoadsCoalesced > 1 (special local read offset conversion is not implemented in tail loop code)
# - DirectToLds + LRVW > 1
# global load width for tail loop (based on AssertFree0, 1 or AssertSummationElementMultiple)
asem = state["AssertSummationElementMultiple"]
# need to adjust asem for GSU
gsu = state["GlobalSplitU"]
asemDivGSU = 1 if asem%gsu !=0 else asem//gsu
noTailLoop = 0
if (asemDivGSU % state["DepthU"] == 0):
noTailLoop = 1
# reject conditions for noTailLoop==0
rejected = False
rejectMessage = ""
if state["PersistentKernel"] and state["PrefetchAcrossPersistent"] and state["PrefetchAcrossPersistentMode"] == 1:
rejectMessage = "PK + PAP + PAPMode"
rejected = True
elif (state["DirectToLdsA"] or state["DirectToLdsB"]):
if (not rejected) and state["EnableMatrixInstruction"] and state["LocalReadVectorWidth"] > state["MIInputPerThread"]:
rejectMessage = "DirectToLds + LocalReadVectorWidth>MIInputPerThread"
rejected = True
for tc in ('A','B'):
if (not rejected) and state["ProblemType"]["TLU%c"%tc] and state["NumLoadsCoalesced%c"%tc] > 1:
rejectMessage = "DirectToLds + TLU%c + NumLoadsCoalesced%c>1"%(tc, tc)
rejected = True
if noTailLoop == 0 and rejected:
# if reject condition for NoTailLoop is true and NoTailLoop is not enabled, reject this kernel
rejectMessage += " requires NoTailLoop."
rejectMessage += "\n" + "To enable NoTailLoop, "
rejectMessage += "\n" + " - AssertSummationElementMultiple/GlobalSplitU) is multiple of DepthU or"
rejectMessage += "\n" + " - BufferLoad and MatrixInstruction + MatrixInstK > 1 and"
rejectMessage += "\n" + " (global read width for TailLoop decided by assert is multiple of GlobalLoadVectorWidth) and"
rejectMessage += "\n" + " (StaggerU = 0 or NT(+BufferLoad))"
reject(state, rejectMessage)
return
# reject condition for PAPM + PGR=2
# (need to check after DepthU calculation (for negative value) is done)
if state["PersistentKernel"] and state["PrefetchAcrossPersistent"] and state["PrefetchAcrossPersistentMode"] == 1 and \
state["PrefetchGlobalRead"] == 2:
# PAPM + PGR=2 requires at least 2 loop iterations to execute global read address calculation in NGLL
# it means K should be >= DepthU * 2 (means larger than DepthU * 2 - 1)
if not (3 in state["AssertSizeGreaterThan"].keys() and state["AssertSizeGreaterThan"][3] >= state["DepthU"] * 2 - 1):
reject(state, "PAPM + PGR=2 does not work if AssertSizeGreaterThan for K is not greater than DepthU * 2 - 1")
return
#ldsNumElementsA = Solution.getLdsNumElements(state, 'A') # not used
ldsNumElementsB = Solution.getLdsNumElements(state, 'B')
ldsNumElementsAlignedA = Solution.getLdsNumElementsAligned(state, 'A')
ldsNumElementsAlignedB = Solution.getLdsNumElementsAligned(state, 'B')
# todo, can the alignment be a power of 2?
state["LdsOffsetA"] = 0
if state["PrefetchGlobalRead"]:
state["LdsNumElementsAlignedA"] = ldsNumElementsAlignedA
state["LdsNumElementsAlignedB"] = ldsNumElementsAlignedB
state["LdsOffsetB"] = state["LdsOffsetA"] + state["LdsNumElementsAlignedA"]
offsetBlk = state["LdsOffsetB"] + ldsNumElementsAlignedB
if offsetBlk>0: # need 0 check to avoid an error
offsetBlk = int(2**(math.ceil(math.log(offsetBlk, 2))))
state["LdsOffsetA_Blk"] = offsetBlk
state["LdsOffsetB_Blk"] = state["LdsOffsetA_Blk"] + state["LdsNumElementsAlignedA"]
ldsNumElementsAB = state["LdsOffsetB_Blk"]+ ldsNumElementsB
else:
state["LdsOffsetB"] = ldsNumElementsAlignedA
ldsNumElementsAB = ldsNumElementsAlignedA + ldsNumElementsB
# lds buffer size for reduction
bytesPerComElem = state["ProblemType"]["ComputeDataType"].numBytes()
bytesPerLoadElem = state["ProblemType"]["DataType"].numBytes()
multiplier = bytesPerComElem // bytesPerLoadElem
ldsNumElementsReduction = multiplier*state["LocalSplitU"]*state["MacroTile0"]*state["MacroTile1"] if state["LocalSplitU"] > 1 else 0
# lds max occupancy
ldsSizeOccupancy = globalParameters["DeviceLDS"] // state["MaxOccupancy"]
ldsNumElementsOccupancy = ldsSizeOccupancy // state["ProblemType"]["DestDataType"].numBytes()
#print("ldsNumElementsA", ldsNumElementsA)
#print("ldsNumElementsB", ldsNumElementsB)
#print("ldsNumElementsAlignedA", ldsNumElementsAlignedA)
#print("ldsNumElementsAlignedB", ldsNumElementsAlignedB)
#print("ldsNumElementsAB", ldsNumElementsAB)
if state["EnableMatrixInstruction"]:
if (state["DirectToLdsA"] or state["DirectToLdsB"]) and state["1LDSBuffer"]:
reject(state, "1LDSBuffer must be 0 for directToLds")
# ThreadSeparateGlobalRead + no DirectToLds case (equivalent to previous SplitGlobalRead)
# No local read offset conversion in this case. A and B must have equivalent ThreadSeparateGlobalRead setting
for tc in ('A','B'):
if (not (state["DirectToLds%s"%tc] and state["ProblemType"]["TLU%s"%tc] == False)) and state["ThreadSeparateGlobalRead%s"%tc]:
divisorName = Solution.getDivisorName(state, tc)
divisor = state[divisorName]
tsgrNum = state["ThreadSeparateGlobalRead%s"%tc] * 2
if tsgrNum >= divisor:
reject(state, "ThreadSeparateGlobalRead * 2 (=%d) must be less than lvc/lsc/lvp/lsp"%(tsgrNum))
if divisor > state["WavefrontSize"]:
reject(state, "WavefrontSize >= divisor(lvc/lsc/lvp/lsp) is required for ThreadSeparateGlobalRead")
if state["DirectToVgpr%s"%tc]:
# set TSGR = 0 if DirectToVgpr is enabled
state["ThreadSeparateGlobalRead%s"%tc] = 0
if state["1LDSBuffer"] == -1:
if ldsNumElementsAB * state["ProblemType"]["DataType"].numBytes() > globalParameters["MaxLDS"]:
state["1LDSBuffer"] = 1
else:
state["1LDSBuffer"] = 0
if state["1LDSBuffer"]:
if not state["PrefetchGlobalRead"]:
reject(state, "PGR=0 already use 1 LDS buffer only")
# Should be able to support as long as NO scheduleLocalWrite
if (not state["ScheduleIterAlg"] == 2) and (not state["ScheduleIterAlg"] == 3) and (state["ScheduleLocalWrite"]):
reject(state, "1LDSBuffer only support SIA2 or SIA3, or SIA1 without SLW")
state["LdsOffsetB"] = ldsNumElementsAlignedA
ldsNumElementsAB = ldsNumElementsAlignedA + ldsNumElementsB
# lds size is the greater of the two
ldsNumElements = max(ldsNumElementsAB, ldsNumElementsReduction, ldsNumElementsOccupancy)
if state["StoreRemapVectorWidth"] == -1:
# use de_read_b64 as default in storeRemap to avoid bank conflict
defaultRemap = 8 // state["ProblemType"]["DestDataType"].numBytes()
defaultRemap = max(defaultRemap, state["MacroTile0"]//state["WavefrontSize"])
ldsRemapPad = max(defaultRemap, state["MIOutputVectorWidth"])
ldsNumElementsRemapC = (state["MacroTile0"]+ldsRemapPad)* state["MatrixInstN"] * state["MIWaveGroup"][1]
if state["_GlobalAccumulation"]:
computeBytes = state["ProblemType"]["ComputeDataType"].numBytes()
ldsNumElementsRemapC *= (computeBytes / state["ProblemType"]["DestDataType"].numBytes())
ldsSize = ldsNumElementsRemapC * state["ProblemType"]["DestDataType"].numBytes()
if not math.log(state["MacroTile0"],2).is_integer() or \
ldsSize > globalParameters["MaxLDS"] or \
state["SourceSwap"] or \
(state["GlobalSplitU"] > 1) and (state["_GlobalAccumulation"] != 'MultipleBuffer') or \
state["MatrixInstBN"] > 1 and state["MatrixInstN"] == 4:
state["StoreRemapVectorWidth"] = 0
else:
state["StoreRemapVectorWidth"] = defaultRemap
# GuaranteeNoPartial
if state["ProblemType"]["TLUA"]:
state["GuaranteeNoPartialA"] = state["AssertFree0ElementMultiple"]%state["GlobalLoadVectorWidthA"]==0
else:
state["GuaranteeNoPartialA"] = True
if state["ProblemType"]["TLUB"]:
state["GuaranteeNoPartialB"] = state["AssertFree1ElementMultiple"]%state["GlobalLoadVectorWidthB"]==0
else:
state["GuaranteeNoPartialB"] = True
# SourceSwap
if state["SourceSwap"]:
if not state["EnableMatrixInstruction"]:
reject(state, "SourceSwap only applies to MatrixInstruction kernels")
return
if state["StoreRemapVectorWidth"]:
reject(state, "SourceSwap not compatible with StoreRemap")
return
# non-SourceSwap+MFMA 4x4 check
if (not state["SourceSwap"]) and state["EnableMatrixInstruction"]:
if state["MatrixInstBM"] > 1 and state["MatrixInstN"] == 4 and (state["MatrixInstM"] > state["MIOutputVectorWidth"]) and \
state["AssertFree0ElementMultiple"] % state["GlobalLoadVectorWidthA"] != 0:
reject(state, "MI4x4 + non-SourceSwap + MatrixInstBM > 1 + MatrixInstN == 4 + MatrixInstM > MIOutputVectorWidth \
AssertFree0ElementMultiple %% bGlobalLoadVectorWidthA != 0 not supported")
return
# check if need to use lds init Acc vgprs
state["LdsInitCVgprs"] = False
if globalParameters["ArchCaps"][isa]["HasAccCD"] and \
state["EnableMatrixInstruction"] and state["StorePriorityOpt"] and \
state["ProblemType"]["DataType"].isDouble():
state["LdsInitCVgprs"] = True
# force MIArchVgpr when using WMMA
if state["EnableMatrixInstruction"] and globalParameters["AsmCaps"][isa]["HasWMMA"]:
state["MIArchVgpr"] = True
if state["MIArchVgpr"]:
if not state["EnableMatrixInstruction"]:
reject(state, "MIArchVgpr only support for MatrixInstruction")
return
if not (globalParameters["AsmCaps"][isa]["HasMFMA_vgpr"] or globalParameters["AsmCaps"][isa]["HasWMMA"]):
reject(state, "MIArchVgpr is not supported by this arch")
return
if globalParameters["AsmCaps"][isa]["HasMFMA"]:
if not (state["ProblemType"]["ComputeDataType"].isDouble() or \
state["ProblemType"]["ComputeDataType"].isSingle() or \
(state["ProblemType"]["ComputeDataType"].isHalf() and state["ProblemType"]["HighPrecisionAccumulate"]) or \
state["ProblemType"]["ComputeDataType"].isInt32() or \
state["ProblemType"]["ComputeDataType"].isComplex()):
reject(state, "MIArchVgpr now only support fp64, fp64c, fp32, fp32c, fp16, int8 MatrixInstruction.")
return
if state["ProblemType"]["ComputeDataType"].isSingleComplex() and (not globalParameters["AsmCaps"][isa]["v_fma_f32"]):
reject(state, "MIArchVgpr + fp32c requires v_fma_f32.")
return
if state["AtomicAddC"]:
if not state["ProblemType"]["DataType"].isDouble():
reject(state, "AtomicAddC currently only available for dgemm")
return
if state["AssertBetaValue"] != 1:
reject(state, "AtomicAddC requires AssertBetaValue = 1")
return
if not state["AssertCEqualsD"]:
reject(state, "AtomicAddC requires AssertCEqualsD")
return
if state["ProblemType"]["Fp16AltImpl"]:
if not (state["ProblemType"]["DataType"].isHalf() and \
state["ProblemType"]["HighPrecisionAccumulate"] and \
state["EnableMatrixInstruction"]):
reject(state, "Fp16AltImpl requires FP16 HPA MFMA")
return
if state["ProblemType"]["StochasticRounding"]:
if not (state["ProblemType"]["DataType"].is8bitFloat()):
reject(state, "StochasticRounding requires F8 types")
return
#check not support cases and calculate lds resources
if state["StoreRemapVectorWidth"]:
if not state["BufferStore"]:
reject(state, "storeRemap only support BufferStore")
return
if not state["EnableMatrixInstruction"]:
reject(state, "storeRemap only support MatrixInstruction kernel")
return
if (state["GlobalSplitU"] > 1) and (state["_GlobalAccumulation"] != 'MultipleBuffer'):
reject(state, "storeRemap doesn't support GlobalSplitU yet, except GSU algorithm 2")
return
if packedC0 or packedC1:
reject(state, "storeRemap doesn't support packedC0 and packedC1 yet")
return
if state["MatrixInstBN"] > 1 and state["MatrixInstN"] == 4:
reject(state, "storeRemap doesn't support MI4x4 multi blocks in N direction yet")
return
if not math.log(state["MacroTile0"],2).is_integer():
reject(state, "storeRemap only supports power-of-2 MT0")
# TODO - this return should be here, but this is a hotfix,
# Somehow we have a "Validation Failed" kernel in rocBLAS now (SRVW=4 and MT0=96) and this will stop the whole building process
# Actions: 1. Hotfix, comment out this "return" temporarily for that invalidated kernel
# 2. Remove / replace that invalidated kernel
# 3. Put back this return
# 4. How to design a better way to prevent from invalid kernel in rocBLAS?
# return
storeInstMinWidth = 1 # minimum dwordx1
storeInstMaxWidth = 4 # maximum dwordx4
srMinVw = max(storeInstMinWidth, int(storeInstMinWidth/state["ProblemType"]["DestDataType"].numRegisters()))
numReg = state["ProblemType"]["DestDataType"].numRegisters()
if state["_GlobalAccumulation"]:
numReg = state["ProblemType"]["ComputeDataType"].numRegisters()
srMaxVw = int(storeInstMaxWidth/numReg)
if srMinVw > state["StoreRemapVectorWidth"] or srMaxVw < state["StoreRemapVectorWidth"]:
reject(state, "StoreRemapVectorWidth %u is not allowed for this data type" % state["StoreRemapVectorWidth"])
return
if state["StoreRemapVectorWidth"] * state["WavefrontSize"] < state["MacroTile0"]:
reject(state, "storeRemap: Per wave single global write instruction doesn't enough to write one M column." + \
" Please use larger StoreRemapVectorWidth.")
return
if (state["MacroTile0"]*state["MatrixInstN"])//state["MIWaveGroup"][0] < state["StoreRemapVectorWidth"]*state["WavefrontSize"]:
reject(state, "storeRemap: number elements of lds less than per wave per local read elements." + \
" Please use smaller StoreRemapVectorWidth.")
return
ldsRemapPad = max(state["StoreRemapVectorWidth"],state["MIOutputVectorWidth"])
ldsNumElementsRemapC = (state["MacroTile0"]+ldsRemapPad)* state["MatrixInstN"] * state["MIWaveGroup"][1]
if state["_GlobalAccumulation"]:
computeBytes = state["ProblemType"]["ComputeDataType"].numBytes()
multiplier = computeBytes // state["ProblemType"]["DataType"].numBytes()
elif state["ProblemType"]["DestDataType"].numBytes() > state["ProblemType"]["DataType"].numBytes():
# Determine ratio of output to input element size.
# SRVW remaps output so we need to scale up resources.
multiplier = state["ProblemType"]["DestDataType"].numBytes() // state["ProblemType"]["DataType"].numBytes()
else:
multiplier = 1
ldsNumElementsRemapC *= multiplier
#print("ldsNumElementsRemapC=%u" % ldsNumElementsRemapC)
# if LDS is bound by RemapC (SRVW), then 1LDSBuffer actually doesn't help in SIA3
# since LDS usage couldn't be reduced
if state["1LDSBuffer"] and (state["ScheduleIterAlg"] == 3) and (ldsNumElements < ldsNumElementsRemapC):
# TODO- Remove this DataType test condition,
# Currently we do this test is just because we don't want to affect existing logic in rocBLAS
if state["ProblemType"]["DataType"].isInt8() or state["ProblemType"]["DataType"].is8bitFloat():
reject(state, "LDS usage is bound be StoreRemap, thus 1LDSBuffer wouldn't have any help. Skip.")
return
ldsNumElements = max(ldsNumElements, ldsNumElementsRemapC)
state["LdsNumElements"] = ldsNumElements
ldsSize = ldsNumElements * state["ProblemType"]["DataType"].numBytes()
if ldsSize > globalParameters["MaxLDS"]:
reject(state, "Kernel Uses %u > %u bytes of LDS" % ( ldsSize, globalParameters["MaxLDS"]))
return
# LoopUnroll = DepthU / LocalSplitU
if "LocalSplitU" in state and "_DepthULds" in state:
state["LoopUnroll"] = state["_DepthULds"] // state["LocalSplitU"]
if state["LoopUnroll"] * state["LocalSplitU"] != state["_DepthULds"]:
state["Valid"] = False
if state["KernelLanguage"] != "Assembly" and state["InnerUnroll"] != 1:
reject(state, "InnerUnroll only supported on assembly")
state["LoopUnroll"] //= state["InnerUnroll"]
#constraints for StoreCInUnroll feature
if state["StoreCInUnroll"]:
if not (state["ProblemType"]["DataType"].isDouble() or state["ProblemType"]["DataType"].isDoubleComplex()):
reject(state, "StoreCInUnroll currently only available for dgemm/zgemm")
return
if state["LocalSplitU"]>1:
reject(state, "LocalSplitU is not supported for StoreCinUnroll")
return
if state["MIArchVgpr"]:
reject(state, "MIArchVgpr is not supported for StoreCinUnroll")
return
if not state["PersistentKernel"]:
reject(state, "StoreCInUnroll requires PersistentKernel feature")
return
if not state["PrefetchAcrossPersistent"]:
reject(state, "StoreCInUnroll requires PrefetchAcrossPersistent feature")
return
if state["PrefetchAcrossPersistentMode"] == 0:
reject(state, "StoreCInUnroll requires PrefetchAcrossPersistentMode")
return
if state["ProblemType"]["DataType"].isDouble() and state["VectorWidthA"] != 2:
reject(state, "StoreCInUnroll requires VectorWidth=2 for dgemm")
return
if state["AtomicAddC"] and state["StoreVectorWidth"] != 1:
reject(state, "StoreCInUnroll requires AtomicAddC with StoreVectorWidth=1")
return
if state["ScheduleGlobalRead"] != 1:
reject(state, "StoreCInUnroll requires ScheduleGlobalRead=1")
return
if state["PrefetchGlobalRead"] == 0:
reject(state, "StoreCInUnroll requires PrefetchGlobalRead!=0")
return
if not state["ExpandPointerSwap"]:
reject(state, "StoreCInUnroll requires ExpandPointerSwap")
return
if state["ScheduleIterAlg"] != 3:
reject(state, "StoreCInUnroll requires ScheduleIterAlg=3")
return
if state['MIWaveGroup'][1] != 1 and state['MIWaveGroup'][1] != 4:
reject(state, "StoreCInUnroll requires [MIWaveGroup][1]=1 or 4")
return
if state["StoreCInUnrollExact"] and state["StoreCInUnrollPostLoop"] :
reject(state, "StoreCInUnrollPostLoop does not work with StoreCInUnrollExact")
return
if not state["SourceSwap"]:
reject(state, "StoreCInUnroll requires SourceSwap feature")
return
if state["ProblemType"]["DataType"].isDouble() and state["NumElementsPerBatchStore"] == 1:
reject(state, "StoreCInUnroll does not work with NumElementsPerBatchStore = 1 for dgemm")
return
if not state["BufferStore"]:
reject(state, "StoreCInUnroll requires BufferStore feature")
return
if state["VectorWidthB"] > 1:
reject(state, "StoreCInUnroll + VectorWidthB > 1 not supported yet")
return
# minimum K check
# PGR=2 requires minimum K
if state["PrefetchGlobalRead"] == 2:
# PGR=2 case, K > DepthU * 2 is necessary
minDUnum = 2
if not (3 in state["AssertSizeGreaterThan"].keys() and state["AssertSizeGreaterThan"][3] >= state["DepthU"] * minDUnum):
reject(state, "StoreCInUnroll does not work if AssertSizeGreaterThan for K is not greater than DepthU * %u"%minDUnum)
return
# exact K check
# StoreCInUnrollExact requires exact K
if state["StoreCInUnrollExact"]:
# K == DepthU * ThreadTile0 * ThreadTile1 // VectorWidth is necessary
exactK = state["DepthU"] * state["ThreadTile0"] * state["ThreadTile1"] // state["VectorWidthA"]
if not (3 in state["AssertSizeEqual"].keys() and state["AssertSizeEqual"][3] == exactK):
reject(state, "StoreCInUnrollExact does not work if AssertSizeEqual for K is not DepthU * ThreadTile0 * ThreadTile1 / VectorWidth")
return
else:
# force to disable if StoreCInUnroll related parameter is enabled
if state["StoreCInUnrollPostLoop"] :
state["StoreCInUnrollPostLoop"] = False
if state["StoreCInUnrollExact"] :
state["StoreCInUnrollExact"] = False
# check LocalDotLayout
ldl = state["LocalDotLayout"]
if ldl> 1:
state["DirectToLdsA"] = False
state["DirectToLdsB"] = False
if state["KernelLanguage"] == "Assembly":
if state["EnableMatrixInstruction"]:
reject(state, "doesn't support LocalDotLayout > 1 in MFMA mode")
else: # VALU mode
if state["ProblemType"]["DataType"].isInt8():
if (ldl != 4) or (state["ProblemType"]["HighPrecisionAccumulate"] != True):
reject(state, "Only support Int8 HPA and LocalDotLayout 4")
return
elif state["ProblemType"]["DataType"].is8bitFloat():
if (ldl != 4) or (state["ProblemType"]["HighPrecisionAccumulate"] != True):
reject(state, "Only support 8bitFloat HPA and LocalDotLayout 4")
return
elif state["ProblemType"]["DataType"].isHalf():
if ldl > 2:
reject(state, "doesn't support FP16 with LocalDotLayout > 2")
return
elif (ldl == 2) and (state["ProblemType"]["HighPrecisionAccumulate"] != True):
reject(state, "doesn't support non HPA FP16 with LocalDotLayout == 2")
return
else: # other type
reject(state, "doesn't support LocalDotLayout with type {}".format(str(state["ProblemType"]["DataType"])))
return
if ldl != state["InnerUnroll"]:
reject(state, "only support LocalDotLayout = InnerUnroll when LocalDotLayout > 1")
return
if ((state["LSPA"] % ldl) != 0) or ((state["LSPB"] % ldl) != 0):
reject(state, "LSPA/B should be multiple of LocalDotLayout")
return
if 0:
print("info: ", pvar(state, "LoopUnroll"), " LDS Stats:", pvar(state, "LdsOffsetA"), pvar(state, "LdsOffsetB"))
print("info: ", pvar(state["ProblemType"], "TLUA"), \
pvar(state, "NumLoadsCoalescedA"), pvar(state, "NumLoadsPerpendicularA"), \
pvar(state, "LSCA"), pvar(state, "LSPA"))
print("info:", pvar(state["ProblemType"], "TLUB"), \
pvar(state, "NumLoadsCoalescedB"), pvar(state, "NumLoadsPerpendicularB"), \
pvar(state, "LSCB"), pvar(state, "LSPB"))
state["LoopIters"] = state["LoopUnroll"]
if "MatrixInstK" in state:
state["LoopIters"] //= state["MatrixInstK"]
if state["LoopIters"] < 1:
reject(state, "LoopIters need to greater than 0")
return
# PLR > 2xLoopIters is redundant setting
# TODO- Why need to x2 ? Why not (if state["PrefetchLocalRead"] >= state["LoopIters"]:)
if state["PrefetchLocalRead"] >= 2*state["LoopIters"]:
reject(state, "Reject since PrefetchLocalRead %u >= 2x LoopIters %u" % (state["PrefetchLocalRead"],state["LoopIters"]))
# reject low performance
if state["PrefetchLocalRead"]%state["LoopIters"] > 1:
reject(state, "PrefetchLocalRead: %u, LoopIters: %u performance is low" % (state["PrefetchLocalRead"],state["LoopIters"]))
# prefetch wider read iteration > LoopIters, no enough iterations for prefetching
if state["EnableMatrixInstruction"] and state["PrefetchLocalRead"] > 0:
# Multiple = WLR-size / input-size = how many iters could be covered by one WLR ?
wlrMultiple = state["LocalReadVectorWidth"]//state["MIInputPerThread"]
if wlrMultiple == 0:
reject(state, "LocalReadVectorWidth %u is less than MIInput" % (state["LocalReadVectorWidth"]))
return
# for example, if the original ds_read is b32...
# 1. if LoopIters = 5 (b32 x 5 times), WLR-Multiple = 2 (b64), then we can fit the WLR
# 2. if LoopIters = 2 (b32 x 2 times), WLR-Multiple = 4 (b128), this is not allowed
# 3. if LoopIters = 2 (b32 x 2 times), WLR-Multiple = 2 (b64), this is allowed
if state["LoopIters"] % wlrMultiple != 0:
reject(state, "LocalReadVectorWidth %u cannot be distributed evenly, LoopIters %u should be divisible by WLR-Multiple %u" \
% (state["LocalReadVectorWidth"], state["LoopIters"], wlrMultiple))
PLR = (state["PrefetchLocalRead"] % state["LoopIters"])
if PLR != 0 and state["LoopIters"] - (PLR * wlrMultiple) <= 0 :
reject(state, "with PrefetchLocalRead %u LoopIters %u LocalReadVectorWidth %u, not enough LoopIters to prefetch %ux%u iterations, " \
% (state["PrefetchLocalRead"],state["LoopIters"],state["LocalReadVectorWidth"], PLR , wlrMultiple) )
if state["PrefetchLocalRead"] > 1 and PLR == 0:
reject(state, "not good performance with PrefetchLocalRead %u LoopIters %u" \
% (state["PrefetchLocalRead"],state["LoopIters"]) )
# # reject conditions with lower performance
# if state["ScheduleIterAlg"] == 2 and \
# (state["ExpandPointerSwap"] != 1 or state["LoopIters"] != 1 or state["ScheduleGlobalRead"] != 1):
# reject(state, "ScheduleIterAlg 2 only work with EPS1_SGR1, LoopIter=1")
if state["TransposeLDS"] == 1:
if not state["EnableMatrixInstruction"]:
reject(state, "TransposeLds Supports only in MatrixInstruction=1")
if state["ProblemType"]["TLUA"] and state["ProblemType"]["TLUB"]:
# TODO: Now in rocBLAS, lot of logic yamls are Type=NT and TLDS=1? Why aren't they rejected and how to get rid of them?
reject(state, "TransposeLds requires TLUA=0 or TLUB=0")
if state["EnableMatrixInstruction"]:
# enable widerLocalRead
if state["LocalReadVectorWidth"] > state["MIInputPerThread"]:
# wider localRead support 2 types
# 1. prefetch all lds to register
# 2. using larger InnerUnroll
if not (state["PrefetchLocalRead"] >= state["LoopIters"] and state["InnerUnroll"] == 1) and \
not state["InnerUnroll"] >= state["LocalReadVectorWidth"] // state["MIInputPerThread"]:
reject(state, "wider localRead only support (PrefetchLocalRead %u >= LoopIters %u) or (InnerUnroll %u > LocalReadxN)" % (state["PrefetchLocalRead"],state["LoopIters"],state["InnerUnroll"]))
if state["DepthULdsDivisor"] > 1:
if state["PrefetchGlobalRead"] == 2:
reject(state, "DepthULdsDivisor > 1 does not support PrefetchGlobalRead=2")
if state["ScheduleIterAlg"] != 3:
reject(state, "DepthULdsDivisor > 1 does not support ScheduleIterAlg other than 3")
if (state["DirectToLdsA"] == True or state["DirectToLdsB"] == True):
reject(state, "DepthULdsDivisor > 1 does not support DirectToLds")
if state["ProblemType"]["TLUA"] or state["ProblemType"]["TLUB"] or not (state["UnrollMajorLDSA"] and state["UnrollMajorLDSB"]):
reject(state, "DepthULdsDivisor > 1: Only works with TN problem layout and UnrollMajorLDS")
if state["PrefetchGlobalRead"]==1 and state["PrefetchLocalRead"]==0:
reject(state, "PGR1 + PLR0 in SplitLDS requires double G2L buffer which is yet to be implemented")
for tc in ('A', 'B'):
if state["ProblemType"]["DataType"].numRegisters()*state["GlobalLoadVectorWidth%s"%tc] < state["DepthULdsDivisor"]:
reject(state, "SplitLDS requires wider GlobalLoadVectorWidth%s; needs RegisterPerElem (%f) * GLVW%s (%u) >= DepthULdsDivisor (%u)"%
(tc, state["ProblemType"]["DataType"].numRegisters(),tc,state["GlobalLoadVectorWidth%s"%tc],state["DepthULdsDivisor"]))
if state["GlobalReadPerMfma"] > 1 and state["PrefetchGlobalRead"] == 2:
reject(state, "GlobalReadPerMfma need to be 1 if PGR2")
if state["UseInstOffsetForGRO"] == -1:
state["UseInstOffsetForGRO"] = 1 if (state["DirectToLdsA"] or state["DirectToLdsB"]) else 0
# Precise bounds check uses the "num_records" field in the buffer to
# precisely detect when we are inbounds or not. Only a one-dimensional
# check is used since this is faster and also for computation we only
# need to ensure that none of the loads fault. threads which are
# computing bogus sections of the C tile will later be ignored.
# precise checking only works when all elements of the load are in-bounds
# since if the vload crosses boundary we ignore all components not just the
# ones that are OOB. See comments for groOffsetInMacroTile in KernelWriterAssembly.py
#
# So check for the cases where the unroll loop can
# generate partial loads here and reject PBC solutions:
# For non-TLU the free dim is in perp dim - should always be TRUE? TODO
#--
# ShiftPtr can't use UseSgprForGRO since it needs to modify the VGPR pointers
if bufferLoad and state["_UseSgprForGRO"] and state["EdgeType"]=="ShiftPtr":
if not state["GuaranteeNoPartialA"] or not state["GuaranteeNoPartialB"]:
state["_UseSgprForGRO"] = False
#reject(state, "PBC with wide load has insufficient overlap guarantees- try GRVW=1 or adding appropriate Assert*ElementMultiple")
if state["EnableMatrixInstruction"]:
cont1 = not state["GuaranteeNoPartialB"]
cont2 = ((state["MatrixInstN"] % state["GlobalLoadVectorWidthB"]) != 0)
if cont1 and cont2:
reject(state, "MatrixInstN %u %% GlobalLoadVectorWidthB %u must be 0" % \
(state["MatrixInstN"], state["GlobalLoadVectorWidthB"]))
else:
if not bufferLoad or not state["GuaranteeNoPartialA"]:
# Restrict GRVW/VW combos so shift-ptr logic will work
if state["GlobalLoadVectorWidthA"] > 1 \
and state["GlobalLoadVectorWidthA"] != state["VectorWidth"]:
reject(state, "GlobalLoadVectorWidthA %u must be == VectorWidth %u or == 1" % \
(state["GlobalLoadVectorWidthA"], state["VectorWidth"]))
if not bufferLoad or not state["GuaranteeNoPartialB"]:
# Restrict GRVW/VW combos so shift-ptr logic will work
if state["GlobalLoadVectorWidthB"] > 1 \
and state["GlobalLoadVectorWidthB"] != state["VectorWidth"]:
reject(state, "GlobalLoadVectorWidthB %u must be == VectorWidth %u or == 1" % \
(state["GlobalLoadVectorWidthB"], state["VectorWidth"]))
# these work everywhere, no special restrictions
state["AssertMinApproxSize"] = 0
if state["KernelLanguage"] == "Assembly":
if state["VectorWidth"] > 1:
# VW>1 kernels require dims>1
state["AssertMinApproxSize"] = 3
elif state["VectorWidth"] > 1:
# VW>1 kernels require dims>1
state["AssertMinApproxSize"] = 2
# Use SGPR to store an offset from GlobalReadOffsetA+0.
# (as opposed to using dedicated VGPR for each GRO
# Requires preciseBounds check since we rely on the buffer bounds check, not
# individual vector registers doing bounds compares.
if not bufferLoad and state["FractionalLoad"]:
reject(state, "Fractional requires BufferLoad")
if state["_UseSgprForGRO"] == -1:
# Don't use SGPR if it looks like we might not have enough - better to leave PBC enabled even if we have to use VGPR
# 40 is based on current SGPR usage, this may need to be tuned in the future:
numLoadsA = state["NumLoadsCoalescedA"]*state["NumLoadsPerpendicularA"]
numLoadsB = state["NumLoadsCoalescedB"]*state["NumLoadsPerpendicularB"]
if numLoadsA + numLoadsB > 35 or state["DirectToVgprA"] or state["DirectToVgprB"]: # force _UseSgprForGRO = 0 if DirectToVgpr is enabled
#print "info: Disabling UseSgprForGRO since predicting too many SGPR will be used"
state["_UseSgprForGRO"] = 0
else:
state["_UseSgprForGRO"] = 1
if packedC0 and not state["GuaranteeNoPartialA"]:
reject(state, "packedC0 requires GuaranteeNoPartialA")
if packedC1 and not state["GuaranteeNoPartialB"]:
reject(state, "packedC1 requires GuaranteeNoPartialB")
if packedC0 or packedC1:
state["_UseSgprForGRO"] = 0
if state["EdgeType"] != "ShiftPtr":
reject(state, "Packed dims requires EdgeType==ShiftPtr")
if state["KernelLanguage"] == "Assembly":
if not bufferLoad:
reject(state, "Packed dims for Assembly requires BufferLoad")
if packedC0 and state["PackGranularity"]==2:
if state["KernelLanguage"] == "Source":
if state["AssertFree0ElementMultiple"]<state["VectorWidth"]:
reject(state, "packedC0 Source requires AF0EM>=VectorWidth (for loads and stores)")
else:
if state["AssertFree0ElementMultiple"]<state["VectorWidth"]\
or state["AssertFree0ElementMultiple"] == 1:
if state["VectorStore"] <= 0:
state["_VectorStore"] = 0
else:
reject(state, "packedC0 Assembly requires AF0EM>=VectorWidth or not VectorStore (for stores)")
if state["AssertStrideCEqual"].get(0,-1) != 1 or state["AssertStrideDEqual"].get(0,-1) != 1:
# Disable vector stores if not allowed:
if state["VectorStore"] <= 0:
state["_VectorStore"] = 0
else:
reject(state, "UseInitialStridesCD requires not VectorStore since store locations not adjacent")
# Not currently suppored. Support would require some changes in the
# zeroPadRegs management:
# - don't allocate VGPRs for multiple perp/pad cases
# - guardZeroPad needs to add soffset to scalar calc
if problemType["ZeroPadA"] or problemType["ZeroPadB"]:
state["_UseSgprForGRO"] = 0
# current requirement to avoid buffer loads that span multiple entries
# if the summation dim participating in the ZeroPad is not fast-moving then
# likely have more performant options.
for tc in ('A', 'B'):
if problemType["ZeroPad%s"%tc] and state["KernelLanguage"] == "Assembly":
if state["GlobalLoadVectorWidth%s"%tc] != 1 \
and problemType["IndexAssignments%s"%tc][0] in problemType["ZeroPad%s"%tc][0][0:1]:
reject(state, "asm ZeroPad requires GlobalLoadVectorWidth==1")
if not bufferLoad:
reject(state, "asm ZeroPad requires BufferLoad")
# Ensure AssertCEqualsD is always used with LdcEqualsLdd --DISABLED CURRENTLY
#if state["AssertCEqualsD"]:
# if not ("LdcEqualsLdd" in state["ProblemType"] and state["ProblemType"]["LdcEqualsLdd"]):
# import pdb; pdb.set_trace()
# reject(state, "AssertCEqualsD requires LdcEqualsLdd=True")
state["AssignedDerivedParameters"] = True
# UnrollLoopEfficiencyEnable does not work with f16/bf16/int8x4
if globalParameters["UnrollLoopEfficiencyEnable"] and (state["ProblemType"]["DataType"].isHalf() or \
state["ProblemType"]["DataType"].isBFloat16() or state["ProblemType"]["DataType"].isInt8x4()):
reject(state, "UnrollLoopEfficiencyEnable does not support f16/bf16/int8x4")
# UnrollLoopEfficiencyEnable supports only ThreadTile0,1=[6,4] or [4,6] or [4,4] or [6.6] or [8,4] or [4,8]
if globalParameters["UnrollLoopEfficiencyEnable"] and \
not ((state["ThreadTile0"] == 6 and state["ThreadTile1"] == 4) or \
(state["ThreadTile0"] == 4 and state["ThreadTile1"] == 6) or \
(state["ThreadTile0"] == 4 and state["ThreadTile1"] == 4) or \
(state["ThreadTile0"] == 6 and state["ThreadTile1"] == 6) or \
(state["ThreadTile0"] == 8 and state["ThreadTile1"] == 4) or \
(state["ThreadTile0"] == 4 and state["ThreadTile1"] == 8)):
reject(state, "UnrollLoopEfficiencyEnable does not support ThreadTile0,1 = [%u,%u]"%(state["ThreadTile0"], state["ThreadTile1"]))
# reject check for VgprForLocalReadPacking
if state["VgprForLocalReadPacking"]:
doable, rejectComment = Solution.isVgprForLocalReadPackingDoable(state)
if not doable:
reject(state, rejectComment)
return
# reject check for ClusterLocalRead
if state["ClusterLocalRead"]:
# Requires VgprForLocalReadPacking
if not state["VgprForLocalReadPacking"]:
reject(state, "ClusterLocalRead works with VgprForLocalReadPacking")
return
if (state["UnrollMajorLDSA"] == False and state["VectorWidthA"] > state["MIInputPerThread"]) or\
(state["UnrollMajorLDSB"] == False and state["VectorWidthB"] > state["MIInputPerThread"]):
reject(state, "ClusterLocalRead does not support VectorWidth or VectorWidthB > MIInputPerThread")
return
# change negative ExtraLatencyForLR to 0 for non DirectToVgpr
if state["ExtraLatencyForLR"] < 0 and not (state["DirectToVgprA"] or state["DirectToVgprB"]):
state["ExtraLatencyForLR"] = 0
# SourceKernel with PrefetchGlobalRead / LoopDoWhile case
# - SourceKernel + PGR2 does not work
# - SourceKernel + LoopDoWhile requires at least 1 loop iteration (K > (DepthU * GSU * 2) - 1)
if state["KernelLanguage"] == "Source":
if state["PrefetchGlobalRead"] == 2:
reject(state, "Source kernel does not support PGR2")
else:
factor = 0
# DoWhile case, minimum iteration is 1
if state["LoopDoWhile"]:
factor = 1
if factor > 0 and (not (3 in state["AssertSizeGreaterThan"].keys() and state["AssertSizeGreaterThan"][3] >= state["DepthU"] * state["GlobalSplitU"] * factor - 1)):
reject(state, "Source kernel requires AssertSizeGreaterThan for K > (DepthU(%u) * GlobalSplitU(%u) * PGR/LoopDoWhile factor(%u) - 1)"%(state["DepthU"], state["GlobalSplitU"], factor))
########################################
# create a dictionary with booleans on whether to include parameter in name
@staticmethod
def getMinNaming(objs):
nonCKObjs = [obj for obj in objs if not isCustomKernelConfig(obj)]
# early return
if len(nonCKObjs) == 0:
return {}
# determine keys
requiredParameters = {}
if isinstance(nonCKObjs[0], Solution):
keys = list(nonCKObjs[0]._state.keys())
else:
keys = list(nonCKObjs[0].keys())
# only 1, rather than name being nothing, it'll be everything
if len(nonCKObjs) == 1:
for key in keys:
if key in list(validParameters.keys()):
requiredParameters[key] = False
else:
for key in keys:
required = False
if key in list(validParameters.keys()):
for i in range(1, len(nonCKObjs)):
if nonCKObjs[0][key] != nonCKObjs[i][key]:
required = True
break
if required:
requiredParameters[key] = True
else:
requiredParameters[key] = False
requiredParameters["ProblemType"] = False # always prepended
requiredParameters["MacroTile0"] = False # always prepended
requiredParameters["MacroTile1"] = False # always prepended
requiredParameters["DepthU"] = False # always prepended
requiredParameters["LdcEqualsLdd"] = False # always prepended
requiredParameters["MatrixInstruction"] = False # always prepended
requiredParameters["MatrixInstM"] = False # always prepended
requiredParameters["MatrixInstN"] = False # always prepended
requiredParameters["MatrixInstK"] = False # always prepended
requiredParameters["MatrixInstB"] = False # always prepended
requiredParameters["MatrixInstBM"] = False # always prepended
requiredParameters["MatrixInstBN"] = False # always prepended
requiredParameters["CustomKernelName"] = False # Will not affect naming
requiredParameters["Fp16AltImpl"] = False # Will show up as a different type
requiredParameters["Fp16AltImplRound"] = False # Will show up as a different type
requiredParameters["StochasticRounding"]= False # Will show up as a different type
requiredParameters["Kernel"] = True # distinguish kernels from solutions
# for single-source compilation
return requiredParameters
########################################
@ staticmethod
def getNameFull(state):
requiredParameters = {}
for key in state:
if key in validParameters:
requiredParameters[key] = True
return Solution.getNameMin(state, requiredParameters)
########################################
# Get Name Min
@ staticmethod
def getNameMin(state, requiredParameters):
if isCustomKernelConfig(state):
return state["CustomKernelName"]
name = ""
first = True
# put problem first
if "ProblemType" in state:
name += str(state["ProblemType"]) + "_"
if "MacroTile0" in state \
and "MacroTile1" in state \
and "DepthU" in state:
name += "%s%ux%ux%u_" \
% ( Solution.getParameterNameAbbreviation("MacroTile"), \
state["MacroTile0"], state["MacroTile1"], state["DepthU"] )
if "MatrixInstM" in state:
name += "%s%ux%ux%ux%u_" \
% ( Solution.getParameterNameAbbreviation("MatrixInstruction"), \
state["MatrixInstM"], state["MatrixInstN"], state["MatrixInstK"], state["MatrixInstB"])
if "LdcEqualsLdd" in state:
if state["LdcEqualsLdd"]:
name += "SE_"
else:
name += "SN_"
for key in sorted(state.keys()):
if key in requiredParameters and key[0] != '_':
if requiredParameters[key] and key != "CustomKernelName":
if not first:
name += "_"
else:
first = False
name += "%s%s" % ( Solution.getParameterNameAbbreviation(key), \
Solution.getParameterValueAbbreviation(key, state[key]) )
return name
########################################
# create a dictionary of lists of parameter values
@staticmethod
def getSerialNaming(objs):
data = {}
for objIdx in range(0, len(objs)):
obj = objs[objIdx]
for paramName in sorted(obj.keys()):
if paramName in list(validParameters.keys()):
paramValue = obj[paramName]
if paramName in data:
if paramValue not in data[paramName]:
data[paramName].append(paramValue)
else:
data[paramName] = [ paramValue ]
maxObjs = 1
for paramName in data:
if not isinstance(data[paramName][0],dict):
data[paramName] = sorted(data[paramName])
maxObjs *= len(data[paramName])
numDigits = len(str(maxObjs))
return [ data, numDigits ]
########################################
# Get Name Serial
@ staticmethod
def getNameSerial(state, serialNaming):
data = serialNaming[0]
numDigits = serialNaming[1]
serial = 0
multiplier = 1
for paramName in sorted(state.keys()):
if paramName in list(validParameters.keys()):
paramValue = state[paramName]
paramData = data[paramName]
paramNameMultiplier = len(paramData)
if paramValue in paramData:
paramValueIdx = paramData.index(paramValue)
serial += paramValueIdx * multiplier
multiplier *= paramNameMultiplier
name = "%s%0*u" % ("S" if isinstance(state, Solution) else "K", \
numDigits, serial)
return name
########################################
@ staticmethod
def getParametersIndented(state, indent):
s = ""
s += "%sProblemType: %s\n" % (indent, str(state["ProblemType"]))
for key in sorted(state):
s += "%s%s: %s\n" % (indent, str(key), str(state[key]))
return s
########################################
@ staticmethod
def getParameterNameAbbreviation(name):
return parameterNameAbbreviations[name]
########################################
@ staticmethod
def getParameterValueAbbreviation( key, value ):
if key == 'ISA':
return str(value[0]) + str(value[1]) + ('%x' % value[2])
elif isinstance(value, str):
return ''.join([c for c in value if c.isupper()])
elif isinstance(value, bool):
return "1" if value else "0"
elif isinstance(value, int):
if value >= 0:
return "%u" % value
else: # -1 -> n1
return "n%01u" % abs(value)
elif isinstance(value, ProblemType):
return str(value)
elif isinstance(value, tuple):
abbrev = ""
for i in range(0, len(value)):
abbrev += str(value[i])
return abbrev
elif isinstance(value, list):
abbrev = ""
for i in range(0, len(value)):
abbrev += Solution.getParameterValueAbbreviation(key, value[i])
if i < len(value)-1:
abbrev += "_"
return abbrev
elif isinstance(value, dict):
s = "_".join(["%d%d"%(pos,k) for pos,k in value.items()])
return s
elif isinstance(value, float):
val1 = int(value)
val2 = int(round(value*100)) - int(value)*100
if val2 > 0:
s = "%dp%s" % (val1,str(val2).zfill(2))
else:
s = "%d" % (val1)
return s
else:
printExit('Parameter {key}={value} is new object type ({t})'.format(key=key, value=value, t=type(value)))
return str(value)
##########################
# make class look like dict
def keys(self):
return self._state.keys()
def __len__(self):
return len(self._state)
def __iter__(self):
return iter(self._state)
def __getitem__(self, key):
return self._state[key]
def __setitem__(self, key, value):
self._name = None
self._state[key] = value
def __str__(self):
if self._name is None:
self._name = Solution.getNameFull(self._state)
return self._name
def __repr__(self):
return self.__str__()
def getAttributes(self):
return self._state
def __hash__(self):
return hash(str(self) + self.get("codeObjectFile", ""))
def __eq__(self, other):
return isinstance(other, Solution) and str(self) == str(other)
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return result
return not result
@property
def enabledSplitLDS(self):
return self["DepthULdsDivisor"] > 1
@property
def enabledSetPrioSplitLDS(self):
# The interaction between SplitLDS's priority policy and StorePriorityOpt's is yet to be
# investigated. For now, disable SplitLDS's priority policy when StorePriorityOpt is present
# TODO: determine suitable priority policy when both are present
return self.enabledSplitLDS and not self["StorePriorityOpt"]