[rocprof-compute] Update Formatting (#671)

* updated rocprof-compute formatting

* fixed ammolite peak variables in parser.py

* format parser.py

* update formatting rocprof_compute_base
このコミットが含まれているのは:
jamessiddeley-amd
2025-08-22 12:22:17 -04:00
committed by GitHub
コミット 5deeea71df
13個のファイルの変更、112行の追加、70行の削除
+2 -1
ファイルの表示
@@ -228,7 +228,8 @@ Examples:
"--set", "--set",
default=None, default=None,
dest="set_selected", dest="set_selected",
help="\t\t\tProfile a set of metrics of topic of interest by collecting counters in a single pass.\n\t\t\tFor available sets, see --list-sets", help="\t\t\tProfile a set of metrics of topic of interest by collecting "
"counters in a single pass.\n\t\t\tFor available sets, see --list-sets",
) )
profile_group.add_argument( profile_group.add_argument(
+12 -15
ファイルの表示
@@ -169,14 +169,17 @@ class OmniAnalyze_Base:
if self.__args.list_metrics: if self.__args.list_metrics:
self.list_metrics() self.list_metrics()
# load required configs def get_sysinfo_path(data_path):
for d in self.__args.path: return (
sysinfo_path = ( Path(data_path)
Path(d[0])
if self.__args.nodes is None if self.__args.nodes is None
and self.__args.spatial_multiplexing is not True and self.__args.spatial_multiplexing is not True
else file_io.find_1st_sub_dir(d[0]) else file_io.find_1st_sub_dir(data_path)
) )
# load required configs
for d in self.__args.path:
sysinfo_path = get_sysinfo_path(d[0])
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv")) sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
arch = sys_info.iloc[0]["gpu_arch"] arch = sys_info.iloc[0]["gpu_arch"]
args = self.__args args = self.__args
@@ -196,20 +199,14 @@ class OmniAnalyze_Base:
# For regular single node case, load sysinfo.csv directly # For regular single node case, load sysinfo.csv directly
# For multi-node, either the default "all", or specified some, # For multi-node, either the default "all", or specified some,
# pick up the one in the 1st sub_dir. We could fix it properly later. # pick up the one in the 1st sub_dir. We could fix it properly later.
sysinfo_path = ( w = schema.Workload()
Path(d[0]) sysinfo_path = get_sysinfo_path(d[0])
if self.__args.nodes is None
and self.__args.spatial_multiplexing is not True
else file_io.find_1st_sub_dir(d[0])
)
w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv")) w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
if not getattr(self.get_args(), "no_roof", False): if not getattr(self.get_args(), "no_roof", False):
try: try:
roofline_path = sysinfo_path.joinpath("roofline.csv") roofline_csv_path = sysinfo_path / "roofline.csv"
roofline_df = pd.read_csv(roofline_path) roofline_df = pd.read_csv(roofline_csv_path)
# use original column names from roofline.csv directly
w.roofline_peaks = roofline_df w.roofline_peaks = roofline_df
except FileNotFoundError: except FileNotFoundError:
+7 -5
ファイルの表示
@@ -229,9 +229,9 @@ class RocProfCompute:
arch = self.__args.list_metrics arch = self.__args.list_metrics
if arch in self.__supported_archs.keys(): if arch in self.__supported_archs.keys():
ac = schema.ArchConfig() ac = schema.ArchConfig()
ac.panel_configs = file_io.load_panel_configs( ac.panel_configs = file_io.load_panel_configs([
[self.__args.config_dir.joinpath(arch)] self.__args.config_dir.joinpath(arch)
) ])
sys_info = self.__mspec.get_class_members().iloc[0] sys_info = self.__mspec.get_class_members().iloc[0]
parser.build_dfs(archConfigs=ac, filter_metrics=[], sys_info=sys_info) parser.build_dfs(archConfigs=ac, filter_metrics=[], sys_info=sys_info)
for key, value in ac.metric_list.items(): for key, value in ac.metric_list.items():
@@ -259,7 +259,8 @@ class RocProfCompute:
# Print header # Print header
print( print(
f"{'Set Option':<35} {'Set Title':<35} {'Metric Name':<30} {'Metric ID':<10}" f"{'Set Option':<35} {'Set Title':<35}"
f" {'Metric Name':<30} {'Metric ID':<10}"
) )
print("-" * 115) print("-" * 115)
@@ -279,7 +280,8 @@ class RocProfCompute:
title_display = title if first_row else "" title_display = title if first_row else ""
print( print(
f"{set_display:<35} {title_display:<35} {metric_name:<30} {metric_id:<10}" f"{set_display:<35} {title_display:<35}"
f" {metric_name:<30} {metric_id:<10}"
) )
first_row = False first_row = False
# Empty line between sets # Empty line between sets
+2 -1
ファイルの表示
@@ -300,7 +300,8 @@ class OmniSoC_Base:
sets_info = parse_sets_yaml(self.__arch) sets_info = parse_sets_yaml(self.__arch)
if set_selected not in set(sets_info.keys()): if set_selected not in set(sets_info.keys()):
console_error( console_error(
f"argument --set: invalid choice: '{set_selected}' (choose from {sets_info.keys()})" f"argument --set: invalid choice: '{set_selected}' "
f"(choose from {sets_info.keys()})"
) )
self.__args.filter_blocks = [ self.__args.filter_blocks = [
next(iter(metric.keys())) next(iter(metric.keys()))
+7 -2
ファイルの表示
@@ -55,7 +55,10 @@ class MainView(Horizontal):
def __init__(self): def __init__(self):
super().__init__(id="main-container") super().__init__(id="main-container")
self.start_path = Path(DEFAULT_START_PATH) if DEFAULT_START_PATH else Path.cwd() self.start_path = (
# NOTE: is cwd the best choice?
Path.cwd() if DEFAULT_START_PATH is None else Path(DEFAULT_START_PATH)
)
self.logger = Logger() self.logger = Logger()
self.logger.info("MainView initialized", update_ui=False) self.logger.info("MainView initialized", update_ui=False)
@@ -167,7 +170,9 @@ class MainView(Horizontal):
def refresh_results(self) -> None: def refresh_results(self) -> None:
kernel_view = self.query_one("#kernel-view") kernel_view = self.query_one("#kernel-view")
if kernel_view: if kernel_view:
kernel_view.update_results(self.kernel_to_df_dict, self.top_kernel_to_df_list) kernel_view.update_results(
self.kernel_to_df_dict, self.top_kernel_to_df_list
)
self.logger.success("Results displayed successfully.") self.logger.success("Results displayed successfully.")
else: else:
self.logger.error("Kernel view not found or no data available") self.logger.error("Kernel view not found or no data available")
+5 -2
ファイルの表示
@@ -51,7 +51,8 @@ def create_table(df: pd.DataFrame) -> DataTable:
def create_widget_from_data(df: pd.DataFrame, tui_style: str = None, context: str = ""): def create_widget_from_data(df: pd.DataFrame, tui_style: str = None, context: str = ""):
if df is None or df.empty: if df is None or df.empty:
return Label( return Label(
f"Data not available{f' for {context}' if context else ''}", classes="warning" f"Data not available{f' for {context}' if context else ''}",
classes="warning",
) )
match tui_style: match tui_style:
@@ -100,7 +101,9 @@ def build_section_from_config(
if isinstance(dfs, dict): if isinstance(dfs, dict):
exclude_keys = subsection_config.get("exclude_keys", []) exclude_keys = subsection_config.get("exclude_keys", [])
for section_name, subsections in dfs.items(): for section_name, subsections in dfs.items():
if section_name not in exclude_keys and isinstance(subsections, dict): if section_name not in exclude_keys and isinstance(
subsections, dict
):
kernel_children = [] kernel_children = []
for subsection_name, data in subsections.items(): for subsection_name, data in subsections.items():
if isinstance(data, dict) and "df" in data: if isinstance(data, dict) and "df" in data:
+2 -1
ファイルの表示
@@ -676,7 +676,8 @@ class Roofline:
console_log("roofline", "{} does not exist".format(roofline_csv)) console_log("roofline", "{} does not exist".format(roofline_csv))
return return
# if workload is detected, utilize Roofline yamls. If not, fallback to legacy calc_ai # if workload is detected, utilize Roofline yamls.
# If not, fallback to legacy calc_ai
if workload is not None: if workload is not None:
self.__ai_data = calc_ai_analyze( self.__ai_data = calc_ai_analyze(
workload=workload, workload=workload,
+5 -4
ファイルの表示
@@ -74,7 +74,8 @@ def load_panel_configs(dirs):
if f.endswith(".yaml"): if f.endswith(".yaml"):
with open(Path(root) / f) as file: with open(Path(root) / f) as file:
config_yml = yaml.safe_load(file) config_yml = yaml.safe_load(file)
# metric key can be None due to some metric tables not having any metrics # metric key can be None due to some metric-
# tables not having any metrics
# metric key should be empty dict instead of None # metric key should be empty dict instead of None
for data_source in config_yml["Panel Config"]["data source"]: for data_source in config_yml["Panel Config"]["data source"]:
metric_table = data_source.get("metric_table") metric_table = data_source.get("metric_table")
@@ -160,9 +161,9 @@ def create_df_kernel_top_stats(
axis=1, axis=1,
) )
grouped = time_stats.groupby(by=["Kernel_Name"]).agg( grouped = time_stats.groupby(by=["Kernel_Name"]).agg({
{"ExeTime": ["count", "sum", "mean", "median"]} "ExeTime": ["count", "sum", "mean", "median"]
) })
time_unit_str = "(" + time_unit + ")" time_unit_str = "(" + time_unit + ")"
grouped.columns = [ grouped.columns = [
+7 -6
ファイルの表示
@@ -996,7 +996,8 @@ def eval_metric(dfs, dfs_type, sys_info, empirical_peaks_df, raw_pmc_df, debug,
except TypeError: except TypeError:
console_warning( console_warning(
"Skipping entry. Encountered a missing " "Skipping entry. Encountered a missing "
"counter\n{} has been assigned to None\n{}".format( "counter\n"
"{} has been assigned to None\n{}".format(
expr, expr,
np.nan, np.nan,
) )
@@ -1024,11 +1025,10 @@ def eval_metric(dfs, dfs_type, sys_info, empirical_peaks_df, raw_pmc_df, debug,
except (TypeError, NameError) as e: except (TypeError, NameError) as e:
if "empirical_peak" in str(e): if "empirical_peak" in str(e):
console_warning( console_warning(
f"Missing empirical peak data: {e}. Using empty value." f"Missing empirical peak data: {e}. "
"Using empty value."
) )
row[expr] = "" row[expr] = ""
else:
row[expr] = ""
except AttributeError as ae: except AttributeError as ae:
if ( if (
str(ae) str(ae)
@@ -1086,7 +1086,8 @@ def apply_filters(workload, dir, is_gui, debug):
for kernel_id in workload.filter_kernel_ids: for kernel_id in workload.filter_kernel_ids:
if kernel_id >= len(kernels_df["Kernel_Name"]): if kernel_id >= len(kernels_df["Kernel_Name"]):
console_error( console_error(
"{} is an invalid kernel id. Please enter an id between 0-{}".format( "{} is an invalid kernel id. "
"Please enter an id between 0-{}".format(
kernel_id, kernel_id,
len(kernels_df["Kernel_Name"]) - 1, len(kernels_df["Kernel_Name"]) - 1,
) )
+26 -23
ファイルの表示
@@ -30,7 +30,7 @@ from pathlib import Path
import pandas as pd import pandas as pd
from utils.logger import console_debug from utils.logger import console_debug, console_warning
from utils.parser import apply_filters, eval_metric from utils.parser import apply_filters, eval_metric
################################################ ################################################
@@ -158,7 +158,8 @@ def get_color(catagory):
# Plot BW at each cache level # Plot BW at each cache level
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def calc_ceilings(roofline_parameters, dtype, benchmark_data): def calc_ceilings(roofline_parameters, dtype, benchmark_data):
"""Given benchmarking data, calculate ceilings (or peak performance) for empirical roofline""" """Given benchmarking data, calculate ceilings (or peak performance) for
empirical roofline"""
# TODO: This is where filtering by memory level will need to occur for standalone # TODO: This is where filtering by memory level will need to occur for standalone
graphPoints = {"hbm": [], "l2": [], "l1": [], "lds": [], "valu": [], "mfma": []} graphPoints = {"hbm": [], "l2": [], "l1": [], "lds": [], "valu": [], "mfma": []}
@@ -189,7 +190,7 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
if dtype in PEAK_OPS_DATATYPES: if dtype in PEAK_OPS_DATATYPES:
x2 = peakOps / peakBw x2 = peakOps / peakBw
y2 = peakOps y2 = peakOps # noqa
# Plot MFMA lines (NOTE: Assuming MI200 soc) # Plot MFMA lines (NOTE: Assuming MI200 soc)
x1_mfma = peakOps / peakBw x1_mfma = peakOps / peakBw
@@ -223,9 +224,9 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
graphPoints[cacheHierarchy[i].lower()].append([y1, peakY]) graphPoints[cacheHierarchy[i].lower()].append([y1, peakY])
graphPoints[cacheHierarchy[i].lower()].append(peakBw) graphPoints[cacheHierarchy[i].lower()].append(peakBw)
# ------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------
# Plot computing roof # Plot computing roof
# ------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------
if dtype in PEAK_OPS_DATATYPES: if dtype in PEAK_OPS_DATATYPES:
# Plot FMA roof # Plot FMA roof
x0 = XMAX x0 = XMAX
@@ -356,7 +357,11 @@ def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
console_debug( console_debug(
"roofline", "roofline",
f"Kernel {kernel_id}: AI_HBM={ai_hbm:.2f}, AI_L2={ai_l2:.2f}, AI_L1={ai_l1:.2f}, Performance={performance:.2e} GFLOP/s", f"Kernel {kernel_id}: "
f"AI_HBM={ai_hbm:.2f}, "
f"AI_L2={ai_l2:.2f}, "
f"AI_L1={ai_l1:.2f}, "
f"Performance={performance:.2e} GFLOP/s",
) )
# add to plot points if we have valid data # add to plot points if we have valid data
@@ -393,11 +398,11 @@ def calc_ai_analyze(workload, mspec, sort_type, config, arch_config):
def calc_ai_profile(mspec, sort_type, ret_df): def calc_ai_profile(mspec, sort_type, ret_df):
"""Given counter data, calculate arithmetic intensity for each kernel in the application. """Given counter data, calculate arithmetic intensity for each kernel
Leverage hard-coded equations to calculate AI values. in the application. Leverage hard-coded equations to calculate AI values.
Used during profiling stage to generate roofline PDF, since Roofline yamls are not available Used during profiling stage to generate roofline PDF, since Roofline yamls
in the profiling stage.""" are not available in the profiling stage."""
console_debug( console_debug(
"calc_ai_profile: Starting legacy roofline calculation (from roofline_calc)" "calc_ai_profile: Starting legacy roofline calculation (from roofline_calc)"
@@ -608,9 +613,7 @@ def calc_ai_profile(mspec, sort_type, ret_df):
calls += 1 calls += 1
if sort_type == "kernels" and ( if sort_type == "kernels" and (at_end or (kernelName != next_kernelName)):
at_end == True or (kernelName != next_kernelName)
):
myList.append( myList.append(
AI_Data( AI_Data(
kernelName, kernelName,
@@ -685,9 +688,8 @@ def calc_ai_profile(mspec, sort_type, ret_df):
while i < TOP_N and i != len(myList): while i < TOP_N and i != len(myList):
if myList[i].total_flops == 0: if myList[i].total_flops == 0:
console_debug( console_debug(
"No flops counted for {}, arithmetic intensities will not display on plots.".format( f"No flops counted for {myList[i].KernelName}, "
myList[i].KernelName "arithmetic intensities will not display on plots."
)
) )
kernelNames.append(myList[i].KernelName) kernelNames.append(myList[i].KernelName)
@@ -696,28 +698,28 @@ def calc_ai_profile(mspec, sort_type, ret_df):
if myList[i].L1cache_data if myList[i].L1cache_data
else intensities["ai_l1"].append(0) else intensities["ai_l1"].append(0)
) )
# print("cur_ai_L1", myList[i].total_flops/myList[i].L1cache_data) if myList[i].L1cache_data else print("null") # print("cur_ai_L1", myList[i].total_flops/myList[i].L1cache_data) if myList[i].L1cache_data else print("null") #noqa
# print() # print()
( (
intensities["ai_l2"].append(myList[i].total_flops / myList[i].L2cache_data) intensities["ai_l2"].append(myList[i].total_flops / myList[i].L2cache_data)
if myList[i].L2cache_data if myList[i].L2cache_data
else intensities["ai_l2"].append(0) else intensities["ai_l2"].append(0)
) )
# print("cur_ai_L2", myList[i].total_flops/myList[i].L2cache_data) if myList[i].L2cache_data else print("null") # print("cur_ai_L2", myList[i].total_flops/myList[i].L2cache_data) if myList[i].L2cache_data else print("null") #noqa
# print() # print()
( (
intensities["ai_hbm"].append(myList[i].total_flops / myList[i].hbm_data) intensities["ai_hbm"].append(myList[i].total_flops / myList[i].hbm_data)
if myList[i].hbm_data if myList[i].hbm_data
else intensities["ai_hbm"].append(0) else intensities["ai_hbm"].append(0)
) )
# print("cur_ai_hbm", myList[i].total_flops/myList[i].hbm_data) if myList[i].hbm_data else print("null") # print("cur_ai_hbm", myList[i].total_flops/myList[i].hbm_data) if myList[i].hbm_data else print("null") #noqa
# print() # print()
( (
curr_perf.append(myList[i].total_flops / myList[i].avgDuration) curr_perf.append(myList[i].total_flops / myList[i].avgDuration)
if myList[i].avgDuration if myList[i].avgDuration
else curr_perf.append(0) else curr_perf.append(0)
) )
# print("cur_perf", myList[i].total_flops/myList[i].avgDuration) if myList[i].avgDuration else print("null") # print("cur_perf", myList[i].total_flops/myList[i].avgDuration) if myList[i].avgDuration else print("null") #noqa
i += 1 i += 1
@@ -726,7 +728,7 @@ def calc_ai_profile(mspec, sort_type, ret_df):
for i in intensities: for i in intensities:
values = intensities[i] values = intensities[i]
color = get_color(i) color = get_color(i) # noqa
x = [] x = []
y = [] y = []
for entryIndx in range(0, len(values)): for entryIndx in range(0, len(values)):
@@ -758,7 +760,8 @@ def constuct_roof(roofline_parameters, dtype):
# ----------------------------------------------------- # -----------------------------------------------------
# Initialize roofline data dictionary from roofline.csv # Initialize roofline data dictionary from roofline.csv
# ----------------------------------------------------- # -----------------------------------------------------
benchmark_data = {} # TODO: consider changing this to an ordered dict for consistency over py versions # TODO: consider changing this to an ordered dict for consistency over py versions
benchmark_data = {}
headers = [] headers = []
try: try:
with open(benchmark_results, "r") as csvfile: with open(benchmark_results, "r") as csvfile:
@@ -776,7 +779,7 @@ def constuct_roof(roofline_parameters, dtype):
rowCount += 1 rowCount += 1
csvfile.close() csvfile.close()
except: except Exception:
graphPoints = { graphPoints = {
"hbm": [None, None, None], "hbm": [None, None, None],
"l2": [None, None, None], "l2": [None, None, None],
+5 -2
ファイルの表示
@@ -167,7 +167,8 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
and workload.roofline_metrics and workload.roofline_metrics
): ):
print( print(
"\n(4.1) Per-Kernel Roofline Metrics and (4.2) AI Plot Points", "\n(4.1) Per-Kernel Roofline Metrics and "
"(4.2) AI Plot Points",
file=output, file=output,
) )
print("-" * 80, file=output) print("-" * 80, file=output)
@@ -201,7 +202,9 @@ def show_all(args, runs, archConfigs, output, profiling_config, roof_plot=None):
else kernel_name else kernel_name
) )
print( print(
f"\nKernel {kernel_id}: {display_name} ({kernel_pct:.1f}%)", f"\nKernel {kernel_id}: "
f"{display_name} "
f"({kernel_pct:.1f}%)",
file=output, file=output,
) )
+27 -7
ファイルの表示
@@ -172,7 +172,9 @@ def get_num_pmc_file(output_dir):
""" """
perfmon_path = Path(output_dir) / "perfmon" perfmon_path = Path(output_dir) / "perfmon"
return len([f for f in perfmon_path.iterdir() if f.is_file() and f.suffix == ".txt"]) return len([
f for f in perfmon_path.iterdir() if f.is_file() and f.suffix == ".txt"
])
# ============================================================================= # =============================================================================
@@ -1769,8 +1771,18 @@ def test_v3_json_to_csv_complex_dispatch(tmp_path, monkeypatch):
{ {
"metadata": {"pid": 12345}, "metadata": {"pid": 12345},
"agents": [ "agents": [
{"id": {"handle": 1}, "type": 2, "node_id": 0, "wave_front_size": 64}, {
{"id": {"handle": 2}, "type": 2, "node_id": 1, "wave_front_size": 32}, "id": {"handle": 1},
"type": 2,
"node_id": 0,
"wave_front_size": 64,
},
{
"id": {"handle": 2},
"type": 2,
"node_id": 1,
"wave_front_size": 32,
},
], ],
"counters": [ "counters": [
{ {
@@ -1923,8 +1935,10 @@ def test_v3_json_to_csv_complex_dispatch(tmp_path, monkeypatch):
def test_v3_json_to_csv_missing_counters_handling(tmp_path, monkeypatch): def test_v3_json_to_csv_missing_counters_handling(tmp_path, monkeypatch):
""" """
Test v3_json_to_csv handles cases where different dispatches have different sets of counters. Test v3_json_to_csv handles cases where different
This addresses the DataFrame creation issue where arrays have different lengths. dispatches have different sets of counters.
This addresses the DataFrame creation issue
where arrays have different lengths.
Args: Args:
tmp_path (pathlib.Path): Temporary directory for test files tmp_path (pathlib.Path): Temporary directory for test files
@@ -1936,7 +1950,12 @@ def test_v3_json_to_csv_missing_counters_handling(tmp_path, monkeypatch):
{ {
"metadata": {"pid": 12345}, "metadata": {"pid": 12345},
"agents": [ "agents": [
{"id": {"handle": 1}, "type": 2, "node_id": 0, "wave_front_size": 64} {
"id": {"handle": 1},
"type": 2,
"node_id": 0,
"wave_front_size": 64,
}
], ],
"counters": [ "counters": [
{ {
@@ -2072,7 +2091,8 @@ def test_v3_json_to_csv_missing_counters_handling(tmp_path, monkeypatch):
except ValueError as e: except ValueError as e:
if "All arrays must be of the same length" in str(e): if "All arrays must be of the same length" in str(e):
pytest.skip( pytest.skip(
"v3_json_to_csv does not currently handle missing counters gracefully - arrays have different lengths" "v3_json_to_csv does not currently "
"handle missing counters gracefully - arrays have different lengths"
) )
else: else:
raise raise
+5 -1
ファイルの表示
@@ -34,7 +34,11 @@ METRIC_ID_TO_NAME_MAP = {gfx_version: {} for gfx_version in GFX_VERSIONS}
def get_autogen_text(config_file="utils/unified_config.yaml"): def get_autogen_text(config_file="utils/unified_config.yaml"):
return f"# AUTOGENERATED FILE. Only edit for testing purposes, not for development. Generated from {config_file}. Generated by utils/split_config.py\n" return (
f"# AUTOGENERATED FILE. Only edit for testing purposes, "
f"not for development. Generated from {config_file}. "
f"Generated by utils/split_config.py\n"
)
def update_analysis_config(): def update_analysis_config():