NumPy Bridge
Zero-copy data exchange between INDArray and numpy.ndarray via python4j-numpy
The python4j-numpy module adds a PythonType implementation called NumpyArray that lets you pass ND4J INDArray objects into Python as numpy.ndarray views, and convert Python numpy arrays back to INDArray objects — in most cases without copying the underlying data buffer.
Maven Dependency
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>python4j-numpy</artifactId>
<version>${dl4j.version}</version>
</dependency>Adding this module to the classpath is sufficient. NumpyArray registers itself via Java's ServiceLoader mechanism, so it becomes available to PythonTypes.get() automatically. No additional configuration is required to enable the conversion.
How Zero-Copy Works
An ND4J INDArray on the CPU backend stores its data in a DataBuffer that wraps a native (off-heap) memory block. A numpy.ndarray is also backed by a contiguous block of native memory. When the two have compatible data types, the NumpyArray converter creates the NumPy array using np.frombuffer (via ctypes) pointing at the same address, avoiding a copy.
On the Java-to-Python direction, NumpyArray.toPython(INDArray) obtains the buffer's native address and creates a NumPy array view pointing to it. The DataBuffer reference is retained in a static cache keyed by (address, length, dtype) to prevent premature garbage collection.
On the Python-to-Java direction, NumpyArray.toJava(PythonObject) reads the PyArrayObject struct to extract shape, strides, and data type, then wraps the numpy array's data pointer in an ND4J DataBuffer. The resulting INDArray shares the same memory as the Python array.
CUDA note. Before passing an INDArray backed by GPU memory to Python, the converter calls Nd4j.getAffinityManager().ensureLocation(array, HOST) to synchronize the array to host memory. The Python side always sees a CPU buffer; GPU synchronization back from the Python side is not automatic.
BFLOAT16 note. NumPy has no native bfloat16 dtype. If you pass a BFLOAT16 INDArray through the bridge, a cast to FLOAT32 is made automatically and a warning is logged. The returned INDArray will be FLOAT32.
Data Type Mapping
ND4J DataType
NumPy dtype
DOUBLE
float64
FLOAT
float32
BFLOAT16
float32 (cast)
HALF (float16)
float16
INT32
int32
INT64 (LONG)
int64
INT16 (SHORT)
int16
INT8 (BYTE)
int8
UINT8 (UBYTE)
uint8
UINT16
uint16
UINT32
uint32
UINT64
uint64
BOOL
bool
Passing an INDArray to Python
The PythonVariable constructor accepts a PythonType<T> as its second argument. Passing NumpyArray.INSTANCE tells Python4J to use the NumPy bridge for that variable.
Reading a numpy.ndarray Back from Python
When an output variable is declared with type NumpyArray.INSTANCE, Python4J calls NumpyArray.toJava() after execution. The returned INDArray wraps the same data buffer as the Python array, so further Python-side modifications to that array will be reflected in the Java INDArray until one of the two is garbage-collected or reallocated.
Memory Considerations
Because the bridge shares native memory rather than copying it, lifetime management is important:
Keep the source alive. If you pass an
INDArrayto Python and the Java reference goes out of scope before Python is done with it, the garbage collector may reclaim theDataBufferwhile the numpy array still points to that memory. Retain a Java reference for the entire duration of the Python execution block.Do not rely on the Python array after the INDArray is deallocated. Conversely, if you return a numpy array from Python, the wrapped
INDArraywill hold the numpy array's data alive via the cache. CallingPy_DecRefon the underlyingPyObjectwhile theINDArraystill exists will cause a use-after-free.PythonGC can interfere. If you use
PythonGC.watch()blocks (see Advanced Usage), be careful thatPythonObjectwrappers for numpy arrays are not collected before you extract theINDArrayfrom them.Workspace scoping.
NumpyArray.toJava()creates the wrappingDataBufferoutside any active memory workspace (Nd4j.getMemoryManager().scopeOutOfWorkspaces()). This prevents the buffer from being unexpectedly freed when a workspace closes.
Checking Whether the Bridge Is Active
The NumpyArray type is initialized once when the class is first loaded. If numpy is not importable on the Python path, the init will fail with a PythonException. You can verify the Python path includes numpy by running:
To disable automatic numpy import (for example, when running in an environment where numpy is absent but python4j-numpy is on the classpath), set the system property before any Python4J class loads:
Next Steps
Advanced Usage — GIL management across threads, context isolation,
PythonGC, and subprocess utilities.
Last updated
Was this helpful?