Files
TheRock/rocm-systems/projects/rocprofiler-compute/tools/metric_validation.py
T
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

367 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
##############################################################################
# This script reads counter values of workloads and computes metrics
# per dispatch based on the counter values. The computed metrics and counter
# values are dumped to CSV files.
##############################################################################
import argparse
import copy
import sys
from pathlib import Path
import pandas as pd
current_path = Path(__file__).resolve().parent
additional_path = current_path / ".." / "src"
sys.path.insert(0, str(additional_path.resolve()))
from argparser import omniarg_parser # noqa: E402
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base # noqa: E402
from utils import file_io, parser # noqa: E402
from utils.mi_gpu_spec import mi_gpu_specs # noqa: E402
from utils.utils_analysis import impute_counters_iteration_multiplex # noqa: E402
class Colors:
"""ANSI color codes as class attributes for easy use."""
GREEN = "\033[92m"
RED = "\033[91m"
ENDC = "\033[0m" # Resets the color
class Analyzer(OmniAnalyze_Base):
"""Analyzer class for dumping raw counter and metric values."""
def __init__(
self, args: argparse.Namespace, supported_archs: dict[str, str]
) -> None:
super().__init__(args, supported_archs)
def dump_values(self) -> None:
"""Dump raw counter and/or metric values to CSV files."""
args = self.get_args()
# Columns to drop from the metric dataframes
cols_to_drop = [
"Kernel_Name",
"Count",
"Sum(ns)",
"Mean(ns)",
"Median(ns)",
"Percent",
"Dispatch_ID",
"GPU_ID",
"Info",
"from_csv",
]
# Define the order of columns for the final output of metrics
start_columns = [
"Dispatch_ID",
"GPU_ID",
"Kernel_Name",
"Metric",
"Channel",
]
# Define the end columns for the final output of metrics
end_columns = [
"Description",
]
# Keep track of written CSV paths to avoid overwriting
written_csv_paths = []
print(
f"{Colors.RED}Dumping values takes a long time for workloads with "
f"large number of dispatches.{Colors.ENDC}"
)
# FIXME: Currently only supports single path input
if len(args.path[0]) > 1:
print(
f"{Colors.RED}Warning: Multiple paths provided. "
f"Only the first path will be processed for dumping values."
f"{Colors.ENDC}"
)
for path_info in args.path:
# create 'mega dataframe'
raw_pmc = file_io.create_df_pmc(
path_info[0],
args.kernel_verbose,
args.verbose,
self._profiling_config,
)
path_suffix_base = "_".join(Path(path_info[0]).parts[-2:])
path_suffix = path_suffix_base
counter_index = 1
# Ensure unique file names
while path_suffix in written_csv_paths:
path_suffix = f"{path_suffix_base}_{counter_index}"
counter_index += 1
written_csv_paths.append(path_suffix)
# Dump counter values if requested
if args.dump_values in ("counter", "all"):
counter_csv_path = f"{args.output_dir}/{path_suffix}_counters.csv"
print(
f"{Colors.GREEN}Writing raw counter values to "
f"{counter_csv_path}{Colors.ENDC}"
)
# Dump the raw counters to CSV
raw_pmc.set_index("Dispatch_ID").to_csv(counter_csv_path)
# Dump metric values if requested
if args.dump_values in ("metric", "all"):
dfs = []
# Handle iteration multiplexing if specified
if policy := self._profiling_config.get("iteration_multiplexing"):
raw_pmc = impute_counters_iteration_multiplex(
raw_pmc, policy, Path(path_info[0])
)
base_workload = self._runs[path_info[0]]
# Make a copy of the dataframe to process
df_new = raw_pmc.copy()
# Process each dispatch individually
for i in range(len(df_new)):
workload = copy.deepcopy(base_workload)
df = df_new.loc[[i]].copy()
df.reset_index(drop=True, inplace=True)
workload.raw_pmc = df
# create the loaded table
parser.load_table_data(
workload=workload,
dir_path=path_info[0],
is_gui=False,
args=args,
skip_kernel_top=False,
)
for _, value in workload.dfs.items():
value.drop(columns=cols_to_drop, inplace=True, errors="ignore")
# Check if the dataframe is not empty after dropping NaNs
if not value.empty:
value = value.dropna(how="all")
# Insert identifying columns
value.insert(
0,
"Dispatch_ID",
df.at[0, "Dispatch_ID"]
if "Dispatch_ID" in df.columns
else 0,
)
value.insert(1, "GPU_ID", df.at[0, "GPU_ID"])
value.insert(2, "Kernel_Name", df.at[0, "Kernel_Name"])
# Append to list of dataframes to merge
dfs.append(value)
merged_df = pd.concat(dfs, ignore_index=True)
# reorder columns
reordered_cols = (
start_columns
+ [
col
for col in merged_df.columns
if col not in start_columns + end_columns
]
+ end_columns
)
merged_df = merged_df.reindex(columns=reordered_cols)
metric_csv_path = f"{args.output_dir}/{path_suffix}_metrics.csv"
print(
f"{Colors.GREEN}Writing metric values to "
f"{metric_csv_path}{Colors.ENDC}"
)
merged_df.set_index(start_columns).to_csv(metric_csv_path)
def pre_processing(self) -> None:
"""Perform any pre-processing steps prior to analysis."""
args = self.get_args()
# Read profiling config
self._profiling_config = file_io.load_profiling_config(args.path[0][0])
# initalize runs
self._runs = self.initalize_runs()
def add_parser_args(parser_obj: argparse.ArgumentParser) -> None:
"""Add arguments to the parser object."""
parser_obj.add_argument(
"--dump-values",
dest="dump_values",
type=str,
choices=["counter", "metric", "all"],
default="all",
required=False,
help="Dump raw counter and/or metric values to CSV files.",
)
parser_obj.add_argument(
"-p",
"--path",
dest="path",
required=False,
metavar="",
help="\t\tSpecify the directory of profiling data.",
)
parser_obj.add_argument(
"-o",
"--output-dir",
dest="output_dir",
required=False,
metavar="",
help="\t\tSpecify the directory for writing values.",
default=".",
)
def copy_actions(
src_parser: argparse.ArgumentParser,
dst_parser: argparse.ArgumentParser,
exclude=(
"--help",
"-h",
"-V",
"--verbose",
"-q",
"--quiet",
"--list-metrics",
"--list-blocks",
"--config-dir",
"-s",
"--specs",
"-p",
"--path",
),
) -> None:
"""Copy actions from src_parser to dst_parser, excluding specified options."""
for action in src_parser._actions:
# Skip general group commands and subparser actions
if any(s in exclude for s in action.option_strings):
continue
if isinstance(action, argparse._SubParsersAction):
continue
# Build kwargs for add_argument
kwargs = {
"dest": action.dest,
"default": action.default,
"type": getattr(action, "type", None),
"choices": getattr(action, "choices", None),
"required": getattr(action, "required", False),
"help": argparse.SUPPRESS,
"metavar": getattr(action, "metavar", None),
}
# Handle special actions (flags, counts, append, etc.)
if action.option_strings:
# Optional-style action
if isinstance(action, argparse._StoreTrueAction):
kwargs["action"] = "store_true"
kwargs.pop("type", None)
elif isinstance(action, argparse._StoreFalseAction):
kwargs["action"] = "store_false"
kwargs.pop("type", None)
elif isinstance(action, argparse._CountAction):
kwargs["action"] = "count"
kwargs.pop("type", None)
elif isinstance(action, argparse._AppendAction):
kwargs["action"] = "append"
elif isinstance(action, argparse._AppendConstAction):
kwargs["action"] = "append_const"
kwargs["const"] = action.const
kwargs.pop("type", None)
elif isinstance(action, argparse._StoreConstAction):
kwargs["action"] = "store_const"
kwargs["const"] = action.const
kwargs.pop("type", None)
elif isinstance(action, argparse._VersionAction):
# skip version, or add as needed
continue
# Clean None values from kwargs
for k in list(kwargs.keys()):
if kwargs[k] is None:
del kwargs[k]
dst_parser.add_argument(*action.option_strings, **kwargs)
else:
# Positional-style action
pos_kwargs = dict(kwargs)
for k in list(pos_kwargs.keys()):
if pos_kwargs[k] is None:
del pos_kwargs[k]
dst_parser.add_argument(action.dest, **pos_kwargs)
def remove_subparsers(parser: argparse.ArgumentParser) -> None:
"""Remove subparsers from the parser object."""
for action in parser._actions:
if isinstance(action, argparse._SubParsersAction):
parser._remove_action(action)
def get_subparsers(
parser: argparse.ArgumentParser,
) -> dict[str, argparse.ArgumentParser]:
"""Get subparsers from the parser object."""
for action in parser._actions:
if isinstance(action, argparse._SubParsersAction):
return action.choices # dict: {name: ArgumentParser}
return {} # no subparsers defined
def main() -> None:
rocprof_version = {"ver": None, "ver_pretty": None}
rocprof_compute_path = additional_path.resolve()
supported_archs = mi_gpu_specs.get_gpu_series_dict()
parser_obj = argparse.ArgumentParser(description="Metric validation tool.")
omniarg_parser(parser_obj, rocprof_compute_path, supported_archs, rocprof_version)
# Move analyze subparser actions to main parser for argument initialization
subparsers = get_subparsers(parser_obj)
analyze_subparser = subparsers["analyze"]
copy_actions(analyze_subparser, parser_obj)
# Suppress help for all actions in the rocprof-compute parser and copied actions
for action in parser_obj._actions:
if not isinstance(action, argparse._HelpAction):
action.help = argparse.SUPPRESS
remove_subparsers(parser_obj)
add_parser_args(parser_obj)
args = parser_obj.parse_args()
if not args.path:
parser_obj.print_help()
sys.exit(1)
# Convert paths to absolute paths
args.path = [[str(Path(args.path).absolute())]]
analyzer = Analyzer(args, supported_archs)
analyzer.pre_processing()
analyzer.dump_values()
if __name__ == "__main__":
main()