Skip to content

Add Python API, semantics and implementation details for DLPack #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 21, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
Binary file added spec/_static/images/DLPack_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions spec/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

# MyST options
myst_heading_anchors = 3
myst_enable_extensions = ["colon_fence"]

# -- Options for HTML output -------------------------------------------------

Expand Down
69 changes: 59 additions & 10 deletions spec/design_topics/data_interchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,67 @@ means the object it is attached to must return a `numpy.ndarray`
containing the data the object holds).
```

TODO: design an appropriate Python API for DLPACK (`to_dlpack` followed by `from_dlpack` is a little clunky, we'd like it to work more like the buffer protocol does on CPU, with a single constructor function).

TODO: specify the expected behaviour with copy/view/move/shared-memory semantics in detail.
## Syntax for data interchange with DLPack

The array API will offer the following syntax for data interchange:

```{note}
1. A `__dlpack__()` method on the array object.
2. A `from_dlpack(x)` function, which accept (array) objects with a
`__dlpack__` method and use that method to construct a new array
containing the data from `x`.


## Semantics

DLPack describe the memory layout of strided, n-dimensional arrays.
When a user calls `y = from_dlpack(x)`, the library implementing `x` (the
"producer") will provide access to the data from `x` to the library
containing `from_dlpack` (the "consumer"). If possible, this must be
zero-copy (i.e. `y` will be a _view_ on `x`). If not possible, that library
may make a copy of the data. In both cases:
- the producer keeps owning the memory
- `y` may or may not be a view, therefore the user must keep the
recommendation to avoid mutating `y` in mind - see
{ref}`copyview-mutability`.
- Both `x` and `y` may continue to be used just like arrays created in other ways.

If an array that is accessed via the interchange protocol lives on a
device that the requesting library does not support, one of two things
must happen: moving data to another device, or raising an exception.
Device transfers are typically expensive, hence doing that silently can
lead to hard to detect performance issues. Hence it is recommended to
raise an exception, and let the user explicitly enable device transfers
via, e.g., a `force=False` keyword that they can set to `True`.
```
device that the requesting library does not support, it is recommended to
raise a `TypeError`.


### Implementation

_Note that while this API standard largely tries to avoid discussing implementation details, some discussion and requirements are needed here because data interchange requires coordination between implementers on, e.g., memory management._

![Diagram of DLPack structs](/_static/images/DLPack_diagram.png)

_DLPack diagram. Dark blue are the structs it defines, light blue struct members, gray text enum values of supported devices and data types._

The `__dlpack__` method will produce a `PyCapsule` containing a
`DLPackManagedTensor`, which will be consumed immediately within
`from_dlpack` - therefore it is consumed exactly once, and it will not be
visible to users of the Python API.

The consumer must set the PyCapsule name to `"used_dltensor"`, and call the
`deleter` of the `DLPackManagedTensor` when it no longer needs the data.

For further details on DLPack design and how to implement support for it,
refer to [github.com/dmlc/dlpack](https://github.com/dmlc/dlpack).

:::{warning}
DLPack contains a `device_id`, which will be the device ID (an integer, `0, 1, ...`) which the producer library uses. In practice this will likely be the same numbering as that of the consumer, however that is not guaranteed. Depending on the hardware type, it may be possible for the consumer library implementation to look up the actual device from the pointer to the data - this is possible for example for CUDA device pointers.

It is recommended that implementers of this array API document whether the
`.device` attribute of the array returned from `from_dlpack` is guaranteed to
be a certain order or not.
:::

## Out of scope for data interchange

- Stream handling, e.g. for CUDA streams. TBD: if DLPack gains
synchronization semantics support (see
[dlpack/issues/57](https://github.com/dmlc/dlpack/issues/57)), details on
semantics can be added here. It may be necessary for correctness to at
least have a way to check which stream is in use.