2023-09-06 16:29:48 -05:00
##############################################################################bl
# MIT License
#
2025-01-23 13:09:32 -06:00
# Copyright (c) 2021 - 2025 Advanced Micro Devices, Inc. All Rights Reserved.
2023-09-06 16:29:48 -05:00
#
# 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
2025-01-02 13:29:47 -08:00
import glob
import io
import json
2024-03-08 17:20:08 -06:00
import locale
2023-09-06 16:29:48 -05:00
import logging
2023-10-31 15:11:17 -05:00
import os
2025-01-02 13:29:47 -08:00
import pathlib
2024-01-10 11:34:54 -06:00
import re
2023-10-31 15:11:17 -05:00
import selectors
2024-01-10 11:34:54 -06:00
import shutil
2025-01-02 13:29:47 -08:00
import subprocess
import sys
import time
from collections import OrderedDict
from itertools import product
2023-10-31 15:11:17 -05:00
from pathlib import Path as path
2025-01-02 13:29:47 -08:00
import pandas as pd
2023-10-31 15:11:17 -05:00
import config
2025-03-25 17:06:37 -05:00
from utils.logger import console_debug , console_error , console_log , console_warning
2025-03-21 02:02:58 -04:00
from utils.mi_gpu_spec import get_mi300_num_xcds
2023-10-31 15:11:17 -05:00
rocprof_cmd = ""
2025-01-02 13:29:47 -08:00
rocprof_args = ""
2023-09-06 16:29:48 -05:00
2024-02-22 15:40:02 -06:00
2025-03-24 17:43:29 -04:00
def is_tcc_channel_counter ( counter ):
return counter . startswith ( "TCC" ) and counter . endswith ( "]" )
2025-03-20 20:09:27 -04:00
2025-03-25 15:12:19 -04:00
def using_v1 ():
return "ROCPROF" in os . environ . keys () and os . environ [ "ROCPROF" ] . endswith ( "rocprof" )
2025-03-24 17:43:29 -04:00
def using_v3 ():
2025-03-25 16:33:12 -04:00
return "ROCPROF" in os . environ . keys () and os . environ [ "ROCPROF" ] . endswith ( "rocprofv3" )
2025-03-20 20:09:27 -04:00
2024-11-01 12:20:21 -04:00
def get_version ( rocprof_compute_home ) -> dict :
"""Return ROCm Compute Profiler versioning info"""
2024-04-16 15:07:49 -05:00
# symantic version info - note that version file(s) can reside in
# two locations depending on development vs formal install
2024-11-01 12:20:21 -04:00
searchDirs = [ rocprof_compute_home , rocprof_compute_home . parent ]
2024-04-16 15:07:49 -05:00
found = False
versionDir = None
for dir in searchDirs :
2025-01-02 13:29:47 -08:00
version = str ( path ( dir ) . joinpath ( "VERSION" ))
2024-04-16 15:07:49 -05:00
try :
with open ( version , "r" ) as file :
VER = file . read () . replace ( " \n " , "" )
found = True
versionDir = dir
break
except :
pass
if not found :
2024-04-25 18:43:20 +00:00
console_error ( "Cannot find VERSION file at {} " . format ( searchDirs ))
2023-10-31 15:11:17 -05:00
# git version info
2025-02-26 11:39:37 -05:00
try :
success , output = capture_subprocess_output (
[ "git" , "-C" , versionDir , "log" , "--pretty=format:%h" , "-n" , "1" ],
2023-10-31 15:11:17 -05:00
)
2025-02-26 11:39:37 -05:00
if success :
SHA = output
2023-10-31 15:11:17 -05:00
MODE = "dev"
2025-02-26 11:39:37 -05:00
else :
raise Exception ( output )
except :
2023-10-31 15:11:17 -05:00
try :
2025-02-26 11:39:37 -05:00
shaFile = path ( versionDir ) . joinpath ( "VERSION.sha" ) . absolute () . resolve ()
2023-10-31 15:11:17 -05:00
with open ( shaFile , "r" ) as file :
SHA = file . read () . replace ( " \n " , "" )
2025-02-26 11:39:37 -05:00
MODE = "release"
except Exception :
SHA = "unknown"
MODE = "unknown"
2023-10-31 15:11:17 -05:00
versionData = { "version" : VER , "sha" : SHA , "mode" : MODE }
return versionData
2024-02-22 15:40:02 -06:00
2023-10-31 15:11:17 -05:00
def get_version_display ( version , sha , mode ):
2024-02-22 15:40:02 -06:00
"""Pretty print versioning info"""
2023-10-31 15:11:17 -05:00
buf = io . StringIO ()
print ( "-" * 40 , file = buf )
2024-11-01 12:20:21 -04:00
print ( "rocprofiler-compute version: %s ( %s )" % ( version , mode ), file = buf )
2023-10-31 15:11:17 -05:00
print ( "Git revision: %s " % sha , file = buf )
print ( "-" * 40 , file = buf )
return buf . getvalue ()
2024-02-22 15:40:02 -06:00
2023-10-31 15:11:17 -05:00
def detect_rocprof ():
2024-02-22 15:40:02 -06:00
"""Detect loaded rocprof version. Resolve path and set cmd globally."""
2023-10-31 15:11:17 -05:00
global rocprof_cmd
2024-01-19 11:01:01 -06:00
# detect rocprof
2023-10-31 15:11:17 -05:00
if not "ROCPROF" in os . environ . keys ():
2023-12-06 09:16:26 -06:00
rocprof_cmd = "rocprof"
2023-10-31 15:11:17 -05:00
else :
rocprof_cmd = os . environ [ "ROCPROF" ]
2024-02-22 15:40:02 -06:00
2024-01-19 11:01:01 -06:00
# resolve rocprof path
2023-10-31 15:11:17 -05:00
rocprof_path = shutil . which ( rocprof_cmd )
if not rocprof_path :
rocprof_cmd = "rocprof"
2024-03-04 12:57:25 -06:00
console_warning (
"Unable to resolve path to %s binary. Reverting to default." % rocprof_cmd
)
2023-10-31 15:11:17 -05:00
rocprof_path = shutil . which ( rocprof_cmd )
2024-01-19 11:01:01 -06:00
if not rocprof_path :
2024-03-04 12:57:25 -06:00
console_error (
"Please verify installation or set ROCPROF environment variable with full path."
)
2023-10-31 15:11:17 -05:00
else :
# Resolve any sym links in file path
2025-01-02 13:29:47 -08:00
rocprof_path = str ( path ( rocprof_path . rstrip ( " \n " )) . resolve ())
2024-03-08 15:05:23 -06:00
console_debug ( "ROC Profiler: " + str ( rocprof_path ))
2025-01-02 13:29:47 -08:00
console_debug ( "rocprof_cmd is {} " . format ( str ( rocprof_cmd )))
return rocprof_cmd # TODO: Do we still need to return this? It's not being used in the function call
def store_app_cmd ( args ):
global rocprof_args
rocprof_args = args
2024-03-04 12:57:25 -06:00
2023-10-31 15:11:17 -05:00
2025-03-24 17:43:29 -04:00
def capture_subprocess_output (
subprocess_args , new_env = None , profileMode = False , enable_logging = True
):
console_debug ( "subprocess" , "Running: " + " " . join ( subprocess_args ))
2023-10-31 15:11:17 -05:00
# Start subprocess
# bufsize = 1 means output is line buffered
# universal_newlines = True is required for line buffering
2024-01-19 11:01:01 -06:00
process = (
subprocess . Popen (
subprocess_args ,
bufsize = 1 ,
stdout = subprocess . PIPE ,
stderr = subprocess . STDOUT ,
universal_newlines = True ,
)
if new_env == None
else subprocess . Popen (
subprocess_args ,
bufsize = 1 ,
stdout = subprocess . PIPE ,
stderr = subprocess . STDOUT ,
universal_newlines = True ,
env = new_env ,
)
)
2023-10-31 15:11:17 -05:00
# Create callback function for process output
buf = io . StringIO ()
2024-01-19 11:01:01 -06:00
2023-10-31 15:11:17 -05:00
def handle_output ( stream , mask ):
2024-10-03 11:27:55 -04:00
try :
# Because the process' output is line buffered, there's only ever one
# line to read when this function is called
line = stream . readline ()
buf . write ( line )
2025-03-24 17:43:29 -04:00
if enable_logging :
if profileMode :
console_log ( rocprof_cmd , line . strip (), indent_level = 1 )
else :
console_log ( line . strip ())
2024-10-03 11:27:55 -04:00
except UnicodeDecodeError :
# Skip this line
pass
2023-10-31 15:11:17 -05:00
# Register callback for an "available for read" event from subprocess' stdout stream
selector = selectors . DefaultSelector ()
selector . register ( process . stdout , selectors . EVENT_READ , handle_output )
# Loop until subprocess is terminated
while process . poll () is None :
# Wait for events and handle them with their registered callbacks
events = selector . select ()
for key , mask in events :
callback = key . data
callback ( key . fileobj , mask )
# Get process return code
return_code = process . wait ()
selector . close ()
2024-02-22 15:40:02 -06:00
success = return_code == 0
2023-10-31 15:11:17 -05:00
# Store buffered output
output = buf . getvalue ()
buf . close ()
return ( success , output )
2024-03-01 11:52:31 -06:00
2025-01-02 13:29:47 -08:00
# Create a dictionary that maps agent ID to agent objects
def get_agent_dict ( data ):
agents = data [ "rocprofiler-sdk-tool" ][ 0 ][ "agents" ]
agent_map = {}
for agent in agents :
agent_id = agent [ "id" ][ "handle" ]
agent_map [ agent_id ] = agent
return agent_map
# Returns a dictionary that maps agent ID to GPU ID
# starting at 0.
def get_gpuid_dict ( data ):
agents = data [ "rocprofiler-sdk-tool" ][ 0 ][ "agents" ]
agent_list = []
# Get agent ID and node_id for GPU agents only
for agent in agents :
if agent [ "type" ] == 2 :
agent_id = agent [ "id" ][ "handle" ]
node_id = agent [ "node_id" ]
agent_list . append (( agent_id , node_id ))
# Sort by node ID
agent_list . sort ( key = lambda x : x [ 1 ])
# Map agent ID to node id
map = {}
gpu_id = 0
for agent in agent_list :
map [ agent [ 0 ]] = gpu_id
gpu_id = gpu_id + 1
return map
# Create a dictionary that maps counter ID to counter objects
def v3_json_get_counters ( data ):
counters = data [ "rocprofiler-sdk-tool" ][ 0 ][ "counters" ]
counter_map = {}
for counter in counters :
counter_id = counter [ "id" ][ "handle" ]
agent_id = counter [ "agent_id" ][ "handle" ]
counter_map [( agent_id , counter_id )] = counter
return counter_map
def v3_json_get_dispatches ( data ):
records = data [ "rocprofiler-sdk-tool" ][ 0 ][ "buffer_records" ]
records_map = {}
for rec in records [ "kernel_dispatch" ]:
id = rec [ "correlation_id" ][ "internal" ]
records_map [ id ] = rec
return records_map
def v3_json_to_csv ( json_file_path , csv_file_path ):
f = open ( json_file_path , "rt" )
data = json . load ( f )
dispatch_records = v3_json_get_dispatches ( data )
dispatches = data [ "rocprofiler-sdk-tool" ][ 0 ][ "callback_records" ][ "counter_collection" ]
kernel_symbols = data [ "rocprofiler-sdk-tool" ][ 0 ][ "kernel_symbols" ]
agents = get_agent_dict ( data )
pid = data [ "rocprofiler-sdk-tool" ][ 0 ][ "metadata" ][ "pid" ]
gpuid_map = get_gpuid_dict ( data )
counter_info = v3_json_get_counters ( data )
# CSV headers. If there are no dispatches we still end up with a valid CSV file.
csv_data = dict . fromkeys (
[
"Dispatch_ID" ,
"GPU_ID" ,
"Queue_ID" ,
"PID" ,
"TID" ,
"Grid_Size" ,
"Workgroup_Size" ,
"LDS_Per_Workgroup" ,
"Scratch_Per_Workitem" ,
"Arch_VGPR" ,
"Accum_VGPR" ,
"SGPR" ,
"Wave_Size" ,
"Kernel_Name" ,
"Start_Timestamp" ,
"End_Timestamp" ,
"Correlation_ID" ,
]
)
for key in csv_data :
csv_data [ key ] = []
for d in dispatches :
dispatch_info = d [ "dispatch_data" ][ "dispatch_info" ]
agent_id = dispatch_info [ "agent_id" ][ "handle" ]
kernel_id = dispatch_info [ "kernel_id" ]
row = {}
row [ "Dispatch_ID" ] = dispatch_info [ "dispatch_id" ]
row [ "GPU_ID" ] = gpuid_map [ agent_id ]
2023-10-31 15:11:17 -05:00
2025-01-02 13:29:47 -08:00
row [ "Queue_ID" ] = dispatch_info [ "queue_id" ][ "handle" ]
row [ "PID" ] = pid
row [ "TID" ] = d [ "thread_id" ]
2024-03-01 11:52:31 -06:00
2025-01-02 13:29:47 -08:00
grid_size = dispatch_info [ "grid_size" ]
row [ "Grid_Size" ] = grid_size [ "x" ] * grid_size [ "y" ] * grid_size [ "z" ]
wg = dispatch_info [ "workgroup_size" ]
row [ "Workgroup_Size" ] = wg [ "x" ] * wg [ "y" ] * wg [ "z" ]
row [ "LDS_Per_Workgroup" ] = d [ "lds_block_size_v" ]
row [ "Scratch_Per_Workitem" ] = kernel_symbols [ kernel_id ][ "private_segment_size" ]
row [ "Arch_VGPR" ] = d [ "arch_vgpr_count" ]
# TODO: Accum VGPR is missing from rocprofv3 output.
row [ "Accum_VGPR" ] = 0
row [ "SGPR" ] = d [ "sgpr_count" ]
row [ "Wave_Size" ] = agents [ agent_id ][ "wave_front_size" ]
row [ "Kernel_Name" ] = kernel_symbols [ kernel_id ][ "formatted_kernel_name" ]
id = d [ "dispatch_data" ][ "correlation_id" ][ "internal" ]
rec = dispatch_records [ id ]
row [ "Start_Timestamp" ] = rec [ "start_timestamp" ]
row [ "End_Timestamp" ] = rec [ "end_timestamp" ]
row [ "Correlation_ID" ] = d [ "dispatch_data" ][ "correlation_id" ][ "external" ]
# Get counters
ctrs = {}
records = d [ "records" ]
for r in records :
ctr_id = r [ "counter_id" ][ "handle" ]
value = r [ "value" ]
name = counter_info [( agent_id , ctr_id )][ "name" ]
if name . endswith ( "_ACCUM" ):
# It's an accumulate counter. Omniperf expects the accumulated value
# to be in SQ_ACCUM_PREV_HIRES.
name = "SQ_ACCUM_PREV_HIRES"
# Some counters appear multiple times and need to be summed
if name in ctrs :
ctrs [ name ] += value
else :
ctrs [ name ] = value
# Append counter values
for ctr , value in ctrs . items ():
row [ ctr ] = value
# Add row to CSV data
for col_name , value in row . items ():
if col_name not in csv_data :
csv_data [ col_name ] = []
csv_data [ col_name ] . append ( value )
df = pd . DataFrame ( csv_data )
df . to_csv ( csv_file_path , index = False )
def v3_counter_csv_to_v2_csv ( counter_file , agent_info_filepath , converted_csv_file ):
"""
Convert the counter file of csv output for a certain csv from rocprofv3 format to rocprfv2 format.
This function is not for use of other csv out file such as kernel trace file.
"""
pd_counter_collections = pd . read_csv ( counter_file )
pd_agent_info = pd . read_csv ( agent_info_filepath )
result = pd_counter_collections . pivot_table (
index = [
"Correlation_Id" ,
"Dispatch_Id" ,
"Agent_Id" ,
"Queue_Id" ,
"Process_Id" ,
"Thread_Id" ,
"Grid_Size" ,
"Kernel_Id" ,
"Kernel_Name" ,
"Workgroup_Size" ,
"LDS_Block_Size" ,
"Scratch_Size" ,
"VGPR_Count" ,
"SGPR_Count" ,
"Start_Timestamp" ,
"End_Timestamp" ,
],
columns = "Counter_Name" ,
values = "Counter_Value" ,
) . reset_index ()
2025-03-25 16:33:12 -04:00
# NB: Agent_Id is int in older rocporfv3, now switched to string with prefix "Agent ". We need to make sure handle both cases.
console_debug (
"The type of Agent ID from counter csv file is {} " . format (
result [ "Agent_Id" ] . dtype
)
)
if result [ "Agent_Id" ] . dtype == "object" :
2025-03-27 16:28:00 -04:00
try :
result [ "Agent_Id" ] = (
result [ "Agent_Id" ]
. apply ( lambda x : int ( re . search ( r "Agent (\d+)" , x ) . group ( 1 )))
. astype ( "int64" )
)
except Exception as e :
console_error (
'Parsing rocprofv3 csv output: Error of getting "Agent_Id", the error message " {} "' . format (
e
)
)
2025-03-25 16:33:12 -04:00
2025-01-02 13:29:47 -08:00
# Grab the Wave_Front_Size column from agent info
result = result . merge (
pd_agent_info [[ "Node_Id" , "Wave_Front_Size" ]],
left_on = "Agent_Id" ,
right_on = "Node_Id" ,
how = "left" ,
)
# Map agent ID (Node_Id) to GPU_ID
gpu_id_map = {}
gpu_id = 0
for idx , row in pd_agent_info . iterrows ():
if row [ "Agent_Type" ] == "GPU" :
agent_id = row [ "Node_Id" ]
gpu_id_map [ agent_id ] = gpu_id
gpu_id = gpu_id + 1
# Update Agent_Id for each record to match GPU ID
for idx , row in result [ "Agent_Id" ] . items ():
agent_id = result . at [ idx , "Agent_Id" ]
result . at [ idx , "Agent_Id" ] = gpu_id_map [ agent_id ]
# Accum_VGPR is currently missing in rocprofv3 output
result [ "Accum_VGPR" ] = 0
# Drop the 'Node_Id' column if you don't need it in the final DataFrame
result . drop ( columns = "Node_Id" , inplace = True )
result [ "Accum_VGPR" ] = 0
name_mapping = {
"Dispatch_Id" : "Dispatch_ID" ,
"Agent_Id" : "GPU_ID" ,
"Queue_Id" : "Queue_ID" ,
"Process_Id" : "PID" ,
"Thread_Id" : "TID" ,
"Grid_Size" : "Grid_Size" ,
"Workgroup_Size" : "Workgroup_Size" ,
"LDS_Block_Size" : "LDS_Per_Workgroup" ,
"Scratch_Size" : "Scratch_Per_Workitem" ,
"VGPR_Count" : "Arch_VGPR" ,
# "":"Accum_VGPR",
"SGPR_Count" : "SGPR" ,
"Wave_Front_Size" : "Wave_Size" ,
"Kernel_Name" : "Kernel_Name" ,
"Start_Timestamp" : "Start_Timestamp" ,
"End_Timestamp" : "End_Timestamp" ,
"Correlation_Id" : "Correlation_ID" ,
"Kernel_Id" : "Kernel_ID" ,
}
result . rename ( columns = name_mapping , inplace = True )
index = [
"Dispatch_ID" ,
"GPU_ID" ,
"Queue_ID" ,
"PID" ,
"TID" ,
"Grid_Size" ,
"Workgroup_Size" ,
"LDS_Per_Workgroup" ,
"Scratch_Per_Workitem" ,
"Arch_VGPR" ,
"Accum_VGPR" ,
"SGPR" ,
"Wave_Size" ,
"Kernel_Name" ,
"Start_Timestamp" ,
"End_Timestamp" ,
"Correlation_ID" ,
"Kernel_ID" ,
]
remaining_column_names = [ col for col in result . columns if col not in index ]
index = index + remaining_column_names
result = result . reindex ( columns = index )
# Rename the accumulate counter to SQ_ACCUM_PREV_HIRES.
for col in result . columns :
if col . endswith ( "_ACCUM" ):
result . rename ( columns = { col : "SQ_ACCUM_PREV_HIRES" }, inplace = True )
result . to_csv ( converted_csv_file , index = False )
def run_prof (
fname , profiler_options , workload_dir , mspec , loglevel , format_rocprof_output
):
time_0 = time . time ()
fbase = path ( fname ) . stem
console_debug ( "pmc file: %s " % path ( fname ) . name )
2023-10-31 15:11:17 -05:00
2025-03-20 20:09:27 -04:00
path_counter_config_yaml = path ( fname ) . with_suffix ( ".yaml" )
2024-01-11 12:52:27 -06:00
# standard rocprof options
2024-02-22 15:40:02 -06:00
default_options = [ "-i" , fname ]
2024-01-11 12:52:27 -06:00
options = default_options + profiler_options
2025-03-20 20:09:27 -04:00
if path_counter_config_yaml . exists ():
options = [ "-E" , str ( path_counter_config_yaml )] + options
2024-01-11 12:52:27 -06:00
2024-01-19 11:01:01 -06:00
# set required env var for mi300
new_env = None
2024-03-01 11:52:31 -06:00
if (
mspec . gpu_model . lower () == "mi300x_a0"
or mspec . gpu_model . lower () == "mi300x_a1"
or mspec . gpu_model . lower () == "mi300a_a0"
or mspec . gpu_model . lower () == "mi300a_a1"
2024-01-19 11:01:01 -06:00
):
new_env = os . environ . copy ()
new_env [ "ROCPROFILER_INDIVIDUAL_XCC_MODE" ] = "1"
2025-02-10 17:59:03 -05:00
is_timestamps = False
if path ( fname ) . name == "timestamps.txt" :
is_timestamps = True
2025-01-02 13:29:47 -08:00
time_1 = time . time ()
2025-03-20 20:09:27 -04:00
console_debug ( "rocprof command: {} " . format ([ rocprof_cmd ] + options ))
2024-01-11 12:52:27 -06:00
# profile the app
2024-01-19 11:01:01 -06:00
if new_env :
2024-03-08 15:05:23 -06:00
success , output = capture_subprocess_output (
[ rocprof_cmd ] + options , new_env = new_env , profileMode = True
)
2024-01-19 11:01:01 -06:00
else :
2024-03-08 15:05:23 -06:00
success , output = capture_subprocess_output (
[ rocprof_cmd ] + options , profileMode = True
)
2024-01-11 12:52:27 -06:00
2025-01-02 13:29:47 -08:00
time_2 = time . time ()
console_debug (
"Finishing subprocess of fname {} , the time it takes was {} m {} sec " . format (
fname , int (( time_2 - time_1 ) / 60 ), str (( time_2 - time_1 ) % 60 )
)
)
2024-01-11 12:52:27 -06:00
if not success :
2024-03-07 11:00:52 -06:00
if loglevel > logging . INFO :
for line in output . splitlines ():
2024-03-08 15:05:23 -06:00
console_error ( output , exit = False )
2024-03-07 11:00:52 -06:00
console_error ( "Profiling execution failed." )
2024-01-19 11:01:01 -06:00
2025-02-22 14:07:14 -05:00
results_files = []
2024-06-12 13:27:08 -04:00
if rocprof_cmd . endswith ( "v2" ):
# rocprofv2 has separate csv files for each process
results_files = glob . glob ( workload_dir + "/out/pmc_1/results_*.csv" )
2025-03-04 14:41:02 -05:00
# Combine results into single CSV file
combined_results = pd . concat (
[ pd . read_csv ( f ) for f in results_files ], ignore_index = True
)
# Overwrite column to ensure unique IDs.
combined_results [ "Dispatch_ID" ] = range ( 0 , len ( combined_results ))
combined_results . to_csv (
workload_dir + "/out/pmc_1/results_" + fbase + ".csv" , index = False
)
2025-02-22 14:07:14 -05:00
elif rocprof_cmd . endswith ( "v3" ):
# rocprofv3 requires additional processing for each process
results_files = process_rocprofv3_output (
format_rocprof_output , workload_dir , is_timestamps
2024-06-12 13:27:08 -04:00
)
2025-02-22 14:07:14 -05:00
# kokkos trace output processing for --kokkos-trace
# TODO: as rocprofv3 --kokkos-trace feature improves, rocprof-compute should make updates accordingly
if "--kokkos-trace" in options :
console_debug (
"[run_prof] --kokkos-trace detected, handling *_marker_api_trace.csv outputs."
2025-01-02 13:29:47 -08:00
)
2025-02-22 14:07:14 -05:00
process_kokkos_trace_output ( workload_dir , fbase )
# TODO: add hip trace output processing
2025-01-02 13:29:47 -08:00
2025-03-04 14:41:02 -05:00
# Combine results into single CSV file
2025-03-18 11:26:17 -04:00
if results_files :
combined_results = pd . concat (
[ pd . read_csv ( f ) for f in results_files ], ignore_index = True
)
else :
console_warning (
f "Cannot write results for { fbase } .csv due to no counter csv files generated."
)
return
2025-01-02 13:29:47 -08:00
2025-03-04 14:41:02 -05:00
# Overwrite column to ensure unique IDs.
combined_results [ "Dispatch_ID" ] = range ( 0 , len ( combined_results ))
2025-01-02 13:29:47 -08:00
2025-03-04 14:41:02 -05:00
combined_results . to_csv (
workload_dir + "/out/pmc_1/results_" + fbase + ".csv" , index = False
)
2025-01-02 13:29:47 -08:00
2025-03-25 15:12:19 -04:00
if new_env and not using_v3 () and not using_v1 ():
2024-01-19 11:01:01 -06:00
# flatten tcc for applicable mi300 input
f = path ( workload_dir + "/out/pmc_1/results_" + fbase + ".csv" )
2025-03-25 17:49:42 -04:00
xcds = total_xcds ( mspec . gpu_model , mspec . compute_partition )
2024-06-03 17:04:32 +00:00
df = flatten_tcc_info_across_xcds ( f , xcds , int ( mspec . _l2_banks ))
2024-01-19 11:01:01 -06:00
df . to_csv ( f , index = False )
2024-01-11 12:52:27 -06:00
2025-01-02 13:29:47 -08:00
if path ( workload_dir + "/out" ) . exists ():
2024-01-19 11:01:01 -06:00
# copy and remove out directory if needed
shutil . copyfile (
workload_dir + "/out/pmc_1/results_" + fbase + ".csv" ,
workload_dir + "/" + fbase + ".csv" ,
)
# Remove temp directory
shutil . rmtree ( workload_dir + "/" + "out" )
2024-01-19 13:46:01 -06:00
2024-01-22 16:13:09 -06:00
# Standardize rocprof headers via overwrite
# {<key to remove>: <key to replace>}
2024-01-19 13:46:01 -06:00
output_headers = {
2024-01-22 16:13:09 -06:00
# ROCm-6.1.0 specific csv headers
2024-01-19 13:46:01 -06:00
"KernelName" : "Kernel_Name" ,
"Index" : "Dispatch_ID" ,
"grd" : "Grid_Size" ,
"gpu-id" : "GPU_ID" ,
"wgr" : "Workgroup_Size" ,
"lds" : "LDS_Per_Workgroup" ,
"scr" : "Scratch_Per_Workitem" ,
"sgpr" : "SGPR" ,
"arch_vgpr" : "Arch_VGPR" ,
"accum_vgpr" : "Accum_VGPR" ,
"BeginNs" : "Start_Timestamp" ,
"EndNs" : "End_Timestamp" ,
2024-01-22 16:13:09 -06:00
# ROCm-6.0.0 specific csv headers
"GRD" : "Grid_Size" ,
"WGR" : "Workgroup_Size" ,
"LDS" : "LDS_Per_Workgroup" ,
"SCR" : "Scratch_Per_Workitem" ,
"ACCUM_VGPR" : "Accum_VGPR" ,
2024-01-19 13:46:01 -06:00
}
df = pd . read_csv ( workload_dir + "/" + fbase + ".csv" )
df . rename ( columns = output_headers , inplace = True )
df . to_csv ( workload_dir + "/" + fbase + ".csv" , index = False )
2024-02-22 15:40:02 -06:00
2024-03-08 15:05:23 -06:00
2025-02-22 14:07:14 -05:00
def process_rocprofv3_output ( rocprof_output , workload_dir , is_timestamps ):
"""
rocprofv3 specific output processing.
takes care of json or csv formats, for csv format, additional processing is performed.
"""
results_files_csv = {}
if rocprof_output == "json" :
results_files_json = glob . glob ( workload_dir + "/out/pmc_1/*/*.json" )
for json_file in results_files_json :
csv_file = pathlib . Path ( json_file ) . with_suffix ( ".csv" )
v3_json_to_csv ( json_file , csv_file )
results_files_csv = glob . glob ( workload_dir + "/out/pmc_1/*/*.csv" )
2025-03-25 17:49:42 -04:00
2025-02-22 14:07:14 -05:00
elif rocprof_output == "csv" :
counter_info_csvs = glob . glob (
workload_dir + "/out/pmc_1/*/*_counter_collection.csv"
)
existing_counter_files_csv = [ d for d in counter_info_csvs if path ( d ) . is_file ()]
2025-03-25 17:49:42 -04:00
if existing_counter_files_csv :
2025-02-22 14:07:14 -05:00
for counter_file in existing_counter_files_csv :
2025-03-25 17:49:42 -04:00
counter_path = path ( counter_file )
current_dir = counter_path . parent
agent_info_filepath = current_dir / counter_path . name . replace (
"_counter_collection" , "_agent_info"
2025-02-22 14:07:14 -05:00
)
2025-03-25 17:49:42 -04:00
if not agent_info_filepath . is_file ():
2025-02-22 14:07:14 -05:00
raise ValueError (
' {} has no coresponding "agent info" file' . format ( counter_file )
)
2025-03-25 17:49:42 -04:00
converted_csv_file = current_dir / counter_path . name . replace (
"_counter_collection" , "_converted"
2025-02-22 14:07:14 -05:00
)
2025-03-25 17:49:42 -04:00
try :
v3_counter_csv_to_v2_csv (
counter_file , str ( agent_info_filepath ), str ( converted_csv_file )
)
except Exception as e :
console_warning (
f "Error converting { counter_file } from v3 to v2 csv: { e } "
)
return []
2025-02-22 14:07:14 -05:00
results_files_csv = glob . glob ( workload_dir + "/out/pmc_1/*/*_converted.csv" )
elif is_timestamps :
# when the input is timestamps, we know counter csv file is not generated and will instead parse kernel trace file
results_files_csv = glob . glob (
workload_dir + "/out/pmc_1/*/*_kernel_trace.csv"
)
else :
# when the input is not for timestamps, and counter csv file is not generated, we assume failed rocprof run and will completely bypass the file generation and merging for current pmc
2025-03-18 11:26:17 -04:00
results_files_csv = []
console_warning ( "No counter csv files generated, rocprofv3 run failed!!!" )
2025-02-22 14:07:14 -05:00
else :
console_error ( "The output file of rocprofv3 can only support json or csv!!!" )
return results_files_csv
def process_kokkos_trace_output ( workload_dir , fbase ):
# marker api trace csv files are generated for each process
marker_api_trace_csvs = glob . glob (
workload_dir + "/out/pmc_1/*/*_marker_api_trace.csv"
)
existing_marker_files_csv = [ d for d in marker_api_trace_csvs if path ( d ) . is_file ()]
# concate and output marker api trace info
combined_results = pd . concat (
[ pd . read_csv ( f ) for f in existing_marker_files_csv ], ignore_index = True
)
combined_results . to_csv (
workload_dir + "/out/pmc_1/results_" + fbase + "_marker_api_trace.csv" ,
index = False ,
)
if path ( workload_dir + "/out" ) . exists ():
shutil . copyfile (
workload_dir + "/out/pmc_1/results_" + fbase + "_marker_api_trace.csv" ,
workload_dir + "/" + fbase + "_marker_api_trace.csv" ,
)
2023-10-31 15:11:17 -05:00
def replace_timestamps ( workload_dir ):
df_stamps = pd . read_csv ( workload_dir + "/timestamps.csv" )
2024-01-19 13:46:01 -06:00
if "Start_Timestamp" in df_stamps . columns and "End_Timestamp" in df_stamps . columns :
2023-10-31 15:11:17 -05:00
# Update timestamps for all *.csv output files
for fname in glob . glob ( workload_dir + "/" + "*.csv" ):
2023-12-19 21:10:32 -06:00
if path ( fname ) . name != "sysinfo.csv" :
df_pmc_perf = pd . read_csv ( fname )
2023-10-31 15:11:17 -05:00
2024-01-19 13:46:01 -06:00
df_pmc_perf [ "Start_Timestamp" ] = df_stamps [ "Start_Timestamp" ]
df_pmc_perf [ "End_Timestamp" ] = df_stamps [ "End_Timestamp" ]
2023-12-19 21:10:32 -06:00
df_pmc_perf . to_csv ( fname , index = False )
2023-10-31 15:11:17 -05:00
else :
2024-03-04 12:57:25 -06:00
console_warning (
"Incomplete profiling data detected. Unable to update timestamps. \n "
)
2023-10-31 15:11:17 -05:00
2024-03-01 11:52:31 -06:00
def gen_sysinfo (
2024-03-01 14:17:12 -06:00
workload_name , workload_dir , ip_blocks , app_cmd , skip_roof , roof_only , mspec , soc
2024-03-01 11:52:31 -06:00
):
2025-03-21 02:02:58 -04:00
console_debug ( "[gen_sysinfo]" )
2024-02-20 13:34:56 -06:00
df = mspec . get_class_members ()
2024-03-01 11:52:31 -06:00
2024-02-20 13:34:56 -06:00
# Append workload information to machine specs
2024-03-01 11:52:31 -06:00
df [ "command" ] = app_cmd
df [ "workload_name" ] = workload_name
2023-10-31 15:11:17 -05:00
blocks = []
2025-03-10 14:42:56 -04:00
if not ip_blocks :
2023-10-31 15:11:17 -05:00
t = [ "SQ" , "LDS" , "SQC" , "TA" , "TD" , "TCP" , "TCC" , "SPI" , "CPC" , "CPF" ]
blocks += t
else :
blocks += ip_blocks
2024-03-01 14:17:12 -06:00
if hasattr ( soc , "roofline_obj" ) and ( not skip_roof ):
2024-02-20 13:34:56 -06:00
blocks . append ( "roofline" )
2024-03-01 11:52:31 -06:00
df [ "ip_blocks" ] = "|" . join ( blocks )
2023-10-31 15:11:17 -05:00
2024-02-20 13:34:56 -06:00
# Save csv
df . to_csv ( workload_dir + "/" + "sysinfo.csv" , index = False )
2024-02-22 15:40:02 -06:00
2024-03-01 11:52:31 -06:00
2024-02-20 13:34:56 -06:00
def detect_roofline ( mspec ):
2024-03-06 17:01:07 -06:00
from utils import specs
2024-01-10 11:34:54 -06:00
rocm_ver = mspec . rocm_version [: 1 ]
2023-10-31 15:11:17 -05:00
os_release = path ( "/etc/os-release" ) . read_text ()
ubuntu_distro = specs . search ( r 'VERSION_ID="(.*?)"' , os_release )
rhel_distro = specs . search ( r 'PLATFORM_ID="(.*?)"' , os_release )
sles_distro = specs . search ( r 'VERSION_ID="(.*?)"' , os_release )
if "ROOFLINE_BIN" in os . environ . keys ():
rooflineBinary = os . environ [ "ROOFLINE_BIN" ]
2025-01-02 13:29:47 -08:00
if path ( rooflineBinary ) . exists ():
2024-04-25 18:43:20 +00:00
console_warning ( "roofline" , "Detected user-supplied binary" )
2024-11-01 12:20:21 -04:00
return {
"rocm_ver" : "override" ,
"distro" : "override" ,
"path" : rooflineBinary ,
}
2023-10-31 15:11:17 -05:00
else :
2024-03-11 11:26:05 -05:00
msg = "user-supplied path to binary not accessible"
2024-03-04 12:57:25 -06:00
msg += "--> ROOFLINE_BIN = %s \n " % target_binary
2024-03-11 11:26:05 -05:00
console_error ( "roofline" , msg )
2025-03-21 12:46:17 -04:00
elif (
rhel_distro == "platform:el8"
or rhel_distro == "platform:el9"
or rhel_distro == "platform:al8"
):
2023-10-31 15:11:17 -05:00
# Must be a valid RHEL machine
2023-12-06 09:16:26 -06:00
distro = "platform:el8"
2023-10-31 15:11:17 -05:00
elif (
2024-02-22 15:40:02 -06:00
( type ( sles_distro ) == str and len ( sles_distro ) >= 3 )
and sles_distro [: 2 ] == "15" # confirm string and len
and int ( sles_distro [ 3 ]) >= 3 # SLES15 and SP >= 3
2023-10-31 15:11:17 -05:00
):
# Must be a valid SLES machine
# Use SP3 binary for all forward compatible service pack versions
distro = "15.3"
2025-01-23 19:53:50 -05:00
elif ubuntu_distro == "20.04" or ubuntu_distro == "22.04" or ubuntu_distro == "24.04" :
2023-10-31 15:11:17 -05:00
# Must be a valid Ubuntu machine
distro = ubuntu_distro
else :
2024-03-04 12:57:25 -06:00
console_error ( "roofline" , "Cannot find a valid binary for your operating system" )
2023-10-31 15:11:17 -05:00
target_binary = { "rocm_ver" : rocm_ver , "distro" : distro }
return target_binary
2024-02-22 15:40:02 -06:00
2023-10-31 15:11:17 -05:00
def run_rocscope ( args , fname ):
# profile the app
if args . use_rocscope == True :
result = shutil . which ( "rocscope" )
if result :
rs_cmd = [
result . stdout . decode ( "ascii" ) . strip (),
"metrics" ,
"-p" ,
args . path ,
"-n" ,
args . name ,
2024-02-22 15:40:02 -06:00
"-t" ,
2023-10-31 15:11:17 -05:00
fname ,
"--" ,
]
for i in args . remaining . split ():
rs_cmd . append ( i )
2024-01-30 17:23:49 -06:00
console_log ( rs_cmd )
2024-03-04 12:57:25 -06:00
success , output = capture_subprocess_output ( rs_cmd )
2023-10-31 15:11:17 -05:00
if not success :
2024-01-30 17:23:49 -06:00
console_error ( result . stderr . decode ( "ascii" ))
2023-10-31 15:11:17 -05:00
2024-03-01 11:52:31 -06:00
2024-02-20 13:34:56 -06:00
def mibench ( args , mspec ):
2024-03-03 14:38:28 -06:00
"""Run roofline microbenchmark to generate peek BW and FLOP measurements."""
2024-01-30 17:23:49 -06:00
console_log ( "roofline" , "No roofline data found. Generating..." )
2024-03-15 14:00:03 -05:00
distro_map = {
"platform:el8" : "rhel8" ,
2024-05-09 17:00:16 +00:00
"15.3" : "sles15sp5" ,
2024-03-15 14:00:03 -05:00
"20.04" : "ubuntu20_04" ,
2024-05-09 17:00:16 +00:00
"22.04" : "ubuntu20_04" ,
2025-01-23 19:53:50 -05:00
"24.04" : "ubuntu20_04" ,
2024-03-15 14:00:03 -05:00
}
2023-10-31 15:11:17 -05:00
2024-04-18 10:38:52 -05:00
binary_paths = []
2024-02-20 13:34:56 -06:00
target_binary = detect_roofline ( mspec )
2023-10-31 15:11:17 -05:00
if target_binary [ "rocm_ver" ] == "override" :
2024-04-18 10:38:52 -05:00
binary_paths . append ( target_binary [ "path" ])
2023-10-31 15:11:17 -05:00
else :
2024-04-18 10:38:52 -05:00
# check two potential locations for roofline binaries due to differences in
# development usage vs formal install
potential_paths = [
2024-11-01 12:20:21 -04:00
" %s /utils/rooflines/roofline" % config . rocprof_compute_home ,
" %s /bin/roofline" % config . rocprof_compute_home . parent . parent ,
2024-04-18 10:38:52 -05:00
]
for dir in potential_paths :
path_to_binary = (
dir
+ "-"
+ distro_map [ target_binary [ "distro" ]]
+ "-"
2025-01-02 13:29:47 -08:00
+ mspec . gpu_series . lower ()
2024-04-18 10:38:52 -05:00
+ "-rocm"
+ target_binary [ "rocm_ver" ]
)
binary_paths . append ( path_to_binary )
2023-10-31 15:11:17 -05:00
# Distro is valid but cant find rocm ver
2024-04-18 10:38:52 -05:00
found = False
for path in binary_paths :
2025-01-02 13:29:47 -08:00
if pathlib . Path ( path ) . exists ():
2024-04-18 10:38:52 -05:00
found = True
2024-04-22 09:00:18 -05:00
path_to_binary = path
2024-04-18 10:38:52 -05:00
break
if not found :
console_error ( "roofline" , "Unable to locate expected binary ( %s )." % binary_paths )
2023-10-31 15:11:17 -05:00
2024-05-09 17:00:16 +00:00
my_args = [
path_to_binary ,
"-o" ,
args . path + "/" + "roofline.csv" ,
"-d" ,
str ( args . device ),
]
2024-05-10 14:57:50 +00:00
if args . quiet :
2024-05-09 17:00:16 +00:00
my_args += "--quiet"
2023-10-31 15:11:17 -05:00
subprocess . run (
2024-05-09 17:00:16 +00:00
my_args ,
2024-02-22 15:40:02 -06:00
check = True ,
2023-10-31 15:11:17 -05:00
)
2024-01-19 11:01:01 -06:00
2024-02-22 15:40:02 -06:00
2024-06-03 17:04:32 +00:00
def flatten_tcc_info_across_xcds ( file , xcds , tcc_channel_per_xcd ):
2024-01-19 11:01:01 -06:00
"""
2024-06-03 17:04:32 +00:00
Flatten TCC per channel counters across all XCDs in partition.
2024-01-19 11:01:01 -06:00
NB: This func highly depends on the default behavior of rocprofv2 on MI300,
which might be broken anytime in the future!
"""
df_orig = pd . read_csv ( file )
# display(df_orig.info)
### prepare column headers
tcc_cols_orig = []
non_tcc_cols_orig = []
for c in df_orig . columns . to_list ():
if "TCC" in c :
tcc_cols_orig . append ( c )
else :
non_tcc_cols_orig . append ( c )
# print(tcc_cols_orig)
cols = non_tcc_cols_orig
tcc_cols_in_group = {}
2024-06-03 17:04:32 +00:00
for i in range ( 0 , xcds ):
2024-01-19 11:01:01 -06:00
tcc_cols_in_group [ i ] = []
for col in tcc_cols_orig :
2024-06-03 17:04:32 +00:00
for i in range ( 0 , xcds ):
2024-01-19 11:01:01 -06:00
# filter the channel index only
2024-01-31 09:34:50 -06:00
p = re . compile ( r "\[(\d+)\]" )
2024-01-19 11:01:01 -06:00
# pick up the 1st element only
2024-01-31 09:34:50 -06:00
r = (
lambda match : "["
2024-06-03 17:04:32 +00:00
+ str ( int ( match . group ( 1 )) + i * tcc_channel_per_xcd )
2024-01-31 09:34:50 -06:00
+ "]"
)
2024-01-19 11:01:01 -06:00
tcc_cols_in_group [ i ] . append ( re . sub ( pattern = p , repl = r , string = col ))
2024-06-03 17:04:32 +00:00
for i in range ( 0 , xcds ):
2024-01-19 11:01:01 -06:00
# print(tcc_cols_in_group[i])
cols += tcc_cols_in_group [ i ]
# print(cols)
df = pd . DataFrame ( columns = cols )
### Rearrange data with extended column names
# print(len(df_orig.index))
2024-06-03 17:04:32 +00:00
for idx in range ( 0 , len ( df_orig . index ), xcds ):
2024-01-19 11:01:01 -06:00
# assume the front none TCC columns are the same for all XCCs
df_non_tcc = df_orig . iloc [ idx ] . filter ( regex = r "^(?!.*TCC).*$" )
# display(df_non_tcc)
flatten_list = df_non_tcc . tolist ()
# extract all tcc from one dispatch
# NB: assuming default contiguous order might not be safe!
2024-06-03 17:04:32 +00:00
df_tcc_all = df_orig . iloc [ idx : ( idx + xcds )] . filter ( regex = "TCC" )
2024-01-19 11:01:01 -06:00
# display(df_tcc_all)
for idx , row in df_tcc_all . iterrows ():
flatten_list += row . tolist ()
# print(len(df.index), len(flatten_list), len(df.columns), flatten_list)
# NB: It is not the best perf to append a row once a time
df . loc [ len ( df . index )] = flatten_list
return df
2024-02-22 15:40:02 -06:00
2025-03-21 02:02:58 -04:00
def total_xcds ( gpu_model , compute_partition ):
"""
Returns the number of xcds for a gpu model and compute_partition pair.
"""
# For mi300 chips, return result from mi_gpu_spec
result = get_mi300_num_xcds ( gpu_model , compute_partition )
if result :
return result
# For other systems, use manual check
2024-06-03 17:04:32 +00:00
# check MI300 has a valid compute partition
2025-03-21 02:02:58 -04:00
mi300a_model = [ "mi300a_a0" , "mi300a_a1" ]
mi300x_model = [ "mi300x_a0" , "mi300x_a1" ]
mi308x_model = [ "mi308x" ]
2024-06-03 17:19:57 +00:00
if (
2025-03-21 02:02:58 -04:00
gpu_model . lower () in mi300a_model + mi300x_model + mi308x_model
2024-06-03 17:19:57 +00:00
and compute_partition == "NA"
):
2025-03-21 02:02:58 -04:00
console_error ( "Invalid compute partition found for {} " . format ( gpu_model ))
if gpu_model . lower () not in mi300a_model + mi300x_model + mi308x_model :
2024-06-03 17:04:32 +00:00
return 1
# from the whitepaper
# https://www.amd.com/content/dam/amd/en/documents/instinct-tech-docs/white-papers/amd-cdna-3-white-paper.pdf
if compute_partition . lower () == "spx" :
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300a_model :
2024-01-19 11:01:01 -06:00
return 6
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300x_model :
2024-01-19 11:01:01 -06:00
return 8
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi308x_model :
2024-06-03 17:04:32 +00:00
return 4
if compute_partition . lower () == "tpx" :
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300a_model :
2024-06-03 17:04:32 +00:00
return 2
if compute_partition . lower () == "dpx" :
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300x_model :
2024-01-19 11:01:01 -06:00
return 4
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi308x_model :
2024-06-03 17:04:32 +00:00
return 2
if compute_partition . lower () == "qpx" :
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300x_model :
2024-01-19 11:01:01 -06:00
return 2
2024-06-03 17:04:32 +00:00
if compute_partition . lower () == "cpx" :
2025-03-21 02:02:58 -04:00
if gpu_model . lower () in mi300x_model :
return 1
if gpu_model . lower () in mi308x_model :
2024-01-19 11:01:01 -06:00
return 1
2024-06-03 17:04:32 +00:00
# TODO implement other archs here as needed
console_error (
"Unknown compute partition / arch found for {} / {} " . format (
2025-03-21 02:02:58 -04:00
compute_partition , gpu_model
2024-06-03 17:04:32 +00:00
)
)
2024-02-22 15:40:02 -06:00
2024-01-24 12:44:42 -06:00
def get_submodules ( package_name ):
2024-02-22 15:40:02 -06:00
"""List all submodules for a target package"""
2024-01-24 12:44:42 -06:00
import importlib
import pkgutil
submodules = []
2024-02-22 15:40:02 -06:00
2024-01-24 12:44:42 -06:00
# walk all submodules in target package
package = importlib . import_module ( package_name )
for _ , name , _ in pkgutil . walk_packages ( package . __path__ ):
pretty_name = name . split ( "_" , 1 )[ 1 ] . replace ( "_" , "" )
# ignore base submodule, add all other
if pretty_name != "base" :
submodules . append ( pretty_name )
return submodules
2024-01-30 10:43:30 -06:00
2024-02-22 15:40:02 -06:00
2024-01-30 10:43:30 -06:00
def is_workload_empty ( path ):
2024-02-22 15:40:02 -06:00
"""Peek workload directory to verify valid profiling output"""
2024-01-30 10:43:30 -06:00
pmc_perf_path = path + "/pmc_perf.csv"
2025-01-02 13:29:47 -08:00
if pathlib . Path ( pmc_perf_path ) . is_file ():
2024-01-30 10:43:30 -06:00
temp_df = pd . read_csv ( pmc_perf_path )
if temp_df . dropna () . empty :
2024-01-30 17:23:49 -06:00
console_error (
"profiling"
2024-03-04 12:57:25 -06:00
"Found empty cells in %s . \n Profiling data could be corrupt."
% pmc_perf_path
2024-02-22 15:40:02 -06:00
)
2024-01-30 10:43:30 -06:00
else :
2024-03-04 12:57:25 -06:00
console_error ( "profiling" , "Cannot find pmc_perf.csv in %s " % path )
2024-03-08 15:05:23 -06:00
2024-01-30 17:23:49 -06:00
def print_status ( msg ):
2024-03-07 10:17:16 -06:00
msg_length = len ( msg )
2024-03-08 15:05:23 -06:00
console_log ( "" )
console_log ( "~" * ( msg_length + 1 ))
console_log ( msg )
console_log ( "~" * ( msg_length + 1 ))
console_log ( "" )
2024-03-08 17:20:08 -06:00
def set_locale_encoding ():
try :
2025-03-25 16:39:02 -05:00
# Attempt to set the locale to 'C.UTF-8'
2025-03-17 16:40:36 -04:00
locale . setlocale ( locale . LC_ALL , "C.UTF-8" )
2025-03-25 16:39:02 -05:00
except locale . Error :
# If 'C.UTF-8' is not available, check if the current locale is UTF-8 based
current_locale = locale . getdefaultlocale ()
if current_locale and "UTF-8" in current_locale [ 1 ]:
try :
locale . setlocale ( locale . LC_ALL , current_locale [ 0 ])
except locale . Error as error :
console_error (
"Failed to set locale to the current UTF-8-based locale." ,
exit = False ,
)
console_error ( error )
else :
console_error (
"Please ensure that a UTF-8-based locale is available on your system." ,
exit = False ,
)
2025-02-13 16:47:25 -05:00
def reverse_multi_index_df_pmc ( final_df ):
"""
Util function to decompose multi-index dataframe.
"""
# Check if the columns have more than one level
if len ( final_df . columns . levels ) < 2 :
raise ValueError ( "Input DataFrame does not have a multi-index column." )
# Extract the first level of the MultiIndex columns (the file names)
coll_levels = final_df . columns . get_level_values ( 0 ) . unique () . tolist ()
# Initialize the list of DataFrames
dfs = []
# Loop through each 'coll_level' and rebuild the DataFrames
for level in coll_levels :
# Select columns that belong to the current 'coll_level'
columns_for_level = final_df . xs ( level , axis = 1 , level = 0 )
# Append the DataFrame for this level
dfs . append ( columns_for_level )
# Return the list of DataFrames and the column levels
return dfs , coll_levels
def merge_counters_spatial_multiplex ( df_multi_index ):
"""
For spatial multiplexing, this merges counter values for the same kernel that runs on different devices. For time stamp, start time stamp will use median while for end time stamp, it will be equal to the summation between median start stamp and median delta time.
"""
non_counter_column_index = [
"Dispatch_ID" ,
"GPU_ID" ,
"Queue_ID" ,
"PID" ,
"TID" ,
"Grid_Size" ,
"Workgroup_Size" ,
"LDS_Per_Workgroup" ,
"Scratch_Per_Workitem" ,
"Arch_VGPR" ,
"Accum_VGPR" ,
"SGPR" ,
"Wave_Size" ,
"Kernel_Name" ,
"Start_Timestamp" ,
"End_Timestamp" ,
"Correlation_ID" ,
"Kernel_ID" ,
"Node" ,
]
expired_column_index = [
"Node" ,
"PID" ,
"TID" ,
"Queue_ID" ,
]
result_dfs = []
# TODO: will need optimize to avoid this convertion to single index format and do merge directly on multi-index dataframe
dfs , coll_levels = reverse_multi_index_df_pmc ( df_multi_index )
for df in dfs :
kernel_name_column_name = "Kernel_Name"
if not "Kernel_Name" in df and "Name" in df :
kernel_name_column_name = "Name"
# Find the values in Kernel_Name that occur more than once
kernel_single_occurances = df [ kernel_name_column_name ] . value_counts () . index
# Define a list to store the merged rows
result_data = []
for kernel_name in kernel_single_occurances :
# Get all rows for the current kernel_name
group = df [ df [ kernel_name_column_name ] == kernel_name ]
# Create a dictionary to store the merged row for the current group
merged_row = {}
# Process non-counter columns
for col in [
col for col in non_counter_column_index if col not in expired_column_index
]:
if col == "Start_Timestamp" :
# For Start_Timestamp, take the median
merged_row [ col ] = group [ "Start_Timestamp" ] . median ()
elif col == "End_Timestamp" :
# For End_Timestamp, calculate the median delta time
delta_time = group [ "End_Timestamp" ] - group [ "Start_Timestamp" ]
median_delta_time = delta_time . median ()
merged_row [ col ] = merged_row [ "Start_Timestamp" ] + median_delta_time
else :
# For other non-counter columns, take the first occurrence (0th row)
merged_row [ col ] = group . iloc [ 0 ][ col ]
# Process counter columns (assumed to be all columns not in non_counter_column_index)
counter_columns = [
col for col in group . columns if col not in non_counter_column_index
]
for counter_col in counter_columns :
# for counter columns, take the first non-none (or non-nan) value
current_valid_counter_group = group [ group [ counter_col ] . notna ()]
first_valid_value = (
current_valid_counter_group . iloc [ 0 ][ counter_col ]
if len ( current_valid_counter_group ) > 0
else None
)
merged_row [ counter_col ] = first_valid_value
# Append the merged row to the result list
result_data . append ( merged_row )
# Create a new DataFrame from the merged rows
result_dfs . append ( pd . DataFrame ( result_data ))
final_df = pd . concat ( result_dfs , keys = coll_levels , axis = 1 , copy = False )
return final_df
2025-03-10 14:42:56 -04:00
def convert_metric_id_to_panel_idx ( metric_id ):
# "4.02" -> 402
# "4.23" -> 423
# "4" -> 400
tokens = metric_id . split ( "." )
if len ( tokens ) == 1 :
return int ( tokens [ 0 ]) * 100
elif len ( tokens ) == 2 :
return int ( tokens [ 0 ]) * 100 + int ( tokens [ 1 ])
else :
raise Exception ( f "Invalid metric id: { metric_id } " )