Skip to content

Commit 24ad1e9

Browse files
roberteg16claude
andcommitted
ggml-cuda: add per-op GPU roofline profiling for the HIP backend
Optional profiler (GGML_HIP_ROOFLINE, off by default) that attributes each GPU kernel's device-measured time to the ggml op that launched it and writes a JSON report, one row per op invocation listing every kernel that op dispatched. Aggregation (grouping identical ops, averaging, counting) is left to the consumer. Activated at runtime by GGML_ROOFLINE_OUT=<path>; otherwise every entry point is a no-op. Kernel durations are read from device dispatch timestamps (the same source as rocprofv3) and match rocprofv3's measurements. An earlier approach based on host CUDA/HIP events (cudaEventElapsedTime around each op) was giving wrong results: it included kernel-launch bubbles and overstated GPU time by roughly 1.7-3x. Uses rocprofiler-sdk, loaded with dlopen and configured at runtime with symbols resolved via dlsym (the library is not linked, since its constructors abort when linked into an early-loaded shared object). Disables GPU graphs while active so each op's kernels are dispatched individually, which is required for per-op attribution. llama-bench calls ggml_cuda_roofline_reset() after its warmup run (via a weak symbol that is a no-op in non-roofline builds), so the report covers only the measured run, not warmup. Each row carries the op's destination/source shapes and types, HBM byte traffic, matmul dimensions, and the name and device time of every GPU kernel it dispatched. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
1 parent 4b5ac96 commit 24ad1e9

8 files changed

Lines changed: 507 additions & 0 deletions

File tree

.github/workflows/build-gfx11-rocm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ jobs:
220220
-DGGML_CUDA_FORCE_CUBLAS=OFF \
221221
-DGGML_RPC=ON \
222222
-DGGML_HIP_ROCWMMA_FATTN=OFF \
223+
-DGGML_HIP_ROOFLINE=ON \
223224
-DLLAMA_BUILD_BORINGSSL=ON \
224225
-DGGML_NATIVE=OFF \
225226
-DGGML_STATIC=OFF \

ggml/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ option(GGML_HIP_NO_VMM "ggml: do not try to use HIP VMM"
219219
option(GGML_HIP_ROCWMMA_FATTN "ggml: enable rocWMMA for FlashAttention" OFF)
220220
option(GGML_HIP_MMQ_MFMA "ggml: enable MFMA MMA for CDNA in MMQ" ON)
221221
option(GGML_HIP_EXPORT_METRICS "ggml: enable kernel perf metrics output" OFF)
222+
option(GGML_HIP_ROOFLINE "ggml: enable per-op roofline profiling" OFF)
222223
option(GGML_MUSA_GRAPHS "ggml: use MUSA graph, experimental, unstable" OFF)
223224
option(GGML_MUSA_MUDNN_COPY "ggml: enable muDNN for accelerated copy" OFF)
224225
option(GGML_VULKAN "ggml: use Vulkan" OFF)

ggml/src/ggml-cuda/ggml-cuda-roofline.cpp

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
// Per-op GPU roofline profiling for the HIP/ROCm backend (see ggml-cuda-roofline.cpp).
4+
// Compiled only when GGML_HIP_ROOFLINE is defined and activated at runtime by the
5+
// environment variable GGML_ROOFLINE_OUT=<path.json>. Every entry point is a no-op
6+
// unless that variable is set.
7+
8+
#include "ggml.h"
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
// Load and configure rocprofiler-sdk when GGML_ROOFLINE_OUT is set. Call once during
15+
// backend registration, before any GPU stream is created. Idempotent; no-op otherwise.
16+
void ggml_cuda_roofline_init(void);
17+
18+
// Record the GPU architecture (e.g. "gfx1151") stored in the report. No-op unless active.
19+
void ggml_cuda_roofline_set_device(const char * arch);
20+
21+
// Discard everything captured so far (drains pending records first). Call after a
22+
// warmup run so the report covers only the measured run. No-op unless active.
23+
void ggml_cuda_roofline_reset(void);
24+
25+
// Tag the GPU kernels launched for this op so their device time is attributed to it.
26+
// Call once per op, before its kernel(s) are dispatched. No-op unless active.
27+
void ggml_cuda_roofline_begin_op(const struct ggml_tensor * node);
28+
29+
#ifdef __cplusplus
30+
}
31+
#endif

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
#include "ggml-impl.h"
33
#include "ggml-backend-impl.h"
44

5+
#ifdef GGML_HIP_ROOFLINE
6+
#include "ggml-cuda/ggml-cuda-roofline.h"
7+
#endif
8+
59
#include "ggml-cuda/allreduce.cuh"
610
#include "ggml-cuda/common.cuh"
711
#include "ggml-cuda/acc.cuh"
@@ -4380,6 +4384,10 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
43804384
continue;
43814385
}
43824386

4387+
#ifdef GGML_HIP_ROOFLINE
4388+
ggml_cuda_roofline_begin_op(node);
4389+
#endif
4390+
43834391
int nodes_to_skip = ggml_cuda_try_fuse(cuda_ctx, cgraph, i);
43844392

43854393
if (nodes_to_skip != 0) {
@@ -5648,6 +5656,14 @@ static const ggml_backend_reg_i ggml_backend_cuda_reg_interface = {
56485656

56495657
// backend registry
56505658
ggml_backend_reg_t ggml_backend_cuda_reg() {
5659+
#ifdef GGML_HIP_ROOFLINE
5660+
ggml_cuda_roofline_init(); // configure rocprofiler tracing before any stream is created
5661+
if (ggml_cuda_info().device_count > 0) {
5662+
char arch[32];
5663+
snprintf(arch, sizeof(arch), "gfx%x", ggml_cuda_info().devices[0].cc & 0xffff);
5664+
ggml_cuda_roofline_set_device(arch);
5665+
}
5666+
#endif
56515667
static ggml_backend_reg reg;
56525668
static bool initialized = false;
56535669

ggml/src/ggml-hip/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,19 @@ if (GGML_HIP_RCCL)
155155
endif()
156156

157157
target_link_libraries(ggml-hip PRIVATE ggml-base hip::host roc::rocblas roc::hipblas)
158+
159+
# Per-op roofline profiling via rocprofiler-sdk. The library is loaded at runtime with
160+
# dlopen and its symbols resolved with dlsym, so only its headers and libdl are needed
161+
# to build; it is not linked (its constructors abort when linked into an early-loaded
162+
# shared object).
163+
if (GGML_HIP_ROOFLINE)
164+
find_path(ROCPROFILER_SDK_INCLUDE_DIR rocprofiler-sdk/rocprofiler.h)
165+
if (NOT ROCPROFILER_SDK_INCLUDE_DIR)
166+
message(FATAL_ERROR "GGML_HIP_ROOFLINE requires rocprofiler-sdk headers (set CMAKE_PREFIX_PATH to the ROCm root)")
167+
endif()
168+
target_sources(ggml-hip PRIVATE ../ggml-cuda/ggml-cuda-roofline.cpp)
169+
set_source_files_properties(../ggml-cuda/ggml-cuda-roofline.cpp PROPERTIES LANGUAGE CXX)
170+
target_include_directories(ggml-hip PRIVATE ${ROCPROFILER_SDK_INCLUDE_DIR})
171+
target_compile_definitions(ggml-hip PRIVATE GGML_HIP_ROOFLINE)
172+
target_link_libraries(ggml-hip PRIVATE ${CMAKE_DL_LIBS})
173+
endif()

tools/llama-bench/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ set_target_properties(${TARGET} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
88
target_include_directories(${TARGET} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
99
target_link_libraries(${TARGET} PUBLIC llama-common llama ${CMAKE_THREAD_LIBS_INIT})
1010

11+
# Compile in the post-warmup roofline reset only when the HIP profiler is built
12+
# (ggml-cuda-roofline.cpp, linked via ggml-hip). No-op in every other build.
13+
if (GGML_HIP_ROOFLINE)
14+
target_compile_definitions(${TARGET} PRIVATE GGML_HIP_ROOFLINE)
15+
target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../ggml/src/ggml-cuda)
16+
endif()
17+
1118
if(LLAMA_TOOLS_INSTALL)
1219
install(TARGETS ${TARGET} LIBRARY)
1320
endif()

tools/llama-bench/llama-bench.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@
3535
# include <windows.h>
3636
#endif
3737

38+
// Optional per-op roofline profiler (HIP build with GGML_HIP_ROOFLINE). Used to drop
39+
// warmup from the report. Compiled out entirely otherwise. See
40+
// ggml/src/ggml-cuda/ggml-cuda-roofline.cpp.
41+
#ifdef GGML_HIP_ROOFLINE
42+
#include "ggml-cuda-roofline.h"
43+
#endif
44+
3845
// utils
3946
static uint64_t get_time_ns() {
4047
using clock = std::chrono::high_resolution_clock;
@@ -2355,6 +2362,12 @@ int llama_bench(int argc, char ** argv) {
23552362
}
23562363
}
23572364

2365+
// Discard warmup from the per-op roofline profiler so the report covers only
2366+
// the measured runs below. Compiled out unless GGML_HIP_ROOFLINE is set.
2367+
#ifdef GGML_HIP_ROOFLINE
2368+
ggml_cuda_roofline_reset();
2369+
#endif
2370+
23582371
for (int i = 0; i < params.reps; i++) {
23592372
llama_memory_clear(llama_get_memory(ctx), false);
23602373

0 commit comments

Comments
 (0)