The space-group search made its two P1 merges one after the other on one engine: the Lorentz-filtered one, its search, then the all-observation one and its search. Since every merge now starts from the same state, the all-observation merge is made on a copy of the engine (CloneIngest: the host half of the ingest copied, the device half staged on the copy's card), started right after the ingest and read where the search needs it, so it runs beside the P1 merge, the re-scaled search merge and the first arm. Its search then runs beside the first arm's. Placement: from three cards up the copy gets card 2 to itself (card 0 has the main engine, card 1 the quality guard's). With one or two cards it shares the last card, and only where two engines of this size fit the card's total memory (DEVICE_BYTES_PER_OBSERVATION = 256; two engines measured at ~150 B/observation each), so the choice never depends on what is free at the moment. On a 16 GB card that admits sets up to ~32M observations; larger ones keep the serial order. Nothing of the copy reaches the run before the first arm says the arm is wanted: its log, its result fields and its per-frame write-back onto the outcomes are taken at the point where the serial merge would have been made. A copy that failed is merged again in series. Results are bit-identical to the serial order (p.hkl md5 and MTZ data), at default -N and -N 8. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
87 lines
2.0 KiB
C++
87 lines
2.0 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 {
|
|
std::mutex gpu_work_beside_mutex;
|
|
std::condition_variable gpu_work_beside_done;
|
|
int gpu_work_beside = 0;
|
|
}
|
|
|
|
GPUWorkBeside::GPUWorkBeside() {
|
|
std::lock_guard lock(gpu_work_beside_mutex);
|
|
++gpu_work_beside;
|
|
}
|
|
|
|
GPUWorkBeside::~GPUWorkBeside() {
|
|
{
|
|
std::lock_guard lock(gpu_work_beside_mutex);
|
|
--gpu_work_beside;
|
|
}
|
|
gpu_work_beside_done.notify_all();
|
|
}
|
|
|
|
bool wait_for_gpu_work_beside(std::chrono::seconds timeout) {
|
|
std::unique_lock lock(gpu_work_beside_mutex);
|
|
return gpu_work_beside_done.wait_for(lock, timeout, [] { return gpu_work_beside == 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 cuda_clear_error() {}
|
|
|
|
void cuda_throw_if_context_lost() {}
|
|
|
|
#endif
|