diff --git a/ci/build_wheel_cuopt.sh b/ci/build_wheel_cuopt.sh index f624b27705..449bc19665 100755 --- a/ci/build_wheel_cuopt.sh +++ b/ci/build_wheel_cuopt.sh @@ -41,6 +41,10 @@ EXCLUDE_ARGS=( --exclude "libcusolver.so.*" --exclude "libcusparse.so.*" --exclude "libcuopt.so" + --exclude "libcuopt_base.so" + --exclude "libcuopt_routing.so" + --exclude "libcuopt_lp.so" + --exclude "libcuopt_grpc.so" --exclude "librapids_logger.so" --exclude "librmm.so" ) diff --git a/ci/test_skills_assets.sh b/ci/test_skills_assets.sh index c75645cb93..3436d721b9 100755 --- a/ci/test_skills_assets.sh +++ b/ci/test_skills_assets.sh @@ -111,7 +111,7 @@ if [[ -n "${CONDA_PREFIX:-}" ]]; then base=$(basename "$cfile" .c) rel="${cfile#"$REPO_ROOT/"}" log "Building and running C asset: $rel" - if ! (cd "$dir" && "${CC}" -I"${INCLUDE_PATH}" -L"${LIB_PATH}" -o "$base" "$(basename "$cfile")" -lcuopt); then + if ! (cd "$dir" && "${CC}" -I"${INCLUDE_PATH}" -L"${LIB_PATH}" -o "$base" "$(basename "$cfile")" -lcuopt -lcuopt_lp); then FAILED+=("$rel (build)") log "FAIL: $rel (build)" continue diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml index 268c1eaef0..ba25054311 100644 --- a/conda/recipes/libcuopt/recipe.yaml +++ b/conda/recipes/libcuopt/recipe.yaml @@ -165,6 +165,10 @@ outputs: - package_contents: files: - lib/libcuopt.so + - lib/libcuopt_base.so + - lib/libcuopt_routing.so + - lib/libcuopt_lp.so + - lib/libcuopt_grpc.so - bin/cuopt_cli - bin/cuopt_grpc_server about: diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index fb8f87b809..97d61cf0e3 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -492,6 +492,9 @@ if (BUILD_TESTS) endif () set(CUOPT_SRC_FILES) +set(CUOPT_BASE_SRC_FILES) +set(CUOPT_ROUTING_SRC_FILES) +set(CUOPT_LP_SRC_FILES) set(MPS_FAST_SRC_FILES) add_subdirectory(src) @@ -502,7 +505,9 @@ set_source_files_properties( PROPERTIES COMPILE_OPTIONS "--split-compile=0") if (HOST_LINEINFO) - set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1") + set_source_files_properties( + ${CUOPT_BASE_SRC_FILES} ${CUOPT_ROUTING_SRC_FILES} ${CUOPT_LP_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) @@ -515,16 +520,17 @@ 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 to solver source files (not gRPC infrastructure). # 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_BASE_SRC_FILES} ${CUOPT_ROUTING_SRC_FILES} ${CUOPT_LP_SRC_FILES} + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} APPEND PROPERTY COMPILE_OPTIONS "-UNDEBUG") endif () if (NOT SKIP_GRPC_BUILD) - # Add gRPC mapper files and generated protobuf sources + # gRPC integration layer: maps proto <-> C++ LP/routing APIs; compiled into cuopt_grpc set(GRPC_INFRA_FILES ${DATA_PROTO_SRCS} ${PROTO_SRCS} @@ -538,8 +544,8 @@ if (NOT SKIP_GRPC_BUILD) src/grpc/client/grpc_client_env.cpp src/grpc/client/cython_grpc_client.cpp src/grpc/client/solve_remote.cpp + src/grpc/client/grpc_registration.cpp ) - list(APPEND CUOPT_SRC_FILES ${GRPC_INFRA_FILES}) # Always keep NDEBUG defined for gRPC infrastructure files so that abseil # headers inline Mutex::Dtor() instead of emitting an external call. @@ -548,22 +554,176 @@ if (NOT SKIP_GRPC_BUILD) # at runtime with "undefined symbol: absl::…::Mutex::Dtor". set_property(SOURCE ${GRPC_INFRA_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} APPEND PROPERTY COMPILE_OPTIONS "-DNDEBUG") + # Include gRPC sources in CUOPT_SRC_FILES so cuopt_objs (and cuopt_static for tests) + # have solve_lp_remote / solve_mip_remote defined when CUOPT_ENABLE_GRPC is set. + list(APPEND CUOPT_SRC_FILES ${GRPC_INFRA_FILES}) endif (NOT SKIP_GRPC_BUILD) +# ################################################################################################## +# - cuopt component libraries (SHARED) ----------------------------------------------------------- + +# Helper: apply compile options, RPATH, and common include paths to all cuOpt component libs +function(cuopt_configure_component target) + set_target_properties(${target} PROPERTIES + CXX_SCAN_FOR_MODULES OFF + BUILD_RPATH_USE_ORIGIN TRUE + INSTALL_RPATH "\$ORIGIN" + ) + target_compile_options(${target} + PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" + "$<$:${CUOPT_CUDA_FLAGS}>" + ) + target_compile_definitions(${target} + PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" + ) + target_include_directories(${target} + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_BINARY_DIR}" + PUBLIC + "$" + "$" + INTERFACE + "$" + ) +endfunction() + +# Compute git hash and generate build_info.hpp before any component is defined +execute_process( + COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message("-- Building with GIT_COMMIT_HASH = '${GIT_COMMIT_HASH}'") + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/utilities/build_info.hpp.in + ${CMAKE_CURRENT_BINARY_DIR}/include/utilities/build_info.hpp + @ONLY +) + +list(JOIN CMAKE_CUDA_ARCHITECTURES "," JOINED_CUDA_ARCHITECTURES) + +set(CUOPT_PRIVATE_CUDA_LIBS + CUDA::cublasLt + CUDA::curand + CUDA::cusolver + TBB::tbb + OpenMP::OpenMP_CXX) + +get_filename_component(CUDSS_MT_LIB_FILE_NAME "${CUDSS_MT_LIB_FILE}" NAME) + +# cuopt_base: utilities + linear algebra (stateful shared infra — logger, work scheduler) +add_library(cuopt_base SHARED ${CUOPT_BASE_SRC_FILES}) +cuopt_configure_component(cuopt_base) +target_include_directories(cuopt_base PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include") +target_compile_definitions(cuopt_base PUBLIC + CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" + CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}" +) +target_link_libraries(cuopt_base + PUBLIC + rmm::rmm + rapids_logger::rapids_logger + CCCL::CCCL + raft::raft + CUDA::cublas + CUDA::cusparse + PRIVATE + OpenMP::OpenMP_CXX + OpenMP::OpenMP_CUDA +) +add_library(cuopt::base ALIAS cuopt_base) + +# cuopt_routing: VRP / routing engine (omitted when SKIP_ROUTING_BUILD=ON) +if(NOT SKIP_ROUTING_BUILD) + add_library(cuopt_routing SHARED ${CUOPT_ROUTING_SRC_FILES}) + cuopt_configure_component(cuopt_routing) + target_link_libraries(cuopt_routing + PUBLIC cuopt_base + PRIVATE simde::simde OpenMP::OpenMP_CXX OpenMP::OpenMP_CUDA + ) + add_library(cuopt::routing ALIAS cuopt_routing) +endif() + +# cuopt_lp: LP / MIP / numerical optimization engine +add_library(cuopt_lp SHARED ${CUOPT_LP_SRC_FILES}) +cuopt_configure_component(cuopt_lp) +target_include_directories(cuopt_lp PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty" + "${CMAKE_CURRENT_SOURCE_DIR}/src/io" + "${CUDSS_INCLUDE}" + $<$:${BZIP2_INCLUDE_DIRS}> + $<$:${ZLIB_INCLUDE_DIRS}> +) +# Adding Papilo as a system include messes up clang's include resolution if papilo is already installed as a conda package +target_include_directories(cuopt_lp PRIVATE + "${papilo_SOURCE_DIR}/src" + "${papilo_BINARY_DIR}" +) +target_include_directories(cuopt_lp SYSTEM PRIVATE + "${pslp_SOURCE_DIR}/include" + "${dejavu_SOURCE_DIR}" +) +target_compile_definitions(cuopt_lp + PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API + PRIVATE CUDSS_MT_LIB_FILE_NAME="${CUDSS_MT_LIB_FILE_NAME}" +) +target_link_libraries(cuopt_lp + PUBLIC + cuopt_base + ${CUDSS_LIB_FILE} + PRIVATE + ${CUOPT_PRIVATE_CUDA_LIBS} + OpenMP::OpenMP_CUDA + simde::simde + nccl_external +) +target_link_libraries(cuopt_lp PRIVATE $) +add_dependencies(cuopt_lp PSLP) +target_include_directories(cuopt_lp SYSTEM PRIVATE + $) +target_compile_definitions(cuopt_lp PRIVATE TBB_PREVIEW_GLOBAL_CONTROL KAMINPAR_64BIT_EDGE_IDS) +target_link_libraries(cuopt_lp PRIVATE $) +if (TARGET KaMinPar) + add_dependencies(cuopt_lp KaMinPar) +endif () +add_library(cuopt::lp ALIAS cuopt_lp) + +# cuopt_grpc: gRPC bridge — proto mappers + Cython client (LP and routing over gRPC) +# Depends on cuopt_lp and cuopt_routing so it can reference both APIs without forcing +# either component to take a gRPC dependency. +if(NOT SKIP_GRPC_BUILD) + add_library(cuopt_grpc SHARED ${GRPC_INFRA_FILES}) + cuopt_configure_component(cuopt_grpc) + target_include_directories(cuopt_grpc PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/client" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated" + "${CMAKE_CURRENT_SOURCE_DIR}/src/io" + ) + if(NOT SKIP_ROUTING_BUILD) + target_link_libraries(cuopt_grpc PUBLIC cuopt_lp cuopt_routing PRIVATE protobuf::libprotobuf gRPC::grpc++) + else() + target_link_libraries(cuopt_grpc PUBLIC cuopt_lp PRIVATE protobuf::libprotobuf gRPC::grpc++) + endif() + add_library(cuopt::grpc ALIAS cuopt_grpc) +endif() + +# ################################################################################################## +# - cuopt_objs: OBJECT library used by cuopt_static for internal test builds --------------------- add_library(cuopt_objs OBJECT ${CUOPT_SRC_FILES} ) - set_target_properties(cuopt_objs PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_SCAN_FOR_MODULES OFF ) - target_compile_definitions(cuopt_objs - PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" - PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API + PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" + PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API ) - target_compile_options(cuopt_objs PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" "$<$:${CUOPT_CUDA_FLAGS}>" @@ -580,8 +740,8 @@ target_include_directories(cuopt_objs PRIVATE ) target_include_directories(cuopt_objs SYSTEM PRIVATE - "${pslp_SOURCE_DIR}/include" - "${dejavu_SOURCE_DIR}" + "${pslp_SOURCE_DIR}/include" + "${dejavu_SOURCE_DIR}" ) target_include_directories(cuopt_objs @@ -607,13 +767,6 @@ target_include_directories(cuopt_objs target_link_libraries(cuopt_objs PRIVATE $) add_dependencies(cuopt_objs PSLP) -# Link KaMinPar by file to avoid export dependency tracking (mirrors PSLP above). -# KaMinPar is a from-source static library fully embedded into libcuopt.so; it is never -# installed (INSTALL_KAMINPAR OFF) and consumers of cuopt::cuopt never use it, so it must -# not leak into cuopt's exported link interface (otherwise rapids_export fails with -# "target KaMinPar is not in any export set"). libKaMinPar.a is self-contained (the -# kaminpar-common OBJECT lib is archived into it); we only need its public headers -# (, which pulls in stdlib + TBB) at compile time. target_include_directories(cuopt_objs SYSTEM PRIVATE $) # partitioner.cpp includes . Because KaMinPar is linked by file, cuopt @@ -632,45 +785,14 @@ if (TARGET KaMinPar) add_dependencies(cuopt_objs KaMinPar) endif () -# ################################################################################################## -# - link libraries -------------------------------------------------------------------------------- - -set(CUOPT_PRIVATE_CUDA_LIBS - CUDA::curand - CUDA::cusolver - TBB::tbb - OpenMP::OpenMP_CXX) - -list(PREPEND CUOPT_PRIVATE_CUDA_LIBS CUDA::cublasLt) - -# Pass CUDSS_MT_LIB_FILE_NAME as a compile definition -get_filename_component(CUDSS_MT_LIB_FILE_NAME "${CUDSS_MT_LIB_FILE}" NAME) -target_compile_definitions(cuopt_objs PRIVATE CUDSS_MT_LIB_FILE_NAME="${CUDSS_MT_LIB_FILE_NAME}") - -execute_process( - COMMAND git rev-parse --short HEAD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE -) -message("-- Building with GIT_COMMIT_HASH = '${GIT_COMMIT_HASH}'") - -# Generate build_info.hpp from template -# configure_file() only updates the output if content changes, avoiding unnecessary rebuilds -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/src/utilities/build_info.hpp.in - ${CMAKE_CURRENT_BINARY_DIR}/include/utilities/build_info.hpp - @ONLY +target_compile_definitions(cuopt_objs + PRIVATE CUDSS_MT_LIB_FILE_NAME="${CUDSS_MT_LIB_FILE_NAME}" + PUBLIC + CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" + CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}" ) - -# Add the generated include directory target_include_directories(cuopt_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include) -list(JOIN CMAKE_CUDA_ARCHITECTURES "," JOINED_CUDA_ARCHITECTURES) -target_compile_definitions(cuopt_objs PUBLIC - CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" - CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}") - target_link_libraries(cuopt_objs PUBLIC CUDA::cublas @@ -735,16 +857,47 @@ if (BUILD_TESTS) add_subdirectory(tests) endif (BUILD_TESTS) -add_library(cuopt SHARED $) +# ################################################################################################## +# - cuopt: thin umbrella shared library ----------------------------------------------------------- +# Links publicly against the four component shared libs so that -lcuopt keeps working for +# all existing consumers. + +# Reference one symbol from each component lib so --as-needed keeps them in +# DT_NEEDED. The constructor in grpc_registration.cpp fires when +# libcuopt_grpc.so is loaded and wires up the remote-solve function pointers +# in libcuopt_lp.so, so that must happen whenever -lcuopt is used. +set(_UMBRELLA_SRC [=[ +// Thin umbrella: one symbol reference per component so --as-needed keeps +// each library in DT_NEEDED. +extern "C" void cuOptDestroyProblem(void*); // libcuopt_lp.so +__attribute__((used)) void (* const _cuopt_lp_anchor)(void*) = &cuOptDestroyProblem; +]=]) + +if(NOT SKIP_GRPC_BUILD) + string(APPEND _UMBRELLA_SRC [=[ +// solve_lp_remote is in libcuopt_grpc.so; pulling it in ensures the +// grpc_registration constructor runs and registers the remote-solve +// function pointers in libcuopt_lp.so. +#include +#include +namespace cuopt::mathematical_optimization { +__attribute__((used)) solve_lp_remote_fn_t const _cuopt_grpc_anchor = + &solve_lp_remote; +} +]=]) +endif() + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "${_UMBRELLA_SRC}") + +add_library(cuopt SHARED "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp") add_library(cuopt::cuopt ALIAS cuopt) -set_target_properties(cuopt - PROPERTIES BUILD_RPATH "\$ORIGIN" + +set_target_properties(cuopt PROPERTIES + BUILD_RPATH_USE_ORIGIN TRUE INSTALL_RPATH "\$ORIGIN" - INTERFACE_POSITION_INDEPENDENT_CODE ON CXX_SCAN_FOR_MODULES OFF - LINKER_LANGUAGE CUDA ) -# cuopt needs PUBLIC interface for consumers + target_include_directories(cuopt PUBLIC "$" @@ -752,39 +905,21 @@ target_include_directories(cuopt INTERFACE "$" ) -target_link_libraries(cuopt - PUBLIC - CUDA::cublas - CUDA::cusparse - rmm::rmm - rapids_logger::rapids_logger - CCCL::CCCL - raft::raft - ${CUDSS_LIB_FILE} - PRIVATE - ${CUOPT_PRIVATE_CUDA_LIBS} - nccl_external - simde::simde - OpenMP::OpenMP_CXX - OpenMP::OpenMP_CUDA - $<$:protobuf::libprotobuf> - $<$:gRPC::grpc++> -) -target_link_libraries(cuopt PRIVATE $) -add_dependencies(cuopt PSLP) -target_link_libraries(cuopt PRIVATE $) -if (TARGET KaMinPar) - add_dependencies(cuopt KaMinPar) -endif () -# Propagate compile definitions that consumers need when including cuopt headers. -# These were on cuopt directly before the cuopt_objs refactor; $ -# does not carry INTERFACE properties, so we restore them explicitly. target_compile_definitions(cuopt PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" CUSPARSE_ENABLE_EXPERIMENTAL_API ) +if(NOT SKIP_ROUTING_BUILD) + target_link_libraries(cuopt PUBLIC cuopt_base cuopt_routing cuopt_lp) +else() + target_link_libraries(cuopt PUBLIC cuopt_base cuopt_lp) +endif() +if(NOT SKIP_GRPC_BUILD) + target_link_libraries(cuopt PUBLIC cuopt_grpc) +endif() + if (WRITE_FATBIN) file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld" [=[ @@ -818,14 +953,26 @@ else () set(_INCLUDE_DEST include/cuopt/) endif () -# adds the .so files to the runtime deb package +set(CUOPT_COMPONENT_TARGETS cuopt_base cuopt_lp) +if(NOT SKIP_ROUTING_BUILD) + list(APPEND CUOPT_COMPONENT_TARGETS cuopt_routing) +endif() +if(NOT SKIP_GRPC_BUILD) + list(APPEND CUOPT_COMPONENT_TARGETS cuopt_grpc) +endif() +install(TARGETS ${CUOPT_COMPONENT_TARGETS} + DESTINATION ${_LIB_DEST} + COMPONENT runtime + EXPORT cuopt-exports +) + +# Install umbrella shared library (the primary runtime artifact) install(TARGETS cuopt DESTINATION ${_LIB_DEST} COMPONENT runtime EXPORT cuopt-exports ) -# adds the .so files to the development deb package install(TARGETS cuopt DESTINATION ${_LIB_DEST} COMPONENT dev @@ -849,12 +996,14 @@ set(doc_string Provide targets for cuOpt. cuOpt library is a collection of GPU accelerated combinatorial optimization algorithms. +Component targets: cuopt::base, cuopt::routing, cuopt::lp, cuopt::grpc (when gRPC is built) +Umbrella target: cuopt::cuopt (links all component libs — backward-compatible with -lcuopt) ]=]) rapids_export(INSTALL cuopt EXPORT_SET cuopt-exports - GLOBAL_TARGETS cuopt + GLOBAL_TARGETS cuopt ${CUOPT_COMPONENT_TARGETS} NAMESPACE cuopt:: DOCUMENTATION doc_string ) @@ -863,7 +1012,7 @@ rapids_export(INSTALL cuopt # - build export ------------------------------------------------------------------------------- rapids_export(BUILD cuopt EXPORT_SET cuopt-exports - GLOBAL_TARGETS cuopt + GLOBAL_TARGETS cuopt ${CUOPT_COMPONENT_TARGETS} NAMESPACE cuopt:: DOCUMENTATION doc_string ) @@ -926,6 +1075,7 @@ if (NOT BUILD_LP_ONLY) target_link_libraries(cuopt_cli PUBLIC cuopt + $<$:cuopt_grpc> OpenMP::OpenMP_CXX ${CUDSS_LIBRARIES} TBB::tbb @@ -1050,7 +1200,7 @@ if (NOT SKIP_GRPC_BUILD) target_link_libraries(cuopt_grpc_server PUBLIC - cuopt + cuopt_grpc OpenMP::OpenMP_CXX PRIVATE protobuf::libprotobuf diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index e070425eab..f2ee18980a 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -195,7 +196,21 @@ int run_single_file(const std::string& file_path, } try { - if (is_mip) { + if (cuopt::mathematical_optimization::is_remote_execution_enabled()) { + // Remote execution: problem_interface holds a cpu_optimization_problem_t. + // Call solve_lp/mip_remote directly so libcuopt_grpc.so is a real DT_NEEDED + // dependency of this binary rather than an implicit runtime lookup. + auto* cpu_prob = + static_cast*>( + problem_interface.get()); + if (is_mip) { + auto& mip_settings = settings.get_mip_settings(); + auto solution = cuopt::mathematical_optimization::solve_mip_remote(*cpu_prob, mip_settings); + } else { + auto& lp_settings = settings.get_pdlp_settings(); + auto solution = cuopt::mathematical_optimization::solve_lp_remote(*cpu_prob, lp_settings); + } + } else if (is_mip) { auto& mip_settings = settings.get_mip_settings(); auto solution = cuopt::mathematical_optimization::solve_mip(problem_interface.get(), mip_settings); diff --git a/cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp b/cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp new file mode 100644 index 0000000000..c0723d49c4 --- /dev/null +++ b/cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp @@ -0,0 +1,40 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#pragma once + +#include +#include + +// Forward declarations — full types live in libcuopt_lp / libcuopt_grpc headers. +namespace cuopt::mathematical_optimization { + +template +class cpu_optimization_problem_t; + +template +struct pdlp_solver_settings_t; + +template +struct mip_solver_settings_t; + +// Function pointer types — only the instantiation is supported. +using solve_lp_remote_fn_t = std::unique_ptr> (*)( + cpu_optimization_problem_t const&, pdlp_solver_settings_t const&); + +using solve_mip_remote_fn_t = std::unique_ptr> (*)( + cpu_optimization_problem_t const&, mip_solver_settings_t const&); + +// Defined in libcuopt_lp.so (remote_solve_registry.cpp). +// Set to nullptr until libcuopt_grpc.so is loaded and calls register_remote_solvers(). +extern solve_lp_remote_fn_t g_solve_lp_remote_fn; +extern solve_mip_remote_fn_t g_solve_mip_remote_fn; + +// Called by libcuopt_grpc.so's constructor to wire up the real implementations. +void register_remote_solvers(solve_lp_remote_fn_t lp_fn, solve_mip_remote_fn_t mip_fn); + +} // namespace cuopt::mathematical_optimization diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index e8737cf6da..8af5cf4dfc 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -25,5 +25,34 @@ add_subdirectory(barrier) add_subdirectory(branch_and_bound) add_subdirectory(cuts) -set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${UTIL_SRC_FILES} PARENT_SCOPE) +# Aggregate per-domain source lists for the three component libraries +set(CUOPT_BASE_SRC_FILES + ${UTIL_SRC_FILES} + ${LINEAR_ALGEBRA_SRC_FILES} +) + +set(CUOPT_ROUTING_SRC_FILES + ${ROUTING_SRC_FILES} +) + +set(CUOPT_LP_SRC_FILES + ${LP_SRC_FILES} + ${MATH_OPT_SRC_FILES} + ${MIP_SRC_FILES} + ${PARSERS_SRC_FILES} + ${DUAL_SIMPLEX_SRC_FILES} + ${BARRIER_SRC_FILES} + ${BRANCH_AND_BOUND_SRC_FILES} + ${CUTS_SRC_FILES} +) + +set(CUOPT_BASE_SRC_FILES ${CUOPT_BASE_SRC_FILES} PARENT_SCOPE) +set(CUOPT_ROUTING_SRC_FILES ${CUOPT_ROUTING_SRC_FILES} PARENT_SCOPE) +set(CUOPT_LP_SRC_FILES ${CUOPT_LP_SRC_FILES} PARENT_SCOPE) +set(CUOPT_SRC_FILES + ${CUOPT_BASE_SRC_FILES} + ${CUOPT_ROUTING_SRC_FILES} + ${CUOPT_LP_SRC_FILES} + PARENT_SCOPE +) set(MPS_FAST_SRC_FILES ${MPS_FAST_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/barrier/CMakeLists.txt b/cpp/src/barrier/CMakeLists.txt index 650bc733e9..83af7da102 100644 --- a/cpp/src/barrier/CMakeLists.txt +++ b/cpp/src/barrier/CMakeLists.txt @@ -10,5 +10,6 @@ set(BARRIER_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/pinned_host_allocator.cu ) +set(BARRIER_SRC_FILES ${BARRIER_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${BARRIER_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/branch_and_bound/CMakeLists.txt b/cpp/src/branch_and_bound/CMakeLists.txt index 1e40c1bbf1..c9f47c4d55 100644 --- a/cpp/src/branch_and_bound/CMakeLists.txt +++ b/cpp/src/branch_and_bound/CMakeLists.txt @@ -10,5 +10,6 @@ set(BRANCH_AND_BOUND_SRC_FILES ) +set(BRANCH_AND_BOUND_SRC_FILES ${BRANCH_AND_BOUND_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${BRANCH_AND_BOUND_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/cuts/CMakeLists.txt b/cpp/src/cuts/CMakeLists.txt index 813ac88a59..07d945135c 100644 --- a/cpp/src/cuts/CMakeLists.txt +++ b/cpp/src/cuts/CMakeLists.txt @@ -8,5 +8,6 @@ set(CUTS_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/objective_step.cpp ) +set(CUTS_SRC_FILES ${CUTS_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${CUTS_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/dual_simplex/CMakeLists.txt b/cpp/src/dual_simplex/CMakeLists.txt index 228f2aedd7..1f424c69ab 100644 --- a/cpp/src/dual_simplex/CMakeLists.txt +++ b/cpp/src/dual_simplex/CMakeLists.txt @@ -25,5 +25,6 @@ set(DUAL_SIMPLEX_SRC_FILES # Uncomment to enable debug info #set_source_files_properties(${DUAL_SIMPLEX_SRC_FILES} DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1") +set(DUAL_SIMPLEX_SRC_FILES ${DUAL_SIMPLEX_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${DUAL_SIMPLEX_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/grpc/client/grpc_registration.cpp b/cpp/src/grpc/client/grpc_registration.cpp new file mode 100644 index 0000000000..5bf4ef0c45 --- /dev/null +++ b/cpp/src/grpc/client/grpc_registration.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Registers the gRPC-based remote solve implementations with libcuopt_lp.so +// at dynamic-link time (before any user code runs). This breaks the circular +// dependency: libcuopt_lp.so holds nullable function pointers rather than a +// hard reference to symbols in libcuopt_grpc.so. + +#include +#include + +namespace { +__attribute__((constructor)) void register_grpc_remote_solvers() +{ + cuopt::mathematical_optimization::register_remote_solvers( + &cuopt::mathematical_optimization::solve_lp_remote, + &cuopt::mathematical_optimization::solve_mip_remote); +} +} // namespace diff --git a/cpp/src/io/CMakeLists.txt b/cpp/src/io/CMakeLists.txt index cafcffb23f..29bd8dc1e2 100644 --- a/cpp/src/io/CMakeLists.txt +++ b/cpp/src/io/CMakeLists.txt @@ -23,5 +23,6 @@ set(PARSERS_SRC_FILES ${MPS_FAST_SRC_FILES} ) -set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${PARSERS_SRC_FILES} PARENT_SCOPE) +set(PARSERS_SRC_FILES ${PARSERS_SRC_FILES} PARENT_SCOPE) set(MPS_FAST_SRC_FILES ${MPS_FAST_SRC_FILES} PARENT_SCOPE) +set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${PARSERS_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/linear_algebra/CMakeLists.txt b/cpp/src/linear_algebra/CMakeLists.txt index 875a016544..7a8ad1fa00 100644 --- a/cpp/src/linear_algebra/CMakeLists.txt +++ b/cpp/src/linear_algebra/CMakeLists.txt @@ -9,5 +9,6 @@ set(LINEAR_ALGEBRA_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/vector_math.cpp ) +set(LINEAR_ALGEBRA_SRC_FILES ${LINEAR_ALGEBRA_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${LINEAR_ALGEBRA_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/math_optimization/CMakeLists.txt b/cpp/src/math_optimization/CMakeLists.txt index efa1600c54..3a118cf163 100644 --- a/cpp/src/math_optimization/CMakeLists.txt +++ b/cpp/src/math_optimization/CMakeLists.txt @@ -11,5 +11,6 @@ list(PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/tic_toc.cpp ) +set(MATH_OPT_SRC_FILES ${MATH_OPT_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${MATH_OPT_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/mip_heuristics/CMakeLists.txt b/cpp/src/mip_heuristics/CMakeLists.txt index 7705465512..4b8bcfd3a8 100644 --- a/cpp/src/mip_heuristics/CMakeLists.txt +++ b/cpp/src/mip_heuristics/CMakeLists.txt @@ -53,5 +53,6 @@ else() set(MIP_SRC_FILES ${MIP_LP_NECESSARY_FILES} ${MIP_NON_LP_FILES}) endif() +set(MIP_SRC_FILES ${MIP_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${MIP_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index 64d78efbc0..fee37530bd 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -6,7 +6,9 @@ /* clang-format on */ #include -#include +#include + +#include #include #include @@ -908,20 +910,17 @@ std::unique_ptr> solve_mip( try { // Check if remote execution is enabled (always uses CPU backend) -#ifdef CUOPT_ENABLE_GRPC if (is_remote_execution_enabled()) { auto* cpu_prob = dynamic_cast*>(problem_interface); cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); - return solve_mip_remote(*cpu_prob, settings); + if (g_solve_mip_remote_fn == nullptr) { dlopen("libcuopt_grpc.so", RTLD_NOW | RTLD_GLOBAL); } + cuopt_expects(g_solve_mip_remote_fn != nullptr, + error_type_t::RuntimeError, + "Remote execution requires the gRPC component (libcuopt_grpc.so) to be loaded"); + return g_solve_mip_remote_fn(*cpu_prob, settings); } -#else - cuopt_expects( - !is_remote_execution_enabled(), - error_type_t::ValidationError, - "Remote execution was requested, but this build was compiled without gRPC support"); -#endif // Local execution - dispatch to appropriate overload based on problem type auto* cpu_prob = dynamic_cast*>(problem_interface); diff --git a/cpp/src/pdlp/CMakeLists.txt b/cpp/src/pdlp/CMakeLists.txt index 2f90f94872..6b5ac9ebf6 100644 --- a/cpp/src/pdlp/CMakeLists.txt +++ b/cpp/src/pdlp/CMakeLists.txt @@ -5,6 +5,7 @@ # Core LP files always included set(LP_CORE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/remote_solve_registry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu ${CMAKE_CURRENT_SOURCE_DIR}/optimization_problem.cu ${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem.cpp @@ -49,4 +50,5 @@ else() set(LP_SRC_FILES ${LP_CORE_FILES} ${LP_ADAPTER_FILES}) endif() +set(LP_SRC_FILES ${LP_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${LP_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/pdlp/remote_solve_registry.cpp b/cpp/src/pdlp/remote_solve_registry.cpp new file mode 100644 index 0000000000..511c0046d0 --- /dev/null +++ b/cpp/src/pdlp/remote_solve_registry.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include + +namespace cuopt::mathematical_optimization { + +solve_lp_remote_fn_t g_solve_lp_remote_fn = nullptr; +solve_mip_remote_fn_t g_solve_mip_remote_fn = nullptr; + +void register_remote_solvers(solve_lp_remote_fn_t lp_fn, solve_mip_remote_fn_t mip_fn) +{ + g_solve_lp_remote_fn = lp_fn; + g_solve_mip_remote_fn = mip_fn; +} + +} // namespace cuopt::mathematical_optimization diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 2f33a7f8ae..3e0ac5a070 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -6,7 +6,9 @@ /* clang-format on */ #include -#include +#include + +#include #include #include #include @@ -2682,7 +2684,6 @@ std::unique_ptr> solve_lp( "problem_interface cannot be null"); // Check if remote execution is enabled (always uses CPU backend) -#ifdef CUOPT_ENABLE_GRPC if (is_remote_execution_enabled()) { cuopt_expects(!is_batch_mode, error_type_t::ValidationError, @@ -2692,13 +2693,12 @@ std::unique_ptr> solve_lp( cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); - return solve_lp_remote(*cpu_prob, settings); + if (g_solve_lp_remote_fn == nullptr) { dlopen("libcuopt_grpc.so", RTLD_NOW | RTLD_GLOBAL); } + cuopt_expects(g_solve_lp_remote_fn != nullptr, + error_type_t::RuntimeError, + "Remote execution requires the gRPC component (libcuopt_grpc.so) to be loaded"); + return g_solve_lp_remote_fn(*cpu_prob, settings); } -#else - cuopt_expects(!is_remote_execution_enabled(), - error_type_t::ValidationError, - "Remote execution was requested, but this build was compiled without gRPC support"); -#endif // Local execution - dispatch to appropriate overload based on problem type auto* cpu_prob = dynamic_cast*>(problem_interface); diff --git a/cpp/src/routing/CMakeLists.txt b/cpp/src/routing/CMakeLists.txt index 452c4806da..fa9653076b 100644 --- a/cpp/src/routing/CMakeLists.txt +++ b/cpp/src/routing/CMakeLists.txt @@ -1,5 +1,5 @@ # cmake-format: off -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # cmake-format: on @@ -50,4 +50,5 @@ set(ROUTING_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/utilities/check_input.cu ${CMAKE_CURRENT_SOURCE_DIR}/utilities/cython.cu) +set(ROUTING_SRC_FILES ${ROUTING_SRC_FILES} PARENT_SCOPE) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${ROUTING_SRC_FILES} PARENT_SCOPE) diff --git a/docs/cuopt/source/conf.py b/docs/cuopt/source/conf.py index b4749d0167..33bc4c3da1 100644 --- a/docs/cuopt/source/conf.py +++ b/docs/cuopt/source/conf.py @@ -365,7 +365,7 @@ def write_project_json(app, _builder): linkcheck_workers = 5 linkcheck_rate_limit_timeout = 60 -# GitHub and GitLab link checker exceptions +# GitHub, GitLab, and PyPI link checker exceptions linkcheck_ignore = [ # GitHub (Rate Limited) r"https://github\.com/.*", @@ -377,6 +377,8 @@ def write_project_json(app, _builder): r"https://api\.gitlab\.com/.*", r"https://gitlab\.org/.*", r"https://api\.gitlab\.org/.*", + # PyPI (Unreliable in CI networks) + r"https://pypi\.org/.*", ] diff --git a/docs/cuopt/source/cuopt-c/convex/examples/Makefile b/docs/cuopt/source/cuopt-c/convex/examples/Makefile index fef30244f1..d663bb518d 100644 --- a/docs/cuopt/source/cuopt-c/convex/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/convex/examples/Makefile @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,7 +46,7 @@ CC = gcc # Compiler flags CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) +LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -lcuopt_lp -Wl,--allow-shlib-undefined -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) # Automatically discover all C source files in current directory SOURCES = $(wildcard *.c) diff --git a/docs/cuopt/source/cuopt-c/mip/examples/Makefile b/docs/cuopt/source/cuopt-c/mip/examples/Makefile index bc287f0d49..dace6ebd11 100644 --- a/docs/cuopt/source/cuopt-c/mip/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/mip/examples/Makefile @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ CC = gcc # Compiler flags CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) +LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -lcuopt_lp -Wl,--allow-shlib-undefined -Wl,-rpath,$(LIBCUOPT_LIBRARY_PATH) # Automatically discover all C source files in current directory SOURCES = $(wildcard *.c) diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt index 4d24169645..3c31190fe2 100644 --- a/python/libcuopt/CMakeLists.txt +++ b/python/libcuopt/CMakeLists.txt @@ -96,5 +96,7 @@ endif() message(STATUS "libcuopt: Final RPATH = ${rpaths}") set_property(TARGET cuopt PROPERTY INSTALL_RPATH ${rpaths} APPEND) +set_property(TARGET cuopt_base PROPERTY INSTALL_RPATH ${rpaths} APPEND) +set_property(TARGET cuopt_lp PROPERTY INSTALL_RPATH ${rpaths} APPEND) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH ${rpaths} APPEND) set_property(TARGET cuopt_grpc_server PROPERTY INSTALL_RPATH ${rpaths} APPEND)