Support stochastic pc sampling

[ROCm/rocprofiler-compute commit: d756aeb3fd]
Этот коммит содержится в:
Fei Zheng
2025-06-06 12:43:52 -06:00
коммит произвёл GitHub
родитель 0db02c5fd6
Коммит 243aa68712
14 изменённых файлов: 249 добавлений и 67 удалений
+4
Просмотреть файл
@@ -6,6 +6,10 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs.
### Added
* Stochastic (hardware-based) PC sampling has been enabled for AMD Instinct MI300X series and later accelerators.
* Sorting of PC sampling by type: offset or count.
* Add rocprof-compute Text User Interface (TUI) support for analyze mode
* A command line based user interface to support interactive single-run analysis
* launch with `--tui` option in analyze mode. i.e., `rocprof-compute analyze --tui`
+1 -1
Просмотреть файл
@@ -56,4 +56,4 @@ Please see the [pre-commit documentation](https://pre-commit.com/#quick-start) f
Below are some repository specific guidelines which are followed througout the repository.
Any future contributions should adhere to these guidelines:
* Use pathlib library functions instead of os.path for manipulating file paths
* Use the `pathlib` library functions instead of `os.path` for manipulating the file paths.
+1 -1
Просмотреть файл
@@ -41,4 +41,4 @@ tabulate python library: MIT
textual python library: MIT
textual_plotext python library: MIT
textual-fspicker python library: MIT
tqdm python library: MIT
tqdm python library: MIT
+1 -1
Просмотреть файл
@@ -19,4 +19,4 @@ CMD ["/bin/bash", "-c", "\
&& rm -rf build \
&& cmake -B build -S . \
&& make -C build standalonebinary \
"]
"]
+1 -1
Просмотреть файл
@@ -9,4 +9,4 @@ services:
security_opt:
- seccomp:unconfined
volumes:
- ../:/app
- ../:/app
+48
Просмотреть файл
@@ -0,0 +1,48 @@
.. meta::
:description: ROCm Compute Profiler: using PC sampling
:keywords: ROCm Compute Profiler, PC sampling
********************************************
Using PC sampling in ROCm Compute Profiler
********************************************
Program Counter (PC) sampling service for GPU profiling is a profiling technique that periodically samples the program counter during the GPU kernel execution to understand code execution patterns and hotspots.
ROCm Compute Profiler supports Host Trap PC sampling and Stochastic (Hardware-Based) PC sampling.
Host Trap PC sampling is enabled for AMD Instinct MI200 series and later
accelerators. Stochastic (hardware-based) PC sampling is enabled for
AMD Instinct MI300 series and later accelerators. Stochastic PC sampling provides additional information that tells whether a sampled wave issued an instruction for a particular PC. It also provides the reason
for not issuing the instruction (stall reason). This type of information is
particularly useful for understanding stalls during the kernel execution. The PC sampling can be used with profiling and analysis options.
---------------------
Profiling options
---------------------
For using profiling options for PC sampling the configuration needed are:
- ``--pc-sampling-method``: Should be either ``stochastic`` or ``host_trap``
- ``--pc-sampling-interval``: For stochastic sampling, the interval is in cycles. The finest granularity is 1 cycle. For host_trap sampling, the interval is in microsecond (DEFAULT: 1048576). The interval should be the power of 2. You are recommended to try to starting from 1048576, and lowering until reaching 65536.
**Sample command:**
.. code-block:: bash
rocprof-compute profile -n pc_test -b 21 --no-roof --pc-sampling-method stochastic --pc-sampling-interval 1048576 -VVV -- target_app
-----------------------
Analysis options
-----------------------
For using analysis options for PC sampling the configuration needed are:
- ``--pc-sampling-sorting-type``: ``offset`` or ``count``. The default option is ``offset``. ``offset`` is an assembly instruction offset in the code object.
**Sample command:**
.. code-block:: bash
rocprof-compute analyze -p workloads/pc_test/MI300A_A1/ -b 21 -k 0 --pc-sampling-sorting-type offset
.. note::
- PC sampling feature is currently in BETA version. To enable PC sampling,
you have to explicitly enable it with block index 21.
- To associate PC sampling info back to HIP source code, you need to build the profiling target app with ``-g`` to keep the symbols. Otherwise, PC sampling info would be only associated with assembly lines.
+2
Просмотреть файл
@@ -18,7 +18,9 @@ subtrees:
- caption: How to
entries:
- file: how-to/use.rst
- file: how-to/pc_sampling.rst
- file: how-to/profile/mode.rst
title: Use PC sampling
- file: how-to/analyze/mode.rst
entries:
- file: how-to/analyze/cli.rst
+1 -1
Просмотреть файл
@@ -1,6 +1,6 @@
.. meta::
:description: ROCm Compute Profiler external training resources
:keywords: Omniperf, examples, tutorials, videos, lesson, lessons, how
:keywords: ROCm Compute Profiler, examples, tutorials, videos, lesson, lessons, how
******************
Learning resources
+25 -6
Просмотреть файл
@@ -30,7 +30,7 @@ from pathlib import Path
def print_avail_arch(avail_arch: list):
ret_str = "\t\t\tList all available metrics for analysis on specified arch:"
ret_str = "\t\tList all available metrics for analysis on specified arch:"
for arch in avail_arch:
ret_str += "\n\t\t\t {}".format(arch)
return ret_str
@@ -324,13 +324,22 @@ Examples:
help="\t\t\tSet the format of output file of rocprof.",
)
profile_group.add_argument(
"--pc-sampling-method",
required=False,
metavar="",
dest="pc_sampling_method",
default="stochastic",
help="\t\t\tSet the method of pc sampling, stochastic or host_trap. Support stochastic only >= MI300",
)
profile_group.add_argument(
"--pc-sampling-interval",
required=False,
metavar="",
dest="pc_sampling_interval",
default=1,
help="\t\t\tSet the interval of pc sampling in microsecond (DEFAULT: 1).",
default=1048576,
help="\t\t\tSet the interval of pc sampling.\n\t\t\t For stochastic sampling, the interval is in cycles.\n\t\t\t For host_trap sampling, the interval is in microsecond (DEFAULT: 1048576).",
)
profile_group.add_argument(
@@ -395,7 +404,7 @@ Examples:
nargs="+",
type=str,
default=["FP32"],
help="Choose datatypes to view roofline PDFs for: (DEFAULT: FP32)\n\t\t\t FP4\n\t\t\t FP6\n\t\t\t FP8\n\t\t\t FP16\n\t\t\t BF16\n\t\t\t FP32\n\t\t\t FP64\n\t\t\t I8\n\t\t\t I32\n\t\t\t I64\n\t\t\t ",
help="\t\t\tChoose datatypes to view roofline PDFs for: (DEFAULT: FP32)\n\t\t\t FP4\n\t\t\t FP6\n\t\t\t FP8\n\t\t\t FP16\n\t\t\t BF16\n\t\t\t FP32\n\t\t\t FP64\n\t\t\t I8\n\t\t\t I32\n\t\t\t I64\n\t\t\t ",
)
# roofline_group.add_argument('-w', '--workgroups', required=False, default=-1, type=int, help="\t\t\tNumber of kernel workgroups (DEFAULT: 1024)")
@@ -584,7 +593,7 @@ Examples:
required=False,
default=False,
action="store_true",
help="\t\t\tMode of spatial multiplexing.",
help="\t\tMode of spatial multiplexing.",
)
analyze_group.add_argument(
"-o",
@@ -614,7 +623,17 @@ Examples:
nargs="+",
type=str,
default=["FP32"],
help="\t\t\tChoose datatypes to view roofline PDFs for: (DEFAULT: FP32)\n\t\t\t FP4\n\t\t\t FP6\n\t\t\t FP8\n\t\t\t FP16\n\t\t\t BF16\n\t\t\t FP32\n\t\t\t FP64\n\t\t\t I8\n\t\t\t I32\n\t\t\t I64\n\t\t\t ",
help="\t\tChoose datatypes to view roofline PDFs for: (DEFAULT: FP32)\n\t\t\t FP4\n\t\t\t FP6\n\t\t\t FP8\n\t\t\t FP16\n\t\t\t BF16\n\t\t\t FP32\n\t\t\t FP64\n\t\t\t I8\n\t\t\t I32\n\t\t\t I64\n\t\t\t ",
)
analyze_group.add_argument(
"--pc-sampling-sorting-type",
required=False,
metavar="",
dest="pc_sampling_sorting_type",
default="offset",
type=str,
help="\t\tSet the sorting type of pc sampling: offset or count (DEFAULT: offset).",
)
analyze_advanced_group.add_argument(
+1 -5
Просмотреть файл
@@ -72,11 +72,7 @@ class cli_analysis(OmniAnalyze_Base):
# create the loaded table
parser.load_table_data(
workload=self._runs[d[0]],
dir=d[0],
is_gui=False,
debug=self.get_args().debug,
verbose=self.get_args().verbose,
workload=self._runs[d[0]], dir=d[0], is_gui=False, args=self.get_args()
)
@demarcate
+1 -2
Просмотреть файл
@@ -164,8 +164,7 @@ class webui_analysis(OmniAnalyze_Base):
workload=base_data[base_run],
dir=self.dest_dir,
is_gui=True,
debug=self.get_args().debug,
verbose=self.get_args().verbose,
args=self.get_args(),
)
# ~~~~~~~~~~~~~~~~~~~~~~~
+1
Просмотреть файл
@@ -442,6 +442,7 @@ class RocProfCompute_Base:
):
start_run_prof = time.time()
pc_sampling_prof(
method=self.get_args().pc_sampling_method,
interval=self.get_args().pc_sampling_interval,
workload_dir=self.get_args().path,
appcmd=self.get_args().remaining,
+154 -46
Просмотреть файл
@@ -1043,33 +1043,83 @@ def search_pc_sampling_record(records):
"""
Search PC sampling records, and group and sort them
"""
# NB:
# The field stall_reason is vailid only for HW stochastic pc sampling.
# Todo: might save wavefront count for HW stochastic pc sampling?
grouped_data = defaultdict(
lambda: defaultdict(lambda: {"count": 0, "inst_index": None})
lambda: defaultdict(
lambda: {
"count": 0,
"inst_index": None,
"stall_reason": {
"NONE": 0,
"NO_INSTRUCTION_AVAILABLE": 0, # No instruction available in the instruction cache.
"ALU_DEPENDENCY": 0, # ALU dependency not resolved.
"WAITCNT": 0,
"INTERNAL_INSTRUCTION": 0, # Wave executes an internal instruction.
"BARRIER_WAIT": 0,
"ARBITER_NOT_WIN": 0, # The instruction did not win the arbiter.
"ARBITER_WIN_EX_STALL": 0, # Arbiter issued an instruction, but the execution pipe pushed it back from execution.
"OTHER_WAIT": 0, # Other types of wait (e.g., wait for XNACK acknowledgment).
"SLEEP_WAIT": 0,
"LAST": 0,
},
}
)
)
# Populate grouped_data
for item in records:
for i, item in enumerate(records):
pc_info = item["record"].get("pc", {})
code_object_id = pc_info.get("code_object_id")
code_object_offset = pc_info.get("code_object_offset")
snapshot = item["record"].get("snapshot", {})
inst_index = item.get("inst_index")
# Todo: opt me
if (
code_object_id is not None
and code_object_offset is not None
and inst_index is not None
):
grouped_data[code_object_id][code_object_offset]["count"] += 1
# NB: the write here could be duplicated. If there is perf issue, We might want to opt it.
grouped_data[code_object_id][code_object_offset]["inst_index"] = inst_index
if len(snapshot):
# NB: 54 is the length of prefix "ROCPROFILER_PC_SAMPLING_INSTRUCTION_NOT_ISSUED_REASON_"
grouped_data[code_object_id][code_object_offset]["stall_reason"][
snapshot.get("stall_reason")[54:]
] += 1
# print(
# inst_index,
# grouped_data[code_object_id][code_object_offset]["stall_reason"],
# )
if len(grouped_data) == 0:
console_warning("PC sampling: no pc sampling record found!")
return None
# print(grouped_data)
# Convert to sorted list of tuples (code_object_id, inst_index, code_object_offset, count)
sorted_counts = sorted(
[
(code_object_id, info["inst_index"], offset, info["count"])
(
code_object_id,
info["inst_index"],
offset,
info["count"],
# For info["stall_reason"], remove the zero entries, sorting the remaining items by their values in descending order
sorted(
((k, v) for k, v in info["stall_reason"].items() if v > 0),
key=lambda item: item[1],
reverse=True,
),
)
for code_object_id, offsets in grouped_data.items()
for offset, info in offsets.items()
],
@@ -1083,10 +1133,24 @@ def search_pc_sampling_record(records):
@demarcate
def load_pc_sampling_data_per_kernel(file_name, kernel_name):
def load_pc_sampling_data_per_kernel(
method: str, file_name: Path, kernel_name: str, sorting_type: str
) -> pd.DataFrame:
"""
Load PC sampling raw data from json file with given kernel name,
Load PC sampling raw data from json file with given method and kernel name,
count pc sampling and sort it in the order of compiled asm and associate with kernel source code if available,
then return df.
:param method: "host_trap" or "stochastic".
:type method: str
:param file_name: The pc sampling json file.
:type file_name: Path
:param kernel_name: The kernel name to be filtered out.
:type kernel_name: str
:param sorting_type: "offset" or "count".
:type sorting_type: str
:return: The counted and reordering pc sampling info.
:rtype: pd.DataFrame:
"""
kernel_info_list = search_key_in_json(file_name, "kernel_symbols")
@@ -1132,25 +1196,31 @@ def load_pc_sampling_data_per_kernel(file_name, kernel_name):
# print("kernel_info", kernel_info)
pc_sample_host_trap = search_key_in_json(file_name, "pc_sample_host_trap")
pc_sample_key_loc = (
search_key_in_json(file_name, "pc_sample_host_trap")
if method == "host_trap"
else search_key_in_json(file_name, "pc_sample_stochastic")
)
# print(type(pc_sample_host_trap), len(pc_sample_host_trap))
# print(pc_sample_host_trap[0]["record"].get("pc", {}).get("code_object_offset"))
# print(search_pc_sampling_record(pc_sample_host_trap))
# print(type(pc_sample_key_loc), len(pc_sample_key_loc))
# print(pc_sample_key_loc[0]["record"].get("pc", {}).get("code_object_offset"))
# print(search_pc_sampling_record(pc_sample_key_loc))
df = pd.DataFrame(
search_pc_sampling_record(pc_sample_host_trap),
columns=["code_object_id", "inst_index", "offset", "count"],
search_pc_sampling_record(pc_sample_key_loc),
columns=["code_object_id", "inst_index", "offset", "count", "stall_reason"],
)
df = df[
(df["code_object_id"] == kernel_info["code_object_id"])
& (df["offset"] > kernel_info["entry_byte_offset"])
& (df["offset"] < kernel_info["potential_end_offset"])
][["inst_index", "offset", "count"]]
][["inst_index", "offset", "count", "stall_reason"]]
df["offset"] = df["offset"].apply(lambda x: hex(x))
# df["stall_reason"] = df["stall_reason"].apply(lambda x: ', '.join(f"{k}: {v}" for k, v in x))
pc_sample_instructions = search_key_in_json(file_name, "pc_sample_instructions")
# print(pc_sample_instructions)
df["instruction"] = df["inst_index"].apply(
@@ -1166,11 +1236,29 @@ def load_pc_sampling_data_per_kernel(file_name, kernel_name):
)
)
return df[["source_line", "instruction", "offset", "count"]]
# print(df[["source_line", "instruction", "offset", "count", "stall_reason"]])
if sorting_type == "offset":
return (
df[["source_line", "instruction", "offset", "count"]]
if method == "host_trap"
else df[["source_line", "instruction", "offset", "count", "stall_reason"]]
)
else: # sort by "count"
return (
df[["source_line", "instruction", "offset", "count"]].sort_values(
by="count", ascending=False
)
if method == "host_trap"
else df[
["source_line", "instruction", "offset", "count", "stall_reason"]
].sort_values(by="count", ascending=False)
)
# might support sort by stall reason in the future
@demarcate
def load_pc_sampling_data(workload, dir, file_prefix):
def load_pc_sampling_data(workload, dir, file_prefix, sorting_type):
"""
Load PC sampling raw data, filter and sort it by specified conditions,
then return df.
@@ -1179,39 +1267,52 @@ def load_pc_sampling_data(workload, dir, file_prefix):
if file_prefix.lower() == "none":
return pd.DataFrame()
# No kernel filter, return grouped and sorted csv directly
if not workload.filter_kernel_ids:
# NB: the default file name is subject to changes from rocprofv3/rocprofiler_sdk
pc_sampling_method = None
# NB:
# - The default file name is subject to changes from rocprofv3
# - Prioritize stochastic
# - Alternatively, we could check pc_sampling_method in json
csv_file_path = Path.joinpath(Path(dir), file_prefix + "_pc_sampling_stochastic.csv")
if csv_file_path.exists():
pc_sampling_method = "stochastic"
else:
csv_file_path = Path.joinpath(
Path(dir), file_prefix + "_pc_sampling_host_trap.csv"
)
if not csv_file_path.exists():
console_error("PC sampling: can not read %s " % csv_file_path)
return pd.DataFrame()
else:
df = pd.read_csv(csv_file_path)
# Group by 'Instruction_Comment' and count occurrences
grouped_counts = (
df.groupby("Instruction_Comment")
.agg(
count=("Instruction_Comment", "count"),
instruction=("Instruction", "first"),
)
.reset_index()
.rename(columns={"Instruction_Comment": "source_line"})
if csv_file_path.exists():
pc_sampling_method = "host_trap"
if pc_sampling_method == None:
console_error("PC sampling: can not find %s " % csv_file_path)
return pd.DataFrame()
# No kernel filter, return grouped and sorted csv directly
if not workload.filter_kernel_ids:
df = pd.read_csv(csv_file_path)
# Group by 'Instruction_Comment' and count occurrences
grouped_counts = (
df.groupby("Instruction_Comment")
.agg(
count=("Instruction_Comment", "count"),
instruction=("Instruction", "first"),
)
.reset_index()
.rename(columns={"Instruction_Comment": "source_line"})
)
grouped_counts = grouped_counts[["source_line", "instruction", "count"]]
grouped_counts = grouped_counts[["source_line", "instruction", "count"]]
grouped_counts["source_line"] = grouped_counts["source_line"].apply(
lambda x: (".../" + Path(x).name)
)
grouped_counts["source_line"] = grouped_counts["source_line"].apply(
lambda x: (".../" + Path(x).name)
)
# Sort by the count of occurrences
sorted_counts = grouped_counts.sort_values(by="count", ascending=False)
# print(sorted_counts.info)
# Sort by the count of occurrences
sorted_counts = grouped_counts.sort_values(by="count", ascending=False)
# print(sorted_counts.info)
return sorted_counts
return sorted_counts
elif len(workload.filter_kernel_ids) > 1:
console_error(
@@ -1234,14 +1335,16 @@ def load_pc_sampling_data(workload, dir, file_prefix):
kernel_name = pd.read_csv(file).loc[
workload.filter_kernel_ids[0], "Kernel_Name"
]
return load_pc_sampling_data_per_kernel(json_file_path, kernel_name)
return load_pc_sampling_data_per_kernel(
pc_sampling_method, json_file_path, kernel_name, sorting_type
)
else:
console_warning("PC sampling: No data")
return pd.DataFrame()
@demarcate
def load_kernel_top(workload, dir):
def load_kernel_top(workload, dir, args):
# NB:
# - Do pmc_kernel_top.csv loading before eval_metric because we need the kernel names.
# - There might be a better way/timing to load raw_csv_table.
@@ -1284,28 +1387,33 @@ def load_kernel_top(workload, dir):
f"Couldn't load {file.name}. This may result in missing analysis data."
)
elif "from_pc_sampling" in df.columns:
tmp[id] = load_pc_sampling_data(workload, dir, df.loc[0, "from_pc_sampling"])
tmp[id] = load_pc_sampling_data(
workload,
dir,
df.loc[0, "from_pc_sampling"],
args.pc_sampling_sorting_type,
)
# print("table id", id, "filter_kernel_ids", workload.filter_kernel_ids)
workload.dfs.update(tmp)
@demarcate
def load_table_data(workload, dir, is_gui, debug, verbose, skipKernelTop=False):
def load_table_data(workload, dir, is_gui, args, skipKernelTop=False):
"""
- Load data for all "raw_csv_table"
- Load dat for "pc_sampling_table"
- Calculate mertric value for all "metric_table"
"""
if not skipKernelTop:
load_kernel_top(workload, dir)
load_kernel_top(workload, dir, args)
eval_metric(
workload.dfs,
workload.dfs_type,
workload.sys_info.iloc[0],
apply_filters(workload, dir, is_gui, debug),
debug,
apply_filters(workload, dir, is_gui, args.debug),
args.debug,
)
+8 -3
Просмотреть файл
@@ -949,12 +949,17 @@ def run_prof(
df.to_csv(workload_dir + "/" + fbase + ".csv", index=False)
def pc_sampling_prof(interval, workload_dir, appcmd, rocprofiler_sdk_library_path):
def pc_sampling_prof(
method, interval, workload_dir, appcmd, rocprofiler_sdk_library_path
):
"""
Run rocprof with pc sampling. Current support v3 only.
"""
# Todo:
# - precheck with rocprofv3 –-list-avail
unit = "time" if method == "host_trap" else "cycles"
if rocprof_cmd == "rocprofiler-sdk":
rocm_libdir = str(pathlib.Path(rocprofiler_sdk_library_path).parent)
rocprofiler_sdk_tool_path = str(
@@ -975,7 +980,7 @@ def pc_sampling_prof(interval, workload_dir, appcmd, rocprofiler_sdk_library_pat
"ROCPROF_OUTPUT_PATH": workload_dir,
"ROCPROF_OUTPUT_FILE_NAME": "ps_file",
"ROCPROFILER_PC_SAMPLING_BETA_ENABLED": "1",
"ROCPROF_PC_SAMPLING_UNIT": "time",
"ROCPROF_PC_SAMPLING_UNIT": unit,
"ROCPROF_PC_SAMPLING_INTERVAL": str(interval),
"ROCPROF_PC_SAMPLING_METHOD": "host_trap",
}
@@ -993,7 +998,7 @@ def pc_sampling_prof(interval, workload_dir, appcmd, rocprofiler_sdk_library_pat
"--pc-sampling-method",
"host_trap",
"--pc-sampling-unit",
"time",
unit,
"--output-format",
"csv",
"json",