Skip to content

build: add the CUDA-free cuopt_client library - #1804

Draft
ramakrishnap-nv wants to merge 1 commit into
split/3-settings-host-constructiblefrom
split/4-cuopt-client-library
Draft

build: add the CUDA-free cuopt_client library#1804
ramakrishnap-nv wants to merge 1 commit into
split/3-settings-host-constructiblefrom
split/4-cuopt-client-library

Conversation

@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator

4 of 4 toward a CUDA-free client library. Stacked on #1803. This is the payoff PR — almost entirely CMake, since #1801#1803 did the code work.

Why

Calling a remote cuopt_grpc_server currently requires the full GPU stack. pip install cuopt pulls cudf, cupy-cuda13x[ctk], rmm, pylibraft, numba-cuda, scipy, pandas and libcuopt onto a machine that only serializes protobuf over a socket.

The motivating consumer is an MCP server: it imports only Client, TlsConfig, DataModel, Read, SolverSettingsno Solve — yet installs the entire CUDA stack today.

What

cuopt_client holds the host-side problem representation (parsers, data_model_view, mps_data_model, writers), the gRPC wire protocol (generated protos + mappers) and the gRPC clients for both LP/MIP and routing. libcuopt and cuopt_grpc_server both link it — one mapper implementation, not a client-side fork.

Both gRPC arms qualify: the routing mappers from #1597 reference no raft/rmm/thrust either. solve_remote.cpp stays in libcuopt — it's the local-vs-remote dispatcher and calls into the GPU solver.

Two build details worth reviewing

Object + shared library pair, mirroring cuopt_objs/cuopt. cuopt_static embeds the objects directly rather than linking the shared library, because internal test binaries reach parser internals like mps_phase_registry_t. Linking the shared library instead produces link failures in MPS_FAST_PARSER_TEST, GRPC_CLIENT_TEST and PDLP_MG_TEST.

Default visibility, deliberately unlike cuopt_objs. cuopt_objs can hide everything not marked CUOPT_EXPORT because libcuopt has a curated public API. cuopt_client is different — it was carved out of the internals, so libcuopt depends on ~214 of its symbols (essentially the whole cpu_optimization_problem_t / data_model_view_t / mps_data_model_t / grpc_client_t surface). With hidden visibility, libcuopt.so fails to load:

undefined symbol: cuopt::mathematical_optimization::grpc_client_t::solve_mip<int,double>

Curating that would mean annotating essentially every host-side method. Open to opinions, but default visibility looks like the right trade.

Also: logger.cpp moves into the client sources — both the parsers and the gRPC code include <utilities/logger.hpp>.

Result

libcuopt_client.so  3.1 MB
  NEEDED : libgrpc++, libprotobuf, libabseil, librapids_logger,
           libc, libstdc++, libgomp, libdl
           -> no libcudart, no rmm, no raft, no cudss
  ldd -r over libcuopt.so + libcuopt_client.so : 0 unresolved symbols

Known gaps

  • 14 cuopt::routing:: symbols still undefined — host-only accessors of routing::solver_settings_t / assignment_t in routing/solver_settings.cu and assignment.cu. Same pattern as refactor: split host-only members out of CUDA translation units #1801; follow-up.
  • No packaging or Python changes yet. Relinking the Cython extensions, splitting the CPU half of solver_wrapper.pyx, and the cuopt-client wheel / libcuopt-client conda output all come next.
  • raft/rmm headers are still needed at build time (the CPU headers transitively include them). Costs nothing at runtime because the device getters throw, but a standalone client package would need the headers separated too — that additionally removes 21 throwing stubs from cpu_optimization_problem_t.

Testing

Full build + 126 test binaries, 0 errors. 111/125 pass; the 14 failures are cudaErrorUnknown from a locally wedged nvidia_uvm, identical on unmodified main. The GPU-dependent tests need a clean CI run to confirm.

🤖 Generated with Claude Code

With the host code now separable, this adds the target that makes use of it.

cuopt_client holds the host-side problem representation (parsers,
data_model_view, mps_data_model, writers), the gRPC wire protocol (generated
protos + mappers) and the gRPC clients for both LP/MIP and routing. libcuopt and
cuopt_grpc_server both link it, so there is one mapper implementation rather than
a client-side fork.

Both gRPC arms qualify: the routing mappers added in #1597 reference no
raft/rmm/thrust either. solve_remote.cpp stays in libcuopt -- it is the
local-vs-remote dispatcher and calls into the GPU solver.

Built as an OBJECT library plus a SHARED library, mirroring cuopt_objs/cuopt.
cuopt_static embeds the objects directly rather than linking the shared library,
because the internal test binaries reach parser internals such as
mps_phase_registry_t.

Two build details worth knowing:

* cuopt_client uses default visibility, deliberately unlike cuopt_objs. libcuopt
  depends on roughly 214 of its symbols -- essentially the whole host-side API --
  because this library was carved out of the internals rather than designed as a
  curated CUOPT_EXPORT surface. Hiding them makes libcuopt.so fail to load with
  "undefined symbol: grpc_client_t::solve_mip".

* logger.cpp moves into the client sources: both the parsers and the gRPC code
  include <utilities/logger.hpp>, so without it the library has an unresolved
  reference to the logger.

Result:

    libcuopt_client.so NEEDED: libgrpc++, libprotobuf, libabseil,
                               librapids_logger, libc, libstdc++, libgomp, libdl
                               -- no libcudart, no rmm, no raft, no cudss
    ldd -r over libcuopt.so + libcuopt_client.so: 0 unresolved symbols

Note that raft/rmm headers are still needed at *build* time (the CPU headers
transitively include them), which costs nothing at runtime because the device
getters throw. Separating the headers is follow-up work, and is what a standalone
client package would additionally require.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

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.

@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test

@github-actions

Copy link
Copy Markdown

CI Test Summary

⏭️ All 5 test job(s) skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant