Файли
rocm-systems/src/rocprof_compute_analyze/analysis_webui.py
T
vedithal-amd f9aa7be97c Support MI 350 profiling (#632)
* Add MI 350 hardware information

* Refactor MI GPU YAML file and corresponding interface

* Add SoC file for gfx950 architecture

* Add analysis report configs for MI 350 containing existing metrics

* Add placeholder None valued metrics for previous architectures to make
  baseline comparison work

* Enable testing on MI 350

* Analysis config metric changes
    - SPI changes
        - Update metric formula for default SPI pipe counter
             - Use efficiently collected pipe wise SPI counters
        - Add SPI Wave Occupancy
        - Add Scheduler-Pipe Wave Utilization
        - Update formula for VGPR Writes
        - Add Scheduler-Pipe FIFO Full Rate
   - CPC changes
	- Add CPC SYNC FIFO Full Rate
	- Add CPC CANE Stall Rate
        - Add CPC ADC Utilization
   - SQ changes
        - Add VALU co-issue efficiency
        - Add F6F4 datatype metrics
        - Update formula for total FLOPs by adding F6F4 counters
        - Add LDS STORE / LOAD / ATOMIC metrics
        - Add LDS STORE / LOAD / ATOMIC bandwidth
        - Add LDS FIFO and TA ADDR / CMD / DATA FIFO full rates

* Collect TCP_TCP_LATENCY_sum only for gfx950 (MI 350)

* Do not inject SQ_ACCUM_PREV_HIRES unnecesarily

* Do not hardcode memory and shader clock speeds

* Write num_hbm_channels to sysinfo.csv instead of hbm_bw while profiling

* Move generate sysinfo.csv to pre processing step of profiling

* Add warnings to use --specs-correction for missing sysinfo.csv values during analysis phase

* Update CHANGELOG

* Analysis phase warning to use --specs-correction when needed
2025-04-03 02:21:18 -04:00

425 рядки
17 KiB
Python

##############################################################################bl
# MIT License
#
# Copyright (c) 2021 - 2025 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 copy
import os
import random
from pathlib import Path
import dash
import dash_bootstrap_components as dbc
from dash import dcc, html
from dash.dependencies import Input, Output, State
from rocprof_compute_analyze.analysis_base import OmniAnalyze_Base
from utils import file_io, parser
from utils.gui import build_bar_chart, build_table_chart
from utils.logger import console_debug, console_error, demarcate
PROJECT_NAME = "rocprofiler-compute"
class webui_analysis(OmniAnalyze_Base):
def __init__(self, args, supported_archs):
super().__init__(args, supported_archs)
self.app = dash.Dash(
__name__, title=PROJECT_NAME, external_stylesheets=[dbc.themes.CYBORG]
)
self.dest_dir = str(Path(args.path[0][0]).absolute().resolve())
self.arch = None
self.__hidden_sections = ["Memory Chart", "Roofline"]
self.__hidden_columns = ["Tips", "coll_level"]
# define different types of bar charts
self.__barchart_elements = {
"instr_mix": [1001, 1002],
"multi_bar": [1604, 1704],
"sol": [1101, 1201, 1301, 1401, 1601, 1701],
# "l2_cache_per_chan": [1802, 1803]
}
# define any elements which will have full width
self.__full_width_elements = {1801}
@demarcate
def build_layout(self, input_filters, arch_configs):
"""
Build gui layout
"""
from utils.gui_components.header import get_header
from utils.gui_components.memchart import get_memchart
comparable_columns = parser.build_comparable_columns(self.get_args().time_unit)
base_run, base_data = next(iter(self._runs.items()))
self.app.layout = html.Div(style={"backgroundColor": "rgb(50, 50, 50)"})
filt_kernel_names = []
kernel_top_df = base_data.dfs[1]
for kernel_id in base_data.filter_kernel_ids:
filt_kernel_names.append(kernel_top_df.loc[kernel_id, "Kernel_Name"])
self.app.layout.children = html.Div(
children=[
dbc.Spinner(
children=[
get_header(base_data.raw_pmc, input_filters, filt_kernel_names),
html.Div(id="container", children=[]),
],
fullscreen=True,
color="primary",
spinner_style={"width": "6rem", "height": "6rem"},
)
]
)
@self.app.callback(
Output("container", "children"),
[Input("disp-filt", "value")],
[Input("kernel-filt", "value")],
[Input("gcd-filt", "value")],
[Input("norm-filt", "value")],
[Input("top-n-filt", "value")],
[State("container", "children")],
)
def generate_from_filter(
disp_filt, kernel_filter, gcd_filter, norm_filt, top_n_filt, div_children
):
console_debug("analysis", "gui normalization is %s" % norm_filt)
base_data = self.initalize_runs() # Re-initalizes everything
panel_configs = copy.deepcopy(arch_configs.panel_configs)
# Generate original raw df
base_data[base_run].raw_pmc = file_io.create_df_pmc(
self.dest_dir,
self.get_args().nodes,
self.get_args().spatial_multiplexing,
self.get_args().kernel_verbose,
self.get_args().verbose,
)
if self.get_args().spatial_multiplexing:
base_data[base_run].raw_pmc = self.spatial_multiplex_merge_counters(
base_data[base_run].raw_pmc
)
console_debug("analysis", "gui dispatch filter is %s" % disp_filt)
console_debug("analysis", "gui kernel filter is %s" % kernel_filter)
console_debug("analysis", "gui gpu filter is %s" % gcd_filter)
console_debug("analysis", "gui top-n filter is %s" % top_n_filt)
base_data[base_run].filter_kernel_ids = kernel_filter
base_data[base_run].filter_gpu_ids = gcd_filter
base_data[base_run].filter_dispatch_ids = disp_filt
base_data[base_run].filter_top_n = top_n_filt
# Reload the pmc_kernel_top.csv for Top Stats panel
file_io.create_df_kernel_top_stats(
df_in=base_data[base_run].raw_pmc,
raw_data_dir=str(self.dest_dir),
filter_gpu_ids=base_data[base_run].filter_gpu_ids,
filter_dispatch_ids=base_data[base_run].filter_dispatch_ids,
filter_nodes=self._runs[self.dest_dir].filter_nodes,
time_unit=self.get_args().time_unit,
max_stat_num=base_data[base_run].filter_top_n,
kernel_verbose=self.get_args().kernel_verbose,
)
# Only display basic metrics if no filters are applied
if not (disp_filt or kernel_filter or gcd_filter):
temp = {}
keep = [1, 2, 101, 201, 301, 401]
for key in base_data[base_run].dfs:
if keep.count(key) != 0:
temp[key] = base_data[base_run].dfs[key]
base_data[base_run].dfs = temp
temp = {}
keep = [0, 100, 200, 300, 400]
for key in panel_configs:
if keep.count(key) != 0:
temp[key] = panel_configs[key]
panel_configs = temp
# All filtering will occur here
parser.load_table_data(
workload=base_data[base_run],
dir=self.dest_dir,
is_gui=True,
debug=self.get_args().debug,
verbose=self.get_args().verbose,
)
# ~~~~~~~~~~~~~~~~~~~~~~~
# Generate GUI content
# ~~~~~~~~~~~~~~~~~~~~~~~
div_children = []
# Append memory chart and roofline
div_children.append(
get_memchart(panel_configs[300]["data source"], base_data[base_run])
)
has_roofline = Path(self.dest_dir).joinpath("roofline.csv").is_file()
if has_roofline and hasattr(self.get_socs()[self.arch], "roofline_obj"):
# update roofline for visualization in GUI
self.get_socs()[self.arch].analysis_setup(
roofline_parameters={
"workload_dir": self.dest_dir,
"device_id": 0,
"sort_type": "kernels",
"mem_level": "ALL",
"include_kernel_names": False,
"is_standalone": False,
}
)
roof_obj = self.get_socs()[self.arch].roofline_obj
div_children.append(
roof_obj.empirical_roofline(
ret_df=parser.apply_filters(
workload=base_data[base_run],
dir=self.dest_dir,
is_gui=True,
debug=self.get_args().debug,
)
)
)
# Iterate over each section as defined in panel configs
for panel_id, panel in panel_configs.items():
title = str(panel_id // 100) + ". " + panel["title"]
section_title = (
panel["title"]
.replace("(", "")
.replace(")", "")
.replace("/", "")
.replace(" ", "_")
.lower()
)
html_section = []
if panel["title"] not in self.__hidden_sections:
# Iterate over each table per section
for data_source in panel["data source"]:
for t_type, table_config in data_source.items():
original_df = base_data[base_run].dfs[table_config["id"]]
# The sys info table need to add index back
if t_type == "raw_csv_table" and "Info" in original_df.keys():
original_df.reset_index(inplace=True)
content = determine_chart_type(
original_df=original_df,
table_config=table_config,
hidden_columns=self.__hidden_columns,
barchart_elements=self.__barchart_elements,
norm_filt=norm_filt,
comparable_columns=comparable_columns,
decimal=self.get_args().decimal,
)
# Update content for this section
if table_config["id"] in self.__full_width_elements:
# Optionally override default (50%) width
html_section.append(
html.Div(
className="float-child",
children=content,
style={"width": "100%"},
)
)
else:
html_section.append(
html.Div(className="float-child", children=content)
)
# Append the new section with all of it's contents
div_children.append(
html.Section(
id=section_title,
children=[
html.H3(
children=title,
style={"color": "white"},
),
html.Div(
className="float-container", children=html_section
),
],
)
)
# Display pop-up message if no filters are applied
if not (disp_filt or kernel_filter or gcd_filter):
div_children.append(
html.Section(
id="popup",
children=[
html.Div(
children="To dive deeper, use the top drop down menus to isolate particular kernel(s) or dispatch(s). You will then see the web page update with additional low-level metrics specific to the filter you've applied.",
),
],
)
)
return div_children
# -----------------------
# Required child methods
# -----------------------
@demarcate
def pre_processing(self):
"""Perform any pre-processing steps prior to analysis."""
super().pre_processing()
if len(self._runs) == 1:
args = self.get_args()
# create 'mega dataframe'
self._runs[self.dest_dir].raw_pmc = file_io.create_df_pmc(
self.dest_dir,
self.get_args().nodes,
self.get_args().spatial_multiplexing,
self.get_args().kernel_verbose,
args.verbose,
)
if self.get_args().spatial_multiplexing:
self._runs[self.dest_dir].raw_pmc = self.spatial_multiplex_merge_counters(
self._runs[self.dest_dir].raw_pmc
)
file_io.create_df_kernel_top_stats(
df_in=self._runs[self.dest_dir].raw_pmc,
raw_data_dir=self.dest_dir,
filter_gpu_ids=self._runs[self.dest_dir].filter_gpu_ids,
filter_dispatch_ids=self._runs[self.dest_dir].filter_dispatch_ids,
filter_nodes=self._runs[self.dest_dir].filter_nodes,
time_unit=args.time_unit,
max_stat_num=args.max_stat_num,
kernel_verbose=self.get_args().kernel_verbose,
)
# create the loaded kernel stats
parser.load_kernel_top(self._runs[self.dest_dir], self.dest_dir)
# set architecture
self.arch = self._runs[self.dest_dir].sys_info.iloc[0]["gpu_arch"]
else:
console_error(
"Multiple runs not yet supported in GUI. Retry without --gui flag."
)
@demarcate
def run_analysis(self):
"""Run CLI analysis."""
super().run_analysis()
args = self.get_args()
input_filters = {
"kernel": self._runs[self.dest_dir].filter_kernel_ids,
"gpu": self._runs[self.dest_dir].filter_gpu_ids,
"dispatch": self._runs[self.dest_dir].filter_dispatch_ids,
"normalization": args.normal_unit,
"top_n": args.max_stat_num,
}
self.build_layout(
input_filters,
self._arch_configs[self.arch],
)
if args.random_port:
self.app.run_server(
debug=False, host="0.0.0.0", port=random.randint(1024, 49151)
)
else:
self.app.run_server(debug=False, host="0.0.0.0", port=args.gui)
@demarcate
def determine_chart_type(
original_df,
table_config,
hidden_columns,
barchart_elements,
norm_filt,
comparable_columns,
decimal,
):
content = []
display_columns = original_df.columns.values.tolist().copy()
# Remove hidden columns. Better way to do it?
for col in hidden_columns:
if col in display_columns:
display_columns.remove(col)
display_df = original_df[display_columns]
# Determine chart type:
# a) Barchart
if table_config["id"] in [x for i in barchart_elements.values() for x in i]:
d_figs = build_bar_chart(display_df, table_config, barchart_elements, norm_filt)
# Smaller formatting if barchart yeilds several graphs
if (
len(d_figs)
> 2
# and not table_config["id"]
# in barchart_elements["l2_cache_per_chan"]
):
temp_obj = []
for fig in d_figs:
temp_obj.append(
html.Div(
className="float-child",
children=[dcc.Graph(figure=fig, style={"margin": "2%"})],
)
)
content.append(html.Div(className="float-container", children=temp_obj))
# Normal formatting if < 2 graphs
else:
for fig in d_figs:
content.append(dcc.Graph(figure=fig, style={"margin": "2%"}))
# B) Tablechart
else:
d_figs = build_table_chart(
display_df,
table_config,
original_df,
display_columns,
comparable_columns,
decimal,
)
for fig in d_figs:
content.append(html.Div([fig], style={"margin": "2%"}))
# subtitle for each table in a panel if existing
if "title" in table_config and table_config["title"]:
subtitle = (
str(table_config["id"] // 100)
+ "."
+ str(table_config["id"] % 100)
+ " "
+ table_config["title"]
+ "\n"
)
content.insert(
0,
html.H4(
children=subtitle,
style={"color": "white"},
),
)
return content