vtkmodules.util.dlpack_support#
Lifetime#
That only works if the memory outlives whoever is looking at it, which is the part these protocols exist to get right.
Exporting builds the capsule around a vtkMemoryDescriptor, which holds
a reference to the source array; the reference is dropped when the
consumer releases the tensor. So a VTK array stays alive for exactly as
long as a CuPy view of it does, even if nothing else refers to it.
Importing goes the other way: the imported array holds the DLPack capsule open, so the CuPy array behind it cannot be collected while VTK is using it.
Neither direction requires the caller to think about this, which is the point – an interop layer that needs a lifetime rule written in the docs is one that will be got wrong.
Device support#
Host arrays work with plain VTK. Device arrays require VTK built with
VTK_ENABLE_VISKORES so that vtkmDataArrayFactory is available to
wrap device memory; importing a device tensor without it raises
RuntimeError rather than silently copying to the host.
Metal tensors are imported as host arrays. A shared-storage MTLBuffer on Apple Silicon is unified memory, so its pointer is host-readable and the wrap is still zero-copy; Viskores has no Metal device adapter, so there is nothing else it could be. Private-storage buffers have no host-readable pointer, report null, and are rejected. VTK never exports Metal memory, because no VTK array can hold it.
Context, device and streams#
Three things this module does not carry, all of them worth knowing before relying on it for device memory.
Context. DLPack identifies a device with two integers, a type and an
ordinal; there is nowhere in the protocol to put a context handle. CUDA and
HIP do not need one – the runtime keeps an implicitly shared primary
context per device, so a pointer identifies itself and every consumer lands
in the same place. Level Zero does not work that way: a USM pointer is only
meaningful inside the ze_context_handle_t it was allocated from, and
recovering that requires a context you already hold. So kDLOneAPI maps
to the "level_zero" space and vtkmDataArrayFactory refuses it,
naming the space, rather than treating the pointer as something it is not.
Supporting Level Zero means finding somewhere for the context to travel
first; it is not a matter of adding a device adapter.
Device ordinal. DLPackArray(array, device_id=...) is taken from the
caller and reported verbatim by __dlpack_device__. It is not derived
from where the memory actually lives, so on a multi-GPU host the default of
0 will mislabel memory that is not on device 0. Pass the right ordinal.
Streams. __dlpack__ accepts DLPack’s stream argument and ignores
it. That argument is how a consumer says “make this safe to read on my
stream”, so a producer with asynchronous work still queued – Viskores on a
Kokkos backend, for instance – can hand over memory whose contents are not
there yet, and the race has no diagnostic. Synchronize before exporting if
anything asynchronous produced the data.
Zero-copy exchange between VTK arrays and DLPack-speaking libraries.
DLPack is the interchange protocol used by CuPy, PyTorch, JAX, NumPy and TensorFlow. Speaking it means a VTK array can be handed to any of them – and taken back – without copying, on the host or on a GPU.
from vtkmodules.util.dlpack_support import vtk_to_dlpack, dlpack_to_vtk
import cupy
gpu = cupy.from_dlpack(vtk_to_dlpack(vtk_array)) # VTK -> CuPy
back = dlpack_to_vtk(gpu) # CuPy -> VTK
Both directions are views. Nothing is copied, so both sides are looking at the same memory, and a write through one is visible through the other.
Module Contents#
Classes#
Adapter exposing a vtkDataArray through the DLPack protocol. |
|
Shows an imported host tensor to numpy, and holds it open. |
Functions#
Drop what was pinned for the export at managed_ptr. |
|
Called by the consumer when it is done with the tensor. |
|
As above for a v1.0 tensor: the deleter is handed only a pointer and cannot tell the layouts apart, so there is one per layout. |
|
Release an export no consumer ever adopted. |
|
DLPack device types this module can import, as a set of ints. |
|
Export a vtkDataArray as a DLPack capsule, without copying. |
|
Import a DLPack tensor as a vtkDataArray, without copying. |
|
Get a capsule from source, preferring the versioned protocol. |
|
Drop the hold on an adopted tensor, calling the producer’s deleter. |
|
Called by VTK when it is finished with an imported tensor. |
|
Take ownership of capsule, and return the key that releases it. |
|
Release the tensor keyed key when descriptor is destroyed. |
|
Build the array from a host pointer, via numpy. |
|
Build the array from a device pointer, via Viskores. |
Data#
API#
- vtkmodules.util.dlpack_support.__all__#
[‘vtk_to_dlpack’, ‘dlpack_to_vtk’, ‘DLPackArray’, ‘supported_device_types’]
- vtkmodules.util.dlpack_support.kDLCPU#
1
- vtkmodules.util.dlpack_support.kDLCUDA#
2
- vtkmodules.util.dlpack_support.kDLCUDAHost#
3
- vtkmodules.util.dlpack_support.kDLOpenCL#
4
- vtkmodules.util.dlpack_support.kDLVulkan#
7
- vtkmodules.util.dlpack_support.kDLMetal#
8
- vtkmodules.util.dlpack_support.kDLROCM#
10
- vtkmodules.util.dlpack_support.kDLROCMHost#
11
- vtkmodules.util.dlpack_support.kDLCUDAManaged#
13
- vtkmodules.util.dlpack_support.kDLOneAPI#
14
- vtkmodules.util.dlpack_support.kDLInt#
0
- vtkmodules.util.dlpack_support.kDLUInt#
1
- vtkmodules.util.dlpack_support.kDLFloat#
2
- vtkmodules.util.dlpack_support._DEVICE_TO_SPACE#
None
- vtkmodules.util.dlpack_support._SPACE_TO_DEVICE#
None
- vtkmodules.util.dlpack_support._LONG_BITS#
None
- vtkmodules.util.dlpack_support._VTK_TO_DLPACK#
None
- vtkmodules.util.dlpack_support._DLPACK_TO_VTK#
None
- class vtkmodules.util.dlpack_support._DLDataType#
Bases:
ctypes.Structure- _fields_#
[(‘code’,), (‘bits’,), (‘lanes’,)]
- class vtkmodules.util.dlpack_support._DLDevice#
Bases:
ctypes.Structure- _fields_#
[(‘device_type’,), (‘device_id’,)]
- class vtkmodules.util.dlpack_support._DLTensor#
Bases:
ctypes.Structure- _fields_#
[(‘data’,), (‘device’,), (‘ndim’,), (‘dtype’,), (‘shape’,), (‘strides’,), (‘byte_offset’,)]
- class vtkmodules.util.dlpack_support._DLManagedTensor#
Bases:
ctypes.Structure- _fields_#
[(‘dl_tensor’,), (‘manager_ctx’,), (‘deleter’,)]
- class vtkmodules.util.dlpack_support._DLPackVersion#
Bases:
ctypes.Structure- _fields_#
[(‘major’,), (‘minor’,)]
- class vtkmodules.util.dlpack_support._DLManagedTensorVersioned#
Bases:
ctypes.Structure- _fields_#
[(‘version’,), (‘manager_ctx’,), (‘deleter’,), (‘flags’,), (‘dl_tensor’,)]
- vtkmodules.util.dlpack_support.DLPACK_FLAG_BITMASK_READ_ONLY#
None
- vtkmodules.util.dlpack_support._PyCapsule_New#
None
- vtkmodules.util.dlpack_support._PyCapsule_IsValid#
None
- vtkmodules.util.dlpack_support._PyCapsule_GetPointer#
None
- vtkmodules.util.dlpack_support._pinned#
None
- vtkmodules.util.dlpack_support._next_key#
1
- vtkmodules.util.dlpack_support._release(managed_ptr, struct=_DLManagedTensor)#
Drop what was pinned for the export at managed_ptr.
struct says which layout to read: manager_ctx sits at a different offset in the two.
- vtkmodules.util.dlpack_support._tensor_deleter(managed_ptr)#
Called by the consumer when it is done with the tensor.
- vtkmodules.util.dlpack_support._tensor_deleter_versioned(managed_ptr)#
As above for a v1.0 tensor: the deleter is handed only a pointer and cannot tell the layouts apart, so there is one per layout.
- vtkmodules.util.dlpack_support._capsule_destructor(capsule_ptr)#
Release an export no consumer ever adopted.
A consumer that takes the tensor renames the capsule to “used_dltensor” and takes over calling the deleter. A capsule collected still named “dltensor” was nobody’s responsibility.
- vtkmodules.util.dlpack_support.supported_device_types()#
DLPack device types this module can import, as a set of ints.
- vtkmodules.util.dlpack_support.vtk_to_dlpack(array, device_id=0, versioned=False)#
Export a vtkDataArray as a DLPack capsule, without copying.
The tensor is 2-D,
(number of tuples, number of components), which is how the array is laid out. Single-component arrays are still 2-D with a trailing 1; reshape on the consumer side if you want 1-D.The array is kept alive for as long as the consumer holds the tensor, so it is safe to drop every other reference to it.
With versioned, produces a v1.0 tensor in a “dltensor_versioned” capsule instead of the legacy “dltensor” one. Consumers that ask for v1.0 need this: over the legacy protocol NumPy cannot tell whether writing is safe, so it marks the view read-only and then will not re-export it.
Raises TypeError for a data type with no DLPack equivalent, and ValueError for an array with no accessible memory – implicit and computed arrays have none to describe.
- class vtkmodules.util.dlpack_support.DLPackArray(array, device_id=0)#
Adapter exposing a vtkDataArray through the DLPack protocol.
Libraries call
__dlpack__rather than taking a capsule directly, so wrapping the array lets them consume it as they would any other array:cupy.from_dlpack(DLPackArray(vtk_array)) torch.from_dlpack(DLPackArray(vtk_array))
Initialization
- __dlpack__(*, stream=None, max_version=None, dl_device=None, copy=None)#
- __dlpack_device__()#
- vtkmodules.util.dlpack_support.dlpack_to_vtk(source, name=None)#
Import a DLPack tensor as a vtkDataArray, without copying.
source may be a capsule or any object implementing
__dlpack__– a CuPy array, a PyTorch tensor, a NumPy array.A 1-D tensor becomes a single-component array. A 2-D tensor becomes
(tuples, components). Higher rank is rejected, since a vtkDataArray has no way to represent it.The tensor is held open for as long as the returned array lives, so the source can be dropped immediately.
- vtkmodules.util.dlpack_support._request_capsule(source)#
Get a capsule from source, preferring the versioned protocol.
Asking for v1.0 first matters: NumPy refuses to export a read-only array over the legacy protocol, because that protocol cannot say so. A producer predating v1.0 raises TypeError, so fall back.
- vtkmodules.util.dlpack_support._adopted#
None
- vtkmodules.util.dlpack_support._next_adopted_key#
1
- vtkmodules.util.dlpack_support._release_adopted(key)#
Drop the hold on an adopted tensor, calling the producer’s deleter.
- vtkmodules.util.dlpack_support._adopted_release(context)#
Called by VTK when it is finished with an imported tensor.
- vtkmodules.util.dlpack_support._PyCapsule_SetName#
None
- vtkmodules.util.dlpack_support._register_adopted(capsule, managed_ptr, struct, used_name)#
Take ownership of capsule, and return the key that releases it.
Renaming the capsule is what marks it consumed: from here its own destructor leaves it alone, and releasing it is this module’s job.
- vtkmodules.util.dlpack_support._hold_via_descriptor(descriptor, key)#
Release the tensor keyed key when descriptor is destroyed.
- class vtkmodules.util.dlpack_support._HostTensor(key, pointer, shape, dtype)#
Shows an imported host tensor to numpy, and holds it open.
numpy.asarray() of this builds a view whose base is this object, so the tensor stays open for exactly as long as anything is looking at the memory, and is released once nothing is.
The hold is this object’s own lifetime, rather than the vtkMemoryDescriptor release callback the device path uses. That callback is a ctypes trampoline invoked from a C++ destructor, and a vtkObject can outlive Py_Finalize: reaching for the GIL then crashes inside PyGILState_Ensure instead of releasing anything. Python finalization runs while the interpreter is still up, and the worst it can do is leak.
The array interface is what makes the view writable. Nothing here asks the producer about mutability; dlpack_to_vtk has already read the read-only flag off the capsule and refused if it was set.
Initialization
- __del__()#
- vtkmodules.util.dlpack_support._wrap_host(key, vtk_type, n_tuples, n_components, pointer, ndim)#
Build the array from a host pointer, via numpy.
Host memory does not go through Viskores, and must not: plain VTK builds have no vtkmDataArrayFactory, and host tensors are the common case. numpy is only the vehicle – numpy_to_vtk(deep=False) points a vtkAOSDataArrayTemplate at the buffer and keeps the view alive – and the dtype comes from the same VTK type the device path derived, so the two agree on what the tensor holds.
- vtkmodules.util.dlpack_support._wrap_descriptor(descriptor, vtk_type, n_tuples, n_components, space)#
Build the array from a device pointer, via Viskores.