Files
Jungfraujoch/common/CUDAWrapper.cpp
T
leonarski_fandClaude Opus 5.5 3befddd649 rugnux: an FFT indexer that does not fit waits for the GPU work beside it, then is built again
Several pipeline copies of one run build FFT indexer pools on the card at once - the
rotation-scale walk's indexing probes start as the output pass begins its image loop,
and the speculative geometry probe runs beside the merge - and each indexer is a cuFFT
plan plus 0.6-0.9 GB of buffers. Construction had no admission control: a cufftPlanMany
or device allocation that found the card full failed straight through, and since
IndexerResult carries the exception that ends the run. With 7.2 GB of the card held by
another process, a small-molecule rotation set failed 4 of 4 runs this way although
each pass fits on its own.

Construction now does what the merge's allocations do: hand back the pool's unused
memory, wait for the work beside it to end, and build again. Who waits for whom is
fixed so that nothing waits in a circle:
- the main line waits for the probes (GPUWorkBeside, as the merge already does);
- a probe waits for the main line's image pass (new GPUImagePass), never for a probe,
  itself included. The image pass waits for no probe, so it ends.
The walk's indexing probes now hold a GPUWorkBeside too (they are exactly that work),
and a run carries gpu_work_beside_ so its pools know which side they are on. Only when
an indexer is built changes, never what it computes; with nothing to wait for it still
fails at once.

Same run with the 7.2 GB hog: 5/5 complete (also at 7.6 and 8.0 GB), p.hkl identical
to the unhogged run; a card that cannot hold the run still fails in 0.5 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
2026-10-11 11:11:42 +02:00

114 lines
2.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 of one kind of GPU work are running, for a thread to wait until none is.
struct GPUWorkCount {
std::mutex m;
std::condition_variable none;
int n = 0;
void Add() {
std::lock_guard lock(m);
++n;
}
void Remove() {
{
std::lock_guard lock(m);
--n;
}
none.notify_all();
}
};
GPUWorkCount gpu_work_beside, gpu_image_passes;
}
GPUWorkBeside::GPUWorkBeside() {
gpu_work_beside.Add();
}
GPUWorkBeside::~GPUWorkBeside() {
gpu_work_beside.Remove();
}
bool wait_for_gpu_work_beside(std::chrono::seconds timeout) {
std::unique_lock lock(gpu_work_beside.m);
return gpu_work_beside.none.wait_for(lock, timeout, [] { return gpu_work_beside.n == 0; });
}
GPUImagePass::GPUImagePass() {
gpu_image_passes.Add();
}
GPUImagePass::~GPUImagePass() {
gpu_image_passes.Remove();
}
void wait_for_gpu_image_passes() {
std::unique_lock lock(gpu_image_passes.m);
gpu_image_passes.none.wait(lock, [] { return gpu_image_passes.n == 0; });
}
#ifndef JFJOCH_USE_CUDA
int32_t get_gpu_count() {
return 0;
}
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) {}
void pin_gpu() {}
void pin_gpu(int32_t dev_id) {}
ScopedGpuPin::ScopedGpuPin(int32_t) {}
ScopedGpuPin::~ScopedGpuPin() {}
void enable_gpu_numa_binding() {}
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