• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IntelPython / dpctl / 14537256782

18 Apr 2025 03:10PM UTC coverage: 86.41% (+0.001%) from 86.409%
14537256782

Pull #2056

github

web-flow
Merge f63bdbb79 into f57963e87
Pull Request #2056: extend pre-commit hooks with cython-lint

3014 of 3710 branches covered (81.24%)

Branch coverage included in aggregate %.

205 of 263 new or added lines in 18 files covered. (77.95%)

4 existing lines in 3 files now uncovered.

12189 of 13884 relevant lines covered (87.79%)

7003.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.69
/dpctl/tensor/_dlpack.pyx
1
#                       Data Parallel Control (dpctl)
1✔
2
#
3
#  Copyright 2020-2025 Intel Corporation
4
#
5
#  Licensed under the Apache License, Version 2.0 (the "License");
6
#  you may not use this file except in compliance with the License.
7
#  You may obtain a copy of the License at
8
#
9
#     http://www.apache.org/licenses/LICENSE-2.0
10
#
11
#  Unless required by applicable law or agreed to in writing, software
12
#  distributed under the License is distributed on an "AS IS" BASIS,
13
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
#  See the License for the specific language governing permissions and
15
#  limitations under the License.
16

17
# distutils: language = c++
18
# cython: language_level=3
19
# cython: linetrace=True
20

21
cdef extern from "numpy/npy_no_deprecated_api.h":
22
    pass
23

24
cimport cpython
25
from libc cimport stdlib
26
from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t, uint64_t
27
from numpy cimport ndarray
28

29
cimport dpctl as c_dpctl
30
cimport dpctl.memory as c_dpmem
31
from dpctl._sycl_queue_manager cimport get_device_cached_queue
32

33
from .._backend cimport (
34
    DPCTLDevice_Delete,
35
    DPCTLDevice_GetParentDevice,
36
    DPCTLSyclDeviceRef,
37
    DPCTLSyclUSMRef,
38
)
39
from ._usmarray cimport USM_ARRAY_C_CONTIGUOUS, USM_ARRAY_WRITABLE, usm_ndarray
40

41
import ctypes
1✔
42

43
import numpy as np
1✔
44

45
import dpctl
1✔
46
import dpctl.memory as dpmem
1✔
47

48
from ._device import Device
1✔
49

50

51
cdef extern from "dlpack/dlpack.h" nogil:
52
    cdef int DLPACK_MAJOR_VERSION
53

54
    cdef int DLPACK_MINOR_VERSION
55

56
    cdef int DLPACK_FLAG_BITMASK_READ_ONLY
57

58
    cdef int DLPACK_FLAG_BITMASK_IS_COPIED
59

60
    ctypedef struct DLPackVersion:
61
        uint32_t major
62
        uint32_t minor
63

64
    cdef enum DLDeviceType:
65
        kDLCPU
66
        kDLCUDA
67
        kDLCUDAHost
68
        kDLCUDAManaged
69
        kDLROCM
70
        kDLROCMHost
71
        kDLOpenCL
72
        kDLVulkan
73
        kDLMetal
74
        kDLVPI
75
        kDLOneAPI
76
        kDLWebGPU
77
        kDLHexagon
78
        kDLMAIA
79

80
    ctypedef struct DLDevice:
81
        DLDeviceType device_type
82
        int device_id
83

84
    cdef enum DLDataTypeCode:
85
        kDLInt
86
        kDLUInt
87
        kDLFloat
88
        kDLBfloat
89
        kDLComplex
90
        kDLBool
91

92
    ctypedef struct DLDataType:
93
        uint8_t code
94
        uint8_t bits
95
        uint16_t lanes
96

97
    ctypedef struct DLTensor:
98
        void *data
99
        DLDevice device
100
        int ndim
101
        DLDataType dtype
102
        int64_t *shape
103
        int64_t *strides
104
        uint64_t byte_offset
105

106
    ctypedef struct DLManagedTensor:
107
        DLTensor dl_tensor
108
        void *manager_ctx
109
        void (*deleter)(DLManagedTensor *)  # noqa: E211
110

111
    ctypedef struct DLManagedTensorVersioned:
112
        DLPackVersion version
113
        void *manager_ctx
114
        void (*deleter)(DLManagedTensorVersioned *)  # noqa: E211
115
        uint64_t flags
116
        DLTensor dl_tensor
117

118

119
def get_build_dlpack_version():
1✔
120
    """
121
    Returns a tuple of integers representing the `major` and `minor`
122
    version of DLPack :module:`dpctl.tensor` was built with.
123
    This tuple can be passed as the `max_version` argument to
124
    `__dlpack__` to guarantee module:`dpctl.tensor` can properly
125
    consume capsule.
126

127
    Returns:
128
        Tuple[int, int]
129
            A tuple of integers representing the `major` and `minor`
130
            version of DLPack used to build :module:`dpctl.tensor`.
131
    """
132
    return (DLPACK_MAJOR_VERSION, DLPACK_MINOR_VERSION)
1✔
133

134

135
cdef void _pycapsule_deleter(object dlt_capsule) noexcept:
1✔
136
    cdef DLManagedTensor *dlm_tensor = NULL
1✔
137
    if cpython.PyCapsule_IsValid(dlt_capsule, "dltensor"):
1✔
138
        dlm_tensor = <DLManagedTensor*>cpython.PyCapsule_GetPointer(
1✔
139
            dlt_capsule, "dltensor")
140
        dlm_tensor.deleter(dlm_tensor)
1✔
141

142

143
cdef void _managed_tensor_deleter(
1✔
144
    DLManagedTensor *dlm_tensor
145
) noexcept with gil:
146
    if dlm_tensor is not NULL:
1✔
147
        # we only delete shape, because we make single allocation to
148
        # acommodate both shape and strides if strides are needed
149
        stdlib.free(dlm_tensor.dl_tensor.shape)
1✔
150
        cpython.Py_DECREF(<object>dlm_tensor.manager_ctx)
1✔
151
        dlm_tensor.manager_ctx = NULL
1✔
152
        stdlib.free(dlm_tensor)
1✔
153

154

155
cdef void _pycapsule_versioned_deleter(object dlt_capsule) noexcept:
1✔
156
    cdef DLManagedTensorVersioned *dlmv_tensor = NULL
1✔
157
    if cpython.PyCapsule_IsValid(dlt_capsule, "dltensor_versioned"):
1✔
158
        dlmv_tensor = <DLManagedTensorVersioned*>cpython.PyCapsule_GetPointer(
1✔
159
            dlt_capsule, "dltensor_versioned")
160
        dlmv_tensor.deleter(dlmv_tensor)
1✔
161

162

163
cdef void _managed_tensor_versioned_deleter(
1✔
164
    DLManagedTensorVersioned *dlmv_tensor
165
) noexcept with gil:
166
    if dlmv_tensor is not NULL:
1✔
167
        # we only delete shape, because we make single allocation to
168
        # acommodate both shape and strides if strides are needed
169
        stdlib.free(dlmv_tensor.dl_tensor.shape)
1✔
170
        cpython.Py_DECREF(<object>dlmv_tensor.manager_ctx)
1✔
171
        dlmv_tensor.manager_ctx = NULL
1✔
172
        stdlib.free(dlmv_tensor)
1✔
173

174

175
cdef object _get_default_context(c_dpctl.SyclDevice dev):
1✔
176
    try:
1✔
177
        default_context = dev.sycl_platform.default_context
1✔
178
    except RuntimeError:
×
179
        # RT does not support default_context
180
        default_context = None
×
181

182
    return default_context
1✔
183

184
cdef int get_array_dlpack_device_id(
1✔
185
    usm_ndarray usm_ary
186
) except -1:
187
    """Finds ordinal number of the parent of device where array
188
    was allocated.
189
    """
190
    cdef c_dpctl.SyclQueue ary_sycl_queue
191
    cdef c_dpctl.SyclDevice ary_sycl_device
192
    cdef DPCTLSyclDeviceRef pDRef = NULL
1✔
193
    cdef int device_id = -1
1✔
194

195
    ary_sycl_queue = usm_ary.get_sycl_queue()
1✔
196
    ary_sycl_device = ary_sycl_queue.get_sycl_device()
1✔
197

198
    default_context = _get_default_context(ary_sycl_device)
1✔
199
    if default_context is None:
1✔
200
        # check that ary_sycl_device is a non-partitioned device
201
        pDRef = DPCTLDevice_GetParentDevice(ary_sycl_device.get_device_ref())
×
202
        if pDRef is not NULL:
×
203
            DPCTLDevice_Delete(pDRef)
×
204
            raise DLPackCreationError(
×
205
                "to_dlpack_capsule: DLPack can only export arrays allocated "
206
                "on non-partitioned SYCL devices on platforms where "
207
                "default_context oneAPI extension is not supported."
208
            )
209
    else:
210
        if not usm_ary.sycl_context == default_context:
1✔
211
            raise DLPackCreationError(
×
212
                "to_dlpack_capsule: DLPack can only export arrays based on USM "
213
                "allocations bound to a default platform SYCL context"
214
            )
215
    device_id = ary_sycl_device.get_device_id()
1✔
216

217
    if device_id < 0:
1✔
218
        raise DLPackCreationError(
×
219
            "get_array_dlpack_device_id: failed to determine device_id"
220
        )
221

222
    return device_id
1✔
223

224

225
cpdef to_dlpack_capsule(usm_ndarray usm_ary):
1✔
226
    """
227
    to_dlpack_capsule(usm_ary)
228

229
    Constructs named Python capsule object referencing
230
    instance of ``DLManagedTensor`` from
231
    :class:`dpctl.tensor.usm_ndarray` instance.
232

233
    Args:
234
        usm_ary: An instance of :class:`dpctl.tensor.usm_ndarray`
235
    Returns:
236
        A new capsule with name ``"dltensor"`` that contains
237
        a pointer to ``DLManagedTensor`` struct.
238
    Raises:
239
        DLPackCreationError: when array can be represented as
240
            DLPack tensor. This may happen when array was allocated
241
            on a partitioned sycl device, or its USM allocation is
242
            not bound to the platform default SYCL context.
243
        MemoryError: when host allocation to needed for ``DLManagedTensor``
244
            did not succeed.
245
        ValueError: when array elements data type could not be represented
246
            in ``DLManagedTensor``.
247
    """
248
    cdef DLManagedTensor *dlm_tensor = NULL
1✔
249
    cdef DLTensor *dl_tensor = NULL
1✔
250
    cdef int nd = usm_ary.get_ndim()
1✔
251
    cdef char *data_ptr = usm_ary.get_data()
1✔
252
    cdef Py_ssize_t *shape_ptr = NULL
1✔
253
    cdef Py_ssize_t *strides_ptr = NULL
1✔
254
    cdef int64_t *shape_strides_ptr = NULL
1✔
255
    cdef int i = 0
1✔
256
    cdef int device_id = -1
1✔
257
    cdef int flags = 0
1✔
258
    cdef Py_ssize_t element_offset = 0
1✔
259
    cdef Py_ssize_t byte_offset = 0
1✔
260
    cdef Py_ssize_t si = 1
1✔
261

262
    ary_base = usm_ary.get_base()
1✔
263

264
    device_id = get_array_dlpack_device_id(usm_ary)
1✔
265

266
    dlm_tensor = <DLManagedTensor *> stdlib.malloc(
1✔
267
        sizeof(DLManagedTensor))
268
    if dlm_tensor is NULL:
1✔
269
        raise MemoryError(
×
270
            "to_dlpack_capsule: Could not allocate memory for DLManagedTensor"
271
        )
272
    shape_strides_ptr = <int64_t *>stdlib.malloc((sizeof(int64_t) * 2) * nd)
1✔
273
    if shape_strides_ptr is NULL:
1✔
274
        stdlib.free(dlm_tensor)
×
275
        raise MemoryError(
×
276
            "to_dlpack_capsule: Could not allocate memory for shape/strides"
277
        )
278
    shape_ptr = usm_ary.get_shape()
1✔
279
    for i in range(nd):
1✔
280
        shape_strides_ptr[i] = shape_ptr[i]
1✔
281
    strides_ptr = usm_ary.get_strides()
1✔
282
    flags = usm_ary.flags_
1✔
283
    if strides_ptr:
1✔
284
        for i in range(nd):
1✔
285
            shape_strides_ptr[nd + i] = strides_ptr[i]
1✔
286
    else:
287
        if not (flags & USM_ARRAY_C_CONTIGUOUS):
1✔
288
            si = 1
1✔
289
            for i in range(0, nd):
1✔
290
                shape_strides_ptr[nd + i] = si
1✔
291
                si = si * shape_ptr[i]
1✔
292
            strides_ptr = <Py_ssize_t *>&shape_strides_ptr[nd]
1✔
293

294
    ary_dt = usm_ary.dtype
1✔
295
    ary_dtk = ary_dt.kind
1✔
296
    element_offset = usm_ary.get_offset()
1✔
297
    byte_offset = element_offset * (<Py_ssize_t>ary_dt.itemsize)
1✔
298

299
    dl_tensor = &dlm_tensor.dl_tensor
1✔
300
    dl_tensor.data = <void*>(data_ptr - byte_offset)
1✔
301
    dl_tensor.ndim = nd
1✔
302
    dl_tensor.byte_offset = <uint64_t>byte_offset
1✔
303
    dl_tensor.shape = &shape_strides_ptr[0]
1✔
304
    if strides_ptr is NULL:
1✔
305
        dl_tensor.strides = NULL
1✔
306
    else:
307
        dl_tensor.strides = &shape_strides_ptr[nd]
1✔
308
    dl_tensor.device.device_type = kDLOneAPI
1✔
309
    dl_tensor.device.device_id = device_id
1✔
310
    dl_tensor.dtype.lanes = <uint16_t>1
1✔
311
    dl_tensor.dtype.bits = <uint8_t>(ary_dt.itemsize * 8)
1✔
312
    if (ary_dtk == "b"):
1✔
313
        dl_tensor.dtype.code = <uint8_t>kDLBool
1✔
314
    elif (ary_dtk == "u"):
1✔
315
        dl_tensor.dtype.code = <uint8_t>kDLUInt
1✔
316
    elif (ary_dtk == "i"):
1✔
317
        dl_tensor.dtype.code = <uint8_t>kDLInt
1✔
318
    elif (ary_dtk == "f"):
1✔
319
        dl_tensor.dtype.code = <uint8_t>kDLFloat
1✔
320
    elif (ary_dtk == "c"):
1✔
321
        dl_tensor.dtype.code = <uint8_t>kDLComplex
1✔
322
    else:
323
        stdlib.free(shape_strides_ptr)
×
324
        stdlib.free(dlm_tensor)
×
325
        raise ValueError("Unrecognized array data type")
×
326

327
    dlm_tensor.manager_ctx = <void*>ary_base
1✔
328
    cpython.Py_INCREF(ary_base)
1✔
329
    dlm_tensor.deleter = _managed_tensor_deleter
1✔
330

331
    return cpython.PyCapsule_New(dlm_tensor, "dltensor", _pycapsule_deleter)
1✔
332

333

334
cpdef to_dlpack_versioned_capsule(usm_ndarray usm_ary, bint copied):
1✔
335
    """
336
    to_dlpack_versioned_capsule(usm_ary, copied)
337

338
    Constructs named Python capsule object referencing
339
    instance of ``DLManagedTensorVersioned`` from
340
    :class:`dpctl.tensor.usm_ndarray` instance.
341

342
    Args:
343
        usm_ary: An instance of :class:`dpctl.tensor.usm_ndarray`
344
        copied: A bint representing whether the data was previously
345
            copied in order to set the flags with the is-copied
346
            bitmask.
347
    Returns:
348
        A new capsule with name ``"dltensor_versioned"`` that
349
        contains a pointer to ``DLManagedTensorVersioned`` struct.
350
    Raises:
351
        DLPackCreationError: when array can be represented as
352
            DLPack tensor. This may happen when array was allocated
353
            on a partitioned sycl device, or its USM allocation is
354
            not bound to the platform default SYCL context.
355
        MemoryError: when host allocation to needed for
356
            ``DLManagedTensorVersioned`` did not succeed.
357
        ValueError: when array elements data type could not be represented
358
            in ``DLManagedTensorVersioned``.
359
    """
360
    cdef DLManagedTensorVersioned *dlmv_tensor = NULL
1✔
361
    cdef DLTensor *dl_tensor = NULL
1✔
362
    cdef uint32_t dlmv_flags = 0
1✔
363
    cdef int nd = usm_ary.get_ndim()
1✔
364
    cdef char *data_ptr = usm_ary.get_data()
1✔
365
    cdef Py_ssize_t *shape_ptr = NULL
1✔
366
    cdef Py_ssize_t *strides_ptr = NULL
1✔
367
    cdef int64_t *shape_strides_ptr = NULL
1✔
368
    cdef int i = 0
1✔
369
    cdef int device_id = -1
1✔
370
    cdef int flags = 0
1✔
371
    cdef Py_ssize_t element_offset = 0
1✔
372
    cdef Py_ssize_t byte_offset = 0
1✔
373
    cdef Py_ssize_t si = 1
1✔
374

375
    ary_base = usm_ary.get_base()
1✔
376

377
    # Find ordinal number of the parent device
378
    device_id = get_array_dlpack_device_id(usm_ary)
1✔
379

380
    dlmv_tensor = <DLManagedTensorVersioned *> stdlib.malloc(
1✔
381
        sizeof(DLManagedTensorVersioned))
382
    if dlmv_tensor is NULL:
1✔
383
        raise MemoryError(
×
384
            "to_dlpack_versioned_capsule: Could not allocate memory "
385
            "for DLManagedTensorVersioned"
386
        )
387
    shape_strides_ptr = <int64_t *>stdlib.malloc((sizeof(int64_t) * 2) * nd)
1✔
388
    if shape_strides_ptr is NULL:
1✔
389
        stdlib.free(dlmv_tensor)
×
390
        raise MemoryError(
×
391
            "to_dlpack_versioned_capsule: Could not allocate memory "
392
            "for shape/strides"
393
        )
394
    # this can be a separate function for handling shapes and strides
395
    shape_ptr = usm_ary.get_shape()
1✔
396
    for i in range(nd):
1✔
397
        shape_strides_ptr[i] = shape_ptr[i]
1✔
398
    strides_ptr = usm_ary.get_strides()
1✔
399
    flags = usm_ary.flags_
1✔
400
    if strides_ptr:
1✔
401
        for i in range(nd):
1✔
402
            shape_strides_ptr[nd + i] = strides_ptr[i]
1✔
403
    else:
404
        if not (flags & USM_ARRAY_C_CONTIGUOUS):
1✔
405
            si = 1
1✔
406
            for i in range(0, nd):
1✔
407
                shape_strides_ptr[nd + i] = si
1✔
408
                si = si * shape_ptr[i]
1✔
409
            strides_ptr = <Py_ssize_t *>&shape_strides_ptr[nd]
1✔
410

411
    # this can all be a function for building the dl_tensor
412
    # object (separate from dlm/dlmv)
413
    ary_dt = usm_ary.dtype
1✔
414
    ary_dtk = ary_dt.kind
1✔
415
    element_offset = usm_ary.get_offset()
1✔
416
    byte_offset = element_offset * (<Py_ssize_t>ary_dt.itemsize)
1✔
417

418
    dl_tensor = &dlmv_tensor.dl_tensor
1✔
419
    dl_tensor.data = <void*>(data_ptr - byte_offset)
1✔
420
    dl_tensor.ndim = nd
1✔
421
    dl_tensor.byte_offset = <uint64_t>byte_offset
1✔
422
    dl_tensor.shape = &shape_strides_ptr[0]
1✔
423
    if strides_ptr is NULL:
1✔
424
        dl_tensor.strides = NULL
1✔
425
    else:
426
        dl_tensor.strides = &shape_strides_ptr[nd]
1✔
427
    dl_tensor.device.device_type = kDLOneAPI
1✔
428
    dl_tensor.device.device_id = device_id
1✔
429
    dl_tensor.dtype.lanes = <uint16_t>1
1✔
430
    dl_tensor.dtype.bits = <uint8_t>(ary_dt.itemsize * 8)
1✔
431
    if (ary_dtk == "b"):
1✔
432
        dl_tensor.dtype.code = <uint8_t>kDLBool
1✔
433
    elif (ary_dtk == "u"):
1✔
434
        dl_tensor.dtype.code = <uint8_t>kDLUInt
1✔
435
    elif (ary_dtk == "i"):
1✔
436
        dl_tensor.dtype.code = <uint8_t>kDLInt
1✔
437
    elif (ary_dtk == "f"):
1✔
438
        dl_tensor.dtype.code = <uint8_t>kDLFloat
1✔
439
    elif (ary_dtk == "c"):
1✔
440
        dl_tensor.dtype.code = <uint8_t>kDLComplex
1✔
441
    else:
442
        stdlib.free(shape_strides_ptr)
×
443
        stdlib.free(dlmv_tensor)
×
444
        raise ValueError("Unrecognized array data type")
×
445

446
    # set flags down here
447
    if copied:
1✔
448
        dlmv_flags |= DLPACK_FLAG_BITMASK_IS_COPIED
1✔
449
    if not (flags & USM_ARRAY_WRITABLE):
1✔
450
        dlmv_flags |= DLPACK_FLAG_BITMASK_READ_ONLY
1✔
451
    dlmv_tensor.flags = dlmv_flags
1✔
452

453
    dlmv_tensor.version.major = DLPACK_MAJOR_VERSION
1✔
454
    dlmv_tensor.version.minor = DLPACK_MINOR_VERSION
1✔
455

456
    dlmv_tensor.manager_ctx = <void*>ary_base
1✔
457
    cpython.Py_INCREF(ary_base)
1✔
458
    dlmv_tensor.deleter = _managed_tensor_versioned_deleter
1✔
459

460
    return cpython.PyCapsule_New(
1✔
461
        dlmv_tensor, "dltensor_versioned", _pycapsule_versioned_deleter
1✔
462
    )
463

464

465
cpdef numpy_to_dlpack_versioned_capsule(ndarray npy_ary, bint copied):
1✔
466
    """
467
    to_dlpack_versioned_capsule(npy_ary, copied)
468

469
    Constructs named Python capsule object referencing
470
    instance of ``DLManagedTensorVersioned`` from
471
    :class:`numpy.ndarray` instance.
472

473
    Args:
474
        npy_ary: An instance of :class:`numpy.ndarray`
475
        copied: A bint representing whether the data was previously
476
            copied in order to set the flags with the is-copied
477
            bitmask.
478
    Returns:
479
        A new capsule with name ``"dltensor_versioned"`` that
480
        contains a pointer to ``DLManagedTensorVersioned`` struct.
481
    Raises:
482
        DLPackCreationError: when array can be represented as
483
            DLPack tensor.
484
        MemoryError: when host allocation to needed for
485
            ``DLManagedTensorVersioned`` did not succeed.
486
        ValueError: when array elements data type could not be represented
487
            in ``DLManagedTensorVersioned``.
488
    """
489
    cdef DLManagedTensorVersioned *dlmv_tensor = NULL
1✔
490
    cdef DLTensor *dl_tensor = NULL
1✔
491
    cdef uint32_t dlmv_flags = 0
1✔
492
    cdef int nd = npy_ary.ndim
1✔
493
    cdef int64_t *shape_strides_ptr = NULL
1✔
494
    cdef int i = 0
1✔
495
    cdef Py_ssize_t byte_offset = 0
1✔
496
    cdef int itemsize = npy_ary.itemsize
1✔
497

498
    dlmv_tensor = <DLManagedTensorVersioned *> stdlib.malloc(
1✔
499
        sizeof(DLManagedTensorVersioned))
500
    if dlmv_tensor is NULL:
1✔
501
        raise MemoryError(
×
502
            "numpy_to_dlpack_versioned_capsule: Could not allocate memory "
503
            "for DLManagedTensorVersioned"
504
        )
505

506
    is_c_contiguous = npy_ary.flags["C"]
1✔
507
    shape = npy_ary.ctypes.shape_as(ctypes.c_int64)
1✔
508
    strides = npy_ary.ctypes.strides_as(ctypes.c_int64)
1✔
509
    if not is_c_contiguous:
1✔
510
        if npy_ary.size != 1:
1✔
511
            for i in range(nd):
1✔
512
                if shape[i] != 1 and strides[i] % itemsize != 0:
1✔
513
                    stdlib.free(dlmv_tensor)
×
514
                    raise BufferError(
×
515
                        "numpy_to_dlpack_versioned_capsule: DLPack cannot "
516
                        "encode an array if strides are not a multiple of "
517
                        "itemsize"
518
                    )
519
        shape_strides_ptr = <int64_t *>stdlib.malloc((sizeof(int64_t) * 2) * nd)
1✔
520
    else:
521
        # no need to pass strides in this case
522
        shape_strides_ptr = <int64_t *>stdlib.malloc(sizeof(int64_t) * nd)
1✔
523
    if shape_strides_ptr is NULL:
1✔
524
        stdlib.free(dlmv_tensor)
×
525
        raise MemoryError(
×
526
            "numpy_to_dlpack_versioned_capsule: Could not allocate memory "
527
            "for shape/strides"
528
        )
529
    for i in range(nd):
1✔
530
        shape_strides_ptr[i] = shape[i]
1✔
531
        if not is_c_contiguous:
1✔
532
            shape_strides_ptr[nd + i] = strides[i] // itemsize
1✔
533

534
    writable_flag = npy_ary.flags["W"]
1✔
535

536
    ary_dt = npy_ary.dtype
1✔
537
    ary_dtk = ary_dt.kind
1✔
538

539
    dl_tensor = &dlmv_tensor.dl_tensor
1✔
540
    dl_tensor.data = <void *> npy_ary.data
1✔
541
    dl_tensor.ndim = nd
1✔
542
    dl_tensor.byte_offset = <uint64_t>byte_offset
1✔
543
    dl_tensor.shape = &shape_strides_ptr[0]
1✔
544
    if is_c_contiguous:
1✔
545
        dl_tensor.strides = NULL
1✔
546
    else:
547
        dl_tensor.strides = &shape_strides_ptr[nd]
1✔
548
    dl_tensor.device.device_type = kDLCPU
1✔
549
    dl_tensor.device.device_id = 0
1✔
550
    dl_tensor.dtype.lanes = <uint16_t>1
1✔
551
    dl_tensor.dtype.bits = <uint8_t>(ary_dt.itemsize * 8)
1✔
552
    if (ary_dtk == "b"):
1✔
553
        dl_tensor.dtype.code = <uint8_t>kDLBool
1✔
554
    elif (ary_dtk == "u"):
1✔
555
        dl_tensor.dtype.code = <uint8_t>kDLUInt
1✔
556
    elif (ary_dtk == "i"):
1✔
557
        dl_tensor.dtype.code = <uint8_t>kDLInt
1✔
558
    elif (ary_dtk == "f" and ary_dt.itemsize <= 8):
1✔
559
        dl_tensor.dtype.code = <uint8_t>kDLFloat
1✔
560
    elif (ary_dtk == "c" and ary_dt.itemsize <= 16):
1✔
561
        dl_tensor.dtype.code = <uint8_t>kDLComplex
1✔
562
    else:
563
        stdlib.free(shape_strides_ptr)
×
564
        stdlib.free(dlmv_tensor)
×
565
        raise ValueError("Unrecognized array data type")
×
566

567
    # set flags down here
568
    if copied:
1✔
569
        dlmv_flags |= DLPACK_FLAG_BITMASK_IS_COPIED
1✔
570
    if not writable_flag:
1✔
571
        dlmv_flags |= DLPACK_FLAG_BITMASK_READ_ONLY
1✔
572
    dlmv_tensor.flags = dlmv_flags
1✔
573

574
    dlmv_tensor.version.major = DLPACK_MAJOR_VERSION
1✔
575
    dlmv_tensor.version.minor = DLPACK_MINOR_VERSION
1✔
576

577
    dlmv_tensor.manager_ctx = <void*>npy_ary
1✔
578
    cpython.Py_INCREF(npy_ary)
1✔
579
    dlmv_tensor.deleter = _managed_tensor_versioned_deleter
1✔
580

581
    return cpython.PyCapsule_New(
1✔
582
        dlmv_tensor, "dltensor_versioned", _pycapsule_versioned_deleter
1✔
583
    )
584

585

586
cdef class _DLManagedTensorOwner:
587
    """
588
    Helper class managing the lifetime of the DLManagedTensor struct
589
    transferred from a 'dlpack' capsule.
590
    """
591
    cdef DLManagedTensor * dlm_tensor
592

593
    def __cinit__(self):
594
        self.dlm_tensor = NULL
1✔
595

596
    def __dealloc__(self):
597
        if self.dlm_tensor:
1✔
598
            self.dlm_tensor.deleter(self.dlm_tensor)
1✔
599
            self.dlm_tensor = NULL
1✔
600

601
    @staticmethod
602
    cdef _DLManagedTensorOwner _create(DLManagedTensor *dlm_tensor_src):
1✔
603
        cdef _DLManagedTensorOwner res
604
        res = _DLManagedTensorOwner.__new__(_DLManagedTensorOwner)
1✔
605
        res.dlm_tensor = dlm_tensor_src
1✔
606
        return res
1✔
607

608

609
cdef class _DLManagedTensorVersionedOwner:
610
    """
611
    Helper class managing the lifetime of the DLManagedTensorVersioned
612
    struct transferred from a 'dlpack_versioned' capsule.
613
    """
614
    cdef DLManagedTensorVersioned * dlmv_tensor
615

616
    def __cinit__(self):
617
        self.dlmv_tensor = NULL
1✔
618

619
    def __dealloc__(self):
620
        if self.dlmv_tensor:
1✔
621
            self.dlmv_tensor.deleter(self.dlmv_tensor)
1✔
622
            self.dlmv_tensor = NULL
1✔
623

624
    @staticmethod
625
    cdef _DLManagedTensorVersionedOwner _create(
1✔
626
        DLManagedTensorVersioned *dlmv_tensor_src
627
    ):
628
        cdef _DLManagedTensorVersionedOwner res
629
        res = _DLManagedTensorVersionedOwner.__new__(
1✔
630
            _DLManagedTensorVersionedOwner
631
        )
632
        res.dlmv_tensor = dlmv_tensor_src
1✔
633
        return res
1✔
634

635

636
cdef dict _numpy_array_interface_from_dl_tensor(DLTensor *dlt, bint ro_flag):
1✔
637
    """Constructs a NumPy `__array_interface__` dictionary from a DLTensor."""
638
    cdef int itemsize = 0
1✔
639

640
    if dlt.dtype.lanes != 1:
1✔
641
        raise BufferError(
×
642
            "Can not import DLPack tensor with lanes != 1"
643
        )
644
    itemsize = dlt.dtype.bits // 8
1✔
645
    shape = list()
1✔
646
    if (dlt.strides is NULL):
1✔
647
        strides = None
1✔
648
        for dim in range(dlt.ndim):
1✔
649
            shape.append(dlt.shape[dim])
1✔
650
    else:
651
        strides = list()
1✔
652
        for dim in range(dlt.ndim):
1✔
653
            shape.append(dlt.shape[dim])
1✔
654
            # convert to byte-strides
655
            strides.append(dlt.strides[dim] * itemsize)
1✔
656
        strides = tuple(strides)
1✔
657
    shape = tuple(shape)
1✔
658
    if (dlt.dtype.code == kDLUInt):
1✔
659
        ary_dt = "u" + str(itemsize)
1✔
660
    elif (dlt.dtype.code == kDLInt):
661
        ary_dt = "i" + str(itemsize)
1✔
662
    elif (dlt.dtype.code == kDLFloat):
663
        ary_dt = "f" + str(itemsize)
1✔
664
    elif (dlt.dtype.code == kDLComplex):
665
        ary_dt = "c" + str(itemsize)
1✔
666
    elif (dlt.dtype.code == kDLBool):
667
        ary_dt = "b" + str(itemsize)
1✔
668
    else:
669
        raise BufferError(
×
670
            "Can not import DLPack tensor with type code {}.".format(
×
671
                <object>dlt.dtype.code
×
672
            )
673
        )
674
    typestr = "|" + ary_dt
1✔
675
    return dict(
1✔
676
        version=3,
1✔
677
        shape=shape,
1✔
678
        strides=strides,
1✔
679
        data=(<size_t> dlt.data, True if ro_flag else False),
1✔
680
        offset=dlt.byte_offset,
1✔
681
        typestr=typestr,
1✔
682
    )
683

684

685
class _numpy_array_interface_wrapper:
1✔
686
    """
687
    Class that wraps a Python capsule and dictionary for consumption by NumPy.
688

689
    Implementation taken from
690
    https://github.com/dmlc/dlpack/blob/main/apps/numpy_dlpack/dlpack/to_numpy.py
691

692
    Args:
693
        array_interface:
694
            A dictionary describing the underlying memory. Formatted
695
            to match `numpy.ndarray.__array_interface__`.
696

697
        pycapsule:
698
            A Python capsule wrapping the dlpack tensor that will be
699
            converted to numpy.
700
    """
701

702
    def __init__(self, array_interface, memory_owner) -> None:
1✔
703
        self.__array_interface__ = array_interface
1✔
704
        self._memory_owner = memory_owner
1✔
705

706

707
cdef bint _is_kdlcpu_device(DLDevice *dev):
1✔
708
    "Check if DLTensor.DLDevice denotes (kDLCPU, 0)"
709
    return (dev[0].device_type == kDLCPU) and (dev[0].device_id == 0)
1✔
710

711

712
cpdef object from_dlpack_capsule(object py_caps):
1✔
713
    """
714
    from_dlpack_capsule(py_caps)
715

716
    Reconstructs instance of :class:`dpctl.tensor.usm_ndarray` from
717
    named Python capsule object referencing instance of ``DLManagedTensor``
718
    without copy. The instance forms a view in the memory of the tensor.
719

720
    Args:
721
        caps:
722
            Python capsule with name ``"dltensor"`` expected to reference
723
            an instance of ``DLManagedTensor`` struct.
724
    Returns:
725
        Instance of :class:`dpctl.tensor.usm_ndarray` with a view into
726
        memory of the tensor. Capsule is renamed to ``"used_dltensor"``
727
        upon success.
728
    Raises:
729
        TypeError:
730
            if argument is not a ``"dltensor"`` capsule.
731
        ValueError:
732
            if argument is ``"used_dltensor"`` capsule
733
        BufferError:
734
            if the USM pointer is not bound to the reconstructed
735
            sycl context, or the DLPack's device_type is not supported
736
            by :mod:`dpctl`.
737
    """
738
    cdef DLManagedTensorVersioned *dlmv_tensor = NULL
1✔
739
    cdef DLManagedTensor *dlm_tensor = NULL
1✔
740
    cdef DLTensor *dl_tensor = NULL
1✔
741
    cdef int versioned = 0
1✔
742
    cdef int readonly = 0
1✔
743
    cdef bytes usm_type
744
    cdef size_t sz = 1
1✔
745
    cdef size_t alloc_sz = 1
1✔
746
    cdef int i
747
    cdef int device_id = -1
1✔
748
    cdef int element_bytesize = 0
1✔
749
    cdef Py_ssize_t offset_min = 0
1✔
750
    cdef Py_ssize_t offset_max = 0
1✔
751
    cdef char *mem_ptr = NULL
1✔
752
    cdef Py_ssize_t mem_ptr_delta = 0
1✔
753
    cdef Py_ssize_t element_offset = 0
1✔
754
    cdef int64_t stride_i = -1
1✔
755
    cdef int64_t shape_i = -1
1✔
756

757
    if cpython.PyCapsule_IsValid(py_caps, "dltensor"):
1✔
758
        dlm_tensor = <DLManagedTensor*>cpython.PyCapsule_GetPointer(
1✔
759
                py_caps, "dltensor")
760
        dl_tensor = &dlm_tensor.dl_tensor
1✔
761
    elif cpython.PyCapsule_IsValid(py_caps, "dltensor_versioned"):
1✔
762
        dlmv_tensor = <DLManagedTensorVersioned*>cpython.PyCapsule_GetPointer(
1✔
763
                py_caps, "dltensor_versioned")
764
        if dlmv_tensor.version.major > DLPACK_MAJOR_VERSION:
1✔
765
            raise BufferError(
×
766
                "Can not import DLPack tensor with major version "
×
767
                f"greater than {DLPACK_MAJOR_VERSION}"
×
768
            )
769
        versioned = 1
1✔
770
        readonly = (dlmv_tensor.flags & DLPACK_FLAG_BITMASK_READ_ONLY) != 0
1✔
771
        dl_tensor = &dlmv_tensor.dl_tensor
1✔
772
    elif (
1✔
773
        cpython.PyCapsule_IsValid(py_caps, "used_dltensor")
1✔
774
        or cpython.PyCapsule_IsValid(py_caps, "used_dltensor_versioned")
1✔
775
    ):
776
        raise ValueError(
1✔
777
            "A DLPack tensor object can not be consumed multiple times"
778
        )
779
    else:
780
        raise TypeError(
×
781
            "`from_dlpack_capsule` expects a Python 'dltensor' capsule"
782
        )
783

784
    # Verify that we can work with this device
785
    if dl_tensor.device.device_type == kDLOneAPI:
1✔
786
        device_id = dl_tensor.device.device_id
1✔
787
        root_device = dpctl.SyclDevice(str(<int>device_id))
1✔
788
        try:
1✔
789
            default_context = root_device.sycl_platform.default_context
1✔
790
        except RuntimeError:
×
791
            default_context = get_device_cached_queue(root_device).sycl_context
×
792
        if dl_tensor.data is NULL:
1✔
793
            usm_type = b"device"
×
794
            q = get_device_cached_queue((default_context, root_device,))
×
795
        else:
796
            usm_type = c_dpmem._Memory.get_pointer_type(
1✔
797
                <DPCTLSyclUSMRef> dl_tensor.data,
798
                <c_dpctl.SyclContext>default_context)
1✔
799
            if usm_type == b"unknown":
1✔
800
                raise BufferError(
×
801
                    "Data pointer in DLPack is not bound to default sycl "
×
802
                    f"context of device '{device_id}', translated to "
×
803
                    f"{root_device.filter_string}"
×
804
                )
805
            alloc_device = c_dpmem._Memory.get_pointer_device(
1✔
806
                <DPCTLSyclUSMRef> dl_tensor.data,
807
                <c_dpctl.SyclContext>default_context
808
            )
809
            q = get_device_cached_queue((default_context, alloc_device,))
1✔
810
        if dl_tensor.dtype.bits % 8:
1✔
811
            raise BufferError(
×
812
                "Can not import DLPack tensor whose element's "
813
                "bitsize is not a multiple of 8"
814
            )
815
        if dl_tensor.dtype.lanes != 1:
1✔
816
            raise BufferError(
×
817
                "Can not import DLPack tensor with lanes != 1"
818
            )
819
        offset_min = 0
1✔
820
        if dl_tensor.strides is NULL:
1✔
821
            for i in range(dl_tensor.ndim):
1✔
822
                sz = sz * dl_tensor.shape[i]
1✔
823
            offset_max = sz - 1
1✔
824
        else:
825
            offset_max = 0
1✔
826
            for i in range(dl_tensor.ndim):
1✔
827
                stride_i = dl_tensor.strides[i]
1✔
828
                shape_i = dl_tensor.shape[i]
1✔
829
                if shape_i > 1:
1✔
830
                    shape_i -= 1
1✔
831
                    if stride_i > 0:
1✔
832
                        offset_max = offset_max + stride_i * shape_i
1✔
833
                    else:
834
                        offset_min = offset_min + stride_i * shape_i
1✔
835
            sz = offset_max - offset_min + 1
1✔
836
        if sz == 0:
1✔
837
            sz = 1
1✔
838

839
        element_bytesize = (dl_tensor.dtype.bits // 8)
1✔
840
        sz = sz * element_bytesize
1✔
841
        element_offset = dl_tensor.byte_offset // element_bytesize
1✔
842

843
        # transfer ownership
844
        if not versioned:
1✔
845
            dlm_holder = _DLManagedTensorOwner._create(dlm_tensor)
1✔
846
            cpython.PyCapsule_SetName(py_caps, "used_dltensor")
1✔
847
        else:
848
            dlmv_holder = _DLManagedTensorVersionedOwner._create(dlmv_tensor)
1✔
849
            cpython.PyCapsule_SetName(py_caps, "used_dltensor_versioned")
1✔
850

851
        if dl_tensor.data is NULL:
1✔
852
            usm_mem = dpmem.MemoryUSMDevice(sz, q)
×
853
        else:
854
            mem_ptr_delta = dl_tensor.byte_offset - (
1✔
855
                element_offset * element_bytesize
1✔
856
            )
857
            mem_ptr = <char *>dl_tensor.data
1✔
858
            alloc_sz = dl_tensor.byte_offset + <uint64_t>(
1✔
859
                (offset_max + 1) * element_bytesize)
860
            tmp = c_dpmem._Memory.create_from_usm_pointer_size_qref(
1✔
861
                <DPCTLSyclUSMRef> mem_ptr,
862
                max(alloc_sz, <uint64_t>element_bytesize),
1✔
863
                (<c_dpctl.SyclQueue>q).get_queue_ref(),
1✔
864
                memory_owner=dlmv_holder if versioned else dlm_holder
1✔
865
            )
866
            if mem_ptr_delta == 0:
1✔
867
                usm_mem = tmp
1✔
868
            else:
869
                alloc_sz = dl_tensor.byte_offset + <uint64_t>(
×
870
                    (offset_max * element_bytesize + mem_ptr_delta))
×
871
                usm_mem = c_dpmem._Memory.create_from_usm_pointer_size_qref(
×
872
                    <DPCTLSyclUSMRef> (
873
                        mem_ptr + (element_bytesize - mem_ptr_delta)
874
                    ),
875
                    max(alloc_sz, <uint64_t>element_bytesize),
×
876
                    (<c_dpctl.SyclQueue>q).get_queue_ref(),
×
877
                    memory_owner=tmp
878
                )
879
        py_shape = list()
1✔
880
        for i in range(dl_tensor.ndim):
1✔
881
            py_shape.append(dl_tensor.shape[i])
1✔
882
        if (dl_tensor.strides is NULL):
1✔
883
            py_strides = None
1✔
884
        else:
885
            py_strides = list()
1✔
886
            for i in range(dl_tensor.ndim):
1✔
887
                py_strides.append(dl_tensor.strides[i])
1✔
888
        if (dl_tensor.dtype.code == kDLUInt):
1✔
889
            ary_dt = np.dtype("u" + str(element_bytesize))
1✔
890
        elif (dl_tensor.dtype.code == kDLInt):
891
            ary_dt = np.dtype("i" + str(element_bytesize))
1✔
892
        elif (dl_tensor.dtype.code == kDLFloat):
893
            ary_dt = np.dtype("f" + str(element_bytesize))
1✔
894
        elif (dl_tensor.dtype.code == kDLComplex):
895
            ary_dt = np.dtype("c" + str(element_bytesize))
1✔
896
        elif (dl_tensor.dtype.code == kDLBool):
897
            ary_dt = np.dtype("?")
1✔
898
        else:
899
            raise BufferError(
×
900
                "Can not import DLPack tensor with type code {}.".format(
×
901
                    <object>dl_tensor.dtype.code
×
902
                )
903
            )
904
        res_ary = usm_ndarray(
1✔
905
            py_shape,
906
            dtype=ary_dt,
1✔
907
            buffer=usm_mem,
1✔
908
            strides=py_strides,
1✔
909
            offset=element_offset
1✔
910
        )
911
        if readonly:
1✔
912
            res_ary.flags_ = (res_ary.flags_ & ~USM_ARRAY_WRITABLE)
1✔
913
        return res_ary
1✔
914
    elif _is_kdlcpu_device(&dl_tensor.device):
1✔
915
        ary_iface = _numpy_array_interface_from_dl_tensor(dl_tensor, readonly)
1✔
916
        if not versioned:
1✔
917
            dlm_holder = _DLManagedTensorOwner._create(dlm_tensor)
1✔
918
            cpython.PyCapsule_SetName(py_caps, "used_dltensor")
1✔
919
            return np.ctypeslib.as_array(
1✔
920
                _numpy_array_interface_wrapper(ary_iface, dlm_holder)
1✔
921
            )
922
        else:
923
            dlmv_holder = _DLManagedTensorVersionedOwner._create(dlmv_tensor)
1✔
924
            cpython.PyCapsule_SetName(py_caps, "used_dltensor_versioned")
1✔
925
            return np.ctypeslib.as_array(
1✔
926
                _numpy_array_interface_wrapper(ary_iface, dlmv_holder)
1✔
927
            )
928
    else:
929
        raise BufferError(
×
930
            "The DLPack tensor resides on unsupported device."
931
        )
932

933
cdef usm_ndarray _to_usm_ary_from_host_blob(object host_blob, dev : Device):
1✔
934
    q = dev.sycl_queue
1✔
935
    np_ary = np.asarray(host_blob)
1✔
936
    dt = np_ary.dtype
1✔
937
    if dt.char in "dD" and q.sycl_device.has_aspect_fp64 is False:
1✔
938
        Xusm_dtype = (
939
            "float32" if dt.char == "d" else "complex64"
×
940
        )
941
    else:
942
        Xusm_dtype = dt
1✔
943
    usm_mem = dpmem.MemoryUSMDevice(np_ary.nbytes, queue=q)
1✔
944
    usm_ary = usm_ndarray(np_ary.shape, dtype=Xusm_dtype, buffer=usm_mem)
1✔
945
    usm_mem.copy_from_host(np.reshape(np_ary.view(dtype="u1"), -1))
1✔
946
    return usm_ary
1✔
947

948

949
# only cdef to make it private
950
cdef object _create_device(object device, object dl_device):
1✔
951
    if isinstance(device, Device):
1✔
952
        return device
1✔
953
    elif isinstance(device, dpctl.SyclDevice):
1✔
954
        return Device.create_device(device)
×
955
    else:
956
        root_device = dpctl.SyclDevice(str(<int>dl_device[1]))
1✔
957
        return Device.create_device(root_device)
1✔
958

959

960
def from_dlpack(x, /, *, device=None, copy=None):
1✔
961
    """from_dlpack(x, /, *, device=None, copy=None)
962

963
    Constructs :class:`dpctl.tensor.usm_ndarray` or :class:`numpy.ndarray`
964
    instance from a Python object ``x`` that implements ``__dlpack__`` protocol.
965

966
    Args:
967
        x (object):
968
            A Python object representing an array that supports
969
            ``__dlpack__`` protocol.
970
        device (
971
            Optional[str, :class:`dpctl.SyclDevice`,
972
            :class:`dpctl.SyclQueue`,
973
            :class:`dpctl.tensor.Device`,
974
            tuple([:class:`enum.IntEnum`, int])])
975
        ):
976
            Device where the output array is to be placed. ``device`` keyword
977
            values can be:
978

979
            * ``None``
980
                The data remains on the same device.
981
            * oneAPI filter selector string
982
                SYCL device selected by :ref:`filter selector string
983
                <filter_selector_string>`.
984
            * :class:`dpctl.SyclDevice`
985
                explicit SYCL device that must correspond to
986
                a non-partitioned SYCL device.
987
            * :class:`dpctl.SyclQueue`
988
                implies SYCL device targeted by the SYCL queue.
989
            * :class:`dpctl.tensor.Device`
990
                implies SYCL device `device.sycl_queue`. The `Device` object
991
                is obtained via :attr:`dpctl.tensor.usm_ndarray.device`.
992
            * ``(device_type, device_id)``
993
               2-tuple matching the format of the output of the
994
               ``__dlpack_device__`` method: an integer enumerator representing
995
               the device type followed by an integer representing the index of
996
               the device. The only supported :class:`dpctl.tensor.DLDeviceType`
997
               device types are ``"kDLCPU"`` and ``"kDLOneAPI"``.
998

999
            Default: ``None``.
1000

1001
        copy (bool, optional)
1002
            Boolean indicating whether or not to copy the input.
1003

1004
            * If ``copy`` is ``True``, the input will always be
1005
              copied.
1006
            * If ``False``, a ``BufferError`` will be raised if a
1007
              copy is deemed necessary.
1008
            * If ``None``, a copy will be made only if deemed
1009
              necessary, otherwise, the existing memory buffer will
1010
              be reused.
1011

1012
            Default: ``None``.
1013

1014
    Returns:
1015
        Alternative[usm_ndarray, numpy.ndarray]:
1016
            An array containing the data in ``x``. When ``copy`` is
1017
            ``None`` or ``False``, this may be a view into the original
1018
            memory.
1019

1020
            The type of the returned object
1021
            depends on where the data backing up input object ``x`` resides.
1022
            If it resides in a USM allocation on a SYCL device, the
1023
            type :class:`dpctl.tensor.usm_ndarray` is returned, otherwise if it
1024
            resides on ``"kDLCPU"`` device the type is :class:`numpy.ndarray`,
1025
            and otherwise an exception is raised.
1026

1027
            .. note::
1028

1029
                If the return type is :class:`dpctl.tensor.usm_ndarray`, the
1030
                associated SYCL queue is derived from the ``device`` keyword.
1031
                When ``device`` keyword value has type :class:`dpctl.SyclQueue`,
1032
                the explicit queue instance is used, when ``device`` keyword
1033
                value has type :class:`dpctl.tensor.Device`, the
1034
                ``device.sycl_queue`` is used. In all other cases, the cached
1035
                SYCL queue corresponding to the implied SYCL device is used.
1036

1037
    Raises:
1038
        TypeError:
1039
            if ``x`` does not implement ``__dlpack__`` method
1040
        ValueError:
1041
            if data of the input object resides on an unsupported device
1042

1043
    See https://dmlc.github.io/dlpack/latest/ for more details.
1044

1045
    :Example:
1046

1047
        .. code-block:: python
1048

1049
            import dpctl
1050
            import dpctl.tensor as dpt
1051

1052
            class Container:
1053
                "Helper class implementing `__dlpack__` protocol"
1054
                def __init__(self, array):
1055
                    self._array = array
1056

1057
                def __dlpack__(self, stream=None):
1058
                    return self._array.__dlpack__(stream=stream)
1059

1060
                def __dlpack_device__(self):
1061
                    return self._array.__dlpack_device__()
1062

1063
            C = Container(dpt.linspace(0, 100, num=20, dtype="int16"))
1064
            # create usm_ndarray view
1065
            X = dpt.from_dlpack(C)
1066
            # migrate content of the container to device of type kDLCPU
1067
            Y = dpt.from_dlpack(C, device=(dpt.DLDeviceType.kDLCPU, 0))
1068

1069
    """
1070
    dlpack_attr = getattr(x, "__dlpack__", None)
1✔
1071
    dlpack_dev_attr = getattr(x, "__dlpack_device__", None)
1✔
1072
    if not callable(dlpack_attr) or not callable(dlpack_dev_attr):
1✔
1073
        raise TypeError(
1✔
1074
            f"The argument of type {type(x)} does not implement "
1✔
1075
            "`__dlpack__` and `__dlpack_device__` methods."
1076
        )
1077
    # device is converted to a dlpack_device if necessary
1078
    dl_device = None
1✔
1079
    if device:
1✔
1080
        if isinstance(device, tuple):
1✔
1081
            dl_device = device
1✔
1082
            if len(dl_device) != 2:
1✔
1083
                raise ValueError(
1✔
1084
                    "Argument `device` specified as a tuple must have length 2"
1085
                )
1086
        else:
1087
            if not isinstance(device, dpctl.SyclDevice):
1✔
1088
                device = Device.create_device(device)
1✔
1089
                d = device.sycl_device
1✔
1090
            else:
1091
                d = device
1✔
1092
            dl_device = (device_OneAPI, d.get_device_id())
1✔
1093
    if dl_device is not None:
1✔
1094
        if (dl_device[0] not in [device_OneAPI, device_CPU]):
1✔
1095
            raise ValueError(
1✔
1096
                f"Argument `device`={device} is not supported."
1✔
1097
            )
1098
    got_type_error = False
1✔
1099
    got_buffer_error = False
1✔
1100
    got_other_error = False
1✔
1101
    saved_exception = None
1✔
1102
    # First DLPack version supporting dl_device, and copy
1103
    requested_ver = (1, 0)
1✔
1104
    cpu_dev = (device_CPU, 0)
1✔
1105
    try:
1✔
1106
        # setting max_version to minimal version that supports
1107
        # dl_device/copy keywords
1108
        dlpack_capsule = dlpack_attr(
1✔
1109
            max_version=requested_ver,
1✔
1110
            dl_device=dl_device,
1✔
1111
            copy=copy
1✔
1112
        )
1113
    except TypeError:
1✔
1114
        # exporter does not support max_version keyword
1115
        got_type_error = True
1✔
1116
    except (BufferError, NotImplementedError, ValueError) as e:
1✔
1117
        # Either dl_device, or copy cannot be satisfied
1118
        got_buffer_error = True
1✔
1119
        saved_exception = e
1✔
1120
    except Exception as e:
×
1121
        got_other_error = True
×
1122
        saved_exception = e
×
1123
    else:
1124
        # execution did not raise exceptions
1125
        return from_dlpack_capsule(dlpack_capsule)
1✔
1126
    finally:
1127
        if got_type_error:
1✔
1128
            # max_version/dl_device, copy keywords are not supported
1129
            # by __dlpack__
1130
            x_dldev = dlpack_dev_attr()
1✔
1131
            if (dl_device is None) or (dl_device == x_dldev):
1✔
1132
                dlpack_capsule = dlpack_attr()
1✔
1133
                return from_dlpack_capsule(dlpack_capsule)
1✔
1134
            # must copy via host
1135
            if copy is False:
1✔
1136
                raise BufferError(
×
1137
                    "Importing data via DLPack requires copying, but "
1138
                    "copy=False was provided"
1139
                )
1140
            # when max_version/dl_device/copy are not supported
1141
            # we can only support importing to OneAPI devices
1142
            # from host, or from another oneAPI device
1143
            is_supported_x_dldev = (
1144
                x_dldev == cpu_dev or
1✔
1145
                (x_dldev[0] == device_OneAPI)
1✔
1146
            )
1147
            is_supported_dl_device = (
1148
                dl_device == cpu_dev or
1✔
1149
                dl_device[0] == device_OneAPI
1✔
1150
            )
1151
            if is_supported_x_dldev and is_supported_dl_device:
1✔
1152
                dlpack_capsule = dlpack_attr()
1✔
1153
                blob = from_dlpack_capsule(dlpack_capsule)
1✔
1154
            else:
NEW
1155
                raise BufferError(
×
NEW
1156
                    f"Can not import to requested device {dl_device}"
×
1157
                )
1158
            dev = _create_device(device, dl_device)
1✔
1159
            if x_dldev == cpu_dev and dl_device == cpu_dev:
1✔
1160
                # both source and destination are CPU
1161
                return blob
×
1162
            elif x_dldev == cpu_dev:
1✔
1163
                # source is CPU, destination is oneAPI
1164
                return _to_usm_ary_from_host_blob(blob, dev)
1✔
1165
            elif dl_device == cpu_dev:
1✔
1166
                # source is oneAPI, destination is CPU
1167
                cpu_caps = blob.__dlpack__(
1✔
1168
                    max_version=get_build_dlpack_version(),
1✔
1169
                    dl_device=cpu_dev
1✔
1170
                )
1171
                return from_dlpack_capsule(cpu_caps)
1✔
1172
            else:
1173
                import dpctl.tensor as dpt
×
1174
                return dpt.asarray(blob, device=dev)
×
1175
        elif got_buffer_error:
1✔
1176
            # we are here, because dlpack_attr could not deal with requested
1177
            # dl_device, or copying was required
1178
            if copy is False:
1✔
1179
                raise BufferError(
×
1180
                    "Importing data via DLPack requires copying, but "
1181
                    "copy=False was provided"
1182
                )
1183
            if dl_device is None:
1✔
1184
                raise saved_exception
1✔
1185
            # must copy via host
1186
            if dl_device[0] != device_OneAPI:
1✔
NEW
1187
                raise BufferError(
×
NEW
1188
                    f"Can not import to requested device {dl_device}"
×
1189
                )
1190
            x_dldev = dlpack_dev_attr()
1✔
1191
            if x_dldev == cpu_dev:
1✔
1192
                dlpack_capsule = dlpack_attr()
1✔
1193
                host_blob = from_dlpack_capsule(dlpack_capsule)
1✔
1194
            else:
1195
                dlpack_capsule = dlpack_attr(
×
1196
                    max_version=requested_ver,
×
1197
                    dl_device=cpu_dev,
×
1198
                    copy=copy
×
1199
                )
1200
                host_blob = from_dlpack_capsule(dlpack_capsule)
×
1201
            dev = _create_device(device, dl_device)
1✔
1202
            return _to_usm_ary_from_host_blob(host_blob, dev)
1✔
1203
        elif got_other_error:
1✔
1204
            raise saved_exception
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc