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

547 lines
21 KiB
Python
Executable File

#!/usr/bin/python3
# ########################################################################
# Copyright (C) 2022-2023 Advanced Micro Devices, Inc.
#
# 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.
#
# ########################################################################
#Expand hipBLASLt YAML test data file into binary Arguments records"""
import re
import sys
import os
import argparse
import ctypes
from fnmatch import fnmatchcase
from typing import List, Tuple
try: # Import either the C or pure-Python YAML parser
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
import yaml
# Regex for type names in the YAML file. Optional *nnn indicates array.
TYPE_RE = re.compile(r'[a-z_A-Z]\w*(:?\s*\*\s*\d+)?$')
# Regex for integer ranges A..B[..C]
INT_RANGE_RE = re.compile(
r'\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*(?:\.\.\s*(-?\d+)\s*)?$')
# Regex for include: YAML extension
INCLUDE_RE = re.compile(r'include\s*:\s*([-.\w/]+)')
args = {}
testcases = set()
datatypes = {}
param = {}
def main():
args.update(parse_args().__dict__)
for doc in get_yaml_docs():
process_doc(doc)
def process_doc(doc):
"""Process one document in the YAML file"""
# Ignore empty documents
if not doc or not doc.get('Tests'):
return
# Clear datatypes and params from previous documents
datatypes.clear()
param.clear()
# Return dictionary of all known datatypes
datatypes.update(get_datatypes(doc))
# Arguments structure corresponding to C/C++ structure
param['Arguments'] = type('Arguments', (ctypes.Structure,),
{'_fields_': get_arguments(doc)})
# Special names which get expanded as lists of arguments
param['dict_lists_to_expand'] = doc.get('Dictionary lists to expand') or ()
# Lists which are not expanded
param['lists_to_not_expand'] = doc.get('Lists to not expand') or ()
# Defaults
defaults = doc.get('Defaults') or {}
# Known Bugs
param['known_bugs'] = doc.get('Known bugs') or []
# Functions
param['Functions'] = doc.get('Functions') or {}
# Instantiate all of the tests, starting with defaults
for test in doc['Tests']:
case = defaults.copy()
case.update(test)
generate(case, instantiate)
def parse_args():
"""Parse command-line arguments, returning input and output files"""
parser = argparse.ArgumentParser(description="""
Expand hipBLASLt YAML test data file into binary Arguments records
""")
parser.add_argument('infile',
nargs='?',
type=argparse.FileType('r'),
default=sys.stdin)
parser.add_argument('-o', '--out',
dest='outfile',
type=argparse.FileType('wb'),
default=sys.stdout)
parser.add_argument('-I',
help="Add include path",
action='append',
dest='includes',
default=[])
parser.add_argument('-t', '--template',
type=argparse.FileType('r'))
return parser.parse_args()
def read_yaml_file(file):
"""Read the YAML file, processing include: lines as an extension"""
file_dir = os.path.dirname(file.name) or os.getcwd()
source = []
for line_no, line in enumerate(file, start=1):
# Keep track of file names and line numbers for each line of YAML
match = line.startswith('include') and INCLUDE_RE.match(line)
if not match:
source.append([line, file.name, line_no])
else:
include_file = match.group(1)
include_dirs = [file_dir] + args['includes']
for path in include_dirs:
path = os.path.join(path, include_file)
if os.path.exists(path):
source.extend(read_yaml_file(open(path, 'r')))
break
else:
sys.exit("In file " + file.name + ", line " +
str(line_no) + ", column " + str(match.start(1)+1) +
":\n" + line.rstrip() + "\n" + " " * match.start(1) +
"^\nCannot open " + include_file +
"\n\nInclude paths:\n" + "\n".join(include_dirs))
file.close()
return source
def get_yaml_docs():
"""Parse the YAML file"""
source = read_yaml_file(args['infile'])
if args.get('template'):
source = read_yaml_file(args['template']) + source
source_str = ''.join([line[0] for line in source])
def mark_str(mark):
line = source[mark.line]
return("In file " + line[1] + ", line " + str(line[2]) + ", column " +
str(mark.column + 1) + ":\n" + line[0].rstrip() + "\n" +
' ' * mark.column + "^\n")
# We iterate through all of the documents to properly diagnose errors,
# because the load_all generator does not handle exceptions correctly.
docs = []
load = Loader(source_str)
while load.check_data():
try:
doc = load.get_data()
except yaml.YAMLError as err:
sys.exit((mark_str(err.problem_mark) if err.problem_mark else "") +
(err.problem + "\n" if err.problem else "") +
(err.note + "\n" if err.note else ""))
else:
docs.append(doc)
return docs
def get_datatypes(doc):
""" Get datatypes from YAML doc"""
dt = ctypes.__dict__.copy()
for declaration in doc.get('Datatypes') or ():
for name, decl in declaration.items():
if isinstance(decl, dict):
# Create derived class type based on bases and attr entries
dt[name] = type(name,
tuple([eval(t, dt)
for t in decl.get('bases') or ()
if TYPE_RE.match(t)]
), decl.get('attr') or {})
# Import class' attributes into the datatype namespace
for subtype in decl.get('attr') or {}:
if TYPE_RE.match(subtype):
dt[subtype] = eval(name+'.'+subtype, dt)
elif isinstance(decl, str) and TYPE_RE.match(decl):
dt[name] = dt[decl]
else:
sys.exit("Unrecognized data type "+name+": "+repr(decl))
return dt
def get_arguments(doc):
"""The kernel argument list, with argument names and types"""
return [(var, eval(decl[var], datatypes))
for decl in doc.get('Arguments') or ()
if len(decl) == 1
for var in decl
if TYPE_RE.match(decl[var])]
def setkey_product(test, key, vals):
"""Helper for setdefaults. Tests that all values in vals is present
in test, if so then sets test[key] to product of all test[vals]."""
if all(x in test for x in vals):
result = 1
for x in vals:
if x in ('incx', 'incy'):
result *= abs(test[x])
else:
result *= test[x]
test[key] = int(result)
def setdefaults(test):
"""Set default values for parameters"""
# Do not put constant defaults here -- use hipblaslt_common.yaml for that.
# These are only for dynamic defaults
if test['transA'] == '*' or test['transB'] == '*':
test.setdefault('lda', 0)
test.setdefault('ldb', 0)
test.setdefault('ldc', 0)
test.setdefault('ldd', 0)
test.setdefault('lde', 0)
elif not test['grouped_gemm']: #catered to gemm default behavior
M = test['M'][0] if isinstance(test['M'], list) else test['M']
N = test['N'][0] if isinstance(test['N'], list) else test['N']
K = test['K'][0] if isinstance(test['K'], list) else test['K']
test.setdefault('lda', (M if M != 0 else 1) if test['transA'].upper() == 'N'
else K if K != 0 else 1)
test.setdefault('ldb', (K if K != 0 else 1) if test['transB'].upper() == 'N'
else N if N != 0 else 1)
test.setdefault('ldc', M if M != 0 else 1)
test.setdefault('ldd', M if M != 0 else 1)
test.setdefault('lde', M if M != 0 else 1)
if test['batch_count'] > 0:
test.setdefault('stride_a', test['lda'] *
(K if test['transA'].upper() == 'N' else
M))
test.setdefault('stride_b', test['ldb'] *
(N if test['transB'].upper() == 'N' else
K))
test.setdefault('stride_c', test['ldc'] * N)
test.setdefault('stride_d', test['ldd'] * N)
test.setdefault('stride_e', test['lde'] * N)
return
else:
Ms = test['M']
Ns = test['N']
Ks = test['K']
test.setdefault('lda', [(M if M != 0 else 1) if test['transA'].upper() == 'N'
else K if K != 0 else 1 for M, K in zip(Ms, Ks)])
test.setdefault('ldb', [(K if K != 0 else 1) if test['transB'].upper() == 'N'
else N if N != 0 else 1 for K, N in zip(Ks, Ns)])
test.setdefault('ldc', [M if M != 0 else 1 for M in Ms])
test.setdefault('ldd', [M if M != 0 else 1 for M in Ms])
test.setdefault('lde', [M if M != 0 else 1 for M in Ms])
if test['batch_count'] > 0:
test.setdefault('stride_a', [lda * K if test['transA'].upper() == 'N' else lda * M for K, M, lda in zip(Ks, Ms, test['lda'])])
test.setdefault('stride_b',
[N * ldb if test['transB'].upper() == 'N' else
K * ldb for N, K, ldb in zip(Ns, Ks, test['ldb'])])
test.setdefault('stride_c', [ldc * N for ldc, N in zip(test['ldc'], Ns)])
test.setdefault('stride_d', [ldd * N for ldd, N in zip(test['ldd'], Ns)])
test.setdefault('stride_e', [lde * N for lde, N in zip(test['lde'], Ns)])
return
test.setdefault('stride_a', 0)
test.setdefault('stride_b', 0)
test.setdefault('stride_c', 0)
test.setdefault('stride_d', 0)
test.setdefault('stride_e', 0)
def write_signature(out):
"""Write the signature used to verify binary file compatibility"""
if 'signature_written' not in args:
sig = 0
byt = bytearray("hipBLASLt", 'utf_8')
byt.append(0)
last_ofs = 0
for (name, ctype) in param['Arguments']._fields_:
member = getattr(param['Arguments'], name)
for i in range(0, member.offset - last_ofs):
byt.append(0)
for i in range(0, member.size):
byt.append(sig ^ i)
sig = (sig + 89) % 256
last_ofs = member.offset + member.size
for i in range(0, ctypes.sizeof(param['Arguments']) - last_ofs):
byt.append(0)
byt.extend(bytes("HIPblaslT", 'utf_8'))
byt.append(0)
out.write(byt)
args['signature_written'] = True
def gen_shape_combinations(M: List, N: List, K: List) -> Tuple[List, List, List]:
"""Generate all shape combinations. This suitable for grouped gemm case."""
from itertools import product
if not isinstance(M, List):
M = [M,]
if not isinstance(N, List):
N = [N,]
if not isinstance(K, List):
K = [K,]
problem_sizes = list(product(M, N, K))
return tuple(tuple(s[i] for s in problem_sizes) for i in range(len(problem_sizes[0])))
def write_test(test):
"""Write the test case out to the binary file if not seen already"""
# For each argument declared in arguments, we generate a positional
# argument in the Arguments constructor. For strings, we pass the
# value of the string directly. For arrays, we unpack their contents
# into the ctype array constructor and pass the ctype array. For
# scalars, we coerce the string/numeric value into ctype.
arg = []
for name, ctype in param['Arguments']._fields_:
try:
if issubclass(ctype, ctypes.Array):
if issubclass(ctype._type_, ctypes.c_char):
arg.append(bytes(test[name], 'utf_8'))
else:
try:
arg.append(ctype(*test[name]))
except TypeError:
arg.append(ctype(test[name]))
elif issubclass(ctype, ctypes.c_char):
arg.append(bytes(test[name], 'utf_8'))
else:
arg.append(ctype(test[name]))
except TypeError as err:
sys.exit("TypeError: " + str(err) + " for " + name +
", which has type " + str(type(test[name])) + "\n")
byt = bytes(param['Arguments'](*arg))
if byt not in testcases:
testcases.add(byt)
write_signature(args['outfile'])
args['outfile'].write(byt)
def instantiate(test):
"""Instantiate a given test case"""
test = test.copy()
if test['grouped_gemm']:
if 'matrix_size' in test:
sizes = test.pop('matrix_size')
for s in ('M', 'N', 'K'):
test[s] = tuple(l[s] for l in sizes)
else:
M, N, K = gen_shape_combinations(test['M'], test['N'], test['K'])
test['M'] = M
test['N'] = N
test['K'] = K
gsu_vector = [-1 for _ in range(32)]
if 'gsu_vector' in test:
gsu_vector[0] = test['gsu_vector']
else:
gsu_vector[0] = 0
test['gsu_vector'] = gsu_vector
wgm_vector = [-1 for _ in range(32)]
if 'wgm_vector' in test:
wgm_vector[0] = test['wgm_vector']
else:
wgm_vector[0] = 0
test['wgm_vector'] = wgm_vector
# Any Arguments fields declared as enums (a_type, b_type, etc.)
enum_args = [decl[0] for decl in param['Arguments']._fields_
if decl[1].__module__ == '__main__']
array_value_args = ["gsu_vector", "wgm_vector"]
try:
setdefaults(test)
# For enum arguments, replace name with value
for typename in enum_args:
if test[typename] in datatypes:
test[typename] = datatypes[test[typename]]
known_bug_platforms = set()
# Match known bugs
if test['category'] not in ('known_bug'):
for bug in param['known_bugs']:
for key, value in bug.items():
if key == 'known_bug_platforms' or key == 'category':
continue
if key not in test:
break
if key == 'function':
if not fnmatchcase(test[key], value):
break
# For keys declared as enums, compare resulting values
if key in enum_args:
if test[key] != datatypes.get(value, value):
break
# For array value type, treat as known bug if empty array was given
elif key in array_value_args:
stripped_val = [i for i in test[key] if i >= 0]
# default case in test
if stripped_val == [0,]:
break
if len(value) == 0:
continue
else:
if stripped_val != value:
break
elif test[key] != value:
break
else:
# All values specified in known bug match the test case
platforms = bug.get('known_bug_platforms', '')
# If at least one known_bug_platforms is specified, add
# each platform in platforms to known_bug_platforms set
if platforms.strip(' :,\f\n\r\t\v'):
known_bug_platforms |= set(re.split('[ :,\f\n\r\t\v]+',
platforms))
else:
test['category'] = 'known_bug'
break
# Unless category is already set to known_bug or disabled, set
# known_bug_platforms to a space-separated list of platforms
test['known_bug_platforms'] = ' ' . join(known_bug_platforms) if test[
'category'] not in ('known_bug') else ''
write_test(test)
except KeyError as err:
sys.exit("Undefined value " + str(err) + "\n" + str(test))
def generate(test, function):
"""Generate test combinations by iterating across lists recursively"""
test = test.copy()
def should_expand(key):
grouped_gemm_cond = (test['grouped_gemm'] and key not in ('M', 'N', 'K'))
normal_cond = type(test[key]) in (tuple, list) and\
key not in param['lists_to_not_expand'] and\
key != 'matrix_size'
if test['grouped_gemm']:
return grouped_gemm_cond and normal_cond
return normal_cond
def should_expand_dict_lists(argname):
normal_cond = argname in test and type(test[argname]) in (tuple, list, dict)
grouped_gemm_cond = argname != 'matrix_size'
if test['grouped_gemm']:
return normal_cond and grouped_gemm_cond
return normal_cond
# For specially named lists, they are expanded and merged into the test
# argument list. When the list name is a dictionary of length 1, its pairs
# indicate that the argument named by its key takes on values paired with
# the argument named by its value, which is another dictionary list. We
# process the value dictionaries' keys in alphabetic order, to ensure
# deterministic test ordering.
for argname in param['dict_lists_to_expand']:
if type(argname) == dict:
if len(argname) == 1:
arg, target = list(argname.items())[0]
if arg in test and type(test[arg]) == dict:
pairs = sorted(list(test[arg].items()), key=lambda x: x[0])
for test[arg], test[target] in pairs:
generate(test, function)
return
elif should_expand_dict_lists(argname):#argname in test and type(test[argname]) in (tuple, list, dict):
# Pop the list and iterate across it
ilist = test.pop(argname)
# For a bare dictionary, wrap it in a list and apply it once
for item in [ilist] if type(ilist) == dict else ilist:
try:
case = test.copy()
case.update(item) # original test merged with each item
generate(case, function)
except TypeError as err:
sys.exit("TypeError: " + str(err) + " for " + argname +
", which has type " + str(type(item)) +
"\nA name listed in \"Dictionary lists to "
"expand\" must be a defined as a dictionary.\n")
return
for key in sorted(list(test)):
# Integer arguments which are ranges (A..B[..C]) are expanded
if type(test[key]) == str:
match = INT_RANGE_RE.match(str(test[key]))
if match:
for test[key] in range(int(match.group(1)),
int(match.group(2))+1,
int(match.group(3) or 1)):
generate(test, function)
return
# For sequence arguments, they are expanded into scalars
elif should_expand(key):
for test[key] in test[key]:
generate(test, function)
return
# Replace typed function names with generic functions and types
if 'hipblaslt_function' in test:
func = test.pop('hipblaslt_function')
if func in param['Functions']:
test.update(param['Functions'][func])
else:
test['function'] = func.rpartition('hipblaslt_')[2]
generate(test, function)
return
function(test)
if __name__ == '__main__':
main()