You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Neither comes from the client doing GPU work. Both come from the shape of types it is exposed to. The dependency is cheap today — librmm.so is 1.5 MB, carries no CUDA in its NEEDED, and its only CUDA-ish dependency is cuda-version, a 21.6 KB metapackage with zero files — so a GPU-free install still works. But the client is meant to be a leaf, and neither symbol is load-bearing.
Found while splitting libcuopt.so into components (#1622). Before that split the symbols resolved by accident: libcuopt.so was a real ELF that linked rmm, so anything loading the client had rmm already present. Once it became a linker script, a Cython extension loading libcuopt_client.so directly had nothing to resolve against, and every conda-python-tests job failed with:
That is fixed by linking rmm::rmm into the cuopt_client target. Note cuopt_client_objs already listed it, but an OBJECT library does not propagate its link interface to a target built from $<TARGET_OBJECTS:...>, so the shared library never got it. This issue is about removing the need, not the link.
Symbol 1: cuda_stream_view constructor — pure waste
rmm/cuda_stream_view.hpp declares two namespace-scope globals:
Being static at namespace scope, every translation unit including that header gets its own copy and its own static initializer. The reference shows up in .text.startup._GLOBAL__sub_I_<tu>.cpp, not in any code path — grpc_client_env.cpp, which only reads environment variables, carries one.
No client source ever names either global. This is emitted in 19 of the client's objects for something never used. Worth fixing on its own, independently of symbol 2.
Symbol 2: device_buffer destructor — forced by a variant
cython_types.hpp:
using gpu_buffer = std::unique_ptr<rmm::device_buffer>;
structlinear_programming_ret_t {
structgpu_solutions_t { gpu_buffer primal_solution_; /* 11 of these */ };
structcpu_solutions_t { /* std::vector<double> equivalents */ };
std::variant<gpu_solutions_t, cpu_solutions_t> solutions_;
};
cython_grpc_client.cpp:222 assigns one of these. A std::variant destructor must handle every alternative, so instantiating it pulls in unique_ptr<rmm::device_buffer>'s destructor — even though the remote client only ever produces the CPU alternative. It pays for a branch it cannot take.
What will not work
Worth recording, because two obvious fixes make things worse:
Moving gpu_solutions_t's destructor out-of-line into a CUDA translation unit. The client would then need that symbol from cuopt_mathopt, creating a client -> mathopt edge and destroying the leaf property that lets the client ship without CUDA at all. Same objection applies to an opaque-handle deleter defined in mathopt.
Pimpl on the settings types. This was the first theory, and it is wrong: the dominant cost is a static initializer that fires on include, so hiding members behind a pointer does not remove it. (A shared_ptr pimpl was also tried earlier in this work and reverted for breaking pdlp_warm_start_data_t's deep-copy semantics.)
What would work
Symbol 1: keep rmm/cuda_stream_view.hpp out of client translation units, or include it through a path that does not instantiate those globals. Cheap and self-contained.
cuopt_client no longer links rmm::rmm, and librmm.so is absent from its DT_NEEDED
the client still has no DT_NEEDED on any other cuOpt library
Note the check that missed this originally counted undefined cuopt:: symbols and NEEDED entries only. Any guard added for this should assert on undefined rmm::/raft::/cuda symbols too.
Description
libcuopt_client.solinkslibrmmfor two symbols it never meaningfully uses:Neither comes from the client doing GPU work. Both come from the shape of types it is exposed to. The dependency is cheap today —
librmm.sois 1.5 MB, carries no CUDA in itsNEEDED, and its only CUDA-ish dependency iscuda-version, a 21.6 KB metapackage with zero files — so a GPU-free install still works. But the client is meant to be a leaf, and neither symbol is load-bearing.Found while splitting
libcuopt.sointo components (#1622). Before that split the symbols resolved by accident:libcuopt.sowas a real ELF that linked rmm, so anything loading the client had rmm already present. Once it became a linker script, a Cython extension loadinglibcuopt_client.sodirectly had nothing to resolve against, and everyconda-python-testsjob failed with:That is fixed by linking
rmm::rmminto thecuopt_clienttarget. Notecuopt_client_objsalready listed it, but an OBJECT library does not propagate its link interface to a target built from$<TARGET_OBJECTS:...>, so the shared library never got it. This issue is about removing the need, not the link.Symbol 1:
cuda_stream_viewconstructor — pure wastermm/cuda_stream_view.hppdeclares two namespace-scope globals:Being
staticat namespace scope, every translation unit including that header gets its own copy and its own static initializer. The reference shows up in.text.startup._GLOBAL__sub_I_<tu>.cpp, not in any code path —grpc_client_env.cpp, which only reads environment variables, carries one.No client source ever names either global. This is emitted in 19 of the client's objects for something never used. Worth fixing on its own, independently of symbol 2.
Symbol 2:
device_bufferdestructor — forced by a variantcython_types.hpp:cython_grpc_client.cpp:222assigns one of these. Astd::variantdestructor must handle every alternative, so instantiating it pulls inunique_ptr<rmm::device_buffer>'s destructor — even though the remote client only ever produces the CPU alternative. It pays for a branch it cannot take.What will not work
Worth recording, because two obvious fixes make things worse:
gpu_solutions_t's destructor out-of-line into a CUDA translation unit. The client would then need that symbol fromcuopt_mathopt, creating aclient -> mathoptedge and destroying the leaf property that lets the client ship without CUDA at all. Same objection applies to an opaque-handle deleter defined in mathopt.shared_ptrpimpl was also tried earlier in this work and reverted for breakingpdlp_warm_start_data_t's deep-copy semantics.)What would work
rmm/cuda_stream_view.hppout of client translation units, or include it through a path that does not instantiate those globals. Cheap and self-contained.linear_programming_ret_twith its GPU alternative. This is the larger piece — it touches the Cython return types and therefore the Python binding layer, which is why it is not folded into build(cmake): split libcuopt into cuopt_client / cuopt_mathopt / cuopt_routing component libs #1622.Acceptance
nm -D --undefined-only libcuopt_client.so | c++filt | grep -E 'rmm::|raft::|cuda'returns nothingcuopt_clientno longer linksrmm::rmm, andlibrmm.sois absent from itsDT_NEEDEDDT_NEEDEDon any other cuOpt libraryNote the check that missed this originally counted undefined
cuopt::symbols andNEEDEDentries only. Any guard added for this should assert on undefinedrmm::/raft::/cudasymbols too.Related: #1622, #1804, #1872, #1635.