diff --git a/install.sh b/install.sh index aa1ac582cd..d542c5a112 100755 --- a/install.sh +++ b/install.sh @@ -393,7 +393,7 @@ matrices_dir_install= gpu_architecture=all cpu_ref_lib=blis tensile_logic= -tensile_cov= +tensile_cov="4" tensile_threads=$(nproc) tensile_fork= tensile_lazy_library_loading=true @@ -564,10 +564,6 @@ while true; do esac done -if [[ -z $tensile_cov ]]; then - tensile_cov=default -fi - if [[ "${cpu_ref_lib}" == blis ]]; then LINK_BLIS=true elif [[ "${cpu_ref_lib}" == lapack ]]; then diff --git a/tensilelite/Tensile/BenchmarkProblems.py b/tensilelite/Tensile/BenchmarkProblems.py index 0f913681dd..24c89e23d0 100644 --- a/tensilelite/Tensile/BenchmarkProblems.py +++ b/tensilelite/Tensile/BenchmarkProblems.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -42,6 +42,8 @@ from .SolutionStructs import Solution, ProblemType, ProblemSizes from .TensileCreateLibrary import copyStaticFiles, writeSolutionsAndKernels from .CustomKernels import getCustomKernelConfig +from .Toolchain.Assembly import AssemblyToolchain +from .Toolchain.Source import SourceToolchain def generateForkedSolutions(problemType, constantParams, forkPermutations, cxxCompiler): @@ -110,7 +112,8 @@ def generateCustomKernelSolutions(problemType, customKernels, internalSupportPar return solutions def writeBenchmarkFiles(stepBaseDir, solutions, problemSizes, \ - biasTypeArgs, factorDimArgs, activationArgs, icacheFlushArgs, stepName, solutionSummationSizes, cxxCompiler, assembler, offloadBundler): + biasTypeArgs, factorDimArgs, activationArgs, icacheFlushArgs, stepName, solutionSummationSizes, \ + asmToolchain: AssemblyToolchain, srcToolchain: SourceToolchain): """Write all the files needed for a given benchmarking step""" copyStaticFiles() @@ -138,19 +141,19 @@ def writeBenchmarkFiles(stepBaseDir, solutions, problemSizes, \ kernelSerialNaming = Solution.getSerialNaming(kernels) kernelMinNaming = Solution.getMinNaming(kernels) - kernelWriterAssembly = KernelWriterAssembly(kernelMinNaming, kernelSerialNaming, cxxCompiler) + kernelWriterAssembly = KernelWriterAssembly(kernelMinNaming, kernelSerialNaming, srcToolchain.compiler) # write solution, kernels and CMake problemType = solutions[0]["ProblemType"] - codeObjectFiles, _ = writeSolutionsAndKernels( \ - globalParameters["WorkingPath"], cxxCompiler, assembler, offloadBundler, \ + codeObjectFiles, _= writeSolutionsAndKernels( \ + globalParameters["WorkingPath"], asmToolchain, srcToolchain, \ solutions, kernels, kernelHelperOjbs, \ kernelWriterAssembly, errorTolerant=True ) # ^ this is where solutions is mutated newLibraryDir = ensurePath(os.path.join(globalParameters["WorkingPath"], 'library')) newLibraryFile = os.path.join(newLibraryDir, "TensileLibrary") - newLibrary = SolutionLibrary.MasterSolutionLibrary.BenchmarkingLibrary(solutions, cxxCompiler) + newLibrary = SolutionLibrary.MasterSolutionLibrary.BenchmarkingLibrary(solutions, srcToolchain.compiler) newLibrary.applyNaming(kernelMinNaming) LibraryIO.write(newLibraryFile, Utils.state(newLibrary), globalParameters["LibraryFormat"]) @@ -192,7 +195,7 @@ def writeBenchmarkFiles(stepBaseDir, solutions, problemSizes, \ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeGroupIdx, useCache, - cxxCompiler: str, cCompiler: str, assembler: str, offloadBundler: str + asmToolchain: AssemblyToolchain, srcToolchain: SourceToolchain, cCompiler: str ): """Run the benchmarking for a single entry in the BenchmarkProblems of a Tensile config""" benchmarkTestFails = 0 @@ -275,10 +278,10 @@ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeG maxPossibleSolutions = len(forkPermutations) regSolutions = generateForkedSolutions(benchmarkProcess.problemType, \ - benchmarkStep.constantParams, forkPermutations, cxxCompiler) + benchmarkStep.constantParams, forkPermutations, srcToolchain.compiler) kcSolutions = generateCustomKernelSolutions(benchmarkProcess.problemType, \ benchmarkStep.customKernels, benchmarkStep.internalSupportParams, \ - not benchmarkStep.customKernelWildcard, cxxCompiler) + not benchmarkStep.customKernelWildcard, srcToolchain.compiler) maxPossibleSolutions += len(kcSolutions) solutions = regSolutions + kcSolutions @@ -307,7 +310,7 @@ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeG codeObjectFiles = writeBenchmarkFiles(stepBaseDir, solutions, \ benchmarkStep.problemSizes, benchmarkStep.biasTypeArgs, \ benchmarkStep.factorDimArgs, benchmarkStep.activationArgs, \ - benchmarkStep.icacheFlushArgs, shortName, [], cxxCompiler, assembler, offloadBundler) + benchmarkStep.icacheFlushArgs, shortName, [], asmToolchain, srcToolchain) # ^ this mutates solutions # write cache data @@ -341,7 +344,7 @@ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeG writeClientConfigIni(True, benchmarkStep.problemSizes, benchmarkStep.biasTypeArgs, benchmarkStep.factorDimArgs, benchmarkStep.activationArgs, - benchmakrStep.icacheFlushArgs, conProblemType, + benchmarkStep.icacheFlushArgs, conProblemType, globalParameters["WorkingPath"], codeObjectFiles, resultsFileName, outFile) @@ -356,7 +359,7 @@ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeG if not os.path.exists(resultsFileName) or globalParameters["ForceRedoBenchmarkProblems"]: libraryLogicPath = None forBenchmark = True - returncode = runClient(libraryLogicPath, forBenchmark, enableTileSelection, cxxCompiler, cCompiler) + returncode = runClient(libraryLogicPath, forBenchmark, enableTileSelection, srcToolchain.compiler, cCompiler) if returncode: benchmarkTestFails += 1 @@ -376,9 +379,9 @@ def benchmarkProblemType(problemTypeConfig, problemSizeGroupConfig, problemSizeG return (resultsFileBaseFinal, benchmarkTestFails) -def main(config, useCache, cxxCompiler: str, cCompiler: str, assembler: str, offloadBundler: str): +def main(config, useCache, asmToolchain: AssemblyToolchain, srcToolchain: SourceToolchain, cCompiler: str): """Entry point for the "BenchmarkProblems" section of a Tensile config yaml""" - ClientExecutable.getClientExecutable(cxxCompiler, cCompiler) + ClientExecutable.getClientExecutable(srcToolchain.compiler, cCompiler) if config is None: print(f'No config specified in {globalParameters["ConfigPath"]}, built client only') @@ -417,7 +420,7 @@ def main(config, useCache, cxxCompiler: str, cCompiler: str, assembler: str, off # benchmark problem size group (resultsFileBaseFinal, benchmarkErrors) = \ - benchmarkProblemType(problemTypeConfig, sizeGroupConfig, idx, useCache, cxxCompiler, cCompiler, assembler, offloadBundler) + benchmarkProblemType(problemTypeConfig, sizeGroupConfig, idx, useCache, asmToolchain, srcToolchain, cCompiler) totalTestFails += benchmarkErrors print("clientExit={} {} for {}" \ diff --git a/tensilelite/Tensile/BuildCommands/AssemblyCommands.py b/tensilelite/Tensile/BuildCommands/AssemblyCommands.py deleted file mode 100644 index df6c7a6764..0000000000 --- a/tensilelite/Tensile/BuildCommands/AssemblyCommands.py +++ /dev/null @@ -1,122 +0,0 @@ -import collections -import math -import os -import shutil -import subprocess - -from pathlib import Path -from typing import List, Union - -from .. import Utils -from ..TensileInstructions import getGfxName -from ..Common import globalParameters, print2, ensurePath, printWarning -from ..KernelWriterAssembly import KernelWriterAssembly -from .SharedCommands import compressCodeObject - -def _linkIntoCodeObject( - objFiles: List[str], coPathDest: Union[Path, str], kernelWriterAssembly: KernelWriterAssembly, assembler: str -): - """Links object files into a code object file. - - Args: - objectFiles: A list of object files to be linked. - coPathDest: The destination path for the code object file. - kernelWriterAssembly: An instance of KernelWriterAssembly to get link arguments. - - Raises: - RuntimeError: If linker invocation fails. - """ - if os.name == "nt": - # Use args file on Windows b/c the command may exceed the limit of 8191 characters - with open(Path.cwd() / "clangArgs.txt", 'wt') as file: - file.write(" ".join(objFiles)) - file.flush() - args = [assembler, '-target', 'amdgcn-amd-amdhsa', '-o', coFileRaw, '@clangArgs.txt'] - subprocess.check_call(args, cwd=asmDir) - else: - numObjFiles = len(objFiles) - maxObjFiles = 10000 - - if numObjFiles > maxObjFiles: - batchedObjFiles = [objFiles[i:i+maxObjFiles] for i in range(0, numObjFiles, maxObjFiles)] - numBatches = int(math.ceil(numObjFiles / maxObjFiles)) - - newObjFiles = [str(coPathDest) + "." + str(i) for i in range(0, numBatches)] - newObjFilesOutput = [] - - for batch, filename in zip(batchedObjFiles, newObjFiles): - if len(batch) > 1: - args = [globalParameters["ROCmLdPath"], "-r"] + batch + [ "-o", filename] - print2(f"Linking object files into fewer object files: {' '.join(args)}") - subprocess.check_call(args) - newObjFilesOutput.append(filename) - else: - newObjFilesOutput.append(batchedObjFiles[0]) - - objFiles = newObjFilesOutput - - args = kernelWriterAssembly.getLinkCodeObjectArgs(objFiles, str(coPathDest)) - print2(f"Linking object files into code object: {' '.join(args)}") - subprocess.check_call(args) - - - -def buildAssemblyCodeObjectFiles(kernels, kernelWriterAssembly, outputPath, assembler: str, offloadBundler: str, compress: bool=True): - - isAsm = lambda k: k["KernelLanguage"] == "Assembly" - - extObj = ".o" - extCo = ".co" - extCoRaw = ".co.raw" - - destDir = Path(ensurePath(os.path.join(outputPath, 'library'))) - asmDir = Path(kernelWriterAssembly.getAssemblyDirectory()) - - archKernelMap = collections.defaultdict(list) - for k in filter(isAsm, kernels): - archKernelMap[tuple(k['ISA'])].append(k) - - coFiles = [] - for arch, archKernels in archKernelMap.items(): - if len(archKernels) == 0: - continue - - gfx = getGfxName(arch) - if globalParameters["LazyLibraryLoading"]: - objectFiles = [str(asmDir / (kernelWriterAssembly.getKernelFileBase(k) + extObj)) for k in archKernels if 'codeObjectFile' not in k] - - coFileMap = collections.defaultdict(list) - - if len(objectFiles): - coFileMap[asmDir / ("TensileLibrary_"+ gfx + extCoRaw)] = objectFiles - - for kernel in archKernels: - coName = kernel.get("codeObjectFile", None) - if coName: - coFileMap[asmDir / (coName + extCoRaw)].append(str(asmDir / (kernelWriterAssembly.getKernelFileBase(kernel) + extObj))) - - for coFileRaw, objFiles in coFileMap.items(): - - _linkIntoCodeObject(objFiles, coFileRaw, kernelWriterAssembly, assembler) - coFile = destDir / coFileRaw.name.replace(extCoRaw, extCo) - if compress: - compressCodeObject(coFileRaw, coFile, gfx, offloadBundler) - else: - shutil.move(coFileRaw, coFile) - - coFiles.append(coFile) - else: - # no mergefiles - def newCoFileName(kName): - return os.path.join(destDir, kName + '_' + gfx + '.co') - - def orgCoFileName(kName): - return os.path.join(asmDir, kName + '.co') - - for src, dst in Utils.tqdm(((orgCoFileName(kName), newCoFileName(kName)) for kName in \ - map(lambda k: kernelWriterAssembly.getKernelFileBase(k), archKernels)), "Copying code objects"): - shutil.copyfile(src, dst) - coFiles.append(dst) - printWarning("Code object files are not compressed in `--no-merge-files` build mode.") - - return coFiles diff --git a/tensilelite/Tensile/BuildCommands/SharedCommands.py b/tensilelite/Tensile/BuildCommands/SharedCommands.py deleted file mode 100644 index 5d77ae45a8..0000000000 --- a/tensilelite/Tensile/BuildCommands/SharedCommands.py +++ /dev/null @@ -1,40 +0,0 @@ -import subprocess - -from typing import Union -from pathlib import Path - -from ..Common import print2 - -def compressCodeObject( - coPathSrc: Union[Path, str], coPathDest: Union[Path, str], gfx: str, bundler: str -): - """Compresses a code object file using the provided bundler. - - Args: - coPathSrc: The source path of the code object file to be compressed. - coPathDest: The destination path for the compressed code object file. - gfx: The target GPU architecture. - bundler: The path to the Clang Offload Bundler executable. - - Raises: - RuntimeError: If compressing the code object file fails. - """ - args = [ - bundler, - "--compress", - "--type=o", - "--bundle-align=4096", - f"--targets=host-x86_64-unknown-linux,hipv4-amdgcn-amd-amdhsa--{gfx}", - "--input=/dev/null", - f"--input={str(coPathSrc)}", - f"--output={str(coPathDest)}", - ] - - print2(f"Bundling/compressing code objects: {' '.join(args)}") - try: - out = subprocess.check_output(args, stderr=subprocess.STDOUT) - print2(f"Output: {out}") - except subprocess.CalledProcessError as err: - raise RuntimeError( - f"Error compressing code object via bundling: {err.output}\nFailed command: {' '.join(args)}" - ) diff --git a/tensilelite/Tensile/BuildCommands/SourceCommands.py b/tensilelite/Tensile/BuildCommands/SourceCommands.py deleted file mode 100644 index 0b1ed01d2f..0000000000 --- a/tensilelite/Tensile/BuildCommands/SourceCommands.py +++ /dev/null @@ -1,194 +0,0 @@ -import itertools -import os -import re -import shlex -import shutil -import subprocess -from pathlib import Path -from typing import Iterable, List, Union - -from ..Common import globalParameters, print2, ensurePath, supportedCompiler, ParallelMap2, splitArchs, which - -def _compileSourceObjectFile(cmdlineArchs: List[str], cxxCompiler: str, cxxSrcPath: str, objDestPath: str, outputPath: str): - """Compiles a source file into an object file. - - Args: - cmdlineArchs: List of architectures for offloading. - cxxCompiler: The C++ compiler to use. - kernelFile: The path to the kernel source file. - buildPath: The build directory path. - objectFilename: The name of the output object file. - outputPath: The output directory path. - globalParameters: A dictionary of global parameters. - - Raises: - RuntimeError: If the compilation command fails. - """ - archFlags = ['--offload-arch=' + arch for arch in cmdlineArchs] - - #TODO(@jichangjichang) Needs to be fixed when Maneesh's change is made available - hipFlags = ["-D__HIP_HCC_COMPAT_MODE__=1"] - hipFlags.extend( - ["--genco"] if cxxCompiler == "hipcc" else ["--cuda-device-only", "-x", "hip", "-O3"] - ) - - hipFlags.extend(['-I', outputPath]) - hipFlags.extend(["-Xoffload-linker", "--build-id=%s"%globalParameters["BuildIdKind"]]) - hipFlags.append('-std=c++17') - if globalParameters["AsanBuild"]: - hipFlags.extend(["-fsanitize=address", "-shared-libasan", "-fuse-ld=lld"]) - if globalParameters["SaveTemps"]: - hipFlags.append('--save-temps') - - launcher = shlex.split(os.environ.get('Tensile_CXX_COMPILER_LAUNCHER', '')) - - if os.name == "nt": - hipFlags.extend(['-fms-extensions', '-fms-compatibility', '-fPIC', '-Wno-deprecated-declarations']) - - args = launcher + [which(cxxCompiler)] + hipFlags + archFlags + [cxxSrcPath, '-c', '-o', objDestPath] - - try: - out = subprocess.check_output(args, stderr=subprocess.STDOUT) - print2(f"Output: {out}" if out else "") - except subprocess.CalledProcessError as err: - raise RuntimeError(f"Error compiling source object file: {err.output}\nFailed command: {' '.join(args)}") - - -def _listTargetTriples(bundler: str, objFile: str) -> List[str]: - """Lists the target triples in an object file. - - Args: - bundler: The path to the bundler, typically ``clang-offload-bundler``. - objFile: The object file path. - - Returns: - List of target triples in the object file. - """ - args = [bundler, "--type=o", f"--input={objFile}", "-list"] - try: - listing = subprocess.check_output(args, stderr=subprocess.STDOUT).decode().split("\n") - except subprocess.CalledProcessError as err: - raise RuntimeError(f"Error listing target triples in object files: {err.output}\nFailed command: {' '.join(args)}") - return listing - - -def _computeSourceCodeObjectFilename(target: str, base: str, buildPath: Union[Path, str], arch: str) -> Union[Path, None]: - """Generates a code object file path using the target, base, and build path. - - Args: - target: The target triple. - base: The base name for the output file (name without extension). - buildPath: The build directory path. - - Returns: - Path to the code object file. - """ - coPath = None - buildPath = Path(buildPath) - if "TensileLibrary" in base and "fallback" in base: - coPath = buildPath / "{0}_{1}.hsaco.raw".format(base, arch) - elif "TensileLibrary" in base: - variant = [t for t in ["", "xnack-", "xnack+"] if t in target][-1] - baseVariant = base + "-" + variant if variant else base - if arch in baseVariant: - coPath = buildPath / (baseVariant + ".hsaco.raw") - else: - coPath= buildPath / "{0}.so-000-{1}.hsaco.raw".format(base, arch) - - return coPath - - -def _unbundleSourceCodeObjects(bundler: str, target: str, infile: str, outfileRaw: str): - """Unbundles source code object files using the Clang Offload Bundler. - - Args: - bundler: The path to the bundler, typically ``clang-offload-bundler``. - target: The target architecture string. - infile: The input file path. - outfileRaw: The output raw file path. - - Raises: - RuntimeError: If unbundling the source code object file fails. - """ - args = [ - bundler, - "--type=o", - f"--targets={target}", - f"--input={infile}", - f"--output={outfileRaw}", - "--unbundle", - ] - - print2("Unbundling source code object file: " + " ".join(args)) - try: - out = subprocess.check_output(args, stderr=subprocess.STDOUT) - print2(f"Output: {out}" if out else "") - except subprocess.CalledProcessError as err: - raise RuntimeError(f"Error unbundling source code object file: {err.output}\nFailed command: {' '.join(args)}") - - -def _buildSourceCodeObjectFile(cxxCompiler: str, offloadBundler: str, outputPath: Union[Path, str], kernelPath: Union[Path, str]) -> List[str]: - """Compiles a HIP source code file into a code object file. - - Args: - cxxCompiler: The C++ compiler to use. - outputPath: The output directory path where code objects will be placed. - kernelPath: The path to the kernel source file. - - Returns: - List of paths to the created code objects. - """ - buildPath = Path(ensurePath(os.path.join(globalParameters['WorkingPath'], 'code_object_tmp'))) - destPath = Path(ensurePath(os.path.join(outputPath, 'library'))) - kernelPath = Path(kernelPath) - - if "CmakeCxxCompiler" in globalParameters and globalParameters["CmakeCxxCompiler"] is not None: - os.environ["CMAKE_CXX_COMPILER"] = globalParameters["CmakeCxxCompiler"] - - objFilename = kernelPath.stem + '.o' - coPathsRaw = [] - coPaths= [] - - if not supportedCompiler(cxxCompiler): - raise RuntimeError("Unknown compiler {}".format(cxxCompiler)) - - _, cmdlineArchs = splitArchs() - - objPath = str(buildPath / objFilename) - _compileSourceObjectFile(cmdlineArchs, cxxCompiler, str(kernelPath), objPath, str(outputPath)) - - if not offloadBundler: - raise RuntimeError("No bundler found; set TENSILE_ROCM_OFFLOAD_BUNDLER_PATH to point to clang-offload-bundler") - - for target in _listTargetTriples(offloadBundler, objPath): - match = re.search("gfx.*$", target) - if match: - arch = re.sub(":", "-", match.group()) - coPathRaw = _computeSourceCodeObjectFilename(target, kernelPath.stem, buildPath, arch) - if not coPathRaw: continue - _unbundleSourceCodeObjects(offloadBundler, target, objPath, str(coPathRaw)) - - coPath = str(destPath / coPathRaw.stem) - coPathsRaw.append(coPathRaw) - coPaths.append(coPath) - - for src, dst in zip(coPathsRaw, coPaths): - shutil.move(src, dst) - - return coPaths - -def buildSourceCodeObjectFiles(cxxCompiler: str, offloadBundler: str, kernelFiles: List[Path], outputPath: Path) -> Iterable[str]: - """Compiles HIP source code files into code object files. - - Args: - cxxCompiler: The C++ compiler to use. - kernelFiles: List of paths to the kernel source files. - outputPath: The output directory path where code objects will be placed. - removeTemporaries: Whether to clean up temporary files. - - Returns: - List of paths to the created code objects. - """ - args = zip(itertools.repeat(cxxCompiler), itertools.repeat(offloadBundler), itertools.repeat(outputPath), kernelFiles) - coFiles = ParallelMap2(_buildSourceCodeObjectFile, args, "Compiling source kernels") - return itertools.chain.from_iterable(coFiles) diff --git a/tensilelite/Tensile/BuildCommands/__init__.py b/tensilelite/Tensile/BuildCommands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tensilelite/Tensile/ClientWriter.py b/tensilelite/Tensile/ClientWriter.py index af537ef215..4b9f7c1ccc 100644 --- a/tensilelite/Tensile/ClientWriter.py +++ b/tensilelite/Tensile/ClientWriter.py @@ -24,7 +24,7 @@ from . import ClientExecutable from . import LibraryIO -from .TensileInstructions import getGfxName, DataType, getCOVFromParam +from .TensileInstructions import getGfxName, DataType from .Common import globalParameters, pushWorkingPath, popWorkingPath, print1, printExit, CHeader, printWarning, listToInitializer, ClientExecutionLock from .SolutionStructs import Problem, ProblemType, ProblemSizesMock, ProblemSizesMockDummy, ActivationArgs, BiasTypeArgs, FactorDimArgs from .TensileCreateLibrary import copyStaticFiles diff --git a/tensilelite/Tensile/Common.py b/tensilelite/Tensile/Common.py index 97a7a708bd..115eeffd8f 100644 --- a/tensilelite/Tensile/Common.py +++ b/tensilelite/Tensile/Common.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -25,9 +25,9 @@ from . import __version__ from . import Parallel from .TensileInstructions import getGfxName, TensileInstructions -from .Utilities.Toolchain import supportedCxxCompiler as supportedCompiler from collections import OrderedDict from copy import deepcopy +from typing import Tuple import math import os.path @@ -35,6 +35,10 @@ import sys import time import re + + +IsaVersion = Tuple[int, int, int] + startTime = time.time() @@ -250,7 +254,7 @@ else: globalParameters["RuntimeLanguage"] = "HIP" -globalParameters["CodeObjectVersion"] = "default" +globalParameters["CodeObjectVersion"] = "4" globalParameters["Architecture"] = "all" # might be deprecated @@ -1582,7 +1586,7 @@ def capRow(caps, cap): printTable([headerRow] + asmCapRows + archCapRows) def which(p): - if supportedCompiler(p) and 'CMAKE_CXX_COMPILER' in os.environ and os.path.isfile(os.environ['CMAKE_CXX_COMPILER']): + if 'CMAKE_CXX_COMPILER' in os.environ and os.path.isfile(os.environ['CMAKE_CXX_COMPILER']): return os.environ['CMAKE_CXX_COMPILER'] if os.name == "nt": exes = [p+x for x in ['.exe', '', '.bat']] # bat may be front end for file with no extension @@ -1698,6 +1702,9 @@ def assignGlobalParameters(config, cxxCompiler=None): if "KeepBuildTmp" in config: globalParameters["KeepBuildTmp"] = config["KeepBuildTmp"] + if "CodeObjectVersion" in config: + globalParameters["CodeObjectVersion"] = config["CodeObjectVersion"] + # read current gfx version returncode = detectGlobalCurrentISA() if globalParameters["CurrentISA"] == (0,0,0): diff --git a/tensilelite/Tensile/Components/Signature.py b/tensilelite/Tensile/Components/Signature.py index bc3ff3adc7..89dc4d30d7 100644 --- a/tensilelite/Tensile/Components/Signature.py +++ b/tensilelite/Tensile/Components/Signature.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -25,7 +25,7 @@ from ..Component import Signature from ..Common import globalParameters from ..Utils import DataDirection -from ..TensileInstructions import SignatureBase, getCOVFromParam +from ..TensileInstructions import SignatureBase from ..TensileInstructions import SignatureValueKind as SVK from ..Activation import ActivationType @@ -128,7 +128,7 @@ def __call__(self, writer) -> SignatureBase: sgprWgZ = 1 if kernel["ProblemType"]["NumIndicesC"] > 2 else 0 signature = SignatureBase(kernelName=writer.states.kernelName, kernArgsVersion=kernel["InternalSupportParams"]["KernArgsVersion"], - codeObjectVersion=getCOVFromParam(kernel["CodeObjectVersion"]), + codeObjectVersion=kernel["CodeObjectVersion"], groupSegmentSize=group_segment_size, sgprWorkGroup=[1, 1, sgprWgZ], vgprWorkItem=0, diff --git a/tensilelite/Tensile/KernelWriter.py b/tensilelite/Tensile/KernelWriter.py index 12b77bfbcf..378d47cc36 100644 --- a/tensilelite/Tensile/KernelWriter.py +++ b/tensilelite/Tensile/KernelWriter.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -26,12 +26,12 @@ from .TensileInstructions import Item, TensileInstructions, slash50, replaceHolder, \ KernelBody, Module, StructuredModule, TextBlock, Dump, LabelManager, \ RegisterPool, Assert, fastdeepcopy, TensileInstructionsPassOptions, \ - TensileInstructionsPass, getAsmCompileArgs, getAsmLinkCodeObjectArgs, \ + TensileInstructionsPass, \ SLongBranchPositive, SBranch, SCBranchSCC0, SCBranchSCC1 from .TensileInstructions.Instructions import * from .KernelWriterModules import * from .TensilePass import TensilePass, TensilePassOptions -from .Common import globalParameters, CHeader, roundUp, Backup, print2, printExit +from .Common import globalParameters, CHeader, print1, printWarning, roundUp, Backup, print2, printExit from .Component import Component, LraTileProperties from .Components.Signature import UserArgumentsInfo from .CustomKernels import isCustomKernelConfig @@ -44,7 +44,6 @@ import abc import os import shutil -import subprocess import sys import collections from dataclasses import dataclass, field @@ -2879,6 +2878,7 @@ def kernelBody( self, kernel, tensorParametersA, tensorParametersB ): TensileInstructionsPass(moduleKernelBody, tipo) error = self.states.overflowedResources + print2(f" found error code {error} with overflowed resources set to {self.states.overflowedResources}") return (error, str(moduleKernelBody)) @@ -4962,194 +4962,53 @@ def _shortenFileBase(self, kernel): return firstPart + secondPart - def _byteArrayScriptSource(self): - return """ -#!/usr/bin/env python - -fileString = "" -fileString += "/*******************************************************************************\\n" -fileString += "* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved.\\n" -fileString += "*\\n" -fileString += "* Permission is hereby granted, free of charge, to any person obtaining a copy\\n" -fileString += '* of this software and associated documentation files (the \"Software\"), to deal\\n' -fileString += "* in the Software without restriction, including without limitation the rights\\n" -fileString += "* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cop-\\n" -fileString += "* ies of the Software, and to permit persons to whom the Software is furnished\\n" -fileString += "* to do so, subject to the following conditions:\\n" -fileString += "*\\n" -fileString += "* The above copyright notice and this permission notice shall be included in all\\n" -fileString += "* copies or substantial portions of the Software.\\n" -fileString += "*\\n" -fileString += '* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM-\\n' -fileString += "* PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\\n" -fileString += "* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\\n" -fileString += "* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\\n" -fileString += "* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNE-\\n" -fileString += "* CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\n" -fileString += "*******************************************************************************/\\n\\n" -fileString += "/**************************************************\\n" -fileString += "* This file was generated by Tensile: *\\n" -fileString += "* https://github.com/ROCmSoftwarePlatform/Tensile *\\n" -fileString += "**************************************************/\\n\\n\\n" -import os.path -fileString += '#include "Kernels.h"\\n\\n' -fileString += "/* code object byte array */\\n\\n" -codeObjectFileNames = [f for f in os.listdir(".") if (os.path.isfile(f) and f.endswith(".co"))] -for codeObjectFileName in codeObjectFileNames: - print codeObjectFileName - print "\\n" - kernelName=os.path.splitext(codeObjectFileName)[0] - codeObjectFile = open(codeObjectFileName, "r") - codeObjectByteArray = bytearray(codeObjectFile.read()) - codeObjectFile.close() -# write code object byte array for asm - fileString += "const unsigned char %s_coba[%u] = {\\n" % (kernelName, len(codeObjectByteArray)) - for byteIdx in range(0, len(codeObjectByteArray)): - byte = codeObjectByteArray[byteIdx] - fileString += "0x%02x" % byte - if byteIdx < len(codeObjectByteArray)-1: - fileString += "," - else: - fileString += "};\\n" - if byteIdx % 16 == 15: - fileString += "\\n" - text_file = open("Kernels.cpp", "w") - text_file.write("%s" % fileString) - text_file.close() -""" - - def _writeByteArrayScript(self): - asmPath = self.getAssemblyDirectory() - - bytearrayFileName = os.path.join(asmPath,"insert_byte_array.py") - if not os.path.isfile(bytearrayFileName): - with open(bytearrayFileName, 'w') as bytearrayFile: - bytearrayFile.write(self._byteArrayScriptSource()) - os.chmod(bytearrayFileName, 0o777) - return bytearrayFileName - - def getReplacementKernelPath(self, kernel): - if not isCustomKernelConfig(kernel): - return None - kernelName = self.getKernelName(kernel) + def _getCustomKernelSource(self, kernel, CustomKernelDirectory): + kernelName = self.getKernelFileBase(kernel) + with open(os.path.join(CustomKernelDirectory, (kernelName + ".s"))) as f: + hipccver = globalParameters['HipClangVersion'].split(".") + hipccMaj = int(hipccver[0]) + hipccPatch = int(hipccver[2].split("-")[0]) + if not (hipccMaj >= 6 and hipccPatch >= 32650): + code = [] + for line in f.readlines(): + if "amdhsa_user_sgpr_kernarg_preload" not in line: + code.append(line) + code = "".join(code) + else: + code = f.read() - if isCustomKernelConfig(kernel): - return os.path.join(globalParameters["CustomKernelDirectory"], (kernelName + ".s")) - else: # Replacement kernel - return ReplacementKernels.Get(kernelName) + self.tPA = tensorParametersA = {} + self.tPB = tensorParametersB = {} + self.states.kernel = kernel + self.states.language = "ASM" + self.states.version = tuple(kernel["ISA"]) if "ISA" in kernel else globalParameters["CurrentISA"] + if not globalParameters["AsmCaps"][self.states.version]["SupportedISA"]: + self.states.version = (9,0,0) + printWarning(f"ISA: {self.version} is not supported; overriding with {self.states.version}") - def _getKernelSource(self, kernel): + return code + + def _getKernelSource(self, kernel: Solution): """ Returns the source of the kernel, either C++ or assembly. """ - fileString = "" tensorParametersA = {} tensorParametersB = {} - self.initKernel(kernel, tensorParametersA, tensorParametersB ) + self.initKernel(kernel, tensorParametersA, tensorParametersB) self.stringIdx = 0 - (error, kb) = self.kernelBody( kernel, tensorParametersA, tensorParametersB) + (error, kb) = self.kernelBody(kernel, tensorParametersA, tensorParametersB) fileString += str(kb) if error != 0: if globalParameters["ForceGenerateKernel"]: - print ("warning: Generating kernel source resulted in error {}, but ForceGenerateKernel=1 so saving source".format(error)) + printWarning("Generating kernel source resulted in error {}, but ForceGenerateKernel=1 so saving source".format(error)) else: raise RuntimeError("Generating kernel source resulted in error {}".format(error)) return fileString - def _getKernelObjectAssemblyFile(self, kernel): - asmPath = self.getAssemblyDirectory() - # write assembly file to assembly directory - kernelName = self.getKernelFileBase(kernel) - fileBase = os.path.join(asmPath, kernelName ) - assemblyFileName = "%s.s" % fileBase - - replacementKernel = self.getReplacementKernelPath(kernel) - - if replacementKernel is not None: - self.tPA = tensorParametersA = {} - self.tPB = tensorParametersB = {} - if isCustomKernelConfig(kernel): - kernelFoundMessage = "Custom kernel filename " - # ISA version, such as 803 - self.states.kernel = kernel - self.states.language = "ASM" - self.states.version = globalParameters["CurrentISA"] - if "ISA" in kernel: - self.states.version = tuple(kernel["ISA"]) - if not globalParameters["AsmCaps"][self.states.version]["SupportedISA"]: - defaultIsa = (9,0,0) - print("warning: ISA:", self.version, " is not supported; overriding with ", defaultIsa) - self.states.version = defaultIsa - else: - kernelFoundMessage = "replacement_assemblyFilename " - self.initKernel(kernel, tensorParametersA, tensorParametersB ) - - shutil.copyfile(replacementKernel, assemblyFileName) - - # Temporary remove preload kernel argument for rpk - hipccver = globalParameters['HipClangVersion'].split(".") - hipccMaj = int(hipccver[0]) - hipccPatch = int(hipccver[2].split("-")[0]) - if not (hipccMaj >= 6 and hipccPatch >= 32650): - os.system("sed -i '/amdhsa_user_sgpr_kernarg_preload_length/d' %s"%assemblyFileName) - os.system("sed -i '/amdhsa_user_sgpr_kernarg_preload_offset/d' %s"%assemblyFileName) - - if globalParameters["PrintLevel"] >= 2: - print(kernelFoundMessage + assemblyFileName) - print(self.states.kernel) - else: - kernelSource = self._getKernelSource(kernel) - - if globalParameters["PrintLevel"] >= 2: - print("write_assemblyFilename %s" % assemblyFileName) - print(self.states.kernel) - - with open(assemblyFileName, 'w') as assemblyFile: - assemblyFile.write(kernelSource) - - return assemblyFileName - - def _getAssembledKernelObjectFile(self, kernel): - assemblyFileName = self._getKernelObjectAssemblyFile(kernel) - - base, ext = os.path.splitext(assemblyFileName) - objectFileName = base + '.o' - - debug = globalParameters.get("AsmDebug", False) - args = self.getCompileArgs(assemblyFileName, objectFileName, debug=debug) - if globalParameters["PrintCodeCommands"]: - print (' '.join(args), " && ") - - subprocess.check_call(args, cwd=self.getAssemblyDirectory()) - - if not globalParameters["KeepBuildTmp"]: - os.remove(assemblyFileName) - - return objectFileName - - def _getSingleCodeObjectFile(self, kernel): - objectFileName = self._getAssembledKernelObjectFile(kernel) - - base, ext = os.path.splitext(objectFileName) - coFileName = base + '.co' - - args = self.getLinkCodeObjectArgs([objectFileName], coFileName) - if globalParameters["PrintCodeCommands"]: - print (' '.join(args)) - - subprocess.check_call(args, cwd=self.getAssemblyDirectory()) - - return coFileName - - ############################################################################## - # - # Entry Functions - # - ############################################################################## ############################################################################## # get kernel name @@ -5167,10 +5026,8 @@ def getKernelName(self, kernel): kernelName = Solution.getNameMin(kernel, self.kernelMinNaming, True) return kernelName - def getAssemblyDirectory(self): - return Common.ensurePath(os.path.join(globalParameters["WorkingPath"], "assembly")) - - def getSourceFileString(self, kernel): + @abc.abstractmethod + def getSourceFileString(self, kernel) -> Tuple[int, str]: """ Returns a string suitable for placing in Kernels.cpp. This means the actual kernel source in the case of a source kernel, or an assembled code object byte array definition in the case of an assembly kernel, @@ -5182,43 +5039,8 @@ def getSourceFileString(self, kernel): * A code object file * A Python script which can create byte array variable definitions. """ + pass - try: - if kernel["KernelLanguage"] == "Assembly": - # asmPath = self.getAssemblyDirectory() - # kernelName = self.getKernelName(kernel) - - # Skip if .o files will have already been built for this file - # @TODO remove need for this with better code organization - if kernel.duplicate: - self.language = "ASM" - return (0, "") - if globalParameters["GenerateSourcesAndExit"]: - # only create the assembly file. - self._getKernelObjectAssemblyFile(kernel) - return (0, "") - else: - self._writeByteArrayScript() - self._getSingleCodeObjectFile(kernel) - - # I guess in this case we are making sure that the code object file exists by executing the code - # above but we aren't placing it into the source. - return (0, "") - - else: - return (0, self._getKernelSource(kernel)) - - except subprocess.CalledProcessError as exc: - print(exc) - return (-1, "") - except RuntimeError as exc: - if globalParameters["PrintSolutionRejectionReason"]: - print(exc) - return (-2, "") - - ############################################################################## - # header file string - ############################################################################## def getHeaderFileString(self, kernel): kernelName = self.getKernelName(kernel) fileString = "" # CHeader @@ -5227,21 +5049,6 @@ def getHeaderFileString(self, kernel): return fileString - ############################################################################## - # Compile Args - ############################################################################## - def getCompileArgs(self, sourceFileName, objectFileName, *moreArgs, isa=None, wavefrontSize=None, debug=False): - if isa is None: - isa = self.states.version - if wavefrontSize is None: - wavefrontSize = self.states.kernel["WavefrontSize"] - return getAsmCompileArgs(self.assembler, \ - globalParameters["CodeObjectVersion"], \ - isa, wavefrontSize, sourceFileName, objectFileName, *moreArgs, debug=debug) - - def getLinkCodeObjectArgs(self, objectFileNames, coFileName, *moreArgs): - return getAsmLinkCodeObjectArgs(self.assembler, \ - objectFileNames, coFileName, globalParameters['BuildIdKind'], *moreArgs) def setTensileInstructions(self, ti): self.ti = ti @@ -5294,3 +5101,7 @@ def findInstByName(module, names, count): else: _placeholder.add(SBranch(labelName=_target.getLabelName())) currentInstLength += _placeholder.countType(Instruction) + + @property + def isa(self): + return self.states.version diff --git a/tensilelite/Tensile/KernelWriterAssembly.py b/tensilelite/Tensile/KernelWriterAssembly.py index f59ff7a586..4f9188de54 100644 --- a/tensilelite/Tensile/KernelWriterAssembly.py +++ b/tensilelite/Tensile/KernelWriterAssembly.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -38,7 +38,7 @@ LabelManager, Assert from .TensileInstructions.Instructions import * from .TensilePass import getActivationFunctionModuleName, getActivationBranchModuleName -from .Common import globalParameters, print2, printExit, printWarning, roundUp +from .Common import globalParameters, print2, printExit, printWarning, roundUp, ensurePath from .TensileInstructions.Containers import HWRegContainer from .Component import Component from .KernelWriter import KernelWriter, ConstValues, StateValues, StateVgprs, CodeModules @@ -48,13 +48,15 @@ from .AsmMemoryInstruction import MemoryInstruction from .Activation import ActivationType from .Utils import DataDirection +from .CustomKernels import isCustomKernelConfig from math import ceil, log, floor from copy import deepcopy from dataclasses import dataclass, field -from typing import NamedTuple +from typing import NamedTuple, Tuple -import collections +import os +import subprocess ################################################################################ # Assembly Kernel @@ -68,6 +70,23 @@ class KernelWriterAssembly(KernelWriter): def __init__(self, kernelMinNaming, kernelSerialNaming, assembler: str): super(KernelWriterAssembly, self).__init__(kernelMinNaming, kernelSerialNaming, assembler) + + def getSourceFileString(self, kernel) -> Tuple[int, str]: + assert kernel["KernelLanguage"] == "Assembly" + # Skip if .o files will have already been built for this file + if kernel.duplicate: + self.language = "ASM" + return (0, "") # should this be an non zero number + + try: + code = self._getCustomKernelSource(kernel, globalParameters["CustomKernelDirectory"]) if isCustomKernelConfig(kernel) else self._getKernelSource(kernel) + errcode = 0 + except RuntimeError as e: + printWarning(f"Failed to generate assembly source code for {kernel}: {e}") + code = "" + errcode = -2 + return (errcode, code) + def getSgprOccupancy(self, sgprs): return self.states.regCaps["PhysicalMaxSgpr"]//sgprs diff --git a/tensilelite/Tensile/KernelWriterBase.py b/tensilelite/Tensile/KernelWriterBase.py index f7038dce6d..bcff0a688d 100644 --- a/tensilelite/Tensile/KernelWriterBase.py +++ b/tensilelite/Tensile/KernelWriterBase.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -26,6 +26,9 @@ from abc import abstractmethod from copy import deepcopy +KERNEL_HELPER_FILENAME_CPP: str = "Kernels.cpp" +KERNEL_HELPER_FILENAME_H: str = "Kernels.h" + class KernelWriterBase(ABC): def __init__(self): diff --git a/tensilelite/Tensile/Ops/AMaxGenerator.py b/tensilelite/Tensile/Ops/AMaxGenerator.py index 04f5dd4a28..a579db0f3d 100644 --- a/tensilelite/Tensile/Ops/AMaxGenerator.py +++ b/tensilelite/Tensile/Ops/AMaxGenerator.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2023-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2023-2025 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 @@ -34,7 +34,7 @@ import Tensile.TensileInstructions as ti from Tensile.Common import detectGlobalCurrentISA, restoreDefaultGlobalParameters, \ assignGlobalParameters, getGfxName, gfxArch, globalParameters -from Tensile.Utilities.Toolchain import ToolchainDefaults, validateToolchain +from Tensile.Toolchain.Validators import ToolchainDefaults, validateToolchain def kernel_header(name: str, gfx_arch: str, vgpr: int, sgpr: int, lds: int): vgpr = ((vgpr+7)//8)*8 diff --git a/tensilelite/Tensile/Ops/LayerNormGenerator.py b/tensilelite/Tensile/Ops/LayerNormGenerator.py index 2d7dc17f20..093a437544 100644 --- a/tensilelite/Tensile/Ops/LayerNormGenerator.py +++ b/tensilelite/Tensile/Ops/LayerNormGenerator.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2023-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2023-2025 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 @@ -34,7 +34,7 @@ import Tensile.TensileInstructions as ti from Tensile.Common import detectGlobalCurrentISA, restoreDefaultGlobalParameters, \ assignGlobalParameters, getGfxName, gfxArch, globalParameters -from Tensile.Utilities.Toolchain import ToolchainDefaults, validateToolchain +from Tensile.Toolchain.Validators import ToolchainDefaults, validateToolchain def kernel_header(name: str, gfx_arch: str, vgpr: int, sgpr: int, lds: int): vgpr = ((vgpr+7)//8)*8 diff --git a/tensilelite/Tensile/Ops/SoftmaxGenerator.py b/tensilelite/Tensile/Ops/SoftmaxGenerator.py index ab40492258..1078615d84 100644 --- a/tensilelite/Tensile/Ops/SoftmaxGenerator.py +++ b/tensilelite/Tensile/Ops/SoftmaxGenerator.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2023-2025 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 @@ -33,7 +33,7 @@ import Tensile.TensileInstructions as ti from Tensile.Common import detectGlobalCurrentISA, restoreDefaultGlobalParameters, \ assignGlobalParameters, getGfxName, gfxArch, globalParameters -from Tensile.Utilities.Toolchain import ToolchainDefaults, validateToolchain +from Tensile.Toolchain.Validators import ToolchainDefaults, validateToolchain def record_num_calls(f): @wraps(f) diff --git a/tensilelite/Tensile/Parallel.py b/tensilelite/Tensile/Parallel.py index bf9a44db1c..99950aba04 100644 --- a/tensilelite/Tensile/Parallel.py +++ b/tensilelite/Tensile/Parallel.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2016-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2016-2025 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 @@ -182,7 +182,6 @@ def ParallelMap2(function, objects, message="", enable=True, multiArg=True, retu multiArg: True if objects represent multiple arguments (differentiates multi args vs single collection arg) """ - if return_as in ('generator', 'generator_unordered') and not joblibParallelSupportsGenerator(): return ParallelMapReturnAsGenerator(function, objects, message, enable, multiArg) @@ -192,8 +191,7 @@ def ParallelMap2(function, objects, message="", enable=True, multiArg=True, retu if threadCount <= 1 and globalParameters["ShowProgressBar"]: # Provide a progress bar for single-threaded operation. - callFunc = lambda args: function(*args) if multiArg else lambda args: function(args) - return [callFunc(args) for args in Utils.tqdm(objects, message)] + return [function(*args) if multiArg else function(args) for args in Utils.tqdm(objects, message)] countMessage = "" try: diff --git a/tensilelite/Tensile/SolutionStructs.py b/tensilelite/Tensile/SolutionStructs.py index 15b0e3bc75..d92a8248dd 100644 --- a/tensilelite/Tensile/SolutionStructs.py +++ b/tensilelite/Tensile/SolutionStructs.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -1095,10 +1095,9 @@ def __init__(self, config, cxxCompiler: str): if "CodeObjectVersion" not in self._state: if "CodeObjectVersion" in config: - self._state["CodeObjectVersion"] = config["CodeObjectVersion"] + self._state["CodeObjectVersion"] = str(config["CodeObjectVersion"]) else: - self._state["CodeObjectVersion"] = globalParameters["CodeObjectVersion"] - + self._state["CodeObjectVersion"] = str(globalParameters["CodeObjectVersion"]) # assign parameters without defaults for key in config: if (key != "ProblemType" or key != "InternalSupportParams") and key not in self._state: diff --git a/tensilelite/Tensile/Tensile.py b/tensilelite/Tensile/Tensile.py index af03f130fa..3b35ba8b06 100644 --- a/tensilelite/Tensile/Tensile.py +++ b/tensilelite/Tensile/Tensile.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -31,7 +31,9 @@ import argparse from .Common import globalParameters, print1, printExit, printWarning, ensurePath, \ assignGlobalParameters, restoreDefaultGlobalParameters, HR -from .Utilities.Toolchain import ToolchainDefaults, validateToolchain +from .Toolchain.Assembly import AssemblyToolchain +from .Toolchain.Source import SourceToolchain +from .Toolchain.Validators import validateToolchain, ToolchainDefaults from . import BenchmarkProblems from . import ClientWriter from . import LibraryIO @@ -48,13 +50,13 @@ # LibraryLogic.main() to analyse final benchmark data and produce logic/yaml # ClientWriter.main() to create client which calls library based on above yaml ################################################################################ -def executeStepsInConfig(config, cxxCompiler: str, cCompiler: str, assembler: str, offloadBundler: str): +def executeStepsInConfig(config, asmToolchain: AssemblyToolchain, srcToolchain: SourceToolchain, cCompiler: str): ############################################################################## # Benchmark Problems ############################################################################## if "BenchmarkProblems" in config: - BenchmarkProblems.main(config["BenchmarkProblems"], config["UseCache"], cxxCompiler, cCompiler, assembler, offloadBundler) + BenchmarkProblems.main(config["BenchmarkProblems"], config["UseCache"], asmToolchain, srcToolchain, cCompiler) print1("") ############################################################################## @@ -72,7 +74,7 @@ def executeStepsInConfig(config, cxxCompiler: str, cCompiler: str, assembler: st libraryLogicConfig = config["LibraryLogic"] else: libraryLogicConfig = {} - LibraryLogic.main(libraryLogicConfig, cxxCompiler) + LibraryLogic.main(libraryLogicConfig, srcToolchain.compiler) print1("") else: print1("# LibraryLogic already done.") @@ -86,7 +88,7 @@ def executeStepsInConfig(config, cxxCompiler: str, cCompiler: str, assembler: st libraryClientConfig = config["LibraryClient"] else: libraryClientConfig = {} - ClientWriter.main(libraryClientConfig, cxxCompiler, cCompiler) + ClientWriter.main(libraryClientConfig, srcToolchain.compiler, cCompiler) print1("") @@ -112,7 +114,7 @@ def splitExtraParameters(par): argParser.add_argument("--runtime-language", dest="RuntimeLanguage", \ choices=["HIP", "OCL"], help="override which runtime language to use") argParser.add_argument("--code-object-version", dest="CodeObjectVersion", \ - choices=["default", "V4", "V5"], help="HSA code-object version") + choices=["4", "5"], action="store", default="4", help="HSA code-object version") argParser.add_argument("-v", "--verbose", action="store_true", \ help="set PrintLevel=2") argParser.add_argument("--debug", dest="debug", action="store_true", \ @@ -271,6 +273,10 @@ def Tensile(userArgs): cxxCompiler, cCompiler, assembler, offloadBundler = validateToolchain(args.CxxCompiler, args.CCompiler, args.Assembler, args.OffloadBundler) assignGlobalParameters(config.get("GlobalParameters", {}), cxxCompiler) + + asmToolchain = AssemblyToolchain(assembler, offloadBundler, globalParameters["BuildIdKind"], globalParameters["CodeObjectVersion"]) + srcToolchain = SourceToolchain(cxxCompiler, offloadBundler, globalParameters["BuildIdKind"], globalParameters["AsanBuild"], globalParameters["SaveTemps"]) + globalParameters["OutputPath"] = ensurePath(os.path.abspath(args.output_path)) globalParameters["WorkingPath"] = globalParameters["OutputPath"] @@ -289,7 +295,7 @@ def Tensile(userArgs): profiler = cProfile.Profile() profiler.enable() - executeStepsInConfig(config, cxxCompiler, cCompiler, assembler, offloadBundler) + executeStepsInConfig(config, asmToolchain, srcToolchain, cCompiler) if profiler: profiler.disable() diff --git a/tensilelite/Tensile/TensileCreateLibrary.py b/tensilelite/Tensile/TensileCreateLibrary.py index 41777a4cba..33fe838b86 100644 --- a/tensilelite/Tensile/TensileCreateLibrary.py +++ b/tensilelite/Tensile/TensileCreateLibrary.py @@ -28,35 +28,37 @@ print("This file can no longer be run as a script. Run 'Tensile/bin/TensileCreateLibrary' instead.") exit(1) +import functools + from . import Common from . import ClientExecutable from . import EmbeddedData from . import LibraryIO from . import Utils +from .Toolchain.Assembly import AssemblyToolchain, buildAssemblyCodeObjectFiles +from .Toolchain.Source import SourceToolchain, buildSourceCodeObjectFile +from .Toolchain.Validators import validateToolchain, getVersion, ToolchainDefaults from .TensileInstructions import getGfxName, TensileInstructions from .Common import globalParameters, HR, print1, print2, printExit, ensurePath, \ - CHeader, CMakeHeader, assignGlobalParameters, \ + CHeader, assignGlobalParameters, \ architectureMap, printWarning, \ - splitArchs + IsaVersion from .KernelWriterAssembly import KernelWriterAssembly +from .KernelWriterBase import KERNEL_HELPER_FILENAME_CPP, KERNEL_HELPER_FILENAME_H from .SolutionLibrary import MasterSolutionLibrary from .SolutionStructs import Solution from .CustomYamlLoader import load_logic_gfx_arch from .Utilities.Profile import profile -from .Utilities.Toolchain import getVersion, validateToolchain, ToolchainDefaults -from .BuildCommands import SourceCommands, AssemblyCommands - import argparse import collections import glob import itertools import os -import re import shutil import sys from timeit import default_timer as timer from pathlib import Path -from typing import Sequence, List, Union +from typing import Sequence, List, Union, NamedTuple, Optional def timing(func): def wrapper(*args, **kwargs): @@ -69,107 +71,119 @@ def wrapper(*args, **kwargs): return res return wrapper -################################################################################ -def processKernelSource(kernel, kernelWriterAssembly, ti): - """ - Generate source for a single kernel. - Returns (error, source, header, kernelName). - """ - try: - kernelWriter = kernelWriterAssembly - # get kernel name - kernelWriter.setTensileInstructions(ti) - kernelName = kernelWriter.getKernelFileBase(kernel) - (err, src) = kernelWriter.getSourceFileString(kernel) - header = kernelWriter.getHeaderFileString(kernel) - # will be put in Kernels.h/cpp if None - filename = kernel._state.get("codeObjectFile", None) - except RuntimeError: - return (1, "", "", kernelName, None) - return (err, src, header, kernelName, filename) +class KernelCodeGenResult(NamedTuple): + err: int + src: str + header: Optional[str] + name: str + targetObjFilename: str + isa: IsaVersion + wavefrontSize: int +def processKernelSource(kernelWriterAssembly, ti, kernel) -> KernelCodeGenResult: + """ + Generate source for a single kernel. + Returns (error, source, header, kernelName). + """ + kernelWriter = kernelWriterAssembly + # get kernel name + kernelWriter.setTensileInstructions(ti) + asmFilename = kernelWriter.getKernelFileBase(kernel) + err, src = kernelWriter.getSourceFileString(kernel) + header = kernelWriter.getHeaderFileString(kernel) + # will be put in Kernels.h/cpp if None + objFilename = kernel._state.get("codeObjectFile", None) + + return KernelCodeGenResult(err, src, header, asmFilename, objFilename, tuple(kernel["ISA"]), kernel["WavefrontSize"]) + + +def removeInvalidSolutionsAndKernels(results, kernels, solutions, errorTolerant, globalParameters): + removeKernels = [] + removeKernelNames = [] + removeSolutions = [] + removeResults = [] + + for kernIdx, r in Utils.tqdm(enumerate(results)) if globalParameters["PrintLevel"] > 1 else enumerate(results): + if r.err != 0: + if not errorTolerant: + print("\nKernel generation failed for kernel: {}".format(kernels[kernIdx]["SolutionIndex"])) + print(kernels[kernIdx]["SolutionNameMin"]) + removeKernels.append(kernels[kernIdx]) + kName = Solution.getKeyNoInternalArgs(kernels[kernIdx]) + if kName not in removeKernelNames: + removeKernelNames.append(kName) + removeResults.append(results[kernIdx]) + + if len(removeKernels) > 0 and not errorTolerant: + printExit("** kernel generation failure **") + + for kern in removeKernels: + kernels.remove(kern) + + for solution in Utils.tqdm(solutions, "Finding invalid solutions") if globalParameters["PrintLevel"] > 1 else solutions: + solutionKernels = solution.getKernels() + for kernel in solutionKernels: + kName = Solution.getKeyNoInternalArgs(kernel) + if kName in removeKernelNames: + removeSolutions.append(solution) + break + + for solut in removeSolutions: + solutions.remove(solut) + + for rel in removeResults: + results.remove(rel) + +def writeAssembly(asmPath: Union[Path, str], result: KernelCodeGenResult): + if result.err: + printExit(f"Failed to build kernel {result.name} because it has error code {result.err}") + path = Path(asmPath) / f"{result.name}.s" + isa = result.isa + wfsize = result.wavefrontSize + with open(path, "w", encoding="utf-8") as f: + f.write(result.src) + del result # result.src is very large so let gc know to clean up asap + + return path, isa, wfsize + + +def writeHelpers(outputPath, kernelHelperObjs, KERNEL_HELPER_FILENAME_CPP, KERNEL_HELPER_FILENAME_H): + kernelSourceFilename = os.path.join(os.path.normcase(outputPath), KERNEL_HELPER_FILENAME_CPP) + kernelHeaderFilename = os.path.join(os.path.normcase(outputPath), KERNEL_HELPER_FILENAME_H) + + with open(kernelHeaderFilename, "w", encoding="utf-8") as kernelHeaderFile, \ + open(kernelSourceFilename, "w", encoding="utf-8") as kernelSourceFile: + kernelSourceFile.write(CHeader) + kernelHeaderFile.write(CHeader) + kernelSourceFile.write("#include \"Kernels.h\"\n") + kernelHeaderFile.write("#pragma once\n") + if globalParameters["RuntimeLanguage"] == "HIP": + kernelHeaderFile.write("#include \n") + kernelHeaderFile.write("#include \n\n") + kernelHeaderFile.write("#include \"KernelHeader.h\"\n\n") + + HeaderText = "" + for ko in kernelHelperObjs: + kernelName = ko.getKernelName() + + (err, src) = ko.getSourceFileString() + kernelSourceFile.write(src) + if err: + print("*** warning: invalid kernel#%u" % kernelName) + + HeaderText += ko.getHeaderFileString() + + kernelHeaderFile.write(HeaderText) -################################################################################ -def buildKernelSourceAndHeaderFiles(results, outputPath, kernelsWithBuildErrs): - """ - Logs errors and writes appropriate info to kernelSourceFile and kernelHeaderFile. - - Arguments: - results: list of (err, src, header, kernelName, filename) - outputPath: path to source directory - kernelsWithBuildErrs: Dictionary to be updated with kernels that have errors - kernelSourceFile: File to write source data to - kernelHeaderFile: File to write header data to - - Returns: - sourceFilenames: Array containing source kernel filenames - """ - - # Find kernels to write - kernelsToWrite = [] - filesToWrite = collections.defaultdict(list) - validKernelCount = 0 - for (err,src,header,kernelName, filename) in results: - - # Keep track of kernels with errors - if err: - kernelsWithBuildErrs[kernelName] = err - - # Don't create a file for empty kernels - if len(src.strip()) == 0: - continue - - kernelsToWrite.append((err, src, header, kernelName)) - - # Create list of files - if filename: - filesToWrite[os.path.join(os.path.normcase(outputPath),filename)].append((err, src, header, kernelName)) - else: - kernelSuffix = "" - filesToWrite[os.path.join(os.path.normcase(outputPath), "Kernels"+kernelSuffix)]\ - .append((err, src, header, kernelName)) - - validKernelCount += 1 - - #Ensure there's at least one kernel file for helper kernels - if globalParameters["LazyLibraryLoading"] or not kernelsToWrite: - kernelSuffix = "" - filesToWrite[os.path.join(os.path.normcase(outputPath), "Kernels"+kernelSuffix)] = [] - - - # Write kernel data to files - #Parse list of files and write kernels - for filename, kernelList in filesToWrite.items(): - with open(filename+".h", "w", encoding="utf-8") as kernelHeaderFile, \ - open(filename+".cpp", "w", encoding="utf-8") as kernelSourceFile: - - kernelSourceFile.write(CHeader) - kernelHeaderFile.write(CHeader) - kernelSourceFile.write("#include \"{}.h\"\n".format(filename)) - kernelHeaderFile.write("#pragma once\n") - if globalParameters["RuntimeLanguage"] == "HIP": - kernelHeaderFile.write("#include \n") - kernelHeaderFile.write("#include \n\n") - kernelHeaderFile.write("#include \"KernelHeader.h\"\n\n") - - for err,src,header,kernelName in kernelList: - kernelSourceFile.write(src) - kernelHeaderFile.write(header) - - sourceFilenames = [filePrefix+".cpp" for filePrefix in filesToWrite] - - return sourceFilenames ################################################################################ # Write Solutions and Kernels for BenchmarkClient or LibraryClient ################################################################################ -@timing -def writeSolutionsAndKernels(outputPath, cxxCompiler, assembler, offloadBundler, solutions, kernels, kernelHelperObjs, \ +def writeSolutionsAndKernels(outputPath, asmToolchain, srcToolchain, solutions, kernels, kernelHelperObjs, \ kernelWriterAssembly, errorTolerant=False, compress=True): - codeObjectFiles = [] # Push working path into build_tmp folder because there may be more than @@ -179,110 +193,94 @@ def writeSolutionsAndKernels(outputPath, cxxCompiler, assembler, offloadBundler, # See buildSourceCodeObjectFile:167 for the call to this binary. Common.pushWorkingPath('build_tmp') Common.pushWorkingPath(os.path.basename(outputPath).upper()) + asmPath = ensurePath(os.path.join(globalParameters["WorkingPath"], "assembly")) - kernelFiles = [] - kernelSourceFile = None - kernelHeaderFile = None - - ############################################################################## - # Write Kernels - ############################################################################## - kernelsWithBuildErrs = {} + asmKernels = [k for k in kernels if k['KernelLanguage'] == 'Assembly'] # Kernels may be intended for different co files, but generate the same .o file # Mark duplicate kernels to avoid race condition # @TODO improve organization so this problem doesn't appear - objFilenames = set() - for kernel in kernels: - if kernel["KernelLanguage"] == "Assembly": - base = kernelWriterAssembly.getKernelFileBase(kernel) - if base in objFilenames: - kernel.duplicate = True - else: - objFilenames.add(base) - kernel.duplicate = False + visited = set() + duplicates = 0 + for k in asmKernels: + base = kernelWriterAssembly.getKernelFileBase(k) + k.duplicate = True if base in visited else False + duplicates += k.duplicate + print2(f"Duplicate: {base}") + visited.add(base) + + print1(f"Number of duplicates: {duplicates}") + + numAsmKernels = len(asmKernels) + numKernels = len(asmKernels) + assert numKernels == numAsmKernels, "Only assembly kernels are supported in TensileLite" + asmIter = zip(itertools.repeat(kernelWriterAssembly), itertools.repeat(TensileInstructions()), asmKernels) + asmResults = Common.ParallelMap2(processKernelSource, asmIter, "Generating assembly kernels") + removeInvalidSolutionsAndKernels(asmResults, asmKernels, solutions, errorTolerant, globalParameters) + def assemble(ret): + p, isa, wavefrontsize = ret + asmToolchain.assemble(str(p), str(p.with_suffix(".o")), getGfxName(isa), wavefrontsize) + unaryWriteAssembly = functools.partial(writeAssembly, asmPath) + compose = lambda *F: functools.reduce(lambda f, g: lambda x: f(g(x)), F) + ret = Common.ParallelMap2(compose(assemble, unaryWriteAssembly), asmResults, "Writing assembly kernels", return_as="list", multiArg=False) + codeObjectFiles += buildAssemblyCodeObjectFiles(asmToolchain, asmKernels, kernelWriterAssembly, outputPath, compress) + + srcKernels = [k for k in kernels if k['KernelLanguage'] != 'Assembly'] + if srcKernels: + raise ValueError(f"Non-helper HIP source kernels are not supported Tensilelite, found {len(srcKernels)}") + writeHelpers(outputPath, kernelHelperObjs, KERNEL_HELPER_FILENAME_CPP, KERNEL_HELPER_FILENAME_H) + srcKernelFile = Path(outputPath) / "Kernels.cpp" + buildSourceCodeObjectFile(srcToolchain, outputPath, srcKernelFile) + + Common.popWorkingPath() # build_tmp + Common.popWorkingPath() # workingDir + + return codeObjectFiles, numKernels + + +def writeSolutionsAndKernelsTCL(outputPath, asmToolchain, srcToolchain, kernels, kernelHelperObjs, \ + kernelWriterAssembly, compress=True): + + Common.pushWorkingPath('build_tmp') + Common.pushWorkingPath(os.path.basename(outputPath).upper()) + asmPath = ensurePath(os.path.join(globalParameters["WorkingPath"], "assembly")) + + asmKernels = [k for k in kernels if k['KernelLanguage'] == 'Assembly'] + + visited = set() + duplicates = 0 + for k in asmKernels: + base = kernelWriterAssembly.getKernelFileBase(k) + k.duplicate = True if base in visited else False + duplicates += k.duplicate + print2(f"Duplicate: {base}") + visited.add(base) + + print1(f"Number of duplicates: {duplicates}") + + uniqueAsmKernels = [k for k in asmKernels if not k.duplicate] + + numAsmKernels = len(asmKernels) numKernels = len(kernels) + assert numKernels == numAsmKernels, "Only assembly kernels are supported in TensileLite" - kIter = zip(kernels, itertools.repeat(kernelWriterAssembly), itertools.repeat(TensileInstructions())) - results = Common.ParallelMap2(processKernelSource, kIter, "Generating kernels") - - removeKernels = [] - removeKernelNames = [] - removeSolutions = [] - removeResults = [] - for kernIdx, res in Utils.tqdm(enumerate(results)) if globalParameters["PrintLevel"] > 1 else enumerate(results): - (err,src,header,kernelName, filename) = res - if(err == -2): - if not errorTolerant: - print("\nKernel generation failed for kernel: {}".format(kernels[kernIdx]["SolutionIndex"])) - print(kernels[kernIdx]["SolutionNameMin"]) - removeKernels.append(kernels[kernIdx]) - kName = Solution.getKeyNoInternalArgs(kernels[kernIdx]) - if kName not in removeKernelNames: - removeKernelNames.append(kName) - removeResults.append(results[kernIdx]) - if len(removeKernels) > 0 and not errorTolerant: - printExit("** kernel generation failure **") - for kern in removeKernels: - kernels.remove(kern) - for solution in Utils.tqdm(solutions, "Finding invalid solutions") if globalParameters["PrintLevel"] > 1 else solutions: - solutionKernels = solution.getKernels() - for kernel in solutionKernels: - kName = Solution.getKeyNoInternalArgs(kernel) - if kName in removeKernelNames: - removeSolutions.append(solution) - break - for solut in removeSolutions: - solutions.remove(solut) - for rel in removeResults: - results.remove(rel) - - kernelFiles += buildKernelSourceAndHeaderFiles(results, outputPath, kernelsWithBuildErrs) - - kernelsToBuild = kernels - if errorTolerant: - def success(kernel): - writer = kernelWriterAssembly - kernelName = writer.getKernelName(kernel) - return kernelName not in kernelsWithBuildErrs - kernelsToBuild = filter(success, kernelsToBuild) - elif len(kernelsWithBuildErrs) > 0: - print("\nKernel compilation failed in one or more subprocesses. May want to set CpuThreads=0 and re-run to make debug easier") - printExit("** kernel compilation failure **") - - kernelSourceFilename = os.path.join(os.path.normcase(outputPath), "Kernels.cpp") - kernelHeaderFilename = os.path.join(os.path.normcase(outputPath), "Kernels.h") - kernelSourceFile = open(kernelSourceFilename, "a", encoding="utf-8") - kernelHeaderFile = open(kernelHeaderFilename, "a", encoding="utf-8") - - HeaderText = "" - # handle helper kernel function - for ko in kernelHelperObjs: - kernelName = ko.getKernelName() - - (err, src) = ko.getSourceFileString() - kernelSourceFile.write(src) - if err: - print("*** warning: invalid kernel#%u"%kernelName) - - HeaderText += ko.getHeaderFileString() - - # write kernel.h in one shot - kernelHeaderFile.write(HeaderText) - - if kernelSourceFile: - kernelSourceFile.close() - if kernelHeaderFile: - kernelHeaderFile.close() - - if not globalParameters["GenerateSourcesAndExit"]: - codeObjectFiles += SourceCommands.buildSourceCodeObjectFiles(cxxCompiler, offloadBundler, kernelFiles, outputPath) - codeObjectFiles += AssemblyCommands.buildAssemblyCodeObjectFiles(kernelsToBuild, kernelWriterAssembly, outputPath, assembler, offloadBundler, compress) + def assemble(ret): + p, isa, wavefrontsize = ret + asmToolchain.assemble(str(p), str(p.with_suffix(".o")), getGfxName(isa), wavefrontsize) + unaryProcessKernelSource = functools.partial(processKernelSource, kernelWriterAssembly, TensileInstructions()) + unaryWriteAssembly = functools.partial(writeAssembly, asmPath) + compose = lambda *F: functools.reduce(lambda f, g: lambda x: f(g(x)), F) + ret = Common.ParallelMap2(compose(assemble, unaryWriteAssembly, unaryProcessKernelSource), uniqueAsmKernels, "Generating assembly kernels", multiArg=False) + buildAssemblyCodeObjectFiles(asmToolchain, asmKernels, kernelWriterAssembly, outputPath, compress) + + writeHelpers(outputPath, kernelHelperObjs, KERNEL_HELPER_FILENAME_CPP, KERNEL_HELPER_FILENAME_H) + srcKernelFile = Path(outputPath) / "Kernels.cpp" + buildSourceCodeObjectFile(srcToolchain, outputPath, srcKernelFile) Common.popWorkingPath() # build_tmp Common.popWorkingPath() # workingDir - return codeObjectFiles, numKernels + return numKernels ############################################################################## @@ -481,7 +479,7 @@ def splitExtraParameters(par): argParser.add_argument("--cmake-cxx-compiler", dest="CmakeCxxCompiler", action="store") argParser.add_argument("--offload-bundler", dest="OffloadBundler", action="store", default=ToolchainDefaults.OFFLOAD_BUNDLER) argParser.add_argument("--assembler", dest="Assembler", action="store", default=ToolchainDefaults.ASSEMBLER) - argParser.add_argument("--code-object-version", dest="CodeObjectVersion", choices=["default", "V4", "V5"], action="store") + argParser.add_argument("--code-object-version", dest="CodeObjectVersion", choices=["4", "5"], action="store", default="4", type=str) argParser.add_argument("--architecture", dest="Architecture", type=str, action="store", default="all", help="Supported archs: " + " ".join(architectureMap.keys())) argParser.add_argument("--short-file-names", dest="ShortNames", action="store_true") argParser.add_argument("--no-short-file-names", dest="ShortNames", action="store_false") @@ -526,6 +524,7 @@ def splitExtraParameters(par): " Example: gfx942/Equality/* for building equality of gfx942 only") args = argParser.parse_args() + args.CodeObjectVersion = "4" if args.CodeObjectVersion == "default" else args.CodeObjectVersion logicPath = args.LogicPath outputPath = args.OutputPath @@ -534,6 +533,8 @@ def splitExtraParameters(par): assembler = args.Assembler libraryFormat = args.LibraryFormat useCompression = not args.NoCompress + coVersion = args.CodeObjectVersion + print2("OutputPath: %s" % outputPath) ensurePath(outputPath) outputPath = os.path.abspath(outputPath) @@ -579,12 +580,15 @@ def splitExtraParameters(par): print1(f"# C Compiler: {cCompiler} (version {getVersion(cCompiler)})") print1(f"# Assembler: {assembler} (version {getVersion(assembler)})") print1(f"# Offload Bundler: {offloadBundler} (version {getVersion(offloadBundler)})") - print1(f"# Code Object Version: {arguments['CodeObjectVersion']}") + print1(f"# Code Object Version: {coVersion}") print1(f"# Architecture(s): {arguments['Architecture']}") print1(f"# Library Format: {libraryFormat}") assignGlobalParameters(arguments, cxxCompiler) + asmToolchain = AssemblyToolchain(assembler, offloadBundler, globalParameters["BuildIdKind"], coVersion) + srcToolchain = SourceToolchain(cxxCompiler, offloadBundler, globalParameters["BuildIdKind"], globalParameters["AsanBuild"], globalParameters["SaveTemps"]) + if not os.path.exists(logicPath): printExit("LogicPath %s doesn't exist" % logicPath) @@ -634,9 +638,7 @@ def validLogicFile(p: Path): # Parse logicData, solutions, and masterLibraries from logic files solutions, masterLibraries, fullMasterLibrary = generateLogicDataAndSolutions(logicFiles, args, cxxCompiler) - kernels, kernelHelperObjs, _ = generateKernelObjectsFromSolutions(solutions) - # if any kernels are assembly, append every ISA supported kernelWriterAssembly, kernelMinNaming, _ = getSolutionAndKernelWriters(solutions, kernels, assembler) @@ -651,8 +653,8 @@ def validLogicFile(p: Path): outputPath ) # write solutions and kernels - codeObjectFiles, numKernels = writeSolutionsAndKernels(outputPath, cxxCompiler, assembler, offloadBundler, solutions, - kernels, kernelHelperObjs, kernelWriterAssembly, compress=useCompression) + numKernels = writeSolutionsAndKernelsTCL(outputPath, asmToolchain, srcToolchain, kernels, + kernelHelperObjs, kernelWriterAssembly, compress=useCompression) archs = [getGfxName(arch) for arch in globalParameters['SupportedISA'] \ if globalParameters["AsmCaps"][arch]["SupportedISA"]] diff --git a/tensilelite/Tensile/TensileInstructions/Base.py b/tensilelite/Tensile/TensileInstructions/Base.py index 1c755d570f..231d6c54c1 100644 --- a/tensilelite/Tensile/TensileInstructions/Base.py +++ b/tensilelite/Tensile/TensileInstructions/Base.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -190,13 +190,6 @@ def getSlcBitName(hasGLCModifier): return "slc" return "sc1" -def getCOVFromParam(versionString): - if versionString == "default" or versionString == "V4": - return 4 - elif versionString == "V5": - return 5 - printExit("Unknown CodeObjectVersion %s" % (versionString)) - def _removeIdent(isaDict) -> list: ids = [th.ident for th in threading.enumerate()] isaDict = [id for id in isaDict if id in ids] diff --git a/tensilelite/Tensile/TensileInstructions/Code.py b/tensilelite/Tensile/TensileInstructions/Code.py index 8cbd202753..808bc810d0 100644 --- a/tensilelite/Tensile/TensileInstructions/Code.py +++ b/tensilelite/Tensile/TensileInstructions/Code.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -751,7 +751,7 @@ def __init__(self, name, kernArgsVersion, groupSegSize, flatWgSize, codeObjectVe self.kernArgsVersion = kernArgsVersion self.groupSegSize = groupSegSize self.flatWgSize = flatWgSize - self.codeObjectVersion = codeObjectVersion + self.codeObjectVersion = str(codeObjectVersion) self.totalVgprs = totalVgprs self.totalSgprs = totalSgprs self.offset = 0 @@ -770,9 +770,9 @@ def __str__(self): kStr += " KernArgsVersion: %d\n"%self.kernArgsVersion kStr += "amdhsa.version:\n" kStr += " - 1\n" - if self.codeObjectVersion == 4: + if self.codeObjectVersion == "4" or self.codeObjectVersion == "default": kStr += " - 1\n" - elif self.codeObjectVersion == 5: + elif self.codeObjectVersion == "5": kStr += " - 2\n" kStr += "amdhsa.kernels:\n" kStr += " - .name: %s\n" % self.name diff --git a/tensilelite/Tensile/TensileInstructions/Utils.py b/tensilelite/Tensile/TensileInstructions/Utils.py index d2740b225d..ae3620bd3d 100644 --- a/tensilelite/Tensile/TensileInstructions/Utils.py +++ b/tensilelite/Tensile/TensileInstructions/Utils.py @@ -1,6 +1,6 @@ ################################################################################ # -# Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2022-2025 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 @@ -20,7 +20,8 @@ # CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ################################################################################ -from .Base import getGfxName, getCOVFromParam +import warnings +from .Base import getGfxName from .Code import Module from .Containers import HolderContainer, RegisterContainer, RegName from .DataType import DataType @@ -235,33 +236,3 @@ def replaceHolder(module, dst): return module -def getAsmCompileArgs(assemblerPath: str, codeObjectVersion: str, \ - isa: Tuple[int, int, int], wavefrontSize: int, \ - sourceFileName: str, objectFileName: str, *moreArgs, debug: bool=False): - launcher = shlex.split(os.environ.get('Tensile_ASM_COMPILER_LAUNCHER', '')) - rv = launcher + [assemblerPath, '-x', 'assembler', '-target', 'amdgcn-amd-amdhsa'] - - rv += ['-mcode-object-version=%s'% getCOVFromParam(codeObjectVersion)] - - rv += ['-mcpu=' + getGfxName(isa)] - - if wavefrontSize == 64: - rv += ['-mwavefrontsize64'] - else: - rv += ['-mno-wavefrontsize64'] - - rv += moreArgs - - if debug: - rv += ['-g',] - - rv += ['-c', '-o', objectFileName, sourceFileName] - return rv - -def getAsmLinkCodeObjectArgs(assemblerPath: str, objectFileNames: List[str], \ - coFileName: str, buildIdKind: str, *moreArgs): - rv = [assemblerPath, '-target', 'amdgcn-amd-amdhsa'] - rv += ["-Xlinker", "--build-id=%s"%(buildIdKind)] - rv += moreArgs - rv += ['-o', coFileName] + objectFileNames - return rv diff --git a/tensilelite/Tensile/Toolchain/Assembly.py b/tensilelite/Tensile/Toolchain/Assembly.py new file mode 100644 index 0000000000..0b44f36dff --- /dev/null +++ b/tensilelite/Tensile/Toolchain/Assembly.py @@ -0,0 +1,210 @@ +################################################################################ +# +# Copyright (C) 2025 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. +# +################################################################################ + +import collections +import math +import os +import shlex +import shutil +import subprocess +import warnings + +from pathlib import Path +from typing import List, Literal, Union, Tuple + +from .. import Utils +from ..TensileInstructions import getGfxName +from ..Common import globalParameters, print2, ensurePath +class AssemblyToolchain: + def __init__(self, assembler: str, bundler: str, buildIdKind: str, coVersion: Literal[4, 5]): + self.assembler = assembler + self.bundler = bundler + self.buildIdKind = buildIdKind + self.coVersion = coVersion + + def invoke(self, args: List[str], desc: str=""): + """Invokes a subprocess with the provided arguments. + + Args: + args: A list of arguments to pass to the subprocess. + desc: A description of the subprocess invocation. + + Raises: + RuntimeError: If the subprocess invocation fails. + """ + print2(f"{desc}: {' '.join(args)}") + try: + out = subprocess.check_output(args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: + raise RuntimeError( + f"Error with {desc}: {err.output}\n" + f"Failed command: {' '.join(args)}" + ) + print2(f"Output: {out}") + return out + + def assemble(self, srcPath: str, destPath: str, gfx: str, wavefrontSize: int, debug: bool=False): + """Assemble an assembly source file into an object file. + + Args: + srcPath: The path to the assembly source file. + destPath: The destination path for the generated object file. + coVersion: The code object version to use. + isa: The target GPU architecture in ISA format. + wavefrontSize: The wavefront size to use. + """ + launcher = shlex.split(os.environ.get('Tensile_ASM_COMPILER_LAUNCHER', '')) + args = [ + *launcher, + self.assembler, + "-x", "assembler", + "--target=amdgcn-amd-amdhsa", + f"-mcode-object-version={self.coVersion}", + f"-mcpu={gfx}", + "-mwavefrontsize64" if wavefrontSize == 64 else "-mno-wavefrontsize64" + "-g" if debug else "", + "-c", + "-o", destPath, srcPath + ] + + return self.invoke(args, "Assembling assembly source code into object file (.s -> .o)") + + def link(self, srcPaths: List[str], destPath: str): + """Links object files into a code object file. + + Args: + srcPaths: A list of paths to object files. + destPath: A destination path for the generated code object file. + + Raises: + RuntimeError: If linker invocation fails. + """ + if os.name == "nt": + # Use args file on Windows b/c the command may exceed the limit of 8191 characters + with open(Path.cwd() / "clang_args.txt", "wt") as file: + file.write(" ".join(objFiles)) + file.flush() + args = [ + self.assembler, + "--target=amdgcn-amd-amdhsa", + "-o", destPath, "@clang_args.txt"] + else: + args = [ + self.assembler, + "--target=amdgcn-amd-amdhsa", + "-Xlinker", f"--build-id={self.buildIdKind}", + "-o", destPath, *srcPaths + ] + + return self.invoke(args, "Linking assembly object files into code object (*.o -> .co)") + + def compress(self, srcPath: str, destPath: str, gfx: str): + """Compresses a code object file using the provided bundler. + + Args: + srcPath: The source path of the code object file to be compressed. + destPath: The destination path for the compressed code object file. + gfx: The target GPU architecture. + + Raises: + RuntimeError: If compressing the code object file fails. + """ + args = [ + self.bundler, + "--compress", + "--type=o", + "--bundle-align=4096", + f"--targets=host-x86_64-unknown-linux,hipv4-amdgcn-amd-amdhsa--{gfx}", + "--input=/dev/null", + f"--input={srcPath}", + f"--output={destPath}", + ] + + return self.invoke(args, "Bundling/compressing code object file (.co -> .co)") + + +def _batchObjectFiles(objFiles: List[str], coPathDest: Union[Path, str], maxObjFiles: int=10000) -> List[str]: + numObjFiles = len(objFiles) + + if numObjFiles <= maxObjFiles: + return objFiles + + batchedObjFiles = [objFiles[i:i+maxObjFiles] for i in range(0, numObjFiles, maxObjFiles)] + numBatches = int(math.ceil(numObjFiles / maxObjFiles)) + + newObjFiles = [str(coPathDest) + "." + str(i) for i in range(0, numBatches)] + newObjFilesOutput = [] + + for batch, filename in zip(batchedObjFiles, newObjFiles): + if len(batch) > 1: + args = [globalParameters["ROCmLdPath"], "-r"] + batch + [ "-o", filename] + print2(f"Linking object files into fewer object files: {' '.join(args)}") + subprocess.check_call(args) + newObjFilesOutput.append(filename) + else: + newObjFilesOutput.append(batchedObjFiles[0]) + + return newObjFilesOutput + +def buildAssemblyCodeObjectFiles(toolchain: AssemblyToolchain, kernels, writerAsm, outputPath, compress: bool=True): + + isAsm = lambda k: k["KernelLanguage"] == "Assembly" + + extObj = ".o" + extCo = ".co" + extCoRaw = ".co.raw" + + destDir = Path(ensurePath(os.path.join(outputPath, 'library'))) + asmDir = Path(ensurePath(os.path.join(globalParameters["WorkingPath"], "assembly"))) + + archKernelMap = collections.defaultdict(list) + for k in filter(isAsm, kernels): + archKernelMap[tuple(k['ISA'])].append(k) + + coFiles = [] + for arch, archKernels in archKernelMap.items(): + if len(archKernels) == 0: + continue + + gfx = getGfxName(arch) + + objectFiles = [str(asmDir / (writerAsm.getKernelFileBase(k) + extObj)) for k in archKernels if 'codeObjectFile' not in k] + coFileMap = collections.defaultdict(list) + if len(objectFiles): + coFileMap[asmDir / ("TensileLibrary_"+ gfx + extCoRaw)] = objectFiles + for kernel in archKernels: + coName = kernel.get("codeObjectFile", None) + if coName: + coFileMap[asmDir / (coName + extCoRaw)].append(str(asmDir / (writerAsm.getKernelFileBase(kernel) + extObj))) + for coFileRaw, objFiles in coFileMap.items(): + objFiles = _batchObjectFiles(objFiles, coFileRaw) + toolchain.link(objFiles, str(coFileRaw)) + coFile = destDir / coFileRaw.name.replace(extCoRaw, extCo) + if compress: + toolchain.compress(str(coFileRaw), str(coFile), gfx) + else: + shutil.move(coFileRaw, coFile) + coFiles.append(coFile) + + return coFiles diff --git a/tensilelite/Tensile/Toolchain/Source.py b/tensilelite/Tensile/Toolchain/Source.py new file mode 100644 index 0000000000..ab0eba5220 --- /dev/null +++ b/tensilelite/Tensile/Toolchain/Source.py @@ -0,0 +1,215 @@ +################################################################################ +# +# Copyright (C) 2025 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. +# +################################################################################ + +import os +import re +import shlex +import shutil +import subprocess + +from pathlib import Path +from timeit import default_timer as timer +from typing import List, Union + +from ..Common import globalParameters, print1, print2, ensurePath, splitArchs + +class SourceToolchain: + def __init__(self, compiler: str, bundler: str, buildIdKind: str, asanBuild: bool=False, saveTemps: bool=False): + self.compiler = compiler + self.bundler = bundler + self.buildIdKind = buildIdKind + self.asanBuild = asanBuild + self.saveTemps = saveTemps + + def invoke(self, args: List[str], desc: str=""): + """Invokes a subprocess with the provided arguments. + + Args: + args: A list of arguments to pass to the subprocess. + desc: A description of the subprocess invocation. + + Raises: + RuntimeError: If the subprocess invocation fails. + """ + print2(f"{desc}: {' '.join(args)}") + try: + out = subprocess.check_output(args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: + raise RuntimeError( + f"Error with {desc}: {err.output}\n" + f"Failed command: {' '.join(args)}" + ) + print2(f"Output: {out}") + return out + + def compile(self, srcPath: str, destPath: str, includePath: str, gfxs: List[str]): + """Compiles a source file into an object file. + + Args: + cmdlineArchs: List of architectures for offloading. + kernelFile: The path to the kernel source file. + buildPath: The build directory path. + objectFilename: The name of the output object file. + outputPath: The output directory path. + globalParameters: A dictionary of global parameters. + + Raises: + RuntimeError: If the compilation command fails. + """ + launcher = shlex.split(os.environ.get("Tensile_CXX_COMPILER_LAUNCHER", "")) + + hipFlags = [ + "-D__HIP_HCC_COMPAT_MODE__=1", + "--offload-device-only", + "-x", "hip", "-O3", + "-I", includePath, + "-Xoffload-linker", f"--build-id={self.buildIdKind}", + "-std=c++17", + ] + if self.asanBuild: + hipFlags.extend(["-fsanitize=address", "-shared-libasan", "-fuse-ld=lld"]) + if self.saveTemps: + hipFlags.append("--save-temps") + if os.name == "nt": + hipFlags.extend(["-fms-extensions", "-fms-compatibility", "-fPIC", "-Wno-deprecated-declarations"]) + + archFlags = [f"--offload-arch={gfx}" for gfx in gfxs] + + args = [ + *launcher, self.compiler, *hipFlags, *archFlags, srcPath, "-c", "-o", destPath + ] + + return self.invoke(args, f"Compiling HIP source kernels into objects (.cpp -> .o)") + + + def targets(self, objFile: str): + """Lists the target triples in an object file. + + Args: + objFile: The object file path. + + Returns: + List of target triples in the object file. + """ + args = [self.bundler, "--type=o", f"--input={objFile}", "-list"] + return self.invoke(args, f"Listing target triples in object file").decode().split("\n") + + def unbundle(self, target: str, srcPath: str, destPath: str): + """Unbundles source code object files using the Clang Offload Bundler. + + Args: + target: The target triple, see https://llvm.org/docs/AMDGPUUsage.html#target-triples. + infile: The path to the input object file. + outfileRaw: The path to the unbundled code object. + + Raises: + RuntimeError: If unbundling the source code object file fails. + """ + args = [ + self.bundler, + "--type=o", + f"--targets={target}", + f"--input={srcPath}", + f"--output={destPath}", + "--unbundle", + ] + + return self.invoke(args, f"Unbundling source code object file") + + +def _computeSourceCodeObjectFilename(target: str, base: str, buildPath: Union[Path, str], arch: str) -> Union[Path, None]: + """Generates a code object file path using the target, base, and build path. + + Args: + target: The target triple. + base: The base name for the output file (name without extension). + buildPath: The build directory path. + + Returns: + Path to the code object file. + """ + coPath = None + buildPath = Path(buildPath) + if "TensileLibrary" in base and "fallback" in base: + coPath = buildPath / "{0}_{1}.hsaco.raw".format(base, arch) + elif "TensileLibrary" in base: + variant = [t for t in ["", "xnack-", "xnack+"] if t in target][-1] + baseVariant = base + "-" + variant if variant else base + if arch in baseVariant: + coPath = buildPath / (baseVariant + ".hsaco.raw") + else: + coPath= buildPath / "{0}.so-000-{1}.hsaco.raw".format(base, arch) + + return coPath + + +def buildSourceCodeObjectFile(toolchain: SourceToolchain, outputPath: Union[Path, str], kernelPath: Union[Path, str]) -> List[str]: + """Compiles a HIP source code file into a code object file. + + Args: + cxxCompiler: The C++ compiler to use. + cxxCompiler: The offload bundler to use. + outputPath: The output directory path where code objects will be placed. + kernelPath: The path to the kernel source file. + + Returns: + List of paths to the created code objects. + """ + start = timer() + + buildPath = Path(ensurePath(os.path.join(globalParameters['WorkingPath'], 'code_object_tmp'))) + destPath = Path(ensurePath(os.path.join(outputPath, 'library'))) + kernelPath = Path(kernelPath) + + if "CmakeCxxCompiler" in globalParameters and globalParameters["CmakeCxxCompiler"] is not None: + os.environ["CMAKE_CXX_COMPILER"] = globalParameters["CmakeCxxCompiler"] + + objFilename = kernelPath.stem + '.o' + coPathsRaw = [] + coPaths= [] + + _, cmdlineArchs = splitArchs() + + objPath = str(buildPath / objFilename) + toolchain.compile(str(kernelPath), objPath, str(outputPath), cmdlineArchs) + + for target in toolchain.targets(objPath): + match = re.search("gfx.*$", target) + if match: + arch = re.sub(":", "-", match.group()) + coPathRaw = _computeSourceCodeObjectFilename(target, kernelPath.stem, buildPath, arch) + if not coPathRaw: continue + toolchain.unbundle(target, objPath, str(coPathRaw)) + + coPath = str(destPath / coPathRaw.stem) + coPathsRaw.append(coPathRaw) + coPaths.append(coPath) + + for src, dst in zip(coPathsRaw, coPaths): + shutil.move(src, dst) + + stop = timer() + print1(f"buildSourceCodeObjectFile time (s): {(stop-start):3.2f}") + + return coPaths diff --git a/tensilelite/Tensile/Utilities/Toolchain.py b/tensilelite/Tensile/Toolchain/Validators.py similarity index 79% rename from tensilelite/Tensile/Utilities/Toolchain.py rename to tensilelite/Tensile/Toolchain/Validators.py index 6158bd52f9..86c3ff074b 100644 --- a/tensilelite/Tensile/Utilities/Toolchain.py +++ b/tensilelite/Tensile/Toolchain/Validators.py @@ -1,8 +1,31 @@ +################################################################################ +# +# Copyright (C) 2025 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. +# +################################################################################ + import os import re from pathlib import Path from typing import List, NamedTuple, Union -from warnings import warn from subprocess import run, PIPE ROCM_BIN_PATH = Path("/opt/rocm/bin") @@ -22,9 +45,9 @@ def _windowsLatestRocmBin(path: Union[Path, str]) -> Path: Typically of the form ``C:/Program Files/AMD/ROCm/X.Y/bin``. """ path = Path(path) - pattern = re.compile(r'^\d+\.\d+$') + pattern = re.compile(r"^\d+\.\d+$") versions = filter(lambda d: d.is_dir() and pattern.match(d.name), path.iterdir()) - latest = max(versions, key=lambda d: tuple(map(int, d.name.split('.')))) + latest = max(versions, key=lambda d: tuple(map(int, d.name.split(".")))) return latest / "bin" # LLVM binaries are in the same directory as ROCm binaries on Windows ROCM_BIN_PATH = _windowsLatestRocmBin("C:/Program Files/AMD/ROCm") @@ -104,9 +127,7 @@ def _exeExists(file: Path) -> bool: Returns: If the file exists and is executable, True; otherwise, False """ - if os.access(file, os.X_OK): - return True - return False + return True if os.access(file, os.X_OK) else False def _validateExecutable(file: str, searchPaths: List[Path]) -> str: @@ -156,7 +177,7 @@ def validateToolchain(*args: str): return next(out) if len(args) == 1 else tuple(out) -def getVersion(executable: str, versionFlag: str="--version", regex: str=r'version\s+([\d.]+)') -> str: +def getVersion(executable: str, versionFlag: str="--version", regex: str=r"version\s+([\d.]+)") -> str: """Print the version of a toolchain component. Args: @@ -170,4 +191,3 @@ def getVersion(executable: str, versionFlag: str="--version", regex: str=r'versi return match.group(1) if match else "" except Exception as e: raise RuntimeError(f"Failed to get version when calling {args}: {e}") - diff --git a/tensilelite/Tensile/Toolchain/__init__.py b/tensilelite/Tensile/Toolchain/__init__.py new file mode 100644 index 0000000000..5bd724068a --- /dev/null +++ b/tensilelite/Tensile/Toolchain/__init__.py @@ -0,0 +1,23 @@ +################################################################################ +# +# Copyright (C) 2025 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. +# +################################################################################