Skip to content

Commit 691f270

Browse files
refactor: make to_optimization_problem a free function
cpu_optimization_problem_t::to_optimization_problem() was a virtual member on optimization_problem_interface_t. That put it in the vtable of every implementer, including the CPU one -- so cpu_optimization_problem_t's vtable held an entry that only libcuopt can define. Vtable relocations are resolved eagerly at load time, unlike ordinary function calls, so this cannot be deferred or hidden behind lazy binding. Any library carrying that vtable is unloadable without libcuopt.so present. It is now a free function declared in optimization_problem.hpp and defined in cpu_optimization_problem_to_gpu.cpp, dispatching on the concrete type: auto gpu = to_optimization_problem(problem, &handle); The GPU override was a one-line `return nullptr` ("already a GPU problem"), so the dispatch is a single dynamic_cast and the semantics are unchanged -- a GPU-backed problem still yields nullptr. cpu_optimization_problem_t befriends the function to reach its host-side storage. Six call sites updated across pdlp/solve.cu, mip_heuristics/solve.cu, grpc/server/grpc_worker.cpp and solution_interface_test.cu. Splitting the definition into its own translation unit also keeps <optimization_problem.hpp> and the raft handle out of cpu_optimization_problem.cpp, which is otherwise pure host code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
1 parent b6f656f commit 691f270

11 files changed

Lines changed: 208 additions & 133 deletions

File tree

cpp/include/cuopt/mathematical_optimization/cpu_optimization_problem.hpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,11 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
173173
* @param handle_ptr RAFT handle with CUDA resources for GPU memory allocation.
174174
* @return unique_ptr to new optimization_problem_t with all data copied to GPU
175175
* @throws std::runtime_error if handle_ptr is null
176+
*
177+
* Provided as the free function to_optimization_problem() in optimization_problem.hpp,
178+
* not as a member: keeping it out of this class's vtable is what lets cuopt_client
179+
* load without libcuopt.so.
176180
*/
177-
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
178-
raft::handle_t const* handle_ptr = nullptr) override;
179181

180182
/**
181183
* @brief Write the optimization problem to an MPS file.
@@ -207,6 +209,13 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
207209
void copy_variable_types_to_host(var_t* output, i_t size) const override;
208210

209211
private:
212+
// to_optimization_problem() reads this class's host-side storage directly. It is a free
213+
// function rather than a member so that it stays out of this class's vtable -- see the
214+
// note in optimization_problem_interface.hpp.
215+
template <typename I, typename F>
216+
friend std::unique_ptr<optimization_problem_t<I, F>> to_optimization_problem(
217+
optimization_problem_interface_t<I, F>&, raft::handle_t const*);
218+
210219
problem_category_t problem_category_ = problem_category_t::LP;
211220
bool maximize_{false};
212221
i_t n_vars_{0};

cpp/include/cuopt/mathematical_optimization/optimization_problem.hpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,8 @@ class optimization_problem_t : public optimization_problem_interface_t<i_t, f_t>
352352
template <typename other_f_t>
353353
optimization_problem_t<i_t, other_f_t> convert_to_other_prec(rmm::cuda_stream_view stream) const;
354354

355-
/**
356-
* @brief Returns nullptr since this is already a GPU problem.
357-
* @return nullptr
358-
*/
359-
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
360-
raft::handle_t const* handle_ptr = nullptr) override;
355+
// to_optimization_problem() is a free function declared at the bottom of this header,
356+
// not a virtual member -- see the note in optimization_problem_interface.hpp.
361357

362358
// ============================================================================
363359
// C API support: Copy to host (polymorphic)
@@ -427,5 +423,26 @@ class optimization_problem_t : public optimization_problem_interface_t<i_t, f_t>
427423
std::vector<std::string> row_names_{};
428424
};
429425

426+
/**
427+
* @brief Convert a problem to a GPU-backed optimization_problem_t.
428+
*
429+
* For optimization_problem_t (GPU): returns nullptr (already is one).
430+
* For cpu_optimization_problem_t: creates a new GPU problem, copies data, returns it.
431+
*
432+
* Usage pattern:
433+
* auto temp = to_optimization_problem(problem_interface, &handle);
434+
* optimization_problem_t& op = temp ? *temp : static_cast<optimization_problem_t&>(problem);
435+
*
436+
* A free function rather than a virtual member so that cpu_optimization_problem_t's vtable
437+
* carries no GPU-defined entry; see optimization_problem_interface.hpp.
438+
*
439+
* @param problem The problem to convert.
440+
* @param handle_ptr RAFT handle with CUDA resources. Required for CPU->GPU conversion.
441+
* @return unique_ptr to a new GPU problem, or nullptr if it already is one.
442+
*/
443+
template <typename i_t, typename f_t>
444+
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
445+
optimization_problem_interface_t<i_t, f_t>& problem, raft::handle_t const* handle_ptr = nullptr);
446+
430447
} // namespace CUOPT_EXPORT mathematical_optimization
431448
} // namespace cuopt

cpp/include/cuopt/mathematical_optimization/optimization_problem_interface.hpp

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -478,22 +478,13 @@ class optimization_problem_interface_t {
478478
// Conversion
479479
// ============================================================================
480480

481-
/**
482-
* @brief Convert to a GPU-backed optimization_problem_t.
483-
*
484-
* For optimization_problem_t (GPU): returns nullptr (already is one).
485-
* For cpu_optimization_problem_t: creates new GPU problem, copies data, returns owned pointer.
486-
*
487-
* Usage pattern:
488-
* auto temp = problem_interface->to_optimization_problem(&handle);
489-
* optimization_problem_t& op = temp ? *temp : static_cast<optimization_problem_t&>(*this);
490-
*
491-
* @param handle_ptr RAFT handle with CUDA resources for GPU memory allocation.
492-
* Required for CPU->GPU conversion. Ignored for GPU problems.
493-
* @return unique_ptr to new GPU problem, or nullptr if already a GPU problem
494-
*/
495-
virtual std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
496-
raft::handle_t const* handle_ptr = nullptr) = 0;
481+
// NOTE: CPU -> GPU conversion is deliberately NOT a virtual member here.
482+
//
483+
// As a virtual, it occupied a slot in cpu_optimization_problem_t's vtable, and vtable
484+
// relocations are resolved eagerly at load time. That made every library containing
485+
// the vtable -- including the CUDA-free cuopt_client -- unable to load without
486+
// libcuopt.so present. It is now the free function to_optimization_problem() declared
487+
// in optimization_problem.hpp, which lives in libcuopt where the GPU types do.
497488
};
498489

499490
} // namespace cuopt::mathematical_optimization

cpp/src/grpc/server/grpc_worker.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ static SolveResult run_mip_solve(DeserializedJob& dj,
425425
}
426426

427427
SERVER_LOG_INFO("[Worker] Converting CPU problem to GPU problem...");
428-
auto gpu_problem = dj.problem.to_optimization_problem(&handle);
428+
auto gpu_problem = to_optimization_problem(dj.problem, &handle);
429429

430430
SERVER_LOG_INFO("[Worker] Calling solve_mip...");
431431
auto gpu_solution = cuopt::mathematical_optimization::solve_mip(*gpu_problem, dj.mip_settings);
@@ -486,7 +486,7 @@ static SolveResult run_lp_solve(DeserializedJob& dj,
486486
dj.lp_settings.log_to_console = config.log_to_console;
487487

488488
SERVER_LOG_INFO("[Worker] Converting CPU problem to GPU problem...");
489-
auto gpu_problem = dj.problem.to_optimization_problem(&handle);
489+
auto gpu_problem = to_optimization_problem(dj.problem, &handle);
490490

491491
SERVER_LOG_INFO("[Worker] Calling solve_lp...");
492492
auto gpu_solution = cuopt::mathematical_optimization::solve_lp(*gpu_problem, dj.lp_settings);

cpp/src/mip_heuristics/solve.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,7 @@ std::unique_ptr<mip_solution_interface_t<i_t, f_t>> solve_mip(
894894
raft::handle_t handle(stream);
895895

896896
// Convert CPU problem to GPU problem
897-
auto gpu_problem = cpu_problem.to_optimization_problem(&handle);
897+
auto gpu_problem = to_optimization_problem(cpu_problem, &handle);
898898

899899
// Synchronize before solving to ensure conversion is complete
900900
stream.synchronize();

cpp/src/pdlp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(LP_CORE_FILES
88
${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu
99
${CMAKE_CURRENT_SOURCE_DIR}/optimization_problem.cu
1010
${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem.cpp
11+
${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem_to_gpu.cpp
1112
${CMAKE_CURRENT_SOURCE_DIR}/backend_selection.cpp
1213
${CMAKE_CURRENT_SOURCE_DIR}/utilities/problem_checking.cu
1314
${CMAKE_CURRENT_SOURCE_DIR}/solve.cu

cpp/src/pdlp/cpu_optimization_problem.cpp

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
1111
#include <cuopt/mathematical_optimization/csr_matrix_utils.hpp>
1212
#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
13-
#include <cuopt/mathematical_optimization/optimization_problem.hpp>
1413
#include <cuopt/mathematical_optimization/optimization_problem_utils.hpp>
1514
#include <cuopt/mathematical_optimization/solve_remote.hpp>
1615

@@ -634,100 +633,6 @@ std::vector<var_t> cpu_optimization_problem_t<i_t, f_t>::get_variable_types_host
634633
return variable_types_;
635634
}
636635

637-
// ==============================================================================
638-
// Conversion to optimization_problem_t
639-
// ==============================================================================
640-
641-
template <typename i_t, typename f_t>
642-
std::unique_ptr<optimization_problem_t<i_t, f_t>>
643-
cpu_optimization_problem_t<i_t, f_t>::to_optimization_problem(raft::handle_t const* handle_ptr)
644-
{
645-
if (handle_ptr == nullptr) {
646-
throw std::runtime_error(
647-
"cpu_optimization_problem_t::to_optimization_problem(): "
648-
"handle_ptr is null. A RAFT handle with CUDA resources is required to convert "
649-
"a CPU-backed problem to a GPU-backed optimization_problem_t.");
650-
}
651-
652-
auto gpu_problem = std::make_unique<optimization_problem_t<i_t, f_t>>(handle_ptr);
653-
654-
// Set scalar values
655-
gpu_problem->set_maximize(maximize_);
656-
gpu_problem->set_objective_scaling_factor(objective_scaling_factor_);
657-
gpu_problem->set_objective_offset(objective_offset_);
658-
gpu_problem->set_problem_category(problem_category_);
659-
660-
// Set string values
661-
if (!objective_name_.empty()) gpu_problem->set_objective_name(objective_name_);
662-
if (!problem_name_.empty()) gpu_problem->set_problem_name(problem_name_);
663-
if (!var_names_.empty()) gpu_problem->set_variable_names(var_names_);
664-
if (!row_names_.empty()) gpu_problem->set_row_names(row_names_);
665-
666-
// Set CSR constraint matrix (data will be copied to GPU by optimization_problem_t setters)
667-
// Use A_offsets_ presence as the guard: a valid CSR can have zero non-zeros but still
668-
// needs row offsets to define the number of constraints.
669-
if (!A_offsets_.empty()) {
670-
gpu_problem->set_csr_constraint_matrix(A_.data(),
671-
A_.size(),
672-
A_indices_.data(),
673-
A_indices_.size(),
674-
A_offsets_.data(),
675-
A_offsets_.size());
676-
}
677-
678-
// Set constraint bounds
679-
if (!b_.empty()) { gpu_problem->set_constraint_bounds(b_.data(), b_.size()); }
680-
681-
// Set objective coefficients
682-
if (!c_.empty()) { gpu_problem->set_objective_coefficients(c_.data(), c_.size()); }
683-
684-
// Set quadratic objective if present (GPU setter symmetrizes once: H = Q + Q^T)
685-
if (!Q_values_.empty()) {
686-
gpu_problem->set_quadratic_objective_matrix(Q_values_.data(),
687-
Q_values_.size(),
688-
Q_indices_.data(),
689-
Q_indices_.size(),
690-
Q_offsets_.data(),
691-
Q_offsets_.size());
692-
}
693-
694-
if (!quadratic_constraints_.empty()) {
695-
gpu_problem->set_quadratic_constraints(
696-
std::vector<typename optimization_problem_interface_t<i_t, f_t>::quadratic_constraint_t>(
697-
quadratic_constraints_));
698-
}
699-
700-
// Set variable bounds
701-
if (!variable_lower_bounds_.empty()) {
702-
gpu_problem->set_variable_lower_bounds(variable_lower_bounds_.data(),
703-
variable_lower_bounds_.size());
704-
}
705-
if (!variable_upper_bounds_.empty()) {
706-
gpu_problem->set_variable_upper_bounds(variable_upper_bounds_.data(),
707-
variable_upper_bounds_.size());
708-
}
709-
710-
// Set variable types
711-
if (!variable_types_.empty()) {
712-
gpu_problem->set_variable_types(variable_types_.data(), variable_types_.size());
713-
}
714-
715-
// Set constraint bounds
716-
if (!constraint_lower_bounds_.empty()) {
717-
gpu_problem->set_constraint_lower_bounds(constraint_lower_bounds_.data(),
718-
constraint_lower_bounds_.size());
719-
}
720-
if (!constraint_upper_bounds_.empty()) {
721-
gpu_problem->set_constraint_upper_bounds(constraint_upper_bounds_.data(),
722-
constraint_upper_bounds_.size());
723-
}
724-
725-
// Set row types
726-
if (!row_types_.empty()) { gpu_problem->set_row_types(row_types_.data(), row_types_.size()); }
727-
728-
return gpu_problem;
729-
}
730-
731636
// ==============================================================================
732637
// File I/O
733638
// ==============================================================================

0 commit comments

Comments
 (0)