-
Notifications
You must be signed in to change notification settings - Fork 225
refactor: Move root relaxation solve archestration to Branch and Bound #1782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| /* clang-format on */ | ||
|
|
||
| #include <branch_and_bound/branch_and_bound.hpp> | ||
| #include <branch_and_bound/concurrent_root_solver.hpp> | ||
| #include <branch_and_bound/diving_heuristics.hpp> | ||
| #include <branch_and_bound/mip_node.hpp> | ||
| #include <branch_and_bound/pseudo_costs.hpp> | ||
|
|
@@ -3050,9 +3051,63 @@ lp_status_t branch_and_bound_t<i_t, f_t>::solve_root_relaxation( | |
| root_vstatus_, | ||
| edge_norms_, | ||
| nullptr); | ||
| // Dual simplex has finished; stop the GPU competitors if they are still running. | ||
| gpu_root_concurrent_halt_.store(1, std::memory_order_release); | ||
| } | ||
|
|
||
| // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex | ||
| // The diversity manager prepares the GPU problem while dual simplex starts on the CPU. | ||
| // Once the GPU problem is ready, launch PDLP and barrier from here so all root-LP | ||
| // competitors are owned by this function. | ||
| while (!concurrent_root_problem_ready_.load(std::memory_order_acquire) && | ||
| *get_root_concurrent_halt() == 0) { | ||
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| #pragma omp taskyield | ||
| } | ||
|
|
||
| if (*get_root_concurrent_halt() == 0 && | ||
| concurrent_root_problem_ready_.load(std::memory_order_acquire)) { | ||
| cuopt_assert(concurrent_root_problem_ != nullptr, "Concurrent root problem is not configured"); | ||
| gpu_root_concurrent_halt_.store(0, std::memory_order_release); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win This store erases the cancellation signal from dual simplex. The dual-simplex task sets Do not reset the flag. Instead, check it and skip the GPU solve when it is already set. 🐛 Proposed fix- gpu_root_concurrent_halt_.store(0, std::memory_order_release);
+ if (gpu_root_concurrent_halt_.load(std::memory_order_acquire) != 0) {
+ // Dual simplex already finished; do not start the GPU root solve.
+ return_early_or_skip = true;
+ }Initialize 🤖 Prompt for AI Agents |
||
| try { | ||
| cuopt_assert(concurrent_root_settings_ != nullptr, | ||
| "Concurrent root settings are not configured"); | ||
| const f_t remaining_time = | ||
| std::max<f_t>(settings_.time_limit - toc(exploration_stats_.start_time), 0); | ||
| const f_t root_time_limit = | ||
| std::min(concurrent_root_max_time_, remaining_time * concurrent_root_time_ratio_); | ||
| auto result = solve_concurrent_root_relaxation(concurrent_root_problem_, | ||
| *concurrent_root_settings_, | ||
| root_time_limit, | ||
| &gpu_root_concurrent_halt_); | ||
| if (result.usable) { | ||
| // Release the heuristics first: they only need the relaxation values, whereas | ||
| // crossover below can run until the time limit without ever producing a root. | ||
| if (root_lp_solution_callback_ != nullptr) { | ||
| root_lp_solution_callback_( | ||
| result.primal, result.dual, result.user_objective, result.optimal); | ||
| } | ||
| set_root_relaxation_solution(result.primal, | ||
| result.dual, | ||
| result.reduced_cost, | ||
| result.solver_objective, | ||
| result.user_objective, | ||
| result.iterations, | ||
| result.method); | ||
| // Same as the old diversity-manager path: an Optimal GPU root LP is a | ||
| // valid MIP dual bound even if dual simplex / crossover has not finished. | ||
| if (result.optimal) { update_user_bound(result.solver_objective); } | ||
| } else if (root_lp_solution_callback_ != nullptr) { | ||
| // No usable relaxation, but the heuristics must still be released so they can run | ||
| // without LP guidance rather than block on a dual simplex that may never finish. | ||
| root_lp_solution_callback_({}, {}, std::numeric_limits<f_t>::infinity(), false); | ||
| } | ||
| } catch (const std::exception& e) { | ||
| settings_.log.printf("Concurrent GPU root LP failed: %s\n", e.what()); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Wait until either the GPU root solve supplies a crossover point or CPU dual | ||
| // simplex finishes. If dual simplex wins, stop and join the GPU solve. | ||
| while (!root_crossover_solution_set_.load(std::memory_order_acquire) && | ||
| *get_root_concurrent_halt() == 0) { | ||
|
Comment on lines
3111
to
3112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift The root relaxation still has no dual-simplex completion signal, so both wait loops can spin indefinitely. The dual-simplex task sets only Two hang paths follow.
Line 3070 compounds the first path. Add a dedicated dual-simplex completion atomic. Set it in the task at Line 3055. Observe it in both wait loops. Check it at Line 3067 and skip the GPU solve instead of resetting the halt flag. As per path instructions: "Apply 'CUDA / GPU — cuOpt idioms'" and verify root-LP coordination for races, deadlocks, and cancellation. 🐛 Proposed fix sketchAdd a member next to std::atomic<bool> dual_simplex_root_done_{false};Publish it when the task ends: // Dual simplex has finished; stop the GPU competitors if they are still running.
gpu_root_concurrent_halt_.store(1, std::memory_order_release);
+ dual_simplex_root_done_.store(true, std::memory_order_release);
}Observe it in the readiness loop: while (!concurrent_root_problem_ready_.load(std::memory_order_acquire) &&
- *get_root_concurrent_halt() == 0) {
+ *get_root_concurrent_halt() == 0 &&
+ !dual_simplex_root_done_.load(std::memory_order_acquire)) {Do not reset the halt flag; skip the GPU solve when dual simplex already won: - if (*get_root_concurrent_halt() == 0 &&
- concurrent_root_problem_ready_.load(std::memory_order_acquire)) {
+ if (*get_root_concurrent_halt() == 0 &&
+ !dual_simplex_root_done_.load(std::memory_order_acquire) &&
+ concurrent_root_problem_ready_.load(std::memory_order_acquire)) {
cuopt_assert(concurrent_root_problem_ != nullptr, "Concurrent root problem is not configured");
- gpu_root_concurrent_halt_.store(0, std::memory_order_release);Observe it in the crossover wait loop: while (!root_crossover_solution_set_.load(std::memory_order_acquire) &&
- *get_root_concurrent_halt() == 0) {
+ *get_root_concurrent_halt() == 0 &&
+ !dual_simplex_root_done_.load(std::memory_order_acquire)) {Run the following script to confirm no other code releases these loops: #!/bin/bash
# Description: Check who writes root_concurrent_halt_ and gpu_root_concurrent_halt_, and who calls notify_concurrent_root_problem_ready.
set -euo pipefail
echo "=== writers of root_concurrent_halt_ / set_root_concurrent_halt ==="
rg -nP -C3 '(set_root_concurrent_halt|root_concurrent_halt_\s*(=|\.store))' cpp/src
echo "=== writers of gpu_root_concurrent_halt_ ==="
rg -nP -C3 'gpu_root_concurrent_halt_\s*(=|\.store|\.load)' cpp/src
echo "=== callers of notify_concurrent_root_problem_ready ==="
rg -nP -C6 'notify_concurrent_root_problem_ready' cpp/src
echo "=== any dual-simplex completion flag already present? ==="
rg -nP 'dual_simplex_root_done_|root_done_|simplex_root_finished' cpp/src || echo "none found"🤖 Prompt for AI AgentsSource: Path instructions |
||
| if (received_halt_signal()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include <branch_and_bound/concurrent_root_solver.hpp> | ||
| #include <utilities/timer.hpp> | ||
|
|
||
| #include <mip_heuristics/problem/problem.cuh> | ||
| #include <pdlp/solve.cuh> | ||
|
|
||
| #include <raft/util/cudart_utils.hpp> | ||
|
|
||
| namespace cuopt::mathematical_optimization::mip { | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| pdlp_solver_settings_t<i_t, f_t> make_mip_root_lp_settings( | ||
| const mip_solver_settings_t<i_t, f_t>& mip_settings) | ||
| { | ||
| pdlp_solver_settings_t<i_t, f_t> settings{}; | ||
| settings.tolerances.absolute_dual_tolerance = mip_settings.tolerances.absolute_tolerance; | ||
| settings.tolerances.relative_dual_tolerance = mip_settings.tolerances.relative_tolerance; | ||
| settings.tolerances.absolute_primal_tolerance = mip_settings.tolerances.absolute_tolerance; | ||
| settings.tolerances.relative_primal_tolerance = mip_settings.tolerances.relative_tolerance; | ||
| settings.first_primal_feasible = false; | ||
| settings.method = mip_settings.method; | ||
| settings.inside_mip = true; | ||
| settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; | ||
| settings.num_gpus = mip_settings.num_gpus; | ||
| settings.presolver = presolver_t::None; | ||
| settings.per_constraint_residual = true; | ||
| set_pdlp_solver_mode(settings); | ||
| return settings; | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| concurrent_root_solution_t<i_t, f_t> solve_concurrent_root_relaxation( | ||
| problem_t<i_t, f_t>* problem, | ||
| const pdlp_solver_settings_t<i_t, f_t>& settings, | ||
| f_t time_limit, | ||
| std::atomic<int>* concurrent_halt) | ||
| { | ||
| concurrent_root_solution_t<i_t, f_t> result; | ||
| auto root_settings = settings; | ||
| root_settings.time_limit = time_limit; | ||
| root_settings.concurrent_halt = concurrent_halt; | ||
|
|
||
| timer_t root_timer(time_limit); | ||
| auto lp_result = solve_lp_with_method<i_t, f_t>(*problem, root_settings, root_timer); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject infeasibility results before publishing the root relaxation. When PDLP returns Proposed fix result.usable =
status != pdlp_termination_status_t::NumericalError &&
status != pdlp_termination_status_t::ConcurrentLimit &&
+ status != pdlp_termination_status_t::PrimalInfeasible &&
+ status != pdlp_termination_status_t::DualInfeasible &&
lp_result.get_primal_solution().size() == static_cast<size_t>(problem->n_variables) &&
lp_result.get_dual_solution().size() == static_cast<size_t>(problem->n_constraints);🤖 Prompt for AI Agents |
||
| const auto status = lp_result.get_termination_status(); | ||
| result.usable = | ||
| status != pdlp_termination_status_t::NumericalError && | ||
| status != pdlp_termination_status_t::ConcurrentLimit && | ||
| lp_result.get_primal_solution().size() == static_cast<size_t>(problem->n_variables) && | ||
| lp_result.get_dual_solution().size() == static_cast<size_t>(problem->n_constraints); | ||
| result.optimal = status == pdlp_termination_status_t::Optimal; | ||
| if (!result.usable) { return result; } | ||
|
|
||
| auto& d_primal = lp_result.get_primal_solution(); | ||
| auto& d_dual = lp_result.get_dual_solution(); | ||
| auto& d_reduced_cost = lp_result.get_reduced_cost(); | ||
| result.primal.resize(d_primal.size()); | ||
| result.dual.resize(d_dual.size()); | ||
| result.reduced_cost.resize(d_reduced_cost.size()); | ||
| auto stream = problem->handle_ptr->get_stream(); | ||
| raft::copy(result.primal.data(), d_primal.data(), d_primal.size(), stream); | ||
| raft::copy(result.dual.data(), d_dual.data(), d_dual.size(), stream); | ||
| raft::copy(result.reduced_cost.data(), d_reduced_cost.data(), d_reduced_cost.size(), stream); | ||
| problem->handle_ptr->sync_stream(); | ||
|
|
||
| result.user_objective = lp_result.get_objective_value(); | ||
| result.solver_objective = problem->get_solver_obj_from_user_obj(result.user_objective); | ||
| result.iterations = lp_result.get_additional_termination_information().number_of_steps_taken; | ||
| result.method = lp_result.get_additional_termination_information().solved_by; | ||
| return result; | ||
| } | ||
|
|
||
| template pdlp_solver_settings_t<int, double> make_mip_root_lp_settings( | ||
| const mip_solver_settings_t<int, double>&); | ||
|
|
||
| template concurrent_root_solution_t<int, double> solve_concurrent_root_relaxation( | ||
| problem_t<int, double>*, const pdlp_solver_settings_t<int, double>&, double, std::atomic<int>*); | ||
|
|
||
| } // namespace cuopt::mathematical_optimization::mip | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| #pragma once | ||
|
|
||
| #include <cuopt/mathematical_optimization/mip/solver_settings.hpp> | ||
| #include <cuopt/mathematical_optimization/pdlp/solver_settings.hpp> | ||
|
|
||
| #include <atomic> | ||
| #include <vector> | ||
|
|
||
| namespace cuopt::mathematical_optimization::mip { | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| class problem_t; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| struct concurrent_root_solution_t { | ||
| bool usable{false}; | ||
| bool optimal{false}; | ||
| std::vector<f_t> primal; | ||
| std::vector<f_t> dual; | ||
| std::vector<f_t> reduced_cost; | ||
| f_t solver_objective{0}; | ||
| f_t user_objective{0}; | ||
| i_t iterations{0}; | ||
| method_t method{method_t::Unset}; | ||
| }; | ||
|
|
||
| // Shared PDLP/barrier settings for the MIP root LP. Callers that own the | ||
| // solve (B&B or the heuristics-only fallback) still apply time_limit and | ||
| // concurrent_halt for their own halt/timer. | ||
| template <typename i_t, typename f_t> | ||
| pdlp_solver_settings_t<i_t, f_t> make_mip_root_lp_settings( | ||
| const mip_solver_settings_t<i_t, f_t>& mip_settings); | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| concurrent_root_solution_t<i_t, f_t> solve_concurrent_root_relaxation( | ||
| problem_t<i_t, f_t>* problem, | ||
| const pdlp_solver_settings_t<i_t, f_t>& settings, | ||
| f_t time_limit, | ||
| std::atomic<int>* concurrent_halt); | ||
|
|
||
| } // namespace cuopt::mathematical_optimization::mip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: NVIDIA/cuopt
Length of output: 325
🏁 Script executed:
Repository: NVIDIA/cuopt
Length of output: 1355
🏁 Script executed:
Repository: NVIDIA/cuopt
Length of output: 634
Add the missing concurrent root solver files.
cpp/src/branch_and_bound/concurrent_root_solver.cuandcpp/src/branch_and_bound/concurrent_root_solver.hppare absent from bothHEADandorigin/main. The new CMake entry and include will fail unless this PR adds both files.🤖 Prompt for AI Agents
Source: Path instructions