build(cmake): split libcuopt into cuopt_client / cuopt_mathopt / cuopt_routing component libs - #1622
build(cmake): split libcuopt into cuopt_client / cuopt_mathopt / cuopt_routing component libs#1622ramakrishnap-nv wants to merge 47 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✅ 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 |
Review feedback. The previous diagram drew cuopt_grpc_server and libcuopt as one box, which hid the fact that libcuopt.so is the shared foundation: both the cuopt Python extension modules and cuopt_grpc_server link it, and so does cuopt_cli. That convergence is the thing #1622 splits and #1804 carves from, so it should be visible. libcuopt.so is now its own box with both link edges arriving at it, and the gRPC arrow terminates at cuopt_grpc_server rather than trailing off toward the library, which misrepresented what a gRPC client actually talks to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
…-libs # Conflicts: # ci/build_wheel_cuopt.sh # cpp/CMakeLists.txt
cuopt_base had one consumer. libcuopt_mathopt.so referenced 9 symbols from it -- seed_generator::seed_, print_version_info, six work_unit_scheduler_t methods and printTimestamp -- while libcuopt_routing.so, libcuopt_grpc.so, libcuopt_client.so, cuopt_cli and cuopt_grpc_server referenced none, and routing carried no DT_NEEDED on it at all. Its rationale had eroded from three directions since it was written. The logger became header-only per component in #1778, linear algebra moved into cuopt_mathopt after review, and #1809 replaced the process-wide seed generator with per-worker RNGs. What remained was UTIL_SRC_FILES, four utility files, shipped as a 1 MB shared library for a single caller. The argument for a separate library is that duplicating stateful code into two components gives two copies of the global state. That no longer applies when only one component uses it. UTIL_SRC_FILES now builds into cuopt_mathopt. Since routing inherited rmm/raft/CCCL/cublas/cusparse transitively through cuopt_base's PUBLIC link interface, both components now declare those explicitly, and the architecture defines that version_info.cpp reports move to cuopt_mathopt. Also drops the component from the linker script, the umbrella INTERFACE lists, the fatbin targets, the export set, the conda recipe, the wheel exclusions and load.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
…-libs # Conflicts: # ci/build_wheel_cuopt.sh # conda/recipes/libcuopt/recipe.yaml # cpp/CMakeLists.txt # cpp/src/CMakeLists.txt # cpp/src/io/CMakeLists.txt # cpp/src/math_optimization/CMakeLists.txt # cpp/src/mip_heuristics/CMakeLists.txt # cpp/src/pdlp/CMakeLists.txt # python/libcuopt/CMakeLists.txt
The routing gRPC mappers were the last thing keeping a GPU-free client from covering VRP. They stayed out of cuopt_client because they reached into the routing engine, but the reach turned out to be shallow: 14 symbols, all trivial host-only accessors that happen to sit in CUDA translation units. grpc_routing_settings_mapper 8 routing::solver_settings_t getters/setters grpc_routing_solution_mapper 6 routing::assignment_t getters grpc_routing_problem_mapper 0 grpc_client_vrp 0 cython_grpc_client_vrp 0 The VRP client itself needed nothing from the engine. routing/solver_settings.cu is renamed to .cpp -- the whole file was already host code, plain accessors over scalar members, and its header pulls in no CUDA. The six assignment_t accessors move to assignment_accessors.cpp and are instantiated per member rather than with `template class`, which would also instantiate the device-facing members and pull CUDA back in. This is the same split #1801 through #1803 applied to the LP/MIP settings, and the approach #1804 anticipated for routing. libcuopt_client.so grows 2.3 MB to 2.4 MB and keeps its defining properties: no CUDA, rmm or raft in NEEDED, no undefined cuopt:: symbols, and no DT_NEEDED on any other cuOpt library. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
The routing gRPC arm had almost no C++ coverage. GRPC_INTEGRATION_TEST held no routing cases at all, and of the two mappers that read the accessors this branch moves, neither had a test -- only the problem mapper did, and it is the one that touches no accessors. Adds two tests that catch different things. DefaultServerTests.SolveVRP follows the same shape as the LP and MIP cases in that fixture: submit a problem, poll to completion, fetch the solution, check it. The problem is built in code rather than loaded from a fixture file so it does not depend on the routing datasets, and the assertions avoid pinning a particular route ordering -- only that the solve succeeded and left no order unserved. GRPC_ROUTING_SETTINGS_MAPPER_TEST round-trips routing::solver_settings_t through the proto. Six of its eight accessors previously had no test at all. It covers the presence semantics an end-to-end solve cannot see: an unset time_limit must not be serialized, since the solver derives its default from absence, while an explicit zero must survive. Both were checked by mutation. Removing the time_limit presence guard leaves SolveVRP passing -- it sets an explicit limit, so it never exercises that path -- while the mapper test fails. The end-to-end test is the right primary but is not a superset of the mapper test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
…t' into feat/split-routing-lp-libs # Conflicts: # cpp/CMakeLists.txt # cpp/src/routing/CMakeLists.txt
…-libs # Conflicts: # cpp/src/routing/CMakeLists.txt
The comment said the direct solve_lp/mip_remote calls make libcuopt_grpc.so a real DT_NEEDED of cuopt_cli. That stopped being true once solve_remote.cpp moved into cuopt_client: the binary links cuopt_mathopt and cuopt_client, and carries no DT_NEEDED on libcuopt_grpc.so at all. The reasoning still holds, for a different reason. Calling the remote entry points directly resolves them at link time from cuopt_client rather than relying on ensure_remote_solvers_loaded() to dlopen libcuopt_grpc.so, which is the path solve_lp/solve_mip take through the registry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
Two changes to the component boundaries, both found by measuring the built libraries rather than reading the CMake. cuopt_grpc no longer earns a component. After #1884 moved the routing arm into cuopt_client, and solve_remote.cpp moved there too, the library was 18 KB holding one source file: an ELF constructor calling register_remote_solvers. It exported no cuOpt symbols at all -- its 11 dynamic symbols were incidental raft and libstdc++ template instantiations leaked from headers. As a shipped package it would have been a wheel and a conda output containing a constructor and nothing callable. grpc_registration.cpp now builds into cuopt_mathopt, which owns the registry it fills. It still cannot build into cuopt_client: that would leave the client library with an undefined register_remote_solvers and cost it the standalone property. With the constructor in the same library as the registry, the dlopen in ensure_remote_solvers_loaded() became dead code and is gone, along with the dlfcn include; the function stays as a documented no-op because two call sites read better for it. Separately, cuopt_routing referenced 8 symbols from cuopt_client with no DT_NEEDED on it -- the assignment_t and solver_settings_t accessors that #1884 moved. It resolved only because something else had pulled cuopt_client in. A routing-only install, which is the point of #1635, would have failed at load. cuopt_routing now links cuopt_client explicitly. cuopt_grpc_server had been reaching cuopt_routing transitively through cuopt_grpc, so it now links both engines directly. It is the one artifact that solves both VRP and LP/MIP, so the dependency belongs in the open. Every cross-library reference now has a matching DT_NEEDED, and cuopt_client remains a leaf: no CUDA, no rmm, no raft, no undefined cuopt:: symbols. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test |
Addresses review: the atomic function pointers, the ELF-constructor
registration and the dlopen all existed to break a dependency cycle that no
longer exists.
When the registry was written, solve_lp_remote and solve_mip_remote lived in
the gRPC component, which depended on the math-opt library, so math-opt could
not call them. They now live in cuopt_client, which is a leaf, and cuopt_mathopt
already links it. The dependency is one-way, so the indirection buys nothing.
Both call sites collapse from a load-acquire on an atomic, a null check and an
indirect call to a direct call. Deleted: remote_solve_registry.{hpp,cpp},
grpc_registration.cpp, both std::atomic function pointers, the ready flag with
its release/acquire pairing, register_remote_solvers and
ensure_remote_solvers_loaded.
The one thing the registry did handle was a build without gRPC, where the
remote entry points do not exist and the null pointer degraded to a runtime
error. That is now a compile-time guard on CUOPT_ENABLE_GRPC, which is already
defined globally, and the same error text is kept for the no-gRPC case.
This also removes solve_lp_remote_fn_t and solve_mip_remote_fn_t, which a
reviewer had asked to unify into one type -- there is now no type to unify.
Remote execution verified end to end: CpuOnlyWithServerTest.lp_solve and
.mip_solve pass against a forked server, exercising the new direct dispatch
through the C API. log_callback_remote still fails, unchanged, as #1878.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test |
…-libs # Conflicts: # cpp/src/io/mps_parser_internal.hpp
|
/ok to test |
Every conda-python-tests job failed to import the Cython extensions:
ImportError: libcuopt_client.so: undefined symbol:
_ZN3rmm10_RMM_26_1016cuda_stream_viewC1EP11CUstream_st
cuopt_client_objs already lists rmm::rmm, but an OBJECT library does not
propagate its link interface to a target built from $<TARGET_OBJECTS:...>, so
libcuopt_client.so carried two undefined rmm symbols and no DT_NEEDED to
resolve them. That went unnoticed because libcuopt.so was a real ELF linking
rmm, so anything loading the client had rmm present already. Once libcuopt.so
became a linker script, a Cython extension loading the client directly had
nothing to resolve against.
The two symbols are cuda_stream_view's constructor, emitted by the static
initializers rmm's header creates in every including translation unit for
globals the client never names, and device_buffer's destructor, instantiated
through the std::variant in linear_programming_ret_t whose GPU alternative the
remote client never constructs. Neither is load-bearing, and #1890 tracks
removing the need; this restores correctness now.
librmm does not reintroduce CUDA: 1.5 MB, no CUDA in its own NEEDED, and its
only CUDA-ish dependency is cuda-version, a 21.6 KB metapackage with no files.
A GPU-free client install still works.
The check that missed this counted undefined cuopt:: symbols and NEEDED
entries, never undefined rmm:: symbols.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test |
CpuOnlyWithServerTest.log_callback_remote failed on every conda-cpp-tests config: the remote solve succeeded but no streamed log lines reached the callback. The components are built with hidden visibility, so each got its own copy of the inline thread_local holding the registration. cuOptSetLogCallback lives in cuopt_c.cpp, which builds into cuopt_mathopt, while the remote-solve log forwarding reads the registration from solve_remote.cpp in cuopt_client. Two copies, so the remote path saw no callback and silently delivered nothing. libcuopt_client t_log_callback exported (client uses default visibility) libcuopt_mathopt own hidden copy libcuopt_routing own hidden copy Marking it CUOPT_EXPORT gives every component default visibility for that symbol, so the dynamic linker collapses the copies into one. Registration stays scoped per thread, which is the property that keeps concurrent solves from capturing each other's callback; sharing across libraries does not weaken it. The header comment said the per-library copy was intended. That was true of the per-thread scoping, not of the per-library split, which was an accident of hidden visibility -- so the comment is rewritten to say why the export is load-bearing. This is a split-only bug: it passes on a monolithic build, which is why it survived until libcuopt.so became a linker script. Verified: log_callback_remote now passes, and the full C++ suite is clean when run serially. Under -j2 five binaries fail with 22 RMM out-of-memory hits from GPU contention on one card; all four affected binaries pass individually. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test |
SolveVRP timed out with the job still QUEUED on one conda-cpp-tests config. The cause is placement, not the test body. DefaultServerTests shares one server across the suite, and gtest runs in declaration order, so the test sat immediately after CancelRunningJob: 22 CancelRunningJob cancels a running job, kills a worker 23 SolveVRP stuck in QUEUED, 30s timeout 24 DeleteQueuedJobPreventsRun failed 25 DeleteRunningJobCancelsWorker failed Everything from the worker kill onward failed. That is the mechanism already recorded in #1716: a job submitted after a cancel test kills the worker can sit in QUEUED indefinitely. The two Delete tests are the known #1814 pair and fail there regardless; SolveVRP became a new casualty by being placed among them. It is a test of the routing mappers, not of recovering from a killed worker, so it now sits with the other solve tests after SolveMIPBlocking, with a comment recording why it belongs there. The underlying server issue is untouched and still tracked in #1716 and #1814. Whole suite now passes locally, 58/58. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
/ok to test |
Splits
libcuopt.sointo three component SHARED libraries —cuopt_client,cuopt_mathopt,cuopt_routing— 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.cuopt::cuoptis now an INTERFACE target, sofind_package(cuopt)and the Cython modules are unchanged.cuopt_clientis not named in the script because it arrives through the other components'DT_NEEDED.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::routingandcuopt::mathopt.Components
Sizes are stripped. Every cross-library reference has a matching
DT_NEEDED.cuopt_clientsolve_remotecuopt_routingcuopt_client(8 symbols)cuopt_mathoptcuopt_client(206 symbols)Neither engine has a
DT_NEEDEDon the other, which is what makes the per-solver package split possible.libcuopt_client.sohas no CUDA, rmm or raft inNEEDEDand no undefinedcuopt::symbols, so it resolves standalone — that is what makes a GPU-free client install possible (#1872).Binaries:
cuopt_clineeds client and mathopt only (0 routing symbols);cuopt_grpc_serverneeds all three. #1635 records where each lands.Components that were tried and removed
Both were dropped after measuring the built libraries rather than reading the CMake.
cuopt_baseheld shared utilities, on the assumption that both engines resolved process-global singletons from it.libcuopt_mathopt.soreferenced 9 symbols from it; routing, grpc, client,cuopt_cliandcuopt_grpc_serverreferenced none, and routing carried noDT_NEEDEDon it at all. Its rationale had also eroded: the logger became header-only per component in #1778, linear algebra moved intocuopt_mathoptafter review, and #1809 replaced the process-wide seed generator with per-worker RNGs. Four utility files shipped as a 1 MB library for one caller, so they now build intocuopt_mathopt.cuopt_grpcheld the gRPC bridge. Once #1884 moved the routing arm intocuopt_client, andsolve_remote.cppmoved there too, what remained was 18 KB holding one source file: an ELF constructor callingregister_remote_solvers. It exported no cuOpt symbols — its 11 dynamic symbols were incidental raft and libstdc++ template instantiations leaked from headers. As a shipped package it would have been a wheel and a conda output containing a constructor and nothing callable.Remote-solve dispatch, simplified
Review raised that the atomic function pointers, the constructor registration and the
dlopenshould not be needed. They existed to break a cycle:solve_lp_remoteandsolve_mip_remotelived in the gRPC component, which depended on math-opt, so math-opt could not call them.That cycle is gone. Both entry points now live in
cuopt_client, which is a leaf, andcuopt_mathoptlinks it — the dependency is one-way. So both call sites became direct calls, andremote_solve_registry.{hpp,cpp},grpc_registration.cpp, bothstd::atomicfunction pointers, the ready flag,register_remote_solversandensure_remote_solvers_loadedare all deleted. Net -163 lines.The one case the registry handled was a build without gRPC, where the remote entry points do not exist and the null pointer degraded to a runtime error. That is now a compile-time guard on
CUOPT_ENABLE_GRPC, with the same error text preserved.This also removes
solve_lp_remote_fn_tandsolve_mip_remote_fn_t, which a reviewer asked to unify — there is now no type to unify.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.cuopt_routingreferenced 8 symbols fromcuopt_clientwith noDT_NEEDED, resolving only because something else had pulled the client in. A routing-only install would have failed at load; the link is now explicit.Testing
Full build clean, no undefined references. Remote execution verified end to end against a forked server:
CpuOnlyWithServerTest.lp_solveand.mip_solvepass, exercising the new direct dispatch through the C API.Verified separately: bare
-lcuoptlinks without--allow-shlib-undefinedand the resulting binary runs;find_package(cuopt)resolves the targets;load.pyloads all components and the C API resolves.cuopt_clientlinks librmmlibcuopt_client.solinkslibrmmfor two symbols it never meaningfully uses:cuda_stream_view's constructor, emitted by static initializers rmm's header creates in every including translation unit for globals the client never names, anddevice_buffer's destructor, instantiated through thestd::variantinlinear_programming_ret_twhose GPU alternative the remote client never constructs.cuopt_client_objsalready listedrmm::rmm, but an OBJECT library does not propagate its link interface to a target built from$<TARGET_OBJECTS:...>, so the shared library carried two undefined rmm symbols and noDT_NEEDED. That resolved by accident whilelibcuopt.sowas a real ELF linking rmm; once it became a linker script, a Cython extension loading the client directly failed to import.This does not reintroduce CUDA:
librmm.sois 1.5 MB with no CUDA in its ownNEEDED, and its only CUDA-ish dependency iscuda-version, a 21.6 KB metapackage with no files. A GPU-free client install still works. Removing the need entirely is tracked in #1890.Remote log callback
CpuOnlyWithServerTest.log_callback_remotefailed on everyconda-cpp-testsconfig: the remote solve succeeded but no streamed log lines reached the callback. Fixed here, and #1878 closed.The components are built with hidden visibility, so each got its own copy of the inline
thread_localholding the registration.cuOptSetLogCallbacklives incuopt_c.cpp, which builds intocuopt_mathopt, while the remote-solve log forwarding reads it fromsolve_remote.cppincuopt_client— two copies, so the remote path saw no callback. Marking itCUOPT_EXPORTlets the dynamic linker collapse them into one. Registration remains scoped per thread, which is what keeps concurrent solves from capturing each other's callback.A split-only bug: it passes on a monolithic build, which is why it survived until
libcuopt.sobecame a linker script.Next steps (#1635)
cuopt::cuopt. Until they link their actual component, every extension pulls the full graph.libcuopt-client/-mathopt/-routingplus alibcuoptmetapackage, andcuopt-grpc-serverfor the server binary.