Skip to content
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

fix: drop zero-cost views of ak.Array #2697

Merged
merged 3 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 1 addition & 27 deletions src/awkward/_connect/dlpack.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
from __future__ import annotations

__all__ = ("DLPackDevice", "get_layout_device", "to_dlpack")
__all__ = ("DLPackDevice",)
from enum import IntEnum

from awkward._typing import Any
from awkward.contents import Content


class DLPackDevice(IntEnum):
CPU = 1 # CPU
Expand All @@ -19,26 +16,3 @@ class DLPackDevice(IntEnum):
ROCM = 10 # GPU
ROCM_PINNED = 11 # GPU & CPU
CUDA_MANAGED = 13 # GPU & CPU


def get_layout_device(layout: Content) -> tuple[int, int]:
while True:
if layout.is_numpy:
break
elif layout.is_regular:
layout = layout.content
else:
raise TypeError(
"Cannot determine the DLPack device for this array layout."
"DLPack is only supported for regular arrays."
)

return layout.data.__dlpack_device__()


def to_dlpack(layout: Content, stream: Any = None) -> Any:
array = layout.to_backend_array(allow_missing=False)
if stream is None:
return array.__dlpack__()
else:
return array.__dlpack__(stream)
8 changes: 8 additions & 0 deletions src/awkward/_connect/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,11 @@ def action(inputs, **ignore):

def action_for_matmul(inputs):
raise NotImplementedError


def convert_to_array(layout, dtype=None):
out = ak.operations.to_numpy(layout, allow_missing=False)
if dtype is None:
return out
else:
return numpy.array(out, dtype=dtype)
73 changes: 36 additions & 37 deletions src/awkward/highlevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from awkward._backends.dispatch import register_backend_lookup_factory
from awkward._backends.numpy import NumpyBackend
from awkward._behavior import behavior_of, get_array_class, get_record_class
from awkward._connect.dlpack import get_layout_device, to_dlpack
from awkward._layout import wrap_layout
from awkward._nplikes.numpy import Numpy
from awkward._nplikes.numpylike import NumpyMetadata
Expand Down Expand Up @@ -1320,30 +1319,37 @@ def _repr_mimebundle_(self, include=None, exclude=None):
"text/plain": repr(self),
}

@non_inspectable_property
def __cuda_array_interface__(self):
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__cuda_array_interface__", (self,), {}
):
array = ak.operations.to_cupy(self)
return array.__cuda_array_interface__
def __array__(self, dtype=None):
"""
Intercepts attempts to convert this Array into a NumPy array and
either performs a zero-copy conversion or raises an error.

@non_inspectable_property
def __array_interface__(self):
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__array_interface__", (self,), {}
):
array = ak.operations.to_numpy(self)
return array.__array_interface__
This function is also called by the
[np.asarray](https://docs.scipy.org/doc/numpy/reference/generated/numpy.asarray.html)
family of functions, which have `copy=False` by default.

>>> np.asarray(ak.Array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6]]))
array([[1.1, 2.2, 3.3],
[4.4, 5.5, 6.6]])

If the data are numerical and regular (nested lists have equal lengths
in each dimension, as described by the #type), they can be losslessly
converted to a NumPy array and this function returns without an error.

def __dlpack_device__(self):
Otherwise, the function raises an error. It does not create a NumPy
array with dtype `"O"` for `np.object_` (see the
[note on object_ type](https://docs.scipy.org/doc/numpy/reference/arrays.scalars.html#arrays-scalars-built-in))
since silent conversions to dtype `"O"` arrays would not only be a
significant performance hit, but would also break functionality, since
nested lists in a NumPy `"O"` array are severed from the array and
cannot be sliced as dimensions.
"""
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__dlpack_device__", (self,), {}
"numpy.asarray", (self,), {"dtype": dtype}
):
return get_layout_device(self._layout)
from awkward._connect.numpy import convert_to_array

def __dlpack__(self, stream=None):
return to_dlpack(self._layout, stream)
return convert_to_array(self._layout, dtype=dtype)

def __arrow_array__(self, type=None):
with ak._errors.OperationErrorContext(
Expand Down Expand Up @@ -2453,27 +2459,20 @@ def show(self, limit_rows=20, limit_cols=80, type=False, stream=sys.stdout):
limit_rows=limit_rows, limit_cols=limit_cols, type=type, stream=stream
)

@property
def __cuda_array_interface__(self):
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__cuda_array_interface__", (self,), {}
):
array = ak.operations.to_cupy(self)
return array.__cuda_array_interface__
def __array__(self, dtype=None):
"""
Intercepts attempts to convert a #snapshot of this array into a
NumPy array and either performs a zero-copy conversion or raises
an error.

@property
def __array_interface__(self):
See #ak.Array.__array__ for a more complete description.
"""
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__array_interface__", (self,), {}
"numpy.asarray", (self,), {"dtype": dtype}
):
array = ak.operations.to_numpy(self)
return array.__array_interface__
from awkward._connect.numpy import convert_to_array

def __dlpack_device__(self):
with ak._errors.OperationErrorContext(
f"{type(self).__name__}.__dlpack_device__", (self,), {}
):
return get_layout_device(self.snapshot())
return convert_to_array(self.snapshot(), dtype=dtype)

def __arrow_array__(self, type=None):
with ak._errors.OperationErrorContext(
Expand Down
18 changes: 0 additions & 18 deletions tests/test_2649_dlpack_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest

import awkward as ak
from awkward._connect.dlpack import DLPackDevice


def test_from_dlpack_numpy():
Expand All @@ -14,14 +13,6 @@ def test_from_dlpack_numpy():
assert np.shares_memory(np_array, np_from_ak)


def test_to_dlpack_numpy():
np_array = np.arange(2 * 3 * 4 * 5).reshape(2, 3, 4, 5)
array = ak.from_numpy(np_array, regulararray=True)
np_from_ak = np.from_dlpack(array)
assert np.shares_memory(np_array, np_from_ak)
assert array.__dlpack_device__()[0] == DLPackDevice.CPU


def test_from_dlpack_cupy():
# This test only checks cupy usage, it doesn't explicitly test GPU & CPU
cp = pytest.importorskip("cupy")
Expand All @@ -31,15 +22,6 @@ def test_from_dlpack_cupy():
assert cp.shares_memory(cp_array, cp_from_ak)


def test_to_dlpack_cupy():
# This test only checks cupy usage, it doesn't explicitly test GPU & CPU
cp = pytest.importorskip("cupy")
cp_array = cp.arange(2 * 3 * 4 * 5).reshape(2, 3, 4, 5)
array = ak.from_cupy(cp_array, regulararray=True)
cp_from_ak = cp.from_dlpack(array)
assert cp.shares_memory(cp_array, cp_from_ak)


class DLPackOf:
def __init__(self, array):
self._array = array
Expand Down