Skip to content

Spectral Embedding #6581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: branch-25.08
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,8 @@ if(BUILD_CUML_CPP_LIBRARY)
if(all_algo OR spectralclustering_algo)
target_sources(${CUML_CPP_TARGET}
PRIVATE
src/spectral/spectral.cu)
src/spectral/spectral.cu
src/spectral/spectral_embedding.cu)
endif()

if(all_algo OR svm_algo)
Expand Down
30 changes: 30 additions & 0 deletions cpp/include/cuml/manifold/spectral_embedding.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <raft/core/device_mdspan.hpp>
#include <raft/core/resources.hpp>

#include <cuvs/preprocessing/spectral/spectral_embedding.hpp>

namespace ML::SpectralEmbedding {

auto spectral_embedding_cuvs(raft::resources const& handle,
cuvs::preprocessing::spectral_embedding::params config,
raft::device_matrix_view<float, int, raft::row_major> dataset,
raft::device_matrix_view<float, int, raft::col_major> embedding)
-> int;

} // namespace ML::SpectralEmbedding
29 changes: 29 additions & 0 deletions cpp/src/spectral/spectral_embedding.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <cuvs/preprocessing/spectral/spectral_embedding.hpp>

namespace ML::SpectralEmbedding {

auto spectral_embedding_cuvs(raft::resources const& handle,
cuvs::preprocessing::spectral_embedding::params config,
raft::device_matrix_view<float, int, raft::row_major> dataset,
raft::device_matrix_view<float, int, raft::col_major> embedding) -> int
{
return cuvs::preprocessing::spectral_embedding::transform(handle, config, dataset, embedding);
}

} // namespace ML::SpectralEmbedding
1 change: 1 addition & 0 deletions python/cuml/cuml/manifold/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_module_gpu_default("simpl_set.pyx" ${simpl_set_algo} ${manifold_algo})
add_module_gpu_default("t_sne.pyx" ${t_sne_algo} ${manifold_algo})
add_module_gpu_default("umap.pyx" ${umap_algo} ${manifold_algo})
add_module_gpu_default("umap_utils.pyx")
add_module_gpu_default("spectral_embedding.pyx")

rapids_cython_create_modules(
CXX
Expand Down
4 changes: 4 additions & 0 deletions python/cuml/cuml/manifold/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@

from cuml.manifold.t_sne import TSNE
from cuml.manifold.umap import UMAP
from cuml.manifold.spectral_embedding import (
SpectralEmbedding,
spectral_embedding,
)
126 changes: 126 additions & 0 deletions python/cuml/cuml/manifold/spectral_embedding.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#
# Copyright (c) 2024-2025, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# cython: profile=False
# distutils: language = c++
# cython: embedsignature = True
# cython: language_level = 3

import cupy as cp
import numpy as np

from cython.operator cimport dereference as deref
from libc.stdint cimport int64_t, uint32_t, uint64_t, uintptr_t

from pylibraft.common import Handle, cai_wrapper, device_ndarray
from pylibraft.common.handle import auto_sync_handle

from libcpp cimport bool
from pylibraft.common.cpp.mdspan cimport (
col_major,
device_matrix_view,
device_vector_view,
make_device_matrix_view,
make_device_vector_view,
row_major,
)
from pylibraft.common.cpp.optional cimport optional
from pylibraft.common.handle cimport device_resources
from pylibraft.random.cpp.rng_state cimport RngState

from cuml.common import input_to_cuml_array
from cuml.internals.base import Base
from cuml.internals.mixins import CMajorInputTagMixin, SparseInputTagMixin


cdef extern from "cuvs/preprocessing/spectral/spectral_embedding.hpp" namespace "cuvs::preprocessing::spectral_embedding" nogil:
cdef cppclass params:
int n_components
int n_neighbors
bool norm_laplacian
bool drop_first
uint64_t seed

cdef params config

cdef extern from "cuml/manifold/spectral_embedding.hpp" namespace "ML::SpectralEmbedding":

cdef int spectral_embedding_cuvs(
const device_resources &handle,
params config,
device_matrix_view[float, int, row_major] dataset,
device_matrix_view[float, int, col_major] embedding) except +


@auto_sync_handle
def spectral_embedding(A, n_components, random_state=None, n_neighbors=None, norm_laplacian=True, drop_first=True, handle=None):

cdef device_resources *h = <device_resources*><size_t>handle.getHandle()

A, n_rows, n_cols, _ = \
input_to_cuml_array(A, order='C', check_dtype=np.float32,
convert_to_dtype=cp.float32)

# print(A.shape)
# print(type(A), A.dtype)

# A = cai_wrapper(A)
A_ptr = <uintptr_t>A.ptr

config.n_components = n_components
config.seed = random_state if random_state is not None else 42

config.n_neighbors = (
n_neighbors
if n_neighbors is not None
else max(int(A.shape[0] / 10), 1)
)

config.norm_laplacian = norm_laplacian
config.drop_first = drop_first

if config.drop_first:
config.n_components += 1


eigenvectors = device_ndarray.empty((A.shape[0], n_components), dtype=A.dtype, order='F')

eigenvectors_cai = cai_wrapper(eigenvectors)
eigenvectors_ptr = <uintptr_t>eigenvectors_cai.data

cdef int result = spectral_embedding_cuvs(deref(h), config, make_device_matrix_view[float, int, row_major](<float *>A_ptr, <int> A.shape[0], <int> A.shape[1]), make_device_matrix_view[float, int, col_major](<float *>eigenvectors_ptr, <int> A.shape[0], <int> n_components))

return cp.asarray(eigenvectors)

class SpectralEmbedding(Base,
CMajorInputTagMixin,
SparseInputTagMixin):

def __init__(self, n_components=2, random_state=None, n_neighbors=None, handle=None):
super().__init__(handle=handle)
self.n_components = n_components
self.random_state = random_state
self.n_neighbors = n_neighbors

def fit_transform(self, X, y=None):
self.fit(X, y)
return self.embedding_

def fit(self, X, y=None):
self.embedding_ = self._fit(X, self.n_components, random_state=self.random_state, n_neighbors=self.n_neighbors)
return self

def _fit(self, A, n_components, random_state=None, n_neighbors=None):
return spectral_embedding(A, n_components, random_state=random_state, n_neighbors=n_neighbors)
Loading