From 9b3d53bdcbbf4c03f468b7c1ffad2d9718cc8ae4 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 23 Jul 2026 15:40:25 -0500 Subject: [PATCH 01/18] build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp + umbrella MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce three STATIC component libraries that logically partition the cuOpt sources by domain, then fold them into the existing libcuopt.so umbrella via --whole-archive (LINK_LIBRARY:WHOLE_ARCHIVE). Component libraries: - cuopt_base — utilities + linear algebra (logger, work scheduler) - cuopt_routing — VRP / routing engine; links cuopt_base - cuopt_lp — LP / MIP / numerical optimization; links cuopt_base Umbrella: - cuopt SHARED — re-exports all symbols from the three statics via --whole-archive; backward-compatible for GAMS (-lcuopt / libcuopt.so) CMake aliases exposed: cuopt::base, cuopt::routing, cuopt::lp, cuopt::cuopt No source files moved. External build output (libcuopt.so, headers, install layout) is unchanged. SKIP_ROUTING_BUILD=ON continues to work by omitting cuopt_routing from the build and umbrella link. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 288 +++++++++++++++-------- cpp/src/CMakeLists.txt | 31 ++- cpp/src/barrier/CMakeLists.txt | 1 + cpp/src/branch_and_bound/CMakeLists.txt | 1 + cpp/src/cuts/CMakeLists.txt | 1 + cpp/src/dual_simplex/CMakeLists.txt | 1 + cpp/src/io/CMakeLists.txt | 3 +- cpp/src/linear_algebra/CMakeLists.txt | 1 + cpp/src/math_optimization/CMakeLists.txt | 1 + cpp/src/mip_heuristics/CMakeLists.txt | 1 + cpp/src/pdlp/CMakeLists.txt | 1 + cpp/src/routing/CMakeLists.txt | 1 + 12 files changed, 227 insertions(+), 104 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 61f6eb91df..699b727655 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -454,6 +454,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) @@ -464,7 +467,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) @@ -477,16 +482,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 API; lives in the umbrella, not in cuopt_lp set(GRPC_INFRA_FILES ${DATA_PROTO_SRCS} ${PROTO_SRCS} @@ -501,7 +507,6 @@ if (NOT SKIP_GRPC_BUILD) src/grpc/client/cython_grpc_client.cpp src/grpc/client/solve_remote.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. @@ -512,67 +517,166 @@ if (NOT SKIP_GRPC_BUILD) APPEND PROPERTY COMPILE_OPTIONS "-DNDEBUG") endif (NOT SKIP_GRPC_BUILD) -add_library(cuopt SHARED - ${CUOPT_SRC_FILES} -) +# ################################################################################################## +# - cuopt component libraries (STATIC, position-independent for use in the umbrella SHARED) ------ -set_target_properties(cuopt - PROPERTIES BUILD_RPATH "\$ORIGIN" - INSTALL_RPATH "\$ORIGIN" - INTERFACE_POSITION_INDEPENDENT_CODE ON - CXX_SCAN_FOR_MODULES OFF -) +# Helper: apply compile options and common include paths to all cuOpt component libs +function(cuopt_configure_component target) + set_target_properties(${target} PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_SCAN_FOR_MODULES OFF + ) + 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() -target_compile_definitions(cuopt - PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" - PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API +# 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}'") -target_compile_options(cuopt - PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" - "$<$:${CUOPT_CUDA_FLAGS}>" +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/utilities/build_info.hpp.in + ${CMAKE_CURRENT_BINARY_DIR}/include/utilities/build_info.hpp + @ONLY ) -if (WRITE_FATBIN) - file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld" - [=[ - SECTIONS - { - .nvFatBinSegment : { *(.nvFatBinSegment) } - .nv_fatbin : { *(.nv_fatbin) } - } - ]=]) - target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld") -endif () +list(JOIN CMAKE_CUDA_ARCHITECTURES "," JOINED_CUDA_ARCHITECTURES) -add_library(cuopt::cuopt ALIAS cuopt) -# ################################################################################################## -# - include paths --------------------------------------------------------------------------------- -message(STATUS "target include directories CUDSS_INCLUDES = ${CUDSS_INCLUDE}") +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 STATIC ${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 STATIC ${CUOPT_ROUTING_SRC_FILES}) + cuopt_configure_component(cuopt_routing) + target_link_libraries(cuopt_routing + PUBLIC cuopt_base + PRIVATE simde::simde OpenMP::OpenMP_CXX + ) + add_library(cuopt::routing ALIAS cuopt_routing) +endif() + +# cuopt_lp: LP / MIP / numerical optimization engine +add_library(cuopt_lp STATIC ${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 PRIVATE +target_include_directories(cuopt_lp PRIVATE "${papilo_SOURCE_DIR}/src" "${papilo_BINARY_DIR}" ) - -target_include_directories(cuopt SYSTEM PRIVATE +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 +) +target_link_libraries(cuopt_lp PRIVATE $) +add_dependencies(cuopt_lp PSLP) +add_library(cuopt::lp ALIAS cuopt_lp) + +# ################################################################################################## +# - cuopt: umbrella shared library ---------------------------------------------------------------- +# Re-exports all symbols from the three component libs via --whole-archive. +# Backward-compatible: GAMS and any consumer using -lcuopt / libcuopt.so is unaffected. +# gRPC integration layer (maps proto <-> C++ LP API) lives here, not in cuopt_lp. + +message(STATUS "target include directories CUDSS_INCLUDES = ${CUDSS_INCLUDE}") + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "// cuopt umbrella\n") + +if(NOT SKIP_GRPC_BUILD) + set(CUOPT_UMBRELLA_EXTRA_SRCS ${GRPC_INFRA_FILES}) +endif() + +add_library(cuopt SHARED + "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" + ${CUOPT_UMBRELLA_EXTRA_SRCS} +) + +set_target_properties(cuopt PROPERTIES + BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + INTERFACE_POSITION_INDEPENDENT_CODE ON + CXX_SCAN_FOR_MODULES OFF +) + +target_compile_options(cuopt + PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" + "$<$:${CUOPT_CUDA_FLAGS}>" +) target_include_directories(cuopt 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}" - "${CUDSS_INCLUDE}" - $<$:${BZIP2_INCLUDE_DIRS}> - $<$:${ZLIB_INCLUDE_DIRS}> PUBLIC "$" "$" @@ -580,67 +684,34 @@ target_include_directories(cuopt "$" ) -# Link PSLP by file to avoid export dependency tracking -target_link_libraries(cuopt PRIVATE $) -add_dependencies(cuopt PSLP) - -# ################################################################################################## -# - 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 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 -) - -# Add the generated include directory -target_include_directories(cuopt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include) - -list(JOIN CMAKE_CUDA_ARCHITECTURES "," JOINED_CUDA_ARCHITECTURES) -target_compile_definitions(cuopt PUBLIC - CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" - CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}") +if(NOT SKIP_ROUTING_BUILD) + target_link_libraries(cuopt PUBLIC + $) +else() + target_link_libraries(cuopt PUBLIC + $) +endif() 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} - simde::simde - OpenMP::OpenMP_CXX - OpenMP::OpenMP_CUDA $<$:protobuf::libprotobuf> $<$:gRPC::grpc++> ) +if (WRITE_FATBIN) + file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld" + [=[ + SECTIONS + { + .nvFatBinSegment : { *(.nvFatBinSegment) } + .nv_fatbin : { *(.nv_fatbin) } + } + ]=]) + target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld") +endif () + +add_library(cuopt::cuopt ALIAS cuopt) + # ################################################################################################## # - generate tests -------------------------------------------------------------------------------- if (BUILD_TESTS) @@ -669,14 +740,25 @@ else () set(_INCLUDE_DEST include/cuopt/) endif () -# adds the .so files to the runtime deb package +# Install component static libs (runtime: none — statics are build-time only; +# dev: headers are shared, statics go in the dev package for downstream cmake consumers) +set(CUOPT_COMPONENT_TARGETS cuopt_base cuopt_lp) +if(NOT SKIP_ROUTING_BUILD) + list(APPEND CUOPT_COMPONENT_TARGETS cuopt_routing) +endif() +install(TARGETS ${CUOPT_COMPONENT_TARGETS} + DESTINATION ${_LIB_DEST} + COMPONENT dev + 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 @@ -700,12 +782,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 +Umbrella target: cuopt::cuopt (re-exports all symbols — 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 ) @@ -714,7 +798,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 ) 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/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/pdlp/CMakeLists.txt b/cpp/src/pdlp/CMakeLists.txt index f5f26837b6..c90332355b 100644 --- a/cpp/src/pdlp/CMakeLists.txt +++ b/cpp/src/pdlp/CMakeLists.txt @@ -44,4 +44,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/routing/CMakeLists.txt b/cpp/src/routing/CMakeLists.txt index 452c4806da..c92b4d0f3f 100644 --- a/cpp/src/routing/CMakeLists.txt +++ b/cpp/src/routing/CMakeLists.txt @@ -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) From 94755a05d690f1c61458d2caf81af4316f6cd63d Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 24 Jul 2026 09:52:31 -0500 Subject: [PATCH 02/18] build(cmake): fix component library linking and include propagation Four issues found while validating the library split locally: - cuopt_routing was missing OpenMP::OpenMP_CUDA, causing routing CUDA files to reject #pragma omp directives as unknown in CUDA compiler mode - cuopt_lp was missing simde::simde, required by the fast MPS parser (io/experimental_mps_fast/) which uses SIMD intrinsics via simde headers - The umbrella cuopt target was missing src/io in its private include dirs, causing gRPC mapper files (grpc_problem_mapper.cpp) that include mps_parser_internal.hpp to fail to compile - WHOLE_ARCHIVE linkage on the umbrella was PUBLIC, propagating the static sub-libs as link dependencies to all consumers (test binaries). This caused double-definition errors when tests linked both libcuopt.so and the statics. Changed to PRIVATE and re-exposed the statics' transitive PUBLIC deps (rmm, raft, CCCL, CUDA libs) directly on the umbrella so that consumers receive the correct source-fetched include dirs. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 699b727655..380dc294f6 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -599,7 +599,7 @@ if(NOT SKIP_ROUTING_BUILD) cuopt_configure_component(cuopt_routing) target_link_libraries(cuopt_routing PUBLIC cuopt_base - PRIVATE simde::simde OpenMP::OpenMP_CXX + PRIVATE simde::simde OpenMP::OpenMP_CXX OpenMP::OpenMP_CUDA ) add_library(cuopt::routing ALIAS cuopt_routing) endif() @@ -634,6 +634,7 @@ target_link_libraries(cuopt_lp PRIVATE ${CUOPT_PRIVATE_CUDA_LIBS} OpenMP::OpenMP_CUDA + simde::simde ) target_link_libraries(cuopt_lp PRIVATE $) add_dependencies(cuopt_lp PSLP) @@ -673,6 +674,7 @@ target_compile_options(cuopt target_include_directories(cuopt PRIVATE "${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" @@ -685,18 +687,34 @@ target_include_directories(cuopt ) if(NOT SKIP_ROUTING_BUILD) - target_link_libraries(cuopt PUBLIC + target_link_libraries(cuopt PRIVATE $) else() - target_link_libraries(cuopt PUBLIC + target_link_libraries(cuopt PRIVATE $) endif() +# Re-expose the component statics' PUBLIC deps so that consumers of cuopt::cuopt +# (tests, downstream cmake, GAMS) receive the correct include dirs and link targets. target_link_libraries(cuopt + PUBLIC + rmm::rmm + rapids_logger::rapids_logger + CCCL::CCCL + raft::raft + CUDA::cublas + CUDA::cusparse + ${CUDSS_LIB_FILE} PRIVATE $<$:protobuf::libprotobuf> $<$:gRPC::grpc++> ) +target_compile_definitions(cuopt PUBLIC + "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" + CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" + CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}" + CUSPARSE_ENABLE_EXPERIMENTAL_API +) if (WRITE_FATBIN) file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld" From 0390d40edbdcced133ee4e8eac4ceb8e0387dfee Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Fri, 24 Jul 2026 13:48:52 -0500 Subject: [PATCH 03/18] build(conda): verify component static libs in libcuopt package_contents Add libcuopt_base.a, libcuopt_routing.a, and libcuopt_lp.a to the package_contents file check so CI fails fast if any of the three component static libraries are missing from the installed package. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- conda/recipes/libcuopt/recipe.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml index 77c957eade..475fafba73 100644 --- a/conda/recipes/libcuopt/recipe.yaml +++ b/conda/recipes/libcuopt/recipe.yaml @@ -162,6 +162,9 @@ outputs: - package_contents: files: - lib/libcuopt.so + - lib/libcuopt_base.a + - lib/libcuopt_routing.a + - lib/libcuopt_lp.a - bin/cuopt_cli - bin/cuopt_grpc_server about: From efc5256c70cd4df058dca90962e2ab250918eafa Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Mon, 27 Jul 2026 10:08:35 -0500 Subject: [PATCH 04/18] build(cmake): switch component libs to SHARED and move gRPC bridge into cuopt_lp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Component libs (cuopt_base, cuopt_routing, cuopt_lp) are now SHARED instead of STATIC. The umbrella libcuopt.so becomes a thin stub (~15 KB) carrying only DT_NEEDED entries for the three component libs; no code or WHOLE_ARCHIVE baking. The gRPC bridge (mapper + Cython client) moves from the umbrella into cuopt_lp where it semantically belongs — LP/MIP remote solve is an LP concern. The umbrella drops all gRPC sources, include dirs, and protobuf/gRPC link deps. Both RPATH settings use $ORIGIN so component libs find each other when co-installed. Conda package_contents check updated from .a to .so. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- conda/recipes/libcuopt/recipe.yaml | 6 +-- cpp/CMakeLists.txt | 86 +++++++++--------------------- 2 files changed, 28 insertions(+), 64 deletions(-) diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml index 475fafba73..f794204c75 100644 --- a/conda/recipes/libcuopt/recipe.yaml +++ b/conda/recipes/libcuopt/recipe.yaml @@ -162,9 +162,9 @@ outputs: - package_contents: files: - lib/libcuopt.so - - lib/libcuopt_base.a - - lib/libcuopt_routing.a - - lib/libcuopt_lp.a + - lib/libcuopt_base.so + - lib/libcuopt_routing.so + - lib/libcuopt_lp.so - bin/cuopt_cli - bin/cuopt_grpc_server about: diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 380dc294f6..9ebc097d31 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -492,7 +492,7 @@ if (DEFINE_ASSERT) endif () if (NOT SKIP_GRPC_BUILD) - # gRPC integration layer: maps proto <-> C++ LP API; lives in the umbrella, not in cuopt_lp + # gRPC integration layer: maps proto <-> C++ LP API; compiled into cuopt_lp set(GRPC_INFRA_FILES ${DATA_PROTO_SRCS} ${PROTO_SRCS} @@ -523,8 +523,9 @@ endif (NOT SKIP_GRPC_BUILD) # Helper: apply compile options and common include paths to all cuOpt component libs function(cuopt_configure_component target) set_target_properties(${target} PROPERTIES - POSITION_INDEPENDENT_CODE ON CXX_SCAN_FOR_MODULES OFF + BUILD_RPATH_USE_ORIGIN TRUE + INSTALL_RPATH "\$ORIGIN" ) target_compile_options(${target} PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" @@ -572,7 +573,7 @@ set(CUOPT_PRIVATE_CUDA_LIBS 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 STATIC ${CUOPT_BASE_SRC_FILES}) +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 @@ -595,7 +596,7 @@ 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 STATIC ${CUOPT_ROUTING_SRC_FILES}) + add_library(cuopt_routing SHARED ${CUOPT_ROUTING_SRC_FILES}) cuopt_configure_component(cuopt_routing) target_link_libraries(cuopt_routing PUBLIC cuopt_base @@ -605,7 +606,7 @@ if(NOT SKIP_ROUTING_BUILD) endif() # cuopt_lp: LP / MIP / numerical optimization engine -add_library(cuopt_lp STATIC ${CUOPT_LP_SRC_FILES}) +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" @@ -638,47 +639,36 @@ target_link_libraries(cuopt_lp ) target_link_libraries(cuopt_lp PRIVATE $) add_dependencies(cuopt_lp PSLP) +if(NOT SKIP_GRPC_BUILD) + target_sources(cuopt_lp PRIVATE ${GRPC_INFRA_FILES}) + target_include_directories(cuopt_lp PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/client" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated" + ) + target_link_libraries(cuopt_lp PRIVATE protobuf::libprotobuf gRPC::grpc++) +endif() add_library(cuopt::lp ALIAS cuopt_lp) # ################################################################################################## -# - cuopt: umbrella shared library ---------------------------------------------------------------- -# Re-exports all symbols from the three component libs via --whole-archive. -# Backward-compatible: GAMS and any consumer using -lcuopt / libcuopt.so is unaffected. -# gRPC integration layer (maps proto <-> C++ LP API) lives here, not in cuopt_lp. +# - cuopt: thin umbrella shared library ----------------------------------------------------------- +# Links publicly against the three component shared libs so that -lcuopt keeps working for +# all existing consumers. No code of its own except the gRPC bridge (mapper + client) which +# sits here because it spans both the LP API and gRPC transport layers. message(STATUS "target include directories CUDSS_INCLUDES = ${CUDSS_INCLUDE}") -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "// cuopt umbrella\n") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "// thin umbrella\n") -if(NOT SKIP_GRPC_BUILD) - set(CUOPT_UMBRELLA_EXTRA_SRCS ${GRPC_INFRA_FILES}) -endif() - -add_library(cuopt SHARED - "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" - ${CUOPT_UMBRELLA_EXTRA_SRCS} -) +add_library(cuopt SHARED "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp") set_target_properties(cuopt PROPERTIES - BUILD_RPATH "\$ORIGIN" + BUILD_RPATH_USE_ORIGIN TRUE INSTALL_RPATH "\$ORIGIN" - INTERFACE_POSITION_INDEPENDENT_CODE ON CXX_SCAN_FOR_MODULES OFF ) -target_compile_options(cuopt - PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" - "$<$:${CUOPT_CUDA_FLAGS}>" -) - target_include_directories(cuopt - PRIVATE - "${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}" PUBLIC "$" "$" @@ -687,35 +677,11 @@ target_include_directories(cuopt ) if(NOT SKIP_ROUTING_BUILD) - target_link_libraries(cuopt PRIVATE - $) + target_link_libraries(cuopt PUBLIC cuopt_base cuopt_routing cuopt_lp) else() - target_link_libraries(cuopt PRIVATE - $) + target_link_libraries(cuopt PUBLIC cuopt_base cuopt_lp) endif() -# Re-expose the component statics' PUBLIC deps so that consumers of cuopt::cuopt -# (tests, downstream cmake, GAMS) receive the correct include dirs and link targets. -target_link_libraries(cuopt - PUBLIC - rmm::rmm - rapids_logger::rapids_logger - CCCL::CCCL - raft::raft - CUDA::cublas - CUDA::cusparse - ${CUDSS_LIB_FILE} - PRIVATE - $<$:protobuf::libprotobuf> - $<$:gRPC::grpc++> -) -target_compile_definitions(cuopt PUBLIC - "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" - CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}" - CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}" - CUSPARSE_ENABLE_EXPERIMENTAL_API -) - if (WRITE_FATBIN) file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld" [=[ @@ -758,15 +724,13 @@ else () set(_INCLUDE_DEST include/cuopt/) endif () -# Install component static libs (runtime: none — statics are build-time only; -# dev: headers are shared, statics go in the dev package for downstream cmake consumers) set(CUOPT_COMPONENT_TARGETS cuopt_base cuopt_lp) if(NOT SKIP_ROUTING_BUILD) list(APPEND CUOPT_COMPONENT_TARGETS cuopt_routing) endif() install(TARGETS ${CUOPT_COMPONENT_TARGETS} DESTINATION ${_LIB_DEST} - COMPONENT dev + COMPONENT runtime EXPORT cuopt-exports ) From 08c8db47e01ba7e8f60e32af634fb34a3cf4e820 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Mon, 27 Jul 2026 14:33:43 -0500 Subject: [PATCH 05/18] build(cmake): extract gRPC bridge into libcuopt_grpc.so MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves all gRPC infrastructure (proto mappers, Cython client, solve_remote) from cuopt_lp into a new cuopt_grpc SHARED component. cuopt_grpc links cuopt_lp + cuopt_routing (when built), keeping both core solver libs free of any gRPC/protobuf dependency. The grpc_server binary now links cuopt_grpc directly. The umbrella links cuopt_grpc when gRPC is built so -lcuopt continues to expose remote-solve symbols to existing consumers. When PR #1597 (VRP gRPC) lands, routing gRPC sources go into cuopt_grpc alongside the LP ones — no cross-dependency between cuopt_lp and cuopt_routing is needed. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- conda/recipes/libcuopt/recipe.yaml | 1 + cpp/CMakeLists.txt | 33 ++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml index f794204c75..280f425734 100644 --- a/conda/recipes/libcuopt/recipe.yaml +++ b/conda/recipes/libcuopt/recipe.yaml @@ -165,6 +165,7 @@ outputs: - 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 9ebc097d31..80c4c32baf 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -492,7 +492,7 @@ if (DEFINE_ASSERT) endif () if (NOT SKIP_GRPC_BUILD) - # gRPC integration layer: maps proto <-> C++ LP API; compiled into cuopt_lp + # gRPC integration layer: maps proto <-> C++ LP/routing APIs; compiled into cuopt_grpc set(GRPC_INFRA_FILES ${DATA_PROTO_SRCS} ${PROTO_SRCS} @@ -639,16 +639,27 @@ target_link_libraries(cuopt_lp ) target_link_libraries(cuopt_lp PRIVATE $) add_dependencies(cuopt_lp PSLP) +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) - target_sources(cuopt_lp PRIVATE ${GRPC_INFRA_FILES}) - target_include_directories(cuopt_lp PRIVATE + 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" ) - target_link_libraries(cuopt_lp PRIVATE protobuf::libprotobuf gRPC::grpc++) + 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() -add_library(cuopt::lp ALIAS cuopt_lp) # ################################################################################################## # - cuopt: thin umbrella shared library ----------------------------------------------------------- @@ -681,6 +692,9 @@ if(NOT SKIP_ROUTING_BUILD) 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" @@ -728,6 +742,9 @@ 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 @@ -764,8 +781,8 @@ 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 -Umbrella target: cuopt::cuopt (re-exports all symbols — backward-compatible with -lcuopt) +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) ]=]) @@ -967,7 +984,7 @@ if (NOT SKIP_GRPC_BUILD) target_link_libraries(cuopt_grpc_server PUBLIC - cuopt + cuopt_grpc OpenMP::OpenMP_CXX PRIVATE protobuf::libprotobuf From a54e62a5b5abc992bbc6ce1763f9b150df4ddfe4 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Mon, 27 Jul 2026 15:36:57 -0500 Subject: [PATCH 06/18] chore: fix copyright year in routing CMakeLists.txt Co-Authored-By: Claude Sonnet 4.6 --- cpp/src/routing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/routing/CMakeLists.txt b/cpp/src/routing/CMakeLists.txt index c92b4d0f3f..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 From 6c24d1f6af468facaa91874754814a8777e0df7f Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Mon, 27 Jul 2026 16:23:25 -0500 Subject: [PATCH 07/18] fix(ci): exclude component libs from auditwheel repair in cuopt wheel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libcuopt.so is now a thin umbrella with DT_NEEDED on libcuopt_base.so, libcuopt_routing.so, libcuopt_lp.so, and libcuopt_grpc.so. auditwheel traverses DT_NEEDED transitively and failed when it couldn't locate the component libs. Exclude them the same way libcuopt.so is excluded — they ship with the libcuopt wheel and are available at runtime. Co-Authored-By: Claude Sonnet 4.6 --- ci/build_wheel_cuopt.sh | 4 ++++ 1 file changed, 4 insertions(+) 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" ) From 705167fa69e55fe7f64d15a55db026f0d336af71 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Mon, 27 Jul 2026 16:32:29 -0500 Subject: [PATCH 08/18] fix(cmake): include gRPC sources in cuopt_objs for cuopt_static test builds main appends GRPC_INFRA_FILES to CUOPT_SRC_FILES before creating cuopt_objs so cuopt_static (used by NUMOPT_INTERNAL_TEST) gets solve_lp_remote / solve_mip_remote. We dropped that line when we moved those files into cuopt_grpc, causing undefined-reference link failures in tests. Co-Authored-By: Claude Sonnet 4.6 --- cpp/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 9d9d74198a..cfb05e146c 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -515,6 +515,9 @@ 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) # ################################################################################################## From a42419b499ce83fa8baa6aeb087a27ee97d757ac Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 28 Jul 2026 10:40:56 -0500 Subject: [PATCH 09/18] fix(cmake): link cuopt_cli against cuopt_grpc for runtime symbol resolution cuopt_lp.so calls solve_lp/mip_remote (under CUOPT_ENABLE_GRPC) which are defined in cuopt_grpc.so. With --as-needed the linker was dropping libcuopt_grpc.so from executables that never directly referenced a grpc symbol, leaving solve_lp_remote unresolved at runtime. Route remote solves in cuopt_cli directly through solve_lp/mip_remote so libcuopt_grpc.so is a genuine DT_NEEDED of the binary; --as-needed then keeps it in the link and the symbol is in scope when libcuopt_lp.so needs it. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 1 + cpp/cuopt_cli.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index cfb05e146c..5270c70ca2 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -981,6 +981,7 @@ if (NOT BUILD_LP_ONLY) target_link_libraries(cuopt_cli PUBLIC cuopt + $<$:cuopt_grpc> OpenMP::OpenMP_CXX ${CUDSS_LIBRARIES} TBB::tbb diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index feb0e8cd76..992c7f455a 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -175,7 +176,22 @@ int run_single_file(const std::string& file_path, } try { - if (is_mip) { + if (memory_backend == cuopt::mathematical_optimization::memory_backend_t::CPU) { + // 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); From 776bd3bae6e14fbeb290bd15765b996a259d49eb Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 28 Jul 2026 12:18:59 -0500 Subject: [PATCH 10/18] fix(cli): use is_remote_execution_enabled() not memory_backend==CPU memory_backend_t::CPU fires on any CPU-only host even when CUOPT_REMOTE_HOST is not set, incorrectly routing local solves through the gRPC client path. is_remote_execution_enabled() checks CUOPT_REMOTE_HOST + CUOPT_REMOTE_PORT and is the correct guard. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/cuopt_cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index 992c7f455a..2374fe7583 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -176,7 +176,7 @@ int run_single_file(const std::string& file_path, } try { - if (memory_backend == cuopt::mathematical_optimization::memory_backend_t::CPU) { + 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. From 8511bd039dba987c7fe40c8f4276055c30ed8ebf Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 28 Jul 2026 14:19:08 -0500 Subject: [PATCH 11/18] style: apply clang-format to cuopt_cli.cpp Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/cuopt_cli.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index 153ee59385..f2ee18980a 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -200,16 +200,15 @@ int run_single_file(const std::string& file_path, // 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()); + 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); + 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); + auto solution = cuopt::mathematical_optimization::solve_lp_remote(*cpu_prob, lp_settings); } } else if (is_mip) { auto& mip_settings = settings.get_mip_settings(); From f2b22e7d8868f031cd8b0a44cad2d774942f117a Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 28 Jul 2026 15:00:58 -0500 Subject: [PATCH 12/18] fix(build): link nccl_external into cuopt_lp distributed_pdlp files (multi_gpu_engine.cu, distributed_algorithms.cu) use NCCL APIs and are compiled into cuopt_lp. After the library split, cuopt_lp must declare its own NCCL dependency rather than inheriting it from the monolithic cuopt_static target. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 1ab920cce9..fbd8646f3d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -677,6 +677,7 @@ target_link_libraries(cuopt_lp ${CUOPT_PRIVATE_CUDA_LIBS} OpenMP::OpenMP_CUDA simde::simde + nccl_external ) target_link_libraries(cuopt_lp PRIVATE $) add_dependencies(cuopt_lp PSLP) From 90ee992ebec2f7e007c4ada26692521a369497b0 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 29 Jul 2026 09:25:04 -0500 Subject: [PATCH 13/18] =?UTF-8?q?fix(build):=20break=20circular=20libcuopt?= =?UTF-8?q?=5Flp=20=E2=86=92=20libcuopt=5Fgrpc=20symbol=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit solve.cu (LP) called solve_lp_remote/solve_mip_remote directly under CUOPT_ENABLE_GRPC. Those symbols live in libcuopt_grpc.so, which itself depends on libcuopt_lp.so — a circular dependency that caused PDLP_MG_TEST, docs, and server tests to fail when loading libcuopt_lp.so without libcuopt_grpc.so present. Fix: introduce a function-pointer registry in libcuopt_lp.so. - remote_solve_registry.cpp defines g_solve_lp_remote_fn / g_solve_mip_remote_fn (nullptr until gRPC is loaded) and register_remote_solvers(). - grpc_registration.cpp registers the real implementations via a __attribute__((constructor)) that fires when libcuopt_grpc.so is dlopen'd. - solve.cu (LP and MIP) call through the function pointers; a clear RuntimeError is raised if remote execution is requested but the gRPC component is not loaded. Also fix the -lcuopt C API contract: cuopt_umbrella.cpp now references cuOptDestroyProblem so --as-needed keeps libcuopt_lp.so in libcuopt.so's DT_NEEDED, allowing C programs to link with -lcuopt as before. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 11 ++++- .../remote_solve_registry.hpp | 40 +++++++++++++++++++ cpp/src/grpc/client/grpc_registration.cpp | 19 +++++++++ cpp/src/mip_heuristics/solve.cu | 14 +++---- cpp/src/pdlp/CMakeLists.txt | 1 + cpp/src/pdlp/remote_solve_registry.cpp | 17 ++++++++ cpp/src/pdlp/solve.cu | 13 +++--- 7 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp create mode 100644 cpp/src/grpc/client/grpc_registration.cpp create mode 100644 cpp/src/pdlp/remote_solve_registry.cpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index fbd8646f3d..8c9b7f7f5d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -544,6 +544,7 @@ 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 ) # Always keep NDEBUG defined for gRPC infrastructure files so that abseil @@ -861,7 +862,15 @@ endif (BUILD_TESTS) # Links publicly against the four component shared libs so that -lcuopt keeps working for # all existing consumers. -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "// thin umbrella\n") +# Reference cuOptDestroyProblem (from libcuopt_lp.so) so --as-needed keeps +# libcuopt_lp.so in DT_NEEDED, preserving the -lcuopt C API contract for users +# who compile C programs with -lcuopt. +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" [=[ +// Thin umbrella. References cuOptDestroyProblem so --as-needed keeps +// libcuopt_lp.so in DT_NEEDED, preserving the -lcuopt C API contract. +extern "C" void cuOptDestroyProblem(void*); +__attribute__((used)) void (* const _cuopt_lp_anchor)(void*) = &cuOptDestroyProblem; +]=]) add_library(cuopt SHARED "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp") add_library(cuopt::cuopt ALIAS cuopt) 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/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/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index 64d78efbc0..07db7f54c7 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -6,7 +6,7 @@ /* clang-format on */ #include -#include +#include #include #include @@ -908,20 +908,16 @@ 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); + 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 f9b40b34c1..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 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..9e643ba865 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -6,7 +6,7 @@ /* clang-format on */ #include -#include +#include #include #include #include @@ -2682,7 +2682,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 +2691,11 @@ 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); + 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); From a83e0f4cb2581462c01000dff024db73847daef9 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 29 Jul 2026 13:00:52 -0500 Subject: [PATCH 14/18] fix(build): add libcuopt_grpc.so anchor to umbrella DT_NEEDED The grpc_registration constructor (which wires up g_solve_lp_remote_fn) only fires when libcuopt_grpc.so is loaded. Programs that link with -lcuopt must therefore have libcuopt_grpc.so in the load chain. Reference solve_lp_remote in cuopt_umbrella.cpp so --as-needed keeps libcuopt_grpc.so in libcuopt.so's DT_NEEDED alongside libcuopt_lp.so. Any binary that links with -lcuopt now gets both component libs loaded automatically, and the remote-solve function pointers are registered before user code runs. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/CMakeLists.txt | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8c9b7f7f5d..97d61cf0e3 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -862,16 +862,33 @@ endif (BUILD_TESTS) # Links publicly against the four component shared libs so that -lcuopt keeps working for # all existing consumers. -# Reference cuOptDestroyProblem (from libcuopt_lp.so) so --as-needed keeps -# libcuopt_lp.so in DT_NEEDED, preserving the -lcuopt C API contract for users -# who compile C programs with -lcuopt. -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" [=[ -// Thin umbrella. References cuOptDestroyProblem so --as-needed keeps -// libcuopt_lp.so in DT_NEEDED, preserving the -lcuopt C API contract. -extern "C" void cuOptDestroyProblem(void*); -__attribute__((used)) void (* const _cuopt_lp_anchor)(void*) = &cuOptDestroyProblem; +# 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) From 0f3b7e1bf8dead5016bcc55184416a3f31d9b572 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 29 Jul 2026 17:43:55 -0500 Subject: [PATCH 15/18] fix(build): load libcuopt_grpc.so on demand via dlopen for remote execution With the library split, --as-needed strips libcuopt.so and libcuopt_grpc.so from test binary DT_NEEDED since all C API symbols come from libcuopt_lp.so directly. This prevents the __attribute__((constructor)) in grpc_registration.cpp from firing, leaving g_solve_lp_remote_fn/g_solve_mip_remote_fn null. Fix: when remote execution is enabled and the function pointer is still null, call dlopen("libcuopt_grpc.so", RTLD_NOW | RTLD_GLOBAL) to load the gRPC component on demand. Its constructor registers the function pointers before we dereference them. If gRPC is not installed the error message is unchanged. Also add -lcuopt_lp to C example Makefiles and ci/test_skills_assets.sh to satisfy newer linkers that reject "DSO missing from command line" errors when symbols are transitively provided through libcuopt.so's DT_NEEDED. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- ci/test_skills_assets.sh | 2 +- cpp/src/mip_heuristics/solve.cu | 5 +++++ cpp/src/pdlp/solve.cu | 5 +++++ docs/cuopt/source/cuopt-c/convex/examples/Makefile | 2 +- docs/cuopt/source/cuopt-c/mip/examples/Makefile | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) 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/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index 07db7f54c7..370ae5ea4b 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -913,6 +915,9 @@ std::unique_ptr> solve_mip( cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); + 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"); diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 9e643ba865..d46c01e047 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -7,6 +7,8 @@ #include #include + +#include #include #include #include @@ -2691,6 +2693,9 @@ std::unique_ptr> solve_lp( cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); + 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"); diff --git a/docs/cuopt/source/cuopt-c/convex/examples/Makefile b/docs/cuopt/source/cuopt-c/convex/examples/Makefile index fef30244f1..59bc0e0bb0 100644 --- a/docs/cuopt/source/cuopt-c/convex/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/convex/examples/Makefile @@ -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,-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..f8d91d2f5f 100644 --- a/docs/cuopt/source/cuopt-c/mip/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/mip/examples/Makefile @@ -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,-rpath,$(LIBCUOPT_LIBRARY_PATH) # Automatically discover all C source files in current directory SOURCES = $(wildcard *.c) From b872bfb3da840a3dc189c168ace2d67326a8d90f Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 29 Jul 2026 18:33:25 -0500 Subject: [PATCH 16/18] style: apply clang-format and fix copyright years Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- cpp/src/mip_heuristics/solve.cu | 4 +--- cpp/src/pdlp/solve.cu | 4 +--- docs/cuopt/source/cuopt-c/convex/examples/Makefile | 2 +- docs/cuopt/source/cuopt-c/mip/examples/Makefile | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index 370ae5ea4b..fee37530bd 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -915,9 +915,7 @@ std::unique_ptr> solve_mip( cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); - if (g_solve_mip_remote_fn == nullptr) { - dlopen("libcuopt_grpc.so", RTLD_NOW | RTLD_GLOBAL); - } + 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"); diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index d46c01e047..3e0ac5a070 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -2693,9 +2693,7 @@ std::unique_ptr> solve_lp( cuopt_expects(cpu_prob != nullptr, error_type_t::ValidationError, "Remote execution requires CPU memory backend"); - if (g_solve_lp_remote_fn == nullptr) { - dlopen("libcuopt_grpc.so", RTLD_NOW | RTLD_GLOBAL); - } + 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"); diff --git a/docs/cuopt/source/cuopt-c/convex/examples/Makefile b/docs/cuopt/source/cuopt-c/convex/examples/Makefile index 59bc0e0bb0..f54dfb1312 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"); diff --git a/docs/cuopt/source/cuopt-c/mip/examples/Makefile b/docs/cuopt/source/cuopt-c/mip/examples/Makefile index f8d91d2f5f..bc1b5983ae 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"); From 7b32d1ba0de63992d9850544e4575c8c12508671 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 30 Jul 2026 11:29:40 -0500 Subject: [PATCH 17/18] fix(build): allow shlib undefined for split libs, ignore pypi in linkcheck C example Makefiles now pass -Wl,--allow-shlib-undefined so the linker accepts libcuopt_lp.so's transitive deps (librmm, libcudss, etc.) being resolved at runtime via RPATH rather than at link time. Required in wheel installs where auditwheel bundles those deps with mangled names outside the standard search path. Add pypi.org to sphinx linkcheck_ignore; pypi.org consistently times out in CI networks and the link was already present in introduction.rst. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- docs/cuopt/source/conf.py | 4 +++- docs/cuopt/source/cuopt-c/convex/examples/Makefile | 2 +- docs/cuopt/source/cuopt-c/mip/examples/Makefile | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) 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 f54dfb1312..d663bb518d 100644 --- a/docs/cuopt/source/cuopt-c/convex/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/convex/examples/Makefile @@ -46,7 +46,7 @@ CC = gcc # Compiler flags CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -lcuopt_lp -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 bc1b5983ae..dace6ebd11 100644 --- a/docs/cuopt/source/cuopt-c/mip/examples/Makefile +++ b/docs/cuopt/source/cuopt-c/mip/examples/Makefile @@ -22,7 +22,7 @@ CC = gcc # Compiler flags CFLAGS = -I$(INCLUDE_PATH) -Wall -Wextra -LDFLAGS = -L$(LIBCUOPT_LIBRARY_PATH) -lcuopt -lcuopt_lp -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) From 80c1c341b39a25a556770279d2bf05e47711a813 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 30 Jul 2026 14:46:26 -0500 Subject: [PATCH 18/18] fix(build): add cuopt_lp/cuopt_base to pip RPATH in libcuopt CMakeLists After the library split, libcuopt_lp.so and libcuopt_base.so are the libs that directly reference libcudss, librmm, etc. They only had INSTALL_RPATH="$ORIGIN" (set in cpp/CMakeLists.txt). The nvidia pip-package paths ($ORIGIN/../../nvidia/cudss/lib, etc.) were only applied to the umbrella cuopt target, cuopt_cli, and cuopt_grpc_server. At runtime the dynamic linker uses each shared library's own RPATH to find its deps, so libcuopt_lp.so needed the nvidia paths too. Without them, libcudss.so.0 (from nvidia-cudss-cu12) could not be found when C examples were run as standalone binaries from wheel installs. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Ramakrishna Prabhu --- python/libcuopt/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) 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)