Using dask-hip#
2026-07-06
5 min read time
This topic walks through practical examples of GPU-distributed computing with
dask-hip, from creating a minimal cluster to deploying UCX-enabled multi-GPU
workloads. Complete example scripts are available in the examples/ directory
of the repository.
Note
dask-hip retains dask-cuda API naming (module dask_cuda, command
dask cuda worker, class LocalCUDACluster) to minimize porting
friction for developers working across both ROCm and CUDA. See
What is dask-hip? for more background.
Creating a cluster#
There are two ways to create a dask-hip cluster: programmatically in Python or from the command line.
In a Python program:#
LocalCUDACluster creates one Dask worker per visible GPU in the current
process. Connect a Client to submit work:
from dask_cuda import LocalCUDACluster
from dask.distributed import Client
if __name__ == "__main__":
cluster = LocalCUDACluster()
client = Client(cluster)
# ... submit work ...
client.shutdown()
Tip
Always wrap LocalCUDACluster usage in an if __name__ == "__main__":
block when running as a standalone script. See
standalone Python scripts
for details.
From the command line:#
For multi-process deployments, start a scheduler and attach workers separately:
$ dask scheduler
distributed.scheduler - INFO - Scheduler at: tcp://127.0.0.1:8786
$ dask cuda worker 127.0.0.1:8786
Then connect a client from Python:
from dask.distributed import Client
client = Client("127.0.0.1:8786")
Selecting GPUs#
By default, dask-hip creates one worker for each visible GPU. Control which
GPUs are used via the HIP_VISIBLE_DEVICES environment variable:
HIP_VISIBLE_DEVICES=0,2 dask cuda worker 127.0.0.1:8786
Or programmatically:
cluster = LocalCUDACluster(HIP_VISIBLE_DEVICES="0,2")
Note
Both HIP_VISIBLE_DEVICES and CUDA_VISIBLE_DEVICES are supported.
Basic GPU computation#
The following example creates a cluster, generates a large random matrix on the GPUs using amd-cupy as the GPU array backend, computes its sum across all GPUs, and collects the result:
from dask_cuda import LocalCUDACluster
from dask.distributed import Client
import dask.array as da
import cupy
if __name__ == "__main__":
cluster = LocalCUDACluster()
client = Client(cluster)
# Create a 10000x10000 random matrix on the GPUs
rs = da.random.RandomState(RandomState=cupy.random.RandomState)
x = rs.random((10000, 10000), chunks=1000)
# Compute the sum -- Dask distributes chunks across GPU workers
result = x.sum().compute()
print(f"Sum: {result}")
client.shutdown()
Dask splits the array into chunks and distributes them across GPU workers. Each
chunk is backed by a cupy array in GPU memory. Operations like sum() are
executed in parallel on the GPUs, and Dask handles inter-worker communication
to combine partial results.
Using hipMM (RMM) memory pools#
Pre-allocating a GPU memory pool with rmm_pool_size is recommended for
workloads that perform many allocations or use UCX communication. A pool avoids
repeated allocation overhead and, when using UCX, only requires a single IPC
handle registration for the entire pool rather than one per allocation:
cluster = LocalCUDACluster(rmm_pool_size="4GB")
Or from the command line:
dask cuda worker 127.0.0.1:8786 --rmm-pool-size 4GB
For more on GPU memory management, see the device_memory_limit and
jit_unspill parameters in the API reference.
UCX-enabled local cluster#
For high-performance GPU-to-GPU communication, dask-hip integrates with
UCX via
hip-ucxx. The following example
(from examples/ucx/local_cuda_cluster.py) creates a UCX-enabled cluster
with ROCm-IPC transport:
from dask_cuda import LocalCUDACluster
from dask.distributed import Client
import dask.array as da
import cupy
if __name__ == "__main__":
cluster = LocalCUDACluster(
protocol="ucx",
enable_tcp_over_ucx=True,
enable_rocm_ipc=True,
enable_infiniband=False,
rmm_pool_size="1GB",
interface="eth0",
)
client = Client(cluster)
rs = da.random.RandomState(RandomState=cupy.random.RandomState)
x = rs.random((10000, 10000), chunks=1000)
x.sum().compute()
client.shutdown()
Key options:
enable_rocm_ipc– enables GPU-to-GPU transfers via ROCm-IPC (intra-node)enable_infiniband– enables InfiniBand RDMA transfers (inter-node)enable_rdmacm– enables the RDMA connection manager (recommended with InfiniBand)interface– network interface for the scheduler; required when ROCm-IPC or InfiniBand are enabledrmm_pool_size– pre-allocates a hipMM (RMM) memory pool per worker
For the full range of UCX configuration options, see the LocalCUDACluster
and CUDAWorker parameters in the API reference.
Multi-process deployment with UCX#
In production, the scheduler, workers, and client typically run as separate
processes. The examples/ucx/ directory demonstrates this pattern.
Step 1: Launch scheduler and workers#
Use dask cuda worker with UCX flags, or the provided
examples/ucx/dask_cuda_worker.sh script:
# Start the scheduler with UCX protocol
dask scheduler --protocol ucx --scheduler-file scheduler.json &
# Attach GPU workers with ROCm-IPC and a memory pool
dask cuda worker --scheduler-file scheduler.json \
--enable-tcp-over-ucx \
--enable-rocm-ipc \
--rmm-pool-size 1GB
Or use the helper script which handles environment variables and flags:
bash examples/ucx/dask_cuda_worker.sh -i eth0 -t rocm-ipc -r 2GB
Step 2: Connect a client#
From a separate process, use initialize() to configure UCX on the client
side before connecting (from examples/ucx/client_initialize.py):
from dask.distributed import Client
from dask_cuda.initialize import initialize
import dask.array as da
import cupy
if __name__ == "__main__":
initialize(
enable_tcp_over_ucx=True,
enable_rocm_ipc=True,
)
client = Client("ucx://127.0.0.1:8786")
rs = da.random.RandomState(RandomState=cupy.random.RandomState)
x = rs.random((10000, 10000), chunks=1000)
x.sum().compute()
client.shutdown()
The initialize() call configures Dask UCX settings so the client can
communicate with the scheduler and workers using the same transports.