Skip to content

Commit 84d107b

Browse files
authored
Update exchange API to be capsule following new convention (#180)
1 parent 6ea9b3e commit 84d107b

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

docs/source/python_spec.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,44 @@ refer to `github.com/dmlc/dlpack <https://github.com/dmlc/dlpack>`_.
179179
guaranteed to be in a certain order or not.
180180

181181

182+
DLPack C Exchange API
183+
~~~~~~~~~~~~~~~~~~~~~
184+
185+
Starting with DLPack 1.3, a new C Exchange API is introduced to enable faster
186+
data exchange than the Python ``__dlpack__`` API at the C extension level.
187+
Producer array frameworks may provide a ``__dlpack_c_exchange_api__``
188+
attribute on the array type.
189+
The attribute should be a ``PyCapsule`` with name ``"dlpack_exchange_api"``.
190+
The consumer can query whether this attribute exists and use it at the C extension level.
191+
Notably, consumer frameworks can always start implementing by only using the Python ``__dlpack__`` API,
192+
and then upgrade to the C Exchange API later when faster data exchange is needed.
193+
194+
.. code-block:: C
195+
196+
// Get type, fetch capsule attribute, and extract the C struct pointer
197+
PyObject *api_capsule = PyObject_GetAttrString((PyObject *)Py_TYPE(tensor_obj), "__dlpack_c_exchange_api__");
198+
if (api_capsule == NULL) { goto handle_error; }
199+
MyDLPackExchangeAPI *api = (MyDLPackExchangeAPI *)PyCapsule_GetPointer(api_capsule, "dlpack_exchange_api");
200+
Py_DECREF(api_capsule);
201+
if (api == NULL) { goto handle_error; }
202+
203+
204+
.. note:: Implementation of the C Exchange API
205+
206+
Producer framework should implement the C Exchange API in a static way either
207+
through Cython, Python C extensions, or Python binding mechanism. Importantly,
208+
because the DLPack C exchange API operates at the C extension level, we need
209+
direct interaction between the array framework ``PyObject*`` and DLPack,
210+
as a result it is harder to implement the C Exchange API through ctypes (because
211+
ctypes release thread state by default which is needed to interact with the Python C API).
212+
213+
A reference implementations of the C Exchange API in frameworks:
214+
215+
216+
* PyTorch: `C++ <https://github.com/pytorch/pytorch/tree/8da5d29de7feb165047246464d09c4c2b2318987/torch/csrc/Module.cpp#L692>`__
217+
* Paddle: `C++ <https://github.com/PaddlePaddle/Paddle/tree/6f808ba7d4742305a3a84de5cd299b8b76adfe5c/paddle/fluid/pybind/pybind.cc#L856>`__
218+
219+
182220
Reference Implementations
183221
~~~~~~~~~~~~~~~~~~~~~~~~~
184222

@@ -198,3 +236,4 @@ ctypes, cffi, etc:
198236
* mpi4py: `Cython <https://github.com/mpi4py/mpi4py/blob/master/src/mpi4py/MPI.src/asdlpack.pxi>`_
199237
* Paddle: `C++ <https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/framework/tensor_util.cc#L901-L951>`__, `Python wrapper using Python C API <https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/pybind/pybind.cc#L1263-L1280>`__
200238
* Hidet: `ctypes <https://github.com/hidet-org/hidet/blob/main/python/hidet/graph/impl/dlpack.py>`__
239+

include/dlpack/dlpack.h

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#define DLPACK_MAJOR_VERSION 1
2020

2121
/*! \brief The current minor version of dlpack */
22-
#define DLPACK_MINOR_VERSION 2
22+
#define DLPACK_MINOR_VERSION 3
2323

2424
/*! \brief DLPACK_DLL prefix for windows */
2525
#ifdef _WIN32
@@ -375,7 +375,7 @@ typedef struct DLManagedTensorVersioned {
375375
} DLManagedTensorVersioned;
376376

377377
//----------------------------------------------------------------------
378-
// DLPack `__c_dlpack_exchange_api__` fast exchange protocol definitions
378+
// DLPack `__dlpack_c_exchange_api__` fast exchange protocol definitions
379379
//----------------------------------------------------------------------
380380
/*!
381381
* \brief Request a producer library to create a new tensor.
@@ -391,7 +391,7 @@ typedef struct DLManagedTensorVersioned {
391391
* \param error_ctx Context for `SetError`.
392392
* \param SetError The function to set the error.
393393
* \return The owning DLManagedTensorVersioned* or NULL on failure.
394-
* SetError is called exactly when NULL is returned (the implementor
394+
* SetError is called exactly when NULL is returned (the implementer
395395
* must ensure this).
396396
* \note - As a C function, must not thrown C++ exceptions.
397397
* - Error propagation via SetError to avoid any direct need
@@ -432,11 +432,11 @@ typedef int (*DLPackManagedTensorFromPyObjectNoSync)( //
432432
* \brief Exports a PyObject* Tensor/NDArray to a provided DLTensor.
433433
*
434434
* This function provides a faster interface for temporary, non-owning, exchange.
435-
* The producer (implementor) still owns the memory of data, strides, shape.
435+
* The producer (implementer) still owns the memory of data, strides, shape.
436436
* The liveness of the DLTensor and the data it views is only guaranteed until
437437
* control is returned.
438438
*
439-
* This function currently assumes that the producer (implementor) can fill
439+
* This function currently assumes that the producer (implementer) can fill
440440
* in the DLTensor shape and strides without the need for temporary allocations.
441441
*
442442
* This function does not perform any stream synchronization. The consumer should query
@@ -488,7 +488,7 @@ typedef int (*DLPackCurrentWorkStream)( //
488488
* \brief Imports a DLManagedTensorVersioned to a PyObject* Tensor/NDArray.
489489
*
490490
* Convert an owning DLManagedTensorVersioned* to the Python tensor of the
491-
* producer (implementor) library with the correct type.
491+
* producer (implementer) library with the correct type.
492492
*
493493
* This function does not perform any stream synchronization.
494494
*
@@ -532,17 +532,24 @@ typedef struct DLPackExchangeAPIHeader {
532532
* \brief Framework-specific function pointers table for DLPack exchange.
533533
*
534534
* Additionally to `__dlpack__()` we define a C function table sharable by
535-
* Python implementations via `__c_dlpack_exchange_api__`.
536-
* This attribute must be set on the type as a Python integer compatible
537-
* with `PyLong_FromVoidPtr`/`PyLong_AsVoidPtr`.
535+
*
536+
* Python implementations via `__dlpack_c_exchange_api__`.
537+
* This attribute must be set on the type as a Python PyCapsule
538+
* with name "dlpack_exchange_api".
538539
*
539540
* A consumer library may use a pattern such as:
540541
*
541542
* \code
542543
*
543-
* PyObject *api_obj = type(tensor_obj).__c_dlpack_exchange_api__; // as C-code
544-
* MyDLPackExchangeAPI *api = PyLong_AsVoidPtr(api_obj);
545-
* if (api == NULL && PyErr_Occurred()) { goto handle_error; }
544+
* PyObject *api_capsule = PyObject_GetAttrString(
545+
* (PyObject *)Py_TYPE(tensor_obj), "__dlpack_c_exchange_api__")
546+
* );
547+
* if (api_capsule == NULL) { goto handle_error; }
548+
* MyDLPackExchangeAPI *api = (MyDLPackExchangeAPI *)PyCapsule_GetPointer(
549+
* api_capsule, "dlpack_exchange_api"
550+
* );
551+
* Py_DECREF(api_capsule);
552+
* if (api == NULL) { goto handle_error; }
546553
*
547554
* \endcode
548555
*

0 commit comments

Comments
 (0)