Skip to content

Conversation

@jnke2016
Copy link
Contributor

@jnke2016 jnke2016 commented Oct 26, 2025

This PR integrates cuVS spectral clustering from upstream into cuGraph while maintaining full backward compatibility. The integration deprecates the legacy balanced cut clustering C++ and Python APIs in favor of spectral_modularity_maximization. In fact, cuVS consolidated the spectral clustering API to assign vertices to clusters that by default maximizes the modularity score

Static Linking with Conda Fallback:

  • cuGraph is configured to statically link libcuvs for optimal performance
  • The build system first attempts to use conda-installed libcuvs if available
  • If libcuvs is not present in the conda environment or static linking is required, the build system automatically clones the main cuVS repository (rapidsai/cuvs) and builds it from source
  • Uses the standard RAPIDS release branch (${rapids-cmake-checkout-tag}) ensuring version compatibility

Note: The header files raft/spectral/partition.cuh and raft/spectral/modularity_maximization.cuh are still necessary in spectraly_clustering.cu because APIs (like raft::spectral::analyzePartition, raft::spectral::analyzeModularity) are still being called in cuGraph and are not supported in cuVS. This is not an issue because those APIs are not deprecated in raft in contrary to kmeans and solvers

@copy-pr-bot
Copy link

copy-pr-bot bot commented Oct 26, 2025

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.

Copy link
Member

@aamijar aamijar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jnke2016, thanks for the PR! Looks good overall, just left some comments for you to check.

unsigned long long seed2 = seed1 + 1;
// Use cuVS for float precision until double precision is supported
if constexpr (std::is_same_v<weight_t, float>) {
// Convert CSR to COO using thrust
Copy link
Member

@aamijar aamijar Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to use raft::sparse::convert::csr_to_coo here if its more convenient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I leveraged this raft utils function to convert from csr to coo

coo_matrix,
raft::make_device_vector_view<vertex_t, vertex_t>(clustering, graph.number_of_vertices));
} else {
CUGRAPH_FAIL("Double precision spectral clustering not yet supported with cuVS");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added double support for the cuvs PR so we should be able to try with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I updated the PR to support double and after merging your latest changes it worked

Copy link
Member

@aamijar aamijar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for incorporating the suggestions!

@jnke2016 jnke2016 requested a review from rlratzel November 3, 2025 16:29
@jnke2016 jnke2016 marked this pull request as ready for review November 3, 2025 16:30
@jnke2016 jnke2016 requested review from a team as code owners November 3, 2025 16:30
Copy link
Collaborator

@ChuckHastings ChuckHastings left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, will do a final review after it passes CI

@jnke2016 jnke2016 requested a review from a team as a code owner November 3, 2025 18:20
@jnke2016 jnke2016 requested review from a team as code owners November 4, 2025 04:36
Copy link
Collaborator

@ChuckHastings ChuckHastings left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good once we solve the build problem.

@jnke2016
Copy link
Contributor Author

/ok to test fed3f33

@jnke2016
Copy link
Contributor Author

@robertmaynard @bdice . I am still getting an overlinking error during the conda build. Can the error be caused by the fact that rapids_cpm_find only discovers shared library (libcuvs.so) which forces CMake to fallback to cuvs::cuvs (dynamic linking? If it is the case, would building libcuvs statically with BUILD_STATIC_LIBS ON in rapids_cpm_find enable libcuvs to be statically linked to libcugraph through cuvs::cuvs_static?

@jnke2016
Copy link
Contributor Author

I am still trying to figure out the source of the overlinking issue, and I think it arose when we introduced the COMPONENTS element. Unless I am wrong, there is no cuvs_static component defined, and both cuvs::cuvs (shared) and cuvs::cuvs_static (static) targets are always loaded together in the main/default package (no component specification needed). cuvs_c is the only component, which provides the C API set(cuvs_comp_names c_api). I am not sure what the expected behavior is in this case, but I can speculate. Since the cuvs_static component doesn't exist, it falls back to shared → overlinking because libcuvs becomes a runtime dependency but is not listed in the conda package metadata (which expects static linking).

@jnke2016
Copy link
Contributor Author

/ok to test d8f2df6

@jnke2016
Copy link
Contributor Author

/ok to test b4117ba

@jnke2016
Copy link
Contributor Author

/ok to test 2e677c8

@robertmaynard
Copy link
Contributor

I am still trying to figure out the source of the overlinking issue, and I think it arose when we introduced the COMPONENTS element. Unless I am wrong, there is no cuvs_static component defined, and both cuvs::cuvs (shared) and cuvs::cuvs_static (static) targets are always loaded together in the main/default package (no component specification needed). cuvs_c is the only component, which provides the C API set(cuvs_comp_names c_api). I am not sure what the expected behavior is in this case, but I can speculate. Since the cuvs_static component doesn't exist, it falls back to shared → overlinking because libcuvs becomes a runtime dependency but is not listed in the conda package metadata (which expects static linking).

Yeah I was wrong about the components. The cuvs::cuvs_static target will be brought in with find_package(cuvs) whenever it is available

sccache --show-adv-stats

EXCLUDE_ARGS=(
--exclude "libcuvs.so"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed. Wheels should always be statically linking, so shared libraries shouldn't be present in the first place. See cuML's wheel building script, which doesn't need this exclusion. cuGraph should be the same. https://github.com/rapidsai/cuml/blob/main/ci/build_wheel_libcuml.sh

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused. Aren't wheels using prebuilt packages and link dynamically (hence we have libcuvs.so in the build artifact). Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels should always be statically linking, so shared libraries shouldn't be present in the first place

In that case, I should update the wheels to compile and build from source instead of using prebuilt packages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it now after reviewing cuML. Thanks

>>> from cugraph.datasets import karate
>>> G = karate.get_graph(download=True)
>>> df = cugraph.spectralBalancedCutClustering(G, 5)
>>> df = cugraph.spectralModularityMaximizationClustering(G, 5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example for the docs of spectralBalancedCutClustering. We shouldn't change the example code here. We could add a deprecation notice in the docs, though.

Suggested change
>>> df = cugraph.spectralModularityMaximizationClustering(G, 5)
>>> df = cugraph.spectralBalancedCutClustering(G, 5)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't change the example code here. We could add a deprecation notice in the docs, though.

Right but this is what I did initially and the pytest doctests would fail unless that warning failure get skipped. @rlratzel any thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably just remove the example entirely. I think an example is useful for someone trying to learn how to add this function to their code, which we/they don't want since it's going away. Make sure the docstring is updated to indicate it's deprecated and direct them to the new API they should call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should still add the ignores I mentioned above because this will apply to all deprecated APIs in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should still add the ignores I mentioned above because this will apply to all deprecated APIs in the future.

I'm thinking that ignoring errors from deprecated APIs in doctests is not good though, and perhaps we should just adopt the policy we're doing here (remove example docstring, ensure new API is referenced) for all deprecated APIs in the future.

I'm concerned the ignores will mask other API deprecations we need to know about (eg. an example docstring uses a deprecated cuDF API, or a cugraph docstring for function A uses deprecated cugraph API B as part of the example and should be updated).

Copy link
Contributor

@bdice bdice Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned the ignores will mask other API deprecations we need to know about

If it helps, ignore::FutureWarning:cugraph will ignore just the deprecations in the cugraph module, or you can use ignore:MESSAGE_REGEX:FutureWarning:cugraph with some regex string to ignore specific (known) deprecations from cugraph.

includes:
- common_build
- depends_on_libcugraph
- depends_on_libcuvs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheel builds shouldn't depend on libcuvs libraries, it should be fetched and built statically by CMake.

Suggested change
- depends_on_libcuvs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I understand what you mean here based on this

includes:
- common_build
- depends_on_librmm
- depends_on_libcuvs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheel builds shouldn't depend on libcuvs libraries, it should be fetched and built statically by CMake.

Suggested change
- depends_on_libcuvs

extras:
table: project
includes:
- depends_on_libcuvs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not want a runtime dependency on libcuvs, that's the point of statically linking.

Suggested change
- depends_on_libcuvs

includes:
- common_build
- depends_on_libcugraph
- depends_on_libcuvs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheel builds shouldn't depend on libcuvs libraries, it should be fetched and built statically by CMake.

Suggested change
- depends_on_libcuvs

common:
- output_types: conda
packages:
- &libcuvs_unsuffixed libcuvs==25.12.*,>=0.0.0a0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what I think you want:

  • conda developer environments: depend on shared library
  • conda devcontainers: depend on shared library
  • conda recipes: fetch with CMake, build and link libcuvs statically
    • No dependency in host or run
    • Note: this differs from cuML, which depends on a shared library here. This can go either way in my opinion.
  • wheel builds: fetch with CMake, build and link libcuvs statically
  • wheel at runtime: no dependency (it's statically linked)
  • pip devcontainers: ???
    • This one we might want to discuss. Probably fetch with CMake, build and link libcuvs statically? I haven't checked what this PR does right now.

@robertmaynard
Copy link
Contributor

@jnke2016 Are you still planning on getting this into 25.12? If so please don't merge in main anymore and re-target release/25.12 as soon as possible.

@jnke2016
Copy link
Contributor Author

Are you still planning on getting this into 25.12? If so please don't merge in main anymore and re-target release/25.12 as soon as possible.

Got it @robertmaynard . @ChuckHastings can you retarget it please?

@ChuckHastings ChuckHastings changed the base branch from main to release/25.12 November 17, 2025 19:43
@ChuckHastings
Copy link
Collaborator

Are you still planning on getting this into 25.12? If so please don't merge in main anymore and re-target release/25.12 as soon as possible.

Got it @robertmaynard . @ChuckHastings can you retarget it please?

Done

@jnke2016
Copy link
Contributor Author

jnke2016 commented Nov 17, 2025

pip devcontainers: ???
This one we might want to discuss. Probably fetch with CMake, build and link libcuvs statically? I haven't checked what this PR does right now.

@bdice Good catch thanks. I didn't have a devcontainers section. I just added it and it installs pre-built libcuvs and link dynamically

@jnke2016
Copy link
Contributor Author

/ok to test 0adde0d

@jnke2016
Copy link
Contributor Author

/ok to test db3dbea

- test_python_common
- test_python_cugraph
- test_python_pylibcugraph
devcontainers:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This devcontainers key isn't doing anything right now. To make it take effect, you'd need to update the devcontainers repo like this: https://github.com/rapidsai/devcontainers/blob/f9a516d1e4cd9b669845527d4ae13a4fdc720933/features/src/rapids-build-utils/opt/rapids-build-utils/manifest.yaml#L186-L187

However, why do we need this? I think there should be a solution that doesn't require a new dependency file key.

Look at how cuML is solving this: https://github.com/rapidsai/cuml/blob/6b42da514957a909a6d9d8921bad9d705161d8d8/dependencies.yaml#L684-L688

cuML only defines libcuvs for conda output types and not pyproject or requirements. I think that's probably what you want here too. Following the other discussion where we broke down the expected behavior by build type, you probably never want libcuvs wheels because you'll always build and statically link libcuvs. The only cases where you want libcuvs packages are for conda developer environments and devcontainers (but not conda recipes!).

I propose:

  • Delete the devcontainers key here
  • Modify depends_on_libcuvs below to only have conda outputs, matching cuML

Copy link
Contributor Author

@jnke2016 jnke2016 Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand correctly, you are suggesting to modify depends_on_libcuvs to only output for conda (not pyproject or requirements) because:

  • Conda developer environments & devcontainers automatically get libcuvs from the all: section
  • Wheel builds & pip devcontainers don't get libcuvs because pyproject outputs won't include it (statically link)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also:

  • Conda recipes don't get libcuvs as a runtime dependency (they also statically link cuVS)

Copy link
Contributor

@bdice bdice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. Approved!

@jnke2016
Copy link
Contributor Author

/ok to test 20dfd1d

@ChuckHastings
Copy link
Collaborator

/merge

@rapids-bot rapids-bot bot merged commit 0f1afef into rapidsai:release/25.12 Nov 19, 2025
100 of 101 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improvement / enhancement to an existing function non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants