Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ jobs:
shell: bash
run: |
apt-get update
apt install -y libdw-dev pkg-config
apt install -y libdw-dev zlib1g-dev pkg-config
echo "✅ ROCm Dependencies Installed!"

- name: Install Latest Nightly ROCm
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rocprofiler-compute-rhel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
yum -y install cmake3
yum -y install which
yum -y install glibc-langpack-en
yum -y install elfutils-devel pkgconf-pkg-config
yum -y install elfutils-devel zlib-devel pkgconf-pkg-config

- name: Install ROCm Packages
timeout-minutes: 30
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rocprofiler-compute-sanitizers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
shell: bash
run: |
apt-get update
apt-get install -y libdw-dev pkg-config
apt-get install -y libdw-dev zlib1g-dev pkg-config
echo "✅ Dependencies Installed!"

- name: Install latest nightly ROCm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/rocprofiler-compute-tarball.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
apt-get install -y /tmp/amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get update
apt install -y rocm-dev
apt install -y libdw-dev pkg-config
apt install -y libdw-dev zlib1g-dev pkg-config
echo "✅ ROCm Dependencies Installed!"

- name: Install Python
Expand Down Expand Up @@ -147,7 +147,7 @@ jobs:
apt-get install -y /tmp/amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get update
apt install -y rocm-dev
apt install -y libdw-dev pkg-config
apt install -y libdw-dev zlib1g-dev pkg-config
echo "✅ ROCm Dependencies Installed!"

- name: Python dependency installs
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rocprofiler-compute-ubuntu-jammy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
apt-get install -y git
apt-get install -y python3-pip
apt-get install -y cmake
apt-get install -y libdw-dev pkg-config
apt-get install -y libdw-dev zlib1g-dev pkg-config

- name: Checkout
uses: actions/checkout@v6
Expand Down
2 changes: 2 additions & 0 deletions projects/rocprofiler-compute/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Full documentation for ROCm Compute Profiler is available at [https://rocm.docs.

* Reduced profile-mode peak memory when writing counter data on large workloads.

* Profile mode now gzip-compresses large counter CSV artifacts to reduce workload directory size.

### Resolved issues

### Upcoming changes
Expand Down
47 changes: 45 additions & 2 deletions projects/rocprofiler-compute/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,30 @@ add_test(
tests/test_utils_profile_csv.py ${WORKING_DIR_OPTION}
)

# ---------------------------------------
# Compression boundary for csv artifacts
# ---------------------------------------

add_test(
NAME test_csv_compression
COMMAND
${PYTHON_TEST_COMMAND} -m pytest ${STANDALONEBINARY_TEST_OPTION}
--junitxml=tests/test_csv_compression.xml ${COV_OPTION}
tests/test_csv_compression.py ${WORKING_DIR_OPTION}
)

# -----------------------------------
# Analyze base unit tests
# -----------------------------------

add_test(
NAME test_analysis_base
COMMAND
${PYTHON_TEST_COMMAND} -m pytest ${STANDALONEBINARY_TEST_OPTION}
--junitxml=tests/test_analysis_base.xml ${COV_OPTION} tests/test_analysis_base.py
${WORKING_DIR_OPTION}
)

# -----------------------------------
# Profiler base unit tests
# -----------------------------------
Expand Down Expand Up @@ -859,6 +883,23 @@ if(TARGET test-pc-sampling-collector)
endif()
endif()

# -----------------------------------
# CSV compression Tests
# -----------------------------------

if(TARGET test-compression)
if(TEST_FROM_INSTALL)
set(_compression_test_cmd tests/test-compression)
else()
set(_compression_test_cmd $<TARGET_FILE:test-compression>)
endif()
add_test(NAME test-compression COMMAND ${_compression_test_cmd})
sanitizer_runtime_env(_san_env OFF)
if(_san_env)
set_tests_properties(test-compression PROPERTIES ENVIRONMENT "${_san_env}")
endif()
endif()

# -----------------------------------
# Generate coverage tests
# -----------------------------------
Expand Down Expand Up @@ -913,6 +954,8 @@ if(${ENABLE_COVERAGE})
test_soc_base
test_tty
test_utils_profile_csv
test_csv_compression
test_analysis_base
test_import_guard
test_profile_iteration_multiplexing_1
test_profile_iteration_multiplexing_2
Expand Down Expand Up @@ -1354,8 +1397,8 @@ set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")

# Dependencies
set(PACKAGE_REQUIRES "rocprofiler" CACHE STRING "Package dependencies")
set(CPACK_RPM_PACKAGE_REQUIRES ${PACKAGE_REQUIRES})
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${PACKAGE_REQUIRES})
set(CPACK_RPM_PACKAGE_REQUIRES "${PACKAGE_REQUIRES}, zlib")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${PACKAGE_REQUIRES}, zlib1g")

# Handle the project rebranding from omniperf to rocprofiler-compute
set(OMNIPERF_PACKAGE_NAME "omniperf")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ both loaded via `LD_PRELOAD` into the target application
and an empty `rocpd_pmc_event` table). It is told NOT to collect counters
(`ROCPROF_COUNTER_COLLECTION=0`).
2. The rocprofiler-compute "native tool" (`src/lib/rocprofiler_compute_tool`) collects the
hardware counters and currently writes a CSV (`<pid>_native_counter_collection.csv`),
hardware counters and currently writes a gzip-compressed CSV
(`<pid>_native_counter_collection.csv.gz`),
which the Python layer parses and injects row by row into the SDK rocpd's
`rocpd_pmc_event` table (`src/utils/rocpd_data.py`, `src/utils/utils_profile.py`).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Profile Interface Architecture

Status: proposal (Phase A implemented)
Status: proposal (Phases A and B implemented)

This document describes the architecture for profile data storage and access in
rocprofiler-compute, and records the decisions made in the design review so the
Expand Down Expand Up @@ -76,8 +76,9 @@ If there is future strong request to return CSV support, we may implement it und
Gzip is short-term storage-size reduction for CSV artifacts that still exist
after CSV profile backend removal.

We introduce gzip streaming for two separate csv artifacts, written through its own compression interface used by both python and backend:
- the results_*.csv(s), written by compute's Python side (rocpd_data.py) at the end of a pass, when the merged result DB is converted to csv and is the artifact analyze reads today.
We introduce gzip streaming for three csv artifacts, written through its own compression interface used by both python and backend:
- the results_*.csv(s), written by compute's Python side (utils_profile.stream_csv_to_file) at the end of a pass, and the artifact analyze reads today.
- the per-pass out/pmc_1/*_counter_collection.csv
- the native tool counter output (countersData), written by the native tool / backend (rocprofiler-compute-tool.so via the counters writer) during in-process collection. This is an early per-process intermediate.

Compression belongs at CSV read/write, where profile writes compressed CSV
Expand Down Expand Up @@ -333,16 +334,12 @@ and the `--join-type` references in the docs.

## Phase B: Add Compression at CSV Read/Write

We are to introduce gzip streaming to provide a size reduction to csv files.
Phase B does not change the shape of
the profile/analyze contract yet, profile still produces the per-pass CSV result
and analyze still reads it.
Status: implemented.

The change goes through one
compression interface shared by both the native/backend counter writer
and the Python result-CSV writer. Compression is applied at two write points, the
native counter output and the result CSV, and decompressed on read
(during merge in analyze).
Gzip streaming reduces size of the two large counter CSV intermediates from AD-2.
Phase B does not change the profile/analyze contract shape. Python and the native
tool each compress at CSV read/write over a shared format contract; analyze accepts
plain `.csv` for backward compatibility.

```mermaid
sequenceDiagram
Expand Down Expand Up @@ -622,7 +619,8 @@ sequenceDiagram
## Success Criteria

- Profile mode cannot produce the legacy CSV profile output format.
- Remaining CSV artifacts are compressed while they still exist.
- Counter CSV artifacts are compressed while they still exist, apart from the
`pmc_perf.csv` that Phase D removes (see AD-2).
- SDK kernel data and native counter data can be read independently by analyze.
- Analyze gets the DataFrame through the profile data interface.
- `pmc_perf.csv` is not an analyze input.
Expand Down
10 changes: 6 additions & 4 deletions projects/rocprofiler-compute/docs/how-to/analyze/mode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ choose.

.. note::

Analyze mode concatenates the per-pass ``results_*.csv`` files written by
``rocpd`` profiling into a unified ``pmc_perf.csv`` for analysis. If the
workload directory already contains a ``pmc_perf.csv``, that file is used
as-is.
Analyze mode builds ``pmc_perf.csv`` by concatenating per-pass
``results_*.csv`` files from ``rocpd`` profiling, or uses an existing
``pmc_perf.csv`` as-is. Current releases read ``.csv`` and ``.csv.gz``
counter files alike, so older workloads do not need to be regenerated. Releases
before 3.9.0 read only uncompressed names; run ``gunzip`` on ``.csv.gz``
files to analyze a compressed workload on an older release.

.. note::

Expand Down
20 changes: 10 additions & 10 deletions projects/rocprofiler-compute/docs/how-to/profile/mode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ The following sample command profiles the ``vcopy`` workload.
INFO |-> [rocprofiler-sdk] W20260323 16:44:44.257594 140315166887680 simple_timer.cpp:55] [rocprofv3] output generation :: 0.106959 sec
INFO |-> [rocprofiler-sdk] W20260323 16:44:44.257624 140315166887680 simple_timer.cpp:55] [rocprofv3] tool finalization :: 0.110297 sec
INFO |-> [rocprofiler-sdk] [rocprofiler-compute] In tool fini
INFO |-> [rocprofiler-sdk] [rocprofiler-compute] [generate_output] Counter collection data has been written to: /home/auser/rocm-systems/projects/rocprofiler-compute/workloads/vcopy/MI325X/out/pmc_1/116379_native_counter_collection.csv
INFO |-> [rocprofiler-sdk] [rocprofiler-compute] [generate_output] Counter collection data has been written to: /home/auser/rocm-systems/projects/rocprofiler-compute/workloads/vcopy/MI325X/out/pmc_1/116379_native_counter_collection.csv.gz
INFO |-> [rocprofiler-sdk] vcopy testing on GCD 0
INFO |-> [rocprofiler-sdk] Finished allocating vectors on the CPU
WARNING PC sampling data collection skipped as --pc-sampling is not specified.
Expand Down Expand Up @@ -322,10 +322,10 @@ Examples:
│ ├── pmc_perf_SQ_INST_LEVEL_VMEM.yaml
│ └── pmc_perf_SQ_LEVEL_WAVES.yaml
├── profiling_config.yaml
├── results_pmc_perf_0.csv
├── results_pmc_perf_1.csv
├── results_pmc_perf_2.csv
├── results_pmc_perf_SQ_LEVEL_WAVES.csv
├── results_pmc_perf_0.csv.gz
├── results_pmc_perf_1.csv.gz
├── results_pmc_perf_2.csv.gz
├── results_pmc_perf_SQ_LEVEL_WAVES.csv.gz
├── roofline.csv
└── sysinfo.csv

Expand Down Expand Up @@ -370,10 +370,10 @@ details on when the final ``pmc_perf.csv`` is created.
│ ├── pmc_perf_SQ_INST_LEVEL_VMEM.yaml
│ └── pmc_perf_SQ_LEVEL_WAVES.yaml
├── profiling_config.yaml
├── results_pmc_perf_0.csv
├── results_pmc_perf_1.csv
├── results_pmc_perf_2.csv
├── results_pmc_perf_SQ_LEVEL_WAVES.csv
├── results_pmc_perf_0.csv.gz
├── results_pmc_perf_1.csv.gz
├── results_pmc_perf_2.csv.gz
├── results_pmc_perf_SQ_LEVEL_WAVES.csv.gz
├── roofline.csv
└── sysinfo.csv

Expand All @@ -386,7 +386,7 @@ Raw performance counter data produced by the underlying
:doc:`ROCprofiler-SDK <rocprofiler-sdk:index>` backend is written in ``rocpd``
(SQLite) format:

* The rocpd database files are converted to CSV files (``results_pmc_perf_0.csv``, ``results_pmc_perf_SQ_*.csv``, etc.) for each profiling run, after which the database files are removed.
* The rocpd database files are converted to gzip-compressed CSV files (``results_pmc_perf_0.csv.gz``, ``results_pmc_perf_SQ_*.csv.gz``, etc.) for each profiling run, after which the database files are removed.
* These files are merged into a single ``pmc_perf.csv`` file when running ``rocprof-compute analyze``.
* Use ``--retain-rocpd-output`` to preserve the ``rocpd`` database(s) in the workload folder for custom analysis.

Expand Down
6 changes: 6 additions & 0 deletions projects/rocprofiler-compute/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ endif()
find_package(rocprofiler-sdk REQUIRED CONFIG)
find_package(amd_comgr REQUIRED CONFIG)

# pkg-config zlib: find_package(ZLIB) can pick up ROCm headers without libz.
pkg_check_modules(zlib REQUIRED IMPORTED_TARGET zlib>=1.2.7)
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB ALIAS PkgConfig::zlib)
endif()

add_subdirectory(external)
add_subdirectory(utils)
add_subdirectory(pc_sampling_collector)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ add_library(
)

target_include_directories(${target} PUBLIC .)
# compression is PRIVATE: not exposed in public headers.
target_link_libraries(
${target}
PUBLIC rocprofiler-sdk::rocprofiler-sdk gsl_assert pc-sampling-collector
PRIVATE compression
)

if(ENABLE_TESTS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,84 @@
// SPDX-License-Identifier: MIT
#include "counters_writer.h"

#include <fstream>
#include "compression/gzip_output_stream.h"

#include <filesystem>
#include <iostream>
#include <sstream>
#include <string>
#include <system_error>

void rocprofiler_compute_tool::CsvCountersWriter::write_counters(tool_data_t* tool_data)
namespace rocprofiler_compute_tool
{
namespace
{
std::ofstream ofs(tool_data->output_filename);
if (!ofs.is_open())
constexpr std::string_view kHeader = "dispatch_id,gpu_id,kernel_id,lds_per_workgroup,"
"counter_id,counter_name,counter_value\n";

// Batch rows so millions of records do not cross the sink per row.
constexpr std::size_t kBatchBytes = 256 * 1024;
} // namespace

bool format_counters_csv(const tool_data_t& tool_data, const std::function<bool(std::string_view)>& sink)
{
// ostringstream keeps counter_value formatting the readers already parse.
std::ostringstream batch;
batch << kHeader;

const auto flush_batch = [&sink, &batch]()
{
const auto text = batch.str();
batch.str(std::string{});
return text.empty() || sink(text);
};

for (const auto& r : tool_data.counter_records)
{
std::cerr << "Failed to open output file: " << tool_data->output_filename << std::endl;
batch << r.dispatch_id << ',' << r.agent_id << ',' << r.kernel_id << ',' << r.LDS_memory_size
<< ',' << r.counter_id << ',' << r.counter_name << ',' << r.counter_value << '\n';

if (static_cast<std::size_t>(batch.tellp()) >= kBatchBytes && !flush_batch())
return false;
}

return flush_batch();
}

void CsvCountersWriter::write_counters(tool_data_t* tool_data)
{
const std::string& final_path = tool_data->output_filename;
const std::string temp_path = final_path + ".tmp";

compression::GzipFileOutputStream stream(temp_path);
if (!stream.is_open())
{
std::cerr << "Failed to open output file: " << final_path << std::endl;
return;
}

const auto wrote = format_counters_csv(*tool_data,
[&stream](std::string_view text)
{ return stream.write(text); });

std::error_code ec;
if (!stream.close() || !wrote)
Comment thread
jamessiddeley-amd marked this conversation as resolved.
Resolved
{
std::filesystem::remove(temp_path, ec);
std::cerr << "Failed to write output file: " << final_path << std::endl;
return;
}
// Write header at the beginning of the file
ofs << "dispatch_id,gpu_id,kernel_id,lds_per_workgroup,"
"counter_id,counter_name,counter_value\n";
for (const auto& r : tool_data->counter_records)
ofs << r.dispatch_id << ',' << r.agent_id << "," << r.kernel_id << ',' << r.LDS_memory_size
<< ',' << r.counter_id << ',' << r.counter_name << ',' << r.counter_value << '\n';
ofs.flush();

std::filesystem::rename(temp_path, final_path, ec);
if (ec)
{
std::filesystem::remove(temp_path, ec);
std::cerr << "Failed to write output file: " << final_path << std::endl;
return;
}

std::clog << "[rocprofiler-compute] [" << __FUNCTION__
<< "] Counter collection data has been written to: " << tool_data->output_filename
<< std::endl;
<< "] Counter collection data has been written to: " << final_path << std::endl;
}

} // namespace rocprofiler_compute_tool
Loading
Loading