Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,11 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
* @param handle_ptr RAFT handle with CUDA resources for GPU memory allocation.
* @return unique_ptr to new optimization_problem_t with all data copied to GPU
* @throws std::runtime_error if handle_ptr is null
*
* Provided as the free function to_optimization_problem() in optimization_problem.hpp,
* not as a member: keeping it out of this class's vtable is what lets cuopt_client
* load without libcuopt.so.
*/
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
raft::handle_t const* handle_ptr = nullptr) override;

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

private:
// to_optimization_problem() reads this class's host-side storage directly. It is a free
// function rather than a member so that it stays out of this class's vtable -- see the
// note in optimization_problem_interface.hpp.
template <typename I, typename F>
friend std::unique_ptr<optimization_problem_t<I, F>> to_optimization_problem(
optimization_problem_interface_t<I, F>&, raft::handle_t const*);

problem_category_t problem_category_ = problem_category_t::LP;
bool maximize_{false};
i_t n_vars_{0};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,8 @@ class optimization_problem_t : public optimization_problem_interface_t<i_t, f_t>
template <typename other_f_t>
optimization_problem_t<i_t, other_f_t> convert_to_other_prec(rmm::cuda_stream_view stream) const;

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

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

/**
* @brief Convert a problem to a GPU-backed optimization_problem_t.
*
* For optimization_problem_t (GPU): returns nullptr (already is one).
* For cpu_optimization_problem_t: creates a new GPU problem, copies data, returns it.
*
* Usage pattern:
* auto temp = to_optimization_problem(problem_interface, &handle);
* optimization_problem_t& op = temp ? *temp : static_cast<optimization_problem_t&>(problem);
*
* A free function rather than a virtual member so that cpu_optimization_problem_t's vtable
* carries no GPU-defined entry; see optimization_problem_interface.hpp.
*
* @param problem The problem to convert.
* @param handle_ptr RAFT handle with CUDA resources. Required for CPU->GPU conversion.
* @return unique_ptr to a new GPU problem, or nullptr if it already is one.
*/
template <typename i_t, typename f_t>
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
optimization_problem_interface_t<i_t, f_t>& problem, raft::handle_t const* handle_ptr = nullptr);

} // namespace CUOPT_EXPORT mathematical_optimization
} // namespace cuopt
Original file line number Diff line number Diff line change
Expand Up @@ -478,22 +478,13 @@ class optimization_problem_interface_t {
// Conversion
// ============================================================================

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

} // namespace cuopt::mathematical_optimization
4 changes: 2 additions & 2 deletions cpp/src/grpc/server/grpc_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ static SolveResult run_mip_solve(DeserializedJob& dj,
}

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

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

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

SERVER_LOG_INFO("[Worker] Calling solve_lp...");
auto gpu_solution = cuopt::mathematical_optimization::solve_lp(*gpu_problem, dj.lp_settings);
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/mip_heuristics/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ std::unique_ptr<mip_solution_interface_t<i_t, f_t>> solve_mip(
raft::handle_t handle(stream);

// Convert CPU problem to GPU problem
auto gpu_problem = cpu_problem.to_optimization_problem(&handle);
auto gpu_problem = to_optimization_problem(cpu_problem, &handle);

// Synchronize before solving to ensure conversion is complete
stream.synchronize();
Expand Down
1 change: 1 addition & 0 deletions cpp/src/pdlp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(LP_CORE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu
${CMAKE_CURRENT_SOURCE_DIR}/optimization_problem.cu
${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem_to_gpu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend_selection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utilities/problem_checking.cu
${CMAKE_CURRENT_SOURCE_DIR}/solve.cu
Expand Down
95 changes: 0 additions & 95 deletions cpp/src/pdlp/cpu_optimization_problem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
#include <cuopt/mathematical_optimization/csr_matrix_utils.hpp>
#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
#include <cuopt/mathematical_optimization/optimization_problem.hpp>
#include <cuopt/mathematical_optimization/optimization_problem_utils.hpp>
#include <cuopt/mathematical_optimization/solve_remote.hpp>

Expand Down Expand Up @@ -634,100 +633,6 @@ std::vector<var_t> cpu_optimization_problem_t<i_t, f_t>::get_variable_types_host
return variable_types_;
}

// ==============================================================================
// Conversion to optimization_problem_t
// ==============================================================================

template <typename i_t, typename f_t>
std::unique_ptr<optimization_problem_t<i_t, f_t>>
cpu_optimization_problem_t<i_t, f_t>::to_optimization_problem(raft::handle_t const* handle_ptr)
{
if (handle_ptr == nullptr) {
throw std::runtime_error(
"cpu_optimization_problem_t::to_optimization_problem(): "
"handle_ptr is null. A RAFT handle with CUDA resources is required to convert "
"a CPU-backed problem to a GPU-backed optimization_problem_t.");
}

auto gpu_problem = std::make_unique<optimization_problem_t<i_t, f_t>>(handle_ptr);

// Set scalar values
gpu_problem->set_maximize(maximize_);
gpu_problem->set_objective_scaling_factor(objective_scaling_factor_);
gpu_problem->set_objective_offset(objective_offset_);
gpu_problem->set_problem_category(problem_category_);

// Set string values
if (!objective_name_.empty()) gpu_problem->set_objective_name(objective_name_);
if (!problem_name_.empty()) gpu_problem->set_problem_name(problem_name_);
if (!var_names_.empty()) gpu_problem->set_variable_names(var_names_);
if (!row_names_.empty()) gpu_problem->set_row_names(row_names_);

// Set CSR constraint matrix (data will be copied to GPU by optimization_problem_t setters)
// Use A_offsets_ presence as the guard: a valid CSR can have zero non-zeros but still
// needs row offsets to define the number of constraints.
if (!A_offsets_.empty()) {
gpu_problem->set_csr_constraint_matrix(A_.data(),
A_.size(),
A_indices_.data(),
A_indices_.size(),
A_offsets_.data(),
A_offsets_.size());
}

// Set constraint bounds
if (!b_.empty()) { gpu_problem->set_constraint_bounds(b_.data(), b_.size()); }

// Set objective coefficients
if (!c_.empty()) { gpu_problem->set_objective_coefficients(c_.data(), c_.size()); }

// Set quadratic objective if present (GPU setter symmetrizes once: H = Q + Q^T)
if (!Q_values_.empty()) {
gpu_problem->set_quadratic_objective_matrix(Q_values_.data(),
Q_values_.size(),
Q_indices_.data(),
Q_indices_.size(),
Q_offsets_.data(),
Q_offsets_.size());
}

if (!quadratic_constraints_.empty()) {
gpu_problem->set_quadratic_constraints(
std::vector<typename optimization_problem_interface_t<i_t, f_t>::quadratic_constraint_t>(
quadratic_constraints_));
}

// Set variable bounds
if (!variable_lower_bounds_.empty()) {
gpu_problem->set_variable_lower_bounds(variable_lower_bounds_.data(),
variable_lower_bounds_.size());
}
if (!variable_upper_bounds_.empty()) {
gpu_problem->set_variable_upper_bounds(variable_upper_bounds_.data(),
variable_upper_bounds_.size());
}

// Set variable types
if (!variable_types_.empty()) {
gpu_problem->set_variable_types(variable_types_.data(), variable_types_.size());
}

// Set constraint bounds
if (!constraint_lower_bounds_.empty()) {
gpu_problem->set_constraint_lower_bounds(constraint_lower_bounds_.data(),
constraint_lower_bounds_.size());
}
if (!constraint_upper_bounds_.empty()) {
gpu_problem->set_constraint_upper_bounds(constraint_upper_bounds_.data(),
constraint_upper_bounds_.size());
}

// Set row types
if (!row_types_.empty()) { gpu_problem->set_row_types(row_types_.data(), row_types_.size()); }

return gpu_problem;
}

// ==============================================================================
// File I/O
// ==============================================================================
Expand Down
147 changes: 147 additions & 0 deletions cpp/src/pdlp/cpu_optimization_problem_to_gpu.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

// CPU -> GPU conversion for cpu_optimization_problem_t.
//
// Split out of cpu_optimization_problem.cpp so that the rest of that class -- which is
// pure host code -- can be compiled into the CUDA-free cuopt_client library. This is the
// only member that constructs an optimization_problem_t, so it is the only one that needs
// <optimization_problem.hpp> and a raft handle. It stays in cuopt_objs (libcuopt).
//
// The explicit member instantiations at the bottom are required: the `template class`
// instantiation in cpu_optimization_problem.cpp no longer sees this definition, so it
// cannot emit this member.

#include <cuopt/export.hpp>
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
#include <cuopt/mathematical_optimization/optimization_problem.hpp>

// Required: the explicit instantiations below are guarded on MIP_INSTANTIATE_*.
// Without this header those macros are undefined and this TU emits no symbols.
#include <mip_heuristics/mip_constants.hpp>

#include <memory>
#include <stdexcept>
#include <vector>

namespace cuopt::mathematical_optimization {

// Free function (was a virtual member; see optimization_problem_interface.hpp).
// Dispatches on the concrete type: a GPU problem is already what the caller wants, so it
// yields nullptr, matching the previous optimization_problem_t override.
template <typename i_t, typename f_t>
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
optimization_problem_interface_t<i_t, f_t>& problem, raft::handle_t const* handle_ptr)
{
auto* cpu_problem = dynamic_cast<cpu_optimization_problem_t<i_t, f_t>*>(&problem);
if (cpu_problem == nullptr) {
// Already a GPU-backed problem.
return nullptr;
}
auto& self = *cpu_problem;

if (handle_ptr == nullptr) {
throw std::runtime_error(
"cpu_optimization_problem_t::to_optimization_problem(): "
"handle_ptr is null. A RAFT handle with CUDA resources is required to convert "
"a CPU-backed problem to a GPU-backed optimization_problem_t.");
}

auto gpu_problem = std::make_unique<optimization_problem_t<i_t, f_t>>(handle_ptr);

// Set scalar values
gpu_problem->set_maximize(self.maximize_);
gpu_problem->set_objective_scaling_factor(self.objective_scaling_factor_);
gpu_problem->set_objective_offset(self.objective_offset_);
gpu_problem->set_problem_category(self.problem_category_);

// Set string values
if (!self.objective_name_.empty()) gpu_problem->set_objective_name(self.objective_name_);
if (!self.problem_name_.empty()) gpu_problem->set_problem_name(self.problem_name_);
if (!self.var_names_.empty()) gpu_problem->set_variable_names(self.var_names_);
if (!self.row_names_.empty()) gpu_problem->set_row_names(self.row_names_);

// Set CSR constraint matrix (data will be copied to GPU by optimization_problem_t setters)
// Use self.A_offsets_ presence as the guard: a valid CSR can have zero non-zeros but still
// needs row offsets to define the number of constraints.
if (!self.A_offsets_.empty()) {
gpu_problem->set_csr_constraint_matrix(self.A_.data(),
self.A_.size(),
self.A_indices_.data(),
self.A_indices_.size(),
self.A_offsets_.data(),
self.A_offsets_.size());
}

// Set constraint bounds
if (!self.b_.empty()) { gpu_problem->set_constraint_bounds(self.b_.data(), self.b_.size()); }

// Set objective coefficients
if (!self.c_.empty()) { gpu_problem->set_objective_coefficients(self.c_.data(), self.c_.size()); }

// Set quadratic objective if present (GPU setter symmetrizes once: H = Q + Q^T)
if (!self.Q_values_.empty()) {
gpu_problem->set_quadratic_objective_matrix(self.Q_values_.data(),
self.Q_values_.size(),
self.Q_indices_.data(),
self.Q_indices_.size(),
self.Q_offsets_.data(),
self.Q_offsets_.size());
}

if (!self.quadratic_constraints_.empty()) {
gpu_problem->set_quadratic_constraints(
std::vector<typename optimization_problem_interface_t<i_t, f_t>::quadratic_constraint_t>(
self.quadratic_constraints_));
}

// Set variable bounds
if (!self.variable_lower_bounds_.empty()) {
gpu_problem->set_variable_lower_bounds(self.variable_lower_bounds_.data(),
self.variable_lower_bounds_.size());
}
if (!self.variable_upper_bounds_.empty()) {
gpu_problem->set_variable_upper_bounds(self.variable_upper_bounds_.data(),
self.variable_upper_bounds_.size());
}

// Set variable types
if (!self.variable_types_.empty()) {
gpu_problem->set_variable_types(self.variable_types_.data(), self.variable_types_.size());
}

// Set constraint bounds
if (!self.constraint_lower_bounds_.empty()) {
gpu_problem->set_constraint_lower_bounds(self.constraint_lower_bounds_.data(),
self.constraint_lower_bounds_.size());
}
if (!self.constraint_upper_bounds_.empty()) {
gpu_problem->set_constraint_upper_bounds(self.constraint_upper_bounds_.data(),
self.constraint_upper_bounds_.size());
}

// Set row types
if (!self.row_types_.empty()) { gpu_problem->set_row_types(self.row_types_.data(), self.row_types_.size()); }

return gpu_problem;
}


// ==============================================================================
// Template instantiations matching cpu_optimization_problem.cpp
// ==============================================================================

#if MIP_INSTANTIATE_FLOAT
template CUOPT_EXPORT std::unique_ptr<optimization_problem_t<int32_t, float>> to_optimization_problem(
optimization_problem_interface_t<int32_t, float>&, raft::handle_t const*);
#endif
#if MIP_INSTANTIATE_DOUBLE
template CUOPT_EXPORT std::unique_ptr<optimization_problem_t<int32_t, double>> to_optimization_problem(
optimization_problem_interface_t<int32_t, double>&, raft::handle_t const*);
#endif

} // namespace cuopt::mathematical_optimization
Loading
Loading