Fichiers
rocm-systems/src/omniperf_soc/soc_base.py
T
colramos-amd d4a649a926 Fix bug in profiling for Mi100
Signed-off-by: colramos-amd <colramos@amd.com>
2024-01-24 16:30:04 -06:00

385 lignes
13 KiB
Python

##############################################################################bl
# MIT License
#
# Copyright (c) 2021 - 2023 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
from abc import ABC, abstractmethod
import logging
import os
import math
import shutil
import glob
import re
import numpy as np
from utils.utils import demarcate
from pathlib import Path
class OmniSoC_Base():
def __init__(self,args):
self.__args = args
self.__soc = None
self.__perfmon_dir = None
self.__perfmon_config = {} # Per IP block max number of simulutaneous counters. GFX IP Blocks
self.__soc_params = {} # SoC specifications
if self.__args.path == os.path.join(os.getcwd(), "workloads"):
self.__workload_dir = os.path.join(self.__args.path, self.__args.name, self.__args.target)
else:
self.__workload_dir = self.__args.path
def __hash__(self):
return hash(self.__soc)
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.__soc == other.get_soc()
def set_perfmon_dir(self, path:str):
self.__perfmon_dir = path
def set_perfmon_config(self, config: dict):
self.__perfmon_config = config
def set_soc_param(self, param: dict):
self.__soc_params = param
def get_workload_perfmon_dir(self):
return str(Path(self.__perfmon_dir).parent.absolute())
def get_soc_param(self):
return self.__soc_params
def set_soc(self, soc: str):
self.__soc = soc
def get_soc(self):
return self.__soc
def get_args(self):
return self.__args
@demarcate
def get_profiler_options(self):
"""Fetch any SoC specific arguments required by the profiler
"""
# assume no SoC specific options and return empty list by default
return []
@demarcate
def perfmon_filter(self, roofline_perfmon_only: bool):
"""Filter default performance counter set based on user arguments
"""
if roofline_perfmon_only and os.path.isfile(os.path.join(self.get_args().path, "pmc_perf.csv")):
return
workload_perfmon_dir = self.__workload_dir + "/perfmon"
# Initialize directories
if not os.path.isdir(self.__workload_dir):
os.makedirs(self.__workload_dir)
else:
shutil.rmtree(self.__workload_dir)
os.makedirs(workload_perfmon_dir)
if not roofline_perfmon_only:
ref_pmc_files_list = glob.glob(self.__perfmon_dir + "/" + "pmc_*perf*.txt")
ref_pmc_files_list += glob.glob(self.__perfmon_dir + "/" + self.__soc + "/pmc_*_perf*.txt")
# Perfmon list filtering
if self.__args.ipblocks != None:
for i in range(len(self.__args.ipblocks)):
self.__args.ipblocks[i] = self.__args.ipblocks[i].lower()
mpattern = "pmc_([a-zA-Z0-9_]+)_perf*"
pmc_files_list = []
for fname in ref_pmc_files_list:
fbase = os.path.splitext(os.path.basename(fname))[0]
ip = re.match(mpattern, fbase).group(1)
if ip in self.__args.ipblocks:
pmc_files_list.append(fname)
logging.info("fname: " + fbase + ": Added")
else:
logging.info("fname: " + fbase + ": Skipped")
else:
# default: take all perfmons
pmc_files_list = ref_pmc_files_list
else:
ref_pmc_files_list = glob.glob(self.__perfmon_dir + "/" + "pmc_roof_perf.txt")
pmc_files_list = ref_pmc_files_list
# Coalesce and writeback workload specific perfmon
pmc_list = perfmon_coalesce(pmc_files_list, self.__perfmon_config, self.__workload_dir)
perfmon_emit(pmc_list, self.__perfmon_config, self.__workload_dir)
#----------------------------------------------------
# Required methods to be implemented by child classes
#----------------------------------------------------
@abstractmethod
def profiling_setup(self):
"""Perform any SoC-specific setup prior to profiling.
"""
logging.debug("[profiling] perform SoC profiling setup for %s" % self.__soc)
@abstractmethod
def post_profiling(self):
"""Perform any SoC-specific post profiling activities.
"""
logging.debug("[profiling] perform SoC post processing for %s" % self.__soc)
@abstractmethod
def analysis_setup(self):
"""Perform any SoC-specific setup prior to analysis.
"""
logging.debug("[analysis] perform SoC analysis setup for %s" % self.__soc)
@demarcate
def perfmon_coalesce(pmc_files_list, perfmon_config, workload_dir):
"""Sort and bucket all related performance counters to minimize required application passes
"""
workload_perfmon_dir = workload_dir + "/perfmon"
# match pattern for pmc counters
mpattern = r"^pmc:(.*)"
pmc_list = dict(
[
("SQ", []),
("GRBM", []),
("TCP", []),
("TA", []),
("TD", []),
("TCC", []),
("SPI", []),
("CPC", []),
("CPF", []),
("GDS", []),
("TCC2", {}), # per-channel TCC perfmon
]
)
for ch in range(perfmon_config["TCC_channels"]):
pmc_list["TCC2"][str(ch)] = []
# Extract all PMC counters and store in separate buckets
for fname in pmc_files_list:
lines = open(fname, "r").read().splitlines()
for line in lines:
# Strip all comements, skip empty lines
stext = line.split("#")[0].strip()
if not stext:
continue
# all pmc counters start with "pmc:"
m = re.match(mpattern, stext)
if m is None:
continue
# we have found all the counters, store them in buckets
counters = m.group(1).split()
# Utilitze helper function once a list of counters has be extracted
save_file = True
pmc_list = update_pmc_bucket(
counters, save_file, perfmon_config, pmc_list, stext, workload_perfmon_dir
)
# add a timestamp file
fd = open(workload_perfmon_dir + "/timestamps.txt", "w")
fd.write("pmc:\n\n")
fd.write("gpu:\n")
fd.write("range:\n")
fd.write("kernel:\n")
fd.close()
# sort the per channel counter, so that same counter in all channels can be aligned
for ch in range(perfmon_config["TCC_channels"]):
pmc_list["TCC2"][str(ch)].sort()
return pmc_list
@demarcate
def update_pmc_bucket(
counters, save_file, perfmon_config, pmc_list=None, stext=None, workload_perfmon_dir=None
):
# Verify inputs.
# If save_file is True, we're being called internally, from perfmon_coalesce
# Else we're being called externally, from rocomni
detected_external_call = False
if save_file and (stext is None or workload_perfmon_dir is None):
raise ValueError(
"stext and workload_perfmon_dir must be specified if save_file is True"
)
if pmc_list is None:
detected_external_call = True
pmc_list = dict(
[
("SQ", []),
("GRBM", []),
("TCP", []),
("TA", []),
("TD", []),
("TCC", []),
("SPI", []),
("CPC", []),
("CPF", []),
("GDS", []),
("TCC2", {}), # per-channel TCC perfmon
]
)
for ch in range(perfmon_config["TCC_channels"]):
pmc_list["TCC2"][str(ch)] = []
if "SQ_ACCUM_PREV_HIRES" in counters and not detected_external_call:
# save all level counters separately
nindex = counters.index("SQ_ACCUM_PREV_HIRES")
level_counter = counters[nindex - 1]
if save_file:
# Save to level counter file, file name = level counter name
fd = open(workload_perfmon_dir + "/" + level_counter + ".txt", "w")
fd.write(stext + "\n\n")
fd.write("gpu:\n")
fd.write("range:\n")
fd.write("kernel:\n")
fd.close()
return pmc_list
# save normal pmc counters in matching buckets
for counter in counters:
IP_block = counter.split(sep="_")[0].upper()
# SQC and SQ belong to the IP block, coalesce them
if IP_block == "SQC":
IP_block = "SQ"
if IP_block != "TCC":
# Insert unique pmc counters into its bucket
if counter not in pmc_list[IP_block]:
pmc_list[IP_block].append(counter)
else:
# TCC counters processing
m = re.match(r"[\s\S]+\[(\d+)\]", counter)
if m is None:
# Aggregated TCC counters
if counter not in pmc_list[IP_block]:
pmc_list[IP_block].append(counter)
else:
# TCC channel ID
ch = m.group(1)
# fake IP block for per channel TCC
if str(ch) in pmc_list["TCC2"]:
# append unique counter into the channel
if counter not in pmc_list["TCC2"][str(ch)]:
pmc_list["TCC2"][str(ch)].append(counter)
else:
# initial counter in this channel
pmc_list["TCC2"][str(ch)] = [counter]
if detected_external_call:
# sort the per channel counter, so that same counter in all channels can be aligned
for ch in range(perfmon_config["TCC_channels"]):
pmc_list["TCC2"][str(ch)].sort()
return pmc_list
@demarcate
def perfmon_emit(pmc_list, perfmon_config, workload_dir=None):
# Calculate the minimum number of iteration to save the pmc counters
# non-TCC counters
pmc_cnt = [
len(pmc_list[key]) / perfmon_config[key]
for key in pmc_list
if key not in ["TCC", "TCC2"]
]
# TCC counters
tcc_channels = perfmon_config["TCC_channels"]
tcc_cnt = len(pmc_list["TCC"]) / perfmon_config["TCC"]
tcc2_cnt = (
np.array([len(pmc_list["TCC2"][str(ch)]) for ch in range(tcc_channels)])
/ perfmon_config["TCC"]
)
# Total number iterations to write pmc: counters line, except TCC2
niter = max(math.ceil(max(pmc_cnt)), math.ceil(tcc_cnt))
# Emit PMC counters into pmc config file
if workload_dir:
workload_perfmon_dir = workload_dir + "/perfmon"
fd = open(workload_perfmon_dir + "/pmc_perf.txt", "w")
else:
batches = []
for iter in range(niter):
# Prefix
line = "pmc: "
# Add all non-TCC counters
for key in pmc_list:
if key not in ["TCC", "TCC2"]:
N = perfmon_config[key]
ip_counters = pmc_list[key][iter * N : iter * N + N]
if ip_counters:
line = line + " " + " ".join(ip_counters)
# Add TCC counters
N = perfmon_config["TCC"]
tcc_counters = pmc_list["TCC"][iter * N : iter * N + N]
# TCC aggregated counters
line = line + " " + " ".join(tcc_counters)
if workload_dir:
fd.write(line + "\n")
else:
b = line.split()
b.remove("pmc:")
batches.append(b)
# TCC2, handle TCC per channel counters separatly
tcc2_index = 0
niter = math.ceil(max(tcc2_cnt))
for iter in range(niter):
# Prefix
line = "pmc: "
N = perfmon_config["TCC"]
# TCC per-channel counters
tcc_counters = []
for ch in range(perfmon_config["TCC_channels"]):
tcc_counters = pmc_list["TCC2"][str(ch)][tcc2_index * N : tcc2_index * N + N]
tcc2_index += 1
# TCC2 aggregated counters
line = line + " " + " ".join(tcc_counters)
if workload_dir:
fd.write(line + "\n")
else:
b = line.split()
b.remove("pmc:")
batches.append(b)
if workload_dir:
fd.write("\ngpu:\n")
fd.write("range:\n")
fd.write("kernel:\n")
fd.close()
else:
return batches