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#

_DLDataType

_DLDevice

_DLTensor

_DLManagedTensor

_DLPackVersion

_DLManagedTensorVersioned

DLPackArray

Adapter exposing a vtkDataArray through the DLPack protocol.

_HostTensor

Shows an imported host tensor to numpy, and holds it open.

Functions#

_release

Drop what was pinned for the export at managed_ptr.

_tensor_deleter

Called by the consumer when it is done with the tensor.

_tensor_deleter_versioned

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.

_capsule_destructor

Release an export no consumer ever adopted.

supported_device_types

DLPack device types this module can import, as a set of ints.

vtk_to_dlpack

Export a vtkDataArray as a DLPack capsule, without copying.

dlpack_to_vtk

Import a DLPack tensor as a vtkDataArray, without copying.

_request_capsule

Get a capsule from source, preferring the versioned protocol.

_release_adopted

Drop the hold on an adopted tensor, calling the producer’s deleter.

_adopted_release

Called by VTK when it is finished with an imported tensor.

_register_adopted

Take ownership of capsule, and return the key that releases it.

_hold_via_descriptor

Release the tensor keyed key when descriptor is destroyed.

_wrap_host

Build the array from a host pointer, via numpy.

_wrap_descriptor

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.