Re-implement machine-specs to contain documentation, units, etc. Print using tabulate

Signed-off-by: Nicholas Curtis <nicurtis@amd.com>
此提交包含在:
Nicholas Curtis
2024-02-29 10:21:29 -05:00
提交者 Cole Ramos
父節點 e86741ec6c
當前提交 200961ffd9
共有 5 個檔案被更改,包括 319 行新增188 行删除
+4 -3
查看文件
@@ -28,7 +28,7 @@ import sys
import os
from pathlib import Path
import shutil
from utils.specs import MachineSpecs
from utils.specs import generate_machine_specs
from utils.utils import demarcate, trace_logger, get_version, get_version_display, detect_rocprof, error, get_submodules
from argparser import omniarg_parser
import config
@@ -160,7 +160,7 @@ class Omniperf:
def load_soc_specs(self, sysinfo:dict=None):
"""Load OmniSoC instance for Omniperf run
"""
self.__mspec = MachineSpecs(self.__args, sysinfo)
self.__mspec = generate_machine_specs(self.__args, sysinfo)
if self.__args.specs:
print(self.__mspec)
sys.exit(0)
@@ -193,7 +193,7 @@ class Omniperf:
if self.__args.mode == None:
if self.__args.specs:
print(MachineSpecs(self.__args))
print(generate_machine_specs(self.__args))
sys.exit(0)
parser.print_help(sys.stderr)
error("Omniperf requires a valid mode.")
@@ -280,6 +280,7 @@ class Omniperf:
for d in analyzer.get_args().path:
sys_info = pd.read_csv(Path(d[0], "sysinfo.csv"))
sys_info = sys_info.to_dict('list')
sys_info = {key: value[0] for key, value in sys_info.items()}
self.load_soc_specs(sys_info)
analyzer.set_soc(self.__soc)
+1 -1
查看文件
@@ -92,7 +92,7 @@ class OmniSoC_Base():
def populate_mspec(self):
from utils.specs import search, run, total_sqc, total_xcds
if not hasattr(self._mspec, "_rocminfo"):
if not hasattr(self._mspec, "_rocminfo") or self._mspec._rocminfo is None:
return
# load stats from rocminfo
+301 -170
查看文件
@@ -36,10 +36,11 @@ import pandas as pd
from datetime import datetime
from math import ceil
from dataclasses import dataclass
from dataclasses import dataclass, field, asdict, fields
from pathlib import Path as path
from textwrap import dedent
from utils.utils import error, get_hbm_stack_num, get_version
from utils.tty import get_table_string
VERSION_LOC = [
"version",
@@ -52,129 +53,264 @@ VERSION_LOC = [
"version-utils",
]
@dataclass
def detect_arch(_rocminfo):
from omniperf_base import SUPPORTED_ARCHS
for idx1, linetext in enumerate(_rocminfo):
gpu_arch = search(r"^\s*Name\s*:\s+ ([a-zA-Z0-9]+)\s*$", linetext)
if gpu_arch in SUPPORTED_ARCHS.keys():
break
if str(gpu_arch) in SUPPORTED_ARCHS.keys():
gpu_arch = str(gpu_arch)
break
if not gpu_arch in SUPPORTED_ARCHS.keys():
error("[profiling] Cannot find a supported arch in rocminfo")
else:
return (gpu_arch, idx1)
def generate_machine_specs(args, sysinfo:dict=None):
if not sysinfo is None:
sysinfo_ver = str(sysinfo['version'])
version = get_version(config.omniperf_home)['version']
if sysinfo_ver != version[:version.find(".")]:
logging.warning("WARNING: Detected mismatch in sysinfo versioning. You may need to reprofile to update data.")
return MachineSpecs(**sysinfo)
# read timestamp info
now = datetime.now()
local_now = now.astimezone()
local_tz = local_now.tzinfo
local_tzname = local_tz.tzname(local_now)
timestamp = now.strftime("%c") + " (" + local_tzname + ")"
hostname = socket.gethostname()
# set specs version
vData = get_version(config.omniperf_home)
version = vData["version"]
# NB: Just taking major as specs version. May want to make this more specific in the future
specs_version = version[:version.find(".")] # version will always follow 'major.minor.patch' format
##########################################
## A. Machine Specs
##########################################
cpuinfo = path("/proc/cpuinfo").read_text()
meminfo = path("/proc/meminfo").read_text()
version = path("/proc/version").read_text()
os_release = path("/etc/os-release").read_text()
cpu_model = search(r"^model name\s*: (.*?)$", cpuinfo)
sbios = path("/sys/class/dmi/id/bios_vendor").read_text().strip() + \
path("/sys/class/dmi/id/bios_version").read_text().strip()
linux_kernel_version = search(r"version (\S*)", version)
amd_gpu_kernel_version = "" #TODO: Extract amdgpu kernel version
cpu_memory = search(r"MemTotal:\s*(\S*)", meminfo)
gpu_memory = "" #TODO: Extract gpu memory
linux_distro = search(r'PRETTY_NAME="(.*?)"', os_release)
if linux_distro is None:
linux_distro = ""
rocm_version = get_rocm_ver().strip()
#FIXME: use device
vbios = search(
r"VBIOS version: (.*?)$", run(["rocm-smi", "-v"], exit_on_error=True))
compute_partition = search(
r"Compute Partition:\s*(\w+)", run(["rocm-smi", "--showcomputepartition"]))
if compute_partition is None:
compute_partition = "NA"
memory_partition = search(
r"Memory Partition:\s*(\w+)", run(["rocm-smi", "--showmemorypartition"]))
if memory_partition is None:
memory_partition = "NA"
##########################################
## B. SoC Specs
##########################################
# read rocminfo
rocminfo_full = run(["rocminfo"])
_rocminfo = rocminfo_full.split("\n")
gpu_arch, idx = detect_arch(_rocminfo)
_rocminfo = _rocminfo[idx + 1 :] # update rocminfo for target section
specs = MachineSpecs(version=specs_version,
timestamp=timestamp,
_rocminfo=_rocminfo,
hostname=hostname,
cpu_model=cpu_model,
sbios=sbios,
linux_kernel_version=linux_kernel_version,
amd_gpu_kernel_version=amd_gpu_kernel_version,
cpu_memory=cpu_memory,
gpu_memory=gpu_memory,
linux_distro=linux_distro,
rocm_version=rocm_version,
vbios=vbios,
compute_partition=compute_partition,
memory_partition=memory_partition,
gpu_arch=gpu_arch)
# Load above SoC specs via module import
try:
soc_module = importlib.import_module('omniperf_soc.soc_'+ specs.gpu_arch)
except ModuleNotFoundError as e:
error("Arch %s marked as supported, but couldn't find class implementation %s." % (specs.gpu_arch, e))
soc_class = getattr(soc_module, specs.gpu_arch+'_soc')
soc_obj = soc_class(args, specs)
# Update arch specific specs
specs.total_l2_chan: str = total_l2_banks(
specs.gpu_model, int(specs._l2_banks), specs.memory_partition
)
specs.hbm_bw: str = str(int(specs.max_mclk) / 1000 * 32 * specs.get_hbm_channels())
return specs
@dataclass(kw_only=True)
class MachineSpecs:
def __init__(self, args, sysinfo:dict=None):
if not sysinfo is None:
sysinfo_ver = str(sysinfo['specs_version'][0])
version = get_version(config.omniperf_home)['version']
if sysinfo_ver != version[:version.find(".")]:
logging.warning("WARNING: Detected mismatch in sysinfo versioning. You may need to reprofile to update data.")
for key, value in sysinfo.items():
setattr(self, key, value[0])
return
# set specs version
vData = get_version(config.omniperf_home)
version = vData["version"]
# NB: Just taking major as specs version. May want to make this more specific in the future
self.specs_version = version[:version.find(".")] # version will alway follow 'major.minor.patch' format
##########################################
## A. Workload / Spec info
##########################################
# read timestamp info
now = datetime.now()
local_now = now.astimezone()
local_tz = local_now.tzinfo
local_tzname = local_tz.tzname(local_now)
self.timestamp = now.strftime("%c") + " (" + local_tzname + ")"
# these three fields are special in that they're not included
# when you use (e.g.,) --specs to view the machinespecs, but they
# _are_ included in profiling/analysis, so we mark them as 'optional'
# in the metadata to avoid erroring out on missing fields on
# serialization
workload_name: str = field(default=None, metadata={
'doc': 'The name of the workload data was collected for.',
'name': 'Workload Name',
'optional': True})
command: str = field(default=None, metadata={
'doc': 'The command the workload was executed with.',
'name': 'Command',
'optional': True})
ip_blocks: str = field(default=None, metadata={
'doc': 'The hardware blocks profiling information was collected for.',
'name': 'IP Blocks',
'optional': True
})
timestamp: str = field(default=None, metadata={
'doc': 'The time (in local system time) when data was collected',
'name': 'Timestamp'})
version: str = field(default=None, metadata={
'doc': 'The version of the machine specification file format.',
'name': 'MachineSpecs Version'})
timestamp: str = field(default=None, metadata={
'doc': 'The time (in local system time) when data was collected',
'name': 'Timestamp'})
_rocminfo: list = field(default=None)
##########################################
## A. Machine Specs
##########################################
hostname: str = field(default=None, metadata={'doc': 'The hostname of the machine.',
'name': 'Hostname'})
cpu_model: str = field(default=None, metadata={'doc': 'The model name of the CPU used.',
'name': 'CPU Model'})
sbios: str = field(default=None, metadata={'doc': 'The system management bios version and vendor.',
'name': "SBIOS"})
linux_distro: str = field(default=None, metadata={'doc': 'The Linux distribution installed on the machine.',
'name': 'Linux Distribution'})
linux_kernel_version: str = field(default=None,
metadata={'doc': 'The Linux kernel version running on the machine.',
'name': 'Linux Kernel Version'})
amd_gpu_kernel_version: str = field(default=None, metadata={
'doc': '[RESERVED] The version of the AMDGPU driver installed on the machine. Unimplemented.',
'name': "AMD GPU Kernel Version"})
cpu_memory: str = field(default=None, metadata={
'doc': 'The total amount of memory available to the CPU.', 'unit': 'KB',
'name': 'CPU Memory'})
gpu_memory: str = field(default=None, metadata={
'doc': '[RESERVED] The total amount of memory available to accelerators/GPUs in the system. Unimplemented.',
'unit': 'KB',
'name': 'GPU Memory'})
rocm_version: str = field(default=None,
metadata={'doc': 'The ROCm version used during data-collection.',
'name': "ROCm Version"})
vbios: str = field(default=None,
metadata={'doc': 'The version of the accelerators/GPUs video bios in the system.',
'name': 'VBIOS'})
compute_partition: str = field(default=None,
metadata={'doc': 'The compute partitioning mode active on the accelerators/GPUs in the system (MI300 only).',
'name': 'Compute Partition'})
memory_partition: str = field(default=None,
metadata={'doc': 'The memory partitioning mode active on the accelerators/GPUs in the system (MI300 only).',
'name': 'Memory Partition'})
# read rocminfo
rocminfo_full = run(["rocminfo"])
self._rocminfo = rocminfo_full.split("\n")
##########################################
## B. SoC Specs
##########################################
gpu_model: str = field(
default=None,
metadata={
'doc': 'The product name of the accelerators/GPUs in the system.',
'name': 'GPU Model'})
gpu_arch: str = field(
default=None,
metadata={
'doc': 'The architecture name of the accelerators/GPUs in the system,\n'
'as used by (e.g.,) the AMDGPU backed of LLVM.',
'name': 'GPU Arch'})
gpu_l1: str = field(default=None, metadata={'doc':
"The size of the vL1D cache (per compute-unit) on the accelerators/GPUs in the system in KiB",
'name': 'GPU L1'})
gpu_l2: str = field(default=None, metadata={'doc':
"The size of the vL1D cache (per compute-unit) on the accelerators/GPUs in the system in KiB",
'name': 'GPU L2'})
cu_per_gpu: str = field(default=None, metadata={'doc':
"The total number of compute units per accelerator/GPU in the system. On systems with configurable\n"
"partitioning, (e.g., MI300) this is the total number of compute units in a partition.",
'name': 'CU per GPU'})
simd_per_cu: str = field(default=None, metadata={'doc':
"The number of SIMD processors in a compute unit for the accelerators/GPUs in the system.",
'name': 'SIMD per CU'})
se_per_gpu: str = field(default=None, metadata={'doc':
'The number of shader engines on the accelerators/GPUs in the system. On systems with configurable\n'
'partitioning, (e.g., MI300) this is the total number of shader engines in a partition.',
'name': 'SE per GPU'})
wave_size: str = field(default=None, metadata={'doc':
'The number work-items in a wavefront on the accelerators/GPUs in the system.',
'name': 'Wave Size'})
workgroup_max_size: str = field(default=None, metadata={'doc':
'The maximum number of work-items in a workgroup on the accelerators/GPUs in the system.',
'name': 'Workgroup Max Size'})
max_waves_per_cu: str = field(default=None, metadata={'doc':
'The maximum number of wavefronts that can be resident on a compute unit on the\n'
'accelerators/GPUs in the system',
'name': 'Max Waves per CU'})
max_sclk: str = field(default=None, metadata={'doc':
'The maximum engine (compute-unit) clock rate of the accelerators/GPUs in the system.',
'name': 'Max SCLK',
'unit': 'MHz'})
max_mclk: str = field(default=None, metadata={'doc':
'The maximum memory clock rate of the accelerators/GPUs in the system.',
'name': 'Max MCLK',
'unit': 'MHz'})
cur_sclk: str = field(default=None, metadata={'doc':
'[RESERVED] The current engine (compute unit) clock rate of the accelerators/GPUs in the system. Unused.',
'name': 'Max SCLK',
'unit': 'MHz'})
cur_mclk: str = field(default=None, metadata={'doc':
'[RESERVED] The current memory clock rate of the accelerators/GPUs in the system. Unused.',
'name': 'Max MCLK',
'unit': 'MHz'})
_l2_banks: str = None # NB: This only used in flatten_tcc_info_across_hbm_stacks()
total_l2_chan: str = field(default=None, metadata={
'doc': 'The maximum number of L2 cache channels on the accelerators/GPUs in the system. On systems with\n'
'configurable partitioning, (e.g., MI300) this is the total number of L2 cache channels in a partition.',
'name': 'Total L2 Channels'})
lds_banks_per_cu: str = field(default=None, metadata={'doc':
'The number of banks in the LDS for a compute unit on the accelerators/GPUs in the system.',
'name': 'LDS Banks per CU'})
sqc_per_gpu: str = field(default=None, metadata={
'doc': 'The number of L1I/sL1D caches on the accelerators/GPUs in the system. On systems with\n'
'configurable partitioning, (e.g., MI300) this is the total number of L1I/sL1D caches in a partition.',
'name': 'SQC per GPU'})
pipes_per_gpu: str = field(default=None, metadata={
'doc': 'The number of scheduler-pipes on the accelerators/GPUs in the system.',
'name': 'Pipes per GPU'})
hbm_bw: str = field(default=None, metadata={
'doc': 'The peak theoretical HBM bandwidth for the accelerators/GPUs in the system. On systems with\n'
'configurable partitioning, (e.g., MI300) this is the peak theoretical HBM bandwidth for a partition.',
'name': 'HBM BW',
'unit': 'MB/s'})
num_xcd: str = field(default=None, metadata={
'doc': 'The total number of accelerator complex dies in a compute partition on the accelerators/GPUs in the\n'
'system. For accelerators without partitioning (i.e., pre-MI300), this is considered to be one.',
'name': 'Num XCDs',
'unit': 'MB/s'})
##########################################
## A. Machine Specs
##########################################
cpuinfo = path("/proc/cpuinfo").read_text()
meminfo = path("/proc/meminfo").read_text()
version = path("/proc/version").read_text()
os_release = path("/etc/os-release").read_text()
self.hostname: str = socket.gethostname()
self.cpu_model: str = search(r"^model name\s*: (.*?)$", cpuinfo)
self.sbios: str = (
path("/sys/class/dmi/id/bios_vendor").read_text().strip()
+ path("/sys/class/dmi/id/bios_version").read_text().strip()
)
self.linux_kernel_version: str = search(r"version (\S*)", version)
self.amd_gpu_kernel_version: str = None
if self.amd_gpu_kernel_version is None: #TODO: Extract amdgpu kernel version
self.amd_gpu_kernel_version = ""
self.cpu_memory: str = search(r"MemTotal:\s*(\S*)", meminfo)
self.gpu_memory: str = None #TODO: Extract total gpu memory
if self.gpu_memory is None:
self.gpu_memory = ""
self.linux_distro: str = search(r'PRETTY_NAME="(.*?)"', os_release)
if self.linux_distro is None:
self.linux_distro = ""
self.rocm_version: str = get_rocm_ver().strip()
#FIXME: use device
self.vbios: str = search(
r"VBIOS version: (.*?)$", run(["rocm-smi", "-v"], exit_on_error=True)
)
self.compute_partition: str = search(
r"Compute Partition:\s*(\w+)", run(["rocm-smi", "--showcomputepartition"])
)
if self.compute_partition is None:
self.compute_partition = "NA"
self.memory_partition: str = search(
r"Memory Partition:\s*(\w+)", run(["rocm-smi", "--showmemorypartition"])
)
if self.memory_partition is None:
self.memory_partition = "NA"
##########################################
## B. SoC Specs
##########################################
self.gpu_arch: str = self.detect_arch()[0]
self.gpu_l1: str = None
self.gpu_l2: str = None
self.cu_per_gpu: str = None
self.simd_per_cu: str = None
self.se_per_gpu: str = None
self.wave_size: str = None
self.workgroup_max_size: str = None
self.max_sclk: str = None
self.max_mclk: str = None
self.cur_sclk: str = None
self.cur_mclk: str = None
self.max_waves_per_cu: str = None
self.gpu_model: str = None
self._l2_banks: str = None #NB: This only used in flatten_tcc_info_across_hbm_stacks()
self.lds_banks_per_cu: str = None
self.sqc_per_gpu: str = None
self.pipes_per_gpu: str = None
self.total_l2_chan: str = None
self.hbm_bw: str = None
self.num_xcd: str = None
# Load above SoC specs via module import
try:
soc_module = importlib.import_module('omniperf_soc.soc_'+ self.gpu_arch)
except ModuleNotFoundError as e:
error("Arch %s marked as supported, but couldn't find class implementation %s." % (self.gpu_arch, e))
soc_class = getattr(soc_module, self.gpu_arch+'_soc')
self._rocminfo = self._rocminfo[self.detect_arch()[1] + 1 :] # update rocminfo for target section
soc_obj = soc_class(args, self)
# Update arch specific specs
self.total_l2_chan: str = total_l2_banks(
self.gpu_model, int(self._l2_banks), self.memory_partition
)
self.hbm_bw: str = str(int(self.max_mclk) / 1000 * 32 * self.get_hbm_channels())
def detect_arch(self):
from omniperf_base import SUPPORTED_ARCHS
for idx1, linetext in enumerate(self._rocminfo):
gpu_arch = search(r"^\s*Name\s*:\s+ ([a-zA-Z0-9]+)\s*$", linetext)
if gpu_arch in SUPPORTED_ARCHS.keys():
break
if str(gpu_arch) in SUPPORTED_ARCHS.keys():
gpu_arch = str(gpu_arch)
break
if not gpu_arch in SUPPORTED_ARCHS.keys():
error("[profiling] Cannot find a supported arch in rocminfo")
else:
return (gpu_arch, idx1)
def get_hbm_channels(self):
hbmchannels = int(self.total_l2_chan)
if (
@@ -189,60 +325,55 @@ class MachineSpecs:
all_populated = True
data = {}
# dataclass uses an OrderedDict for member variables, ensuring order consistency
for attr_name in self.__dict__.keys():
if not attr_name.startswith("_"):
attr_value = getattr(self, attr_name)
if attr_value is None:
#TODO: use proper logging function when that's merged
logging.warning(f"WARNING: Incomplete class definition for {self.gpu_arch}. Expecting populated {attr_name} but detected None.")
all_populated = False
data[attr_name] = attr_value
for field in fields(self):
name = field.name
if not name.startswith("_"):
value = getattr(self, name)
if value is None:
# check if we've marked it optional
if field.metadata and 'optional' in field.metadata and field.metadata['optional']:
pass
else:
#TODO: use proper logging function when that's merged
logging.warning(
f"WARNING: Incomplete class definition for {self.gpu_arch}. "
f"Expecting populated {name} but detected None.")
all_populated = False
data[name] = value
if not all_populated:
error("Missing specs fields for %s" % self.gpu_arch)
return pd.DataFrame(data, index=[0])
def __str__(self):
return dedent(
f"""\
Specs version: v{self.specs_version}
Host info:
Hostname: {self.hostname}
CPU Model: {self.cpu_model}
sbios: {self.sbios}
CPU Memory: {self.cpu_memory}
GPU Memory: {self.gpu_memory}
Linux Distro: {self.linux_distro}
Linux Kernel Version: {self.linux_kernel_version}
AMD GPU Kernel Version: {self.amd_gpu_kernel_version}
ROCm Version: {self.rocm_version}
Device info:
GPU Model: {self.gpu_model}
GPU Arch: {self.gpu_arch}
vbios: {self.vbios}
GPU L1: {self.gpu_l1} KB
GPU L2: {self.gpu_l2} KB
Max SCLK: {self.max_sclk} MHz
Max MCLK: {self.max_mclk} MHz
Cur SCLK: {self.cur_sclk} MHz
Cur MCLK: {self.cur_mclk} MHz
CU per GPU: {self.cu_per_gpu}
SIMD per CU: {self.simd_per_cu}
SE per GPU: {self.se_per_gpu}
Wave Size: {self.wave_size}
Workgroup Max Size: {self.workgroup_max_size}
Max Waves per CU: {self.max_waves_per_cu}
Total L2 Channels: {self.total_l2_chan}
LDS Banks per CU: {self.lds_banks_per_cu}
SQC per GPU: {self.sqc_per_gpu}
Pipes per GPU: {self.pipes_per_gpu}
HBM BW: {self.hbm_bw} MB/s
Compute Partition: {self.compute_partition}
Memory Partition: {self.memory_partition}
Num XCDs: {self.num_xcd}
"""
)
def __repr__(self):
data = []
for field in fields(self):
name = field.name
if not name.startswith("_"):
_data = {}
value = getattr(self, name)
if field.metadata:
if 'name' in field.metadata:
name = field.metadata['name']
if 'unit' in field.metadata:
_data['Unit'] = field.metadata['unit']
if 'doc' in field.metadata:
_data['Description'] = field.metadata['doc']
_data['Spec'] = name
_data['Value'] = value
data.append(_data)
df = pd.DataFrame(data)
columns = ['Spec', 'Value']
if 'Description' in df.columns:
columns += ['Description']
if 'Unit' in df.columns:
columns += ['Unit']
df = df[columns]
df = df.fillna('')
return (
"Machine Specifications: describing the state of the machine that Omniperf data was collected on." +
"\n" +
get_table_string(df, transpose=False, decimal=2))
def get_rocm_ver():
@@ -350,4 +481,4 @@ def total_xcds(archname, compute_partition):
if __name__ == "__main__":
print(MachineSpecs())
print(generate_machine_specs())
+11 -12
查看文件
@@ -50,6 +50,13 @@ def string_multiple_lines(source, width, max_rows):
return "\n".join(lines)
def get_table_string(df, transpose=False, decimal=2):
return tabulate(df.transpose() if transpose else df,
headers="keys",
tablefmt="fancy_grid",
floatfmt="." + str(decimal) + "f")
def show_all(args, runs, archConfigs, output):
"""
Show all panels with their data in plain text mode.
@@ -214,19 +221,11 @@ def show_all(args, runs, archConfigs, output):
# df when load it, because we need those items in column.
# For metric_table, we only need to show the data in column
# fash for now.
transpose = (type != "raw_csv_table"
and "columnwise" in table_config
and table_config["columnwise"] == True)
ss += (
tabulate(
(
df.transpose()
if type != "raw_csv_table"
and "columnwise" in table_config
and table_config["columnwise"] == True
else df
),
headers="keys",
tablefmt="fancy_grid",
floatfmt="." + str(args.decimal) + "f",
)
get_table_string(df, transpose=transpose, decimal=args.decimal)
+ "\n"
)
+2 -2
查看文件
@@ -299,8 +299,8 @@ def gen_sysinfo(workload_name, workload_dir, ip_blocks, app_cmd, skip_roof, roof
df = mspec.get_class_members()
# Append workload information to machine specs
df.insert(0, 'command', app_cmd)
df.insert(0,'workload_name', workload_name)
df['command'] = app_cmd
df['workload_name'] = workload_name
blocks = []
if ip_blocks == None: