最佳实践
多 GPU 机器
在选择两种多 GPU 设置时,最好选择 GPU 之间大多数互相位于同一位置的设置。这可以是 DGX、具有多 GPU 选项的云实例、高密度 GPU HPC 实例等。这样做有两个原因
在 GPU 之间移动数据代价高昂,并且由于通信开销、主机到设备/设备到主机传输等原因导致计算停止时,性能会下降
多 GPU 实例通常配备加速网络,例如 NVLink。与传统网络相比,这些加速网络路径通常具有更高的吞吐量/带宽,并且不会强制进行主机到设备/设备到主机传输。有关更多讨论,请参见加速网络。
from dask_cuda import LocalCUDACluster
cluster = LocalCUDACluster(n_workers=2) # will use GPUs 0,1
cluster = LocalCUDACluster(CUDA_VISIBLE_DEVICES="3,4") # will use GPUs 3,4
有关控制 worker 数量/使用多个 GPU 的更多讨论,请参见控制 worker 数量。
GPU 内存管理
使用 Dask-CUDA,尤其是在结合 RAPIDS 使用时,最好使用RMM 内存池来预先在 GPU 上分配内存。虽然分配内存速度快,但需要少量时间;然而,在简单的 workflow 中很容易进行数十万甚至数百万次分配,从而导致性能显著下降。使用 RMM 内存池,分配是从更大的内存池中进行的,这大大减少了分配时间,从而提高了性能
from dask_cuda import LocalCUDACluster
cluster = LocalCUDACluster(CUDA_VISIBLE_DEVICES="0,1",
protocol="ucx",
rmm_pool_size="30GB")
我们还建议分配 GPU 内存空间的绝大部分,但不是全部。我们这样做是因为 CUDA 上下文(Context)会在设备上占用非零(通常为 200-500 MBs)的 GPU RAM。
此外,当使用加速网络时,我们只需要为整个内存池注册一个 IPC handle(这很耗时,但只需进行一次),因为从 IPC 的角度来看,只有一个分配。这与仅使用不带内存池的 RMM 不同,后者中每次新的分配都必须向 IPC 注册。
从设备溢出
Dask-CUDA 提供了几种不同的方法来启用设备内存自动溢出。最佳方法通常取决于特定的 workflow。对于使用Dask cuDF 的经典 ETL 工作负载,原生的 cuDF 溢出通常是最好的起点。有关更多详细信息,请参阅Dask-CUDA 的溢出文档。
加速网络
如多 GPU 机器中所述,与传统网络硬件相比,加速网络具有更好的带宽/吞吐量,并且不会强制进行任何耗时的主机到设备/设备到主机传输。Dask-CUDA 可以利用UCX-Py 来利用加速网络硬件。
举个例子,让我们比较在使用 NVLink 连接的 2 个 GPU 时进行合并(merge)benchmark 的结果。首先,我们将使用标准 TCP 通信运行
python local_cudf_merge.py -d 0,1 -p tcp -c 50_000_000 --rmm-pool-size 30GB
在上面,我们使用了 2 个 GPU(2 个 dask-cuda worker),预分配了 30GB 的 GPU RAM(以加快 GPU 内存分配),并在 Dask 需要在 worker 之间来回移动数据时使用了 TCP 通信。这种设置的平均 wall clock 时间为:19.72 s +/- 694.36 ms
================================================================================
Wall clock | Throughput
--------------------------------------------------------------------------------
20.09 s | 151.93 MiB/s
20.33 s | 150.10 MiB/s
18.75 s | 162.75 MiB/s
================================================================================
Throughput | 154.73 MiB/s +/- 3.14 MiB/s
Bandwidth | 139.22 MiB/s +/- 2.98 MiB/s
Wall clock | 19.72 s +/- 694.36 ms
================================================================================
(w1,w2) | 25% 50% 75% (total nbytes)
--------------------------------------------------------------------------------
(0,1) | 138.48 MiB/s 150.16 MiB/s 157.36 MiB/s (8.66 GiB)
(1,0) | 107.01 MiB/s 162.38 MiB/s 188.59 MiB/s (8.66 GiB)
================================================================================
Worker index | Worker address
--------------------------------------------------------------------------------
0 | tcp://127.0.0.1:44055
1 | tcp://127.0.0.1:41095
================================================================================
为了比较,现在我们将 protocol
从 tcp
更改为 ucx
python local_cudf_merge.py -d 0,1 -p ucx -c 50_000_000 –rmm-pool-size 30GB
使用 UCX 和 NVLink,我们将 wall clock 时间大幅缩短至:347.43 ms +/- 5.41 ms
。
================================================================================
Wall clock | Throughput
--------------------------------------------------------------------------------
354.87 ms | 8.40 GiB/s
345.24 ms | 8.63 GiB/s
342.18 ms | 8.71 GiB/s
================================================================================
Throughput | 8.58 GiB/s +/- 78.96 MiB/s
Bandwidth | 6.98 GiB/s +/- 46.05 MiB/s
Wall clock | 347.43 ms +/- 5.41 ms
================================================================================
(w1,w2) | 25% 50% 75% (total nbytes)
--------------------------------------------------------------------------------
(0,1) | 17.38 GiB/s 17.94 GiB/s 18.88 GiB/s (8.66 GiB)
(1,0) | 16.55 GiB/s 17.80 GiB/s 18.87 GiB/s (8.66 GiB)
================================================================================
Worker index | Worker address
--------------------------------------------------------------------------------
0 | ucx://127.0.0.1:35954
1 | ucx://127.0.0.1:53584
================================================================================