Skip to content

Commit a30b573

Browse files
Merge branch 'master' into feature/dlpack-kdlcpu-support
2 parents 2bd92df + ff0d4ea commit a30b573

File tree

24 files changed

+474
-86
lines changed

24 files changed

+474
-86
lines changed

.github/workflows/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @oleksandr-pavlyk @ndgrigorian

.github/workflows/conda-package.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ jobs:
6262
$CHANNELS \
6363
conda-recipe
6464
- name: Upload artifact
65-
uses: actions/[email protected].4
65+
uses: actions/[email protected].5
6666
with:
6767
name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
6868
path: /usr/share/miniconda/conda-bld/linux-64/${{ env.PACKAGE_NAME }}-*.tar.bz2
6969
- name: Upload wheels artifact
70-
uses: actions/[email protected].4
70+
uses: actions/[email protected].5
7171
with:
7272
name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python }}
7373
path: ${{ env.WHEELS_OUTPUT_FOLDER }}${{ env.PACKAGE_NAME }}-*.whl
@@ -111,12 +111,12 @@ jobs:
111111
OVERRIDE_INTEL_IPO: 1 # IPO requires more resources that GH actions VM provides
112112
run: conda build --no-test --python ${{ matrix.python }} -c dppy/label/bootstrap -c ${{ env.INTEL_CHANNEL }} -c conda-forge --override-channels conda-recipe
113113
- name: Upload artifact
114-
uses: actions/[email protected].4
114+
uses: actions/[email protected].5
115115
with:
116116
name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
117117
path: ${{ env.conda-bld }}${{ env.PACKAGE_NAME }}-*.tar.bz2
118118
- name: Upload wheels artifact
119-
uses: actions/[email protected].4
119+
uses: actions/[email protected].5
120120
with:
121121
name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Wheels Python ${{ matrix.python }}
122122
path: ${{ env.WHEELS_OUTPUT_FOLDER }}${{ env.PACKAGE_NAME }}-*.whl

.github/workflows/generate-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
git push tokened_docs gh-pages
105105
- name: Save built docs as an artifact
106106
if: ${{ github.event.pull_request && github.event.pull_request.head.repo.fork && github.event.action != 'closed'}}
107-
uses: actions/[email protected].4
107+
uses: actions/[email protected].5
108108
with:
109109
name: ${{ env.PACKAGE_NAME }} rendered documentation
110110
path: ~/docs

.github/workflows/openssf-scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
6161
# format to the repository Actions tab.
6262
- name: "Upload artifact"
63-
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
63+
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
6464
with:
6565
name: SARIF file
6666
path: results.sarif

conda-recipe/build.sh

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,6 @@ CMAKE_ARGS="${CMAKE_ARGS} -DDPCTL_LEVEL_ZERO_INCLUDE_DIR=${PREFIX}/include/level
2727
# -wnx flags mean: --wheel --no-isolation --skip-dependency-check
2828
${PYTHON} -m build -w -n -x
2929

30-
pushd dist
31-
${PYTHON} -m wheel unpack -d dpctl_wheel dpctl*.whl
32-
export lib_name=libDPCTLSyclInterface
33-
export so_full_path=$(find dpctl_wheel -regextype posix-extended -regex "^.*${lib_name}\.so")
34-
export sox_full_path=$(find dpctl_wheel -regextype posix-extended -regex "^.*${lib_name}\.so\.[0-9]*$")
35-
export soxxx_full_path=$(find dpctl_wheel -regextype posix-extended -regex "^.*${lib_name}\.so\.[0-9]*\.[0-9]*$")
36-
37-
rm -rf ${so_full_path} ${soxxx_full_path}
38-
39-
export so_name=$(basename ${so_full_path})
40-
export sox_name=$(basename ${sox_full_path})
41-
export soxxx_name=$(basename ${soxxx_full_path})
42-
export wheel_path=$(dirname $(dirname ${so_full_path}))
43-
44-
# deal with hard copies
45-
${PYTHON} -m wheel pack ${wheel_path}
46-
47-
rm -rf dpctl_wheel
48-
popd
49-
5030
${PYTHON} -m wheel tags --remove --build "$GIT_DESCRIBE_NUMBER" \
5131
--platform-tag "manylinux_${GLIBC_MAJOR}_${GLIBC_MINOR}_x86_64" \
5232
dist/dpctl*.whl
@@ -59,15 +39,6 @@ ${PYTHON} -m pip install dist/dpctl*.whl \
5939
--prefix "${PREFIX}" \
6040
-vv
6141

62-
export libdir=$(find $PREFIX -name 'libDPCTLSyclInterface*' -exec dirname \{\} \;)
63-
64-
# Recover symbolic links
65-
# libDPCTLSyclInterface.so.0 -> libDPCTLSyclInterface.so.0.17
66-
# libDPCTLSyclInterface.so -> libDPCTLSyclInterface.so.0
67-
mv ${libdir}/${sox_name} ${libdir}/${soxxx_name}
68-
ln -s ${libdir}/${soxxx_name} ${libdir}/${sox_name}
69-
ln -s ${libdir}/${sox_name} ${libdir}/${so_name}
70-
7142
# Copy wheel package
7243
if [[ -d "${WHEELS_OUTPUT_FOLDER}" ]]; then
7344
cp dist/dpctl*.whl "${WHEELS_OUTPUT_FOLDER[@]}"

docs/doc_sources/api_reference/dpctl/tensor.indexing_functions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ by either integral arrays of indices or boolean mask arrays.
1515
place
1616
put
1717
take
18+
take_along_axis

dpctl/apis/include/dpctl4pybind11.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "dpctl_capi.h"
2929
#include <complex>
30+
#include <exception>
3031
#include <memory>
3132
#include <pybind11/pybind11.h>
3233
#include <sycl/sycl.hpp>
@@ -748,6 +749,53 @@ class usm_memory : public py::object
748749
throw py::error_already_set();
749750
}
750751

752+
/*! @brief Create usm_memory object from shared pointer that manages
753+
* lifetime of the USM allocation.
754+
*/
755+
usm_memory(void *usm_ptr,
756+
size_t nbytes,
757+
const sycl::queue &q,
758+
std::shared_ptr<void> shptr)
759+
{
760+
auto const &api = ::dpctl::detail::dpctl_capi::get();
761+
DPCTLSyclUSMRef usm_ref = reinterpret_cast<DPCTLSyclUSMRef>(usm_ptr);
762+
auto q_uptr = std::make_unique<sycl::queue>(q);
763+
DPCTLSyclQueueRef QRef =
764+
reinterpret_cast<DPCTLSyclQueueRef>(q_uptr.get());
765+
766+
auto vacuous_destructor = []() {};
767+
py::capsule mock_owner(vacuous_destructor);
768+
769+
// create memory object owned by mock_owner, it is a new reference
770+
PyObject *_memory =
771+
api.Memory_Make_(usm_ref, nbytes, QRef, mock_owner.ptr());
772+
auto ref_count_decrementer = [](PyObject *o) noexcept { Py_DECREF(o); };
773+
774+
using py_uptrT =
775+
std::unique_ptr<PyObject, decltype(ref_count_decrementer)>;
776+
777+
if (!_memory) {
778+
throw py::error_already_set();
779+
}
780+
781+
auto memory_uptr = py_uptrT(_memory, ref_count_decrementer);
782+
std::shared_ptr<void> *opaque_ptr = new std::shared_ptr<void>(shptr);
783+
784+
Py_MemoryObject *memobj = reinterpret_cast<Py_MemoryObject *>(_memory);
785+
// replace mock_owner capsule as the owner
786+
memobj->refobj = Py_None;
787+
// set opaque ptr field, usm_memory now knowns that USM is managed
788+
// by smart pointer
789+
memobj->_opaque_ptr = reinterpret_cast<void *>(opaque_ptr);
790+
791+
// _memory will delete created copies of sycl::queue, and
792+
// std::shared_ptr and the deleter of the shared_ptr<void> is
793+
// supposed to free the USM allocation
794+
m_ptr = _memory;
795+
q_uptr.release();
796+
memory_uptr.release();
797+
}
798+
751799
sycl::queue get_queue() const
752800
{
753801
Py_MemoryObject *mem_obj = reinterpret_cast<Py_MemoryObject *>(m_ptr);

dpctl/memory/_opaque_smart_ptr.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "syclinterface/dpctl_sycl_types.h"
3535
#include <memory>
3636
#include <sycl/sycl.hpp>
37+
#include <utility>
3738

3839
#include <exception>
3940
#include <iostream>

dpctl/tensor/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@
6060
)
6161
from dpctl.tensor._device import Device
6262
from dpctl.tensor._dlpack import from_dlpack
63-
from dpctl.tensor._indexing_functions import extract, nonzero, place, put, take
63+
from dpctl.tensor._indexing_functions import (
64+
extract,
65+
nonzero,
66+
place,
67+
put,
68+
take,
69+
take_along_axis,
70+
)
6471
from dpctl.tensor._linear_algebra_functions import (
6572
matmul,
6673
matrix_transpose,
@@ -377,4 +384,5 @@
377384
"diff",
378385
"count_nonzero",
379386
"DLDeviceType",
387+
"take_along_axis",
380388
]

dpctl/tensor/_copy_utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -795,13 +795,18 @@ def _nonzero_impl(ary):
795795
return res
796796

797797

798-
def _take_multi_index(ary, inds, p):
798+
def _take_multi_index(ary, inds, p, mode=0):
799799
if not isinstance(ary, dpt.usm_ndarray):
800800
raise TypeError(
801801
f"Expecting type dpctl.tensor.usm_ndarray, got {type(ary)}"
802802
)
803803
ary_nd = ary.ndim
804804
p = normalize_axis_index(operator.index(p), ary_nd)
805+
mode = operator.index(mode)
806+
if mode not in [0, 1]:
807+
raise ValueError(
808+
"Invalid value for mode keyword, only 0 or 1 is supported"
809+
)
805810
queues_ = [
806811
ary.sycl_queue,
807812
]
@@ -860,7 +865,7 @@ def _take_multi_index(ary, inds, p):
860865
ind=inds,
861866
dst=res,
862867
axis_start=p,
863-
mode=0,
868+
mode=mode,
864869
sycl_queue=exec_q,
865870
depends=dep_ev,
866871
)

dpctl/tensor/_indexing_functions.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import dpctl.tensor._tensor_impl as ti
2222
import dpctl.utils
2323

24-
from ._copy_utils import _extract_impl, _nonzero_impl
24+
from ._copy_utils import _extract_impl, _nonzero_impl, _take_multi_index
2525
from ._numpy_helper import normalize_axis_index
2626

2727

@@ -423,3 +423,82 @@ def nonzero(arr):
423423
if arr.ndim == 0:
424424
raise ValueError("Array of positive rank is expected")
425425
return _nonzero_impl(arr)
426+
427+
428+
def _range(sh_i, i, nd, q, usm_t, dt):
429+
ind = dpt.arange(sh_i, dtype=dt, usm_type=usm_t, sycl_queue=q)
430+
ind.shape = tuple(sh_i if i == j else 1 for j in range(nd))
431+
return ind
432+
433+
434+
def take_along_axis(x, indices, /, *, axis=-1, mode="wrap"):
435+
"""
436+
Returns elements from an array at the one-dimensional indices specified
437+
by ``indices`` along a provided ``axis``.
438+
439+
Args:
440+
x (usm_ndarray):
441+
input array. Must be compatible with ``indices``, except for the
442+
axis (dimension) specified by ``axis``.
443+
indices (usm_ndarray):
444+
array indices. Must have the same rank (i.e., number of dimensions)
445+
as ``x``.
446+
axis: int
447+
axis along which to select values. If ``axis`` is negative, the
448+
function determines the axis along which to select values by
449+
counting from the last dimension. Default: ``-1``.
450+
mode (str, optional):
451+
How out-of-bounds indices will be handled. Possible values
452+
are:
453+
454+
- ``"wrap"``: clamps indices to (``-n <= i < n``), then wraps
455+
negative indices.
456+
- ``"clip"``: clips indices to (``0 <= i < n``).
457+
458+
Default: ``"wrap"``.
459+
460+
Returns:
461+
usm_ndarray:
462+
an array having the same data type as ``x``. The returned array has
463+
the same rank (i.e., number of dimensions) as ``x`` and a shape
464+
determined according to :ref:`broadcasting`, except for the axis
465+
(dimension) specified by ``axis`` whose size must equal the size
466+
of the corresponding axis (dimension) in ``indices``.
467+
468+
Note:
469+
Treatment of the out-of-bound indices in ``indices`` array is controlled
470+
by the value of ``mode`` keyword.
471+
"""
472+
if not isinstance(x, dpt.usm_ndarray):
473+
raise TypeError(f"Expected dpctl.tensor.usm_ndarray, got {type(x)}")
474+
if not isinstance(indices, dpt.usm_ndarray):
475+
raise TypeError(
476+
f"Expected dpctl.tensor.usm_ndarray, got {type(indices)}"
477+
)
478+
x_nd = x.ndim
479+
if x_nd != indices.ndim:
480+
raise ValueError(
481+
"Number of dimensions in the first and the second "
482+
"argument arrays must be equal"
483+
)
484+
pp = normalize_axis_index(operator.index(axis), x_nd)
485+
out_usm_type = dpctl.utils.get_coerced_usm_type(
486+
(x.usm_type, indices.usm_type)
487+
)
488+
exec_q = dpctl.utils.get_execution_queue((x.sycl_queue, indices.sycl_queue))
489+
if exec_q is None:
490+
raise dpctl.utils.ExecutionPlacementError(
491+
"Execution placement can not be unambiguously inferred "
492+
"from input arguments. "
493+
)
494+
mode_i = _get_indexing_mode(mode)
495+
indexes_dt = ti.default_device_index_type(exec_q.sycl_device)
496+
_ind = tuple(
497+
(
498+
indices
499+
if i == pp
500+
else _range(x.shape[i], i, x_nd, exec_q, out_usm_type, indexes_dt)
501+
)
502+
for i in range(x_nd)
503+
)
504+
return _take_multi_index(x, _ind, 0, mode=mode_i)

dpctl/tensor/_usmarray.pyx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,22 @@ cdef class usm_ndarray:
621621

622622
@shape.setter
623623
def shape(self, new_shape):
624+
"""
625+
Modifies usm_ndarray instance in-place by changing its metadata
626+
about the shape and the strides of the array, or raises
627+
`AttributeError` exception if in-place change is not possible.
628+
629+
Args:
630+
new_shape: (tuple, int)
631+
New shape. Only non-negative values are supported.
632+
The new shape may not lead to the change in the
633+
number of elements in the array.
634+
635+
Whether the array can be reshape in-place depends on its
636+
strides. Use :func:`dpctl.tensor.reshape` function which
637+
always succeeds to reshape the array by performing a copy
638+
if necessary.
639+
"""
624640
cdef int new_nd = -1
625641
cdef Py_ssize_t nelems = -1
626642
cdef int err = 0
@@ -634,7 +650,11 @@ cdef class usm_ndarray:
634650

635651
from ._reshape import reshaped_strides
636652

637-
new_nd = len(new_shape)
653+
try:
654+
new_nd = len(new_shape)
655+
except TypeError:
656+
new_nd = 1
657+
new_shape = (new_shape,)
638658
try:
639659
new_shape = tuple(operator.index(dim) for dim in new_shape)
640660
except TypeError:

dpctl/tensor/_utility_functions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ def diff(x, /, *, axis=-1, n=1, prepend=None, append=None):
453453
x_nd = x.ndim
454454
axis = normalize_axis_index(operator.index(axis), x_nd)
455455
n = operator.index(n)
456-
456+
if n < 0:
457+
raise ValueError(f"`n` must be positive, got {n}")
457458
arr = _concat_diff_input(x, axis, prepend, append)
458459
if n == 0:
459460
return arr

dpctl/tests/test_service.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,11 @@ def test_syclinterface(should_skip_syclinterface):
163163
if should_skip_syclinterface:
164164
pass
165165
else:
166-
assert len(paths) > 1 and any(
167-
[os.path.islink(fn) for fn in paths]
168-
), "All library instances are hard links"
166+
len_p = len(paths)
167+
n_symlinks = sum([int(os.path.islink(fn)) for fn in paths])
168+
assert len_p == 1 + n_symlinks, (
169+
"Multiple library instances are hard links",
170+
)
169171
elif sys.platform in ["win32", "cygwin"]:
170172
exts = []
171173
for fn in paths:

dpctl/tests/test_tensor_diff.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,17 @@ def test_diff_input_validation():
313313
dpt.diff,
314314
bad_in,
315315
)
316+
317+
318+
def test_diff_positive_order():
319+
get_queue_or_skip()
320+
321+
x = dpt.ones(1, dtype="i4")
322+
n = -1
323+
assert_raises_regex(
324+
ValueError,
325+
".*must be positive.*",
326+
dpt.diff,
327+
x,
328+
n=n,
329+
)

0 commit comments

Comments
 (0)