Skip to content

Commit 9c6d0f6

Browse files
committed
Add cuda support
1 parent 700fa47 commit 9c6d0f6

2 files changed

Lines changed: 81 additions & 34 deletions

File tree

‎Tutorials/ML/README-benchmark.md‎

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,37 @@ It accepts one dense floating-point input of shape `[batch, features]` and one
66
or more outputs. Static dimensions must match the options; symbolic dimensions
77
are filled from the options. Output types are left to ONNX Runtime.
88

9-
## Build and AMD prerequisites
9+
## Build and GPU prerequisites
1010

1111
Build O2Physics normally after loading its O2 environment. This target links
1212
`O2::ML`, `O2::GPUCommon`, `ONNXRuntime::ONNXRuntime`, and the platform dynamic
13-
loader. No HIP headers are required for building the task. The O2 installation
13+
loader. No HIP or CUDA headers are required for building the task. The O2 installation
1414
must contain `ML/3rdparty/GPUORTFloat16.h` and its GPUCommon headers.
1515

16-
For GPU runs, ONNX Runtime must contain **MIGraphXExecutionProvider**, and its
16+
For AMD GPU runs, ONNX Runtime must contain **MIGraphXExecutionProvider**, and its
1717
MIGraphX/ROCm shared libraries, including `libamdhip64.so`, must be discoverable
1818
at runtime. The task checks provider availability and disables CPU fallback:
1919
unsupported graphs fail rather than producing a misleading GPU benchmark.
2020
No changes to O2's OrtInterface are needed for this task; the current checked-out
2121
OrtInterface does not attach an AMD execution provider, so this example uses
2222
ORT directly.
2323

24+
For NVIDIA runs, select `--provider cuda`. ONNX Runtime must contain
25+
**CUDAExecutionProvider**, with matching CUDA/cuDNN libraries and an NVIDIA
26+
driver. Telemetry loads `libcudart.so` (also tries `.so.13`, `.so.12`, `.so.11.0`)
27+
from the library search path. Keep this runtime consistent with the ORT CUDA
28+
build. CUDA provider options require ORT 1.18 or newer: `use_tf32=0` disables
29+
TF32 so the FP32 benchmark does not silently use TF32 matrix multiplication.
30+
Both GPU backends disable CPU fallback and use the same timing/CSV path.
31+
2432
Check the actual hydra driver, ROCm, MIGraphX and ORT combination first. MI100
2533
(gfx908) and MI50 (gfx906) do not have identical software support. MI50 is absent
2634
from current supported ROCm hardware lists, so it needs a compatible older
2735
stack or a separately validated build. The old ROCm execution provider was
2836
removed from ONNX Runtime starting with 1.23; this task uses MIGraphX.
2937

3038
References:
39+
- https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html
3140
- https://onnxruntime.ai/docs/execution-providers/MIGraphX-ExecutionProvider.html
3241
- https://onnxruntime.ai/docs/execution-providers/ROCm-ExecutionProvider.html
3342
- https://rocm.docs.amd.com/projects/install-on-linux/en/latest/reference/system-requirements.html
@@ -52,8 +61,20 @@ o2-analysis-benchmark-onnx-model -b --model-path model-fp16.onnx \
5261
--warmup 20 --iterations 200 --output-csv gpu-fp16.csv
5362
```
5463

55-
`gpu-id` is the HIP logical ID after any `HIP_VISIBLE_DEVICES` or
56-
`ROCR_VISIBLE_DEVICES` masking. Run one benchmark process at a time on an idle
64+
For NVIDIA, replace `--provider migraphx` with `--provider cuda`, for example:
65+
66+
```bash
67+
o2-analysis-benchmark-onnx-model -b --model-path model-fp32.onnx \
68+
--provider cuda --gpu-id 0 --fp16 false --batch-size 1024 --features 5 \
69+
--warmup 20 --iterations 200 --output-csv cuda-fp32.csv
70+
71+
o2-analysis-benchmark-onnx-model -b --model-path model-fp16.onnx \
72+
--provider cuda --gpu-id 0 --fp16 true --batch-size 1024 --features 5 \
73+
--warmup 20 --iterations 200 --output-csv cuda-fp16.csv
74+
```
75+
76+
`gpu-id` is the logical ID after `HIP_VISIBLE_DEVICES` / `ROCR_VISIBLE_DEVICES`
77+
masking on AMD or `CUDA_VISIBLE_DEVICES` masking on NVIDIA. Run one benchmark process at a time on an idle
5778
GPU. Repeat with the same batch sizes and CPU thread count, and compare several
5879
runs. Small batches can be dominated by host overhead and transfers.
5980

@@ -71,9 +92,9 @@ tutorial intentionally rejects them. CPU runs require FP32 input.
7192
warmup, input generation, FP16 conversion, telemetry, and CSV I/O. This is
7293
host-to-host inference throughput, not GPU kernel-only throughput.
7394
- GPU memory fields are **device-wide used bytes**, sampled through
74-
`hipMemGetInfo` before session creation, after warmup, and after timed runs.
95+
`hipMemGetInfo` (AMD) or `cudaMemGetInfo` (NVIDIA) before session creation, after warmup, and after timed runs.
7596
They include other processes and runtime allocations; they are neither
76-
model-only memory nor a transient peak. The baseline follows HIP context
97+
model-only memory nor a transient peak. The baseline follows GPU runtime/context
7798
initialization. CPU runs leave these fields empty. GPU telemetry failures
7899
fail the run rather than reporting zero usage.
79100
- Precision labels describe the requested model/input variant, not measured

‎Tutorials/ML/benchmarkOnnxModel.cxx‎

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// granted to it by virtue of its status as an Intergovernmental Organization
1010
// or submit itself to any jurisdiction.
1111

12-
/// Benchmark one dense ONNX input [batch, features] using CPU or AMD MIGraphX.
12+
/// Benchmark one dense ONNX input [batch, features] using CPU, AMD MIGraphX or NVIDIA CUDA.
1313
#include <Framework/AnalysisTask.h>
1414
#include <Framework/Configurable.h>
1515
#include <Framework/ControlService.h>
@@ -34,43 +34,55 @@
3434

3535
namespace
3636
{
37-
// Optional HIP telemetry without introducing a ROCm build dependency for CPU users.
37+
// Load GPU telemetry at runtime so CPU builds need no HIP or CUDA headers.
3838
// Memory is device-wide, not process-local; samples are not a transient peak.
39-
class HipMemory
39+
class GpuMemory
4040
{
4141
public:
42-
explicit HipMemory(int device)
42+
explicit GpuMemory(int device, bool cuda)
4343
{
44-
library = dlopen("libamdhip64.so", RTLD_LAZY | RTLD_LOCAL);
44+
const std::vector<const char*> libraries = cuda
45+
? std::vector<const char*>{"libcudart.so", "libcudart.so.13", "libcudart.so.12", "libcudart.so.11.0"}
46+
: std::vector<const char*>{"libamdhip64.so"};
47+
for (auto name : libraries) {
48+
library = dlopen(name, RTLD_LAZY | RTLD_LOCAL);
49+
if (library) {
50+
break;
51+
}
52+
}
4553
if (!library) {
46-
throw std::runtime_error("GPU telemetry requires libamdhip64.so on LD_LIBRARY_PATH");
54+
throw std::runtime_error(cuda ? "GPU telemetry requires libcudart.so (CUDA 11/12/13) on LD_LIBRARY_PATH"
55+
: "GPU telemetry requires libamdhip64.so on LD_LIBRARY_PATH");
4756
}
48-
auto setDevice = reinterpret_cast<int (*)(int)>(dlsym(library, "hipSetDevice"));
49-
getMemory = reinterpret_cast<int (*)(size_t*, size_t*)>(dlsym(library, "hipMemGetInfo"));
57+
auto setDevice = reinterpret_cast<int (*)(int)>(dlsym(library, cuda ? "cudaSetDevice" : "hipSetDevice"));
58+
memoryFunction = cuda ? "cudaMemGetInfo" : "hipMemGetInfo";
59+
getMemory = reinterpret_cast<int (*)(size_t*, size_t*)>(dlsym(library, memoryFunction));
5060
if (!setDevice || !getMemory || setDevice(device) != 0) {
5161
dlclose(library);
5262
library = nullptr;
53-
throw std::runtime_error("Cannot select HIP device or resolve HIP memory API");
63+
throw std::runtime_error(cuda ? "Cannot select CUDA device or resolve CUDA memory API"
64+
: "Cannot select HIP device or resolve HIP memory API");
5465
}
5566
}
56-
~HipMemory()
67+
~GpuMemory()
5768
{
5869
if (library) {
5970
dlclose(library);
6071
}
6172
}
62-
HipMemory(HipMemory const&) = delete;
63-
HipMemory& operator=(HipMemory const&) = delete;
73+
GpuMemory(GpuMemory const&) = delete;
74+
GpuMemory& operator=(GpuMemory const&) = delete;
6475
uint64_t used() const
6576
{
6677
size_t free = 0, total = 0;
6778
if (getMemory(&free, &total) != 0 || free > total) {
68-
throw std::runtime_error("hipMemGetInfo failed");
79+
throw std::runtime_error(std::string(memoryFunction) + " failed");
6980
}
7081
return total - free;
7182
}
7283

7384
private:
85+
const char* memoryFunction = nullptr;
7486
void* library = nullptr;
7587
int (*getMemory)(size_t*, size_t*) = nullptr;
7688
};
@@ -92,8 +104,8 @@ using namespace o2::framework;
92104

93105
struct BenchmarkOnnxModel {
94106
Configurable<std::string> modelPath{"model-path", "model.onnx", "Local ONNX model"};
95-
Configurable<std::string> provider{"provider", "cpu", "cpu or migraphx"};
96-
Configurable<int> gpuId{"gpu-id", 0, "HIP logical device ID (after visibility masking)"};
107+
Configurable<std::string> provider{"provider", "cpu", "cpu, migraphx or cuda"};
108+
Configurable<int> gpuId{"gpu-id", 0, "HIP/CUDA logical device ID (after visibility masking)"};
97109
Configurable<bool> fp16{"fp16", false, "Require FP16 input; does not convert model weights"};
98110
Configurable<int> batchSize{"batch-size", 1024, "Samples per inference"};
99111
Configurable<int> features{"features", 5, "Features per sample, in model order"};
@@ -114,33 +126,47 @@ struct BenchmarkOnnxModel {
114126
if (batchSize.value <= 0 || features.value <= 0 || iterations.value <= 0 || warmup.value < 1 || cpuThreads.value <= 0 || gpuId.value < 0) {
115127
throw std::runtime_error("Require positive batch, features, iterations, warmup, threads and nonnegative GPU ID");
116128
}
117-
bool gpu = provider.value == "migraphx";
129+
bool cuda = provider.value == "cuda";
130+
bool gpu = cuda || provider.value == "migraphx";
118131
if (!gpu && provider.value != "cpu") {
119-
throw std::runtime_error("provider must be cpu or migraphx");
132+
throw std::runtime_error("provider must be cpu, migraphx or cuda");
120133
}
121134
if (!gpu && fp16.value) {
122135
throw std::runtime_error("Use FP32 for the CPU baseline");
123136
}
124-
std::unique_ptr<HipMemory> memory;
137+
std::unique_ptr<GpuMemory> memory;
125138
uint64_t before = 0, afterWarmup = 0, afterRun = 0;
126139
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "tutorial-benchmark");
127140
Ort::SessionOptions options;
128141
options.SetIntraOpNumThreads(cpuThreads.value);
129142
options.SetInterOpNumThreads(1);
130143
options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
131144
if (gpu) {
145+
const std::string providerName = cuda ? "CUDAExecutionProvider" : "MIGraphXExecutionProvider";
132146
auto providers = Ort::GetAvailableProviders();
133-
if (std::find(providers.begin(), providers.end(), "MIGraphXExecutionProvider") == providers.end()) {
134-
throw std::runtime_error("This ONNX Runtime has no MIGraphXExecutionProvider");
147+
if (std::find(providers.begin(), providers.end(), providerName) == providers.end()) {
148+
throw std::runtime_error("This ONNX Runtime has no " + providerName);
135149
}
136-
memory = std::make_unique<HipMemory>(gpuId.value);
150+
memory = std::make_unique<GpuMemory>(gpuId.value, cuda);
137151
before = memory->used();
138-
OrtMIGraphXProviderOptions amd{};
139-
amd.device_id = gpuId.value;
140-
amd.migraphx_mem_limit = std::numeric_limits<size_t>::max();
141-
// Keep the supplied model precision; do not automatically quantize FP32.
142-
amd.migraphx_fp16_enable = 0;
143-
options.AppendExecutionProvider_MIGraphX(amd);
152+
if (cuda) {
153+
// V2 options allow disabling TF32 for the FP32 comparison.
154+
OrtCUDAProviderOptionsV2* raw = nullptr;
155+
Ort::ThrowOnError(Ort::GetApi().CreateCUDAProviderOptions(&raw));
156+
std::unique_ptr<OrtCUDAProviderOptionsV2, decltype(Ort::GetApi().ReleaseCUDAProviderOptions)> nvidia(raw, Ort::GetApi().ReleaseCUDAProviderOptions);
157+
const std::string device = std::to_string(gpuId.value);
158+
const char* keys[] = {"device_id", "use_tf32"};
159+
const char* values[] = {device.c_str(), "0"};
160+
Ort::ThrowOnError(Ort::GetApi().UpdateCUDAProviderOptions(nvidia.get(), keys, values, 2));
161+
options.AppendExecutionProvider_CUDA_V2(*nvidia);
162+
} else {
163+
OrtMIGraphXProviderOptions amd{};
164+
amd.device_id = gpuId.value;
165+
amd.migraphx_mem_limit = std::numeric_limits<size_t>::max();
166+
// Keep the supplied model precision; do not automatically quantize FP32.
167+
amd.migraphx_fp16_enable = 0;
168+
options.AppendExecutionProvider_MIGraphX(amd);
169+
}
144170
options.AddConfigEntry("session.disable_cpu_ep_fallback", "1");
145171
}
146172
Ort::Session session(env, modelPath.value.c_str(), options);

0 commit comments

Comments
 (0)