840 rivejä
28 KiB
Python
840 rivejä
28 KiB
Python
##############################################################################
|
|
# 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.
|
|
|
|
##############################################################################
|
|
|
|
import csv
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Optional, Union
|
|
|
|
import pandas as pd
|
|
|
|
from utils import schema
|
|
from utils.logger import console_debug, console_warning
|
|
from utils.parser import apply_filters, eval_metric
|
|
from utils.specs import MachineSpecs
|
|
|
|
################################################
|
|
# Global vars
|
|
################################################
|
|
XMIN = 0.01
|
|
XMAX = 1000
|
|
|
|
FONT_SIZE = 16
|
|
FONT_COLOR = "black"
|
|
FONT_WEIGHT = "bold"
|
|
|
|
# SUPPORTED_DATATYPES table is based on datatype support in rocm-amdgpu-bench repository
|
|
# Indicates which datatypes per gpu arch can be generated by the roofline binary
|
|
SUPPORTED_DATATYPES: dict[str, list[str]] = {
|
|
"gfx90a": [
|
|
"FP16",
|
|
"BF16",
|
|
"FP32",
|
|
"FP64",
|
|
"I8",
|
|
"I32",
|
|
"I64",
|
|
], # Unsupported: F4, F6, F8
|
|
"gfx940": [
|
|
"FP8",
|
|
"FP16",
|
|
"BF16",
|
|
"FP32",
|
|
"FP64",
|
|
"I8",
|
|
"I32",
|
|
"I64",
|
|
], # Unsupported: F4, F6
|
|
"gfx941": [
|
|
"FP8",
|
|
"FP16",
|
|
"BF16",
|
|
"FP32",
|
|
"FP64",
|
|
"I8",
|
|
"I32",
|
|
"I64",
|
|
], # Unsupported: F4, F6
|
|
"gfx942": [
|
|
"FP8",
|
|
"FP16",
|
|
"BF16",
|
|
"FP32",
|
|
"FP64",
|
|
"I8",
|
|
"I32",
|
|
"I64",
|
|
], # Unsupported: F4, F6
|
|
"gfx950": [
|
|
"FP4",
|
|
"FP6",
|
|
"FP8",
|
|
"FP16",
|
|
"BF16",
|
|
"FP32",
|
|
"FP64",
|
|
"I8",
|
|
"I32",
|
|
"I64",
|
|
], # Unsupported:
|
|
}
|
|
|
|
PEAK_OPS_DATATYPES = ["FP8", "FP16", "BF16", "FP32", "FP64", "I8", "I32", "I64"]
|
|
MFMA_DATATYPES = ["FP4", "FP6", "FP8", "FP16", "BF16", "FP32", "FP64", "I8"]
|
|
CACHE_HIERARCHY = ["HBM", "L2", "L1", "LDS"]
|
|
|
|
TOP_N = 10
|
|
|
|
|
|
################################################
|
|
# Helper funcs
|
|
################################################
|
|
@dataclass
|
|
class AI_Data:
|
|
KernelName: str
|
|
numCalls: float
|
|
|
|
total_flops: float
|
|
valu_flops: float
|
|
mfma_flops_f6f4: float
|
|
mfma_flops_f8: float
|
|
mfma_flops_f16: float
|
|
mfma_flops_bf16: float
|
|
mfma_flops_f32: float
|
|
mfma_flops_f64: float
|
|
mfma_iops_i8: float
|
|
lds_data: float
|
|
L1cache_data: float
|
|
L2cache_data: float
|
|
hbm_data: float
|
|
|
|
totalDuration: float
|
|
avgDuration: float
|
|
|
|
|
|
@dataclass
|
|
class PlotPoints:
|
|
"""Data structure for storing roofline plot points."""
|
|
|
|
ai_l1: list[list[float]]
|
|
ai_l2: list[list[float]]
|
|
ai_hbm: list[list[float]]
|
|
kernelNames: list[str]
|
|
|
|
@classmethod
|
|
def empty(cls) -> "PlotPoints":
|
|
"""Create empty plot points structure."""
|
|
return cls(ai_l1=[[], []], ai_l2=[[], []], ai_hbm=[[], []], kernelNames=[])
|
|
|
|
|
|
@dataclass
|
|
class GraphPoints:
|
|
"""Data structure for storing roofline graph ceiling points."""
|
|
|
|
hbm: list[Union[list[float], float, None]]
|
|
l2: list[Union[list[float], float, None]]
|
|
l1: list[Union[list[float], float, None]]
|
|
lds: list[Union[list[float], float, None]]
|
|
valu: list[Union[list[float], float, None]]
|
|
mfma: list[Union[list[float], float, None]]
|
|
|
|
@classmethod
|
|
def empty(cls) -> "GraphPoints":
|
|
"""Create empty graph points structure."""
|
|
return cls(
|
|
hbm=[None, None, None],
|
|
l2=[None, None, None],
|
|
l1=[None, None, None],
|
|
lds=[None, None, None],
|
|
valu=[None, None, None],
|
|
mfma=[None, None, None],
|
|
)
|
|
|
|
|
|
################################################
|
|
# Helper functions
|
|
################################################
|
|
def get_font() -> dict[str, Union[int, str]]:
|
|
return {
|
|
"size": FONT_SIZE,
|
|
"color": FONT_COLOR,
|
|
"weight": FONT_WEIGHT,
|
|
"family": "serif",
|
|
}
|
|
|
|
|
|
def get_color(category: str) -> str:
|
|
color_map = {"ai_l1": "green", "ai_l2": "blue", "ai_hbm": "red"}
|
|
|
|
if category not in color_map:
|
|
raise RuntimeError(f"Invalid category passed to get_color(): {category}")
|
|
|
|
return color_map[category]
|
|
|
|
|
|
# -------------------------------------------------------------------------------------
|
|
# Plot BW at each cache level
|
|
# -------------------------------------------------------------------------------------
|
|
def calc_ceilings(
|
|
roofline_parameters: dict[str, Any],
|
|
dtype: str,
|
|
benchmark_data: dict[str, list[str]],
|
|
ai_data: Optional[dict] = None,
|
|
) -> dict[str, list[Union[list[float], float, None]]]:
|
|
"""Given benchmarking data, calculate ceilings (or peak performance) for
|
|
empirical roofline"""
|
|
|
|
if ai_data:
|
|
max_ai = 0
|
|
for cache_level in ["ai_l1", "ai_l2", "ai_hbm"]:
|
|
if cache_level in ai_data and ai_data[cache_level][0]:
|
|
cache_max = max(ai_data[cache_level][0])
|
|
max_ai = max(max_ai, cache_max)
|
|
|
|
dynamic_xmax = max_ai * 1.2 if max_ai > 0 else 1000
|
|
else:
|
|
dynamic_xmax = 1000
|
|
|
|
# TODO: This is where filtering by memory level will need to occur for standalone
|
|
graph_points: dict[str, list[Union[list[float], float, None]]] = {
|
|
"hbm": [],
|
|
"l2": [],
|
|
"l1": [],
|
|
"lds": [],
|
|
"valu": [],
|
|
"mfma": [],
|
|
}
|
|
|
|
cache_hierarchy = (
|
|
CACHE_HIERARCHY
|
|
if roofline_parameters["mem_level"] == "ALL"
|
|
else roofline_parameters["mem_level"]
|
|
)
|
|
|
|
x1 = y1 = x2 = y2 = -1
|
|
x1_mfma = y1_mfma = x2_mfma = y2_mfma = -1
|
|
|
|
ops_flops = "Ops" if dtype.startswith("I") else "Flops"
|
|
|
|
peak_ops = 0.0
|
|
if dtype in PEAK_OPS_DATATYPES:
|
|
peak_ops = float(
|
|
benchmark_data[f"{dtype}{ops_flops}"][roofline_parameters["device_id"]]
|
|
)
|
|
|
|
for cache_level in cache_hierarchy:
|
|
# Plot BW line
|
|
console_debug("roofline", f"Current cache level is {cache_level}")
|
|
curr_bw = f"{cache_level}Bw"
|
|
peak_bw = float(benchmark_data[curr_bw][roofline_parameters["device_id"]])
|
|
|
|
x1 = float(XMIN)
|
|
y1 = float(XMIN) * peak_bw
|
|
|
|
if dtype in PEAK_OPS_DATATYPES:
|
|
x2 = peak_ops / peak_bw
|
|
y2 = peak_ops # noqa
|
|
|
|
# Plot MFMA lines (NOTE: Assuming MI200 soc)
|
|
x1_mfma = peak_ops / peak_bw
|
|
y1_mfma = peak_ops
|
|
|
|
peak_mfma = 0.0
|
|
if dtype in MFMA_DATATYPES:
|
|
target_precision = dtype if dtype.startswith("I") else f"F{dtype[2:]}"
|
|
|
|
peak_mfma = float(
|
|
benchmark_data[f"MFMA{target_precision}{ops_flops}"][
|
|
roofline_parameters["device_id"]
|
|
]
|
|
)
|
|
x2_mfma = peak_mfma / peak_bw
|
|
y2_mfma = peak_mfma
|
|
|
|
# Check which peak is higher for formatting bandwidth lines
|
|
if y2_mfma > y1_mfma: # peak_mfma
|
|
peak_x = x2_mfma
|
|
peak_y = y2_mfma
|
|
else: # peakVALU
|
|
peak_x = x1_mfma
|
|
peak_y = y1_mfma
|
|
|
|
# These are the points to use:
|
|
console_debug("roofline", "coordinate points:")
|
|
console_debug(f"x = [{x1}, {peak_x}]")
|
|
console_debug(f"y = [{y1}, {peak_y}]")
|
|
|
|
cache_key = cache_level.lower()
|
|
graph_points[cache_key].extend([[x1, peak_x], [y1, peak_y], peak_bw])
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# Plot computing roof
|
|
# ----------------------------------------------------------------------------------
|
|
if dtype in PEAK_OPS_DATATYPES:
|
|
# Plot FMA roof
|
|
x0 = min(x2, dynamic_xmax) if x2 < dynamic_xmax else dynamic_xmax
|
|
|
|
console_debug(f"FMA ROOF [{x0}, {dynamic_xmax}], [{peak_ops},{peak_ops}]")
|
|
graph_points["valu"].extend([
|
|
[x0, dynamic_xmax],
|
|
[peak_ops, peak_ops],
|
|
peak_ops,
|
|
])
|
|
|
|
# Plot MFMA roof
|
|
if dtype in MFMA_DATATYPES: # assert that mfma has been assigned
|
|
x0_mfma = min(x2_mfma, dynamic_xmax) if x2_mfma < dynamic_xmax else dynamic_xmax
|
|
|
|
console_debug(
|
|
f"MFMA ROOF [{x0_mfma}, {dynamic_xmax}], [{peak_mfma},{peak_mfma}]"
|
|
)
|
|
graph_points["mfma"].extend([
|
|
[x0_mfma, dynamic_xmax],
|
|
[peak_mfma, peak_mfma],
|
|
peak_mfma,
|
|
])
|
|
|
|
return graph_points
|
|
|
|
|
|
# -------------------------------------------------------------------------------------
|
|
# Overlay application performance
|
|
# -------------------------------------------------------------------------------------
|
|
# Calculate relevant metrics for ai calculation
|
|
def calc_ai_analyze(
|
|
workload: schema.Workload,
|
|
mspec: MachineSpecs,
|
|
sort_type: str,
|
|
config: dict[str, Any],
|
|
arch_config: schema.ArchConfig,
|
|
) -> dict[str, Union[list[list[float]], list[str]]]:
|
|
"""
|
|
Calculate per-kernel metrics and AI points with Roofline yamls using eval_metric.
|
|
"""
|
|
console_debug("calc_ai_analyze", "Starting calc_ai analysis using Roofline yamls")
|
|
plot_points = PlotPoints.empty()
|
|
|
|
workload.roofline_metrics = {}
|
|
filtered_pmc = apply_filters(workload, workload.path, is_gui=False, debug=False)
|
|
|
|
kernel_ids_to_process: list[int] = []
|
|
kernel_top_table_id = 1
|
|
|
|
if workload.filter_kernel_ids:
|
|
kernel_ids_to_process = workload.filter_kernel_ids
|
|
elif kernel_top_table_id in workload.dfs:
|
|
kernel_top_df = workload.dfs[kernel_top_table_id]
|
|
kernel_ids_to_process = kernel_top_df.index.tolist()
|
|
console_debug(
|
|
"roofline", f"Found {len(kernel_ids_to_process)} kernels to process"
|
|
)
|
|
|
|
if not kernel_ids_to_process:
|
|
console_warning("No kernels found to process for roofline")
|
|
return plot_points.__dict__
|
|
|
|
for kernel_id in kernel_ids_to_process:
|
|
kernel_name = ""
|
|
if kernel_top_table_id in workload.dfs:
|
|
kernel_top_df = workload.dfs[kernel_top_table_id]
|
|
if kernel_id not in kernel_top_df.index:
|
|
continue
|
|
kernel_name = kernel_top_df.loc[kernel_id, "Kernel_Name"]
|
|
else:
|
|
continue
|
|
|
|
console_debug("roofline", f"Processing kernel {kernel_id}: {kernel_name[:50]}")
|
|
|
|
# filter PMC data for specific kernel
|
|
kernel_pmc_df = filtered_pmc[
|
|
filtered_pmc["pmc_perf"]["Kernel_Name"] == kernel_name
|
|
]
|
|
|
|
if kernel_pmc_df.empty:
|
|
console_debug("roofline", f"No PMC data for kernel {kernel_id}")
|
|
continue
|
|
|
|
kernel_only_data = {"pmc_perf": kernel_pmc_df["pmc_perf"]}
|
|
|
|
kernel_dfs: dict[int, pd.DataFrame] = {}
|
|
kernel_dfs_type: dict[int, str] = {}
|
|
|
|
for table_id in [401, 402]:
|
|
if table_id in arch_config.dfs:
|
|
kernel_dfs[table_id] = arch_config.dfs[table_id].copy()
|
|
kernel_dfs_type[table_id] = arch_config.dfs_type[table_id]
|
|
|
|
# eval metrics for single kernel only
|
|
eval_metric(
|
|
kernel_dfs,
|
|
kernel_dfs_type,
|
|
workload.sys_info.iloc[0],
|
|
workload.roofline_peaks,
|
|
kernel_only_data,
|
|
debug=False,
|
|
config=config,
|
|
)
|
|
|
|
# DEBUG
|
|
if 402 in kernel_dfs:
|
|
console_debug("roofline", f"Table 402 for kernel {kernel_id}:")
|
|
for idx, row in kernel_dfs[402].iterrows():
|
|
console_debug(
|
|
"roofline", f" {row.get('Metric', '')}: {row.get('Value', '')}"
|
|
)
|
|
|
|
ai_hbm = ai_l2 = ai_l1 = performance = 0
|
|
|
|
if 402 in kernel_dfs:
|
|
for idx, row in kernel_dfs[402].iterrows():
|
|
metric = row.get("Metric", "")
|
|
value = row.get("Value", 0)
|
|
if metric == "AI HBM":
|
|
ai_hbm = value if value and value != "" else 0
|
|
elif metric == "AI L2":
|
|
ai_l2 = value if value and value != "" else 0
|
|
elif metric == "AI L1":
|
|
ai_l1 = value if value and value != "" else 0
|
|
elif metric == "Performance (GFLOPs)":
|
|
performance = value if value and value != "" else 0
|
|
|
|
console_debug(
|
|
"roofline",
|
|
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
|
|
if performance > 0:
|
|
if ai_hbm > 0:
|
|
plot_points.ai_hbm[0].append(ai_hbm)
|
|
plot_points.ai_hbm[1].append(performance)
|
|
if ai_l2 > 0:
|
|
plot_points.ai_l2[0].append(ai_l2)
|
|
plot_points.ai_l2[1].append(performance)
|
|
if ai_l1 > 0:
|
|
plot_points.ai_l1[0].append(ai_l1)
|
|
plot_points.ai_l1[1].append(performance)
|
|
|
|
plot_points.kernelNames.append(f"K{kernel_id}")
|
|
console_debug("roofline", f"Added kernel {kernel_id} to plot points")
|
|
else:
|
|
console_debug(
|
|
"roofline", f"Skipping kernel {kernel_id} - no performance data"
|
|
)
|
|
|
|
# store metrics for display
|
|
workload.roofline_metrics[kernel_id] = {
|
|
"name": kernel_name,
|
|
"ai_table": kernel_dfs.get(401, pd.DataFrame()),
|
|
"calc_table": kernel_dfs.get(402, pd.DataFrame()),
|
|
}
|
|
|
|
console_debug("roofline", f"Generated {len(plot_points.kernelNames)} plot points")
|
|
console_debug("roofline", f"Plot points: {plot_points}")
|
|
return plot_points.__dict__
|
|
|
|
|
|
def calc_ai_profile(
|
|
mspec: MachineSpecs, sort_type: str, ret_df: dict[str, pd.DataFrame]
|
|
) -> dict[str, Union[list[list[float]], list[str]]]:
|
|
"""Given counter data, calculate arithmetic intensity for each kernel
|
|
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 in the profiling stage."""
|
|
|
|
console_debug(
|
|
"calc_ai_profile: Starting legacy roofline calculation (from roofline_calc)"
|
|
)
|
|
df = ret_df["pmc_perf"]
|
|
# Sort by top kernels or top dispatches?
|
|
df = df.sort_values(by=["Kernel_Name"]).reset_index(drop=True)
|
|
|
|
total_flops = valu_flops = mfma_flops_f6f4 = mfma_flops_f8 = mfma_flops_bf16 = (
|
|
mfma_flops_f16
|
|
) = mfma_iops_i8 = mfma_flops_f32 = mfma_flops_f64 = lds_data = L1cache_data = (
|
|
L2cache_data
|
|
) = hbm_data = calls = totalDuration = avgDuration = 0.0
|
|
|
|
kernel_name = ""
|
|
my_list: list[AI_Data] = []
|
|
|
|
supported_dt = (
|
|
SUPPORTED_DATATYPES[mspec.gpu_arch]
|
|
if mspec.gpu_arch in SUPPORTED_DATATYPES
|
|
else None
|
|
)
|
|
|
|
for idx in df.index:
|
|
# CASE: Top kernels
|
|
# Calculate + append AI data if
|
|
# a) current KernelName is different than previous OR
|
|
# b) We've reached the end of list
|
|
at_end = idx + 1 == df.shape[0]
|
|
next_kernel_name = df["Kernel_Name"][idx + 1] if not at_end else ""
|
|
kernel_name = df["Kernel_Name"][idx]
|
|
|
|
try:
|
|
total_flops += (
|
|
(
|
|
64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F16"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F16"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F16"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F16"][idx]
|
|
)
|
|
)
|
|
+ (
|
|
64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F32"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F32"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F32"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F32"][idx]
|
|
)
|
|
)
|
|
+ (
|
|
64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F64"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F64"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F64"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F64"][idx]
|
|
)
|
|
)
|
|
+ (df["SQ_INSTS_VALU_MFMA_MOPS_F16"][idx] * 512)
|
|
+ (df["SQ_INSTS_VALU_MFMA_MOPS_BF16"][idx] * 512)
|
|
+ (df["SQ_INSTS_VALU_MFMA_MOPS_F32"][idx] * 512)
|
|
+ (df["SQ_INSTS_VALU_MFMA_MOPS_F64"][idx] * 512)
|
|
)
|
|
if "FP8" in supported_dt:
|
|
total_flops += df["SQ_INSTS_VALU_MFMA_MOPS_F8"][idx] * 512
|
|
if ("FP4" in supported_dt) or ("FP6" in supported_dt):
|
|
total_flops += df["SQ_INSTS_VALU_MFMA_MOPS_F6F4"][idx] * 512
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped total_flops at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
try:
|
|
valu_flops += (
|
|
64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F16"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F16"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F16"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F16"][idx]
|
|
)
|
|
+ 64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F32"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F32"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F32"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F32"][idx]
|
|
)
|
|
+ 64
|
|
* (
|
|
df["SQ_INSTS_VALU_ADD_F64"][idx]
|
|
+ df["SQ_INSTS_VALU_MUL_F64"][idx]
|
|
+ (2 * df["SQ_INSTS_VALU_FMA_F64"][idx])
|
|
+ df["SQ_INSTS_VALU_TRANS_F64"][idx]
|
|
)
|
|
)
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped valu_flops at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
|
|
try:
|
|
if "FP8" in supported_dt:
|
|
mfma_flops_f8 += df["SQ_INSTS_VALU_MFMA_MOPS_F8"][idx] * 512
|
|
if ("FP4" in supported_dt) or ("FP6" in supported_dt):
|
|
mfma_flops_f6f4 += df["SQ_INSTS_VALU_MFMA_MOPS_F6F4"][idx] * 512
|
|
mfma_flops_f16 += df["SQ_INSTS_VALU_MFMA_MOPS_F16"][idx] * 512
|
|
mfma_flops_bf16 += df["SQ_INSTS_VALU_MFMA_MOPS_BF16"][idx] * 512
|
|
mfma_flops_f32 += df["SQ_INSTS_VALU_MFMA_MOPS_F32"][idx] * 512
|
|
mfma_flops_f64 += df["SQ_INSTS_VALU_MFMA_MOPS_F64"][idx] * 512
|
|
mfma_iops_i8 += df["SQ_INSTS_VALU_MFMA_MOPS_I8"][idx] * 512
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped mfma ops at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
|
|
try:
|
|
lds_data += (
|
|
(df["SQ_LDS_IDX_ACTIVE"][idx] - df["SQ_LDS_BANK_CONFLICT"][idx])
|
|
* 4
|
|
* (mspec.lds_banks_per_cu)
|
|
)
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped lds_data at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
|
|
try:
|
|
L1cache_data += df["TCP_TOTAL_CACHE_ACCESSES_sum"][idx] * 64
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped L1cache_data at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
|
|
try:
|
|
L2cache_data += (
|
|
df["TCP_TCC_WRITE_REQ_sum"][idx] * 64
|
|
+ df["TCP_TCC_ATOMIC_WITH_RET_REQ_sum"][idx] * 64
|
|
+ df["TCP_TCC_ATOMIC_WITHOUT_RET_REQ_sum"][idx] * 64
|
|
+ df["TCP_TCC_READ_REQ_sum"][idx] * 64
|
|
)
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped L2cache_data at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
try:
|
|
if mspec.gpu_series == "MI200":
|
|
hbm_data += (
|
|
(df["TCC_EA_RDREQ_32B_sum"][idx] * 32)
|
|
+ (
|
|
(df["TCC_EA_RDREQ_sum"][idx] - df["TCC_EA_RDREQ_32B_sum"][idx])
|
|
* 64
|
|
)
|
|
+ (df["TCC_EA_WRREQ_64B_sum"][idx] * 64)
|
|
+ (
|
|
(df["TCC_EA_WRREQ_sum"][idx] - df["TCC_EA_WRREQ_64B_sum"][idx])
|
|
* 32
|
|
)
|
|
)
|
|
|
|
else:
|
|
# Use TCC_BUBBLE_sum to calculate hbm_data
|
|
hbm_data += (
|
|
(df["TCC_BUBBLE_sum"][idx] * 128)
|
|
+ (df["TCC_EA0_RDREQ_32B_sum"][idx] * 32)
|
|
+ (
|
|
(
|
|
df["TCC_EA0_RDREQ_sum"][idx]
|
|
- df["TCC_BUBBLE_sum"][idx]
|
|
- df["TCC_EA0_RDREQ_32B_sum"][idx]
|
|
)
|
|
* 64
|
|
)
|
|
+ (
|
|
(
|
|
df["TCC_EA0_WRREQ_sum"][idx]
|
|
- df["TCC_EA0_WRREQ_64B_sum"][idx]
|
|
)
|
|
* 32
|
|
)
|
|
+ (df["TCC_EA0_WRREQ_64B_sum"][idx] * 64)
|
|
)
|
|
except KeyError as e:
|
|
console_debug(
|
|
"roofline",
|
|
f"{kernel_name[:35]}: Skipped hbm_data at index {idx} due to {e}",
|
|
)
|
|
pass
|
|
|
|
totalDuration += df["End_Timestamp"][idx] - df["Start_Timestamp"][idx]
|
|
avgDuration += df["End_Timestamp"][idx] - df["Start_Timestamp"][idx]
|
|
calls += 1
|
|
|
|
if sort_type == "kernels" and (at_end or (kernel_name != next_kernel_name)):
|
|
my_list.append(
|
|
AI_Data(
|
|
kernel_name,
|
|
calls,
|
|
total_flops / calls,
|
|
valu_flops / calls,
|
|
mfma_flops_f6f4 / calls,
|
|
mfma_flops_f8 / calls,
|
|
mfma_flops_f16 / calls,
|
|
mfma_flops_bf16 / calls,
|
|
mfma_flops_f32 / calls,
|
|
mfma_flops_f64 / calls,
|
|
mfma_iops_i8 / calls,
|
|
lds_data / calls,
|
|
L1cache_data / calls,
|
|
L2cache_data / calls,
|
|
hbm_data / calls,
|
|
totalDuration,
|
|
avgDuration / calls,
|
|
)
|
|
)
|
|
console_debug(f"Just added {kernel_name} to AI_Data. # of calls: {calls}")
|
|
|
|
total_flops = valu_flops = mfma_flops_f6f4 = mfma_flops_f8 = (
|
|
mfma_flops_bf16
|
|
) = mfma_flops_f16 = mfma_iops_i8 = mfma_flops_f32 = mfma_flops_f64 = (
|
|
lds_data
|
|
) = L1cache_data = L2cache_data = hbm_data = calls = totalDuration = (
|
|
avgDuration
|
|
) = 0.0
|
|
|
|
if sort_type == "dispatches":
|
|
my_list.append(
|
|
AI_Data(
|
|
kernel_name,
|
|
calls,
|
|
total_flops,
|
|
valu_flops,
|
|
mfma_flops_f6f4,
|
|
mfma_flops_f8,
|
|
mfma_flops_f16,
|
|
mfma_flops_bf16,
|
|
mfma_flops_f32,
|
|
mfma_flops_f64,
|
|
mfma_iops_i8,
|
|
lds_data,
|
|
L1cache_data,
|
|
L2cache_data,
|
|
hbm_data,
|
|
totalDuration,
|
|
avgDuration,
|
|
)
|
|
)
|
|
total_flops = valu_flops = mfma_flops_f6f4 = mfma_flops_f8 = (
|
|
mfma_flops_bf16
|
|
) = mfma_flops_f16 = mfma_iops_i8 = mfma_flops_f32 = mfma_flops_f64 = (
|
|
lds_data
|
|
) = L1cache_data = L2cache_data = hbm_data = calls = totalDuration = (
|
|
avgDuration
|
|
) = 0.0
|
|
|
|
my_list.sort(key=lambda x: x.totalDuration, reverse=True)
|
|
|
|
intensities: dict[str, list[float]] = {"ai_l1": [], "ai_l2": [], "ai_hbm": []}
|
|
curr_perf: list[float] = []
|
|
kernel_names: list[str] = []
|
|
|
|
# Create list of top N intensities
|
|
for i in range(min(TOP_N, len(my_list))):
|
|
kernel_data = my_list[i]
|
|
|
|
if my_list[i].total_flops == 0:
|
|
console_debug(
|
|
f"No flops counted for {my_list[i].KernelName}, "
|
|
"arithmetic intensities will not display on plots."
|
|
)
|
|
|
|
kernel_names.append(my_list[i].KernelName)
|
|
|
|
# Calculate arithmetic intensities
|
|
intensities["ai_l1"].append(
|
|
kernel_data.total_flops / kernel_data.L1cache_data
|
|
if kernel_data.L1cache_data
|
|
else 0
|
|
)
|
|
intensities["ai_l2"].append(
|
|
kernel_data.total_flops / kernel_data.L2cache_data
|
|
if kernel_data.L2cache_data
|
|
else 0
|
|
)
|
|
intensities["ai_hbm"].append(
|
|
kernel_data.total_flops / kernel_data.hbm_data
|
|
if kernel_data.hbm_data
|
|
else 0
|
|
)
|
|
curr_perf.append(
|
|
kernel_data.total_flops / kernel_data.avgDuration
|
|
if kernel_data.avgDuration
|
|
else 0
|
|
)
|
|
|
|
# Create intensity points for plotting
|
|
intensity_points: dict[str, Union[list[list[float]], list[str]]] = {}
|
|
|
|
for ai_type in intensities:
|
|
values = intensities[ai_type]
|
|
|
|
x = values
|
|
y = curr_perf[: len(values)]
|
|
intensity_points[ai_type] = [x, y]
|
|
|
|
# Add kernel names
|
|
intensity_points["kernelNames"] = kernel_names
|
|
return intensity_points
|
|
|
|
|
|
def construct_roof(
|
|
roofline_parameters: dict[str, Any], dtype: str, ai_data: Optional[dict] = None
|
|
) -> dict[str, list[Union[list[float], float, None]]]:
|
|
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 = Path(base_dir) / "roofline.csv"
|
|
|
|
# -----------------------------------------------------
|
|
# Initialize roofline data dictionary from roofline.csv
|
|
# -----------------------------------------------------
|
|
# TODO: consider changing this to an ordered dict for consistency over py versions
|
|
benchmark_data: dict[str, list[str]] = {}
|
|
headers: list[str] = []
|
|
|
|
try:
|
|
with open(benchmark_results) as csvfile:
|
|
csv_reader = csv.reader(csvfile, delimiter=",")
|
|
row_count = 0
|
|
|
|
for row in csv_reader:
|
|
row.pop(0) # remove devID
|
|
if row_count == 0:
|
|
headers = row
|
|
for header in headers:
|
|
benchmark_data[header] = []
|
|
else:
|
|
for i, key in enumerate(headers):
|
|
benchmark_data[key].append(row[i])
|
|
row_count += 1
|
|
except Exception as e:
|
|
console_debug("roofline", f"Failed to read benchmark results: {e}")
|
|
return GraphPoints.empty().__dict__
|
|
|
|
# ------------------
|
|
# Generate Roofline
|
|
# ------------------
|
|
return calc_ceilings(roofline_parameters, dtype, benchmark_data, ai_data)
|