f9aa7be97c
* 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
425 рядки
17 KiB
Python
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
|