From 3fa17b9385647c72c4c26a07c3bc02e057bf6061 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 17 Oct 2026 10:34:05 +0200 Subject: [PATCH] Model validation: the rigid-body engines on every card in turn, even where this card has room for all The engines past this card's first now go one card after the other, from the next card and this one last, each within half of its own budget, as MR's do. Before, this card took every extra engine it had room for and the other cards only the rest, so a small cell on four cards ran all its fits on one. Where this card has no room for a second engine (a large cell on 15.6 GB cards) the plan is unchanged: 1/3/3/3. This card's scale fits take the room its engines leave. Engines are interchangeable and deterministic, so only where fits run changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4 --- .../structure_refinement/ModelValidation.cpp | 35 ++++++++++--------- .../structure_refinement/ModelValidation.h | 9 ++--- tests/ModelValidationTest.cpp | 12 +++++++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/image_analysis/structure_refinement/ModelValidation.cpp b/image_analysis/structure_refinement/ModelValidation.cpp index 7be413fe1..5556c7532 100644 --- a/image_analysis/structure_refinement/ModelValidation.cpp +++ b/image_analysis/structure_refinement/ModelValidation.cpp @@ -1909,30 +1909,33 @@ ModelValidationGPUPlan PlanModelValidationGPU(const ModelValidationGPUNeed &need plan.null_structure_factors = take(need.null_structure_factors, plan.budget); plan.scale_fit_engines = need.scale_fit_engines > 0 && take(need.scale_fit, plan.budget) ? 1 : 0; - // More engines only within half of the budget (see the declaration). + // More engines only within half of the budget (see the declaration). The rigid body's past this card's first go + // one card after the other in turn, from the next card and this one last, so that the fits run on every card + // even where this one alone has room for them all. const auto room = [&](size_t budget) { return std::min(budget / 2, budget > need.beside ? budget - need.beside : 0); }; - while (plan.rigid_body_engines > 0 && plan.rigid_body_engines < need.rigid_body_engines - && take(need.rigid_body, room(plan.budget))) - plan.rigid_body_engines++; - while (plan.scale_fit_engines > 0 && plan.scale_fit_engines < need.scale_fit_engines - && take(need.scale_fit, room(plan.budget))) - plan.scale_fit_engines++; - - // The rigid-body engines this card has no room for, on the other cards in turn. plan.rigid_body_per_card.assign(1 + need.other_cards.size(), 0); plan.rigid_body_per_card[0] = plan.rigid_body_engines; std::vector held(plan.rigid_body_per_card.size(), 0); - held[0] = plan.bytes; for (bool added = plan.rigid_body_engines > 0; added;) { added = false; - for (size_t k = 1; k < held.size() && plan.rigid_body_engines < need.rigid_body_engines; k++) - if (held[k] + need.rigid_body <= room(ModelGPUBudget(need.other_cards[k - 1], need.resident))) { + for (size_t i = 1; i <= held.size() && plan.rigid_body_engines < need.rigid_body_engines; i++) { + const size_t k = i % held.size(); // this card last in each turn + const bool fits = k == 0 ? take(need.rigid_body, room(plan.budget)) + : held[k] + need.rigid_body + <= room(ModelGPUBudget(need.other_cards[k - 1], need.resident)); + if (!fits) + continue; + if (k > 0) held[k] += need.rigid_body; - plan.rigid_body_per_card[k]++; - plan.rigid_body_engines++; - added = true; - } + plan.rigid_body_per_card[k]++; + plan.rigid_body_engines++; + added = true; + } } + while (plan.scale_fit_engines > 0 && plan.scale_fit_engines < need.scale_fit_engines + && take(need.scale_fit, room(plan.budget))) + plan.scale_fit_engines++; + held[0] = plan.bytes; plan.most_on_a_card = *std::max_element(held.begin(), held.end()); const size_t cards = held.size(); diff --git a/image_analysis/structure_refinement/ModelValidation.h b/image_analysis/structure_refinement/ModelValidation.h index eee350c03..3159c5d6d 100644 --- a/image_analysis/structure_refinement/ModelValidation.h +++ b/image_analysis/structure_refinement/ModelValidation.h @@ -329,10 +329,11 @@ size_t ModelGPUBudget(size_t card, size_t resident); // only within half of the budget, less what a validation beside holds: they change only how many fits // run at once, and buy little - on a large cell a fit already fills the card (measured on a 16 GB card: // 13.6 s with one rigid-body engine, 13.1 s with two, 12.6 s with five) - so the card keeps headroom -// rather than filling with them. Where this card has no room for all the rigid-body engines the -// validation can use, the rest go on the other cards, one card after the other in turn, each by the same -// rule against its own budget (80% of its total, less what is resident): this card's parts are decided as -// on a machine with this card alone, and how many cards there are changes only where fits run. A second +// rather than filling with them. The rigid-body engines past this card's first go one card after the other +// in turn, from the next card and this one last, each by the same rule against its own budget (80% of its +// total, less what is resident), so that the fits run on every card even where this one has room for them +// all; this card's other parts are decided as on a machine with this card alone (its scale fits in the room +// its engines leave), and how many cards there are changes only where fits run. A second // validation may run beside where twice the whole of this one's GPU parts, at every engine it can use, fits // the budgets of the cards together, and twice what this one holds on any card fits every card's budget. ModelValidationGPUPlan PlanModelValidationGPU(const ModelValidationGPUNeed &need); diff --git a/tests/ModelValidationTest.cpp b/tests/ModelValidationTest.cpp index 885728b16..44ad573f3 100644 --- a/tests/ModelValidationTest.cpp +++ b/tests/ModelValidationTest.cpp @@ -197,6 +197,18 @@ TEST_CASE("ModelValidation_GPUPlanEnginesOnOtherCards", "[ModelValidation]") { // One card alone: the plan as it was. CHECK(alone.rigid_body_per_card == std::vector{1}); CHECK(alone.most_on_a_card == alone.bytes); + + // Engines this card alone has room for still go on every card in turn: ten of 0.1 GB on four cards, from the + // next card and this one last. + need = LargeCellNeed(card); + need.other_cards = {card, card, card}; + need.rigid_body = 100000000; + const ModelValidationGPUPlan small = PlanModelValidationGPU(need); + CHECK(small.rigid_body_per_card == std::vector{3, 3, 2, 2}); + CHECK(small.rigid_body_engines == 10); + CHECK(small.scale_fit_engines == 4); + need.other_cards.clear(); + CHECK(PlanModelValidationGPU(need).rigid_body_per_card == std::vector{10}); } TEST_CASE("ModelValidation_GPUPlanPartThatDoesNotFitGoesToTheCPU", "[ModelValidation]") {