.. Copyright 2024 Intel 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:: substitutions.rst
.. _array_api:
=================
Array API support
=================
Overview
========
Many estimators from the |sklearnex| support passing data classes that conform to the
`Array API `_ specification as inputs to methods like ``.fit()``
and ``.predict()``, such as |dpnp_array| or `torch.tensor `__.
This is particularly useful for GPU computations, as it allows performing operations on inputs that are already
on GPU without moving the data from host to device.
.. important::
Array API is disabled by default in |sklearn|. In order to get array API support in the |sklearnex|, it must
be :external+sklearn:doc:`enabled in scikit-learn `, which requires either changing
global settings or using a :doc:`config_context `.
.. hint::
Executing computations on array API inputs (whether on CPUs or GPUs) requires additional dependencies, particularly on package ``scikit-learn-intelex-gpu`` - see
:doc:`oneapi-gpu` for details.
When passing array API inputs whose data is on a SYCL-enabled device (e.g. an Intel GPU), as
supported for example by `PyTorch `__
and |dpnp|, if array API support is enabled, :doc:`GPU dependencies ` are available, and the
requested operation (e.g. call to ``.fit()`` / ``.predict()``
on the estimator class being used) is :ref:`supported on device/GPU `, computations
will be performed on the device where the data lives, without involving any data transfers.
If the requested operation is not supported on the device where the data lives, then it will either fall
back to |sklearn|, or to an accelerated CPU version from the |sklearnex| when supported - these are controllable
through options ``allow_sklearn_after_onedal`` (default is ``True``) and ``allow_fallback_to_host`` (default is
``False``), respectively, which are accepted by :obj:`config_context ` and :obj:`set_config ` after
:doc:`patching scikit-learn ` or when importing those directly from ``sklearnex`` (see :doc:`config-contexts`).
.. note::
Under default settings for :obj:`sklearnex.set_config` / :obj:`sklearnex.config_context`, operations that are not supported on GPU will
fall back to |sklearn| instead of falling back to CPU versions from the |sklearnex|.
If array API is enabled for |sklearn| and the estimator being used has array API support on |sklearn| (which can be
verified by attribute ``array_api_support`` from :obj:`sklearn.utils.get_tags`), then array API inputs whose data
is allocated neither on CPU nor on a SYCL device will be forwarded directly to the unpatched methods from |sklearn|,
without using the accelerated versions from this library, regardless of option ``allow_sklearn_after_onedal``.
While other array API inputs (e.g. torch arrays with data allocated on a non-SYCL device) might be supported
by the |sklearnex| in cases where the same class from |sklearn| doesn't support array API, note that the data will
be transferred to host if it isn't already, and the computations will happen on CPU.
.. hint::
Enable :ref:`verbose` to see information about whether data transfers happen during an operation or not,
whether an accelerated version from the extension is used, and where (CPU/device) the operation is executed.
.. warning::
If array API inputs are passed to an estimator's ``.fit()``, subsequent data passed to methods such as
``.predict()`` or ``.score()`` of the fitted model **must reside on the same device** - meaning: a model that
was fitted with GPU arrays cannot make predictions on CPU arrays, and a model fitted with CPU array API inputs
cannot make predictions on GPU arrays, even if they are of the same class. Attempting to pass data on the
wrong device might lead to process-wide crashes.
.. note::
An estimator fitted to array API inputs should only be passed objects of the same class that was passed to
``.fit()`` in subsequent calls to ``.predict()``, ``.score()``, and similar. In some cases, it might be
possible to pass a different class at prediction time without errors (particularly when fitting on CPU only),
but this is generally not supported and users should not rely on these interchanges working reliably.
.. note::
The :ref:`target_offload ` option in config contexts and settings is not intended to work with array API
classes that have :external+dpctl:doc:`USM data `. In order to ensure that computations
happen on the intended device under array API, make sure that the data is already on the desired device.
.. _array_api_estimators:
Supported classes
=================
The following patched classes have support for array API inputs:
- :obj:`sklearnex.basic_statistics.BasicStatistics`
- :obj:`sklearnex.basic_statistics.IncrementalBasicStatistics`
- :obj:`sklearn.cluster.DBSCAN`
- :obj:`sklearn.cluster.KMeans`
- :obj:`sklearn.covariance.EmpiricalCovariance`
- :obj:`sklearnex.covariance.IncrementalEmpiricalCovariance`
- :obj:`sklearn.decomposition.PCA`
- :obj:`sklearn.ensemble.ExtraTreesClassifier`
- :obj:`sklearn.ensemble.ExtraTreesRegressor`
- :obj:`sklearn.ensemble.RandomForestClassifier`
- :obj:`sklearn.ensemble.RandomForestRegressor`
- :obj:`sklearn.linear_model.LinearRegression`
- :obj:`sklearn.linear_model.LogisticRegression`
- :obj:`sklearn.linear_model.Ridge`
- :obj:`sklearnex.linear_model.IncrementalLinearRegression`
- :obj:`sklearnex.linear_model.IncrementalRidge`
- :obj:`sklearn.neighbors.KNeighborsClassifier`
- :obj:`sklearn.neighbors.KNeighborsRegressor`
- :obj:`sklearn.neighbors.NearestNeighbors`
- :obj:`sklearn.neighbors.LocalOutlierFactor`
- :obj:`sklearn.svm.NuSVC`
- :obj:`sklearn.svm.NuSVR`
- :obj:`sklearn.svm.SVC`
- :obj:`sklearn.svm.SVR`
.. note::
In the cases where |sklearn| does not have array API support but the |sklearnex| does,
there might be some methods where array API support is incomplete - see details in the
next subsection.
.. note::
Result attributes of |sklearnex| classes which contain |sklearn| or |sklearnex| classes may not themselves be
array API compliant. For example, ensemble algorithms contain decision tree estimators result objects which
do not comply with the array API standard.
Coverage of array API support
-----------------------------
All of the classes with array API support in the |sklearnex| have full support for core
methods common to base classes for regression and classification:
- ``.fit()``
- ``.predict()``
- ``.predict_proba()``
- ``.predict_log_proba()``
- ``.score()``
However, some classes have additional methods that might not be fully covered by array API
support when the corresponding class from stock |sklearn| does not support array API. For
example, :obj:`sklearn.ensemble.RandomForestClassifier` also offers methods
:meth:`sklearn.ensemble.RandomForestClassifier.apply` and
:meth:`sklearn.ensemble.RandomForestClassifier.decision_path()`, which do not have
accelerated analogs in the |sklearnex| and thus rely on |sklearn| for the computations.
Calling methods such as ``.apply()`` from a ``RandomForestClassifier`` from the |sklearnex|
that was fitted to array API inputs will work, but it will do so by transferring the data
to host if not already there, passing the intermediate object to |sklearn|, and outputting
a host NumPy array, with some exceptions where |dpnp_array| classes might be returned.
Similarly, :obj:`sklearn.neighbors.KNeighborsClassifier` also offers methods such as
:meth:`~sklearn.neighbors.KNeighborsClassifier.radius_neighbors` and
:meth:`~sklearn.neighbors.KNeighborsClassifier.kneighbors_graph`, which do not have
accelerated analogs in the |sklearnex| and thus rely on |sklearn| for the computations.
Calling such methods from a KNN estimator from the |sklearnex| that was fitted to array
API inputs will work, but it will do so by transferring the data to host if not already
there, passing the intermediate object to |sklearn|, and outputting a host NumPy array.
Note that some cases of estimator-specific methods are still fully array API compatible -
for example, :meth:`sklearn.neighbors.NearestNeighbors.kneighbors` will produce outputs
of array API classes when fitted to them.
For :obj:`sklearn.linear_model.LogisticRegression`, array API coverage is limited to cases where the input array
is allocated on a GPU device, so passing array API inputs on CPU other than NumPy arrays will not result
in calling accelerated routines from the |sklearnex|.
Function ``move_estimator_to``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Starting with version 1.9, |sklearn| provides an experimental utility ``sklearn.utils._array_api.move_estimator_to``,
which can be used to move an estimator that was fitted with one array API library namespace and device to
another.
This function is not supported with estimators from the |sklearnex| at the moment. This lack of support
also extends to cases where :doc:`patching ` is applied, but the ``.fit()`` routine is handled
through |sklearn| as a fallback (see :doc:`algorithms` for details) - meaning: ``move_estimator_to`` cannot
be used with classes from the |sklearnex| regardless of which backend was used to fit the estimator.
Example usage
=============
GPU operations on GPU arrays
----------------------------
.. tabs::
.. tab:: With Torch tensors
.. code-block:: python
# Array API support from sklearn requires enabling it on SciPy too
import os
os.environ["SCIPY_ARRAY_API"] = "1"
import numpy as np
import torch
from sklearnex import config_context
from sklearnex.linear_model import LinearRegression
# Random data for a regression problem
rng = np.random.default_rng(seed=123)
X_np = rng.standard_normal(size=(100, 10), dtype=np.float32)
y_np = rng.standard_normal(size=100, dtype=np.float32)
# Torch offers an array-API-compliant class where data can be on GPU (referred to as 'xpu')
X = torch.tensor(X_np, device="xpu")
y = torch.tensor(y_np, device="xpu")
# Important to note again that array API must be enabled on scikit-learn
model = LinearRegression()
with config_context(array_api_dispatch=True):
model.fit(X, y)
# Fitted attributes are now of the same class as inputs
assert isinstance(model.coef_, torch.Tensor)
# Predictions are also of the same class
with config_context(array_api_dispatch=True):
pred = model.predict(X[:5])
assert isinstance(pred, torch.Tensor)
.. tab:: With DPNP arrays
.. code-block:: python
# Array API support from sklearn requires enabling it on SciPy too
import os
os.environ["SCIPY_ARRAY_API"] = "1"
import numpy as np
import dpnp
from sklearnex import config_context
from sklearnex.linear_model import LinearRegression
# Random data for a regression problem
rng = np.random.default_rng(seed=123)
X_np = rng.standard_normal(size=(100, 10), dtype=np.float32)
y_np = rng.standard_normal(size=100, dtype=np.float32)
# DPNP offers an array-API-compliant class where data can be on GPU
X = dpnp.array(X_np, device="gpu")
y = dpnp.array(y_np, device="gpu")
# Important to note again that array API must be enabled on scikit-learn
model = LinearRegression()
with config_context(array_api_dispatch=True):
model.fit(X, y)
# Fitted attributes are now of the same class as inputs
assert isinstance(model.coef_, X.__class__)
# Predictions are also of the same class
with config_context(array_api_dispatch=True):
pred = model.predict(X[:5])
assert isinstance(pred, X.__class__)
``array-api-strict``
--------------------
Example code showcasing how to use `array-api-strict `__
arrays to run patched :obj:`sklearn.cluster.DBSCAN`.
.. toggle::
.. literalinclude:: ../../examples/sklearnex/dbscan_array_api.py
:language: python