Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b0d20be
feat(primbench): support CUDA
MyNameIsTrez Mar 17, 2026
6507a20
refactor(primbench): move code to filter_specializations() and sort_s…
MyNameIsTrez Mar 4, 2026
4753dc1
refactor(primbench): move code to get_common_algorithm()
MyNameIsTrez Mar 4, 2026
ffb89d5
refactor(primbench): move code to get_header()
MyNameIsTrez Mar 4, 2026
fd3396e
refactor(primbench): move code to run_all_specializations()
MyNameIsTrez Mar 4, 2026
7e2e1cc
refactor(primbench): move code to ensure_specializations_exist() and …
MyNameIsTrez Mar 4, 2026
5d38072
docs(primbench): use consistent comment style
MyNameIsTrez Mar 17, 2026
6f8cde6
chore(primbench): remove unused <optional> header
MyNameIsTrez Mar 17, 2026
c51f17f
feat(primbench): support vector CLI args
MyNameIsTrez Mar 17, 2026
fcc01e4
refactor(primbench): split state.run() into state.run_iteration()
MyNameIsTrez Mar 9, 2026
76cb345
docs(primbench): update example results.json
MyNameIsTrez Mar 9, 2026
206f1fe
fix(primbench): escape filter in JSON
MyNameIsTrez Mar 9, 2026
4ab47b0
fix(primbench): fix mi300x outputting extra data in arch
MyNameIsTrez Mar 16, 2026
0e54a9a
feat(primbench): improve backend JSON format
MyNameIsTrez Mar 17, 2026
30e4202
fix(rocprim): remove old primbench args
MyNameIsTrez Mar 17, 2026
c78014a
chore(primbench): apply clang-format
MyNameIsTrez Mar 18, 2026
4c192e9
fix(primbench): fix specialization column width
MyNameIsTrez Mar 19, 2026
70cf749
chore(primbench): rename 'family' to 'specialization' index
MyNameIsTrez Mar 19, 2026
0f4c5af
chore(primbench): rename 'col' to 'column'
MyNameIsTrez Mar 19, 2026
f97c8f1
refactor(primbench): introduce PRIMBENCH_PREFIX_BACKEND()
MyNameIsTrez Mar 27, 2026
21253ad
feat(primbench): print estimated time remaining
MyNameIsTrez Mar 28, 2026
d540f6d
feat(primbench): don't print index column when there's only 1 special…
MyNameIsTrez Mar 28, 2026
6e4df8b
refactor(primbench): always use `#elif defined(__CUDACC__)`
MyNameIsTrez Mar 31, 2026
e8b1919
docs(primbench): improve error message
MyNameIsTrez Apr 10, 2026
70cdadb
fix(primbench): allow boolean flags that are true by default
MyNameIsTrez Apr 14, 2026
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
22 changes: 0 additions & 22 deletions projects/rocprim/.gitlab/run_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ def run_benchmarks(
max_gpu_temp,
max_warming_secs,
max_cooling_secs,
output_hip_device_properties_context,
output_amdsmi_context,
output_batches,
spaces_per_indent,
stream_blocking_timeout_secs,
Expand Down Expand Up @@ -148,10 +146,6 @@ def should_skip(json_out_path):
args += ["--max-warming-secs", max_warming_secs]
if max_cooling_secs:
args += ["--max-cooling-secs", max_cooling_secs]
if output_hip_device_properties_context:
args += ["--output-hip-device-properties-context"]
if output_amdsmi_context:
args += ["--output-amdsmi-context"]
if output_batches:
args += ["--output-batches"]
if spaces_per_indent:
Expand Down Expand Up @@ -294,20 +288,6 @@ def main():
default="",
required=False,
)
parser.add_argument(
"--output-hip-device-properties-context",
help="Output a `hip_device_properties` object in the context, containing GPU details",
default=False,
action="store_true",
required=False,
)
parser.add_argument(
"--output-amdsmi-context",
help="Output an `amdsmi` object in the context, containing GPU details",
default=False,
action="store_true",
required=False,
)
parser.add_argument(
"--output-batches",
help="Output a `batches` array for each specialization, containing per-batch details",
Expand Down Expand Up @@ -351,8 +331,6 @@ def main():
args.max_gpu_temp,
args.max_warming_secs,
args.max_cooling_secs,
args.output_hip_device_properties_context,
args.output_amdsmi_context,
args.output_batches,
args.spaces_per_indent,
args.stream_blocking_timeout_secs,
Expand Down
78 changes: 48 additions & 30 deletions shared/primbench/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# primbench

primbench is a single-header HIP benchmarking library.
primbench is a single-header HIP and CUDA benchmarking library.

![primbench demo](./assets/primbench.gif)

Expand All @@ -16,8 +16,8 @@ primbench is a single-header HIP benchmarking library.
## Dependencies

primbench has the following dependencies:
- HIP
- AMD SMI (for querying live GPU statistics)
- HIP or CUDA
- [AMD SMI](https://rocm.docs.amd.com/projects/amdsmi/en/latest/) or [NVML](https://developer.nvidia.com/management-library-nvml) (for querying live GPU statistics)
- C++17 or later

## Example
Expand Down Expand Up @@ -81,15 +81,15 @@ struct copy_benchmark : public primbench::benchmark_interface
primbench::log("Allocating device memory");
T* d_input;
T* d_output;
PRIMBENCH_HIP_CHECK(hipMalloc(&d_input, items * sizeof(T)));
PRIMBENCH_HIP_CHECK(hipMalloc(&d_output, items * sizeof(T)));
PRIMBENCH_CHECK(hipMalloc(&d_input, items * sizeof(T)));
PRIMBENCH_CHECK(hipMalloc(&d_output, items * sizeof(T)));

primbench::log("Copying to device");
PRIMBENCH_HIP_CHECK(hipMemcpyAsync(d_input,
h_input.data(),
items * sizeof(T),
hipMemcpyHostToDevice,
stream));
PRIMBENCH_CHECK(hipMemcpyAsync(d_input,
h_input.data(),
items * sizeof(T),
hipMemcpyHostToDevice,
stream));

dim3 grid(items / items_per_block);
dim3 block(BlockSize);
Expand All @@ -107,8 +107,8 @@ struct copy_benchmark : public primbench::benchmark_interface
<<<grid, block, 0, stream>>>(d_input, d_output);
});

PRIMBENCH_HIP_CHECK(hipFree(d_input));
PRIMBENCH_HIP_CHECK(hipFree(d_output));
PRIMBENCH_CHECK(hipFree(d_input));
PRIMBENCH_CHECK(hipFree(d_output));
}
};

Expand All @@ -123,29 +123,49 @@ int main(int argc, char* argv[])
}
```

The benchmark is compiled and run like so:
The HIP benchmark is compiled and run like so:

```bash
hipcc -o copy_benchmark examples/copy_benchmark.cpp -I. -lamd_smi && ./copy_benchmark
hipcc -o copy_benchmark examples/hip/copy_benchmark.cpp -I. -lamd_smi && ./copy_benchmark
```

And its equivalent CUDA benchmark is compiled and run like so:

```bash
nvcc -o copy_benchmark examples/cuda/copy_benchmark.cu -I. -lnvidia-ml && ./copy_benchmark
```

It outputs this `results.json`:
```json
{
"context": {
"results_version": "2.0.0",
"results_version": "3.0.0",
"general": {
"algorithm": "copy",
"specialization_count": 2,
"gpu_name": "AMD Instinct MI210",
"gpu_arch": "gfx90a",
"gpu_pci_bus_id": "0000:83:00.0",
"library_build_type": "debug",
"temp_type": "edge",
"gpu": {
"name": "AMD Radeon RX 9070 XT",
"arch": "gfx1201",
"pci_bus_id": "0000:83:00.0"
},
"backend": {
"name": "hip",
"hip_version": "6.4.43482-0f2d60242",
"runtime_version": "6.4.43482",
"driver_version": "6.4.43482",
"compiler": {
"name": "clang",
"version": "19.0.0git (https://github.com/RadeonOpenCompute/llvm-project roc-6.4.0 25133 c7fe45cf4b819c5991fe208aaa96edf142730f1d)"
},
"monitoring": {
"name": "amdsmi",
"version": "25.3.0"
}
},
"temperature_type": "edge",
"host_name": "host",
"date": "2025-11-24T15:01:50+00:00",
"hip_version": "6.4.43482-0f2d60242",
"clang_version": "19.0.0git (https://github.com/RadeonOpenCompute/llvm-project roc-6.4.0 25133 c7fe45cf4b819c5991fe208aaa96edf142730f1d)"
"date": "2025-11-24T15:01:50+00:00"
},
"settings": {
"size": 134217728,
Expand All @@ -164,8 +184,6 @@ It outputs this `results.json`:
"max_gpu_temp": 60,
"max_warming_secs": 60,
"max_cooling_secs": 60,
"output_hip_device_properties_context": false,
"output_amdsmi_context": false,
"output_batches": false,
"spaces_per_indent": 4,
"stream_blocking_timeout_secs": 10
Expand Down Expand Up @@ -265,8 +283,6 @@ You can also pass `--help` to benchmarks to print the available options.
| `--max-gpu-temp` | Maximum GPU temperature in °C. Too low slows benchmarks; too high increases noise. (default: 60) |
| `--max-warming-secs` | Maximum seconds allowed for GPU warming before an error is thrown. (default: 60) |
| `--max-cooling-secs` | Maximum seconds allowed for GPU cooling before an error is thrown. (default: 60) |
| `--output-hip-device-properties-context` | Output a `hip_device_properties` object in the context object, containing details about the GPU. |
| `--output-amdsmi-context` | Output an `amdsmi` object in the context object, containing details about the GPU. |
| `--output-batches` | Output a `batches` array for each specialization, containing per-batch details. |
| `--spaces-per-indent` | Number of spaces per indentation level in JSON output. Set to 0 for no indentation. (default: 4) |
| `--stream-blocking-timeout-secs` | Maximum stream blocking duration in seconds before timing out. Stream is blocked while queueing kernel calls. Use `primbench::flags::sync` if kernel is synchronous. (default: 10) |
Expand Down Expand Up @@ -344,13 +360,15 @@ Before benchmarking a specialization, primbench:

primbench currently does not cool down the GPU *while* benchmarking a specialization, but this may be changed in the future.

Temperatures are read via AMD SMI. Warming uses short GPU workloads; cooling waits until the GPU naturally drops back into range. If either process takes more than 60 seconds, primbench aborts (`--max-warming-secs`, `--max-cooling-secs`).
Temperatures are read using AMD SMI/NVML. Warming uses short GPU workloads; cooling waits until the GPU naturally drops back into range. If either process takes more than 60 seconds, primbench aborts (`--max-warming-secs`, `--max-cooling-secs`).

### GPU Temperature Sensor Selection

primbench supports multiple GPU temperature sensors exposed by AMD SMI, such as *edge* and *hotspot*. At startup, it probes the available sensors and selects the first one that successfully returns a reading. The chosen sensor type is cached and used consistently for warming, cooling, and reporting. If no supported sensor can be read, primbench terminates with an error.
primbench supports multiple GPU temperature sensors exposed by AMD SMI, such as `edge` and `hotspot`. At startup, it probes the available sensors and selects the first one that successfully returns a reading. The chosen sensor type is cached and used consistently for warming, cooling, and reporting. If no supported sensor can be read, primbench terminates with an error.

The selected temperature sensor type is recorded in the JSON output as `context.general.temperature_type`.

The selected temperature sensor type is recorded in the JSON output as `context.general.temp_type`.
primbench always uses the GPU temperature sensor called `gpu` for CUDA benchmarks.

### GPU Cache Clearing

Expand Down Expand Up @@ -410,7 +428,7 @@ The command below embeds the current branch and commit hash.
If the repository is in a detached HEAD state (for example in CI or when checking out a specific commit), no branch is active and `BRANCH_NAME` is set to `DETACHED`.

```bash
hipcc -o copy_benchmark examples/copy_benchmark.cpp \
hipcc -o copy_benchmark examples/hip/copy_benchmark.cpp \
-I. \
-lamd_smi \
-DBRANCH_NAME=\"$(git symbolic-ref -q --short HEAD || echo DETACHED)\" \
Expand Down
98 changes: 98 additions & 0 deletions shared/primbench/examples/cuda/copy_benchmark.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include "primbench.hpp"

#include <cuda_runtime.h>

// All benchmarked types must be declared
// This allows you to for example format `long long` as `int64_t`, or `i64`
PRIMBENCH_REGISTER_TYPE(char, "char")
PRIMBENCH_REGISTER_TYPE(long long, "long long")

// Simple copy kernel
template<typename T, unsigned int BlockSize, unsigned int ItemsPerThread>
__global__ __launch_bounds__(BlockSize)
void copy_kernel(const T* input, T* output)
{
unsigned int idx = threadIdx.x + blockIdx.x * BlockSize * ItemsPerThread;
#pragma unroll
for(unsigned int i = 0; i < ItemsPerThread; ++i)
output[idx + i * BlockSize] = input[idx + i * BlockSize];
}

template<typename T>
struct copy_benchmark : public primbench::benchmark_interface
{
primbench::json meta() const override
{
// This meta() method must return an `algo` key
// The `algo` name must be identical across all queued specializations
// primbench::json objects can be nested
return primbench::json{}.add("algo", "copy").add("type", primbench::name<T>());
}

// This contains both the setup and running of the benchmark
void run(primbench::state& state) override
{
const auto& stream = state.stream;
const auto& bytes = state.size;

// primbench::log() calls report progress in gray
// They help with discovering slow setup steps
primbench::log("Calculating items");
size_t N = bytes / sizeof(T);
constexpr unsigned int BlockSize = 256;
constexpr unsigned int ItemsPerThread = 4;

const size_t items_per_block = BlockSize * ItemsPerThread;
const size_t items = items_per_block * ((N + items_per_block - 1) / items_per_block);

primbench::log("Generating input data");
std::vector<T> h_input(items);
for(size_t i = 0; i < items; ++i)
h_input[i] = T(i);

primbench::log("Allocating output vector");
std::vector<T> h_output(items);

primbench::log("Allocating device memory");
T* d_input;
T* d_output;
PRIMBENCH_CHECK(cudaMalloc(&d_input, items * sizeof(T)));
PRIMBENCH_CHECK(cudaMalloc(&d_output, items * sizeof(T)));

primbench::log("Copying to device");
PRIMBENCH_CHECK(cudaMemcpyAsync(d_input,
h_input.data(),
items * sizeof(T),
cudaMemcpyHostToDevice,
stream));

dim3 grid(items / items_per_block);
dim3 block(BlockSize);

// primbench uses this to calculates the items/sec and bytes/sec
state.set_items(items);
state.add_reads<T>(items);
state.add_writes<T>(items);

// This passes a lambda to primbench, which calls it many times
// primbench completely handles synchronization
state.run(
[&] {
copy_kernel<T, BlockSize, ItemsPerThread>
<<<grid, block, 0, stream>>>(d_input, d_output);
});

PRIMBENCH_CHECK(cudaFree(d_input));
PRIMBENCH_CHECK(cudaFree(d_output));
}
};

int main(int argc, char* argv[])
{
primbench::executor executor(argc, argv);

executor.queue<copy_benchmark<char>>();
executor.queue<copy_benchmark<long long>>();

executor.run();
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ struct copy_benchmark : public primbench::benchmark_interface
primbench::log("Allocating device memory");
T* d_input;
T* d_output;
PRIMBENCH_HIP_CHECK(hipMalloc(&d_input, items * sizeof(T)));
PRIMBENCH_HIP_CHECK(hipMalloc(&d_output, items * sizeof(T)));
PRIMBENCH_CHECK(hipMalloc(&d_input, items * sizeof(T)));
PRIMBENCH_CHECK(hipMalloc(&d_output, items * sizeof(T)));

primbench::log("Copying to device");
PRIMBENCH_HIP_CHECK(hipMemcpyAsync(d_input,
h_input.data(),
items * sizeof(T),
hipMemcpyHostToDevice,
stream));
PRIMBENCH_CHECK(hipMemcpyAsync(d_input,
h_input.data(),
items * sizeof(T),
hipMemcpyHostToDevice,
stream));

dim3 grid(items / items_per_block);
dim3 block(BlockSize);
Expand All @@ -80,8 +80,8 @@ struct copy_benchmark : public primbench::benchmark_interface
<<<grid, block, 0, stream>>>(d_input, d_output);
});

PRIMBENCH_HIP_CHECK(hipFree(d_input));
PRIMBENCH_HIP_CHECK(hipFree(d_output));
PRIMBENCH_CHECK(hipFree(d_input));
PRIMBENCH_CHECK(hipFree(d_output));
}
};

Expand Down
Loading
Loading