Skip to content

Commit

Permalink
Improved CPU/GPU interoperability (rapidsai#5001)
Browse files Browse the repository at this point in the history
Authors:
  - Victor Lafargue (https://github.com/viclafargue)
  - William Hicks (https://github.com/wphicks)
  - Corey J. Nolet (https://github.com/cjnolet)
  - Dante Gama Dessavre (https://github.com/dantegd)
  - Carl Simon Adorf (https://github.com/csadorf)

Approvers:
  - Dante Gama Dessavre (https://github.com/dantegd)

URL: rapidsai#5001
  • Loading branch information
viclafargue authored Dec 17, 2022
1 parent cf69ffb commit 5ece23f
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 269 deletions.
5 changes: 0 additions & 5 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,6 @@ Dataset Generation (Dask-based Multi-GPU)
.. automodule:: cuml.dask.datasets.regression
:members:

Array Wrappers (Internal API)
-----------------------------

.. autoclass:: cuml.common.CumlArray
:members:

Metrics (regression, classification, and distance)
--------------------------------------------------
Expand Down
15 changes: 10 additions & 5 deletions python/cuml/decomposition/pca.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ from cuml.common.exceptions import NotFittedError
from cuml.internals.mixins import FMajorInputTagMixin
from cuml.internals.mixins import SparseInputTagMixin
from cuml.internals.api_decorators import device_interop_preparation
from cuml.internals.api_decorators import enable_device_interop


cdef extern from "cuml/decomposition/pca.hpp" namespace "ML":
Expand Down Expand Up @@ -406,7 +407,8 @@ class PCA(UniversalBase,
return self

@generate_docstring(X='dense_sparse')
def _fit(self, X, y=None) -> "PCA":
@enable_device_interop
def fit(self, X, y=None) -> "PCA":
"""
Fit the model with X. y is currently ignored.

Expand Down Expand Up @@ -496,7 +498,8 @@ class PCA(UniversalBase,
'description': 'Transformed values',
'shape': '(n_samples, n_components)'})
@cuml.internals.api_base_return_array_skipall
def _fit_transform(self, X, y=None) -> CumlArray:
@enable_device_interop
def fit_transform(self, X, y=None) -> CumlArray:
"""
Fit the model with X and apply the dimensionality reduction on X.

Expand Down Expand Up @@ -541,8 +544,9 @@ class PCA(UniversalBase,
'type': 'dense_sparse',
'description': 'Transformed values',
'shape': '(n_samples, n_features)'})
def _inverse_transform(self, X, convert_dtype=False,
return_sparse=False, sparse_tol=1e-10) -> CumlArray:
@enable_device_interop
def inverse_transform(self, X, convert_dtype=False,
return_sparse=False, sparse_tol=1e-10) -> CumlArray:
"""
Transform data back to its original space.

Expand Down Expand Up @@ -642,7 +646,8 @@ class PCA(UniversalBase,
'type': 'dense_sparse',
'description': 'Transformed values',
'shape': '(n_samples, n_components)'})
def _transform(self, X, convert_dtype=False) -> CumlArray:
@enable_device_interop
def transform(self, X, convert_dtype=False) -> CumlArray:
"""
Apply dimensionality reduction to X.

Expand Down
13 changes: 9 additions & 4 deletions python/cuml/decomposition/tsvd.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ from cuml.common.array_descriptor import CumlArrayDescriptor
from cuml.common.doc_utils import generate_docstring
from cuml.internals.mixins import FMajorInputTagMixin
from cuml.internals.api_decorators import device_interop_preparation
from cuml.internals.api_decorators import enable_device_interop

from cython.operator cimport dereference as deref

Expand Down Expand Up @@ -299,7 +300,8 @@ class TruncatedSVD(UniversalBase,
dtype=self.dtype)

@generate_docstring()
def _fit(self, X, y=None) -> "TruncatedSVD":
@enable_device_interop
def fit(self, X, y=None) -> "TruncatedSVD":
"""
Fit LSI model on training cudf DataFrame X. y is currently ignored.

Expand All @@ -313,7 +315,8 @@ class TruncatedSVD(UniversalBase,
'type': 'dense',
'description': 'Reduced version of X',
'shape': '(n_samples, n_components)'})
def _fit_transform(self, X, y=None) -> CumlArray:
@enable_device_interop
def fit_transform(self, X, y=None) -> CumlArray:
"""
Fit LSI model to X and perform dimensionality reduction on X.
y is currently ignored.
Expand Down Expand Up @@ -377,7 +380,8 @@ class TruncatedSVD(UniversalBase,
'type': 'dense',
'description': 'X in original space',
'shape': '(n_samples, n_features)'})
def _inverse_transform(self, X, convert_dtype=False) -> CumlArray:
@enable_device_interop
def inverse_transform(self, X, convert_dtype=False) -> CumlArray:
"""
Transform X back to its original space.
Returns X_original whose transform would be X.
Expand Down Expand Up @@ -426,7 +430,8 @@ class TruncatedSVD(UniversalBase,
'type': 'dense',
'description': 'Reduced version of X',
'shape': '(n_samples, n_components)'})
def _transform(self, X, convert_dtype=False) -> CumlArray:
@enable_device_interop
def transform(self, X, convert_dtype=False) -> CumlArray:
"""
Perform dimensionality reduction on X.

Expand Down
40 changes: 15 additions & 25 deletions python/cuml/internals/api_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import typing
from functools import wraps
import warnings
from importlib import import_module

import cuml.internals.array
import cuml.internals.array_sparse
Expand Down Expand Up @@ -779,38 +778,17 @@ def inner_f(*args, **kwargs):

def device_interop_preparation(init_func):
"""
This function serves as a decorator to cuML estimators that implement
the CPU/GPU interoperability feature. It imports the joint CPU estimator
and processes the hyperparameters.
This function serves as a decorator for cuML estimators that implement
the CPU/GPU interoperability feature. It processes the estimator's
hyperparameters by saving them and filtering them for GPU execution.
"""

@functools.wraps(init_func)
def processor(self, *args, **kwargs):
# if child class (parent class was already decorated), skip
if hasattr(self, '_cpu_model_class'):
return init_func(self, *args, **kwargs)

if hasattr(self, '_cpu_estimator_import_path'):
# if import path differs from the one of sklearn
# look for _cpu_estimator_import_path
estimator_path = self._cpu_estimator_import_path.split('.')
model_path = '.'.join(estimator_path[:-1])
model_name = estimator_path[-1]
else:
# import from similar path to the current estimator
# class
model_path = 'sklearn' + self.__class__.__module__[4:]
model_name = self.__class__.__name__
self._cpu_model_class = getattr(import_module(model_path), model_name)

# Save all kwargs
self._full_kwargs = kwargs
# Generate list of available cuML hyperparameters
gpu_hyperparams = list(inspect.signature(init_func).parameters.keys())
# Save list of available CPU estimator hyperparameters
self._cpu_hyperparams = list(
inspect.signature(self._cpu_model_class.__init__).parameters.keys()
)

# Filter provided parameters for cuML estimator initialization
filtered_kwargs = {}
Expand All @@ -824,3 +802,15 @@ def processor(self, *args, **kwargs):

return init_func(self, *args, **filtered_kwargs)
return processor


def enable_device_interop(gpu_func):
@functools.wraps(gpu_func)
def dispatch(self, *args, **kwargs):
# check that the estimator implements CPU/GPU interoperability
if hasattr(self, 'dispatch_func'):
func_name = gpu_func.__name__
return self.dispatch_func(func_name, gpu_func, *args, **kwargs)
else:
return gpu_func(self, *args, **kwargs)
return dispatch
Loading

0 comments on commit 5ece23f

Please sign in to comment.