Speed-up follow-ups: card memory, carried long-axis answer, CPU twin arms, live host reserve

- Long-axis speculation: the re-indexes of the offered cells ran at once, one wide FFT pool each, beside
  the measured-centre spot finding; with two offers on an 8 GB card the latter's allocations could fail
  where they used to fit. They now run one after the other, each releasing its indexers when done, so no
  more wide pools are alive at once than the hypothesis itself builds.
- Long-axis answer carried to later passes: where the beam-centre check asked at both centres, the run
  recorded the adoption of whichever arm adopted, even if the other centre won. Each arm's adoption is
  kept and only the one on the lattice the pass ends on is carried.
- Twinned arm beside the untwinned one: with the untwinned arm on the CPU (CPU-only build, or a model
  too big for the card) nothing is held on a card and the memory gate let two full CPU refinements run at
  once. It now waits in that case.
- Broker model warm-up: a CUDA error while loading the refinement modules skipped the whole validation
  warm-up; it is now caught, cleared and logged.
- Live host budget: nine tenths of the machine, not all of it, is shared between the image buffer, the
  broker's margin and rugnux, leaving a tenth to the system and other services on the host.
- First pass: the measured-centre speculation starts before the coarse long-axis search, since it is on
  the critical path with two or more cards.

Results unchanged: thau, lyso and cytc reports, .hkl and refined models identical to rc184-port, also
with every pinned host buffer filled with 0xA5 on allocation (reused pool pieces carry no stale data
into results); the ~1000 A test sweep identical to the run without these fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-08 23:55:35 +01:00
1 parent 1a259f1578
commit 2cd3da67be
6 files changed
+39 -13

No files matched your search

@@ -2192,9 +2192,14 @@ void WarmUpModelValidation(std::shared_ptr<const RugnuxModel> model, size_t nthr
// The refinement's kernels that a validation does not launch, on every card: lazily their modules would be // The refinement's kernels that a validation does not launch, on every card: lazily their modules would be
// loaded at their first launch, in the refinement, from the executable - 1-3 s in a live run where its // loaded at their first launch, in the refinement, from the executable - 1-3 s in a live run where its
// pages had left the page cache and the disk was busy. // pages had left the page cache and the disk was busy.
for (int d = 0; d < get_gpu_count(); d++) { try {
const ScopedGpuPin pin(d); for (int d = 0; d < get_gpu_count(); d++) {
RefinementGPUEngine::LoadModules(); const ScopedGpuPin pin(d);
RefinementGPUEngine::LoadModules();
}
} catch (const std::exception &e) {
cuda_clear_error();
logger.Warning("Refinement modules not loaded ahead: {}", e.what());
} }
Logger quiet = Logger::Buffered(); Logger quiet = Logger::Buffered();
if (const SearchModel search = PrepareSearchModel(*model); search.failure_reason.empty() && search.predicted) { if (const SearchModel search = PrepareSearchModel(*model); search.failure_reason.empty() && search.predicted) {
@@ -3584,7 +3584,9 @@ RefinementResult RefineModel(const gemmi::Structure &placed, const std::vector<M
#ifdef JFJOCH_USE_CUDA #ifdef JFJOCH_USE_CUDA
const ScopedGpuPin pin(twin_card); const ScopedGpuPin pin(twin_card);
#endif #endif
if (2 * held_bytes.get() > budget) // Nothing held on a card: the untwinned arm is on the CPU, where a second arm only shares its threads.
const size_t held_on_card = held_bytes.get();
if (held_on_card == 0 || 2 * held_on_card > budget)
untwinned.wait(); untwinned.wait();
DecideTwin(placed, data, cell, sg, wavelength_A, twin_logger, settings, result, untwinned); DecideTwin(placed, data, cell, sg, wavelength_A, twin_logger, settings, result, untwinned);
}); });
+4 -2
View File
@@ -30,12 +30,14 @@ namespace {
// machine, and a margin - at most the machine's budget. Both come off the machine's total, not off the // machine, and a margin - at most the machine's budget. Both come off the machine's total, not off the
// budget: the budget's own fraction is the reserve for the rest of the machine, which the margin already // budget: the budget's own fraction is the reserve for the rest of the machine, which the margin already
// keeps for the broker, and taken off twice it left nothing (1 byte) with a buffer sized to most of the // keeps for the broker, and taken off twice it left nothing (1 byte) with a buffer sized to most of the
// machine. Never 0, which would take the whole machine's budget: at worst what could run side by side // machine. A tenth of the machine stays out of it for the system and the other services on the host (a
// writer, for one). Never 0, which would take the whole machine's budget: at worst what could run side by side
// runs one part after the other. // runs one part after the other.
size_t RugnuxHostBudget(const ImageBuffer &buffer) { size_t RugnuxHostBudget(const ImageBuffer &buffer) {
const size_t total = get_host_total_memory(); const size_t total = get_host_total_memory();
const size_t taken = buffer.GetBufferSize() + RUGNUX_HOST_MEMORY_MARGIN; const size_t taken = buffer.GetBufferSize() + RUGNUX_HOST_MEMORY_MARGIN;
return total > taken ? std::min(DefaultHostMemoryBudget(), total - taken) : 1; const size_t usable = total / 10 * 9;
return usable > taken ? std::min(DefaultHostMemoryBudget(), usable - taken) : 1;
} }
double SecondsSince(std::chrono::steady_clock::time_point t) { double SecondsSince(std::chrono::steady_clock::time_point t) {
+9 -3
View File
@@ -491,8 +491,14 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) {
if (!long_axis_asked) if (!long_axis_asked)
best = run.LongAxisHypothesis(std::move(best)); best = run.LongAxisHypothesis(std::move(best));
// What later passes of the run carry (Rugnux::long_axis_answer_). // What later passes of the run carry (Rugnux::long_axis_answer_).
if (run.long_axis_asked_here || run.long_axis_adopted) // Where the beam-centre check asked at both centres, the adoption carried is the one the pass ended on.
long_axis_answer_ = LongAxisAnswer{run.long_axis_adopted}; if (run.long_axis_asked_here || !run.long_axis_adopted.empty()) {
LongAxisAnswer answer;
for (const auto &a : run.long_axis_adopted)
if (best.result && SameLattice(a, *best.result))
answer.adopted = a.lattice.GetUnitCell();
long_axis_answer_ = answer;
}
// The end wedge, at the geometry the rescues settled on. // The end wedge, at the geometry the rescues settled on.
best = run.AddEndWedge(std::move(best), *p.indexer_pool, *p.indexer); best = run.AddEndWedge(std::move(best), *p.indexer_pool, *p.indexer);
run.ReleaseSpotEngines(); // no more frames to read; give the cards their memory back run.ReleaseSpotEngines(); // no more frames to read; give the cards their memory back
@@ -1521,8 +1527,8 @@ void Rugnux::FirstPassRun::StartCentreSpeculation() {
auto Rugnux::FirstPassRun::IndexSchemes() -> FirstPass { auto Rugnux::FirstPassRun::IndexSchemes() -> FirstPass {
return PickBest(*indexer_pool, *indexer, [&] { return PickBest(*indexer_pool, *indexer, [&] {
StartCentreSpeculation(); // on the critical path with two or more cards; the coarse search is not
StartLongAxisCoarse(); StartLongAxisCoarse();
StartCentreSpeculation();
}); });
} }
+2 -2
View File
@@ -356,7 +356,7 @@ struct Rugnux::FirstPassRun {
std::unique_ptr<DiffractionExperiment> experiment; std::unique_ptr<DiffractionExperiment> experiment;
std::unique_ptr<IndexerThreadPool> own_pool; // none where the run's pool is wide enough std::unique_ptr<IndexerThreadPool> own_pool; // none where the run's pool is wide enough
SchemeIndexers ris; SchemeIndexers ris;
std::future<void> done; // last, so it is waited for before the rest goes std::shared_future<void> done; // last, so it is waited for before the rest goes
}; };
std::vector<LongAxisSpeculation> long_axis_spec; std::vector<LongAxisSpeculation> long_axis_spec;
// The experiment the coarse search was started on. // The experiment the coarse search was started on.
@@ -367,5 +367,5 @@ struct Rugnux::FirstPassRun {
// Whether this pass asked the long-axis question itself, and the cell it adopted (see // Whether this pass asked the long-axis question itself, and the cell it adopted (see
// Rugnux::long_axis_answer_). // Rugnux::long_axis_answer_).
bool long_axis_asked_here = false; bool long_axis_asked_here = false;
std::optional<UnitCell> long_axis_adopted; std::vector<RotationIndexerResult> long_axis_adopted; // what each centre asked here adopted
}; };
+13 -2
View File
@@ -197,7 +197,18 @@ void Rugnux::FirstPassRun::StartLongAxisSearch(std::unique_ptr<DiffractionExperi
std::vector<RotationIndexer *> ris; std::vector<RotationIndexer *> ris;
for (const auto &ri : s.ris) for (const auto &ri : s.ris)
ris.push_back(ri.get()); ris.push_back(ri.get());
s.done = std::async(std::launch::async, [ris] { RunIndexing(ris); }); // One after the other, as the hypothesis asks them, each handing its card memory back when done: no more
// wide indexers on a card at once than the hypothesis itself would build.
const std::shared_future<void> before =
long_axis_spec.size() > 1 ? long_axis_spec[long_axis_spec.size() - 2].done : std::shared_future<void>();
IndexerThreadPool *pool = s.own_pool.get();
s.done = std::async(std::launch::async, [ris, before, pool] {
if (before.valid())
before.wait();
RunIndexing(ris);
if (pool != nullptr)
pool->ReleaseIndexers();
}).share();
} }
void Rugnux::FirstPassRun::StartLongAxisCoarse() { void Rugnux::FirstPassRun::StartLongAxisCoarse() {
@@ -378,7 +389,7 @@ auto Rugnux::FirstPassRun::LongAxisHypothesis(FirstPass standing) -> FirstPass {
choice ? "adopting " + cell_text(*constrained[*choice].result) : std::string("keeping the standing cell"), choice ? "adopting " + cell_text(*constrained[*choice].result) : std::string("keeping the standing cell"),
seconds); seconds);
if (choice) { if (choice) {
long_axis_adopted = constrained[*choice].result->lattice.GetUnitCell(); long_axis_adopted.push_back(*constrained[*choice].result);
return std::move(constrained[*choice]); return std::move(constrained[*choice]);
} }
return standing; return standing;