Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 42 additions & 28 deletions cpp/src/pdlp/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -1490,12 +1490,20 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
std::tuple<dual_simplex::lp_solution_t<i_t, f_t>, dual_simplex::lp_status_t, f_t, f_t, f_t>>
sol_dual_simplex_ptr;
std::thread dual_simplex_thread;
std::exception_ptr dual_simplex_exception;
auto request_concurrent_halt = [&settings_pdlp]() {
if (settings_pdlp.concurrent_halt != nullptr) { settings_pdlp.concurrent_halt->store(1); }
};
if (!settings.inside_mip) {
dual_simplex_thread = std::thread(run_dual_simplex_thread<i_t, f_t>,
std::ref(dual_simplex_problem),
std::ref(settings_pdlp),
std::ref(sol_dual_simplex_ptr),
std::ref(timer));
dual_simplex_thread = std::thread([&]() {
try {
run_dual_simplex_thread<i_t, f_t>(
dual_simplex_problem, settings_pdlp, sol_dual_simplex_ptr, timer);
} catch (...) {
dual_simplex_exception = std::current_exception();
request_concurrent_halt();
}
});
}
// Create a thread for barrier.
// The barrier handle is owned here so that its destructor runs on the
Expand All @@ -1505,25 +1513,28 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
std::unique_ptr<
std::tuple<dual_simplex::lp_solution_t<i_t, f_t>, dual_simplex::lp_status_t, f_t, f_t, f_t>>
sol_barrier_ptr;
std::exception_ptr barrier_exception;
auto barrier_thread = std::thread([&]() {
auto call_barrier_thread = [&]() {
rmm::cuda_stream_view barrier_stream = rmm::cuda_stream_per_thread;
barrier_handle_ptr = std::make_unique<raft::handle_t>(barrier_stream);
auto barrier_problem = dual_simplex_problem;
barrier_problem.handle_ptr = barrier_handle_ptr.get();

run_barrier_thread<i_t, f_t>(std::ref(barrier_problem),
std::ref(settings_pdlp),
std::ref(sol_barrier_ptr),
std::ref(timer));
};
if (settings.num_gpus > 1) {
problem.handle_ptr->sync_stream();
raft::device_setter device_setter(1); // Scoped variable
CUOPT_LOG_DEBUG("Barrier device: %d", device_setter.get_current_device());
call_barrier_thread();
} else {
call_barrier_thread();
try {
auto call_barrier_thread = [&]() {
rmm::cuda_stream_view barrier_stream = rmm::cuda_stream_per_thread;
barrier_handle_ptr = std::make_unique<raft::handle_t>(barrier_stream);
auto barrier_problem = dual_simplex_problem;
barrier_problem.handle_ptr = barrier_handle_ptr.get();

run_barrier_thread<i_t, f_t>(barrier_problem, settings_pdlp, sol_barrier_ptr, timer);
};
if (settings.num_gpus > 1) {
problem.handle_ptr->sync_stream();
raft::device_setter device_setter(1); // Scoped variable
CUOPT_LOG_DEBUG("Barrier device: %d", device_setter.get_current_device());
call_barrier_thread();
} else {
call_barrier_thread();
}
} catch (...) {
barrier_exception = std::current_exception();
request_concurrent_halt();
}
});

Expand All @@ -1540,19 +1551,22 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
try {
sol_pdlp = run_pdlp(problem, settings_pdlp, timer, is_batch_mode);
} catch (...) {
pdlp_exception = std::current_exception();
*settings_pdlp.concurrent_halt = 1;
std::rethrow_exception(pdlp_exception);
pdlp_exception = std::current_exception();
request_concurrent_halt();
}

// Wait for dual simplex thread to finish
if (!settings.inside_mip) { dual_simplex_thread.join(); }
if (dual_simplex_thread.joinable()) { dual_simplex_thread.join(); }

barrier_thread.join();
if (barrier_thread.joinable()) { barrier_thread.join(); }
// At this point, it is safe to destroy the barrier context since we're outside of any PDLP graph
// capture.
barrier_handle_ptr.reset();

if (pdlp_exception) { std::rethrow_exception(pdlp_exception); }
if (dual_simplex_exception) { std::rethrow_exception(dual_simplex_exception); }
if (barrier_exception) { std::rethrow_exception(barrier_exception); }

// copy the dual simplex solution to the device
auto sol_dual_simplex =
!settings.inside_mip
Expand Down
25 changes: 25 additions & 0 deletions cpp/tests/linear_programming/pdlp_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <thrust/functional.h>
#include <thrust/logical.h>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <chrono>
Expand Down Expand Up @@ -142,6 +143,30 @@ TEST(pdlp_class, precision_mixed)
1e-2);
}

TEST(pdlp_class, concurrent_pdlp_exception_joins_worker_threads)
{
const raft::handle_t handle_{};

auto path = make_path_absolute("linear_programming/afiro_original.mps");
cuopt::mps_parser::mps_data_model_t<int, double> op_problem =
cuopt::mps_parser::parse_mps<int, double>(path, true);

auto settings = pdlp_solver_settings_t<int, double>{};
settings.method = cuopt::linear_programming::method_t::Concurrent;
settings.presolver = cuopt::linear_programming::presolver_t::None;
settings.log_to_console = false;
// In concurrent mode, dual simplex and barrier workers are started before PDLP validates that
// all_primal_feasible is batch-only. This exercises the exception path with live worker threads.
settings.all_primal_feasible = true;

optimization_problem_solution_t<int, double> solution = solve_lp(&handle_, op_problem, settings);
const auto error_status = solution.get_error_status();

EXPECT_EQ(error_status.get_error_type(), cuopt::error_type_t::ValidationError);
EXPECT_THAT(error_status.what(),
testing::HasSubstr("all_primal_feasible only applies in batch mode"));
}

TEST(pdlp_class, run_double_very_low_accuracy)
{
const raft::handle_t handle_{};
Expand Down
Loading