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

500 lines
20 KiB
Python

################################################################################
#
# Copyright (C) 2016-2022 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 shlex, subprocess
import sys
import os
import argparse
from .BenchmarkSplitter import BenchmarkSplitter
from .Common import tPrint
from .Configuration import ProjectConfig
from .TensileBenchmarkClusterScripts import ScriptWriter
from Tensile.Utilities.merge import mergePartialLogics
try:
import mgzip as gzip
except ImportError:
print("Package mgzip not found: docker zipping will be slow. Install pip3 install mgzip to improve performance.")
# Fallback package import
import gzip
class BenchmarkImplSLURM(object):
# baseImage: remote URL to rocm build
# dockerFilePath: path to docker file for slurm tensile build
# tag: name:TAG of new image
# outDir: where to export the new image
# tensile<Fork/Branch/Commit>: tensile code to use on github
@staticmethod
def __createTensileBenchmarkContainer(baseImage, dockerFilePath, tag, outDir, logDir, tensileFork, tensileBranch, tensileCommit):
"""
Build a docker container with a specific
ROCm image, Tensile branch and tag. Docker
will pre-build the Tensile Client and
configure the container to run the benchmark.
"""
# Save stdout and stderr to file
with open(os.path.join(logDir, "dockerBuildLog.log"), 'w') as logFile:
# Docker build command
buildCmd = str("\
docker build \
-t {0} \
--pull -f {1} \
--build-arg user_uid=$UID \
--build-arg base_image={2} \
--build-arg tensile_fork={3} \
--build-arg tensile_branch={4} \
--build-arg tensile_commit={5} \
. ").format(tag, dockerFilePath, baseImage, tensileFork, tensileBranch, tensileCommit)
# Build container and save output streams
print("Building docker image: {0} ...".format(tag))
print(buildCmd)
# change to use check_output to force windows cmd block util command finish
try:
out = subprocess.check_output(shlex.split(buildCmd), stdout=logFile, stderr=logFile)
tPrint(3, out)
except subprocess.CalledProcessError as err:
print(err.output)
raise
print("Done building docker image!")
# Docker save command
imageBaseName = tag.split(':')[0]
archivePath = os.path.join(outDir, imageBaseName + str(".tar.gz"))
saveCmd = str("docker save {0}").format(tag)
# Docker will save .tar binary to stdout as long as it's not attached to console.
# Pipe stdout into gzip to get smaller .tar.gz archive
print("Saving docker image: {0} to {1} ...".format(tag, archivePath))
with gzip.open(archivePath, 'wb') as zipFile:
with subprocess.Popen(shlex.split(saveCmd), stdout=subprocess.PIPE, stderr=logFile) as proc:
zipFile.write(proc.stdout.read())
print("Done saving docker image!")
@staticmethod
def __createClusterBenchmarkScripts(baseDir, tasksDir, taskScriptName, jobScriptName):
"""
Build SLURM specific scripts.
Entrypoint is the 'sbatch' command, which will request resources and
enqueue several taskScripts.
TaskScripts each enqueue a benchmark run to
the cluster with 'srun'.
NodeScripts are the actual host code on each node to run the benchmark.
In this case, they will load the docker image and mount directories
that contain the .yaml file, and the results.
"""
# Entrypoint - SLURM specific
ScriptWriter.writeBenchmarkJobScript(jobScriptName, baseDir)
# Job submission script - SLURM specific
ScriptWriter.writeBenchmarkTaskScript(taskScriptName, baseDir)
# Move each individual config into it's own subfolder and create the node host script
# These scripts are NOT SLURM specific but rely on the implementation of the executable.
configFiles = [f for f in os.listdir(tasksDir) if os.path.isfile(os.path.join(tasksDir, f))]
for f in configFiles:
(baseFileName, _) = os.path.splitext(f)
configSubdir = os.path.join(tasksDir, baseFileName)
try:
os.makedirs(configSubdir)
except:
pass
os.rename(os.path.join(tasksDir, f), os.path.join(configSubdir, f))
ScriptWriter.writeBenchmarkNodeScript(baseFileName + ".sh", configSubdir)
@classmethod
def initializeConfig(cls, config):
"""
Store SLURM backend-specific configurations.
These can all be overridden via commandline
"""
# Dirs
rootTensileDir = config["RootTensileDir"]
# SLURM config
baseSection = config.createSection("SLURM")
# Script names
section = baseSection.createSection("SCRIPTS")
section.createValue("JobScriptName", "runBenchmark.sh")
section.createValue("TaskScriptName", "enqueueTask.sh")
# Docker build
section = baseSection.createSection("DOCKER")
section.createValue("DockerBaseImage", "compute-artifactory.amd.com:5000/rocm-plus-docker/compute-rocm-dkms-amd-feature-targetid:3004-STG1")
section.createValue("DockerBuildFile", os.path.join(rootTensileDir, "docker", "dockerfile-tensile-tuning-slurm"))
section.createValue("DockerImageName", "tensile-tuning-cluster-executable")
section.createValue("DockerImageTag", "TEST")
section.createValue("TensileFork", "ROCm")
section.createValue("TensileBranch", "develop")
section.createValue("TensileCommit", "HEAD")
@classmethod
def generateBenchmark(cls, config):
"""
Build executables used in SLURM backend,
based on configuration parameters
"""
# Dirs
(baseDir, tasksDir, imageDir, logsDir) = \
(config["BenchmarkBaseDir"], \
config["BenchmarkTasksDir"], \
config["BenchmarkImageDir"], \
config["BenchmarkLogsDir"])
# Create the base image for task executable in image dir
sConfig = config["SLURM"]["DOCKER"]
cls.__createTensileBenchmarkContainer( \
sConfig["DockerBaseImage"], \
sConfig["DockerBuildFile"], \
"{0}:{1}".format(sConfig["DockerImageName"], sConfig["DockerImageTag"]), \
imageDir, \
logsDir, \
sConfig["TensileFork"], \
sConfig["TensileBranch"], \
sConfig["TensileCommit"])
# Create the scripts that will be used to invoke the benchmark in base dir
# Create the scripts that node hosts will run in the tasks dir
sConfig = config["SLURM"]["SCRIPTS"]
cls.__createClusterBenchmarkScripts( \
baseDir, \
tasksDir, \
sConfig["TaskScriptName"], \
sConfig["JobScriptName"])
@classmethod
def preInvokeBenchmark(cls, config):
pass
@classmethod
def invokeBenchmark(cls, config):
# Dirs
(baseDir, tasksDir, imageDir, resultsDir, logsDir) = \
(config["BenchmarkBaseDir"], \
config["BenchmarkTasksDir"], \
config["BenchmarkImageDir"], \
config["BenchmarkResultsDir"], \
config["BenchmarkLogsDir"])
# Entry point script
scriptName = config["SLURM"]["SCRIPTS"]["JobScriptName"]
runScriptPath = os.path.join(baseDir, scriptName)
# Enqueue script
enqueueScriptName = config["SLURM"]["SCRIPTS"]["TaskScriptName"]
enqueueScriptPath = os.path.join(baseDir, enqueueScriptName)
# Log file
(name, ext) = os.path.splitext(scriptName)
logFilePath = os.path.join(logsDir, name + ".log")
invokeCmd = str("\
{0} \
-i {1} \
-l {2} \
-r {3} \
-s {4} \
-t {5}").format(runScriptPath, imageDir, logsDir, resultsDir, enqueueScriptPath, tasksDir)
with open(logFilePath, "wt") as logFile:
# change to use check_output to force windows cmd block util command finish
try:
out = subprocess.check_output(shlex.split(invokeCmd), stdout=logFile, stderr=logFile)
tPrint(3, out)
except subprocess.CalledProcessError as err:
print(err.output)
raise
@classmethod
def postInvokeBenchmark(cls, benchmarkObj):
pass
class TensileBenchmarkCluster(object):
"""
This is the main driver class for building and running
benchmarks with a cluster-configured backend.
The general workflow (main):
- Deploy benchmark
- Invoke benchmark
- Merge results
Expected backend interface:
- initializeConfig
- generateBenchmark
- preInvokeBenchmark
- invokeBenchmark
- postInvokeBenchmark
Other notes:
- The command line provides full control over
workflow and configuration.
- You can choose different backends as they
become available
- Workflow steps may be selected individually
- Any part of configuration may be overidden
by command line args.
"""
def __init__(self, cmdlineArgs):
self._config = ProjectConfig()
self.__initializeConfig(cmdlineArgs)
def __initializeConfig(self, cmdlineArgs):
"""
Initialize configuration parameters needed
to build and run the benchmark.
"""
# 1. Parse command line
args = self.__parseArgs(cmdlineArgs)
# 2. Setup default configurations that depend on inputs
# Directories
self._config.createValue("RootTensileDir", os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
self._config.createValue("BenchmarkLogicPath", args.BenchmarkLogicPath)
self._config.createValue("BenchmarkBaseDir", args.DeploymentPath)
self._config.createValue("BenchmarkTasksDir", os.path.join(args.DeploymentPath, "Tasks"))
self._config.createValue("BenchmarkImageDir", os.path.join(args.DeploymentPath, "Image"))
self._config.createValue("BenchmarkResultsDir", os.path.join(args.DeploymentPath, "Results"))
self._config.createValue("BenchmarkFinalLogicDir", os.path.join(args.DeploymentPath, "Results", "Final"))
self._config.createValue("BenchmarkLogsDir", os.path.join(args.DeploymentPath, "Logs"))
# Benchmarking
self._config.createValue("BenchmarkTaskSize", 10) # Sizes per benchmark file
self._config.addConstraint("BenchmarkTaskSize > 0")
self._config.createValue("RunDeployStep", True and (not args.RunOnly and not args.ResultsOnly and not args.RunAndResultsOnly))
self._config.createValue("RunBenchmarkStep", True and (not args.DeployOnly and not args.ResultsOnly))
self._config.createValue("RunResultsStep", True and (not args.DeployOnly and not args.RunOnly))
self._config.addConstraint("RunDeployStep or RunBenchmarkStep or RunResultsStep")
# Results merging settings
self._config.createValue("FinalLogicForceMerge", False)
self._config.createValue("FinalLogicTrim", True)
# Initialize the backend implementation
if args.ClusterBackend.lower() == "slurm":
self._backendImpl = BenchmarkImplSLURM
else:
raise NotImplementedError("Cluster backend not recognized")
# 3. Setup backend configuration
self._backendImpl.initializeConfig(self._config)
# 4. Override requested config parameters
overrideArgs = {}
for key, value in args.benchmark_parameters:
overrideArgs[key] = value
self.__overrideConfig(overrideArgs)
self._config.checkConstraints()
@staticmethod
def __parseArgs(cmdlineArgs):
"""
Parse input arguments to call script
"""
def splitExtraParameters(par):
"""
Allows the --benchmark-parameters option to specify any parameters from the command line.
"""
(key, value) = par.split("=")
value = eval(value)
return (key, value)
# Parse incoming args
argParser = argparse.ArgumentParser()
argParser.add_argument("BenchmarkLogicPath", help="Path to benchmark config .yaml files.")
argParser.add_argument("DeploymentPath", help="Where to deploy benchmarking files. Should target a directory on shared nfs mount of cluster.")
argParser.add_argument("--cluster-backend", dest="ClusterBackend", type=str, default="slurm", help="Choose backend plugin to run benchmark")
argParser.add_argument("--deploy-only", dest="DeployOnly", action="store_true", default=False, help="Deploy benchmarking files only without running or reducing results")
argParser.add_argument("--run-only", dest="RunOnly", action="store_true", default=False, help="Run benchmark without deploying or reducing results")
argParser.add_argument("--results-only", dest="ResultsOnly", action="store_true", default=False, help="Reduce results without deploying or running")
argParser.add_argument("--run-and-results-only", dest="RunAndResultsOnly", action="store_true", default=False, help="Run benchmark and reduce results without deploying")
argParser.add_argument("--benchmark-parameters", nargs="+", type=splitExtraParameters, default=[], help="Pairs of X=Y assignments. Note: if Y is a string, then it must have escaped quotes \\\"Y\\\"")
return argParser.parse_args()
def __overrideConfig(self, args):
"""
Assign Benchmark Parameters
Each parameter has a default parameter, and the user
can override them, those overridings happen here
"""
for key in args:
value = args[key]
if key not in self._config:
print("Warning: Benchmark parameter {0} = {1} unrecognised.".format( key, value ))
else:
self._config[key] = value
def __generateClusterBenchmark(self):
"""
Generate everything for the benchmark to run
"""
# Ensure that we have all directories
self.ensurePath(self.baseDir())
self.ensurePath(self.tasksDir())
self.ensurePath(self.imageDir())
self.ensurePath(self.resultsDir())
self.ensurePath(self.finalLogicDir())
self.ensurePath(self.logsDir())
# Split master config into smaller task-sized configs
# These are stored under the tasks dir
BenchmarkSplitter.splitBenchmarkBySizes( \
self._config["BenchmarkLogicPath"], \
self.tasksDir(), \
self._config["BenchmarkTaskSize"], \
suffixFormat="{:04d}") # Support lots of jobs up to 9999
# Delegate to the backend implementation to generate everything it needs for the benchmark run
self._backendImpl.generateBenchmark(self._config)
def __runClusterBenchmark(self):
"""
Invoke backend benchmark
"""
# Delegate to the backend implementation to invoke
self._backendImpl.preInvokeBenchmark(self._config)
self._backendImpl.invokeBenchmark(self._config)
self._backendImpl.postInvokeBenchmark(self._config)
def __combineClusterBenchmarkResults(self):
"""
Combine all partial results into the final
kernel logic.
Each partial result is expected to be under
the base/ResultsDir/<PART>/3_LibraryLogic
directory.
"""
(resultsDir, finalLogicDir) = (self.resultsDir(), self.finalLogicDir())
# Find the partial logic .yaml files
# These are in each result directory under 3_LibraryLogic
resultsDirs = [os.path.join(resultsDir, d, "3_LibraryLogic") for d in os.listdir(resultsDir) if os.path.isdir(os.path.join(resultsDir, d, "3_LibraryLogic"))]
resultsFiles = []
for d in resultsDirs:
resultsFiles += [os.path.join(d, f) for f in os.listdir(d) if os.path.isfile(os.path.join(d, f)) ]
if len(resultsDirs) != len(resultsFiles):
print("Warning: inconsistent number of expected results. Check that results are complete.")
mergePartialLogics(
resultsFiles, \
finalLogicDir, \
self._config["FinalLogicForceMerge"], \
self._config["FinalLogicTrim"])
def workflowSteps(self):
"""
Helper function to easily receive a tuple of important
workflow step indicators
"""
return( \
self._config["RunDeployStep"], \
self._config["RunBenchmarkStep"], \
self._config["RunResultsStep"])
def config(self):
return self._config
def rootTensileDir(self):
return self.config()["RootTensileDir"]
def baseDir(self):
return self.config()["BenchmarkBaseDir"]
def tasksDir(self):
return self.config()["BenchmarkTasksDir"]
def imageDir(self):
return self.config()["BenchmarkImageDir"]
def resultsDir(self):
return self.config()["BenchmarkResultsDir"]
def finalLogicDir(self):
return self.config()["BenchmarkFinalLogicDir"]
def logsDir(self):
return self.config()["BenchmarkLogsDir"]
@staticmethod
def ensurePath(path):
"""
Helper function to create path if necessary
"""
try:
os.makedirs(path)
except OSError:
pass
return path
def main(self):
"""
Main driver for benchmark:
- Deploy
- Run
- Results
"""
(doDeploy, doBenchmark, doResults) = self.workflowSteps()
# Deploy
if doDeploy is True:
print("Preparing benchmarking files...")
self.__generateClusterBenchmark()
print("Finished preparing benchmarking files")
# Benchmark invoke
if doBenchmark is True:
print("Running benchmark tasks (this might take a while)...")
self.__runClusterBenchmark()
print("Finished benchmark tasks")
# Combining results
if doResults is True:
print("Combining benchmark results...")
self.__combineClusterBenchmarkResults()
print("Final logic file saved to: {0}".format(self.finalLogicDir()))
print("Finished")
def main():
entryPoint = TensileBenchmarkCluster(sys.argv[1:])
entryPoint.main()