Initial overhaul of Profile mode

Signed-off-by: colramos-amd <colramos@amd.com>
This commit is contained in:
colramos-amd
2023-10-31 15:11:17 -05:00
parent c15ec38eca
commit 87388227ca
421 changed files with 16949 additions and 1912 deletions
+1
View File
@@ -12,3 +12,4 @@ dash-svg
dash-bootstrap-components
kaleido
setuptools
pyfiglet
+138 -149
View File
@@ -22,36 +22,23 @@
# SOFTWARE.
##############################################################################el
import os
import argparse
import shutil
import os
from common import (
OMNIPERF_HOME,
PROG,
SOC_LIST,
) # Import global variables
from common import getVersion, getVersionDisplay
def parse(my_parser):
# versioning info
vData = getVersion()
versionString = getVersionDisplay(vData["version"], vData["sha"], vData["mode"])
def omniarg_parser(parser, omniperf_home, omniperf_version):
# -----------------------------------------
# Parse arguments (dependent on mode)
# -----------------------------------------
## General Command Line Options
## ----------------------------
general_group = my_parser.add_argument_group("General Options")
my_parser._positionals.title = "Modes"
my_parser._optionals.title = "Help"
general_group.add_argument("-v", "--version", action="version", version=versionString)
general_group = parser.add_argument_group("General Options")
parser._positionals.title = "Modes"
parser._optionals.title = "Help"
general_group.add_argument("-v", "--version", action="version", version=omniperf_version["ver_pretty"])
subparsers = my_parser.add_subparsers(
subparsers = parser.add_subparsers(
dest="mode", help="Select mode of interaction with the target application:"
)
@@ -61,17 +48,17 @@ def parse(my_parser):
"profile",
help="Profile the target application",
usage="""
\nomniperf profile --name <workload_name> [profile options] [roofline options] -- <profile_cmd>
\nomniperf profile --name <workload_name> [profile options] [roofline options] -- <profile_cmd>
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf profile -n vcopy_all -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_SPI_TCC -b SQ TCC -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_kernel -k vecCopy -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_disp -d 0 -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_roof --roof-only -- ./vcopy 1048576 256
\n-------------------------------------------------------------------------------\n
""",
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf profile -n vcopy_all -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_SPI_TCC -b SQ TCC -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_kernel -k vecCopy -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_disp -d 0 -- ./vcopy 1048576 256
\n\tomniperf profile -n vcopy_roof --roof-only -- ./vcopy 1048576 256
\n-------------------------------------------------------------------------------\n
""",
prog="tool",
allow_abbrev=False,
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
@@ -79,14 +66,16 @@ def parse(my_parser):
),
)
profile_parser._optionals.title = "Help"
general_group = profile_parser.add_argument_group("General Options")
profile_group = profile_parser.add_argument_group("Profile Options")
roofline_group = profile_parser.add_argument_group("Standalone Roofline Options")
general_group.add_argument("-v", "--version", action="version", version=versionString)
general_group.add_argument("-v", "--version", action="version", version=omniperf_version["ver_pretty"])
general_group.add_argument(
"-V", "--verbose", help="Increase output verbosity", action="count", default=0
)
profile_group.add_argument(
"-n",
"--name",
@@ -103,7 +92,7 @@ def parse(my_parser):
metavar="",
type=str,
dest="path",
default=os.getcwd() + "/workloads",
default=os.path.join(os.getcwd(), "workloads"),
required=False,
help="\t\t\tSpecify path to save workload.\n\t\t\t(DEFAULT: {}/workloads/<name>)".format(
os.getcwd()
@@ -120,6 +109,27 @@ def parse(my_parser):
default=None,
help="\t\t\tKernel filtering.",
)
profile_group.add_argument(
"-d",
"--dispatch",
type=str,
metavar="",
nargs="+",
dest="dispatch",
required=False,
help="\t\t\tDispatch ID filtering.",
)
profile_group.add_argument(
"-b",
"--ipblocks",
type=str,
dest="ipblocks",
metavar="",
nargs="+",
required=False,
choices=["SQ", "SQC", "TA", "TD", "TCP", "TCC", "SPI", "CPC", "CPF"],
help="\t\t\tIP block filtering:\n\t\t\t SQ\n\t\t\t SQC\n\t\t\t TA\n\t\t\t TD\n\t\t\t TCP\n\t\t\t TCC\n\t\t\t SPI\n\t\t\t CPC\n\t\t\t CPF",
)
result = shutil.which("rocscope")
if result:
@@ -160,28 +170,6 @@ def parse(my_parser):
profile_group.add_argument(
"--kernel-summaries", default=False, dest="summaries", help=argparse.SUPPRESS
)
profile_group.add_argument(
"-b",
"--ipblocks",
type=str,
dest="ipblocks",
metavar="",
nargs="+",
required=False,
choices=["SQ", "SQC", "TA", "TD", "TCP", "TCC", "SPI", "CPC", "CPF"],
help="\t\t\tIP block filtering:\n\t\t\t SQ\n\t\t\t SQC\n\t\t\t TA\n\t\t\t TD\n\t\t\t TCP\n\t\t\t TCC\n\t\t\t SPI\n\t\t\t CPC\n\t\t\t CPF",
)
profile_group.add_argument(
"-d",
"--dispatch",
type=str,
metavar="",
nargs="+",
dest="dispatch",
required=False,
help="\t\t\tDispatch ID filtering.",
)
profile_group.add_argument(
"--join-type",
metavar="",
@@ -205,7 +193,7 @@ def parse(my_parser):
help="\t\t\tProvide command for profiling after double dash.",
)
profile_group.add_argument(
"--kernelVerbose",
"--kernel-verbose",
required=False,
metavar="",
help="\t\t\tSpecify Kernel Name verbose level 1-5. Lower the level, shorter the kernel name. (DEFAULT: 2) (DISABLE: 5)",
@@ -268,14 +256,14 @@ def parse(my_parser):
"database",
help="Interact with Omniperf database",
usage="""
\nomniperf database <interaction type> [connection options]
\nomniperf database <interaction type> [connection options]
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf database --import -H pavii1 -u temp -t asw -w workloads/vcopy/mi200/
\n\tomniperf database --remove -H pavii1 -u temp -w omniperf_asw_sample_mi200
\n-------------------------------------------------------------------------------\n
""",
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf database --import -H pavii1 -u temp -t asw -w workloads/vcopy/mi200/
\n\tomniperf database --remove -H pavii1 -u temp -w omniperf_asw_sample_mi200
\n-------------------------------------------------------------------------------\n
""",
prog="tool",
allow_abbrev=False,
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
@@ -288,7 +276,7 @@ def parse(my_parser):
interaction_group = db_parser.add_argument_group("Interaction Type")
connection_group = db_parser.add_argument_group("Connection Options")
general_group.add_argument("-v", "--version", action="version", version=versionString)
general_group.add_argument("-v", "--version", action="version", version=omniperf_version["ver_pretty"])
general_group.add_argument(
"-V", "--verbose", help="Increase output verbosity", action="count", default=0
)
@@ -357,15 +345,15 @@ def parse(my_parser):
"analyze",
help="Analyze existing profiling results at command line",
usage="""
\nomniperf analyze --path <workload_path> [analyze options]
\nomniperf analyze --path <workload_path> [analyze options]
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf analyze -p workloads/vcopy/mi200/ --list-metrics gfx90a
\n\tomniperf analyze -p workloads/mixbench/mi200/ --filter-dispatch-ids 12 34 --decimal 3
\n\tomniperf analyze -p workloads/mixbench/mi200/ --gui
\n-------------------------------------------------------------------------------\n
""",
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf analyze -p workloads/vcopy/mi200/ --list-metrics gfx90a
\n\tomniperf analyze -p workloads/mixbench/mi200/ --dispatch 12 34 --decimal 3
\n\tomniperf analyze -p workloads/mixbench/mi200/ --gui
\n-------------------------------------------------------------------------------\n
""",
prog="tool",
allow_abbrev=False,
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
@@ -376,8 +364,9 @@ def parse(my_parser):
general_group = analyze_parser.add_argument_group("General Options")
analyze_group = analyze_parser.add_argument_group("Analyze Options")
analyze_advanced_group = analyze_parser.add_argument_group("Advanced Options")
general_group.add_argument("-v", "--version", action="version", version=versionString)
general_group.add_argument("-v", "--version", action="version", version=omniperf_version["ver_pretty"])
general_group.add_argument(
"-V", "--verbose", help="Increase output verbosity", action="count", default=0
)
@@ -392,31 +381,16 @@ def parse(my_parser):
action="append",
help="\t\tSpecify the raw data root dirs or desired results directory.",
)
analyze_group.add_argument(
"-o",
"--output",
metavar="",
dest="output_file",
help="\t\tSpecify the output file.",
)
analyze_group.add_argument(
"--list-kernels",
action="store_true",
help="\t\tList kernels. Top 10 kernels sorted by duration (descending order).",
help="\t\tList all detected kernels. Sorted by duration (descending order).",
)
analyze_group.add_argument(
"--list-metrics",
metavar="",
choices=["gfx906", "gfx908", "gfx90a"],
help="\t\tList metrics can be customized to analyze on specific arch:\n\t\t gfx906\n\t\t gfx908\n\t\t gfx90a",
)
analyze_group.add_argument(
"-b",
"--metric",
dest="filter_metrics",
metavar="",
nargs="+",
help="\t\tSpecify IP block/metric id(s) from --list-metrics for filtering.",
help="\t\tList all availible metrics for analysis on specified arch:\n\t\t gfx906\n\t\t gfx908\n\t\t gfx90a",
)
analyze_group.add_argument(
"-k",
@@ -437,6 +411,14 @@ def parse(my_parser):
action="append",
help="\t\tSpecify dispatch id(s) for filtering.",
)
analyze_group.add_argument(
"-b",
"--metric",
dest="filter_metrics",
metavar="",
nargs="+",
help="\t\tSpecify IP block/metric id(s) from --list-metrics for filtering.",
)
analyze_group.add_argument(
"--gpu-id",
dest="gpu_id",
@@ -445,62 +427,11 @@ def parse(my_parser):
help="\t\tSpecify GPU id(s) for filtering.",
)
analyze_group.add_argument(
"-n",
"--normal-unit",
dest="normal_unit",
"-o",
"--output",
metavar="",
default="per_wave",
choices=["per_wave", "per_cycle", "per_second", "per_kernel"],
help="\t\tSpecify the normalization unit: (DEFAULT: per_wave)\n\t\t per_wave\n\t\t per_cycle\n\t\t per_second\n\t\t per_kernel",
)
analyze_group.add_argument(
"--max-kernel-num",
dest="max_kernel_num",
metavar="",
type=int,
default=10,
help="\t\tSpecify the maximum number of kernels shown (DEFAULT: 10)",
)
analyze_group.add_argument(
"--config-dir",
dest="config_dir",
metavar="",
help="\t\tSpecify the directory of customized configs.",
default=OMNIPERF_HOME.joinpath("omniperf_analyze/configs"),
)
analyze_group.add_argument(
"-t",
"--time-unit",
dest="time_unit",
metavar="",
default="ns",
choices=["s", "ms", "us", "ns"],
help="\t\tSpecify display time unit in kernel top stats: (DEFAULT: ns)\n\t\t s\n\t\t ms\n\t\t us\n\t\t ns",
)
analyze_group.add_argument(
"--decimal",
type=int,
metavar="",
default=2,
help="\t\tSpecify the decimal to display. (DEFAULT: 2)",
)
analyze_group.add_argument(
"--save-dfs",
dest="df_file_dir",
metavar="",
help="\t\tSpecify the dirctory to save analysis dataframe csv files.",
)
analyze_group.add_argument(
"--cols",
type=int,
dest="cols",
metavar="",
nargs="+",
help="\t\tSpecify column indices to display.",
)
analyze_group.add_argument("-g", action="store_true", help="\t\tDebug single metric.")
analyze_group.add_argument(
"--dependency", action="store_true", help="\t\tList the installation dependency."
dest="output_file",
help="\t\tSpecify an output file to save analysis results.",
)
analyze_group.add_argument(
"--gui",
@@ -509,19 +440,77 @@ def parse(my_parser):
const=8050,
help="\t\tActivate a GUI to interate with Omniperf metrics.\n\t\tOptionally, specify port to launch application (DEFAULT: 8050)",
)
analyze_group.add_argument(
analyze_advanced_group.add_argument(
"--random-port",
action="store_true",
help="\t\tRandomly generate a port to launch GUI application.\n\t\tRegistered Ports range inclusive (1024-49151).",
)
analyze_group.add_argument(
"--kernelVerbose",
analyze_advanced_group.add_argument(
"--max-kernel-num",
dest="max_kernel_num",
metavar="",
type=int,
default=10,
help="\t\tSpecify the maximum number of kernels shown in \"Top Stats\" table (DEFAULT: 10)",
)
analyze_advanced_group.add_argument(
"-n",
"--normal-unit",
dest="normal_unit",
metavar="",
default="per_wave",
choices=["per_wave", "per_cycle", "per_second", "per_kernel"],
help="\t\tSpecify the normalization unit: (DEFAULT: per_wave)\n\t\t per_wave\n\t\t per_cycle\n\t\t per_second\n\t\t per_kernel",
)
analyze_advanced_group.add_argument(
"-t",
"--time-unit",
dest="time_unit",
metavar="",
default="ns",
choices=["s", "ms", "us", "ns"],
help="\t\tSpecify display time unit in kernel top stats: (DEFAULT: ns)\n\t\t s\n\t\t ms\n\t\t us\n\t\t ns",
)
analyze_advanced_group.add_argument(
"--decimal",
type=int,
metavar="",
default=2,
help="\t\tSpecify desired decimal precision of analysis results. (DEFAULT: 2)",
)
analyze_advanced_group.add_argument(
"--config-dir",
dest="config_dir",
metavar="",
help="\t\tSpecify the directory of customized configs.",
default=omniperf_home.joinpath("omniperf_analyze/configs"),
)
analyze_advanced_group.add_argument(
"--save-dfs",
dest="df_file_dir",
metavar="",
help="\t\tSpecify the dirctory to save analysis dataframe csv files.",
)
analyze_advanced_group.add_argument(
"--cols",
type=int,
dest="cols",
metavar="",
nargs="+",
help="\t\tSpecify column indices to display.",
)
analyze_advanced_group.add_argument("-g", action="store_true", help="\t\tDebug single metric.")
analyze_advanced_group.add_argument(
"--dependency", action="store_true", help="\t\tList the installation dependency."
)
analyze_advanced_group.add_argument(
"--kernel-verbose",
required=False,
metavar="",
help="\t\tSpecify Kernel Name verbose level 1-5. Lower the level, shorter the kernel name. (DEFAULT: 5) (DISABLE: 5)",
default=5,
type=int,
)
analyze_group.add_argument(
analyze_advanced_group.add_argument(
"--report-diff", default=0, nargs="?", type=int, help=argparse.SUPPRESS
)
)
-110
View File
@@ -1,110 +0,0 @@
##############################################################################bl
# MIT License
#
# Copyright (c) 2021 - 2023 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.
##############################################################################el
import os
import sys
import io
from pathlib import Path
import subprocess
import shutil
OMNIPERF_HOME = Path(__file__).resolve().parent
# OMNIPERF INFO
PROG = "omniperf"
SOC_LIST = ["mi50", "mi100", "mi200", "vega10"]
DISTRO_MAP = {"platform:el8": "rhel8", "15.3": "sle15sp3", "20.04": "ubuntu20_04"}
def resolve_rocprof(returnPath=False):
# ROCPROF INFO
if not "ROCPROF" in os.environ.keys():
rocprof_cmd = "rocprof"
else:
rocprof_cmd = os.environ["ROCPROF"]
rocprof_path = shutil.which(rocprof_cmd)
if not rocprof_path:
print("\nError: Unable to resolve path to %s binary" % rocprof_cmd)
print(
"Please verify installation or set ROCPROF environment variable with full path."
)
sys.exit(1)
else:
# Resolve any sym links in file path
rocprof_path = os.path.realpath(rocprof_path.rstrip("\n"))
print("ROC Profiler: ", rocprof_path)
if returnPath:
return rocprof_path
else:
return rocprof_cmd
def getVersion():
# symantic version info
version = os.path.join(OMNIPERF_HOME.parent, "VERSION")
try:
with open(version, "r") as file:
VER = file.read().replace("\n", "")
except EnvironmentError:
print("ERROR: Cannot find VERSION file at {}".format(version))
sys.exit(1)
# git version info
gitDir = os.path.join(OMNIPERF_HOME.parent, ".git")
if (shutil.which("git") is not None) and os.path.exists(gitDir):
gitQuery = subprocess.run(
["git", "log", "--pretty=format:%h", "-n", "1"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
if gitQuery.returncode != 0:
SHA = "unknown"
MODE = "unknown"
else:
SHA = gitQuery.stdout.decode("utf-8")
MODE = "dev"
else:
shaFile = os.path.join(OMNIPERF_HOME.parent, "VERSION.sha")
try:
with open(shaFile, "r") as file:
SHA = file.read().replace("\n", "")
except EnvironmentError:
print("ERROR: Cannot find VERSION.sha file at {}".format(shaFile))
sys.exit(1)
MODE = "release"
versionData = {"version": VER, "sha": SHA, "mode": MODE}
return versionData
def getVersionDisplay(version, sha, mode):
buf = io.StringIO()
print("-" * 40, file=buf)
print("Omniperf version: %s (%s)" % (version, mode), file=buf)
print("Git revision: %s" % sha, file=buf)
print("-" * 40, file=buf)
return buf.getvalue()
+4 -18
View File
@@ -22,22 +22,8 @@
# SOFTWARE.
##############################################################################el
from abc import ABC, abstractmethod
class OmniSoC_Base():
def __init__(self,args):
self.__args = args
# Required methods to be implemented by child classes
@abstractmethod
def profiling_setup(self):
"""Perform any SoC-specific setup prior to profiling.
"""
pass
@abstractmethod
def analysis_setup(self):
"""Perform any SoC-specific setup prior to analysis.
"""
pass
from pathlib import Path
# NB: Creating a new module to share global vars across modules
omniperf_home = Path(__file__).resolve().parent
prog = "omniperf"
+13 -855
View File
@@ -24,866 +24,24 @@
# SOFTWARE.
##############################################################################el
import sys
import os
import io
import selectors
import argparse
import subprocess
import glob
import pandas as pd
from datetime import datetime
from pathlib import Path as path
import warnings
import shutil
from parser import parse
from utils import specs
from utils.perfagg import perfmon_filter, pmc_filter, pmc_perf_split, join_prof
from utils import remove_workload
from utils import csv_processor # Import workload
from omniperf_analyze.omniperf_analyze import roofline_only # Standalone roofline
from omniperf_analyze.omniperf_analyze import analyze # CLI analysis
from common import resolve_rocprof
from common import (
OMNIPERF_HOME,
PROG,
SOC_LIST,
DISTRO_MAP,
) # Import global variables
from common import getVersion
################################################
# Helper Functions
################################################
def run_subprocess(cmd):
subprocess.run(cmd, check=True)
def capture_subprocess_output(subprocess_args):
# Start subprocess
# bufsize = 1 means output is line buffered
# universal_newlines = True is required for line buffering
process = subprocess.Popen(subprocess_args,
bufsize=1,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
# Create callback function for process output
buf = io.StringIO()
def handle_output(stream, mask):
# Because the process' output is line buffered, there's only ever one
# line to read when this function is called
line = stream.readline()
buf.write(line)
sys.stdout.write(line)
# Register callback for an "available for read" event from subprocess' stdout stream
selector = selectors.DefaultSelector()
selector.register(process.stdout, selectors.EVENT_READ, handle_output)
# Loop until subprocess is terminated
while process.poll() is None:
# Wait for events and handle them with their registered callbacks
events = selector.select()
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
# Get process return code
return_code = process.wait()
selector.close()
success = (return_code == 0)
# Store buffered output
output = buf.getvalue()
buf.close()
return (success, output)
def get_soc():
mspec = specs.get_machine_specs(0)
target = ""
if mspec.GPU == "gfx906":
target = "mi50"
elif mspec.GPU == "gfx908":
target = "mi100"
elif mspec.GPU == "gfx90a":
target = "mi200"
elif mspec.GPU == "gfx900":
target = "vega10"
else:
print("\nInvalid SoC")
sys.exit(0)
return target
def throw_parse_error(my_parser, message):
my_parser.print_help(sys.stderr)
print("\n\n")
my_parser.error(message)
def isWorkloadEmpty(my_parser, path):
if os.path.isfile(path + "/pmc_perf.csv"):
temp_df = pd.read_csv(path + "/pmc_perf.csv")
if temp_df.dropna().empty:
print("Profiling Error: Found empty cells. Profiling data could be corrupt.")
sys.exit(0)
else:
throw_parse_error(
my_parser, "Profling Error: Cannot find pmc_perf.csv in {}".format(path)
)
def replace_timestamps(workload_dir, log_file):
df_stamps = pd.read_csv(workload_dir + "/timestamps.csv")
if "BeginNs" in df_stamps.columns and "EndNs" in df_stamps.columns:
# Update timestamps for all *.csv output files
for fname in glob.glob(workload_dir + "/" + "*.csv"):
df_pmc_perf = pd.read_csv(fname)
df_pmc_perf["BeginNs"] = df_stamps["BeginNs"]
df_pmc_perf["EndNs"] = df_stamps["EndNs"]
df_pmc_perf.to_csv(fname, index=False)
else:
warning = "WARNING: Incomplete profiling data detected. Unable to update timestamps."
warnings.warn(
warning
)
log_file.write(warning + "\n")
def gen_sysinfo(workload_name, workload_dir, ip_blocks, app_cmd, skip_roof):
# Record system information
mspec = specs.get_machine_specs(0)
sysinfo = open(workload_dir + "/" + "sysinfo.csv", "w")
# write header
header = "workload_name,"
header += "command,"
header += "host_name,host_cpu,host_distro,host_kernel,host_rocmver,date,"
header += "gpu_soc,numSE,numCU,numSIMD,waveSize,maxWavesPerCU,maxWorkgroupSize,"
header += "L1,L2,sclk,mclk,cur_sclk,cur_mclk,L2Banks,LDSBanks,name,numSQC,hbmBW,"
header += "ip_blocks\n"
sysinfo.write(header)
# timestamp
now = datetime.now()
local_now = now.astimezone()
local_tz = local_now.tzinfo
local_tzname = local_tz.tzname(local_now)
timestamp = now.strftime("%c") + " (" + local_tzname + ")"
# host info
param = [workload_name]
param += ['"' + app_cmd + '"']
param += [
mspec.hostname,
mspec.cpu,
mspec.distro,
mspec.kernel,
mspec.rocmversion,
timestamp,
]
# GPU info
param += [
mspec.GPU,
mspec.SE,
mspec.CU,
mspec.SIMD,
mspec.wave_size,
mspec.wave_occu,
mspec.workgroup_size,
]
param += [
mspec.L1,
mspec.L2,
mspec.SCLK,
mspec.cur_MCLK,
mspec.cur_SCLK,
mspec.cur_MCLK,
]
blocks = []
hbmBW = int(mspec.cur_MCLK) / 1000 * 4096 / 8 * 2
if mspec.GPU == "gfx906":
param += ["16", "32", "mi50", str(int(mspec.CU) // 4), str(hbmBW)]
elif mspec.GPU == "gfx908":
param += ["32", "32", "mi100", "48", str(hbmBW)]
elif mspec.GPU == "gfx90a":
param += ["32", "32", "mi200", "56", str(hbmBW)]
if not skip_roof:
blocks.append("roofline")
# ip block info
if ip_blocks == None:
t = ["SQ", "LDS", "SQC", "TA", "TD", "TCP", "TCC", "SPI", "CPC", "CPF"]
blocks += t
else:
blocks += ip_blocks
param.append("|".join(blocks))
sysinfo.write(",".join(param))
sysinfo.close()
def mongo_import(args, profileAndImport):
# Validate target directory
connectionInfo = csv_processor.parse(args, profileAndImport)
# Convert and upload data
print("-- Conversion & Upload in Progress --")
csv_processor.convert_folder(connectionInfo)
print("-- Complete! --")
################################################
# Roofline Helpers
################################################
def roof_setup(args, my_parser, VER):
if args.path == os.getcwd() + "/workloads":
args.path += "/" + args.name + "/" + args.target
# Do we need a new directory for roofline?
if not os.path.isdir(args.path):
os.makedirs(args.path)
# Does roof data exist?
print("Checking for roofline.csv in ", args.path)
roof_path = args.path + "/roofline.csv"
roofline_exists = os.path.isfile(roof_path)
if not roofline_exists:
if get_soc() != "mi200":
throw_parse_error(
my_parser, "Invalid SoC.\nRoofline only availible on MI200."
)
mibench(args)
# Does sysinfo exist?
print("Checking for sysinfo.csv in ", args.path)
sysinfo_path = args.path + "/sysinfo.csv"
sysinfo_exists = os.path.isfile(sysinfo_path)
if not sysinfo_exists:
print("sysinfo not found")
gen_sysinfo(args.name, args.path, [], args.remaining, False)
# Does app data exist?
print("Checking for pmc_perf.csv in ", args.path)
app_path = args.path + "/pmc_perf.csv"
app_exists = os.path.isfile(app_path)
if not app_exists:
if get_soc() != "mi200":
throw_parse_error(
my_parser, "Invalid SoC.\nRoofline only availible on MI200."
)
if not args.remaining:
throw_parse_error(
my_parser,
"Cannot find existing application data.\nAttempting to generate application data from -- <app_cmd>.\n-- <app_cmd> option is required to generate application data.",
)
else:
characterize_app(args, VER)
def detect_roofline():
mspec = specs.get_machine_specs(0)
rocm_ver = mspec.rocmversion[:1]
os_release = path("/etc/os-release").read_text()
ubuntu_distro = specs.search(r'VERSION_ID="(.*?)"', os_release)
rhel_distro = specs.search(r'PLATFORM_ID="(.*?)"', os_release)
sles_distro = specs.search(r'VERSION_ID="(.*?)"', os_release)
if "ROOFLINE_BIN" in os.environ.keys():
rooflineBinary = os.environ["ROOFLINE_BIN"]
if os.path.exists(rooflineBinary):
print("Detected user-supplied binary")
return {"rocm_ver": "override", "distro": "override", "path": rooflineBinary}
else:
print("ROOFLINE ERROR: user-supplied path to binary not accessible")
print("--> ROOFLINE_BIN = %s\n" % target_binary)
sys.exit(1)
elif rhel_distro == "platform:el8":
# Must be a valid RHEL machine
distro = rhel_distro
elif (
(type(sles_distro) == str and len(sles_distro) >= 3) and # confirm string and len
sles_distro[:2] == "15" and int(sles_distro[3]) >= 3 # SLES15 and SP >= 3
):
# Must be a valid SLES machine
# Use SP3 binary for all forward compatible service pack versions
distro = "15.3"
elif ubuntu_distro == "20.04":
# Must be a valid Ubuntu machine
distro = ubuntu_distro
else:
print("ROOFLINE ERROR: Cannot find a valid binary for your operating system")
sys.exit(1)
target_binary = {"rocm_ver": rocm_ver, "distro": distro}
return target_binary
def mibench(args):
print("No roofline data found. Generating...")
target_binary = detect_roofline()
if target_binary["rocm_ver"] == "override":
path_to_binary = target_binary["path"]
else:
path_to_binary = (
str(OMNIPERF_HOME)
+ "/utils/rooflines/roofline"
+ "-"
+ DISTRO_MAP[target_binary["distro"]]
+ "-"
+ args.target.lower()
+ "-rocm"
+ target_binary["rocm_ver"]
)
# Distro is valid but cant find rocm ver
if not os.path.exists(path_to_binary):
print("ROOFLINE ERROR: Unable to locate expected binary (%s)." % path_to_binary)
sys.exit(1)
run_subprocess(
[
path_to_binary,
"-o",
args.path + "/" + "roofline.csv",
"-d",
str(args.device),
]
)
def characterize_app(args, VER):
# Basic Info
print("\n", PROG, "ver: ", VER)
print("Path: ", args.path)
print("Target: ", args.target)
print("Command: ", args.remaining)
print("Kernel Selection: ", args.kernel)
print("Dispatch Selection: ", args.dispatch)
perfmon_dir = str(OMNIPERF_HOME) + "/perfmon_pub"
print("permon dir is ", os.path.abspath(perfmon_dir))
app_cmd = args.remaining
workload_dir = args.path
# Perfmon filtering
pmc_filter(workload_dir, perfmon_dir, args.target)
# Separate pmc_perf runs
pmc_perf_split(workload_dir)
# Set up a log file
log = open(workload_dir + "/log.txt", "w")
print("Log: ", workload_dir + "/log.txt\n")
# Workload profiling
for fname in glob.glob(workload_dir + "/perfmon/*.txt"):
# Kernel filtering (in-place replacement)
if not args.kernel == None:
success, output = capture_subprocess_output(
[
"sed",
"-i",
"-r",
"s%^(kernel:).*%" + "kernel: " + ",".join(args.kernel) + "%g",
fname,
]
)
log.write(output)
# Dispatch filtering (inplace replacement)
if not args.dispatch == None:
success, output = capture_subprocess_output(
[
"sed",
"-i",
"-r",
"s%^(range:).*%" + "range: " + " ".join(args.dispatch) + "%g",
fname,
]
)
log.write(output)
print(fname)
if args.use_rocscope == True:
run_rocscope(args, fname)
else:
run_prof(fname, workload_dir, perfmon_dir, app_cmd, args.target, log, args.verbose)
# Update timestamps
replace_timestamps(workload_dir, log)
if args.use_rocscope == False:
# Manually join each pmc_perf*.csv output
join_prof(workload_dir, args.join_type, log, args.verbose)
# Demangle and overwrite original KernelNames
csv_processor.kernel_name_shortener(workload_dir, args.kernelVerbose)
log.close()
################################################
# Profiling Helpers
################################################
def run_rocscope(args, fname):
# profile the app
if args.use_rocscope == True:
result = shutil.which("rocscope")
if result:
rs_cmd = [
result.stdout.decode("ascii").strip(),
"metrics",
"-p",
args.path,
"-n",
args.name,
"-t",
fname,
"--",
]
for i in args.remaining.split():
rs_cmd.append(i)
print(rs_cmd)
result = run_subprocess(
rs_cmd
) # , stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(result.stderr.decode("ascii"))
sys.exit(1)
def run_prof(fname, workload_dir, perfmon_dir, cmd, target, log_file, verbose):
global rocprof_cmd
fbase = os.path.splitext(os.path.basename(fname))[0]
if verbose:
print("pmc file:", os.path.basename(fname))
# profile the app (run w/ custom config files for mi100)
if target == "mi100":
print("RUNNING WITH CUSTOM METRICS")
success, output = capture_subprocess_output(
[
rocprof_cmd,
"-i",
fname,
"-m",
perfmon_dir + "/" + "metrics.xml",
"--timestamp",
"on",
"-o",
workload_dir + "/" + fbase + ".csv",
'"' + cmd + '"',
]
)
else:
success, output = capture_subprocess_output(
[
rocprof_cmd,
"-i",
fname,
"--timestamp",
"on",
"-o",
workload_dir + "/" + fbase + ".csv",
'"' + cmd + '"',
]
)
# Write output to log
log_file.write(output)
def omniperf_profile(args, VER):
# Verify valid name
if args.name.find(".") != -1 or args.name.find("-") != -1:
raise ValueError("'-' and '.' are not permited in workload name", args.name)
# Basic Info
print(PROG, "ver: ", VER)
print("Path: ", args.path)
print("Target: ", args.target)
print("Command: ", args.remaining)
print("Kernel Selection: ", args.kernel)
print("Dispatch Selection: ", args.dispatch)
if args.ipblocks == None:
print("IP Blocks: All")
else:
print("IP Blocks: ", args.ipblocks)
if args.kernelVerbose > 5:
print("KernelName verbose level: DISABLED")
else:
print("KernelName verbose level: ", str(args.kernelVerbose))
# Set up directories
if args.path == os.getcwd() + "/workloads":
workload_dir = args.path + "/" + args.name + "/" + args.target
else:
workload_dir = args.path
perfmon_dir = str(OMNIPERF_HOME) + "/perfmon_pub"
# Perfmon filtering
perfmon_filter(workload_dir, perfmon_dir, args)
# Separate pmc_perf runs
pmc_perf_split(workload_dir)
# Set up a log file
log = open(workload_dir + "/log.txt", "w")
print("Log: ", workload_dir + "/log.txt\n")
if not args.lucky == None and args.lucky == True:
print("You're feeling lucky - only profiling top N kernels")
# look for whether workload_dir exists - create if not
try:
os.makedirs(workload_dir, exist_ok=True)
except Exception as e:
print("Unable to create workload directory: ", workload_dir)
print(e)
sys.exit(1)
result = shutil.which("rocscope")
if result:
rs_cmd = [
result.stdout.decode("ascii").strip(),
"top10",
"-p",
args.path,
"-n",
args.name,
"--",
]
for i in args.remaining.split():
rs_cmd.append(i)
print(rs_cmd)
result = run_subprocess(
rs_cmd
) # , stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(result.stderr.decode("ascii"))
else:
print("rocscope must be in the PATH")
sys.exit(1)
elif not args.summaries == None and args.summaries == True:
print("creating kernel summaries")
# look for whether workload_dir exists - create if not
try:
os.makedirs(workload_dir, exist_ok=True)
except Exception as e:
print("Unable to create workload directory: ", workload_dir)
print(e)
sys.exit(1)
result = shutil.which("rocscope")
if result:
rs_cmd = [
result.stdout.decode("ascii").strip(),
"summary",
"-p",
args.path,
"-n",
args.name,
"--",
]
for i in args.remaining.split():
rs_cmd.append(i)
print(rs_cmd)
result = run_subprocess(
rs_cmd
) # , stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(result.stderr.decode("ascii"))
else:
print("rocscope must be in the PATH")
sys.exit(1)
else:
for fname in glob.glob(workload_dir + "/perfmon/*.txt"):
# Kernel filtering (in-place replacement)
if not args.kernel == None:
success, output = capture_subprocess_output(
[
"sed",
"-i",
"-r",
"s%^(kernel:).*%" + "kernel: " + ",".join(args.kernel) + "%g",
fname,
]
)
log.write(output)
# Dispatch filtering (inplace replacement)
if not args.dispatch == None:
success, output = capture_subprocess_output(
[
"sed",
"-i",
"-r",
"s%^(range:).*%" + "range: " + " ".join(args.dispatch) + "%g",
fname,
]
)
log.write(output)
print(fname)
if args.use_rocscope == True:
run_rocscope(args, fname)
else:
run_prof(fname, workload_dir, perfmon_dir, args.remaining, args.target, log, args.verbose)
# Update timestamps
replace_timestamps(workload_dir, log)
if args.use_rocscope == False:
# Manually join each pmc_perf*.csv output
join_prof(workload_dir, args.join_type, log, args.verbose)
# Demangle and overwrite original KernelNames
csv_processor.kernel_name_shortener(workload_dir, args.kernelVerbose)
# Generate sysinfo
gen_sysinfo(args.name, workload_dir, args.ipblocks, args.remaining, args.no_roof)
# Add tracing & roofline metrics (mi200 only)
if args.target.lower() == "mi200":
# Skip roofline if --no-roof is set.
if not args.no_roof:
target_binary = detect_roofline()
if target_binary["rocm_ver"] == "override":
path_to_binary = target_binary["path"]
else:
path_to_binary = (
str(OMNIPERF_HOME)
+ "/utils/rooflines/roofline"
+ "-"
+ DISTRO_MAP[target_binary["distro"]]
+ "-"
+ args.target.lower()
+ "-rocm"
+ target_binary["rocm_ver"]
)
# Distro is valid but cant find valid binary
if not os.path.exists(path_to_binary):
print(
"ROOFLINE ERROR: Unable to locate expected binary (%s))."
% path_to_binary
)
sys.exit(1)
run_subprocess(
[
path_to_binary,
"-o",
workload_dir + "/" + "roofline.csv",
"-d",
str(args.device),
]
)
# Close log
log.close()
################################################
# MAIN
################################################
from omniperf_base import Omniperf
def main():
my_parser = argparse.ArgumentParser(
description="Command line interface for AMD's GPU profiler, Omniperf",
prog="tool",
formatter_class=lambda prog: argparse.RawTextHelpFormatter(
prog, max_help_position=30
),
usage="omniperf [mode] [options]",
)
parse(my_parser)
args = my_parser.parse_args()
vData = getVersion()
VER = vData["version"]
omniperf = Omniperf()
omniperf.parse_args() #TODO: We already do this in the __init__ for Omniperf(). Change that?
if args.mode == None:
throw_parse_error(
my_parser,
"Omniperf requires you pass a valid mode. Please see documentation.",
)
##############
# PROFILE MODE
##############
if args.mode == "profile":
Extractionlvl = args.kernelVerbose
print("Resolving rocprof")
global rocprof_cmd
rocprof_cmd = resolve_rocprof()
# Cannot access parent directories
if ".." in str(args.path):
throw_parse_error(
my_parser, "Access denied. Cannot access parent directories in path ../"
)
# Must have a valid soc in profile mode
args.target = get_soc()
# Verify correct command formatting
args.remaining = args.remaining[1:]
if args.remaining:
if not os.path.isfile(args.remaining[0]):
throw_parse_error(
my_parser,
'Your command "{}" doesn\'t point to a file. Try again.'.format(
args.remaining[0]
),
)
args.remaining = " ".join(args.remaining)
else:
throw_parse_error(
my_parser,
"Profiling command required. Pass application executable after -- at the end of options.\n\ti.e. omniperf profile -n vcopy -- ./vcopy 1048576 256",
)
# Name cannot exceed MongoDB max len
if len(args.name) > 35:
throw_parse_error(my_parser, "--name exceeds 35 character limit. Try again.")
elif args.roof_only:
print("\n--------\nRoofline only\n--------\n")
# Setup prerequisits for roofline
roof_setup(args, my_parser, VER)
# Generate roofline
roofline_only(args.path, args.device, args.sort, args.mem_level, args.kernel_names, args.verbose)
# Profile only
else:
print("\n-------------\nProfile only\n-------------\n")
omniperf_profile(args, VER)
##############
# DATABASE MODE
##############
if args.mode == "database":
# Remove a workload
if args.remove and not args.upload:
print("\n--------\nRemove workload\n--------\n")
fullWorkloadName = args.workload.count("_") >= 3
if not fullWorkloadName:
throw_parse_error(
my_parser,
"--workload is not valid. Please use full workload name as seen in GUI when removing (i.e. omniperf_asw_vcopy_mi200)",
)
if args.host == None or args.username == None:
throw_parse_error(
my_parser, "--host and --username are required when --remove is set."
)
remove_workload.remove_workload(args)
# Import a workload
elif args.upload and not args.remove:
print("\n--------\nImport Profiling Results\n--------\n")
if (
args.host == None
or args.team == None
or args.username == None
or args.workload == None
):
throw_parse_error(
my_parser,
"--host, --workload, --username, and --team are all required when --import is set.",
)
if os.path.isdir(os.path.abspath(args.workload)):
isWorkloadEmpty(
my_parser, args.workload
) # Throw warning if workload is empty
else:
throw_parse_error(
my_parser,
"--workload is invalid. Please pass path to a valid directory.",
)
if len(args.team) > 13:
throw_parse_error(
my_parser, "--team exceeds 13 character limit. Try again."
)
args.workload = os.path.abspath(args.workload) # Format path properly
mongo_import(args, False)
else:
throw_parse_error(
my_parser, "Pass either -i/--import or -r/--remove when import mode"
)
##############
# ANALYZE MODE
##############
if args.mode == "analyze":
if args.list_metrics:
analyze(args)
else:
if args.path:
if ".." in str(args.path):
throw_parse_error(
my_parser,
"Access denied. Cannot access parent directories in path ../",
)
if args.filter_metrics and args.gui:
throw_parse_error(
my_parser,
"""
omniperf analyze --path <workload_path> [analyze options]
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf analyze -p workloads/vcopy/mi200/ --list-metrics gfx90a
\n\tomniperf analyze -p workloads/mixbench/mi200/ --filter-dispatch-ids 12 34 --decimal 3
\n\tomniperf analyze -p workloads/mixbench/mi200/ --gui
\n-------------------------------------------------------------------------------\n
\ntool: error: --gui cannot be used in combination with: -b/--metric
"""
)
print("\n--------\nAnalyze\n--------\n")
# Ensure absolute path
for dir in args.path:
full_path = os.path.abspath(dir[0])
dir[0] = full_path
if not os.path.isdir(dir[0]):
throw_parse_error(
my_parser,
"Error: invalid directory {}\nPlease try again.".format(
dir[0]
),
)
isWorkloadEmpty(
my_parser, dir[0]
) # Verify workload is valid before analyzing
analyze(args)
else:
throw_parse_error(
my_parser,
"""
omniperf analyze --path <workload_path> [analyze options]
\n\n-------------------------------------------------------------------------------
\nExamples:
\n\tomniperf analyze -p workloads/vcopy/mi200/ --list-metrics gfx90a
\n\tomniperf analyze -p workloads/mixbench/mi200/ --filter-dispatch-ids 12 34 --decimal 3
\n\tomniperf analyze -p workloads/mixbench/mi200/ --gui
\n-------------------------------------------------------------------------------\n
\ntool: error: the following arguments are required: -p/--path
""",
)
sys.exit(0) # Indicate successful on exit
mode = omniperf.get_mode()
# major omniperf execution modes
if mode == "profile":
omniperf.run_profiler()
elif mode == "database":
omniperf.update_DB()
elif mode == "analyze":
omniperf.run_analysis()
else:
omniperf.error("Unsupported execution mode")
if __name__ == "__main__":
main()
-47
View File
@@ -1,47 +0,0 @@
#!/usr/bin/env python3
##############################################################################bl
# MIT License
#
# Copyright (c) 2021 - 2023 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.
##############################################################################el
from omniperf_base import Omniperf
def main():
omniperf2 = Omniperf()
omniperf2.parseArgs()
mode = omniperf2.get_mode()
# major omniperf execution modes
if mode == "profile":
omniperf2.run_profiler()
elif mode == "database":
omniperf2.update_DB()
elif mode == "analyze":
omniperf2.run_analysis()
else:
omniperf2.error("Unsupported execution mode")
if __name__ == "__main__":
main()

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Some files were not shown because too many files have changed in this diff Show More