Description
#1804 makes libcuopt_client.so CUDA-free at the binary level — no cuda, rmm or raft in NEEDED, and no undefined cuopt:: symbols. But it does not yet make a CUDA-free install possible, which was the motivation. Installing the client still pulls the whole CUDA stack, because the packaging has not been split.
Two independent gaps, at two different layers.
1. Conda / wheel packaging
conda/recipes/libcuopt/recipe.yaml has exactly two outputs, libcuopt and libcuopt-tests. libcuopt_client.so ships inside libcuopt, whose run: requirements include:
- cuda-version
- librmm
- cuda-nvrtc
- libcudss
So the library cannot pull CUDA in, but the package it ships in does. This needs a separate libcuopt-client output (and the equivalent wheel) whose run deps are limited to grpc/protobuf/rapids_logger.
2. Python extension modules
This is the larger piece, and it is a separate problem from the .so linkage. The motivating consumer imports only Client, DataModel, Read and SolverSettings. Those are Cython extension modules in the cuopt wheel, and python/cuopt/pyproject.toml declares:
cudf==26.10.*
cupy-cuda13x[ctk]
pylibraft==26.10.*
rmm==26.10.*
Those are Python-level dependencies. They apply regardless of what the extension modules link against, so pointing them at cuopt::cuopt_client alone changes nothing about what pip installs.
Reaching a GPU-free client install needs the host-only extension modules (data_model, solver_settings, io, gRPC client) to link cuopt::cuopt_client and live in a package that does not declare the RAPIDS Python deps. Note that today only routing and distance_engine set linked_libraries explicitly; the LP/settings/io modules would need the same treatment.
Only then can the MCP server depend on that package instead of cuopt.
Sequencing
Worth settling the packaging boundary after #1622 (split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs), not before. That PR is renaming cuopt_lp to cuopt_mathematical_optimization and reshaping what the components are, so fixing package names now risks naming them twice. cuopt_client is plausibly a fourth component in that scheme rather than a standalone addition.
Acceptance
libcuopt-client conda output and wheel, with no CUDA/rmm/cudss run deps
- host-only Python extension modules linking
cuopt::cuopt_client, packaged without cudf/cupy/rmm/pylibraft
- a clean-environment check that installs the client package and imports
Client, DataModel, Read, SolverSettings with no CUDA runtime present
- MCP server depending on the client package
Relationship to #1635
#1635 tracks splitting the libcuopt wheel into base / routing / mathopt / grpc packages once #1622 merges. cuopt_client is a fifth component in that same split, and the two share almost all of their machinery: new ci/build_wheel_libcuopt_*.sh scripts, new python/libcuopt_*/ package directories, per-package dependency sets in dependencies.yaml, and conda recipe outputs.
These should be done together rather than separately. The distinction worth preserving is that cuopt_client is the only component with no CUDA dependency at all — measured 2.3 MB stripped, needing only grpc/protobuf/abseil — so it is the one that makes a GPU-free install possible, while the others are motivated by the PyPI size limit.
Related: #1801, #1802, #1803, #1804, #1622, #1635.
Description
#1804 makes
libcuopt_client.soCUDA-free at the binary level — nocuda,rmmorraftinNEEDED, and no undefinedcuopt::symbols. But it does not yet make a CUDA-free install possible, which was the motivation. Installing the client still pulls the whole CUDA stack, because the packaging has not been split.Two independent gaps, at two different layers.
1. Conda / wheel packaging
conda/recipes/libcuopt/recipe.yamlhas exactly two outputs,libcuoptandlibcuopt-tests.libcuopt_client.soships insidelibcuopt, whoserun:requirements include:So the library cannot pull CUDA in, but the package it ships in does. This needs a separate
libcuopt-clientoutput (and the equivalent wheel) whose run deps are limited to grpc/protobuf/rapids_logger.2. Python extension modules
This is the larger piece, and it is a separate problem from the
.solinkage. The motivating consumer imports onlyClient,DataModel,ReadandSolverSettings. Those are Cython extension modules in thecuoptwheel, andpython/cuopt/pyproject.tomldeclares:Those are Python-level dependencies. They apply regardless of what the extension modules link against, so pointing them at
cuopt::cuopt_clientalone changes nothing about what pip installs.Reaching a GPU-free client install needs the host-only extension modules (data_model, solver_settings, io, gRPC client) to link
cuopt::cuopt_clientand live in a package that does not declare the RAPIDS Python deps. Note that today onlyroutinganddistance_enginesetlinked_librariesexplicitly; the LP/settings/io modules would need the same treatment.Only then can the MCP server depend on that package instead of
cuopt.Sequencing
Worth settling the packaging boundary after #1622 (
split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs), not before. That PR is renamingcuopt_lptocuopt_mathematical_optimizationand reshaping what the components are, so fixing package names now risks naming them twice.cuopt_clientis plausibly a fourth component in that scheme rather than a standalone addition.Acceptance
libcuopt-clientconda output and wheel, with no CUDA/rmm/cudss run depscuopt::cuopt_client, packaged withoutcudf/cupy/rmm/pylibraftClient,DataModel,Read,SolverSettingswith no CUDA runtime presentRelationship to #1635
#1635 tracks splitting the
libcuoptwheel intobase/routing/mathopt/grpcpackages once #1622 merges.cuopt_clientis a fifth component in that same split, and the two share almost all of their machinery: newci/build_wheel_libcuopt_*.shscripts, newpython/libcuopt_*/package directories, per-package dependency sets independencies.yaml, and conda recipe outputs.These should be done together rather than separately. The distinction worth preserving is that
cuopt_clientis the only component with no CUDA dependency at all — measured 2.3 MB stripped, needing only grpc/protobuf/abseil — so it is the one that makes a GPU-free install possible, while the others are motivated by the PyPI size limit.Related: #1801, #1802, #1803, #1804, #1622, #1635.