Skip to content
Closed
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
152 changes: 140 additions & 12 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,12 @@ if (BUILD_TESTS)
endif ()

set(CUOPT_SRC_FILES)
set(CUOPT_CLIENT_SRC_FILES)
set(MPS_FAST_SRC_FILES)
add_subdirectory(src)

if (HOST_LINEINFO)
set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1")
set_source_files_properties(${CUOPT_SRC_FILES} ${CUOPT_CLIENT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1")
endif ()

# Needed for the fast MPS parser, available on all x86-64-v3 compliant x86 CPUs (essentially since Haswell ~2013)
Expand All @@ -567,11 +568,11 @@ endif ()
# TODO: figure out a set of flags for ARM that fits the range of CPUs we wish to support (neoverse?)
# NEON should be universal on aarch64 and enough for our purposes (parsing) though

# Apply -UNDEBUG only to solver source files (not gRPC infrastructure).
# Must happen before gRPC files are appended to CUOPT_SRC_FILES.
# Apply -UNDEBUG only to solver and parser source files (not gRPC infrastructure).
# Must happen before gRPC files are appended to CUOPT_CLIENT_SRC_FILES.
# Uses APPEND to preserve any existing per-file options (e.g. -g1 from HOST_LINEINFO).
if (DEFINE_ASSERT)
set_property(SOURCE ${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
set_property(SOURCE ${CUOPT_SRC_FILES} ${CUOPT_CLIENT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-UNDEBUG")
endif ()

Expand All @@ -598,9 +599,7 @@ if (NOT SKIP_GRPC_BUILD)
src/grpc/client/grpc_client.cpp
src/grpc/client/grpc_client_env.cpp
src/grpc/client/cython_grpc_client.cpp
src/grpc/client/solve_remote.cpp
)

# Routing (VRP) arm: everything that depends on the routing engine. Kept as
# its own list so a routing-only gRPC client can be split out of the
# cuopt_grpc component without moving code around again.
Expand All @@ -616,7 +615,17 @@ if (NOT SKIP_GRPC_BUILD)
if (CUOPT_ENABLE_GRPC_ROUTING)
list(APPEND GRPC_INFRA_FILES ${GRPC_ROUTING_FILES})
endif ()
list(APPEND CUOPT_SRC_FILES ${GRPC_INFRA_FILES})

# Both arms are CUDA-free -- the routing mappers reference no raft/rmm/thrust
# either -- so the wire protocol and clients build into cuopt_client, shared by
# libcuopt, cuopt_grpc_server and the Python client extensions. One mapper
# implementation, not a client-side fork of it.
list(APPEND CUOPT_CLIENT_SRC_FILES ${GRPC_INFRA_FILES})

# solve_remote.cpp is the local-vs-remote dispatcher: it calls into the GPU
# solver, so it stays in cuopt_objs rather than moving down to cuopt_client.
list(APPEND CUOPT_SRC_FILES src/grpc/client/solve_remote.cpp)
list(APPEND GRPC_INFRA_FILES src/grpc/client/solve_remote.cpp)

# Always keep NDEBUG defined for gRPC infrastructure files so that abseil
# headers inline Mutex::Dtor() instead of emitting an external call.
Expand All @@ -631,6 +640,120 @@ if (NOT SKIP_GRPC_BUILD)
APPEND PROPERTY COMPILE_OPTIONS "$<$<COMPILE_LANGUAGE:CXX>:-fvisibility=default>")
endif (NOT SKIP_GRPC_BUILD)

# ##################################################################################################
# - cuopt_client - CPU-only support library ----------------------------------------------------------
#
# Holds the host-side problem representation (parsers, data_model_view, mps_data_model,
# writers), the gRPC wire protocol (generated protos + mappers), and the gRPC client.
# None of it touches CUDA, so this library links no CUDA runtime.
#
# It exists so the Python extension modules that never call into the GPU -- data_model,
# solver_settings, io, and the gRPC client -- can link something other than libcuopt.so,
# which is what makes a GPU-free client install possible. libcuopt and cuopt_grpc_server
# both link it, so there is exactly one implementation of the mappers, not a client fork.
#
# LANGUAGES is deliberately not CUDA here: adding a .cu file to CUOPT_CLIENT_SRC_FILES
# should fail loudly rather than quietly reintroduce a CUDA dependency.
# Built as an OBJECT library first, mirroring cuopt_objs/cuopt. The shared library below
# keeps hidden visibility and exports only the curated CUOPT_EXPORT surface, while
# cuopt_static (for internal tests) links the objects directly -- internal symbols such as
# the fast MPS parser's mps_phase_registry_t are not exported, and the internal test
# binaries need them.
add_library(cuopt_client_objs OBJECT ${CUOPT_CLIENT_SRC_FILES})
# NOTE: default visibility, deliberately unlike cuopt_objs.
#
# cuopt_objs can hide everything not marked CUOPT_EXPORT because libcuopt has a curated
# public C++ API. cuopt_client is different: it was carved out of the *internals*, so
# libcuopt itself depends on ~214 of its symbols (the whole cpu_optimization_problem_t /
# data_model_view_t / mps_data_model_t / grpc_client_t surface). Those are internal
# cross-library references, not a public API, and hiding them makes libcuopt.so fail to
# load with e.g. "undefined symbol: grpc_client_t::solve_mip".
#
# Curating them behind CUOPT_EXPORT would mean annotating essentially every host-side
# method, so default visibility is the right trade here.
set_target_properties(cuopt_client_objs
PROPERTIES POSITION_INDEPENDENT_CODE ON
CXX_SCAN_FOR_MODULES OFF
)

add_library(cuopt_client SHARED $<TARGET_OBJECTS:cuopt_client_objs>)
add_library(cuopt::cuopt_client ALIAS cuopt_client)

target_include_directories(cuopt_client
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
INTERFACE
"$<INSTALL_INTERFACE:include>"
)

target_compile_definitions(cuopt_client
PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
)

set_target_properties(cuopt_client
PROPERTIES POSITION_INDEPENDENT_CODE ON
CXX_SCAN_FOR_MODULES OFF
BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN"
LINKER_LANGUAGE CXX
)

target_compile_definitions(cuopt_client_objs
PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
)

target_compile_options(cuopt_client_objs
PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CUOPT_CXX_FLAGS}>"
)

target_include_directories(cuopt_client_objs
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/src/io"
"${CMAKE_CURRENT_SOURCE_DIR}/src/grpc"
"${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/client"
"${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated"
"${CMAKE_CURRENT_BINARY_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}/include"
$<$<BOOL:${CUOPT_PARSER_WITH_BZIP2}>:${BZIP2_INCLUDE_DIRS}>
$<$<BOOL:${CUOPT_PARSER_WITH_ZLIB}>:${ZLIB_INCLUDE_DIRS}>
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
INTERFACE
"$<INSTALL_INTERFACE:include>"
)

# CCCL is a compile-time (header-only) dependency here: the fast MPS parser uses
# host helpers from <cuda/cmath> (ceil_div, round_up). It pulls in no CUDA runtime.
# bzip2 / zlib / lz4 are dlopen'd at runtime by file_to_string.cpp, so they are
# header-only here too and deliberately absent from the link line.
# The OBJECT library needs these for their INTERFACE include dirs / defines at compile time.
target_link_libraries(cuopt_client_objs
PUBLIC
rapids_logger::rapids_logger
CCCL::CCCL
PRIVATE
simde::simde
OpenMP::OpenMP_CXX
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:protobuf::libprotobuf>
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
)

target_link_libraries(cuopt_client
PUBLIC
rapids_logger::rapids_logger
CCCL::CCCL
PRIVATE
simde::simde
OpenMP::OpenMP_CXX
${CMAKE_DL_LIBS}
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:protobuf::libprotobuf>
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
)

add_library(cuopt_objs OBJECT
${CUOPT_SRC_FILES}
)
Expand Down Expand Up @@ -757,6 +880,7 @@ target_compile_definitions(cuopt_objs PUBLIC

target_link_libraries(cuopt_objs
PUBLIC
cuopt::cuopt_client
CUDA::cublas
CUDA::cusparse
rmm::rmm
Expand All @@ -777,7 +901,10 @@ target_link_libraries(cuopt_objs
# - generate tests --------------------------------------------------------------------------------
if (BUILD_TESTS)
include(CTest)
add_library(cuopt_static STATIC $<TARGET_OBJECTS:cuopt_objs>)
# Embeds cuopt_client_objs directly rather than linking libcuopt_client.so: the internal
# test binaries reach parser internals that the shared library deliberately does not
# export. Do not also link cuopt::cuopt_client here -- that would duplicate every symbol.
add_library(cuopt_static STATIC $<TARGET_OBJECTS:cuopt_objs> $<TARGET_OBJECTS:cuopt_client_objs>)
target_link_libraries(cuopt_static
PUBLIC
CUDA::cublas
Expand Down Expand Up @@ -839,6 +966,7 @@ target_include_directories(cuopt
)
target_link_libraries(cuopt
PUBLIC
cuopt::cuopt_client
CUDA::cublas
CUDA::cusparse
rmm::rmm
Expand Down Expand Up @@ -904,14 +1032,14 @@ else ()
endif ()

# adds the .so files to the runtime deb package
install(TARGETS cuopt
install(TARGETS cuopt cuopt_client
DESTINATION ${_LIB_DEST}
COMPONENT runtime
EXPORT cuopt-exports
)

# adds the .so files to the development deb package
install(TARGETS cuopt
install(TARGETS cuopt cuopt_client
DESTINATION ${_LIB_DEST}
COMPONENT dev
)
Expand Down Expand Up @@ -939,7 +1067,7 @@ cuOpt library is a collection of GPU accelerated combinatorial optimization algo

rapids_export(INSTALL cuopt
EXPORT_SET cuopt-exports
GLOBAL_TARGETS cuopt
GLOBAL_TARGETS cuopt cuopt_client
NAMESPACE cuopt::
DOCUMENTATION doc_string
)
Expand All @@ -948,7 +1076,7 @@ rapids_export(INSTALL cuopt
# - build export -------------------------------------------------------------------------------
rapids_export(BUILD cuopt
EXPORT_SET cuopt-exports
GLOBAL_TARGETS cuopt
GLOBAL_TARGETS cuopt cuopt_client
NAMESPACE cuopt::
DOCUMENTATION doc_string
)
Expand Down
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
Loading