For the complete documentation index, see llms.txt. This page is also available as Markdown.

CUDA (nd4j-cuda)

Setting up the nd4j-cuda GPU backend — CUDA versions, cuDNN integration, multi-GPU, and GPU memory management

nd4j-cuda is the GPU backend for ND4J. It executes array operations through libnd4j CUDA kernels and cuBLAS for matrix operations, delivering significant throughput improvements over the CPU backend for large batch sizes and wide matrix operations. This page covers Maven setup, CUDA version compatibility, cuDNN integration, multi-GPU configuration, and GPU memory management.

Maven Dependencies

CUDA version in the artifact name

Unlike most Maven artifacts, the CUDA toolkit version is encoded directly in the artifact ID. M2.1 ships with CUDA 11.6 support:

<properties>
  <dl4j.version>1.0.0-M2.1</dl4j.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.nd4j</groupId>
    <artifactId>nd4j-cuda-11.6-platform</artifactId>
    <version>${dl4j.version}</version>
  </dependency>
</dependencies>

The -platform suffix bundles native JARs for all supported OS + CUDA combinations (Linux x86_64, Windows x86_64). Use it unless you have strong JAR-size constraints.

Minimal: explicit classifier

Available classifiers for nd4j-cuda-11.6:

Classifier
Platform

linux-x86_64

Linux 64-bit x86

windows-x86_64

Windows 64-bit x86

macOS is not supported by the CUDA backend (NVIDIA does not ship CUDA for macOS).

-compile classifier (1.0.0-rewrite)

The 1.0.0-rewrite release adds a -compile variant for the CUDA backend that bundles the Triton MLIR GPU JIT compiler, NVRTC runtime compiler, and PTX string-template backend. This enables DSP kernel fusion — where consecutive element-wise ops are compiled into a single GPU kernel at runtime — on top of CUDA graph capture/replay.

Classifier
Description

linux-x86_64-cuda-12.9-compile

CUDA 12.9 with Triton + NVRTC + PTX JIT

Trade-off: The base CUDA classifier already supports CUDA graph capture/replay (which eliminates per-kernel launch overhead). The -compile variant adds JIT kernel fusion on top, reducing global memory traffic between ops. This is most impactful for transformer/LLM inference at low batch sizes. The cost is a larger binary that includes the Triton/LLVM compiler stack.

When using -platform, select the -compile variant at runtime:

See Hardware Backends — Classifier Variants for the complete trade-off analysis.

Using the DL4J BOM

CUDA Version Compatibility

The CUDA version in the artifact ID refers to the CUDA toolkit that libnd4j was compiled against. The CUDA runtime on the host machine must be compatible with this version.

ND4J Artifact
Required CUDA Runtime
Minimum Driver Version (Linux)

nd4j-cuda-11.6

CUDA 11.6

510.39

Forward compatibility: CUDA is forward-compatible at the minor version level. A CUDA 11.6 binary runs on a machine with CUDA 11.7 or 11.8 drivers. It does not run on CUDA 10.x or earlier.

Checking installed CUDA version:

Checking minimum GPU compute capability:

ND4J M2.1 requires compute capability 3.5 (Kepler) or higher. Modern training workloads perform best on compute capability 7.0 (Volta) or later, which enables Tensor Cores for mixed-precision matrix multiplication.

cuDNN Integration

cuDNN (CUDA Deep Neural Network library) provides highly optimized implementations of convolutions, pooling, batch normalization, and RNN cells. It accelerates DL4J neural network training but is not required for raw ND4J array operations.

To enable cuDNN acceleration, add the deeplearning4j-cuda artifact to your project alongside nd4j-cuda:

When both JARs are present and cuDNN is installed on the host, DL4J automatically detects and uses cuDNN for supported layer types (Conv2D, LSTM, BatchNormalization, etc.). No additional code changes are required in the model definition.

cuDNN installation: Download cuDNN from developer.nvidia.com/cudnn. The cuDNN version must match the CUDA toolkit version (cuDNN 8.x for CUDA 11.6). Place the cuDNN shared libraries on LD_LIBRARY_PATH or in /usr/local/cuda/lib64.

Checking cuDNN availability at runtime:

GPU Memory Management

GPU memory (VRAM) is a finite resource. ND4J allocates GPU memory for array data using a pooling allocator that reduces the overhead of frequent cudaMalloc/cudaFree calls.

maxbytes: capping VRAM usage

This property caps the total GPU memory ND4J will allocate. Set it to a value below the GPU's total VRAM to leave room for the CUDA runtime, cuDNN workspace buffers, and the operating system.

A 16 GB GPU used for training should leave 1–2 GB headroom:

Memory mode: IMMEDIATE vs. DELAYED

ND4J on CUDA supports two memory allocation modes controlled at startup:

  • IMMEDIATE (default): allocate GPU memory as soon as an array is created.

  • DELAYED: defer GPU allocation until the array is actually used in a computation. Useful when constructing large model parameter arrays where only a subset will be used in any given forward pass.

GPU memory fragmentation

Long-running training jobs can fragment the GPU memory pool, leading to cudaMalloc failures even when nvidia-smi shows available VRAM. If you encounter this, enable the memory defragmentation by periodically calling:

For training loops, wrapping mini-batch computation in ND4J workspaces is the most effective strategy — see Memory and Workspaces.

Multi-GPU Configuration

ND4J supports using multiple GPUs within a single JVM process. By default only device 0 is used. Enable multi-GPU support before any ND4J operations:

Device affinity per thread

ND4J maintains a thread-to-device mapping. All arrays created by a given thread are allocated on that thread's assigned device. Use the affinity manager to read or change the assignment:

Multi-GPU with DL4J ParallelWrapper

For data-parallel training across multiple GPUs, use ParallelWrapper:

Each worker thread is pinned to a different GPU. Gradients are averaged across workers every averagingFrequency mini-batches, and the averaged weights are copied back to all workers.

Listing available GPUs

Or from the command line:

Device Selection

When a machine has multiple GPUs but you want to restrict ND4J to a specific subset, use the CUDA environment variable:

CUDA_VISIBLE_DEVICES renumbers devices from the process's perspective: GPU 0 inside the process is the physical GPU 0, and GPU 1 inside the process is physical GPU 2. ND4J respects this environment variable and will only see the enumerated devices.

To select a device programmatically within the process:

Checking GPU Backend Availability

Common initialization errors

Error
Cause
Fix

No nd4jbackend found

nd4j-cuda-11.6 not on classpath

Add the Maven dependency

CUDA driver version is insufficient

Driver too old for CUDA 11.6

Upgrade NVIDIA driver to 510.39+

no CUDA-capable device is detected

No GPU, or CUDA_VISIBLE_DEVICES=""

Check nvidia-smi output

cudaMalloc failed: out of memory

VRAM exhausted

Reduce batch size, lower maxbytes, use workspaces

cuDNN not found

cuDNN not installed or not on LD_LIBRARY_PATH

Install cuDNN matching CUDA 11.6

Performance Tips

Use FLOAT, not DOUBLE, on GPU

Consumer GPUs (GeForce/RTX series) run FLOAT32 operations at full throughput and FLOAT64 at 1/32 or 1/64 of that. Volta and Ampere data-center GPUs have better FP64 throughput, but FP32 is still faster. Always prefer DataType.FLOAT for training unless double precision is a hard requirement.

Minimize host-device transfers

Every call to getDouble(i, j), toDoubleVector(), or toDoubleMatrix() copies data from the GPU back to the Java heap. In a training loop this is expensive. Avoid reading individual elements inside mini-batch loops; read scalars (loss, accuracy) only after an epoch or at checkpoint intervals.

Batch size and occupancy

CUDA kernels achieve full GPU occupancy only when there are enough parallel work items to fill all streaming multiprocessors. For matrix operations this typically means a batch size of at least 32, and ideally 128 or higher. Very small batch sizes (1–8) will under-utilize the GPU.

Quick Reference

Goal
Setting

Add CUDA 11.6 backend (all platforms)

org.nd4j:nd4j-cuda-11.6-platform:1.0.0-M2.1

Add cuDNN support

org.deeplearning4j:deeplearning4j-cuda-11.6:1.0.0-M2.1

Limit VRAM usage

-Dorg.bytedeco.javacpp.maxbytes=Ng

Enable multi-GPU

CudaEnvironment.getInstance().getConfiguration().allowMultiGPU(true)

Get current thread's device

Nd4j.getAffinityManager().getDeviceForCurrentThread()

Restrict to specific GPUs

export CUDA_VISIBLE_DEVICES=0,1

Check backend class name

Nd4j.getBackend().getClass().getName()

Set default device

CudaEnvironment.getInstance().getConfiguration().setDefaultDevice(N)

See Also

Last updated

Was this helpful?