build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs - #1622
build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs#1622ramakrishnap-nv wants to merge 31 commits into
Conversation
…lp + umbrella 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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
…to cuopt_lp 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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Resolve conflicts between: - Our component library split (cuopt_base/routing/lp/grpc as SHARED + thin umbrella) - main's cuopt_objs OBJECT library approach added in #1581 Both coexist: cuopt_objs + cuopt_static serve internal tests; the SHARED component libs + umbrella serve all other consumers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
/ok to test ec81ebc |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughChangescuOpt componentized build and remote execution
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/CMakeLists.txt (1)
902-918: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winExported component names should match the documented API
cuopt::base/cuopt::lpare build-tree aliases only. The install/export set will expose the real targets (cuopt::cuopt_base,cuopt::cuopt_lp, etc.) unless those targets setEXPORT_NAME, so a consumer usingfind_package(cuopt)won’t be able to link againstcuopt::baseas documented.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/CMakeLists.txt` around lines 902 - 918, Update the component target export configuration used by the rapids_export INSTALL and BUILD calls so installed targets retain the documented names cuopt::base, cuopt::routing, cuopt::lp, and cuopt::grpc. Set the appropriate EXPORT_NAME values on the underlying cuopt_component targets, while preserving cuopt::cuopt as the umbrella target and keeping build-tree aliases consistent.
🧹 Nitpick comments (2)
cpp/CMakeLists.txt (2)
549-556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueGive the git hash lookup a fallback for non-git source trees.
Without
RESULT_VARIABLE/ERROR_QUIET, tarball builds leak git's error to the configure log and bake an empty hash intobuild_info.hpp.♻️ Proposed fallback
execute_process( COMMAND git rev-parse --short HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _git_hash_result + ERROR_QUIET ) +if(NOT _git_hash_result EQUAL 0 OR GIT_COMMIT_HASH STREQUAL "") + set(GIT_COMMIT_HASH "unknown") +endif()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/CMakeLists.txt` around lines 549 - 556, Update the git hash lookup in the top-level CMake configuration to capture the execute_process result and suppress stderr for source trees without Git metadata. When the lookup fails, assign a stable non-empty fallback hash before the existing GIT_COMMIT_HASH message and build_info.hpp generation; preserve the real short HEAD value for Git checkouts.
664-675: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff
cuopt_objsduplicates the component include/definition setup.The object library re-declares papilo/pslp/dejavu includes, CUDSS defines, and architecture defines that
cuopt_configure_component(pluscuopt_lp) already establish. Sincecuopt_objsbackscuopt_staticfor the test builds, drift here means tests compile under a different configuration than shipped libraries. Consider factoring the shared include/definition block into a helper both paths call.Also applies to: 693-694, 720-724
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/CMakeLists.txt` around lines 664 - 675, Refactor the shared include directories and compile definitions currently duplicated by cuopt_objs and the cuopt_configure_component/cuopt_lp setup into a reusable CMake helper. Invoke that helper for both cuopt_objs and the shipped-library path, including the papilo/pslp/dejavu includes, CUDSS definitions, and architecture definitions, while preserving target-specific settings such as POSITION_INDEPENDENT_CODE and logging definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@cpp/CMakeLists.txt`:
- Around line 902-918: Update the component target export configuration used by
the rapids_export INSTALL and BUILD calls so installed targets retain the
documented names cuopt::base, cuopt::routing, cuopt::lp, and cuopt::grpc. Set
the appropriate EXPORT_NAME values on the underlying cuopt_component targets,
while preserving cuopt::cuopt as the umbrella target and keeping build-tree
aliases consistent.
---
Nitpick comments:
In `@cpp/CMakeLists.txt`:
- Around line 549-556: Update the git hash lookup in the top-level CMake
configuration to capture the execute_process result and suppress stderr for
source trees without Git metadata. When the lookup fails, assign a stable
non-empty fallback hash before the existing GIT_COMMIT_HASH message and
build_info.hpp generation; preserve the real short HEAD value for Git checkouts.
- Around line 664-675: Refactor the shared include directories and compile
definitions currently duplicated by cuopt_objs and the
cuopt_configure_component/cuopt_lp setup into a reusable CMake helper. Invoke
that helper for both cuopt_objs and the shipped-library path, including the
papilo/pslp/dejavu includes, CUDSS definitions, and architecture definitions,
while preserving target-specific settings such as POSITION_INDEPENDENT_CODE and
logging definitions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 2d788dd1-855a-4038-900e-9a3eab8f07be
📒 Files selected for processing (13)
conda/recipes/libcuopt/recipe.yamlcpp/CMakeLists.txtcpp/src/CMakeLists.txtcpp/src/barrier/CMakeLists.txtcpp/src/branch_and_bound/CMakeLists.txtcpp/src/cuts/CMakeLists.txtcpp/src/dual_simplex/CMakeLists.txtcpp/src/io/CMakeLists.txtcpp/src/linear_algebra/CMakeLists.txtcpp/src/math_optimization/CMakeLists.txtcpp/src/mip_heuristics/CMakeLists.txtcpp/src/pdlp/CMakeLists.txtcpp/src/routing/CMakeLists.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
/ok to test a54e62a |
CI Test Summary✅ 2 passed · 2 skipped · 11 cancelled / not completed |
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 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
|
/ok to test 705167f |
…lution 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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Conflicts resolved in cpp/CMakeLists.txt: - cuopt_objs: take KaMinPar include-dirs, compile-defs, and dependency from main; drop duplicate variable definitions already hoisted to the top of the file by the split PR (CUOPT_PRIVATE_CUDA_LIBS, git hash, build_info.hpp, JOINED_CUDA_ARCHITECTURES, CUDSS_MT_LIB_FILE_NAME). - cuopt (umbrella): keep HEAD (empty) — the thin umbrella does not need direct CUDA/rmm/PSLP/KaMinPar links; those live in the component libs. KaMinPar is added to cuopt_lp (shared lib that compiles partitioner.cpp) separately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 1bc38f7 |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 8511bd0 |
…y races - Add umbrella anchors for cuopt_base and cuopt_routing. libcuopt.so listed only libcuopt_lp.so and libcuopt_grpc.so in DT_NEEDED; with --as-needed the base and routing components were dropped, so routing vanished entirely under SKIP_GRPC_BUILD. - Export components as cuopt::base/routing/lp/grpc via EXPORT_NAME, matching the names advertised in the package doc string. - Make the remote-solve registry slots std::atomic. The gRPC ELF constructor publishes them during a lazy dlopen while other threads read them. - Move the lazy dlopen into ensure_remote_solvers_loaded(), shared by the LP and MIP dispatchers. - Validate the CPU-problem downcast in cuopt_cli instead of static_cast. - Add cuopt_routing and cuopt_grpc to the pip wheel RPATH list. - Fall back to "unknown" for GIT_COMMIT_HASH in non-git source trees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 91f5b84 |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
cpp/src/pdlp/remote_solve_registry.cpp (1)
19-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd
${CMAKE_DL_LIBS}tocuopt_lp
remote_solve_registry.cppusesdlopen, butcuopt_lphas no explicit dynamic-loader dependency. Add${CMAKE_DL_LIBS}for portability. Current Ubuntu LTS releases resolvedlopenfromlibc, so this is not a current Ubuntu LTS link failure.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/src/pdlp/remote_solve_registry.cpp` around lines 19 - 24, Add ${CMAKE_DL_LIBS} to the cuopt_lp target's link dependencies so the dlopen call in ensure_remote_solvers_loaded has an explicit, portable dynamic-loader dependency. Preserve the existing target configuration and avoid changing the remote solver loading logic.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cpp/CMakeLists.txt`:
- Line 1035: Update both GLOBAL_TARGETS entries in cpp/CMakeLists.txt at lines
1035 and 1044 to use ${CUOPT_COMPONENT_TARGETS} instead of
${CUOPT_COMPONENT_EXPORT_NAMES}, while preserving the existing cuopt target.
In `@cpp/src/pdlp/remote_solve_registry.cpp`:
- Around line 15-16: Update the callback publication order in the remote
registration routine so g_solve_mip_remote_fn is stored before
g_solve_lp_remote_fn, since the LP slot is the readiness sentinel. Add a
concurrent registration and MIP-dispatch unit test that verifies MIP dispatch
cannot observe a ready registry with a null MIP callback.
In `@python/libcuopt/CMakeLists.txt`:
- Around line 98-102: Update the target property assignment in the foreach block
for cuopt-related targets so the CMake set_property arguments place APPEND
before PROPERTY, preventing APPEND from being interpreted as an INSTALL_RPATH
entry. Also update the relevant package checks to reject relative runtime paths.
---
Nitpick comments:
In `@cpp/src/pdlp/remote_solve_registry.cpp`:
- Around line 19-24: Add ${CMAKE_DL_LIBS} to the cuopt_lp target's link
dependencies so the dlopen call in ensure_remote_solvers_loaded has an explicit,
portable dynamic-loader dependency. Preserve the existing target configuration
and avoid changing the remote solver loading logic.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 7351d224-a16d-48f8-838b-f7f085862934
📒 Files selected for processing (7)
cpp/CMakeLists.txtcpp/cuopt_cli.cppcpp/include/cuopt/mathematical_optimization/remote_solve_registry.hppcpp/src/mip_heuristics/solve.cucpp/src/pdlp/remote_solve_registry.cppcpp/src/pdlp/solve.cupython/libcuopt/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (3)
- cpp/src/pdlp/solve.cu
- cpp/src/mip_heuristics/solve.cu
- cpp/cuopt_cli.cpp
register_remote_solvers() stored the LP callback before the MIP callback while
ensure_remote_solvers_loaded() used the LP slot as its readiness sentinel. A
concurrent MIP solve could observe the LP slot set, skip the lazy dlopen, then
read a still-null MIP slot and fail with a spurious "gRPC component not loaded"
error. Publish a separate ready flag after both callbacks instead, so readiness
does not depend on callback count or store order.
Also report dlopen failures via dlerror() rather than discarding them, and link
cuopt_lp against ${CMAKE_DL_LIBS} since it calls dlopen directly.
Use the documented APPEND-before-PROPERTY form in the pip RPATH loop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test a5000c3 |
| * exceptions across the component boundary. Only the `<int, double>` | ||
| * instantiation is supported. | ||
| */ | ||
| using solve_lp_remote_fn_t = std::unique_ptr<lp_solution_interface_t<int, double>> (*)( |
There was a problem hiding this comment.
We should unify these and just have one solve_remote_fn_t
There was a problem hiding this comment.
I tried this and backed it out — I don't think there's a version that comes out ahead. The two callbacks differ in both positions that matter, not just cosmetically.
Settings have no common base. pdlp_solver_settings_t (pdlp/solver_settings.hpp:113) and mip_solver_settings_t (mip/solver_settings.hpp:65) are unrelated classes. The combined solver_settings_t can't stand in either: it's non-copyable and non-movable (all four special members deleted), and neither dispatch site has one — solve_lp/solve_mip only ever receive the specific settings object.
Return types do share a base, but unifying on it costs more than it saves. Both solve_lp and solve_mip return the concrete interface and today pass the callback result straight through:
return remote_fn(*cpu_prob, settings); // solve.cu:2699, mip_heuristics/solve.cu:921A base-typed callback forces a downcast back at both sites. And since function-pointer types are invariant, &solve_lp_remote<int, double> wouldn't bind to a base-returning typedef without also changing the public solve_remote.hpp signatures — which pushes the same downcast into cuopt_cli. That's the exact conversion pattern flagged earlier in this review.
That leaves two shapes, neither good:
- Type-erased settings (
void const*+ tag, or a variant) — trades two precisely-typed callbacks for one untyped one. - An alias template — gives one name but still two distinct types, turns 4 lines into 9, and makes you resolve a template to see what the signature actually is. I wrote it, read it back, and it was worse.
The only genuine unification is a shared base for the two settings classes. That's a public-header change plus a virtual hierarchy on settings, and the payoff is deleting one duplicated parameter type from a typedef. I don't think that trade is worth it right now — but if a third remote solver shows up the balance changes, and it'd be worth revisiting then.
Leaving the two typedefs as-is unless you or @tmckayus feel strongly.
| --exclude "libcuopt.so" | ||
| --exclude "libcuopt_base.so" | ||
| --exclude "libcuopt_routing.so" | ||
| --exclude "libcuopt_lp.so" |
There was a problem hiding this comment.
We might want to call this libcuopt_mathematical_optimization.so. We are trying to move away from lp
There was a problem hiding this comment.
Isn't it a very long name for a library? Normally libraries have very short names and many libraries use abbreviations. Maybe we could do something like: libcuopt_mathopt.so ?
| # Aggregate per-domain source lists for the three component libraries | ||
| set(CUOPT_BASE_SRC_FILES | ||
| ${UTIL_SRC_FILES} | ||
| ${LINEAR_ALGEBRA_SRC_FILES} |
There was a problem hiding this comment.
Linear algebra files are only used inside the mathematical optimization (CUOPT_LP_SRC_FILES), I would put them directly in CUOPT_LP_SRC_FILES
| add_library(cuopt::routing ALIAS cuopt_routing) | ||
| endif() | ||
|
|
||
| # cuopt_lp: LP / MIP / numerical optimization engine |
There was a problem hiding this comment.
Let's call this cuopt_mathematical_optimization: LP / QP / SOCP / MIP
| # 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 [=[ |
There was a problem hiding this comment.
Why do we have source code in the CMakeList.txt? Maybe pull this out into an umbrella.cpp or umbrella.cpp.in and just configure it as appropriate in the CMakeList.txt.
chris-maes
left a comment
There was a problem hiding this comment.
This is concerning to me:
C API users must now link the component that defines the symbols, e.g. -lcuopt -lcuopt_lp for LP/MILP/QP.
It's quite annoying to have to link multiple libraries. Is there a way around this?
The component covers LP, QP, SOCP and MIP, so name it for what it is. Renames the target, the shipped libcuopt_mathematical_optimization.so, the cuopt::mathematical_optimization export, and the source-list variable. Also moves the linear algebra sources out of cuopt_base and into the mathematical optimization component: routing resolves only default_logger() and seed_generator::seed_ from base and references no linear algebra symbols, so they do not belong in the shared base. cuopt_base drops to 1.17 MB. Extracts the umbrella translation unit out of CMakeLists.txt into cmake/umbrella.cpp.in, configured with #cmakedefine rather than assembled with string(APPEND). The C++ now lives in a real .cpp file that can be formatted and read on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Bare -lcuopt stopped resolving once libcuopt.so became a thin umbrella: the linker does not satisfy a consumer's undefined symbols through a dependency's DT_NEEDED (--no-copy-dt-needed-entries, the default since binutils 2.22), so C consumers had to name each component and the example Makefiles had to paper over it with --allow-shlib-undefined. Ship libcuopt.so as a GNU ld script naming the components instead. This is how glibc ships libc.so. -lcuopt resolves to the whole set again and consumers get a direct DT_NEEDED on each component, so the umbrella library and its --as-needed anchor symbols are no longer needed and are removed. cuopt::cuopt becomes an INTERFACE target, leaving CMake and Cython consumers unchanged. libcuopt.so is no longer an ELF object, so it cannot be dlopen()ed: load.py loads the components directly, and the wheel omits the script since nothing in it links. Anything previously linked against libcuopt.so needs a rebuild. Also fixes WRITE_FATBIN, which applied fatbin.ld to the umbrella. That target held no device code after the split, so the section grouping had silently become a no-op; it now applies to the components carrying the fatbins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Rewraps comments that exceeded the column limit after the cuopt_mathematical_optimization rename. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
| add_library(cuopt::base ALIAS cuopt_base) | ||
|
|
||
| # cuopt_routing: VRP / routing engine (omitted when SKIP_ROUTING_BUILD=ON) | ||
| if(NOT SKIP_ROUTING_BUILD) |
There was a problem hiding this comment.
Do we intend to build with
./build.sh libcuopt_routing
./build.sh libcuopt_mathematical_optimization
./build.sh libcuopt_grpc
If so do we still need SKIP_ROUTING_BUILD etc?
| "Remote execution requires CPU memory backend"); | ||
| return solve_mip_remote(*cpu_prob, settings); | ||
| ensure_remote_solvers_loaded(); | ||
| auto* remote_fn = g_solve_mip_remote_fn.load(std::memory_order_acquire); |
There was a problem hiding this comment.
I don't think we should need an atomic for calling the grpc solver.
I believe this comes from a circular dependency of libgrpc on libmathematical_optimizaiton since libgrpc uses cpu_optimization_problem_t, mip_solver_settings_t, and solution types
The umbrella/CLI/Python layer should select local versus remote, with cuopt_grpc depending one-way on mathematical-optimization types. Can we remove the dlopen/atomic registration mechanism entirely?
|
|
||
| #include <dlfcn.h> | ||
|
|
||
| namespace cuopt::mathematical_optimization { |
There was a problem hiding this comment.
Since libcuopt.so already links both mathematical optimization and gRPC, could it own the local-versus-remote dispatch? Then the mathematical-optimization library remains local-only, the dependency stays one-way, and we can remove dlopen, constructor registration, and the atomic function pointers.
There was a problem hiding this comment.
Agreed on the substance — the registry is working around a layering inversion, and cuopt_mathematical_optimization should not know about remote execution at all. Tracked as #1692.
Two notes on the specifics.
The umbrella can no longer own the dispatch. As of b3b8702 libcuopt.so is a GNU ld script rather than a shared library:
INPUT(libcuopt_base.so libcuopt_routing.so libcuopt_mathematical_optimization.so libcuopt_grpc.so)
That was to fix the -lcuopt breakage @chris-maes raised: the linker does not resolve a consumer's undefined symbols through a dependency's DT_NEEDED (--no-copy-dt-needed-entries, default since binutils 2.22), so a thin umbrella forced C consumers to name every component. The script restores plain -lcuopt, but it means there is no umbrella object left to host dispatch.
The atomic is a consequence, not a choice. With the lazy dlopen, libcuopt_grpc.so's ELF constructor publishes the pointers on whichever thread triggers the load while other threads may be reading them — a real race (flagged earlier in this review, and the reason the readiness flag exists). Remove the lazy-load design and the atomics go with it; they are not independently justified.
On where dispatch should move: #1597 already does exactly what you are describing, for routing. No CUOPT_REMOTE_HOST, no is_remote_execution_enabled, no registry, no dlopen — an explicit RoutingClient in cuopt_grpc, with cpu_routing_problem_t as a plain type in cuopt_routing. LP/MIP's env-var dispatch is the outlier. #1692 proposes LPClient / MIPClient mirroring it.
I would rather not do that conversion here. It is a user-visible API change for LP/MIP, it means relocating cuopt_c.cpp and cython_solve.cu (the only two remaining dispatch sites outside the CLI), and #1597 already owns the gRPC client layer and has settled the shape. Doing it there avoids duplicating that design and keeps this PR to the build split.
On the SKIP_ROUTING_BUILD question: yes, still needed. There are no per-component build.sh targets — VALIDARGS is libcuopt / cuopt_grpc_server / cuopt / cuopt_server / cuopt_sh_client / docs / deb. The two do different jobs: cmake --build . --target cuopt_routing controls an incremental build, whereas SKIP_ROUTING_BUILD omits routing from the install set, the export set, the linker script's INPUT(...) list and load.py's expectations. A per-target build cannot express "ship a cuOpt without routing".
Integrates #1625 (symbol visibility controls and exports) with the component split. Beyond the three textual conflicts, the two changes interact in ways git could not see: - #1625 applies hidden visibility to cuopt_objs, which in main is libcuopt.so. On this branch cuopt_objs only backs cuopt_static for tests, so the shipped component libraries would have kept default visibility. The presets now live in cuopt_configure_component(). - Hidden visibility then broke the inter-component ABI. Symbols that were internal to a single libcuopt.so now cross library boundaries, and shared libraries link fine with undefined symbols, so this only surfaced at runtime as symbol lookup errors in 10 tests. Eleven symbols across six headers make up that surface; they are annotated CUOPT_EXPORT. - check_symbols.sh ran against libcuopt.so, which is now a linker script, and asserted the C API is exported from every library. Only mathematical optimization provides the C API, so the recipe checks the components and passes --no-public-api-check where the C API does not belong. The forbidden-symbol checks still run everywhere. Build clean, ctest 125/125, check_symbols passes on all four components. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
One conflict in the conda recipe: main renamed date_string to datetime_string (#1661) in the same block where this branch replaced the prefix_detection entry for libcuopt.so with the component libraries. Both changes kept. Build clean, ctest 125/125, check_symbols passes on all four components. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
Fixed in b3b8702 —
This is the mechanism glibc uses — Verified end to end: Two consequences worth flagging before this merges:
This also removed the umbrella library and its Your other comments are addressed as well:
On "why do we need |
'1' is shell-redirection output and small_mip.mps is a local test problem; neither belongs in the repository. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
cuopt became an INTERFACE target when libcuopt.so turned into a linker script,
and PRIVATE/PUBLIC are not valid on a target that compiles nothing, so the
libcuopt wheel build failed to configure:
CMake Error at CMakeLists.txt:65 (target_link_libraries):
INTERFACE library can only be used with the INTERFACE keyword of
target_link_libraries
The call was redundant anyway. argparse is used only by cuopt_cli.cpp and
grpc_server_main.cpp, and cpp/CMakeLists.txt already links argparse::argparse
into both cuopt_cli and cuopt_grpc_server.
Verified by running ci/build_wheel_libcuopt.sh in the CI wheel image: the wheel
builds, auditwheel repair succeeds, pydistcheck reports no errors and twine
passes. The wheel contains the four component libraries and not the linker
script. python/libcuopt is only configured by the wheel build, which is why a
local cpp/build could not catch this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
The wheel installs 2020 C/C++ headers including cuopt_c.h, and the C API docs tell users to pip install libcuopt-cuXX then locate libcuopt.so and link -lcuopt. Excluding the linker script left that path with headers but nothing to link against. The exclusion was guarding against auditwheel choking on a non-ELF .so, which it does not do: auditwheel's elf_file_filter parses each candidate and skips anything raising ELFError, so the script is copied through untouched. Its --exclude option skips SONAMEs from being grafted, not files from being processed, so no exception was needed in the first place. Verified with ci/build_wheel_libcuopt.sh in the CI wheel image: auditwheel repair, pydistcheck and twine all pass, and the repaired wheel contains the linker script alongside the four component libraries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Splits
libcuopt.sointo four component SHARED libraries —cuopt_base,cuopt_routing,cuopt_mathematical_optimization,cuopt_grpc— and shipslibcuopt.soas a GNU ld script naming them.Packaging is unchanged: still one
libcuoptwheel and onelibcuoptconda package. This PR is the prerequisite for the per-solver package split tracked in #1635.Consumer impact
-lcuoptkeeps working, and CMake consumers are unaffected.libcuopt.sois no longer an ELF object. It is a linker script:This is the mechanism glibc uses for
libc.so. It is needed becauselddoes not resolve a consumer's undefined symbols through a dependency'sDT_NEEDED(--no-copy-dt-needed-entries, the default since binutils 2.22), so a thin umbrella library would have forced every C consumer to name the components explicitly. With the script,gcc app.c -lcuoptlinks exactly as before and the consumer ends up with a directDT_NEEDEDon each component.cuopt::cuoptis now an INTERFACE target, sofind_package(cuopt)and the Cython modules are unchanged.Two consequences worth noting:
dlopen("libcuopt.so")no longer works — load the components instead.load.pydoes this.libcuopt.soneeds a rebuild, since itsDT_NEEDEDnow names a file that is not ELF.find_package(cuopt)exposescuopt::cuoptpluscuopt::base,cuopt::routing,cuopt::mathematical_optimizationandcuopt::grpc.Components
Sizes are stripped. "Runtime" is the measured
DT_NEEDED; "CMake PUBLIC" is what propagates to consumers at link time. They differ because--as-neededdrops libraries a component does not call.cuopt_basecuopt_routingcuopt_mathematical_optimizationcuopt_grpcNeither engine has a
DT_NEEDEDon the other; onlycuopt_grpclinks both.cuopt_baseexists because routing and mathematical optimization both resolvedefault_logger()andseed_generator::seed_from it, and those are process-global singletons that must not be duplicated.Remote solve
cuopt_mathematical_optimizationno longer hard-links gRPC. It holds nullable callback slots thatlibcuopt_grpc.so's ELF constructor fills in viaregister_remote_solvers(), and loads that component on demand. Ag_remote_solvers_readyflag is published with release ordering after both callbacks are stored, so a reader that observes it sees both.Also in this PR
WRITE_FATBINappliedfatbin.ldto the umbrella, which held no device code after the split — the section grouping had silently become a no-op. It now applies to the components that carry the fatbins.prefix_detection.ignorelisted onlylibcuopt.so, which is no longer a binary; it now lists the components.--as-neededanchor symbols are gone entirely — the linker script makes them unnecessary.Testing
Full build plus
ctest(131/131) on a local RTX 8000. Verified separately: bare-lcuoptlinks without--allow-shlib-undefinedand the resulting binary runs;find_package(cuopt)resolves all five targets;load.pyloads all four components and the C API resolves.Next steps (#1635)
python/cuopt/cuopt/{routing,linear_programming/solver,grpc/linear_programming,distance_engine}/CMakeLists.txtall linkcuopt::cuopt. Until they link their actual component, every extension pulls the full graph.cuopt_grpc's LP and routing proto mappers so a routing-only gRPC client is possible.libcuopt-base/routing/mathematical-optimization/grpcplus alibcuoptmetapackage for backward compatibility.🤖 Generated with Claude Code