Files
Jungfraujoch/common/CUDAWrapper.cpp
T
leonarski_fandClaude Opus 5.5 e3b33591ca GPU: kernels read host buffers in place on unified-memory systems
gpu_direct_host_read() (CUDAWrapper) is true where the device accesses pageable
memory through the host's page tables (cudaDevAttrPageableMemoryAccess and
...UsesHostPageTables: GB10, GH200, GB200), asked once per device;
JFJOCH_FORCE_DIRECT_HOST_READ forces it on for testing with page-locked inputs.
Where it holds:
- BSLZ4DecoderGPU: the LZ4/fused kernels and the tail copy read the reader's
  compressed chunk directly, no pinned staging memcpy and no H2D copy;
- ImagePreprocessorGPU: Analyze and the UINT16_WIDENED path hand the host
  buffer to preprocess_kernel (if aligned to the pixel type), and nothing is
  page-locked;
- RotationScaleMergeGPU::IngestUpload: IngestExtractKernel reads the pinned
  stage in place, without device staging buffers; the stage is reused only
  after the kernel has run.
Discrete cards take the unchanged path. Every caller synchronises its stream
before the host buffer goes away. DirectHostReadGPUTest checks the three
sites against the CPU on page-locked inputs, in either mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:49:37 +02:00

135 lines
3.6 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "CUDAWrapper.h"
#include <condition_variable>
#include <mutex>
// Build-independent: the CUDA build gets get_gpu_names() from CUDAWrapper.cu, the CPU-only build from
// the stub below, and this collapses whichever list came back. Four identical cards read better as
// "4x <name>" than as the same name four times, and a mixed machine keeps one group per model.
std::string get_gpu_description() {
const auto names = get_gpu_names();
std::string out;
for (size_t i = 0; i < names.size();) {
size_t n = 1;
while (i + n < names.size() && names[i + n] == names[i])
n++;
if (!out.empty())
out += ", ";
if (n > 1)
out += std::to_string(n) + "x ";
out += names[i];
i += n;
}
return out;
}
namespace {
// How many probe passes (GPUWorkBeside) and main-line image passes (GPUImagePass) are running, for a
// thread to wait until none of one kind is.
std::mutex gpu_work_m;
std::condition_variable gpu_work_changed;
int gpu_work_beside_n = 0, gpu_image_passes_n = 0;
// A card too small for a probe pass beside the main line's image pass, or beside another probe pass:
// measured on a 16 Mpx detector, two of them together held 8.6 GB. On such a card a probe pass waits,
// before it starts, for every other one and for the image passes to end, and an image pass for the
// probe passes - so they take turns, and only when each runs depends on the card, never what it
// computes. None waits once it is running, so nothing waits in a circle.
constexpr size_t TAKE_TURNS_BELOW_BYTES = 12'000'000'000ull;
bool take_turns() {
static const bool small = get_gpu_count() > 0 && get_gpu_total_memory(0) < TAKE_TURNS_BELOW_BYTES;
return small;
}
void remove(int &n) {
{
std::lock_guard lock(gpu_work_m);
--n;
}
gpu_work_changed.notify_all();
}
}
GPUWorkBeside::GPUWorkBeside() {
std::unique_lock lock(gpu_work_m);
if (take_turns())
gpu_work_changed.wait(lock, [] { return gpu_image_passes_n == 0 && gpu_work_beside_n == 0; });
++gpu_work_beside_n;
}
GPUWorkBeside::~GPUWorkBeside() {
remove(gpu_work_beside_n);
}
bool wait_for_gpu_work_beside(std::chrono::seconds timeout) {
std::unique_lock lock(gpu_work_m);
return gpu_work_changed.wait_for(lock, timeout, [] { return gpu_work_beside_n == 0; });
}
GPUImagePass::GPUImagePass() {
std::unique_lock lock(gpu_work_m);
if (take_turns())
gpu_work_changed.wait(lock, [] { return gpu_work_beside_n == 0; });
++gpu_image_passes_n;
}
GPUImagePass::~GPUImagePass() {
remove(gpu_image_passes_n);
}
void wait_for_gpu_image_passes() {
std::unique_lock lock(gpu_work_m);
gpu_work_changed.wait(lock, [] { return gpu_image_passes_n == 0; });
}
#ifndef JFJOCH_USE_CUDA
int32_t get_gpu_count() {
return 0;
}
void hide_gpus() {}
std::vector<std::string> get_gpu_names() {
return {};
}
size_t get_gpu_total_memory(int32_t) {
return 0;
}
void set_gpu(int32_t dev_id) {}
bool gpu_direct_host_read() {
return false;
}
void pin_gpu() {}
void pin_gpu(int32_t dev_id) {}
ScopedGpuPin::ScopedGpuPin(int32_t) {}
ScopedGpuPin::~ScopedGpuPin() {}
void enable_gpu_numa_binding() {}
std::vector<int> get_gpu_numa_nodes() { return {}; }
void set_gpu_blocking_sync() {}
void create_gpu_contexts(int32_t) {}
void release_gpu_contexts() {}
void cuda_clear_error() {}
void cuda_throw_if_context_lost() {}
#endif