Skip to content

[CI] upgrade dpctl/dpnp versions #2414

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

Merged
merged 71 commits into from
Aug 7, 2025
Merged
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8950327
Update build-and-test-lnx.yml
icfaust Aug 4, 2025
9fa3d75
Update ci.yml
icfaust Aug 4, 2025
324176f
Update build-and-test-lnx.yml
icfaust Aug 4, 2025
8797462
Update renovate.json
icfaust Aug 4, 2025
63004d4
Update renovate.json
icfaust Aug 4, 2025
3377625
Update test_common.py
icfaust Aug 4, 2025
9f043b3
Update test_common.py
icfaust Aug 4, 2025
59cb8aa
Update test_common.py
icfaust Aug 4, 2025
9cf320a
Update build-and-test-lnx.yml
icfaust Aug 4, 2025
2732fb4
Update build-and-test-lnx.yml
icfaust Aug 4, 2025
1eaff2d
Update ci.yml
icfaust Aug 4, 2025
ebfdafc
Update renovate.json
icfaust Aug 4, 2025
ce267f0
Update build-and-test-lnx.yml
icfaust Aug 4, 2025
804ca9a
Update test_common.py
icfaust Aug 4, 2025
e280c00
Update test_common.py
icfaust Aug 4, 2025
ea36d57
Update common.py
icfaust Aug 4, 2025
61f076c
Update coordinate_descent.py
icfaust Aug 4, 2025
e6cb93d
Update logistic_regression.py
icfaust Aug 4, 2025
5f11430
Update logistic_regression.py
icfaust Aug 4, 2025
b1690fd
Update _forest.py
icfaust Aug 4, 2025
0a1dcdf
Update _forest.py
icfaust Aug 4, 2025
59c6679
Merge branch 'uxlfoundation:main' into dev/dpnp_upgrade
icfaust Aug 4, 2025
ff19168
Update ci.yml
icfaust Aug 4, 2025
aa2ea77
Update ci.yml
icfaust Aug 4, 2025
a0cc908
Update _data_conversion.py
icfaust Aug 4, 2025
b6982e6
Update _data_conversion.py
icfaust Aug 4, 2025
e4284b5
Update _device_offload.py
icfaust Aug 5, 2025
b6a7b22
try again
icfaust Aug 5, 2025
0c2c67b
fixes
icfaust Aug 5, 2025
28ee59d
fix KMeans transform issue
icfaust Aug 5, 2025
9d17782
formatting
icfaust Aug 5, 2025
588d6a9
further fixes
icfaust Aug 5, 2025
0e196c7
Update install_dpcpp.sh
icfaust Aug 5, 2025
6f323a7
stopping point
icfaust Aug 6, 2025
1089047
Update ci.yml
icfaust Aug 6, 2025
54d57e6
face facts
icfaust Aug 6, 2025
e8b5805
clarification
icfaust Aug 6, 2025
6df4cfb
add renovatebot checks
icfaust Aug 6, 2025
5f54a90
switch source for dpctl/dpnp tracking
icfaust Aug 6, 2025
0a04bca
fix potential issue with nightly.yml
icfaust Aug 6, 2025
3b88e56
fix bug
icfaust Aug 6, 2025
a9d782e
forgotten save
icfaust Aug 6, 2025
80bdacf
try and fix CI
icfaust Aug 6, 2025
c87628f
fix method vs staticmethod issue
icfaust Aug 6, 2025
747c246
try again
icfaust Aug 6, 2025
029b32c
try again
icfaust Aug 6, 2025
e545af2
move validate_data
icfaust Aug 6, 2025
2f329c9
Update test_data.py
icfaust Aug 6, 2025
1852f69
reformat
icfaust Aug 6, 2025
3befd59
try again
icfaust Aug 6, 2025
0e1bda5
try to lazy import changes for dpnp
icfaust Aug 6, 2025
c80321d
try with the spelling correction
icfaust Aug 6, 2025
34f8a6f
try again
icfaust Aug 6, 2025
f7e5ee8
Update ci.yml
icfaust Aug 6, 2025
1ec6f73
Update _data_conversion.py
icfaust Aug 6, 2025
c0678b1
Update coordinate_descent.py
icfaust Aug 6, 2025
a25abbd
Update _data_conversion.py
icfaust Aug 6, 2025
ab327d2
Update coordinate_descent.py
icfaust Aug 6, 2025
94e5003
Update _data_conversion.py
icfaust Aug 6, 2025
9c9dfce
Update ci.yml
icfaust Aug 6, 2025
4ab6f5c
Update ci.yml
icfaust Aug 6, 2025
225122e
fix issues related to dpctl/dpnp
icfaust Aug 6, 2025
2666996
further fixes
icfaust Aug 6, 2025
791917f
final hopefully
icfaust Aug 7, 2025
f234744
sometime I can be really dumb
icfaust Aug 7, 2025
f86992e
sometime I can be really dumb
icfaust Aug 7, 2025
bc0be32
Update _device_offload.py
icfaust Aug 7, 2025
694026a
fixes and requests
icfaust Aug 7, 2025
928744d
Merge branch 'dev/dpnp_upgrade' of https://github.com/icfaust/scikit-…
icfaust Aug 7, 2025
548e7d3
try to fix spmd use_raw_input issues
icfaust Aug 7, 2025
bc72cd6
fixes and requests
icfaust Aug 7, 2025
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
9 changes: 5 additions & 4 deletions .ci/pipeline/build-and-test-lnx.yml
Original file line number Diff line number Diff line change
@@ -60,13 +60,14 @@ steps:
pip install --upgrade -r requirements-test.txt
pip install $(python .ci/scripts/get_compatible_scipy_version.py)
# dpep installation is set to pypi to avoid conflict of numpy versions from pip and conda
# py312 is disabled due to segfault on exit of program with usage of dpctl
if [ $(echo $(PYTHON_VERSION) | grep '3.9\|3.10\|3.11') ] && [ $(SKLEARN_VERSION) != "1.0" ] && [ -z ${NO_DPC} ]; then pip install dpctl==0.18.* dpnp==0.16.*; fi
# issues exist with conda-forge dpcpp-cpp-rt=2025.1.1 it is needed to use the dpc build
if [ -z "${NO_DPC}" ]; then pip install dpcpp-cpp-rt==2025.1.*; fi
if [ $(echo $(PYTHON_VERSION) | grep '3.9\|3.10\|3.11\|3.12') ] && [ $(SKLEARN_VERSION) != "1.0" ] && [ -z ${NO_DPC} ]; then pip install dpctl==$DPCTL_VERSION dpnp==$DPNP_VERSION; fi
if [ -z "${NO_DPC}" ]; then pip install dpcpp-cpp-rt==$DPCPP_RT_VERSION; fi
pip list
env:
NO_DPC: ${{ variables.NO_DPC }}
DPCTL_VERSION: 0.20.1
DPNP_VERSION: 0.18.1
DPCPP_RT_VERSION: 2025.2.0
displayName: "Install testing requirements"
- script: |
. /usr/share/miniconda/etc/profile.d/conda.sh
2 changes: 1 addition & 1 deletion .ci/scripts/install_dpcpp.sh
Original file line number Diff line number Diff line change
@@ -21,5 +21,5 @@ rm GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list
sudo add-apt-repository -y "deb https://apt.repos.intel.com/oneapi all main"
sudo apt-get update
sudo apt-get install -y intel-dpcpp-cpp-compiler-2025.1
sudo apt-get install -y intel-dpcpp-cpp-compiler-2025.2
sudo bash -c 'echo libintelocl.so > /etc/OpenCL/vendors/intel-cpu.icd'
37 changes: 36 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
@@ -20,5 +20,40 @@
],
"pre-commit": {
"enabled": true
}
},
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"^\\.ci/pipeline/build-and-test-lnx\\.yml$",
"^\\.github/workflows/ci\\.yml$"
],
"matchStrings": [
"\\s*DPCTL_VERSION: (?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"depNameTemplate": "dpctl",
"datasourceTemplate": "pypi"
},
{
"customType": "regex",
"fileMatch": [
"^\\.ci/pipeline/build-and-test-lnx\\.yml$",
"^\\.github/workflows/ci\\.yml$"
],
"matchStrings": [
"\\s*DPNP_VERSION: (?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"depNameTemplate": "dpnp",
"datasourceTemplate": "pypi"
},
{
"customType": "regex",
"fileMatch": ["^\\.ci/pipeline/build-and-test-lnx\\.yml$"],
"matchStrings": [
"\\s*DPCPP_RT_VERSION: (?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"depNameTemplate": "dpcpp-cpp-rt",
"datasourceTemplate": "pypi"
}
]
}
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -30,8 +30,8 @@ concurrency:
cancel-in-progress: true

env:
DPCTL_VERSION: 0.18.1
DPNP_VERSION: 0.16.0
DPCTL_VERSION: 0.20.1
DPNP_VERSION: 0.18.1
DPCTL_PY_VERSIONS: '3.9\|3.11'
UXL_PYTHONVERSION: "3.12"
UXL_SKLEARNVERSION: "1.4"
@@ -463,7 +463,7 @@ jobs:
- name: Install PyTorch
if: contains(matrix.FRAMEWORKS, 'pytorch')
run: |
pip install torch --index-url https://download.pytorch.org/whl/xpu
pip install torch --index-url https://download.pytorch.org/whl/cpu
python -c "import torch; _=[print(torch.xpu.get_device_name(i)) for i in range(torch.xpu.device_count())]"
- name: Install daal4py/sklearnex
run: pip install *.whl
51 changes: 38 additions & 13 deletions onedal/_device_offload.py
Original file line number Diff line number Diff line change
@@ -17,14 +17,15 @@
import inspect
import logging
from functools import wraps
from operator import xor

import numpy as np
from sklearn import get_config

from ._config import _get_config
from .datatypes import copy_to_dpnp, copy_to_usm, dlpack_to_numpy, usm_to_numpy
from .datatypes import copy_to_dpnp, copy_to_usm, dlpack_to_numpy
from .utils import _sycl_queue_manager as QM
from .utils._array_api import _asarray, _is_numpy_namespace
from .utils._array_api import _asarray, _get_sycl_namespace, _is_numpy_namespace
from .utils._third_party import is_dpnp_ndarray

logger = logging.getLogger("sklearnex")
@@ -62,23 +63,23 @@ def wrapper(self, *args, **kwargs):


def _transfer_to_host(*data):
has_usm_data, has_host_data = False, False
has_usm_data = None

host_data = []
for item in data:
if usm_iface := getattr(item, "__sycl_usm_array_interface__", None):
item = usm_to_numpy(item, usm_iface)
has_usm_data = True
if item is None:
host_data.append(item)
continue

if usm_iface := hasattr(item, "__sycl_usm_array_interface__"):
xp = item.__array_namespace__()
item = xp.asnumpy(item)
has_usm_data = has_usm_data or has_usm_data is None
elif not isinstance(item, np.ndarray) and (hasattr(item, "__dlpack_device__")):
item = dlpack_to_numpy(item)
has_host_data = True
else:
has_host_data = True

mismatch_host_item = usm_iface is None and item is not None and has_usm_data
mismatch_usm_item = usm_iface is not None and has_host_data

if mismatch_host_item or mismatch_usm_item:
# set has_usm_data to boolean and use xor to see if they don't match
if xor((has_usm_data := bool(has_usm_data)), usm_iface):
raise RuntimeError("Input data shall be located on single target device")

host_data.append(item)
@@ -171,3 +172,27 @@ def wrapper_impl(*args, **kwargs):
return result

return wrapper_impl


def support_sycl_format(func):
# This wrapper enables scikit-learn functions and methods to work with
# all sycl data frameworks as they no longer support numpy implicit
# conversion and must be manually converted. This is only necessary
# when array API is supported but not active.

@wraps(func)
def wrapper(*args, **kwargs):
if (
not get_config().get("array_api_dispatch", False)
and _get_sycl_namespace(*args)[2]
):
with QM.manage_global_queue(kwargs.get("queue"), *args):
if inspect.isfunction(func) and "." in func.__qualname__:
self, (args, kwargs) = args[0], _get_host_inputs(*args[1:], **kwargs)
return func(self, *args, **kwargs)
else:
args, kwargs = _get_host_inputs(*args, **kwargs)
return func(*args, **kwargs)
return func(*args, **kwargs)

return wrapper
3 changes: 1 addition & 2 deletions onedal/datatypes/__init__.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@

from ._data_conversion import from_table, return_type_constructor, to_table
from ._dlpack import dlpack_to_numpy, get_torch_queue
from ._sycl_usm import copy_to_dpnp, copy_to_usm, usm_to_numpy
from ._sycl_usm import copy_to_dpnp, copy_to_usm

__all__ = [
"copy_to_dpnp",
@@ -26,5 +26,4 @@
"get_torch_queue",
"to_table",
"return_type_constructor",
"usm_to_numpy",
]
11 changes: 6 additions & 5 deletions onedal/datatypes/_data_conversion.py
Original file line number Diff line number Diff line change
@@ -95,21 +95,22 @@ def return_type_constructor(array):
# prioritized: it provides finer-grained control of SYCL queues and the
# related SYCL devices which are generally unavailable via DLPack
# representations (such as SYCL contexts, SYCL sub-devices, etc.).
xp = array.__array_namespace__()
# array api support added in dpnp starting in 0.19, will fail for
# older versions
if is_dpctl_tensor(array):
xp = array.__array_namespace__()
func = lambda x: (
xp.asarray(x)
if hasattr(x, "__sycl_usm_array_interface__")
else xp.asarray(backend.from_table(x), device=device)
)
elif is_dpnp_ndarray(array):
xp = array._array_obj.__array_namespace__()
from_usm = array._create_from_usm_ndarray
func = lambda x: from_usm(
xp.asarray(x)
func = lambda x: (
xp.asarray(xp.as_usm_ndarray(x))
if hasattr(x, "__sycl_usm_array_interface__")
else xp.asarray(backend.from_table(x), device=device)
)

elif hasattr(array, "__array_namespace__"):
func = array.__array_namespace__().from_dlpack
else:
33 changes: 9 additions & 24 deletions onedal/datatypes/_sycl_usm.py
Original file line number Diff line number Diff line change
@@ -17,12 +17,13 @@
from collections.abc import Iterable

import numpy as np
import scipy.sparse as sp

from ..utils._third_party import lazy_import


@lazy_import("dpctl.memory", "dpctl.tensor")
def array_to_usm(memory, tensor, queue, array):
def _array_to_usm(memory, tensor, queue, array):
try:
mem = memory.MemoryUSMDevice(array.nbytes, queue=queue)
mem.copy_from_host(array.tobytes())
@@ -37,42 +38,26 @@ def array_to_usm(memory, tensor, queue, array):


@lazy_import("dpnp", "dpctl.tensor")
def to_dpnp(dpnp, tensor, array):
def _to_dpnp(dpnp, tensor, array):
if isinstance(array, tensor.usm_ndarray):
return dpnp.array(array, copy=False)
else:
return array


def copy_to_usm(queue, array):
if hasattr(array, "__array__"):
return array_to_usm(queue, array)
if hasattr(array, "tobytes"):
return _array_to_usm(queue, array)
else:
if isinstance(array, Iterable):
if isinstance(array, Iterable) and not sp.issparse(array):
array = [copy_to_usm(queue, i) for i in array]
return array


def copy_to_dpnp(queue, array):
if hasattr(array, "__array__"):
return to_dpnp(array_to_usm(queue, array))
if hasattr(array, "tobytes"):
return _to_dpnp(_array_to_usm(queue, array))
else:
if isinstance(array, Iterable):
if isinstance(array, Iterable) and not sp.issparse(array):
array = [copy_to_dpnp(queue, i) for i in array]
return array


@lazy_import("dpctl.memory")
def usm_to_numpy(memorymod, item, usm_iface):
buffer = memorymod.as_usm_memory(item).copy_to_host()
order = "C"
if usm_iface["strides"] is not None and len(usm_iface["strides"]) > 1:
if usm_iface["strides"][0] < usm_iface["strides"][1]:
order = "F"
item = np.ndarray(
shape=usm_iface["shape"],
dtype=usm_iface["typestr"],
buffer=buffer,
order=order,
)
return item
14 changes: 14 additions & 0 deletions onedal/tests/test_common.py
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@
import os
from glob import glob

from onedal.tests.utils._dataframes_support import test_frameworks


def _check_primitive_usage_ban(primitive_name, package, allowed_locations=None):
"""This test blocks the usage of the primitive in
@@ -55,3 +57,15 @@ def test_sklearn_check_version_ban():
# remove this file from the list
output = "\n".join([i for i in output if "test_common.py" not in i])
assert output == "", f"sklearn versioning is occurring in: \n{output}"


def test_frameworks_intentionality():
"""Only silent skip frameworks which are not installed"""
fmwks = test_frameworks.replace("array_api", "array_api_strict").split(",")
for module in fmwks:
try:
importlib.import_module(module)
# If a module isn't installed, working as intended.
# If an ImportError occurs, then something is wrong.
except ModuleNotFoundError:
pass
3 changes: 3 additions & 0 deletions onedal/utils/_array_api.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
from functools import lru_cache

import numpy as np
import scipy.sparse as sp

from ..utils._third_party import _is_subclass_fast

@@ -44,6 +45,8 @@ def _asarray(data, xp, *args, **kwargs):
for i in range(len(data)):
result_data.append(_asarray(data[i], xp, *args, **kwargs))
data = tuple(result_data)
elif sp.issparse(data):
pass
else:
for i in range(len(data)):
data[i] = _asarray(data[i], xp, *args, **kwargs)
6 changes: 4 additions & 2 deletions sklearnex/_device_offload.py
Original file line number Diff line number Diff line change
@@ -182,8 +182,10 @@ def wrapper(self, *args, **kwargs) -> Any:
result = func(self, *args, **kwargs)
if not (len(args) == 0 and len(kwargs) == 0):
data = (*args, *kwargs.values())[0]

if usm_iface := getattr(data, "__sycl_usm_array_interface__", None):
# Remove check for result __sycl_usm_array_interface__ on deprecation of use_raw_inputs
if (
usm_iface := getattr(data, "__sycl_usm_array_interface__", None)
) and not hasattr(result, "__sycl_usm_array_interface__"):
queue = usm_iface["syclobj"]
return (
copy_to_dpnp(queue, result)
1 change: 1 addition & 0 deletions sklearnex/cluster/k_means.py
Original file line number Diff line number Diff line change
@@ -334,6 +334,7 @@ def _check_test_data(self, X):
return X

_transform = support_input_format(_sklearn_KMeans._transform)
transform = support_input_format(_sklearn_KMeans.transform)
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this meant to replace the line before?

Copy link
Contributor Author

@icfaust icfaust Aug 7, 2025

Choose a reason for hiding this comment

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

Unfortunately no ☹️ . This is an ugly solution based on how things have been implemented before this, KMeans.transform has its validate_data in a separate function, and then calls a function KMeans._transform . It was done this way on their side to do fit_transform, allowing for the second valdiation to be removed. We reuse _transform in our fit_transform implementation too, so I can't remove it there, but I need make transform work. I couldn't swap in a dispatch because we don't accelerate anything there with oneDAL in either case. Direct use of KMeans.transform using dpctl 0.18 is likely to be extremely slow. In general this is because its not a high-prority method, which we technically support but in a very bad way/ without benchmarking.

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps that could be left as a comment for when it comes the time to refactor again.


@wrap_output_data
def score(self, X, y=None, sample_weight=None):
2 changes: 2 additions & 0 deletions sklearnex/covariance/incremental_covariance.py
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@

from daal4py.sklearn._n_jobs_support import control_n_jobs
from daal4py.sklearn._utils import daal_check_version, sklearn_check_version
from onedal._device_offload import support_sycl_format
from onedal.covariance import (
IncrementalEmpiricalCovariance as onedal_IncrementalEmpiricalCovariance,
)
@@ -231,6 +232,7 @@ def _onedal_partial_fit(self, X, queue=None, check_input=True):
return self

@wrap_output_data
@support_sycl_format
def score(self, X_test, y=None):
xp, _ = get_namespace(X_test)

6 changes: 6 additions & 0 deletions sklearnex/decomposition/pca.py
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@

from sklearn.decomposition import PCA as _sklearn_PCA

from onedal._device_offload import support_sycl_format
from onedal.decomposition import PCA as onedal_PCA
from onedal.utils._array_api import _is_numpy_namespace

@@ -110,6 +111,10 @@ def __init__(
self.iterated_power = iterated_power
self.random_state = random_state

# guarantee operability with dpnp/dpctl, runs on CPU unless
# array_api_dispatch is enabled.
score_samples = support_sycl_format(_sklearn_PCA.score_samples)

def fit(self, X, y=None):
self._fit(X)
return self
@@ -413,6 +418,7 @@ def _validate_n_features_in_after_fitting(self, X):
transform.__doc__ = _sklearn_PCA.transform.__doc__
fit_transform.__doc__ = _sklearn_PCA.fit_transform.__doc__
inverse_transform.__doc__ = _sklearn_PCA.inverse_transform.__doc__
score_samples.__doc__ = _sklearn_PCA.score_samples.__doc__

else:
from daal4py.sklearn.decomposition import PCA
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.