Repository navigation
[RFC] C Functions for Speed Exchange and Stream Handling #175
Description
This is a proposal to bring C standard functions pointer APIs to exchange DLPack that can potentially bring 3x-5x speedup to previous best effort cases, and will enable more effective use in GPU settings
Overview
As of now, DLPack exchange relies on python functions such as tensor.__dlpack__(). While they works well for common cases, the general overhead of such exchange is at the level of 0.2-0.3 us for very well optimized version, and can go up to 0.4-1 us for less optimized implementation.
For a function that takes three arguments f(a, b, c), assume we run DLPack exchange for each argument, the general conversion overhead usually gets to around 0.7us - 3us.
While such overhead can be acceptable in many settings, in GPU applications the extra 1-3us overhead can still be significant. For a kernel that takes 2us to finish, 0.7 us means 30% additional overhead in execution
This PR proposes three functions for speed exchange DLPack tensors without going through python interpreter.
Proposed Functions
/*!
* \brief A generic C-style allocator that exposes allocation of a Tensor/Array.
*
* This information can then be used to set allocators of a callee to run allocations.
* This information can then be used to set the callee's allocator to perform allocations.
* This function can be exposed by the framework through the DLPackExchangeAPI.
*
* This particular function does not assume a Python environment; as a result,
* the error handling mechanism is different from Python-related functions.
*
* \param prototype The prototype DLTensor to offer details about the device and shape.
* Other field information will be ignored during allocation.
* \param out The output DLManagedTensorVersioned.
* \param error_ctx The context to set the error.
* \param SetError The function to set the error.
* \return 0 on success, -1 on failure.
* The callee should call SetError(error_ctx, kind, message) to set the error kind and message.
* \note Error propagation via SetError.
*
* \sa DLPackExchangeAPI
*/
typedef int (*DLPackManagedTensorAllocator)( //
DLTensor* prototype, DLManagedTensorVersioned** out, void* error_ctx, //
void (*SetError)(void* error_ctx, const char* kind, const char* message) //
);
/*!
* \brief Exports a PyObject* Tensor/NDArray to a DLManagedTensorVersioned.
*
* This function is a C-style function pointer to quickly convert a PyObject* Tensor/NDArray
* to a DLManagedTensorVersioned without going through the Python interpreter.
*
* This function does not perform any stream synchronization. The consumer should query
* DLPackCurrentWorkStream to get the current work stream and launch kernels on it.
*
* This function is exposed by the framework through the DLPackExchangeAPI.
*
* This information can then be picked up by importers and libraries to perform a fast conversion.
* This function should not throw any exceptions; if it fails, it should return -1 and
* set the error message via PyErr_SetXXX.
*
* \param py_object The Python object to convert; this should be PyObject*.
* We use void* to avoid dependency on Python.h.
*
* \param out The output DLManagedTensorVersioned.
* \return 0 on success, -1 on failure. PyError should be set if -1 is returned.
* \note We use void* to avoid dependency on Python.h, so this specific type is
* not dependent on Python.h and can be copied to dlpack.h.
*
* \sa DLPackExchangeAPI, DLPackCurrentWorkStream
*/
typedef int (*DLPackManagedTensorFromPyObjectNoSync)( //
void* py_object, //
DLManagedTensorVersioned** out //
);
/*!
* \brief Imports a DLManagedTensorVersioned to a PyObject* Tensor/NDArray.
*
* This function is a C-style function pointer to quickly convert a DLManagedTensorVersioned
* to a PyObject* without going through the Python Interpreter.
*
* This function does not perform any stream synchronization.
*
* This function is exposed by the framework through the DLPackExchangeAPI.
*
* \param tensor The DLManagedTensorVersioned to convert.
* \param out_py_object The output Python object.
* \return 0 on success, -1 on failure. PyError should be set if -1 is returned.
* \note We use void* to avoid dependency on Python.h, so this specific type is
* not dependent on Python.h and can be copied to dlpack.h.
*
* \sa DLPackExchangeAPI
*/
typedef int (*DLPackManagedTensorToPyObjectNoSync)( //
DLManagedTensorVersioned* tensor, void** out_py_object //
);
/*!
* \brief Exports a PyObject* Tensor/NDArray to a DLTensor whose space is pre-allocated on stack.
*
* This function is a C-style function pointer to quickly convert a PyObject* Tensor/NDArray
* to a DLTensor whose space is pre-allocated on stack without going through the Python interpreter.
*
* This is an non-owning conversion, the producer still owns the memory of data, strides, shape.
* The liveness of DLTensor is only guaranteed until the consumer returns control to the caller.
*
* In the context of this function, we expect the producer to allocated space for data, strides and shape.
*
* This function does not perform any stream synchronization. The consumer should query
* DLPackCurrentWorkStream to get the current work stream and launch kernels on it.
*
* This function is useful when the consumer do not need to retain the tensor memory.
* It generally can provide about 2x faster conversion than DLPackManagedTensorFromPyObjectNoSync.
*
* For cases where consumer may needs to reorganize the tensor memory via temporary managed copy,
* DLPackManagedTensorFromPyObjectNoSync should be used.
*
* This function is exposed by the framework through the DLPackExchangeAPI.
*
* This information can then be picked up by importers and libraries to perform a fast conversion.
* This function should not throw any exceptions; if it fails, it should return -1 and
* set the error message via PyErr_SetXXX.
*
* \param py_object The Python object to convert; this should be PyObject*.
* We use void* to avoid dependency on Python.h.
*
* \param out The output DLTensor, whose space is pre-allocated on stack.
* \return 0 on success, -1 on failure. PyError should be set if -1 is returned.
* \note We use void* to avoid dependency on Python.h, so this specific type is
* not dependent on Python.h and can be copied to dlpack.h.
*
* \sa DLPackExchangeAPI, DLPackCurrentWorkStream
*/
typedef int (*DLPackDLTensorFromPyObjectNoSync)( //
void* py_object, //
DLTensor* out //
);
/*!
* \brief Obtain the current work stream of a device.
*
* This function is a C-style function pointer to obtain the current work stream
* of a device for frameworks that rely on a context manager to manage the stream.
* For example, it should map to torch.cuda.current_stream in PyTorch.
*
* This function can be set to NULL if the framework does not rely on a context manager
* to manage the stream. However, we encourage frameworks to provide this function
* if possible.
*
* As if this field is not set, likely consumer cannot safely do stream based
* exchange based on the
*
* \param device_type The device type.
* \param device_id The device id.
* \param out_current_stream The output current work stream.
* \return 0 on success, -1 on failure.
*
* \sa DLPackExchangeAPI
*/
typedef int (*DLPackCurrentWorkStream)( //
DLDeviceType device_type, //
int32_t device_id, //
void** out_current_stream //
);
/*!
* \brief Framework-specific function pointers table for DLPack exchange.
*
* Guidelines for leveraging DLPackExchangeAPI:
*
* There are generally two kinds of consumer needs for DLPack exchange:
* - N0: library support, where consumer.kernel(x, y, z) would like to run a kernel
* with the data from x, y, z. The consumer is also expected to run the kernel with the same
* stream context as the producer. For example, when x, y, z is torch.Tensor,
* consumer should query exchange_api->current_work_stream to get the
* current stream and launch the kernel with the same stream.
* This setup is necessary for no synchronization in kernel launch and maximum compatibility
* with CUDA graph capture in the producer.
* This is the desirable behavior for library extension support for frameworks like PyTorch.
* - N1: data ingestion and retention
*
* Note that obj.__dlpack__() API should provide useful ways for N1.
* The primary focus of the current DLPackExchangeAPI is to enable faster exchange N0
* with the support of the function pointer current_work_stream.
*
* Array/Tensor libraries should statically create and initialize this structure
* then return a pointer to DLPackExchangeAPI as an int value in Tensor/Array.
* The DLPackExchangeAPI* should stay alive throughout the lifetime of the process.
*
* One simple way to do so is to create a static instance of DLPackExchangeAPI
* within the framework and return a pointer to it. The following code
* shows an example to do so in C++. It should also be reasonably easy
* to do so in other languages.
*
* \code
* struct MyDLPackExchangeAPI : public DLPackExchangeAPI {
* MyDLPackExchangeAPI() {
* version.major = DLPACK_MAJOR_VERSION;
* version.minor = DLPACK_MINOR_VERSION;
* managed_tensor_allocator = MyDLPackManagedTensorAllocator;
* managed_tensor_from_py_object_no_sync = MyDLPackManagedTensorFromPyObjectNoSync;
* managed_tensor_to_py_object_no_sync = MyDLPackManagedTensorToPyObjectNoSync;
* dltensor_from_py_object_no_sync = MyDLPackDLTensorFromPyObjectNoSync;
* current_work_stream = MyDLPackCurrentWorkStream;
* prev_version_api = nullptr;
* }
*
* static const DLPackExchangeAPI* Global() {
* static MyDLPackExchangeAPI inst;
* return &inst;
* }
* };
* \endcode
*
* Each framework should attach a dunder `__c_dlpack_exchange_api__` integer
* to point to the DLPackExchangeAPI* pointer.
*
* Importantly, the attribute should be attached to the class of the Tensor, not the instance.
*
* mypackage.Tensor.__c_dlpack_exchange_api__ = MyPackageDLPackExchangeAPI
*
* or equivalently:
*
* type(tensor_obj).__c_dlpack_exchange_api__ = MyPackageDLPackExchangeAPI
*/
struct DLPackExchangeAPI {
/*!
* \brief The current DLPack version.
*/
DLPackVersion version;
/*!
* \brief Optional pointer to an older DLPackExchangeAPI in the chain.
*
* It should be set to NULL if the framework does not support older versions.
*
* \sa DLPackExchangeAPI
*/
struct DLPackExchangeAPI* prev_version_api;
/*!
* \brief Framework-specific function pointer for DLPackManagedTensorAllocator
* \sa DLPackManagedTensorAllocator
*/
DLPackManagedTensorAllocator managed_tensor_allocator;
/*!
* \brief Framework-specific function pointer for DLPackManagedTensorFromPyObject
* \sa DLPackManagedTensorFromPyObject
*/
DLPackManagedTensorFromPyObjectNoSync managed_tensor_from_py_object_no_sync;
/*!
* \brief Framework-specific function pointer for DLPackManagedTensorToPyObject
* \sa DLPackManagedTensorToPyObject
*/
DLPackManagedTensorToPyObjectNoSync managed_tensor_to_py_object_no_sync;
/*!
* \brief Framework-specific function pointer for DLPackDLTensorFromPyObject
* \sa DLPackDLTensorFromPyObjectNoSync
*/
DLPackDLTensorFromPyObjectNoSync dltensor_from_py_object_no_sync;
/*!
* \brief Framework-specific function pointer for DLPackCurrentWorkStream
*
* This function can be set to NULL if the framework does not rely on context manager to manage the stream.
*
* \sa DLPackCurrentWorkStream
*/
DLPackCurrentWorkStream current_work_stream;
};Discussions
- DLPackManagedTensorFromPyObjectNoSync for fast exchange owned tensors into consumer
- DLPackDLTensorFromPyObjectNoSync for fast exchange non-owned tensors into consumer
- DLPackManagedTensorToPyObjectNoSync for fast exchange consumer tensors into producer (return values)
- DLPackCurrentWorkStream for query the current working stream from the stream context
Our preliminary results show that these functions, when incorporated correctly via native extensions such as c/c++, can bring exchange cost to the level of 30ns - 80ns, giving us about one order of maginitude speedup. That means the API overhead of functions like f(a, b, c) will be at 0.2us-0.4us level (including exchange), which is close to what native cpp extension overhead do without exchange.
Relation with current DLPack API
The proposed speed exchange function can be used in conjunction with the current DLPack exchange, to gracefully handle fallback cases. To ensure exchange happens in very efficient way for common cases that need zero copy, likely we don't expect exchange to happen for all cases. We can still fallback to the normal dlpack exchange that offers more robust and still efficient exchanges
Activity
tqchen commentedon Sep 12, 2025
Reference pytorch implementation (in torch extension)
tqchen commentedon Sep 12, 2025
PR #174
tqchen commentedon Sep 12, 2025
Some benchmark on torch on three argument function f(x, y, z), benchmark the overhead of API by passing in arguments, doing conversion at cpp extension level, and get into C and return(nop), x, y, z are torch.Tensors with one element, [stream] means we set a custom stream
dalcinl commentedon Sep 14, 2025
Wouldn't it be better to group all the C function pointers in a container, let say
__c_dlpack_api__, rather than using individual attributes for each function? Such container could be a dict or, if the dict lookup cost is prohibitive, a plain tuple. There are to reasons for proposing this. The fist one is avoiding dunder attributes being added over and over again when new functionality is introduced. The second one is about encapsulation: I would argue that the allocator/to/from functions may be related and share common bits of implementation (or even internal runtime state), and perhaps it would have sense to set/override them as a group rather than individually.tqchen commentedon Sep 14, 2025
Thanks @dalcinl. I think it is certainly possible to go with one grouped field, There are several ways to go with it
DLPackExchangeAPI, that is statically allocated and return a global pointer toDLPackExchangeAPI. Use a C Function table and expose through__c_dlpack_exchange_api__as PyLong_FromVoidPtr. Consumer can checkDLPackVersionfor compact if needed. The struct will be alive throughout the entire lifetime of process so can be used as a jumptableWould be good to see what people prefer
tqchen commentedon Sep 14, 2025
I now agree that A1 perhaps is better, updated proposal and PR #174 to reflect that. thank you @dalcinl !
seberg commentedon Sep 16, 2025
Let's please use a custom object for this? And with custom object, I mean a capsule. I really don't like this passing raw pointers around for 1ns of speed difference.
I know that it tends to confuse everyone, but I would also make it
type(obj).__dlpack_slots__. These functions are unbound methods and unbound methods belong logically to the type (that also makes it hard to attach them to something else incorrectly).(There is one downside that type lookups used to rarely take a fast path for failure, but that might even be fixed in Python now should have a look, because this was a larger annoyance also in NumPy.)
As used above, I would call it
__dlpack_slots__or so because that is the correct term in Python C-API nomenclature.Some thoughts on the actual slots:
(My gut feeling is that lifetime guarantees are probably OKish in practice -- in the unlikely case that is really hard, I suspect one might be able to get away with default stream.)
void **streamptrunless you know the device type. So you need a way to query the device type independently. That is unless you attach the stream to the Tensor directly.dlpack_to_pyobjectis indeed a useful addition!DLPackTensorAllocator, need to think about it more.out=arrayandout=None.MemoryError(out of memory) andValueErrorin practice.(Unrelated, although I am slightly curious about the Pytorch speeds. It seemed to me that torch spends most of it's time inside some dispatching logic, I am sure this will help a little, but will it help, since I would think this dispatching happens much the same in the C++ layer? I mean, this should safe around 100ns per argument, although much more seems mainly due to forcing C rather than the other being intrinsically slower here.)
dalcinl commentedon Sep 16, 2025
I have a mayor concern about using capsules: they are way too hard to manipulate in pure Python code. Exposing function pointers as integers make it very easy to use ctypes or CFFI to wrap these pointers and use them in pure Python code. If capsules are used, then you have to deal with extracting the pointer out of the capsule. While it is doable, there is an extra layer of complexity on understanding and accessing the Python C/API and dealing with reference counting issues.
It took me a lot of work to get all the gory details right in mpi4py for testing purpose, and there are still a few obscure things related to Python object reference ownership that work simply because of ctypes magic.
https://github.com/mpi4py/mpi4py/blob/master/test/dlpackimpl.py#L287
All that being said, I'm not opposed to using capsules. Just wanted to point out that it is a bit annoying that what you would get from
__dlpack_slots__is hard to manipulate form pure Python code. While I understand that the primary motivation for DLPack is to import/export buffers to a foreign language (C/C++), it would be good to easily also support the native language (Python) with tools like ctypes or CFFI wrapping raw pointers. CPython is going that way with the introduction of the__buffer__dunder method.seberg commentedon Sep 16, 2025
Sure, I just prefer the extra type safety when it seems easy/fast enough. But either way it wouldn't be the end of the world here to use integers.
But note that the problem with
__dlpack__s capsule is really a DLPack problem. The issue is that it wanted (for historic reasons) an owning capsule which additionally adds the annoyance of both an owning and non-owning capsule adding layers of deallocation logic that is almost as annoying in C as it is in Python.This API will always return a static pointer, which is even specified already. So all of that ownership problem goes away. I.e. there is never a destructor, context, .... You do need
PyCapsule_NewandPyCapsule_GetPointerviactypesof course, but nothing else.Well,
__buffer__returns a memoryview, so it is fully type safe. (Although, I have a bit doubt that the choice of__releasebuffer_and forcing a memoryview return was good; I wish I had paid attention at the time.)tqchen commentedon Sep 16, 2025
Thanks for the discussions. Some thoughts here
Choice of Type for Dunder Attribute
I think the main discussion seems to boils down to using raw int or PyCapsule
B0: Use raw int
This is simple dunder and things are static
B1: Use PyCapsule
Looking at precedence from numpy, seems numpy exposesa PyCapsule named
_ARRAY_APIfor its C function table. The name__slots__may confuse with the python slots mechanism as the layout of struct may not strictly follow python object slots(although they serve similar purposes). Given numpy calls the function table "api", i think it maybe ok following this existing practices.How about we say that we require
__dlpack_exchange_api__to be a PyCapsule that stores static pointer(without destructor), and we can get it viaPyCapsule_GetPointer(__dlpack_exchange_api__, NULL), this is what numpy usedPyCapsule_GetPointer(__dlpack_exchange_api__, "dlpack_exchange_api"), add name to itOne possible argument for it is perhaps from typing pov PyCapsule type indicate external opaque pointer, and it follows a precedence(numpy practice)
Discussions
Personally I think we can go with B1b, B1a, or B0. Following numpy's practice perhaps B1a or B1b can be used, it does adds one level of indirection, so if the data structure is livefetched per call there can be overhead in the ctypes layer if things are implemented in this way. Love to get takes from others as well.
cc @rgommers @dalcinl @leofang
oleksandr-pavlyk commentedon Sep 17, 2025
+1 for using
__dlpack_exchange_api__name over__dlpack_slots__.I would suggest renaming
DLPackTensorAllocatortoDLPackManagedTensorCreateorDLPackManagedTensorNew, to better reflect that it creates new copy ofDLManagedTensorVersionedstruct.Even if the name is kept as is, this point about it creating a new copy of struct must be emphasized in the doc-string.
Ditto for
DLPackToPyObjectandDLPackFromPyObject.Since the purpose of
dlpack_from_py_objectis to enable faster kernel launches on arbitrary types representing CUDA device-accessible allocations, we either need standardize layout behind opaque pointerenv_stream, or this argument never sees adoption.kkraus14 commentedon Sep 17, 2025
Happy to open discussion in another issue related to stream ownership semantics / lifetime management if appropriate, let me know.
After a lot of discussion and consideration from the Apache Arrow community in building the Arrow C Device Data Interface, they decided on exposing an event pointer to use for stream / synchronization management: https://arrow.apache.org/docs/format/CDeviceDataInterface.html#c.ArrowDeviceArray.sync_event and that the lifetime of that event is to be controlled as part of the
releasefunction.If I remember correctly, the reason for the
__dlpack__python protocol using a consumer provided stream instead of a producer provided stream was because there wasn't confidence in frameworks being able to expose stream lifetime management / ownership semantics through the dlpack deleter function. I imagine we run into the same problem here with the current proposal?What are the lifetime guarantees of
optional_out_env_stream? Who is responsible for destroying that stream?tqchen commentedon Sep 17, 2025
Thanks @kkraus14 for the comment. You are right that thinking about stream management beyond the local scope is still hard.
In this case, we are thinking about the common pattern that major deep learning framework(e.g. PyTorch) and we are limiting the scope here in the same way as
__dlpack__protocolSo reflecting that status, stream ownership is not done by the DLPack itself, but instead managed by the "environment framework". The particular exchange
optional_out_env_streamguarantees that if we do dlpack exchange forcompute, the following computation should be carried out using the stream. However, the stream is not attached to the Tenaor/Array itself. The ownership of stream still belong to torch.tqchen commentedon Sep 17, 2025
@oleksandr-pavlyk, good pt about naming, perhaps we can rename to
DLPackManagedTensorAllocatorDLPackManagedTensorToPyObjectDLPackManagedTensorFromPyObjectI am not too sure about what you mean by layout of
env_stream, but in this case, it is supposed to be the native raw stream, akacudaStream_tin the case of cuda.7 remaining items
kkraus14 commentedon Sep 30, 2025
This seems very Python specific where I think we should reword this to indicate that the stream lifetime is expected to be managed outside of DLPack as opposed to specifically talking about a context manager.
Using a stream here still feels like the wrong approach to me versus having an optional Event member as part of the
DLManagedTensorVersionedstruct with lifetime / ownership semantics handled by the existing deleter function. I feel there's a number of shortcomings of the current approach in passing a stream:I would propose serving both
N0andN1from a single API (needs wordsmithing but hopefully gets the idea across 😅):TLDR:
DLManagedTensorVersionedand Event lifetime is controlled by existingDLManagedTensorVersioneddeleter functionDLManagedTensorVersionedand an optional stream expected to be used for consumer kernels.optional_out_current_streamand Event member are not NULL. Data is guaranteed to be safe to use in the stream but the Event can be waited on to access the data safely.optional_out_current_streamis not NULL and Event member is NULL, data is guaranteed to be safe to use in that stream but not guaranteed to be safe to use in any other stream.optional_out_current_streamis NULL and Event member is not NULL, then Event member must be waited on to access the data safely.optional_out_current_streamand Event member are NULL, then data is guaranteed to be safe to use from any Stream / is synchronized.This allows for passing Streams around nicely for use cases like Graph Capture but still supports the "ingestion" path where a producing library wants to share only data and not anything related to its execution environment.
tqchen commentedon Sep 30, 2025
Thanks @kkraus14 . I still think we would benefit from two explicit APIs that distinguishes N0 and N1.
DLPackManagedTensorFromPyObjectperhaps comes at a case that strongly streamlines N0 while it maybe hard to retrofit to N1.DLPackTensorAndStreamFromPyObject).Specifically coming from context of N0:
optional_out_last_active_streamcan be NULL, which means the consumer gives up query stream, instead relies on DLPackCurrentWorkStream query once. Which helps the critical loopIn summary, the N0 consumers have desire to minimize amount of event recording, sync decisions made.
Comparison of Proposals: Should the Event Recording Strategy Falls into Producer or Consumer
Now coming back to the two function proposal. I think in general the final achievable result is that that different from your proposal, except that in the case of
DLPackTensorAndStreamFromPyObjectthe overall decision/duty goes into the producer to decide on whether to allocate event, manage lifecycles and so on.While in the
DLPackManagedTensorFromPyObject+DLPackCurrentWorkStreamcase, the decision falls onto the consumer:optional_out_last_active_stream, and attach that during ingestion, that is effectively equivalent to the event recording process in N1, except thatFinally specific to note some of the concerns listed. "Shortcomings of the current approach in passing a stream":
We are limiting the possible scope of stream usage, specifically for N0, it is the current library invocation, for N1, only record during exchange, so there is no issue of dangling pointer as we are not retaining stream.
I think honest take here is that we do not want consumer to retain stream, instead consumer should only use that for the specific kernel invocation following exchange (in case of N0), or recored event/sync to its own stream.
That is true, however, i would argue such cases are corner case, as a matetr of fact, i cannot think of an existing framework that it happens. Note that when stream lifetime goes out and event is still provided, we are effectively saying one have to pre-record an event eagerly.
Note that if I am a framework implementer, likely what i would do when stream lifecycle goes out and i still like to have some visible effect to downstream, is to add a sync to some default stream, so future queries can retrun
I am not sure what is being meant here. I assume passing
optional_out_last_active_streamis OK for N1 use-cases to immediately create an event recording. For the case of N0 very likely a separate stream mechanism is used besides the thread default streamConsumer should record the event right away at the same thread of the producer after the query in the case of N1, so likely won't block unrelated producer work when the call returns to the producer.
tqchen commentedon Sep 30, 2025
I also want to followup on a possible details that needs to be call out. For stream context manager based framework like PyTorch. It is generally not possible to guarantee the data availability under the current working stream abitrary. Consider the code below:
In this case, the current working stream in the s1 context is s1. However, the above code do not guarantee that b_imported is synced to s1. Note that even if we replace mylib.kernel by a torch.kernel, it still causes a bug. So for frameworks that comes with stream context manager, the implicit expectation is that stream sync is done by the user, so a typical correct code looks like
So there must be some pre-context to the statement like the follows (which is not always true):
Instead, usually what is stated instead is that "usually the expectation is that the user will take responsibility to synchronize the data to the current working stream before hand, and for N0 usecases, it is desirable to run compute on the current stream".
Again given stream context and user-managed dependency is something that is common for our key use-cases, i think it is important to call it out, and have a path to optimize for that
kkraus14 commentedon Sep 30, 2025
I'm going to step away from this issue as I think the arguments are going in circles at this point and I'm not helping to make forward progress. Thanks for all of your hard work and efforts here, they are appreciated!
tqchen commentedon Oct 6, 2025
Following up on this, capturing more discussions. We have updated the proposal to focus on N0 needs. I think the proposal is getting to a ready state, please share your thoughts.
Thanks everyone for discussions so far
tqchen commentedon Oct 9, 2025
sending a note here, we are likely going to merge #174 soon
hpkfft commentedon Oct 9, 2025
I'm surprised this would be considered for merging into the standard this year. It seems that there has recently been significant development work, with at least one contributor having to step away from this issue. Shouldn't we see buy-in from stakeholders and at least tentative implementations before proceeding to standardization?
tqchen commentedon Oct 9, 2025
Thanks @hpkfft for the note. The original contention point is to support both N0 and N1, and as of now the latest update primarily focused on N0 while leaving N1 topics for future.
This is a chicken and egg problem, as we need to settle on at least one version of the standard before bring up the implementations in the downstreams stakeholders. We did discuss the proposal at the array api meeting a few weeks ago and generally the agreement is to go forward. In terms of development, we are already converging on the technical parts that focusing on N0.
Given the significant performance improvement it can bring, and further changes are still compatible through verisoning. I see it is overall postive to roll it out to make dlpack the de-facto choice for the N0 scenarios.
tqchen commentedon Oct 11, 2025
#174 is now merged, we will followup with 1.2 release after #177 get merged
[DLPack] C Functions for DLPack Speed Exchange and Stream Handling (#96)
tqchen commentedon Oct 11, 2025
tagged in https://github.com/dmlc/dlpack/releases/tag/v1.2
[DLPack] C Functions for DLPack Speed Exchange and Stream Handling (#…
[DLPack] C Functions for DLPack Speed Exchange and Stream Handling (p…
[DLPack] C Functions for DLPack Speed Exchange and Stream Handling (#…