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 20 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.
|
📝 WalkthroughWalkthroughChangescuOpt componentized build and remote execution
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 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✅ All 31 test job(s) passed. |
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 |
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test f2b22e7 |
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 90ee992 |
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<int,double> 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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test a83e0f4 |
…cution
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 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 0f3b7e1 |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test b872bfb |
…check 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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok totest 7b32d1b |
|
/ok to test 7b32d1b |
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 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test 80c1c34 |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
cpp/src/mip_heuristics/solve.cu (1)
918-922: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated lazy-load-and-validate sequence.
This is a line-for-line copy of the LP path in
cpp/src/pdlp/solve.cu(Lines 2696-2700), including the discardeddlopenresult and the hardcoded unversioned soname. A single helper in the registry (e.g.ensure_remote_solvers_loaded()) would keep the loader name, thedlerror()reporting, and any future synchronization in one place instead of two components.🤖 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/mip_heuristics/solve.cu` around lines 918 - 922, Replace the inline lazy-load and null-check sequence in the MIP remote execution path with a shared registry helper such as ensure_remote_solvers_loaded(). Move loader-name handling, dlopen result validation, dlerror reporting, and synchronization into that helper, then reuse it from both this path and the corresponding LP path while preserving the existing runtime-error behavior when the remote solver remains unavailable.cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp (1)
25-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd Doxygen comments for the new registry surface.
This lives under
cpp/include/cuopt/, where new declarations are expected to carry Doxygen-style documentation. Line comments cover the intent, but the callback contract worth stating explicitly is: who owns the returned solution, whether the callbacks may throw across the component boundary, and that only<int, double>is instantiated.As per path instructions, "New public functions/classes need Doxygen-style documentation" and "Suggest documenting thread-safety, GPU requirements, numerical behavior".
🤖 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/include/cuopt/mathematical_optimization/remote_solve_registry.hpp` around lines 25 - 30, Add Doxygen-style documentation immediately before solve_lp_remote_fn_t and solve_mip_remote_fn_t, describing that callbacks return ownership via unique_ptr, must not throw across the component boundary, and are instantiated only for <int, double>. Retain the existing intent comment while documenting the callback contract for both typedefs.Source: Path instructions
🤖 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`:
- Around line 865-890: Extend _UMBRELLA_SRC with used symbol-reference anchors
for exported symbols from cuopt_base and cuopt_routing so their libraries remain
in DT_NEEDED under --as-needed; add the routing anchor only under the
appropriate routing build guard, while preserving the existing LP anchor and
SKIP_GRPC_BUILD gRPC guard.
In `@cpp/cuopt_cli.cpp`:
- Around line 203-205: Replace the unchecked static_cast in the remote execution
path with a validated dynamic_cast to cpu_optimization_problem_t. Before passing
cpu_prob to solve_*_remote, enforce that the cast succeeded and fail cleanly
using the existing cuopt_expects-style validation, preserving the GPU backend
path without reinterpreting its problem type.
In `@cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp`:
- Around line 32-38: Synchronize remote-solver registry access by declaring
g_solve_lp_remote_fn and g_solve_mip_remote_fn as atomic function-pointer slots
in cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp, and
store registrations with release ordering in
cpp/src/pdlp/remote_solve_registry.cpp. In cpp/src/pdlp/solve.cu lines
2696-2700, load g_solve_lp_remote_fn once into a local before the null check and
reuse it for dispatch; apply the same pattern to g_solve_mip_remote_fn in
cpp/src/mip_heuristics/solve.cu lines 918-922.
- Around line 19-23: Change the forward declarations of pdlp_solver_settings_t
and mip_solver_settings_t to use class instead of struct, matching their class
definitions and keeping both template parameter lists unchanged.
In `@python/libcuopt/CMakeLists.txt`:
- Around line 98-102: Update the RPATH property list in the CMake configuration
to include the cuopt_routing target with the same ${rpaths} append used by
cuopt_base and cuopt_lp, ensuring its installed shared library receives the
bundled CUDA library runtime paths.
---
Nitpick comments:
In `@cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp`:
- Around line 25-30: Add Doxygen-style documentation immediately before
solve_lp_remote_fn_t and solve_mip_remote_fn_t, describing that callbacks return
ownership via unique_ptr, must not throw across the component boundary, and are
instantiated only for <int, double>. Retain the existing intent comment while
documenting the callback contract for both typedefs.
In `@cpp/src/mip_heuristics/solve.cu`:
- Around line 918-922: Replace the inline lazy-load and null-check sequence in
the MIP remote execution path with a shared registry helper such as
ensure_remote_solvers_loaded(). Move loader-name handling, dlopen result
validation, dlerror reporting, and synchronization into that helper, then reuse
it from both this path and the corresponding LP path while preserving the
existing runtime-error behavior when the remote solver remains unavailable.
🪄 Autofix (Beta)
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: ea594003-fad8-4a06-acda-c89669a7e541
📒 Files selected for processing (16)
ci/build_wheel_cuopt.shci/test_skills_assets.shconda/recipes/libcuopt/recipe.yamlcpp/CMakeLists.txtcpp/cuopt_cli.cppcpp/include/cuopt/mathematical_optimization/remote_solve_registry.hppcpp/src/grpc/client/grpc_registration.cppcpp/src/mip_heuristics/solve.cucpp/src/pdlp/CMakeLists.txtcpp/src/pdlp/remote_solve_registry.cppcpp/src/pdlp/solve.cucpp/src/routing/CMakeLists.txtdocs/cuopt/source/conf.pydocs/cuopt/source/cuopt-c/convex/examples/Makefiledocs/cuopt/source/cuopt-c/mip/examples/Makefilepython/libcuopt/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (1)
- cpp/src/routing/CMakeLists.txt
| # 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 <cuopt/mathematical_optimization/solve_remote.hpp> | ||
| #include <cuopt/mathematical_optimization/remote_solve_registry.hpp> | ||
| namespace cuopt::mathematical_optimization { | ||
| __attribute__((used)) solve_lp_remote_fn_t const _cuopt_grpc_anchor = | ||
| &solve_lp_remote<int, double>; | ||
| } | ||
| ]=]) | ||
| endif() | ||
|
|
||
| file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cuopt_umbrella.cpp" "${_UMBRELLA_SRC}") |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'SKIP_GRPC_BUILD|CUOPT_ENABLE_GRPC' cpp/CMakeLists.txt
rg -n '_cuopt_base_anchor|_cuopt_routing_anchor|_UMBRELLA_SRC' cpp/CMakeLists.txtRepository: NVIDIA/cuopt
Length of output: 1129
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- CMake target/link context ---'
sed -n '520,575p;780,850p;850,935p;1050,1100p' cpp/CMakeLists.txt
printf '%s\n' '--- component target and exported-symbol references ---'
rg -n 'add_library|target_sources|target_link_libraries|install|cuopt_base|cuopt_routing|cuOptDestroyProblem|solve_lp_remote' cpp/CMakeLists.txt cpp/src cpp/include -g '*.{cmake,txt,cpp,cu,c,h,hpp}' | head -300
printf '%s\n' '--- candidate component files ---'
git ls-files | rg '(^|/)(base|routing|.*base.*|.*routing.*)|solve_remote|remote_solve_registry'Repository: NVIDIA/cuopt
Length of output: 32659
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- option resolution and gRPC target conditions ---'
sed -n '35,65p;350,410p;610,715p;930,1010p;1150,1230p' cpp/CMakeLists.txt
printf '%s\n' '--- linker/as-needed configuration ---'
rg -n --hidden -i 'as-needed|no-as-needed|copy-dt-needed|link_options|CMAKE_SHARED_LINKER_FLAGS|CMAKE_EXE_LINKER_FLAGS' . -g '!**/build/**' -g '!**/node_modules/**'
printf '%s\n' '--- public base/routing declarations and definitions ---'
rg -n '^(export )?(namespace|class|struct)|(^|[^A-Za-z0-9_])(solve|cuOpt[A-Z]|cuopt_[A-Za-z0-9_]+)\s*\\(' cpp/include/cuopt/routing cpp/include/cuopt/mathematical_optimization cpp/src/base cpp/src/routing cpp/src/pdlp -g '*.{h,hpp,cpp,cu}' | head -300Repository: NVIDIA/cuopt
Length of output: 16472
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- component source lists ---'
sed -n '410,525p' cpp/CMakeLists.txt
printf '%s\n' '--- installed/public routing API declarations ---'
rg -n 'namespace cuopt|class |struct |cuOpt|solve\\s*\\(' cpp/include/cuopt/routing cpp/include/cuopt/mathematical_optimization -g '*.{h,hpp}' | head -240
printf '%s\n' '--- routing/base definitions and exported C API definitions ---'
rg -n 'namespace cuopt|cuOpt[A-Z][A-Za-z0-9_]*\\s*\\(|^[A-Za-z_][A-Za-z0-9_:<>*& ]*::[A-Za-z_][A-Za-z0-9_]*\\s*\\(' cpp/src/routing cpp/src/base cpp/src/pdlp -g '*.{cpp,cu}' | head -240
printf '%s\n' '--- all CUOPT_ENABLE_GRPC references ---'
rg -n --hidden 'CUOPT_ENABLE_GRPC|SKIP_GRPC_BUILD' . -g '!**/build/**' -g '!**/node_modules/**'Repository: NVIDIA/cuopt
Length of output: 9218
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import re
text = Path("cpp/CMakeLists.txt").read_text()
umbrella = re.search(
r"set\(_UMBRELLA_SRC \[=\[(.*?)\]=\]\)",
text,
re.S,
).group(1)
anchors = {
name: name in umbrella
for name in ("_cuopt_lp_anchor", "_cuopt_grpc_anchor",
"_cuopt_base_anchor", "_cuopt_routing_anchor")
}
cuopt_links = re.search(
r"if\(NOT SKIP_ROUTING_BUILD\)\s*"
r"target_link_libraries\(cuopt PUBLIC (.*?)\)\s*"
r"else\(\)\s*"
r"target_link_libraries\(cuopt PUBLIC (.*?)\)",
text,
re.S,
)
grpc_link = re.search(
r"if\(NOT SKIP_GRPC_BUILD\)\s*"
r"target_link_libraries\(cuopt PUBLIC cuopt_grpc\)",
text,
)
print("umbrella anchors:", anchors)
print("routing-enabled umbrella dependencies:",
" ".join(cuopt_links.group(1).split()) if cuopt_links else "<not found>")
print("routing-disabled umbrella dependencies:",
" ".join(cuopt_links.group(2).split()) if cuopt_links else "<not found>")
print("gRPC umbrella dependency guarded by SKIP_GRPC_BUILD:",
bool(grpc_link))
print("gRPC anchor guarded by SKIP_GRPC_BUILD:",
"if(NOT SKIP_GRPC_BUILD)" in text[text.index("if(NOT SKIP_GRPC_BUILD)", text.index("set(_UMBRELLA_SRC")):])
assert anchors["_cuopt_lp_anchor"]
assert not anchors["_cuopt_base_anchor"]
assert not anchors["_cuopt_routing_anchor"]
assert cuopt_links and "cuopt_base" in cuopt_links.group(1)
assert cuopt_links and "cuopt_routing" in cuopt_links.group(1)
assert grpc_link
PYRepository: NVIDIA/cuopt
Length of output: 521
Add real DT_NEEDED anchors for cuopt_base and cuopt_routing.
cuopt links these components publicly, but the umbrella TU references only LP and gRPC. With --as-needed, base and routing can be omitted, breaking consumers that use -lcuopt. Add exported-symbol anchors, conditionally for routing. The gRPC guards are already aligned with SKIP_GRPC_BUILD.
🤖 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 865 - 890, Extend _UMBRELLA_SRC with used
symbol-reference anchors for exported symbols from cuopt_base and cuopt_routing
so their libraries remain in DT_NEEDED under --as-needed; add the routing anchor
only under the appropriate routing build guard, while preserving the existing LP
anchor and SKIP_GRPC_BUILD gRPC guard.
Source: Path instructions
| auto* cpu_prob = | ||
| static_cast<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>*>( | ||
| problem_interface.get()); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Unchecked static_cast to cpu_optimization_problem_t — wrong downcast when remote execution is enabled with the GPU backend.
is_remote_execution_enabled() and get_memory_backend_type() are independent conditions. If the backend resolves to GPU (Lines 136-140 construct an optimization_problem_t) while CUOPT_REMOTE_HOST/CUOPT_REMOTE_PORT are set, this static_cast reinterprets a GPU problem as a CPU problem and *cpu_prob is then handed to solve_*_remote — undefined behavior, most likely a corrupt read rather than a clean error.
The library-side dispatchers guard this exact conversion (cpp/src/pdlp/solve.cu Line 2692 and cpp/src/mip_heuristics/solve.cu Line 914 use dynamic_cast plus cuopt_expects); the CLI should not be the one place that skips the check.
🛡️ Proposed fix: validate the downcast
auto* cpu_prob =
- static_cast<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>*>(
+ dynamic_cast<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>*>(
problem_interface.get());
+ if (cpu_prob == nullptr) {
+ CUOPT_LOG_ERROR(
+ "Remote execution requires the CPU memory backend, but a GPU-backed problem was "
+ "created. Unset CUOPT_REMOTE_HOST/CUOPT_REMOTE_PORT or select the CPU backend.");
+ return -1;
+ }
if (is_mip) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| auto* cpu_prob = | |
| static_cast<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>*>( | |
| problem_interface.get()); | |
| auto* cpu_prob = | |
| dynamic_cast<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>*>( | |
| problem_interface.get()); | |
| if (cpu_prob == nullptr) { | |
| CUOPT_LOG_ERROR( | |
| "Remote execution requires the CPU memory backend, but a GPU-backed problem was " | |
| "created. Unset CUOPT_REMOTE_HOST/CUOPT_REMOTE_PORT or select the CPU backend."); | |
| return -1; | |
| } | |
| if (is_mip) { |
🤖 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/cuopt_cli.cpp` around lines 203 - 205, Replace the unchecked static_cast
in the remote execution path with a validated dynamic_cast to
cpu_optimization_problem_t. Before passing cpu_prob to solve_*_remote, enforce
that the cast succeeded and fail cleanly using the existing cuopt_expects-style
validation, preserving the GPU backend path without reinterpreting its problem
type.
| template <typename i_t, typename f_t> | ||
| struct pdlp_solver_settings_t; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| struct mip_solver_settings_t; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP --type=cpp --type=cuda -g '!**/tests/**' '\b(class|struct)\s+(pdlp_solver_settings_t|mip_solver_settings_t|cpu_optimization_problem_t)\b' | head -50Repository: NVIDIA/cuopt
Length of output: 150
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' 'Definitions and declarations across tracked source files:'
rg -n --glob '!**/tests/**' '\b(class|struct)\s+(pdlp_solver_settings_t|mip_solver_settings_t|cpu_optimization_problem_t)\b' .
printf '%s\n' 'References to the settings types:'
rg -n --glob '!**/tests/**' '\b(pdlp_solver_settings_t|mip_solver_settings_t)\b' cpp python 2>/dev/null | head -100Repository: NVIDIA/cuopt
Length of output: 14060
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' 'remote_solve_registry.hpp:'
sed -n '1,80p' cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp
printf '%s\n' 'Definitions:'
sed -n '100,130p' cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp
sed -n '50,75p' cpp/include/cuopt/mathematical_optimization/mip/solver_settings.hpp
printf '%s\n' 'Include/use context:'
rg -n -C 3 'remote_solve_registry|pdlp_solver_settings_t|mip_solver_settings_t' cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp cpp/include/cuopt/mathematical_optimization/solve_remote.hpp cpp/src -g '*.cpp' -g '*.cu' -g '*.hpp' -g '*.cuh' | head -180Repository: NVIDIA/cuopt
Length of output: 18505
🏁 Script executed:
#!/bin/bash
set -eu
if command -v g++ >/dev/null 2>&1; then
printf '%s\n' 'Compiler behavior for mismatched class-keys:'
printf '%s\n' 'template<class T> class X; template<class T> struct X {}; int main() {}' |
g++ -std=c++17 -x c++ -fsyntax-only -Wall -Wextra -
else
printf '%s\n' 'g++ unavailable; source evidence is sufficient to identify the class-key mismatch.'
fiRepository: NVIDIA/cuopt
Length of output: 233
Use class for both forward declarations.
These templates are defined as classes; the mismatched class-keys are ill-formed when both declarations are visible.
🤖 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/include/cuopt/mathematical_optimization/remote_solve_registry.hpp` around
lines 19 - 23, Change the forward declarations of pdlp_solver_settings_t and
mip_solver_settings_t to use class instead of struct, matching their class
definitions and keeping both template parameter lists unchanged.
| // 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); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Unsynchronized access to the remote-solver registry across three sites. The shared root cause is that the registry slots are declared as plain function pointers: they are published from libcuopt_grpc.so's ELF constructor during a lazy dlopen while other threads read them with no synchronization, so concurrent solve_lp/solve_mip calls race on a non-atomic object.
cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp#L32-L38: declare both slots asstd::atomic<...>(and store with release ordering incpp/src/pdlp/solve.cu's siblingremote_solve_registry.cpp); this is the only edit that fixes the race.cpp/src/pdlp/solve.cu#L2696-L2700: once the slot is atomic, load it once into a local before the null test and reuse that local for the dispatch, rather than reading the global three times.cpp/src/mip_heuristics/solve.cu#L918-L922: apply the same single-load-into-a-local pattern forg_solve_mip_remote_fn.
📍 Affects 3 files
cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp#L32-L38(this comment)cpp/src/pdlp/solve.cu#L2696-L2700cpp/src/mip_heuristics/solve.cu#L918-L922
🤖 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/include/cuopt/mathematical_optimization/remote_solve_registry.hpp` around
lines 32 - 38, Synchronize remote-solver registry access by declaring
g_solve_lp_remote_fn and g_solve_mip_remote_fn as atomic function-pointer slots
in cpp/include/cuopt/mathematical_optimization/remote_solve_registry.hpp, and
store registrations with release ordering in
cpp/src/pdlp/remote_solve_registry.cpp. In cpp/src/pdlp/solve.cu lines
2696-2700, load g_solve_lp_remote_fn once into a local before the null check and
reuse it for dispatch; apply the same pattern to g_solve_mip_remote_fn in
cpp/src/mip_heuristics/solve.cu lines 918-922.
| 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) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
cuopt_routing is missing from the RPATH list.
cuopt_base and cuopt_lp were given the same ${rpaths} append as cuopt, cuopt_cli, and cuopt_grpc_server, but cuopt_routing (introduced as its own component library in this PR) isn't included. If it links any of the bundled CUDA libs the RPATH set targets (cublas/cusparse/cusolver/nccl/nvjitlink), the wheel-installed libcuopt_routing.so could fail to locate them at runtime.
🔧 Suggested fix
set_property(TARGET cuopt PROPERTY INSTALL_RPATH ${rpaths} APPEND)
set_property(TARGET cuopt_base PROPERTY INSTALL_RPATH ${rpaths} APPEND)
+set_property(TARGET cuopt_routing 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)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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) | |
| set_property(TARGET cuopt PROPERTY INSTALL_RPATH ${rpaths} APPEND) | |
| set_property(TARGET cuopt_base PROPERTY INSTALL_RPATH ${rpaths} APPEND) | |
| set_property(TARGET cuopt_routing 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) |
🤖 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 `@python/libcuopt/CMakeLists.txt` around lines 98 - 102, Update the RPATH
property list in the CMake configuration to include the cuopt_routing target
with the same ${rpaths} append used by cuopt_base and cuopt_lp, ensuring its
installed shared library receives the bundled CUDA library runtime paths.
Splits
libcuopt.sointo four component SHARED libraries (cuopt_base,cuopt_routing,cuopt_lp,cuopt_grpc) plus a thin umbrellalibcuopt.sothat links them all — existing-lcuoptconsumers are unaffected.🤖 Generated with Claude Code