* Update changelog, remove unused code.

* enable roofline in TUI.

* Remove roofline section when data not available.

* Fix workload dir path.
Этот коммит содержится в:
xuchen-amd
2025-06-18 13:58:42 -04:00
коммит произвёл GitHub
родитель 80159ee117
Коммит 5d8fd55e36
8 изменённых файлов: 187 добавлений и 148 удалений
+1 -1
Просмотреть файл
@@ -12,7 +12,7 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs.
* Sorting of PC sampling by type: offset or count.
* Add rocprof-compute Text User Interface (TUI) support for analyze mode
* Add rocprof-compute Text User Interface (TUI) support for analyze mode (beta version)
* A command line based user interface to support interactive single-run analysis
* launch with `--tui` option in analyze mode. i.e., `rocprof-compute analyze --tui`
+36 -8
Просмотреть файл
@@ -37,6 +37,7 @@ class tui_analysis(OmniAnalyze_Base):
def __init__(self, args, supported_archs, path):
super().__init__(args, supported_archs)
self.path = str(path)
self.arch = None
# -----------------------
# Required child methods
@@ -95,10 +96,10 @@ class tui_analysis(OmniAnalyze_Base):
# load required configs
sysinfo_path = Path(self.path)
sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
arch = sys_info.iloc[0]["gpu_arch"]
self.arch = sys_info.iloc[0]["gpu_arch"]
args = self.get_args()
self.generate_configs(
arch,
self.arch,
args.config_dir,
args.list_stats,
args.filter_metrics,
@@ -112,15 +113,13 @@ class tui_analysis(OmniAnalyze_Base):
# For regular single node case, load sysinfo.csv directly
# 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.
sysinfo_path = Path(self.path)
w.sys_info = file_io.load_sys_info(sysinfo_path.joinpath("sysinfo.csv"))
arch = w.sys_info.iloc[0]["gpu_arch"]
mspec = self.get_socs()[arch]._mspec
mspec = self.get_socs()[self.arch]._mspec
if args.specs_correction:
w.sys_info = parser.correct_sys_info(mspec, args.specs_correction)
w.avail_ips = w.sys_info["ip_blocks"].item().split("|")
w.dfs = copy.deepcopy(self._arch_configs[arch].dfs)
w.dfs_type = self._arch_configs[arch].dfs_type
w.dfs = copy.deepcopy(self._arch_configs[self.arch].dfs)
w.dfs_type = self._arch_configs[self.arch].dfs_type
self._runs[self.path] = w
return self._runs
@@ -130,10 +129,39 @@ class tui_analysis(OmniAnalyze_Base):
"""Run TUI analysis."""
super().run_analysis()
roof_plot = None
# 1. check if not baseline && compatible soc:
if self.arch in [
# >= MI200
"gfx90a",
"gfx940",
"gfx941",
"gfx942",
"gfx950",
]:
# add roofline plot to cli output
self.get_socs()[self.arch].analysis_setup(
roofline_parameters={
"workload_dir": self.path,
"device_id": 0,
"sort_type": "kernels",
"mem_level": "ALL",
"include_kernel_names": False,
"is_standalone": False,
"roofline_data_type": "FP32",
}
)
roof_obj = self.get_socs()[self.arch].roofline_obj
if roof_obj:
# NOTE: using default data type
roof_plot = roof_obj.cli_generate_plot(roof_obj.get_dtype()[0])
results = process_panels_to_dataframes(
self.get_args(),
self._runs,
self._arch_configs[self._runs[self.path].sys_info.iloc[0]["gpu_arch"]],
self._arch_configs[self.arch],
self._profiling_config,
roof_plot=roof_plot,
)
return results
+4 -2
Просмотреть файл
@@ -287,7 +287,9 @@ def get_table_dfs():
return section_dfs
def process_panels_to_dataframes(args, runs, archConfigs, profiling_config):
def process_panels_to_dataframes(
args, runs, archConfigs, profiling_config, roof_plot=None
):
"""
Process panel data into pandas DataFrames.
Returns a nested dictionary structure with DataFrames and tui_style information.
@@ -416,7 +418,7 @@ def process_panels_to_dataframes(args, runs, archConfigs, profiling_config):
# Save to CSV if requested
if args.df_file_dir:
save_dataframe_to_csv(df, table_id_str, table_config, args)
result_structure["roofline"] = roof_plot
return dict(result_structure)
+4
Просмотреть файл
@@ -237,6 +237,10 @@ class MainView(Horizontal):
else:
self.app.call_from_thread(self.refresh_results)
self.logger.info("Step 8: Analysis completed successfully")
if self.dfs["roofline"]:
self.logger.info("Step 8: Roofline data available")
else:
self.logger.info("Step 8: Roofline data not available")
except Exception as e:
self.logger.error(f"Step 8 failed - Error running analysis: {str(e)}")
raise
+39 -89
Просмотреть файл
@@ -263,6 +263,45 @@ def px_simple_multi_bar(df, title=None, id=None):
return dfigs
class RooflinePlot(Static):
"""Roofline Plot visualization widget."""
DEFAULT_CSS = """
RooflinePlot {
border: solid $accent;
padding: 0;
width: auto;
height: auto;
overflow-y: auto;
overflow-x: auto;
background: $surface;
color: $text;
}
"""
def __init__(self, df: pd.DataFrame, **kwargs):
"""Initialize the roofline plot"""
super().__init__("", classes="roofline", **kwargs)
self.df = df
# Disable markup rendering
self._render_markup = False
try:
plot_str = ""
try:
result = self.df["roofline"]
if result:
plot_str = str(result)
except:
plot_str = "No roofline data generated"
self.update(plot_str)
except Exception as e:
error_message = f"Roofline plot error: {str(e)}\n{traceback.format_exc()}"
self.update(error_message)
class MemoryChart(Static):
"""Memory chart visualization widget."""
@@ -317,95 +356,6 @@ class MemoryChart(Static):
self.update(f"Error: {str(error_message)}")
class RooflinePlot(PlotextPlot):
"""
HACK: will be replaced with real roof line plot
Roofline plot visualization widget.
"""
DEFAULT_CSS = """
RooflinePlot {
padding: 1;
width: auto;
height: auto;
background: $surface;
color: $text;
border: solid $accent;
}
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.styles.height = "75%"
self.styles.width = "75%"
self.plot_initialized = False
self.plt.theme("pro")
def on_mount(self):
self.refresh_plot()
def refresh_plot(self):
# Get the current size of this widget
plot_width, plot_height = self.size
# Configure the plot size
self.plt.plot_size(plot_width, plot_height)
self.create_roofline()
self.plot_initialized = True
def on_resize(self):
if self.plot_initialized:
self.refresh_plot()
def create_roofline(self):
"""Generate the roofline plot data and visualization."""
# Roofline model parameters
peak_performance = 1000.0 # GFLOPS
memory_bandwidth = 100.0 # GB/s
# For memory-bound region (diagonal line)
x_mem = [0.1, 0.5, 1.0, 5.0, 10.0]
y_mem = [x * memory_bandwidth for x in x_mem]
# For compute-bound region (horizontal line)
x_comp = [10.0, 20.0, 50.0, 100.0]
y_comp = [peak_performance] * len(x_comp)
# Example workloads with safe values
workloads = [
(0.5, 45.0, "Workload A"), # Memory bound
(2.0, 180.0, "Workload B"), # Memory bound
(15.0, 950.0, "Workload C"), # Compute bound
(30.0, 980.0, "Workload D"), # Compute bound
]
# Clear the plot and set properties
self.plt.clear_figure()
self.plt.title("Roofline Model (🚧 Under Construction)")
self.plt.xlabel("Arithmetic Intensity (FLOPs/Byte)")
self.plt.ylabel("Performance (GFLOP/sec)")
# Plot memory-bound and compute-bound lines
self.plt.plot(x_mem, y_mem, label="Memory Bound")
self.plt.plot(x_comp, y_comp, label="Compute Bound")
# Add workload points
workload_x = [w[0] for w in workloads]
workload_y = [w[1] for w in workloads]
workload_names = [w[2] for w in workloads]
# Plot workload points one by one to avoid errors
for i in range(len(workload_x)):
self.plt.scatter([workload_x[i]], [workload_y[i]], label=workload_names[i])
# Set a reasonable view range
self.plt.xlim(0, 100)
self.plt.ylim(0, 1100)
# Draw the plot
self.refresh()
class SimpleBar(Static):
"""Simple Bar visualization widget."""
+7 -10
Просмотреть файл
@@ -93,10 +93,6 @@ def create_widget_from_data(df: pd.DataFrame, tui_style: Optional[str] = None) -
case "mem_chart":
return MemoryChart(df)
case "roofline":
# TODO: implement real roofline plot
pass
case "simple_bar":
return SimpleBar(df)
@@ -150,12 +146,12 @@ def build_subsection(
widgets.append(widget)
collapsible = Collapsible(*widgets, title=title, collapsed=collapsed)
# HACK: only because no real roofline data right now
elif tui_style == "roofline":
widget = VerticalScroll(RooflinePlot())
collapsible = Collapsible(widget, title=title, collapsed=collapsed)
if dfs["roofline"]:
widget = RooflinePlot(dfs)
collapsible = Collapsible(widget, title=title, collapsed=collapsed)
else:
return None
# Fallback for subsections without data or style
else:
collapsible = Collapsible(
@@ -228,7 +224,8 @@ def build_section_from_config(
for subsection_config in section_config["subsections"]:
try:
subsection = build_subsection(subsection_config, dfs)
children.append(subsection)
if subsection:
children.append(subsection)
except Exception as e:
error_msg = f"{subsection_config.get('title', 'Unknown')} error: {str(e)}"
children.append(Label(error_msg, classes="warning"))
+84 -35
Просмотреть файл
@@ -109,27 +109,57 @@ class Roofline:
def roof_setup(self):
# Setup the workload directory for roofline profiling.
workload_dir_val = self.__run_parameters.get("workload_dir")
if (
workload_dir_val
and Path(workload_dir_val).name == "workloads"
and Path(workload_dir_val).parent == Path(os.getcwd())
):
app_name = getattr(self.__args, "name", "default_app_name")
gpu_model_name = getattr(self.__mspec, "gpu_model", "default_gpu_model")
self.__run_parameters["workload_dir"] = str(
Path(workload_dir_val).joinpath(
app_name,
gpu_model_name,
)
)
current_workload_dir = self.__run_parameters.get("workload_dir")
if current_workload_dir:
Path(current_workload_dir).mkdir(parents=True, exist_ok=True)
else:
if not workload_dir_val:
console_error(
"Workload directory is not set. Cannot perform setup.", exit=False
)
return
if isinstance(workload_dir_val, list):
if not workload_dir_val or not workload_dir_val[0]:
console_error(
"Workload directory list is empty or invalid. Cannot perform setup.",
exit=False,
)
return
# Handle nested list structure [0][0] or simple list [0]
base_dir = (
workload_dir_val[0][0]
if isinstance(workload_dir_val[0], (list, tuple))
else workload_dir_val[0]
)
else:
# workload_dir_val is a string
base_dir = workload_dir_val
base_path = Path(base_dir)
if base_path.name == "workloads" and base_path.parent == Path(os.getcwd()):
app_name = getattr(self.__args, "name", "default_app_name")
gpu_model_name = getattr(self.__mspec, "gpu_model", "default_gpu_model")
# Create the new path
new_path = base_path / app_name / gpu_model_name
# Update workload_dir with the new path, maintaining original data structure
if isinstance(workload_dir_val, list):
# Update the nested list structure
if isinstance(workload_dir_val[0], (list, tuple)):
self.__run_parameters["workload_dir"][0][0] = str(new_path)
else:
self.__run_parameters["workload_dir"][0] = str(new_path)
else:
# Update string value
self.__run_parameters["workload_dir"] = str(new_path)
final_dir = str(new_path)
else:
final_dir = base_dir
# Create the directory
Path(final_dir).mkdir(parents=True, exist_ok=True)
@demarcate
def empirical_roofline(
@@ -593,37 +623,56 @@ class Roofline:
)
return
# Normalize workload_dir to get the base directory
workload_dir = self.__run_parameters.get("workload_dir")
if workload_dir is None:
console_error(
"workload_dir is not set",
exit=False,
)
return
# Extract base directory path regardless of whether workload_dir is list or string
if isinstance(workload_dir, list):
if not workload_dir or not workload_dir[0]:
console_error(
"workload_dir list is empty or contains invalid entries",
exit=False,
)
return
# Handle nested list structure [0][0] or simple list [0]
base_dir = (
workload_dir[0][0]
if isinstance(workload_dir[0], (list, tuple))
else workload_dir[0]
)
else:
# workload_dir is a string
base_dir = workload_dir
self.roof_setup()
# Convert to Path object for easier manipulation
base_path = Path(base_dir)
# Check proper datatype input - takes single str
if not isinstance(dtype, str):
console_error("Unsupported datatype input - must be str")
if (
not isinstance(self.__run_parameters["workload_dir"], list)
and self.__run_parameters["workload_dir"] != None
):
self.roof_setup()
# Change vL1D to a interpretable str, if required
if "vL1D" in self.__run_parameters["mem_level"]:
self.__run_parameters["mem_level"].remove("vL1D")
self.__run_parameters["mem_level"].append("L1")
roofline_csv = str(
Path(self.__run_parameters["workload_dir"][0][0]).joinpath("roofline.csv")
)
roofline_csv_exists = Path(roofline_csv).is_file()
if not roofline_csv_exists:
roofline_csv = base_path / "roofline.csv"
if not roofline_csv.is_file():
console_log("roofline", "{} does not exist".format(roofline_csv))
return
app_path = str(
Path(self.__run_parameters["workload_dir"][0][0]).joinpath("pmc_perf.csv")
)
roofline_exists = Path(app_path).is_file()
if not roofline_exists:
console_error("roofline", "{} does not exist".format(app_path))
pmc_perf_csv = base_path / "pmc_perf.csv"
if not pmc_perf_csv.is_file():
console_error("roofline", "{} does not exist".format(pmc_perf_csv))
t_df = OrderedDict()
t_df["pmc_perf"] = pd.read_csv(app_path)
t_df["pmc_perf"] = pd.read_csv(pmc_perf_csv)
color_scheme = {
"HBM": "blue+",
+12 -3
Просмотреть файл
@@ -590,9 +590,18 @@ def calc_ai(mspec, sort_type, ret_df):
def constuct_roof(roofline_parameters, dtype):
benchmark_results = str(
Path(roofline_parameters["workload_dir"][0][0]).joinpath("roofline.csv")
)
workload_dir = roofline_parameters.get("workload_dir")
if isinstance(workload_dir, list):
base_dir = (
workload_dir[0][0]
if isinstance(workload_dir[0], (list, tuple))
else workload_dir[0]
)
else:
base_dir = workload_dir
benchmark_results = str(Path(base_dir) / "roofline.csv")
# -----------------------------------------------------
# Initialize roofline data dictionary from roofline.csv
# -----------------------------------------------------