From e214fde39098c20c56cf78f6adfa2457f49053d0 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Wed, 7 Feb 2024 16:36:03 -0500 Subject: [PATCH 01/13] support copy in from_dlpack --- src/array_api_stubs/_draft/array_object.py | 19 ++++++++++-- .../_draft/creation_functions.py | 31 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 9f8be2479..ab7b1798d 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -293,6 +293,8 @@ def __dlpack__( *, stream: Optional[Union[int, Any]] = None, max_version: Optional[tuple[int, int]] = None, + dl_device: Optional[Tuple[Enum, int]] = None, + copy: Optional[bool] = False ) -> PyCapsule: """ Exports the array for consumption by :func:`~array_api.from_dlpack` as a DLPack capsule. @@ -339,6 +341,17 @@ def __dlpack__( if it does support that), or of a different version. This means the consumer must verify the version even when `max_version` is passed. + dl_device: Optional[Tuple[Enum, int]] + The DLPack device type. Default is ``None``, meaning the exported capsule + should be on the same device as ``self`` is. When specified, the format + must follow that of the return value of :meth:`array.__dlpack_device__`. + If the device type cannot be handled by the producer, this function must + raise `BufferError`. + copy: Optional[bool] + Whether or not a copy should be made. Default is ``False`` to enable + zero-copy data exchange. However, a user can request a copy to be made + by the producer (through the consumer's :func:`~array_api.from_dlpack`) + to move data across the library (and/or device) boundary. Returns ------- @@ -394,7 +407,7 @@ def __dlpack__( # here to tell users that the consumer's max_version is too # old to allow the data exchange to happen. - And this logic for the consumer in ``from_dlpack``: + And this logic for the consumer in :func:`~array_api.from_dlpack`: .. code:: python @@ -409,7 +422,7 @@ def __dlpack__( Added BufferError. .. versionchanged:: 2023.12 - Added the ``max_version`` keyword. + Added the ``max_version``, ``dl_device``, and ``copy`` keywords. """ def __dlpack_device__(self: array, /) -> Tuple[Enum, int]: @@ -436,6 +449,8 @@ def __dlpack_device__(self: array, /) -> Tuple[Enum, int]: METAL = 8 VPI = 9 ROCM = 10 + CUDA_MANAGED = 13 + ONE_API = 14 """ def __eq__(self: array, other: Union[int, float, bool, array], /) -> array: diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index d0f967717..3a3944ffd 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -19,6 +19,7 @@ from ._types import ( + Any, List, NestedSequence, Optional, @@ -214,7 +215,11 @@ def eye( """ -def from_dlpack(x: object, /) -> array: +def from_dlpack( + x: object, /, *, + device: Optional[device] = None, + copy: Optional[bool] = False, +) -> Union[array, Any]: """ Returns a new array containing the data from another (array) object with a ``__dlpack__`` method. @@ -222,11 +227,24 @@ def from_dlpack(x: object, /) -> array: ---------- x: object input (array) object. + device: Optional[device] + device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array device must be inferred from ``x``. Default: ``None``. + + The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array on CPU (using + the library-chosen way to represent the CPU device - ``kDLCPU`` in DLPack - e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). + If the compliant library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so + with a clear user documentation and/or run-time warning. If a copy must be made to enable this, and ``copy`` is set to ``False``, + the function must raise ``ValueError``. + + Other kinds of devices will be considered for standardization in a future version. + copy: Optional[bool] + boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy would be necessary (e.g. the producer disallows views). Default: ``False``. Returns ------- - out: array - an array containing the data in `x`. + out: Union[array, Any] + an array containing the data in ``x``. In the case that the compliant library does not support the given ``device`` out of box + and must oursource to another (compliant) library, the output will be that library's compliant array object. .. admonition:: Note :class: note @@ -238,9 +256,9 @@ def from_dlpack(x: object, /) -> array: BufferError The ``__dlpack__`` and ``__dlpack_device__`` methods on the input array may raise ``BufferError`` when the data cannot be exported as DLPack - (e.g., incompatible dtype or strides). It may also raise other errors + (e.g., incompatible dtype, strides, or device). It may also raise other errors when export fails for other reasons (e.g., not enough memory available - to materialize the data). ``from_dlpack`` must propagate such + to materialize the data, a copy must made, etc). ``from_dlpack`` must propagate such exceptions. AttributeError If the ``__dlpack__`` and ``__dlpack_device__`` methods are not present @@ -251,6 +269,9 @@ def from_dlpack(x: object, /) -> array: ----- See :meth:`array.__dlpack__` for implementation suggestions for `from_dlpack` in order to handle DLPack versioning correctly. + + .. versionchanged:: 2023.12 + Added device and copy support. """ From a92e4813425a9ab1185f0400de98f7551bc920b2 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Thu, 8 Feb 2024 09:42:09 -0500 Subject: [PATCH 02/13] specify copy stream --- src/array_api_stubs/_draft/creation_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 3a3944ffd..0d4ab2ee7 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -240,6 +240,8 @@ def from_dlpack( copy: Optional[bool] boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy would be necessary (e.g. the producer disallows views). Default: ``False``. + If a copy is needed, the stream over which the copy is performed must be taken from the consumer, following the DLPack protocol (see :meth:`array.__dlpack__`). + Returns ------- out: Union[array, Any] From c102bcbb584b506514a4b90be094408f2d1b89e9 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Thu, 8 Feb 2024 09:49:45 -0500 Subject: [PATCH 03/13] allow 3-way copy arg to align all constructors --- src/array_api_stubs/_draft/array_object.py | 15 ++++++++------- src/array_api_stubs/_draft/creation_functions.py | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index ab7b1798d..89362aca2 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -294,7 +294,7 @@ def __dlpack__( stream: Optional[Union[int, Any]] = None, max_version: Optional[tuple[int, int]] = None, dl_device: Optional[Tuple[Enum, int]] = None, - copy: Optional[bool] = False + copy: Optional[bool] = None ) -> PyCapsule: """ Exports the array for consumption by :func:`~array_api.from_dlpack` as a DLPack capsule. @@ -335,23 +335,24 @@ def __dlpack__( not want to think about stream handling at all, potentially at the cost of more synchronizations than necessary. max_version: Optional[tuple[int, int]] - The maximum DLPack version that the *consumer* (i.e., the caller of + the maximum DLPack version that the *consumer* (i.e., the caller of ``__dlpack__``) supports, in the form of a 2-tuple ``(major, minor)``. This method may return a capsule of version ``max_version`` (recommended if it does support that), or of a different version. This means the consumer must verify the version even when `max_version` is passed. dl_device: Optional[Tuple[Enum, int]] - The DLPack device type. Default is ``None``, meaning the exported capsule + the DLPack device type. Default is ``None``, meaning the exported capsule should be on the same device as ``self`` is. When specified, the format must follow that of the return value of :meth:`array.__dlpack_device__`. If the device type cannot be handled by the producer, this function must raise `BufferError`. copy: Optional[bool] - Whether or not a copy should be made. Default is ``False`` to enable - zero-copy data exchange. However, a user can request a copy to be made - by the producer (through the consumer's :func:`~array_api.from_dlpack`) - to move data across the library (and/or device) boundary. + boolean indicating whether or not to copy the input. If ``True``, the + function must always copy (paerformed by the producer), potentially allowing + data movement across the library (and/or device) boundary. If ``False``, + the function must never copy. If ``None``, the function must reuse existing + memory buffer if possible and copy otherwise. Default: ``None``. Returns ------- diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 0d4ab2ee7..540d62d20 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -218,7 +218,7 @@ def eye( def from_dlpack( x: object, /, *, device: Optional[device] = None, - copy: Optional[bool] = False, + copy: Optional[bool] = None ) -> Union[array, Any]: """ Returns a new array containing the data from another (array) object with a ``__dlpack__`` method. @@ -238,7 +238,7 @@ def from_dlpack( Other kinds of devices will be considered for standardization in a future version. copy: Optional[bool] - boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy would be necessary (e.g. the producer disallows views). Default: ``False``. + boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy would be necessary (e.g. the producer disallows views). If ``None``, the function must reuse existing memory buffer if possible and copy otherwise. Default: ``None``. If a copy is needed, the stream over which the copy is performed must be taken from the consumer, following the DLPack protocol (see :meth:`array.__dlpack__`). From 45133393ba731f4bc63aa290eec957bb4fca00e1 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Fri, 9 Feb 2024 16:58:40 -0500 Subject: [PATCH 04/13] update to reflect the discussions --- src/array_api_stubs/_draft/array_object.py | 23 +++++++++++++++---- .../_draft/creation_functions.py | 12 ++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 89362aca2..5d1c2bd58 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -326,6 +326,12 @@ def __dlpack__( - ``> 2``: stream number represented as a Python integer. - Using ``1`` and ``2`` is not supported. + .. note:: + When ``dl_device`` is provided explicitly, ``stream`` must be a valid + construct for the specified device type. In particular, when ``kDLCPU`` + is in use, ``stream`` must be ``None`` and a synchronization must be + performed to ensure data safety. + .. admonition:: Tip :class: important @@ -344,9 +350,9 @@ def __dlpack__( dl_device: Optional[Tuple[Enum, int]] the DLPack device type. Default is ``None``, meaning the exported capsule should be on the same device as ``self`` is. When specified, the format - must follow that of the return value of :meth:`array.__dlpack_device__`. + must be a 2-tuple, following that of the return value of :meth:`array.__dlpack_device__`. If the device type cannot be handled by the producer, this function must - raise `BufferError`. + raise ``BufferError``. copy: Optional[bool] boolean indicating whether or not to copy the input. If ``True``, the function must always copy (paerformed by the producer), potentially allowing @@ -354,6 +360,12 @@ def __dlpack__( the function must never copy. If ``None``, the function must reuse existing memory buffer if possible and copy otherwise. Default: ``None``. + When a copy happens, the ``DLPACK_FLAG_BITMASK_IS_COPIED`` flag must be set. + + .. note:: + If a copy happens, and if the consumer-provided ``stream`` and ``dl_device`` + can be understood by the producer, the copy must be performed over ``stream``. + Returns ------- capsule: PyCapsule @@ -413,11 +425,14 @@ def __dlpack__( .. code:: python try: - x.__dlpack__(max_version=(1, 0)) + x.__dlpack__(max_version=(1, 0), ...) # if it succeeds, store info from the capsule named "dltensor_versioned", # and need to set the name to "used_dltensor_versioned" when we're done except TypeError: - x.__dlpack__() + x.__dlpack__(...) + + This logic is also applicable to handling of the new ``dl_device`` and ``copy`` + keywords. .. versionchanged:: 2022.12 Added BufferError. diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 540d62d20..1d47c8356 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -228,19 +228,17 @@ def from_dlpack( x: object input (array) object. device: Optional[device] - device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array device must be inferred from ``x``. Default: ``None``. + device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array device must be inferred from ``x.device``. Default: ``None``. The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array on CPU (using - the library-chosen way to represent the CPU device - ``kDLCPU`` in DLPack - e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). - If the compliant library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so + a library-specifc way to represent the CPU device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). + If the array library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so with a clear user documentation and/or run-time warning. If a copy must be made to enable this, and ``copy`` is set to ``False``, the function must raise ``ValueError``. - Other kinds of devices will be considered for standardization in a future version. + Other kinds of devices will be considered for standardization in a future version of this API standard. copy: Optional[bool] - boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy would be necessary (e.g. the producer disallows views). If ``None``, the function must reuse existing memory buffer if possible and copy otherwise. Default: ``None``. - - If a copy is needed, the stream over which the copy is performed must be taken from the consumer, following the DLPack protocol (see :meth:`array.__dlpack__`). + boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy is deemed necessary (e.g. the producer disallows views). If ``None``, the function must reuse the existing memory buffer if possible and copy otherwise. Default: ``None``. Returns ------- From b855568ae079bd6f0c9bda019edf4212dc044e8b Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sat, 10 Feb 2024 15:49:00 -0500 Subject: [PATCH 05/13] clairfy a bit and fix typos --- src/array_api_stubs/_draft/array_object.py | 9 +++++---- src/array_api_stubs/_draft/creation_functions.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 5d1c2bd58..683c8fb03 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -355,10 +355,11 @@ def __dlpack__( raise ``BufferError``. copy: Optional[bool] boolean indicating whether or not to copy the input. If ``True``, the - function must always copy (paerformed by the producer), potentially allowing - data movement across the library (and/or device) boundary. If ``False``, - the function must never copy. If ``None``, the function must reuse existing - memory buffer if possible and copy otherwise. Default: ``None``. + function must always copy (performed by the producer). If ``False``, the + function must never copy, and raise a ``BufferError`` in case a copy is + deemed necessary (e.g. if a cross-device data movement is requested, and + it is not possible without a copy). If ``None``, the function must reuse + the existing memory buffer if possible and copy otherwise. Default: ``None``. When a copy happens, the ``DLPACK_FLAG_BITMASK_IS_COPIED`` flag must be set. diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 1d47c8356..561e42820 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -228,10 +228,10 @@ def from_dlpack( x: object input (array) object. device: Optional[device] - device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array device must be inferred from ``x.device``. Default: ``None``. + device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array must be on the same device as ``x``. Default: ``None``. The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array on CPU (using - a library-specifc way to represent the CPU device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). + a library-specific way to represent the CPU device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). If the array library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so with a clear user documentation and/or run-time warning. If a copy must be made to enable this, and ``copy`` is set to ``False``, the function must raise ``ValueError``. From 60f743965468b3a8336d7a2b22d061411d63d7d6 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sat, 10 Feb 2024 15:58:03 -0500 Subject: [PATCH 06/13] sync the copy docs --- src/array_api_stubs/_draft/creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 561e42820..8a178c403 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -238,7 +238,7 @@ def from_dlpack( Other kinds of devices will be considered for standardization in a future version of this API standard. copy: Optional[bool] - boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy and must raise a ``BufferError`` in case a copy is deemed necessary (e.g. the producer disallows views). If ``None``, the function must reuse the existing memory buffer if possible and copy otherwise. Default: ``None``. + boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy, and raise ``BufferError`` in case a copy is deemed necessary (e.g. if a cross-device data movement is requested, and it is not possible without a copy). If ``None``, the function must reuse the existing memory buffer if possible and copy otherwise. Default: ``None``. Returns ------- From 338742a252c226edbeeecbd806f347793eaac103 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sun, 11 Feb 2024 18:32:35 -0500 Subject: [PATCH 07/13] clarify what's 'on CPU' --- src/array_api_stubs/_draft/creation_functions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 8a178c403..a4d159a5b 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -230,8 +230,9 @@ def from_dlpack( device: Optional[device] device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array must be on the same device as ``x``. Default: ``None``. - The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array on CPU (using - a library-specific way to represent the CPU device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). + The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array + whose underlying memory is accessible to the Python interpreter (using a library-specific way to represent the CPU + device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). If the array library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so with a clear user documentation and/or run-time warning. If a copy must be made to enable this, and ``copy`` is set to ``False``, the function must raise ``ValueError``. From 0b3177cf7e9c7d863ea8198aa74fb1659a60d599 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Mon, 12 Feb 2024 10:49:18 -0500 Subject: [PATCH 08/13] try to make linter happy --- src/array_api_stubs/_draft/array_object.py | 2 +- src/array_api_stubs/_draft/creation_functions.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 683c8fb03..63ec6b10f 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -294,7 +294,7 @@ def __dlpack__( stream: Optional[Union[int, Any]] = None, max_version: Optional[tuple[int, int]] = None, dl_device: Optional[Tuple[Enum, int]] = None, - copy: Optional[bool] = None + copy: Optional[bool] = None, ) -> PyCapsule: """ Exports the array for consumption by :func:`~array_api.from_dlpack` as a DLPack capsule. diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index a4d159a5b..8bbd13614 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -216,9 +216,11 @@ def eye( def from_dlpack( - x: object, /, *, + x: object, + /, + *, device: Optional[device] = None, - copy: Optional[bool] = None + copy: Optional[bool] = None, ) -> Union[array, Any]: """ Returns a new array containing the data from another (array) object with a ``__dlpack__`` method. From e3535ce5b9687b5651b9275a8de462112cdcd153 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Mon, 12 Feb 2024 23:15:11 -0500 Subject: [PATCH 09/13] remove namespace leak clause, clean up, and add an example --- .../_draft/creation_functions.py | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 8bbd13614..27699039c 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -19,7 +19,6 @@ from ._types import ( - Any, List, NestedSequence, Optional, @@ -221,7 +220,7 @@ def from_dlpack( *, device: Optional[device] = None, copy: Optional[bool] = None, -) -> Union[array, Any]: +) -> array: """ Returns a new array containing the data from another (array) object with a ``__dlpack__`` method. @@ -232,12 +231,10 @@ def from_dlpack( device: Optional[device] device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array must be on the same device as ``x``. Default: ``None``. - The v2023.12 standard only mandates that a compliant library must offer a way for ``from_dlpack`` to create an array - whose underlying memory is accessible to the Python interpreter (using a library-specific way to represent the CPU - device (``kDLCPU`` in DLPack) e.g. a ``"CPU"`` string or a ``Device("CPU")`` object). - If the array library does not support the CPU device and needs to outsource to another (compliant) array library, it may do so - with a clear user documentation and/or run-time warning. If a copy must be made to enable this, and ``copy`` is set to ``False``, - the function must raise ``ValueError``. + The v2023.12 standard only mandates that a compliant library should offer a way for ``from_dlpack`` to create an array + whose underlying memory is accessible to the Python interpreter (represented by the ``kDLCPU`` enumerator in DLPack). + If the array library does not support such case at all, the function must raise ``BufferError``. If a copy must be made + to enable this but ``copy`` is set to ``False``, the function must raise ``ValueError``. Other kinds of devices will be considered for standardization in a future version of this API standard. copy: Optional[bool] @@ -245,9 +242,8 @@ def from_dlpack( Returns ------- - out: Union[array, Any] - an array containing the data in ``x``. In the case that the compliant library does not support the given ``device`` out of box - and must oursource to another (compliant) library, the output will be that library's compliant array object. + out: array + an array containing the data in ``x``. .. admonition:: Note :class: note @@ -261,18 +257,38 @@ def from_dlpack( may raise ``BufferError`` when the data cannot be exported as DLPack (e.g., incompatible dtype, strides, or device). It may also raise other errors when export fails for other reasons (e.g., not enough memory available - to materialize the data, a copy must made, etc). ``from_dlpack`` must propagate such + to materialize the data). ``from_dlpack`` must propagate such exceptions. AttributeError If the ``__dlpack__`` and ``__dlpack_device__`` methods are not present on the input array. This may happen for libraries that are never able to export their data with DLPack. + ValueError + If data exchange is possible via an explicit copy but ``copy`` is set to ``False``. Notes ----- See :meth:`array.__dlpack__` for implementation suggestions for `from_dlpack` in order to handle DLPack versioning correctly. + A way to move data from two array libraries to the same device (assumed supported by both libraries) in + a library-agnostic fashion is illustrated below: + + .. code:: python + + def func(x, y): + xp_x = x.__array_namespace__() + xp_y = y.__array_namespace__() + + # Other functions than `from_dlpack` only work if both arrays are from the same library. So if + # `y` is from a different one than `x`, let's convert `y` into an array of the same type as `x`: + if not xp_x == xp_y: + y = xp_x.from_dlpack(y, copy=True, device=x.device) + + # From now on use `xp_x.xxxxx` functions, as both arrays are from the library `xp_x` + ... + + .. versionchanged:: 2023.12 Added device and copy support. """ From 65ca5bdcb93bde61eefe589f8ea2308b310c1b6e Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Tue, 13 Feb 2024 09:34:52 -0500 Subject: [PATCH 10/13] make linter happy --- src/array_api_stubs/_draft/creation_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 27699039c..4e1062f26 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -279,12 +279,12 @@ def from_dlpack( def func(x, y): xp_x = x.__array_namespace__() xp_y = y.__array_namespace__() - + # Other functions than `from_dlpack` only work if both arrays are from the same library. So if # `y` is from a different one than `x`, let's convert `y` into an array of the same type as `x`: if not xp_x == xp_y: y = xp_x.from_dlpack(y, copy=True, device=x.device) - + # From now on use `xp_x.xxxxx` functions, as both arrays are from the library `xp_x` ... From a7f8d22b2f77f99e0b51527b8923a9ee3d3089f9 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Tue, 13 Feb 2024 16:40:20 +0100 Subject: [PATCH 11/13] fix Sphinx complaint about Enum --- src/array_api_stubs/_draft/array_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 63ec6b10f..1d71aa611 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -293,7 +293,7 @@ def __dlpack__( *, stream: Optional[Union[int, Any]] = None, max_version: Optional[tuple[int, int]] = None, - dl_device: Optional[Tuple[Enum, int]] = None, + dl_device: Optional[tuple[Enum, int]] = None, copy: Optional[bool] = None, ) -> PyCapsule: """ @@ -347,7 +347,7 @@ def __dlpack__( if it does support that), or of a different version. This means the consumer must verify the version even when `max_version` is passed. - dl_device: Optional[Tuple[Enum, int]] + dl_device: Optional[tuple[enum.Enum, int]] the DLPack device type. Default is ``None``, meaning the exported capsule should be on the same device as ``self`` is. When specified, the format must be a 2-tuple, following that of the return value of :meth:`array.__dlpack_device__`. From 24cb4db4b6e4731b660e19d88ec1dc0a7cbfb9c4 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Tue, 13 Feb 2024 11:30:04 -0500 Subject: [PATCH 12/13] add/update v2023-specific notes on device --- src/array_api_stubs/_draft/array_object.py | 10 ++++++++++ src/array_api_stubs/_draft/creation_functions.py | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index 1d71aa611..fc11f964d 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -353,6 +353,16 @@ def __dlpack__( must be a 2-tuple, following that of the return value of :meth:`array.__dlpack_device__`. If the device type cannot be handled by the producer, this function must raise ``BufferError``. + + The v2023.12 standard only mandates that a compliant library should offer a way for + ``__dlpack__`` to return a capsule referencing an array whose underlying memory is + accessible to the Python interpreter (represented by the ``kDLCPU`` enumerator in DLPack). + If the array library does not support ``kDLCPU`` at all, the function must raise + ``BufferError``. If a copy must be made to enable this support but ``copy`` is set to + ``False``, the function must raise ``ValueError``. + + Other device kinds will be considered for standardization in a future version of this + API standard. copy: Optional[bool] boolean indicating whether or not to copy the input. If ``True``, the function must always copy (performed by the producer). If ``False``, the diff --git a/src/array_api_stubs/_draft/creation_functions.py b/src/array_api_stubs/_draft/creation_functions.py index 4e1062f26..dec09db0c 100644 --- a/src/array_api_stubs/_draft/creation_functions.py +++ b/src/array_api_stubs/_draft/creation_functions.py @@ -231,12 +231,12 @@ def from_dlpack( device: Optional[device] device on which to place the created array. If ``device`` is ``None`` and ``x`` supports DLPack, the output array must be on the same device as ``x``. Default: ``None``. - The v2023.12 standard only mandates that a compliant library should offer a way for ``from_dlpack`` to create an array - whose underlying memory is accessible to the Python interpreter (represented by the ``kDLCPU`` enumerator in DLPack). - If the array library does not support such case at all, the function must raise ``BufferError``. If a copy must be made - to enable this but ``copy`` is set to ``False``, the function must raise ``ValueError``. + The v2023.12 standard only mandates that a compliant library should offer a way for ``from_dlpack`` to return an array + whose underlying memory is accessible to the Python interpreter, when the corresponding ``device`` is provided. If the + array library does not support such cases at all, the function must raise ``BufferError``. If a copy must be made to + enable this support but ``copy`` is set to ``False``, the function must raise ``ValueError``. - Other kinds of devices will be considered for standardization in a future version of this API standard. + Other device kinds will be considered for standardization in a future version of this API standard. copy: Optional[bool] boolean indicating whether or not to copy the input. If ``True``, the function must always copy. If ``False``, the function must never copy, and raise ``BufferError`` in case a copy is deemed necessary (e.g. if a cross-device data movement is requested, and it is not possible without a copy). If ``None``, the function must reuse the existing memory buffer if possible and copy otherwise. Default: ``None``. From 43263ce7eaa4c1ae685bcd3ff93c64905c9f6b00 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Tue, 13 Feb 2024 11:47:02 -0500 Subject: [PATCH 13/13] remove a note on kDLCPU --- src/array_api_stubs/_draft/array_object.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/array_api_stubs/_draft/array_object.py b/src/array_api_stubs/_draft/array_object.py index fc11f964d..9f8f3ca4d 100644 --- a/src/array_api_stubs/_draft/array_object.py +++ b/src/array_api_stubs/_draft/array_object.py @@ -357,9 +357,8 @@ def __dlpack__( The v2023.12 standard only mandates that a compliant library should offer a way for ``__dlpack__`` to return a capsule referencing an array whose underlying memory is accessible to the Python interpreter (represented by the ``kDLCPU`` enumerator in DLPack). - If the array library does not support ``kDLCPU`` at all, the function must raise - ``BufferError``. If a copy must be made to enable this support but ``copy`` is set to - ``False``, the function must raise ``ValueError``. + If a copy must be made to enable this support but ``copy`` is set to ``False``, the + function must raise ``ValueError``. Other device kinds will be considered for standardization in a future version of this API standard.