ctypes-dlpack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctypes_dlpack/__init__.py +61 -0
- ctypes_dlpack/_c_api.py +210 -0
- ctypes_dlpack/_dlpack.py +524 -0
- ctypes_dlpack-0.1.0.dist-info/METADATA +55 -0
- ctypes_dlpack-0.1.0.dist-info/RECORD +8 -0
- ctypes_dlpack-0.1.0.dist-info/WHEEL +5 -0
- ctypes_dlpack-0.1.0.dist-info/licenses/LICENSE +28 -0
- ctypes_dlpack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from ._c_api import (
|
|
2
|
+
DLPACK_FLAG_BITMASK_IS_COPIED,
|
|
3
|
+
DLPACK_FLAG_BITMASK_IS_SUBBYTE_TYPE_PADDED,
|
|
4
|
+
DLPACK_FLAG_BITMASK_READ_ONLY,
|
|
5
|
+
DLPACK_MAJOR_VERSION,
|
|
6
|
+
DLPACK_MINOR_VERSION,
|
|
7
|
+
DLDataType,
|
|
8
|
+
DLDataTypeCode,
|
|
9
|
+
DLDevice,
|
|
10
|
+
DLDeviceType,
|
|
11
|
+
DLManagedTensor,
|
|
12
|
+
DLManagedTensorVersioned,
|
|
13
|
+
DLPackCurrentWorkStream,
|
|
14
|
+
DLPackDLTensorFromPyObjectNoSync,
|
|
15
|
+
DLPackExchangeAPI,
|
|
16
|
+
DLPackExchangeAPIHeader,
|
|
17
|
+
DLPackManagedTensorAllocator,
|
|
18
|
+
DLPackManagedTensorFromPyObjectNoSync,
|
|
19
|
+
DLPackManagedTensorToPyObjectNoSync,
|
|
20
|
+
DLPackVersion,
|
|
21
|
+
DLTensor,
|
|
22
|
+
)
|
|
23
|
+
from ._dlpack import DLPackArray, array_as_dlpack
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DLPACK_FLAG_BITMASK_IS_COPIED",
|
|
31
|
+
"DLPACK_FLAG_BITMASK_IS_SUBBYTE_TYPE_PADDED",
|
|
32
|
+
"DLPACK_FLAG_BITMASK_READ_ONLY",
|
|
33
|
+
"DLPACK_MAJOR_VERSION",
|
|
34
|
+
"DLPACK_MINOR_VERSION",
|
|
35
|
+
"DLPackManagedTensorAllocator",
|
|
36
|
+
"DLPackManagedTensorFromPyObjectNoSync",
|
|
37
|
+
"DLPackDLTensorFromPyObjectNoSync",
|
|
38
|
+
"DLPackCurrentWorkStream",
|
|
39
|
+
"DLPackManagedTensorToPyObjectNoSync",
|
|
40
|
+
"DLDeviceType",
|
|
41
|
+
"DLDataTypeCode",
|
|
42
|
+
"DLPackVersion",
|
|
43
|
+
"DLDevice",
|
|
44
|
+
"DLDataType",
|
|
45
|
+
"DLTensor",
|
|
46
|
+
"DLManagedTensor",
|
|
47
|
+
"DLManagedTensorVersioned",
|
|
48
|
+
"DLPackExchangeAPIHeader",
|
|
49
|
+
"DLPackExchangeAPI",
|
|
50
|
+
"DLPackArray",
|
|
51
|
+
"array_as_dlpack",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
for _name in __all__:
|
|
56
|
+
_value = globals()[_name]
|
|
57
|
+
if getattr(_value, "__module__", None) == "ctypes_dlpack._c_api":
|
|
58
|
+
_value.__module__ = __name__
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
del _name, _value
|
ctypes_dlpack/_c_api.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
import ctypes
|
|
3
|
+
|
|
4
|
+
DLPACK_FLAG_BITMASK_IS_COPIED = 1 << 1
|
|
5
|
+
DLPACK_FLAG_BITMASK_IS_SUBBYTE_TYPE_PADDED = 1 << 2
|
|
6
|
+
DLPACK_FLAG_BITMASK_READ_ONLY = 1 << 0
|
|
7
|
+
DLPACK_MAJOR_VERSION = 1
|
|
8
|
+
DLPACK_MINOR_VERSION = 3
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _EnumType(type(ctypes.c_int32)):
|
|
12
|
+
def __new__(metacls, name, bases, namespace):
|
|
13
|
+
if "_members_" not in namespace:
|
|
14
|
+
members = {}
|
|
15
|
+
for key, value in namespace.items():
|
|
16
|
+
if not key.startswith("_"):
|
|
17
|
+
members[key] = value
|
|
18
|
+
namespace["_members_"] = members
|
|
19
|
+
else:
|
|
20
|
+
members = namespace["_members_"]
|
|
21
|
+
|
|
22
|
+
namespace["_reverse_map_"] = {v: k for k, v in members.items()}
|
|
23
|
+
return type(ctypes.c_int32).__new__(metacls, name, bases, namespace)
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
return f"<Enum {self.__name__}>"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _Enum(ctypes.c_int32, metaclass=_EnumType):
|
|
30
|
+
_members_ = {}
|
|
31
|
+
|
|
32
|
+
def __repr__(self):
|
|
33
|
+
value_name = self._reverse_map_.get(self.value, str(self.value))
|
|
34
|
+
return f"{self.__class__.__name__}.{value_name}"
|
|
35
|
+
|
|
36
|
+
def __eq__(self, other):
|
|
37
|
+
if isinstance(other, int):
|
|
38
|
+
return self.value == other
|
|
39
|
+
if type(self) is type(other):
|
|
40
|
+
return self.value == other.value
|
|
41
|
+
return NotImplemented
|
|
42
|
+
|
|
43
|
+
def __hash__(self):
|
|
44
|
+
return hash(self.value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DLDeviceType(_Enum):
|
|
48
|
+
pass
|
|
49
|
+
kDLCPU = 1
|
|
50
|
+
kDLCUDA = 2
|
|
51
|
+
kDLCUDAHost = 3
|
|
52
|
+
kDLOpenCL = 4
|
|
53
|
+
kDLVulkan = 7
|
|
54
|
+
kDLMetal = 8
|
|
55
|
+
kDLVPI = 9
|
|
56
|
+
kDLROCM = 10
|
|
57
|
+
kDLROCMHost = 11
|
|
58
|
+
kDLExtDev = 12
|
|
59
|
+
kDLCUDAManaged = 13
|
|
60
|
+
kDLOneAPI = 14
|
|
61
|
+
kDLWebGPU = 15
|
|
62
|
+
kDLHexagon = 16
|
|
63
|
+
kDLMAIA = 17
|
|
64
|
+
kDLTrn = 18
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DLDataTypeCode(_Enum):
|
|
68
|
+
pass
|
|
69
|
+
kDLInt = 0
|
|
70
|
+
kDLUInt = 1
|
|
71
|
+
kDLFloat = 2
|
|
72
|
+
kDLOpaqueHandle = 3
|
|
73
|
+
kDLBfloat = 4
|
|
74
|
+
kDLComplex = 5
|
|
75
|
+
kDLBool = 6
|
|
76
|
+
kDLFloat8_e3m4 = 7
|
|
77
|
+
kDLFloat8_e4m3 = 8
|
|
78
|
+
kDLFloat8_e4m3b11fnuz = 9
|
|
79
|
+
kDLFloat8_e4m3fn = 10
|
|
80
|
+
kDLFloat8_e4m3fnuz = 11
|
|
81
|
+
kDLFloat8_e5m2 = 12
|
|
82
|
+
kDLFloat8_e5m2fnuz = 13
|
|
83
|
+
kDLFloat8_e8m0fnu = 14
|
|
84
|
+
kDLFloat6_e2m3fn = 15
|
|
85
|
+
kDLFloat6_e3m2fn = 16
|
|
86
|
+
kDLFloat4_e2m1fn = 17
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class DLPackVersion(ctypes.Structure):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DLDevice(ctypes.Structure):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class DLDataType(ctypes.Structure):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DLTensor(ctypes.Structure):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DLManagedTensor(ctypes.Structure):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DLManagedTensorVersioned(ctypes.Structure):
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DLPackExchangeAPIHeader(ctypes.Structure):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class DLPackExchangeAPI(ctypes.Structure):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
DLPackManagedTensorAllocator = ctypes.CFUNCTYPE(
|
|
122
|
+
ctypes.c_int,
|
|
123
|
+
ctypes.POINTER(DLTensor),
|
|
124
|
+
ctypes.POINTER(ctypes.POINTER(DLManagedTensorVersioned)),
|
|
125
|
+
ctypes.c_void_p,
|
|
126
|
+
ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
DLPackManagedTensorFromPyObjectNoSync = ctypes.CFUNCTYPE(
|
|
131
|
+
ctypes.c_int,
|
|
132
|
+
ctypes.c_void_p,
|
|
133
|
+
ctypes.POINTER(ctypes.POINTER(DLManagedTensorVersioned)),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
DLPackDLTensorFromPyObjectNoSync = ctypes.CFUNCTYPE(
|
|
138
|
+
ctypes.c_int,
|
|
139
|
+
ctypes.c_void_p,
|
|
140
|
+
ctypes.POINTER(DLTensor),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
DLPackCurrentWorkStream = ctypes.CFUNCTYPE(
|
|
145
|
+
ctypes.c_int,
|
|
146
|
+
DLDeviceType,
|
|
147
|
+
ctypes.c_int32,
|
|
148
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
DLPackManagedTensorToPyObjectNoSync = ctypes.CFUNCTYPE(
|
|
153
|
+
ctypes.c_int,
|
|
154
|
+
ctypes.POINTER(DLManagedTensorVersioned),
|
|
155
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
DLPackVersion._fields_ = [
|
|
159
|
+
("major", ctypes.c_uint32),
|
|
160
|
+
("minor", ctypes.c_uint32),
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
DLDevice._fields_ = [
|
|
164
|
+
("device_type", DLDeviceType),
|
|
165
|
+
("device_id", ctypes.c_int32),
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
DLDataType._fields_ = [
|
|
169
|
+
("code", ctypes.c_uint8),
|
|
170
|
+
("bits", ctypes.c_uint8),
|
|
171
|
+
("lanes", ctypes.c_uint16),
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
DLTensor._fields_ = [
|
|
175
|
+
("data", ctypes.c_void_p),
|
|
176
|
+
("device", DLDevice),
|
|
177
|
+
("ndim", ctypes.c_int32),
|
|
178
|
+
("dtype", DLDataType),
|
|
179
|
+
("shape", ctypes.POINTER(ctypes.c_int64)),
|
|
180
|
+
("strides", ctypes.POINTER(ctypes.c_int64)),
|
|
181
|
+
("byte_offset", ctypes.c_uint64),
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
DLManagedTensor._fields_ = [
|
|
185
|
+
("dl_tensor", DLTensor),
|
|
186
|
+
("manager_ctx", ctypes.c_void_p),
|
|
187
|
+
("deleter", ctypes.CFUNCTYPE(None, ctypes.POINTER(DLManagedTensor))),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
DLManagedTensorVersioned._fields_ = [
|
|
191
|
+
("version", DLPackVersion),
|
|
192
|
+
("manager_ctx", ctypes.c_void_p),
|
|
193
|
+
("deleter", ctypes.CFUNCTYPE(None, ctypes.POINTER(DLManagedTensorVersioned))),
|
|
194
|
+
("flags", ctypes.c_uint64),
|
|
195
|
+
("dl_tensor", DLTensor),
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
DLPackExchangeAPIHeader._fields_ = [
|
|
199
|
+
("version", DLPackVersion),
|
|
200
|
+
("prev_api", ctypes.POINTER(DLPackExchangeAPIHeader)),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
DLPackExchangeAPI._fields_ = [
|
|
204
|
+
("header", DLPackExchangeAPIHeader),
|
|
205
|
+
("managed_tensor_allocator", DLPackManagedTensorAllocator),
|
|
206
|
+
("managed_tensor_from_py_object_no_sync", DLPackManagedTensorFromPyObjectNoSync),
|
|
207
|
+
("managed_tensor_to_py_object_no_sync", DLPackManagedTensorToPyObjectNoSync),
|
|
208
|
+
("dltensor_from_py_object_no_sync", DLPackDLTensorFromPyObjectNoSync),
|
|
209
|
+
("current_work_stream", DLPackCurrentWorkStream),
|
|
210
|
+
]
|
ctypes_dlpack/_dlpack.py
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
|
|
3
|
+
from ._c_api import (
|
|
4
|
+
DLDevice,
|
|
5
|
+
DLManagedTensor,
|
|
6
|
+
DLManagedTensorVersioned,
|
|
7
|
+
DLPackVersion,
|
|
8
|
+
DLTensor,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
PYTHON_API = ctypes.pythonapi
|
|
13
|
+
|
|
14
|
+
# Configure Python C-API entry points once, then reuse them from hot paths.
|
|
15
|
+
PYTHON_API.PyCapsule_GetPointer.restype = ctypes.c_void_p
|
|
16
|
+
PYTHON_API.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
|
|
17
|
+
|
|
18
|
+
PYTHON_API.PyCapsule_SetName.restype = ctypes.c_int
|
|
19
|
+
PYTHON_API.PyCapsule_SetName.argtypes = [ctypes.py_object, ctypes.c_char_p]
|
|
20
|
+
|
|
21
|
+
PYTHON_API.PyCapsule_GetName.restype = ctypes.c_char_p
|
|
22
|
+
PYTHON_API.PyCapsule_GetName.argtypes = [ctypes.py_object]
|
|
23
|
+
|
|
24
|
+
PYTHON_API.PyMem_RawMalloc.restype = ctypes.c_void_p
|
|
25
|
+
PYTHON_API.PyMem_RawMalloc.argtypes = [ctypes.c_size_t]
|
|
26
|
+
|
|
27
|
+
PYTHON_API.PyMem_RawFree.restype = None
|
|
28
|
+
PYTHON_API.PyMem_RawFree.argtypes = [ctypes.c_void_p]
|
|
29
|
+
|
|
30
|
+
PYTHON_API.PyCapsule_New.restype = ctypes.py_object
|
|
31
|
+
PYTHON_API.PyCapsule_New.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p]
|
|
32
|
+
|
|
33
|
+
PYTHON_API.PyErr_Fetch.restype = None
|
|
34
|
+
PYTHON_API.PyErr_Fetch.argtypes = [
|
|
35
|
+
ctypes.POINTER(ctypes.py_object),
|
|
36
|
+
ctypes.POINTER(ctypes.py_object),
|
|
37
|
+
ctypes.POINTER(ctypes.py_object),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
PYTHON_API.PyErr_Clear.restype = None
|
|
41
|
+
PYTHON_API.PyErr_Clear.argtypes = []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _fetch_pyerr():
|
|
45
|
+
"""Detach and return the current C-API exception instance, or ``None``.
|
|
46
|
+
|
|
47
|
+
``PyErr_Fetch`` clears the thread-local exception state, so the caller is free to
|
|
48
|
+
raise a fresh exception without CPython discarding the original one.
|
|
49
|
+
"""
|
|
50
|
+
etype = ctypes.py_object()
|
|
51
|
+
evalue = ctypes.py_object()
|
|
52
|
+
etb = ctypes.py_object()
|
|
53
|
+
PYTHON_API.PyErr_Fetch(ctypes.byref(etype), ctypes.byref(evalue), ctypes.byref(etb))
|
|
54
|
+
return evalue.value or None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ``PyCapsule_GetName``/``PyCapsule_GetPointer`` re-bound with a raw ``c_void_p``
|
|
58
|
+
# argument. These are needed from inside ``_versioned_capsule_destructor``:
|
|
59
|
+
# CPython invokes the destructor during ``PyCapsule dealloc`` while the capsule
|
|
60
|
+
# refcount is already 0, so the argument cannot be passed as ``py_object``
|
|
61
|
+
# (which would INCREF then DECREF back to 0, recursively re-triggering
|
|
62
|
+
# ``dealloc``). The raw pointer is obtained from the cached symbol address.
|
|
63
|
+
_PyCapsule_GetName_c_void_p = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_void_p)(
|
|
64
|
+
ctypes.cast(PYTHON_API.PyCapsule_GetName, ctypes.c_void_p).value
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
_PyCapsule_GetPointer_c_void_p = ctypes.CFUNCTYPE(
|
|
68
|
+
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p
|
|
69
|
+
)(ctypes.cast(PYTHON_API.PyCapsule_GetPointer, ctypes.c_void_p).value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
DLPACK_VERSIONED_NAME = b"dltensor_versioned"
|
|
73
|
+
USED_DLPACK_VERSIONED_NAME = b"used_dltensor_versioned"
|
|
74
|
+
DLPACK_NAME = b"dltensor"
|
|
75
|
+
USED_DLPACK_NAME = b"used_dltensor"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _versioned_capsule_destructor(capsule_ptr):
|
|
79
|
+
"""Release the ``DLManagedTensorVersioned`` held by a capsule.
|
|
80
|
+
|
|
81
|
+
This is invoked by CPython when the capsule is garbage collected without having
|
|
82
|
+
been consumed by a ``__dlpack__`` importer (i.e. its name is still
|
|
83
|
+
``dltensor_versioned``). Capsules renamed to ``used_dltensor_versioned`` have
|
|
84
|
+
already been taken over by an importer and are left untouched.
|
|
85
|
+
|
|
86
|
+
The argument is a raw ``void*`` (the capsule pointer) rather than
|
|
87
|
+
``py_object`` because CPython invokes the destructor during ``dealloc`` with
|
|
88
|
+
the capsule already at refcount 0: wrapping it as ``py_object`` would INCREF
|
|
89
|
+
then DECREF it back to 0, recursively re-triggering ``dealloc``.
|
|
90
|
+
"""
|
|
91
|
+
if not capsule_ptr:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
name = _PyCapsule_GetName_c_void_p(capsule_ptr)
|
|
95
|
+
if name != DLPACK_VERSIONED_NAME:
|
|
96
|
+
# Clear any C-API exception (e.g. set by ``GetName`` on failure)
|
|
97
|
+
PYTHON_API.PyErr_Clear()
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
ptr = _PyCapsule_GetPointer_c_void_p(capsule_ptr, DLPACK_VERSIONED_NAME)
|
|
101
|
+
if not ptr:
|
|
102
|
+
PYTHON_API.PyErr_Clear()
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
versioned_ptr = ctypes.cast(ptr, ctypes.POINTER(DLManagedTensorVersioned))
|
|
106
|
+
versioned = versioned_ptr.contents
|
|
107
|
+
if versioned.deleter:
|
|
108
|
+
versioned.deleter(versioned_ptr)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Keep a reference so the CFUNCTYPE is not garbage-collected while CPython holds
|
|
112
|
+
# the destructor pointer inside the capsule.
|
|
113
|
+
_VERSIONED_CAPSULE_DESTRUCTOR_C = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(
|
|
114
|
+
_versioned_capsule_destructor
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _unversioned_capsule_destructor(capsule_ptr):
|
|
119
|
+
"""Release the ``DLManagedTensor`` held by a legacy capsule.
|
|
120
|
+
|
|
121
|
+
See ``_versioned_capsule_destructor`` for why the argument is a raw
|
|
122
|
+
``void*`` instead of ``py_object``.
|
|
123
|
+
"""
|
|
124
|
+
if not capsule_ptr:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
name = _PyCapsule_GetName_c_void_p(capsule_ptr)
|
|
128
|
+
if name != DLPACK_NAME:
|
|
129
|
+
# Clear any C-API exception (see ``_versioned_capsule_destructor``).
|
|
130
|
+
PYTHON_API.PyErr_Clear()
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
ptr = _PyCapsule_GetPointer_c_void_p(capsule_ptr, DLPACK_NAME)
|
|
134
|
+
if not ptr:
|
|
135
|
+
PYTHON_API.PyErr_Clear()
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
unversioned_ptr = ctypes.cast(ptr, ctypes.POINTER(DLManagedTensor))
|
|
139
|
+
unversioned = unversioned_ptr.contents
|
|
140
|
+
if unversioned.deleter:
|
|
141
|
+
unversioned.deleter(unversioned_ptr)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Keep a reference so the CFUNCTYPE is not garbage-collected while CPython holds
|
|
145
|
+
# the destructor pointer inside the capsule.
|
|
146
|
+
_UNVERSIONED_CAPSULE_DESTRUCTOR_C = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(
|
|
147
|
+
_unversioned_capsule_destructor
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@ctypes.CFUNCTYPE(None, ctypes.POINTER(DLManagedTensorVersioned))
|
|
152
|
+
def _versioned_wrapper_deleter(versioned_ptr):
|
|
153
|
+
"""Forward to the legacy deleter, then free the wrapper allocation."""
|
|
154
|
+
if not versioned_ptr:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
versioned = versioned_ptr.contents
|
|
158
|
+
original_ptr = ctypes.cast(versioned.manager_ctx, ctypes.POINTER(DLManagedTensor))
|
|
159
|
+
|
|
160
|
+
if original_ptr and original_ptr.contents.deleter:
|
|
161
|
+
original_ptr.contents.deleter(original_ptr)
|
|
162
|
+
|
|
163
|
+
PYTHON_API.PyMem_RawFree(ctypes.cast(versioned_ptr, ctypes.c_void_p))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@ctypes.CFUNCTYPE(None, ctypes.POINTER(DLManagedTensor))
|
|
167
|
+
def _unversioned_wrapper_deleter(unversioned_ptr):
|
|
168
|
+
"""Forward to the versioned deleter, then free the wrapper allocation."""
|
|
169
|
+
if not unversioned_ptr:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
unversioned = unversioned_ptr.contents
|
|
173
|
+
original_ptr = ctypes.cast(
|
|
174
|
+
unversioned.manager_ctx, ctypes.POINTER(DLManagedTensorVersioned)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if original_ptr and original_ptr.contents.deleter:
|
|
178
|
+
original_ptr.contents.deleter(original_ptr)
|
|
179
|
+
|
|
180
|
+
PYTHON_API.PyMem_RawFree(ctypes.cast(unversioned_ptr, ctypes.c_void_p))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _wrap_unversioned_as_versioned(unversioned_ptr):
|
|
184
|
+
"""Wrap a legacy DLManagedTensor pointer in a versioned allocation."""
|
|
185
|
+
versioned_size = ctypes.sizeof(DLManagedTensorVersioned)
|
|
186
|
+
versioned_mem = PYTHON_API.PyMem_RawMalloc(versioned_size)
|
|
187
|
+
if not versioned_mem:
|
|
188
|
+
raise MemoryError("failed to allocate DLManagedTensorVersioned")
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
versioned_ptr = ctypes.cast(
|
|
192
|
+
versioned_mem, ctypes.POINTER(DLManagedTensorVersioned)
|
|
193
|
+
)
|
|
194
|
+
versioned = versioned_ptr.contents
|
|
195
|
+
|
|
196
|
+
versioned.version = DLPackVersion()
|
|
197
|
+
versioned.version.major = 1
|
|
198
|
+
versioned.version.minor = 0
|
|
199
|
+
versioned.manager_ctx = ctypes.cast(unversioned_ptr, ctypes.c_void_p)
|
|
200
|
+
versioned.deleter = _versioned_wrapper_deleter
|
|
201
|
+
versioned.flags = 0
|
|
202
|
+
|
|
203
|
+
# DLManagedTensor starts with a DLTensor, so copying from the struct base
|
|
204
|
+
# address into versioned.dl_tensor preserves the payload layout.
|
|
205
|
+
ctypes.memmove(
|
|
206
|
+
ctypes.addressof(versioned.dl_tensor),
|
|
207
|
+
unversioned_ptr,
|
|
208
|
+
ctypes.sizeof(DLTensor),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return versioned_ptr
|
|
212
|
+
except BaseException:
|
|
213
|
+
PYTHON_API.PyMem_RawFree(versioned_mem)
|
|
214
|
+
raise
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _wrap_versioned_as_unversioned(versioned_ptr):
|
|
218
|
+
"""Wrap a ``DLManagedTensorVersioned`` pointer in a legacy allocation.
|
|
219
|
+
|
|
220
|
+
This is used to expose a versioned tensor to importers that only support the
|
|
221
|
+
unversioned DLPack protocol (``max_version`` is ``None`` or has a major
|
|
222
|
+
version of ``0``).
|
|
223
|
+
"""
|
|
224
|
+
unversioned_size = ctypes.sizeof(DLManagedTensor)
|
|
225
|
+
unversioned_mem = PYTHON_API.PyMem_RawMalloc(unversioned_size)
|
|
226
|
+
if not unversioned_mem:
|
|
227
|
+
raise MemoryError("failed to allocate DLManagedTensor")
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
unversioned_ptr = ctypes.cast(unversioned_mem, ctypes.POINTER(DLManagedTensor))
|
|
231
|
+
unversioned = unversioned_ptr.contents
|
|
232
|
+
|
|
233
|
+
unversioned.manager_ctx = ctypes.cast(versioned_ptr, ctypes.c_void_p)
|
|
234
|
+
unversioned.deleter = _unversioned_wrapper_deleter
|
|
235
|
+
|
|
236
|
+
# Both structs embed a ``DLTensor`` with identical layout; copy the
|
|
237
|
+
# payload from the versioned struct into the legacy one.
|
|
238
|
+
ctypes.memmove(
|
|
239
|
+
ctypes.addressof(unversioned.dl_tensor),
|
|
240
|
+
ctypes.addressof(versioned_ptr.contents.dl_tensor),
|
|
241
|
+
ctypes.sizeof(DLTensor),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return unversioned_ptr
|
|
245
|
+
except BaseException:
|
|
246
|
+
PYTHON_API.PyMem_RawFree(unversioned_mem)
|
|
247
|
+
raise
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _make_dlpack_versioned_capsule(dl_managed_versioned_ptr):
|
|
251
|
+
ptr_value = ctypes.cast(dl_managed_versioned_ptr, ctypes.c_void_p).value
|
|
252
|
+
if not ptr_value:
|
|
253
|
+
raise ValueError("DLManagedTensorVersioned pointer is null")
|
|
254
|
+
|
|
255
|
+
destructor = ctypes.cast(_VERSIONED_CAPSULE_DESTRUCTOR_C, ctypes.c_void_p)
|
|
256
|
+
return PYTHON_API.PyCapsule_New(ptr_value, DLPACK_VERSIONED_NAME, destructor)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _make_dlpack_capsule(dl_managed_ptr):
|
|
260
|
+
ptr_value = ctypes.cast(dl_managed_ptr, ctypes.c_void_p).value
|
|
261
|
+
if not ptr_value:
|
|
262
|
+
raise ValueError("DLManagedTensor pointer is null")
|
|
263
|
+
|
|
264
|
+
destructor = ctypes.cast(_UNVERSIONED_CAPSULE_DESTRUCTOR_C, ctypes.c_void_p)
|
|
265
|
+
return PYTHON_API.PyCapsule_New(ptr_value, DLPACK_NAME, destructor)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _call_dlpack_with_fallbacks(array, stream, max_version, dl_device, copy):
|
|
269
|
+
"""Try multiple __dlpack__ signatures for broad compatibility.
|
|
270
|
+
|
|
271
|
+
This tries both the ``2023.12`` array API signature (with ``max_version``,
|
|
272
|
+
``dl_device``, and ``copy`` kwargs), as well as the legacy signature (with only
|
|
273
|
+
``stream``). The first signature that does not raise a ``TypeError`` (due to unknown
|
|
274
|
+
keyword arguments) is used, and the resulting capsule is returned.
|
|
275
|
+
"""
|
|
276
|
+
last_error = None
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
return array.__dlpack__(
|
|
280
|
+
stream=stream,
|
|
281
|
+
max_version=max_version,
|
|
282
|
+
dl_device=dl_device,
|
|
283
|
+
copy=copy,
|
|
284
|
+
)
|
|
285
|
+
except TypeError as error:
|
|
286
|
+
last_error = error
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
return array.__dlpack__(stream=stream)
|
|
290
|
+
except TypeError as error:
|
|
291
|
+
last_error = error
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
return array.__dlpack__()
|
|
295
|
+
except TypeError as error:
|
|
296
|
+
last_error = error
|
|
297
|
+
|
|
298
|
+
raise RuntimeError("__dlpack__ call failed") from last_error
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class DLPackArray:
|
|
302
|
+
"""
|
|
303
|
+
Take a ctypes pointer to a ``DLManagedTensorVersioned`` and wrap it in a Python
|
|
304
|
+
object that implements the ``__dlpack__`` protocol. This allows the pointer to be
|
|
305
|
+
passed to other libraries that support DLPack, while ensuring that the pointer is
|
|
306
|
+
only used once and that the underlying memory is freed when the object is garbage
|
|
307
|
+
collected.
|
|
308
|
+
|
|
309
|
+
>>> import numpy as np
|
|
310
|
+
>>> import torch
|
|
311
|
+
>>> # Get the pointer in some way
|
|
312
|
+
>>> pointer = ...
|
|
313
|
+
>>> array = DLPackArray(pointer)
|
|
314
|
+
>>> # the array can be used with `from_dlpack`
|
|
315
|
+
>>> use_numpy = True
|
|
316
|
+
>>> if use_numpy:
|
|
317
|
+
... np_array = np.from_dlpack(array)
|
|
318
|
+
... print(np_array.shape)
|
|
319
|
+
... else:
|
|
320
|
+
... torch_tensor = torch.from_dlpack(array)
|
|
321
|
+
... print(torch_tensor.shape)
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
def __init__(self, pointer: ctypes.POINTER(DLManagedTensorVersioned)):
|
|
325
|
+
"""
|
|
326
|
+
:param pointer: pointer to a ``DLManagedTensorVersioned`` to be re-exposed as
|
|
327
|
+
Python object following the ``__dlpack__`` protocol.
|
|
328
|
+
"""
|
|
329
|
+
if not isinstance(pointer, ctypes.POINTER(DLManagedTensorVersioned)):
|
|
330
|
+
raise TypeError(
|
|
331
|
+
"`pointer` must be a ctypes.POINTER(DLManagedTensorVersioned), "
|
|
332
|
+
f"got {type(pointer).__name__}"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if not pointer:
|
|
336
|
+
raise ValueError("`pointer` is null")
|
|
337
|
+
|
|
338
|
+
self._pointer = pointer
|
|
339
|
+
|
|
340
|
+
def __del__(self):
|
|
341
|
+
pointer = getattr(self, "_pointer", None)
|
|
342
|
+
if pointer is None:
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
self._pointer = None
|
|
346
|
+
if pointer.contents.deleter:
|
|
347
|
+
pointer.contents.deleter(pointer)
|
|
348
|
+
|
|
349
|
+
def __dlpack__(
|
|
350
|
+
self,
|
|
351
|
+
stream: int | None = None,
|
|
352
|
+
max_version: tuple[int, int] | None = None,
|
|
353
|
+
dl_device: tuple[int, int] | None = None,
|
|
354
|
+
copy: bool | None = None,
|
|
355
|
+
):
|
|
356
|
+
pointer = self._pointer
|
|
357
|
+
if pointer is None:
|
|
358
|
+
raise RuntimeError("can not call __dlpack__ twice on DLPackArray")
|
|
359
|
+
|
|
360
|
+
if stream is not None:
|
|
361
|
+
raise RuntimeError("only `stream=None` is currently supported")
|
|
362
|
+
|
|
363
|
+
if dl_device is not None and dl_device != self.__dlpack_device__():
|
|
364
|
+
raise BufferError("device conversion is not supported")
|
|
365
|
+
|
|
366
|
+
if copy is None:
|
|
367
|
+
copy = False
|
|
368
|
+
|
|
369
|
+
if copy:
|
|
370
|
+
raise BufferError("only `copy=False` is supported")
|
|
371
|
+
|
|
372
|
+
if max_version is None or max_version[0] == 0:
|
|
373
|
+
# The caller only supports the unversioned DLPack protocol (or did
|
|
374
|
+
# not request a specific version): wrap our versioned tensor into a
|
|
375
|
+
# legacy ``DLManagedTensor`` capsule.
|
|
376
|
+
unversioned_ptr = _wrap_versioned_as_unversioned(pointer)
|
|
377
|
+
result = _make_dlpack_capsule(unversioned_ptr)
|
|
378
|
+
else:
|
|
379
|
+
result = _make_dlpack_versioned_capsule(pointer)
|
|
380
|
+
|
|
381
|
+
self._pointer = None
|
|
382
|
+
return result
|
|
383
|
+
|
|
384
|
+
def __dlpack_device__(self):
|
|
385
|
+
if self._pointer is None:
|
|
386
|
+
raise RuntimeError("can not query __dlpack_device__ after __dlpack__")
|
|
387
|
+
|
|
388
|
+
device = self._pointer[0].dl_tensor.device
|
|
389
|
+
return (device.device_type.value, device.device_id)
|
|
390
|
+
|
|
391
|
+
def __repr__(self):
|
|
392
|
+
if self._pointer is None:
|
|
393
|
+
return "<DLPackArray (consumed)>"
|
|
394
|
+
|
|
395
|
+
return "<DLPackArray>"
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def array_as_dlpack(
|
|
399
|
+
array,
|
|
400
|
+
*,
|
|
401
|
+
stream=None,
|
|
402
|
+
max_version: DLPackVersion | tuple[int, int] | None = None,
|
|
403
|
+
dl_device: DLDevice | tuple[int, int] | None = None,
|
|
404
|
+
copy: bool | None = None,
|
|
405
|
+
) -> ctypes.POINTER(DLManagedTensorVersioned):
|
|
406
|
+
"""
|
|
407
|
+
Get a ctypes pointer to a ``DLManagedTensorVersioned`` struct from any array
|
|
408
|
+
implementing the ``__dlpack__`` protocol. See
|
|
409
|
+
https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html
|
|
410
|
+
for the protocol specification.
|
|
411
|
+
|
|
412
|
+
This function will try to work with older version of the ``__dlpack__`` protocol,
|
|
413
|
+
wrapping the unversioned ``DLManagedTensor`` returned into a
|
|
414
|
+
``DLManagedTensorVersioned``. Some arguments might be ignored in this case, since
|
|
415
|
+
they do not have an equivalent.
|
|
416
|
+
|
|
417
|
+
:param array: any array type that implements the ``__dlpack__`` protocol
|
|
418
|
+
:param stream: the CUDA or ROCm synchronization stream, passed as-is to the
|
|
419
|
+
``__dlpack__`` method of ``array``.
|
|
420
|
+
:param max_version: the maximum DLPack version that the caller of this function
|
|
421
|
+
supports. If this is a ``DLPackVersion``, it is converted to the 2-tuple
|
|
422
|
+
expected by ``__dlpack__``.
|
|
423
|
+
:param dl_device: the DLPack device type. Default is ``None``, meaning the exported
|
|
424
|
+
pointer should be on the same device as ``array``. If this is a ``DLDevice``, it
|
|
425
|
+
is converted to the 2-tuple expected by ``__dlpack__``.
|
|
426
|
+
:param copy: boolean indicating whether or not to copy the input.
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
if max_version is None:
|
|
430
|
+
max_version_tuple = None
|
|
431
|
+
elif isinstance(max_version, DLPackVersion):
|
|
432
|
+
max_version_tuple = (max_version.major, max_version.minor)
|
|
433
|
+
elif (
|
|
434
|
+
isinstance(max_version, tuple)
|
|
435
|
+
and len(max_version) == 2
|
|
436
|
+
and isinstance(max_version[0], int)
|
|
437
|
+
and isinstance(max_version[1], int)
|
|
438
|
+
):
|
|
439
|
+
max_version_tuple = max_version
|
|
440
|
+
else:
|
|
441
|
+
raise TypeError(
|
|
442
|
+
"`max_version` must be a DLPackVersion, a 2-tuple of ints, or None; "
|
|
443
|
+
f"got {type(max_version).__name__}"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if dl_device is None:
|
|
447
|
+
dl_device_tuple = None
|
|
448
|
+
elif isinstance(dl_device, DLDevice):
|
|
449
|
+
dl_device_tuple = (int(dl_device.device_type.value), dl_device.device_id)
|
|
450
|
+
elif (
|
|
451
|
+
isinstance(dl_device, tuple)
|
|
452
|
+
and len(dl_device) == 2
|
|
453
|
+
and isinstance(dl_device[0], int)
|
|
454
|
+
and isinstance(dl_device[1], int)
|
|
455
|
+
):
|
|
456
|
+
dl_device_tuple = dl_device
|
|
457
|
+
else:
|
|
458
|
+
raise TypeError(
|
|
459
|
+
"`dl_device` must be a DLDevice, a 2-tuple of ints, or None; "
|
|
460
|
+
f"got {type(dl_device).__name__}"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
capsule = _call_dlpack_with_fallbacks(
|
|
464
|
+
array,
|
|
465
|
+
stream,
|
|
466
|
+
max_version_tuple,
|
|
467
|
+
dl_device_tuple,
|
|
468
|
+
copy,
|
|
469
|
+
)
|
|
470
|
+
capsule_name = PYTHON_API.PyCapsule_GetName(capsule)
|
|
471
|
+
|
|
472
|
+
if capsule_name == DLPACK_VERSIONED_NAME:
|
|
473
|
+
pointer = PYTHON_API.PyCapsule_GetPointer(capsule, DLPACK_VERSIONED_NAME)
|
|
474
|
+
if not pointer:
|
|
475
|
+
err = _fetch_pyerr()
|
|
476
|
+
raise RuntimeError("failed to get pointer from versioned capsule") from err
|
|
477
|
+
|
|
478
|
+
result = ctypes.cast(pointer, ctypes.POINTER(DLManagedTensorVersioned))
|
|
479
|
+
|
|
480
|
+
status = PYTHON_API.PyCapsule_SetName(capsule, USED_DLPACK_VERSIONED_NAME)
|
|
481
|
+
if status != 0:
|
|
482
|
+
err = _fetch_pyerr()
|
|
483
|
+
raise RuntimeError("failed to mark versioned capsule as used") from err
|
|
484
|
+
|
|
485
|
+
elif capsule_name == DLPACK_NAME:
|
|
486
|
+
pointer = PYTHON_API.PyCapsule_GetPointer(capsule, DLPACK_NAME)
|
|
487
|
+
if not pointer:
|
|
488
|
+
err = _fetch_pyerr()
|
|
489
|
+
raise RuntimeError("failed to get pointer from legacy capsule") from err
|
|
490
|
+
|
|
491
|
+
unversioned_ptr = ctypes.cast(pointer, ctypes.POINTER(DLManagedTensor))
|
|
492
|
+
result = _wrap_unversioned_as_versioned(unversioned_ptr)
|
|
493
|
+
|
|
494
|
+
# Only disown the capsule once ``result`` is fully built; if SetName ran
|
|
495
|
+
# earlier and any subsequent step raised, the capsule destructor would
|
|
496
|
+
# see the ``used_`` name and skip the deleter, leaking the source tensor.
|
|
497
|
+
status = PYTHON_API.PyCapsule_SetName(capsule, USED_DLPACK_NAME)
|
|
498
|
+
if status != 0:
|
|
499
|
+
err = _fetch_pyerr()
|
|
500
|
+
raise RuntimeError("failed to mark legacy capsule as used") from err
|
|
501
|
+
else:
|
|
502
|
+
# Unexpected capsule name. If ``GetName`` itself failed (returned NULL)
|
|
503
|
+
# it set a C-API exception which we clear here; otherwise there is
|
|
504
|
+
# nothing to clear.
|
|
505
|
+
PYTHON_API.PyErr_Clear()
|
|
506
|
+
name = capsule_name.decode() if capsule_name else "NULL"
|
|
507
|
+
raise RuntimeError(
|
|
508
|
+
f"unexpected capsule name: {name}; expected 'dltensor' "
|
|
509
|
+
"or 'dltensor_versioned'"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
actual_device = result.contents.dl_tensor.device
|
|
513
|
+
actual_device = (actual_device.device_type.value, actual_device.device_id)
|
|
514
|
+
if dl_device_tuple is not None and actual_device != dl_device_tuple:
|
|
515
|
+
if result.contents.deleter:
|
|
516
|
+
result.contents.deleter(result)
|
|
517
|
+
|
|
518
|
+
raise ValueError(
|
|
519
|
+
"DLPack device mismatch: "
|
|
520
|
+
f"expected type={dl_device_tuple[0]} id={dl_device_tuple[1]}, "
|
|
521
|
+
f"got type={actual_device[0]} id={actual_device[1]}"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
return result
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctypes-dlpack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ctypes helpers for DLPack interop
|
|
5
|
+
Author-email: Guillaume Fraux <guillaume.fraux@epfl.ch>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: repository, https://github.com/metatensor/ctypes-dlpack
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# ctypes-dlpack
|
|
21
|
+
|
|
22
|
+
<h4 align="center">
|
|
23
|
+
|
|
24
|
+
[](https://github.com/metatensor/ctypes-dlpack/actions?query=branch%3Amain)
|
|
25
|
+
[](https://docs.metatensor.org/ctypes-dlpack/)
|
|
26
|
+
</h4>
|
|
27
|
+
|
|
28
|
+
The [DLPack](https://dmlc.github.io/dlpack/latest/) standard allows to share
|
|
29
|
+
arrays/tensors data between different frameworks. It is defined as a C API, and
|
|
30
|
+
the standard way to use it from Python is the `__dlpack__` protocol defined in
|
|
31
|
+
the [array
|
|
32
|
+
api](https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html),
|
|
33
|
+
which relies on `PyCapsule` to pass the pointer around through Python code.
|
|
34
|
+
|
|
35
|
+
However, there might be times where one does not have a `PyCapsule` but just a
|
|
36
|
+
raw pointer to the DLPack C struct. In particular, this might be the case when
|
|
37
|
+
adding bindings to a C library through `ctypes` instead of going through a
|
|
38
|
+
CPython extension module. This library is here to bridge this gap, creating
|
|
39
|
+
ctypes pointers from the `__dlpack__` protocol, and making existing pointers
|
|
40
|
+
compatible with this protocol.
|
|
41
|
+
|
|
42
|
+
This library exposes both the C structs/enums/constants from the DLPack C header
|
|
43
|
+
as `ctypes` types (containing among other the version of the DLPack library
|
|
44
|
+
that was used to generate the ctypes declarations), and a Python API to convert
|
|
45
|
+
between DLPack pointers and Python objects. See the [documentation](http://docs.metatensor.org/ctypes-dlpack/) for more details.
|
|
46
|
+
|
|
47
|
+
You can install the code with `pip install ctypes-dlpack`.
|
|
48
|
+
|
|
49
|
+
### License and contributions
|
|
50
|
+
|
|
51
|
+
This project is distributed under the terms of the [BSD license](LICENSE), and
|
|
52
|
+
[maintained](https://github.com/lab-cosmo/.github/blob/main/Maintainers.md) by
|
|
53
|
+
[@Luthaf](https://github.com/Luthaf), who will reply to issues and pull requests
|
|
54
|
+
opened on this repository as soon as possible. You can mention them directly if
|
|
55
|
+
you did not receive an answer after a couple of days.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ctypes_dlpack/__init__.py,sha256=uUb2KN_4d8_g9XT5nym8HCS4DSLahYVrW6g746WUjdw,1495
|
|
2
|
+
ctypes_dlpack/_c_api.py,sha256=R41osT7m9iwo8bBC-gC6nMexQrGdbhMiX7V_XvrbAr4,4944
|
|
3
|
+
ctypes_dlpack/_dlpack.py,sha256=_dRFVagWaDtBvQ033sPkuJVmKOqdb-K-x8Jo1deoWR0,19207
|
|
4
|
+
ctypes_dlpack-0.1.0.dist-info/licenses/LICENSE,sha256=ePI5b-CYfiIBh1k1S-Mx-pl8PoryJ1cbUq1E94keQgs,1518
|
|
5
|
+
ctypes_dlpack-0.1.0.dist-info/METADATA,sha256=pjXQeW54mtmNdFXENCYhw9YcXi0TEzOOLjxE8HzaXzM,2746
|
|
6
|
+
ctypes_dlpack-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
ctypes_dlpack-0.1.0.dist-info/top_level.txt,sha256=wjlIXBuJ4vMD4BrUkXvvqTsOunDr-8EB9rDiTzPaPwU,14
|
|
8
|
+
ctypes_dlpack-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guillaume Fraux and contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ctypes_dlpack
|