CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
742 lines
32 KiB
Python
742 lines
32 KiB
Python
################################################################################
|
|
#
|
|
# Copyright (C) 2022-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# 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.
|
|
#
|
|
################################################################################
|
|
|
|
if __name__ == "__main__":
|
|
print("This file can no longer be run as a script. Run 'Tensile/bin/Tensile' instead.")
|
|
exit(1)
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import argparse
|
|
import glob
|
|
import json
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, Optional
|
|
|
|
from Tensile import __version__
|
|
from Tensile.Common import print1, printExit, printWarning, ensurePath, HR, isRhel8, \
|
|
LIBRARY_LOGIC_DIR, setVerbosity, IsaInfo, makeDebugConfig, \
|
|
DebugConfig, IsaVersion, coVersionMap
|
|
from Tensile.Common.Architectures import detectGlobalCurrentISA, isaToGfx
|
|
from Tensile.Common.Capabilities import makeIsaInfoMap
|
|
from Tensile.Common.GlobalParameters import globalParameters, assignGlobalParameters, \
|
|
restoreDefaultGlobalParameters
|
|
from Tensile.Common.TimingInstrumentation import timing_context, flush_timing_buffer
|
|
from Tensile.Toolchain.Assembly import AssemblyToolchain, makeAssemblyToolchain
|
|
from Tensile.Toolchain.Source import SourceToolchain, makeSourceToolchain
|
|
from Tensile.Toolchain.Validators import validateToolchain, ToolchainDefaults
|
|
from Tensile.Utilities.Decorators.Profile import profile
|
|
from Tensile import BenchmarkProblems
|
|
from Tensile import ClientWriter
|
|
from Tensile import LibraryIO
|
|
from Tensile.backends.config import parse_backend_config
|
|
from Tensile import LibraryLogic
|
|
|
|
TENSILE_SCRIPT_DIR = Path(os.path.dirname(os.path.abspath(__file__)))
|
|
TENSILE_CLIENT_PATH = Path('build_tmp') / 'tensilelite' / 'client' / 'tensilelite-client'
|
|
TENSILE_CLIENT_PATH = TENSILE_SCRIPT_DIR.parent / TENSILE_CLIENT_PATH
|
|
|
|
###############################################################################
|
|
# Execute Steps in Config
|
|
# called from Tensile() below
|
|
# calls
|
|
# BenchmarkProblems.main() to run benchmark steps
|
|
# LibraryLogic.main() to analyse final benchmark data and produce logic/yaml
|
|
# ClientWriter.main() to create client which calls library based on above yaml
|
|
################################################################################
|
|
@profile
|
|
def executeStepsInConfig(
|
|
config: dict,
|
|
outputPath: Path,
|
|
asmToolchain: AssemblyToolchain,
|
|
srcToolchain: SourceToolchain,
|
|
isaInfoMap: Dict[str, IsaInfo],
|
|
cCompiler: str,
|
|
debugConfig: DebugConfig,
|
|
deviceId: int,
|
|
probSolDict: dict,
|
|
buildOnly: bool = False,
|
|
solutionPoolFiles: list = None,
|
|
):
|
|
"""Conducts the steps in the provided ``config`` according to the Tensile workflow.
|
|
|
|
The top-level steps are:
|
|
1. BenchmarkProblems: Runs the benchmarking steps and generates the directories
|
|
build_tmp, 1_BenchmarkProblems, 2_BenchmarkData
|
|
2. LibraryLogic: Analyzes the benchmark data, makes logic files, and generates
|
|
the directory 3_LibraryLogic
|
|
3. LibraryClient: Makes the client callable libraries and generates the
|
|
directory 4_LibraryClient
|
|
|
|
Args:
|
|
config (dict): The configuration dictionary.
|
|
outputPath (Path): The path to the top-level build directory.
|
|
asmToolchain (AssemblyToolchain): The toolchain for making assembly kernels.
|
|
srcToolchain (SourceToolchain): The toolchain for making source kernels.
|
|
cCompiler (str): The C compiler to use.
|
|
buildOnly (bool): If True, generate and build kernels but skip benchmarking.
|
|
solutionPoolFiles (list): Resolved paths to library logic YAMLs to use as solution pools.
|
|
"""
|
|
|
|
buildTmpPath = outputPath / "build_tmp"
|
|
|
|
##############################################################################
|
|
# Benchmark Problems
|
|
##############################################################################
|
|
gfxName = isaToGfx(next(iter(isaInfoMap)))
|
|
if "BenchmarkProblems" in config:
|
|
with timing_context("python_benchmark_problems"):
|
|
BenchmarkProblems.main(
|
|
config.get("Backend", {}),
|
|
config["BenchmarkProblems"],
|
|
config["UseCache"],
|
|
asmToolchain,
|
|
srcToolchain,
|
|
cCompiler,
|
|
outputPath,
|
|
buildTmpPath,
|
|
debugConfig,
|
|
deviceId,
|
|
gfxName,
|
|
isaInfoMap,
|
|
probSolDict,
|
|
buildOnly,
|
|
solutionPoolFiles,
|
|
)
|
|
flush_timing_buffer()
|
|
print1("")
|
|
|
|
if buildOnly:
|
|
print1("# Build-only mode: skipping LibraryLogic and LibraryClient.")
|
|
return
|
|
|
|
##############################################################################
|
|
# Library Logic
|
|
##############################################################################
|
|
libraryLogicDataPath = os.path.join(outputPath, LIBRARY_LOGIC_DIR)
|
|
if "LibraryLogic" in config:
|
|
if os.path.exists(libraryLogicDataPath):
|
|
libraryLogicFiles = os.listdir(libraryLogicDataPath)
|
|
else:
|
|
libraryLogicFiles = []
|
|
if len(libraryLogicFiles) < 1 or globalParameters["ForceRedoLibraryLogic"]:
|
|
if config["LibraryLogic"] != None:
|
|
libraryLogicConfig = config["LibraryLogic"]
|
|
else:
|
|
libraryLogicConfig = {}
|
|
with timing_context("python_library_logic"):
|
|
LibraryLogic.main(
|
|
libraryLogicConfig,
|
|
srcToolchain.compiler,
|
|
outputPath,
|
|
debugConfig.splitGSU,
|
|
debugConfig.printSolutionRejectionReason,
|
|
debugConfig.printIndexAssignmentInfo,
|
|
isaInfoMap,
|
|
)
|
|
flush_timing_buffer()
|
|
print1("")
|
|
else:
|
|
print1("# LibraryLogic already done.")
|
|
print1("")
|
|
|
|
##############################################################################
|
|
# Write Client
|
|
##############################################################################
|
|
if "LibraryClient" in config:
|
|
if config["LibraryClient"] != None:
|
|
libraryClientConfig = config["LibraryClient"]
|
|
else:
|
|
libraryClientConfig = {}
|
|
with timing_context("python_client_writer"):
|
|
ClientWriter.main(
|
|
libraryClientConfig,
|
|
asmToolchain.assembler,
|
|
cCompiler,
|
|
isaInfoMap,
|
|
outputPath,
|
|
deviceId,
|
|
gfxName,
|
|
)
|
|
flush_timing_buffer()
|
|
print1("")
|
|
|
|
|
|
def addCommonArguments(argParser):
|
|
"""
|
|
Add a common set of arguments to `argParser`.
|
|
|
|
Currently used by the main Tensile script and the unit tests but could also be used for TensileCreateLibrary.
|
|
"""
|
|
|
|
def splitExtraParameters(par):
|
|
"""
|
|
Allows the --global-parameters option to specify any parameters from the command line.
|
|
"""
|
|
(key, value) = par.split("=")
|
|
value = eval(value)
|
|
return (key, value)
|
|
|
|
argParser.add_argument("-d", "--device", dest="device", default=0, type=int, \
|
|
help="override which device to benchmark")
|
|
argParser.add_argument("-p", "--platform", dest="platform", type=int, \
|
|
help="override which OpenCL platform to benchmark")
|
|
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=["4", "5", "V4", "V5", "default"], 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", \
|
|
help="set PrintLevel=2 and CMakeBuildType=Debug")
|
|
argParser.add_argument("--cxx-compiler", dest="CxxCompiler", \
|
|
action="store", default=ToolchainDefaults.CXX_COMPILER, help="select which C++/HIP compiler to use")
|
|
argParser.add_argument("--c-compiler", dest="CCompiler", \
|
|
action="store", default=ToolchainDefaults.C_COMPILER, help="select which C compiler to use")
|
|
argParser.add_argument("--assembler", dest="Assembler", \
|
|
action="store", default=ToolchainDefaults.ASSEMBLER, help="select which assembler to use")
|
|
argParser.add_argument("--offload-bundler", dest="OffloadBundler", \
|
|
action="store", default=ToolchainDefaults.OFFLOAD_BUNDLER, help="select which offload bundler to use")
|
|
argParser.add_argument("--device-enumerator", dest="DeviceEnumerator", \
|
|
action="store", default=ToolchainDefaults.DEVICE_ENUMERATOR, help="select which device enumerator to use")
|
|
argParser.add_argument("--logic-format", dest="LogicFormat", choices=["yaml", "json"], \
|
|
action="store", default="yaml", help="select which logic format to use")
|
|
argParser.add_argument("--library-format", dest="LibraryFormat", choices=["yaml", "msgpack"], \
|
|
action="store", default="yaml", help="select which library format to use")
|
|
argParser.add_argument("--client-lock", default=None)
|
|
argParser.add_argument("--prebuilt-client", default=str(TENSILE_CLIENT_PATH), \
|
|
type=os.path.abspath, help="Specify the full path to a pre-built tensilelite-client executable")
|
|
argParser.add_argument("--mx-scale-format", dest="MXScaleFormat", type=int, default=0, \
|
|
help="MX scale data format (0=none, 1=pre-swizzle for GPU kernel layout)")
|
|
argParser.add_argument("--rocm-agent-enumerator", default=None, action="store", dest="rocm_agent_enumerator")
|
|
argParser.add_argument("--cpu-only", dest="cpuOnly", action="store_true", default=False, \
|
|
help="Run the benchmark flow GPU-less for a target arch (requires --gpu-targets): spoof ISA "
|
|
"detection, skip the GPU clock-frequency probe, and stub the client launch with a "
|
|
"synthetic results CSV. For CPU-only CI/coverage; perf numbers are synthetic.")
|
|
argParser.add_argument("--global-parameters", nargs="+", type=splitExtraParameters, default=[])
|
|
|
|
|
|
def argUpdatedGlobalParameters(args):
|
|
"""
|
|
Returns a dictionary with `globalParameters` keys that should be updated based on `args`.
|
|
"""
|
|
rv = {}
|
|
# override config with command-line options
|
|
if args.platform:
|
|
print1("# Command-line override: Platform")
|
|
rv["Platform"] = args.platform
|
|
if args.RuntimeLanguage:
|
|
print1("# Command-line override: RuntimeLanguage")
|
|
rv["RuntimeLanguage"] = args.RuntimeLanguage
|
|
if args.CodeObjectVersion:
|
|
print1("# Command-line override: CodeObjectVersion")
|
|
rv["CodeObjectVersion"] = args.CodeObjectVersion
|
|
if args.debug:
|
|
print1("# Command-line override: Debug")
|
|
rv["CMakeBuildType"] = "Debug"
|
|
if args.client_lock:
|
|
rv["ClientExecutionLockPath"] = args.client_lock
|
|
if args.prebuilt_client:
|
|
rv["PrebuiltClient"] = args.prebuilt_client
|
|
if args.MXScaleFormat:
|
|
print1("# Command-line override: MXScaleFormat")
|
|
rv["MXScaleFormat"] = args.MXScaleFormat
|
|
|
|
for key, value in args.global_parameters:
|
|
rv[key] = value
|
|
|
|
PyTestBuildArchNames = os.environ.get("PyTestBuildArchNames")
|
|
if PyTestBuildArchNames != None and len(PyTestBuildArchNames) > 0:
|
|
rv["Architecture"] = PyTestBuildArchNames
|
|
|
|
return rv
|
|
|
|
def get_gpu_max_frequency_smi(device_id):
|
|
'''
|
|
Get the maximum frequency of the specified GPU device
|
|
'''
|
|
try:
|
|
# Run amd-smi command and capture the GFX clock info as JSON
|
|
result = subprocess.run(
|
|
['amd-smi', 'metric', '-g', str(device_id), '--clock', '--json'],
|
|
capture_output=True, text=True)
|
|
|
|
if result.returncode != 0:
|
|
print(f"Error running amd-smi: {result.stderr}")
|
|
return None
|
|
|
|
data = json.loads(result.stdout)
|
|
clocks = data['gpu_data'][0]['clock']
|
|
|
|
# Collect the max GFX (sclk) clock across all gfx engines/partitions
|
|
frequencies = []
|
|
for name, info in clocks.items():
|
|
if name.startswith('gfx'):
|
|
max_clk = info.get('max_clk', {}).get('value')
|
|
if isinstance(max_clk, int):
|
|
frequencies.append(max_clk)
|
|
|
|
# Return the maximum frequency found
|
|
return max(frequencies) if frequencies else None
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return None
|
|
|
|
def get_gpu_max_frequency(device_id):
|
|
try:
|
|
from hip import hip
|
|
except ImportError:
|
|
# hip-python is optional and intentionally NOT auto-installed here.
|
|
# Auto-installing from a package index at build time is a supply-chain
|
|
# risk (ROCM-26748 / SEC-00581) and hip-python has no wheel on some
|
|
# platforms (e.g. Windows). Return None so the caller falls back to
|
|
# amd-smi (get_gpu_max_frequency_smi) and then the manual prompt.
|
|
return None
|
|
|
|
def hip_check(call_result):
|
|
err, result = call_result[0], call_result[1]
|
|
if isinstance(err, hip.hipError_t) and err != hip.hipError_t.hipSuccess:
|
|
return None
|
|
return result
|
|
|
|
attrib = hip.hipDeviceAttribute_t.hipDeviceAttributeClockRate
|
|
try:
|
|
freq = hip_check(hip.hipDeviceGetAttribute(attrib, device_id))
|
|
except:
|
|
freq = None
|
|
|
|
return freq // 1000 if freq else None
|
|
|
|
def get_user_max_frequency() -> Optional[int]:
|
|
'''
|
|
Get the maximum frequency from the user when the GPU frequency cannot be determined.
|
|
Returns None if the GPU frequency cannot be determined.
|
|
'''
|
|
# Non-interactive guard: when stdin is not a tty (CI, --build-only via
|
|
# pytest, etc.) we cannot prompt. Returning None is safe because:
|
|
# - The caller in Tensile() already handles None/<=0 with a warning and
|
|
# skips store_max_frequency().
|
|
# - The downstream consumer (LibraryLogic.read_max_freq) also handles a
|
|
# missing MAX_FREQ env var gracefully (returns None, caller checks).
|
|
# - The entire frequency block is gated on `not buildOnly`, so build-only
|
|
# runs never reach this path.
|
|
if not sys.stdin.isatty():
|
|
printWarning("Cannot prompt for GPU frequency in non-interactive mode")
|
|
return None
|
|
while True:
|
|
try:
|
|
user_input = input("Please enter the maximum frequency (MHz): ")
|
|
|
|
frequency = int(user_input)
|
|
|
|
if frequency <= 0:
|
|
print("Error: Frequency must be greater than 0 MHz")
|
|
continue
|
|
|
|
return frequency
|
|
|
|
except ValueError:
|
|
print("Error: Please enter a valid number")
|
|
except Exception as e:
|
|
print(f"Error: {str(e)}")
|
|
print("Please try again")
|
|
|
|
def store_max_frequency(max_frequency):
|
|
try:
|
|
os.environ["MAX_FREQ"] = str(max_frequency)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error setting MAX_FREQ environment variable: {e}")
|
|
return False
|
|
|
|
def restore_prob_sol_map(logfile):
|
|
|
|
print(f"# Restore Previous Tuning From Log File: {str(logfile)}")
|
|
|
|
startHint = "run,problem-progress,"
|
|
finTuningHint = "clientExit"
|
|
finProblemHint = "####"
|
|
keyword = "Contraction"
|
|
runningTuning = False
|
|
|
|
prob_best_us_record = {}
|
|
prob_sol_map = {}
|
|
cur_prob_idx = -1
|
|
cur_sol_idx = -1
|
|
last_fin_prob = -1
|
|
allProblems = 0
|
|
allSolutions = 0
|
|
|
|
try:
|
|
with open(logfile, 'r') as sh_file:
|
|
# Iterate over each line in the file
|
|
for line in sh_file:
|
|
line = line.strip()
|
|
# run until we see a line: "run,problem-progress,...."
|
|
if not runningTuning:
|
|
runningTuning = line.startswith(startHint)
|
|
continue
|
|
|
|
# following lines should be the bench results
|
|
if runningTuning:
|
|
# we see a line with clientExit, then finish parsing log.
|
|
if line.startswith(finTuningHint):
|
|
print1(f'# Parsed log finished the tuning with line: {line}')
|
|
break
|
|
|
|
# a line starts and ends with "####" is printed in postProblem() which means the cur_prob completed tuning.
|
|
elif line.startswith(finProblemHint) and line.endswith(finProblemHint):
|
|
# print1(f'# Parsed Prob-Idx: {cur_prob_idx} completed tuning.')
|
|
last_fin_prob = cur_prob_idx
|
|
|
|
# not a valid tuning result line (a valid tuning result line contains "Contraction")
|
|
elif keyword not in line:
|
|
continue
|
|
|
|
# a valid tuning result line
|
|
else:
|
|
tokens = line.split('"')
|
|
# split by '"' to extract "(problem-size-desc)"
|
|
# there should be 3 tokens: { "some-info," , "(problem-size-desc)" , ",rest-of-other-info"}
|
|
assert len(tokens) == 3, f'bench result line: {line} is not fitting the expected format'
|
|
# original tokens[1] looks like "(M,N,B,K)", replace it with a string without comma
|
|
tokens[1] = 'problem-sizes'
|
|
newline = tokens[0] + tokens[1] + tokens[2]
|
|
# now the new line = "some-info, 'problem-sizes' , rest-of-other-info"
|
|
# 0,29/31,8/247,Contraction....,'problem-sizes',None,,None,[SOLUTION-NAME],[VALIDATION],[53.6248(us)],[63354.7(gflops)],.........
|
|
tokens = newline.split(',')
|
|
probInfo = tokens[1].split('/')
|
|
solInfo = tokens[2].split('/')
|
|
assert len(probInfo) == 2, f'tokens[1]: {tokens[1]} should be a token as probId/AllProb'
|
|
assert len(solInfo) == 2, f'tokens[2]: {tokens[2]} should be a token as solId/AllSol'
|
|
# solName = tokens[8] # Might be another choice to put in map...
|
|
usTimeInfo = float(tokens[10]) # tokens[10] should be the mju-sec
|
|
cur_prob_idx = int(probInfo[0])
|
|
cur_sol_idx = int(solInfo[0])
|
|
|
|
# This can handle logs with or without PrintWinnerOnly
|
|
current_best_us_for_prob = prob_best_us_record.get(cur_prob_idx)
|
|
if current_best_us_for_prob is None:
|
|
# print(f"None, added for probID: {probInfo[0]}, time: {usTimeInfo}")
|
|
prob_best_us_record.update( {cur_prob_idx : usTimeInfo} )
|
|
prob_sol_map.update( {cur_prob_idx : cur_sol_idx} )
|
|
else:
|
|
# update the winner if better
|
|
if usTimeInfo < current_best_us_for_prob:
|
|
# print(f"Better, updated for probID: {probInfo[0]}, new time: {usTimeInfo}, old time: {current_best_us_for_prob}")
|
|
prob_best_us_record.update( {cur_prob_idx : usTimeInfo} )
|
|
prob_sol_map.update( {cur_prob_idx : cur_sol_idx} )
|
|
|
|
# for printing information
|
|
if allProblems == 0:
|
|
allProblems = int(probInfo[1]) + 1
|
|
if allSolutions == 0:
|
|
allSolutions = int(solInfo[1]) + 1
|
|
|
|
# The final updated "cur_prob_idx" is not finished since we don't see its postProblem() "####" line
|
|
# -> The problem did not complete tuning, so we need to remove it from map
|
|
if cur_prob_idx != last_fin_prob:
|
|
prob_sol_map.pop(cur_prob_idx)
|
|
print1(f'# Parsed Prob-Idx {cur_prob_idx} did not complete tuning. Remove it from map.')
|
|
|
|
except FileNotFoundError:
|
|
print(f'Error: The file {logfile} was not found.')
|
|
|
|
except Exception as e:
|
|
print(f'An error occurred: {e}')
|
|
|
|
print(f"# Parsed log is for a '{allProblems}-problems-vs-{allSolutions}-solutions' tuning.")
|
|
|
|
return prob_sol_map
|
|
|
|
|
|
################################################################################
|
|
# Tensile
|
|
# - below entry points call here
|
|
################################################################################
|
|
def Tensile(userArgs):
|
|
global globalParameters
|
|
|
|
print1("")
|
|
print1(HR)
|
|
print1("#")
|
|
print1("# Tensile v%s" % (__version__))
|
|
|
|
argParser = argparse.ArgumentParser()
|
|
argParser.add_argument("ConfigFile", type=os.path.realpath, nargs="+",
|
|
help="Benchmark config.yaml file")
|
|
argParser.add_argument("OutputPath", \
|
|
help="Path to conduct benchmark and write output files")
|
|
argParser.add_argument("--version", action="version", \
|
|
version="%(prog)s {version}".format(version=__version__))
|
|
argParser.add_argument("--alternate-format", dest="AlternateFormat", action="store_true",
|
|
help="Alternate format for config_file(s): first file is alternate config "
|
|
"and optional second file is size list")
|
|
argParser.add_argument("--use-cache", dest="useCache", action="store_true",
|
|
help="Bypass redo parameter forking and solution generation and used existing solutions.")
|
|
argParser.add_argument("--build-only", dest="buildOnly", action="store_true",
|
|
help="Generate and compile kernels but skip benchmarking. "
|
|
"Useful for splitting compilation and benchmarking across runs/nodes. "
|
|
"First run using this flag, then rerun with --use-cache.")
|
|
argParser.add_argument("--restore-from-log", type=str, dest="RestoreLog",
|
|
help="A log file captured in previous tuning. ONLY RELIABLE when configs yaml not changes")
|
|
argParser.add_argument("--solution-pool", dest="solutionPool", default=None,
|
|
help="Glob pattern matching library logic YAML file(s). Solutions from the "
|
|
"matching file (by ProblemType) will be used as the solution pool instead "
|
|
"of generating from ForkParameters. "
|
|
"Example: '3_LibraryLogic/*.yaml'")
|
|
argParser.add_argument("--gpu-targets", dest="gpuTargets", default=None,
|
|
help="Semicolon-separated GPU targets (e.g. gfx942). "
|
|
"Overrides ISA auto-detection and YAML config ISA.")
|
|
|
|
addCommonArguments(argParser)
|
|
args = argParser.parse_args(userArgs)
|
|
configPaths = args.ConfigFile
|
|
altFormat = args.AlternateFormat
|
|
useCache = args.useCache
|
|
buildOnly = args.buildOnly
|
|
outputPath = Path(ensurePath(os.path.abspath(args.OutputPath)))
|
|
print1(f"# OutputPath: {str(outputPath)}")
|
|
|
|
setVerbosity(2 if (args.debug or args.verbose) else 1)
|
|
|
|
if altFormat and len(configPaths) > 2:
|
|
printExit("Only 1 or 2 config_files are accepted for the alternate config format: "
|
|
"the alternate config file and an optional size list")
|
|
elif not altFormat and len(configPaths) != 1:
|
|
printExit("Only 1 config_file is accepted for the default config format. "
|
|
"Did you mean to add '--alternate-formate'?")
|
|
|
|
prob_sol_map = {}
|
|
if args.RestoreLog:
|
|
restoreLogPath = Path(os.path.abspath(args.RestoreLog))
|
|
if not os.path.exists(restoreLogPath):
|
|
printExit(f'restore log file: {restoreLogPath} is not existing, abort. Please check')
|
|
prob_sol_map = restore_prob_sol_map(restoreLogPath)
|
|
for key,value in prob_sol_map.items():
|
|
print1(f'# Restored Prob-Solution From Log: [Prob:{key},Sol:{value}]')
|
|
|
|
solutionPoolFiles = None
|
|
if args.solutionPool:
|
|
pool = args.solutionPool
|
|
if os.path.isdir(pool):
|
|
globPattern = os.path.join(pool, "**", "*.yaml")
|
|
else:
|
|
globPattern = pool
|
|
solutionPoolFiles = sorted(f for f in glob.iglob(globPattern, recursive=True) if os.path.isfile(f))
|
|
if not solutionPoolFiles:
|
|
printExit("--solution-pool '{}' matched no files".format(args.solutionPool))
|
|
print1("# Solution pool: {} files".format(len(solutionPoolFiles)))
|
|
|
|
# 2nd half of splash
|
|
if len(configPaths) == 1:
|
|
print1("# Config: {}".format(configPaths[0]))
|
|
else:
|
|
print1("# Configs: {} and {}".format(configPaths[0], configPaths[1]))
|
|
print1("# Date & Time: %s" % (datetime.now().strftime("%d/%m/%Y %H:%M:%S")))
|
|
print1("#")
|
|
print1(HR)
|
|
print1("")
|
|
|
|
print1("# Restoring default globalParameters")
|
|
restoreDefaultGlobalParameters()
|
|
|
|
# Stash the --cpu-only flag in undocumented internal plumbing so the deep seams
|
|
# (Architectures ISA spoof, frequency-probe skip, ClientWriter launch stub) can read
|
|
# it without threading a new parameter through executeStepsInConfig. Set AFTER
|
|
# restoreDefaultGlobalParameters() (which would otherwise clobber it back to the
|
|
# default False). Kept out of the documented --global-parameters surface.
|
|
globalParameters["CpuOnly"] = args.cpuOnly
|
|
|
|
if args.LogicFormat:
|
|
globalParameters['LogicFormat'] = args.LogicFormat
|
|
if args.LibraryFormat:
|
|
globalParameters['LibraryFormat'] = args.LibraryFormat
|
|
globalParameters['CodeObjectVersion'] = coVersionMap[args.CodeObjectVersion]
|
|
print1(f"# Code Object Version: {globalParameters['CodeObjectVersion']}")
|
|
|
|
# default config format
|
|
if not altFormat:
|
|
config = LibraryIO.read(configPaths[0])
|
|
# convert alternate format into default format
|
|
else:
|
|
base = LibraryIO.read(configPaths[0])
|
|
sizes = []
|
|
if len(configPaths) == 2:
|
|
sizes = LibraryIO.read(configPaths[1])
|
|
|
|
config = {"GlobalParameters": base.get("GlobalParameters")}
|
|
if "LibraryLogic" in base and len(sizes) > 0:
|
|
config["LibraryLogic"] = base["LibraryLogic"]
|
|
if "LibraryClient" in base and len(sizes) > 0:
|
|
config["LibraryClient"] = None
|
|
|
|
solParams = {
|
|
"BenchmarkCommonParameters": base.get("BenchmarkCommonParameters"),
|
|
"ForkParameters": base.get("ForkParameters"),
|
|
"GroupForkParameters": base.get("GroupForkParameters"),
|
|
"BenchmarkFinalParameters": [{
|
|
"ProblemSizes": sizes
|
|
}]
|
|
}
|
|
config["BenchmarkProblems"] = [[base["ProblemType"], solParams]]
|
|
|
|
config["UseCache"] = useCache
|
|
globalParameters["ConfigPath"] = configPaths
|
|
|
|
# Backend selection precedence:
|
|
# 1) YAML Backend (with strict validation)
|
|
# 2) tensile (default)
|
|
yaml_backend = config.get("Backend", None)
|
|
config["Backend"] = parse_backend_config(yaml_backend)
|
|
|
|
asm_debug = config["GlobalParameters"].get("AsmDebug", False)
|
|
device_id = config["GlobalParameters"].get("Device", int(args.device))
|
|
UseEffLike = config["GlobalParameters"].get("UseEffLike", globalParameters["UseEffLike"])
|
|
UseEffLike = False if isRhel8() else UseEffLike
|
|
|
|
if 'LibraryLogic' in config and UseEffLike and not buildOnly and not globalParameters["CpuOnly"]:
|
|
max_frequency = get_gpu_max_frequency(device_id)
|
|
|
|
if not max_frequency or max_frequency <= 0:
|
|
max_frequency = get_gpu_max_frequency_smi(device_id) # Using amd-smi just in case
|
|
|
|
if not max_frequency or max_frequency <= 0:
|
|
print(f"Could not detect valid GPU frequency for device {device_id}")
|
|
max_frequency = get_user_max_frequency()
|
|
|
|
if not max_frequency or max_frequency <= 0:
|
|
printWarning("Could not determine GPU frequency. "
|
|
"Skipping frequency-dependent configuration.")
|
|
else:
|
|
print(f"Successfully retrieved max frequency: "
|
|
f"{max_frequency} for device {device_id}")
|
|
store_max_frequency(max_frequency)
|
|
|
|
cxxCompiler, \
|
|
cCompiler, \
|
|
offloadBundler = validateToolchain(args.CxxCompiler,
|
|
args.CCompiler,
|
|
args.OffloadBundler)
|
|
|
|
if args.gpuTargets:
|
|
enumerator = None # not needed — ISA comes from --gpu-targets
|
|
else:
|
|
enumerator = validateToolchain(ToolchainDefaults.DEVICE_ENUMERATOR if args.rocm_agent_enumerator is None else args.rocm_agent_enumerator)
|
|
|
|
asmToolchain = makeAssemblyToolchain(
|
|
cxxCompiler,
|
|
offloadBundler,
|
|
args.CodeObjectVersion,
|
|
debug=asm_debug,
|
|
)
|
|
srcToolchain = makeSourceToolchain(
|
|
cxxCompiler,
|
|
offloadBundler,
|
|
)
|
|
|
|
if args.gpuTargets:
|
|
from Tensile.Common.Architectures import gfxToIsa
|
|
isaList = []
|
|
for arch in args.gpuTargets.split(";"):
|
|
arch = arch.strip()
|
|
if not arch:
|
|
raise ValueError(f"Invalid GPU target: '{arch}'")
|
|
isa = gfxToIsa(arch)
|
|
if isa is None:
|
|
raise ValueError(f"Unrecognized GPU target: '{arch}'")
|
|
isaList.append(isa)
|
|
elif "ISA" in config["GlobalParameters"]:
|
|
isaList = [IsaVersion(isa[0], isa[1], isa[2]) for isa in config["GlobalParameters"]["ISA"]]
|
|
|
|
else:
|
|
isaList = [detectGlobalCurrentISA(device_id, enumerator)]
|
|
|
|
if IsaVersion(9, 5, 0) in isaList or IsaVersion(12, 5, 0) in isaList:
|
|
printWarning("HardwareMonitor currently disabled for gfx950 and gfx1250")
|
|
globalParameters["HardwareMonitor"] = False
|
|
|
|
isaInfoMap = makeIsaInfoMap(isaList, cxxCompiler)
|
|
assignGlobalParameters(config.get("GlobalParameters", {}), isaInfoMap)
|
|
|
|
overrideParameters = argUpdatedGlobalParameters(args)
|
|
|
|
debugConfig = makeDebugConfig(config["GlobalParameters"])
|
|
|
|
for key, value in overrideParameters.items():
|
|
print("Overriding {0}={1}".format(key, value))
|
|
globalParameters[key] = value
|
|
|
|
if "MaxFileName" in globalParameters or "MaxFileName" in config:
|
|
printWarning("MaxFileName is no longer configurable, it will be automatically set to 64")
|
|
|
|
executeStepsInConfig(config, outputPath, asmToolchain, srcToolchain, isaInfoMap, cCompiler, debugConfig, device_id, prob_sol_map, buildOnly, solutionPoolFiles)
|
|
|
|
def TensileConfigPath(*args):
|
|
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "Configs", *args)
|
|
|
|
|
|
def TensileTestPath(*args):
|
|
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "Tests", *args)
|
|
|
|
|
|
################################################################################
|
|
# Entry points
|
|
# the first several of these can be deprecated, only main() is used
|
|
################################################################################
|
|
|
|
|
|
# installed "tensile_rocblas_sgemm" command
|
|
def TensileROCBLASSGEMM():
|
|
Tensile([TensileConfigPath("rocblas_sgemm.yaml"), "."])
|
|
|
|
|
|
# installed "tensile_rocblas_dgemm" command
|
|
def TensileROCBLASDGEMM():
|
|
Tensile([TensileConfigPath("rocblas_dgemm.yaml"), "."])
|
|
|
|
|
|
# installed "tensile_rocblas_cgemm" command
|
|
def TensileROCBLASCGEMM():
|
|
Tensile([TensileConfigPath("rocblas_cgemm.yaml"), "."])
|
|
|
|
|
|
# installed "tensile_rocblas_zgemm" command
|
|
def TensileROCBLASZGEMM():
|
|
Tensile([TensileConfigPath("rocblas_zgemm.yaml"), "."])
|
|
|
|
|
|
# installed "tensile_sgemm" command
|
|
def TensileSGEMM5760():
|
|
Tensile([TensileConfigPath("sgemm_5760.yaml"), "."])
|
|
|
|
|
|
# installed "tensile" command
|
|
def main():
|
|
Tensile(sys.argv[1:])
|