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]") {