Skip to content

Split the cuopt Python package into per-solver packages for VRP and LP #2005

Description

@ramakrishnap-nv

Background

#1635 split the C++ libraries into libcuopt-client, libcuopt-mathopt and libcuopt-routing, so a routing-only install no longer pulls cudss, nccl or nvjitlink. That work is done and on main.

The Python side did not follow. cuopt is still one wheel covering both engines, so pip install cuopt for a VRP-only user still installs libcuopt-mathopt and its CUDA math libraries -- roughly 1.2 GB of uncompressed on-disk libraries that are never called. The per-solver Python packages were part of #1635's original plan and are the half that remains.

The split is already latent in the tree

The Python package maps almost one-to-one onto the C++ components, and the two engines are already independent.

No cross-imports. Nothing under cuopt/routing or cuopt/distance_engine imports or cimports cuopt.linear_programming, and nothing under cuopt/linear_programming reaches into cuopt.routing. This mirrors the C++ libraries, where readelf -d shows no DT_NEEDED between the two engines.

Each directory links only what it needs:

Directory Linked libraries
routing/ cuopt::routing, cuopt::client
distance_engine/ cuopt::routing
linear_programming/ cuopt::mathopt, cuopt::client
grpc/client/ cuopt::client (plus rmm::rmm, for the exception handlers Cython emits)

Partial installs are already anticipated. cuopt/__init__.py imports libcuopt inside a try/except ModuleNotFoundError and resolves linear_programming, routing, distance_engine and grpc lazily through __getattr__, with the stated reason being CPU-only hosts using remote solve. import cuopt therefore already succeeds when a submodule is absent; the import only fails on attribute access.

Tests already sit on the boundary, under tests/routing and tests/linear_programming.

Proposed boundaries

  • cuopt-vrp -- routing/, distance_engine/; depends on libcuopt-routing
  • cuopt-lp -- linear_programming/; depends on libcuopt-mathopt
  • cuopt -- metapackage depending on both, so existing installs are unaffected

Open questions

Where the shared code goes. cuopt/utilities/ (exception_handler.py, type_casting.py, utils.py) is imported by both halves, as is cuopt._version. These need a common home -- either a small shared package or, following the C++ boundary, the client package.

How cuopt becomes splittable at all. Shipping cuopt.routing and cuopt.linear_programming from different wheels means cuopt has to become a namespace package (PEP 420) or carry an equivalent shim. This is the main technical decision and worth settling before any code moves, since it affects every import path.

Relationship to the Python client. Separating the gRPC client into its own CUDA-free package is a related but distinct piece, discussed in #1635 (comment): grpc/client already links cuopt::client alone, and is blocked only by three symbols that DataModel and SolverSettings resolve from mathopt. If both splits happen, cuopt/grpc belongs with the client package rather than with either engine.

Whether the metapackage keeps the name. cuopt as a metapackage preserves pip install cuopt, but import cuopt.routing then depends on a package the user may not have installed, and today that surfaces as an AttributeError from __getattr__ rather than a useful message. Worth an explicit error that names the missing package.

Out of scope

cuopt-sh-client and cuopt_mcp are separate packaging axes; cuopt_mcp has its own issue in #1755.

Activity

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

Metadata

Metadata

Assignees

Labels

awaiting responseThis expects a response from maintainer or contributor depending on who requested in last comment.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions