当前位置:   article > 正文

为什么调用了gpu却没起作用_利用多 GPU 加速深度学习模型训练

深度学习当中进行gpu加速效果并不明显是怎么回事

394475be95bbbcaa89ae77f9ed3efb33.png

01

前言

深度学习模型通常使用 GPU 训练,因为 GPU 具有相比 CPU 更高的计算能力,以 Tesla V100 为例,使用 Tensor Core 加速的半精度浮点计算能力达到 125 TFLOPS【1】,配有 V100 GPU 的单个服务器节点最多可替代 60 个 CPU 节点,正如每年 GTC Keynote 上黄仁勋宣称的“The more you buy, the more you save”。

9dad2fadf6a5347cc12fc9697be0a4af.png

9ddc8b2228225955bb9d219a590f7884.png

目前计算机视觉、语音识别、自然语言处理等领域最好的深度学习模型通常采用更高参数量模型结合更大规模数据训练得到。例如 ResNet-50 有大约 2300 万参数量,用于训练的 ImageNet 数据集有 128 万张图片,利用一块 Tesla V100 完成 90 轮(epoch)训练大约需要 2~3 天时间;中英文语音识别模型 DeepSpeech2 拥有 1 亿参数量,语音训练数据总时长超过 2 万小时,单 GPU 训练时间约 3~6 周;用于文本生成的 GPT-2 模型参数量多达 15 亿,预训练所需数据 WebText 多达 800 万篇文章,使用单块 GPU 训练耗时将长达一个月甚至更久。这些顶尖模型由于训练到部署迭代周期长,无法保证业务上线时间需求,为了加快节奏,需要借助更多 GPU 并行处理。

02

多 GPU 通信原理

2.1 单机多 GPU 通信

首先我们关注单台服务器上插有多张 GPU 的情况。受限于主板 PCIe 插槽数目和拓扑方式,在一台服务器中一般不超过 16 张卡。单机 8 GPU 或 16 GPU 已经可以承担一般规模训练任务。下图为典型的 8 卡 GPU 服务器外观:

c402718f671fa3d10bd857ccbd93f5f5.png

8 卡服务器内部 CPU-CPU、CPU-GPU、GPU-GPU 之间互联拓扑关系如下图所示:

f80704c76012177413c3b6a7a4a117cf.png

其中 CPU0 和 CPU1 通过 QPI 互联,CPU 与 GPU 则通过 PCIe Switch 互联。GPU 0~3 两两之间可以既可以通过 NVLink 通信又可以走 PCIe Switch,同样 GPU 4~7 也是如此。而 GPU 0 和 GPU 4 之间没有直接通路,需要借助其他途径间接通信。不同互联通路的有效带宽如下图所示:

4b19192419e1afef1fc49f967cfd1457.png

可以看到两颗 GPU 之间如果有高速通路 NVLink,通信开销将远低于间接通路,从而实现线性扩展。程序设计时应尽可能减少使用低速通路如 QPI。

多 GPU 通信可以借助 CUDA API,通过显式调用 cudaSetDevice() 指定使用哪张卡进行显存分配和执行计算,如需交换数据,可以调用 cudaMemcpyPeer() 拷贝到目标设备,再启动 Kernel 计算。

除此之外,NVIDIA 提供了用于多 GPU 通信库 NCCL(NVIDIA Collective Communications Libary)【2】,实现了 AllReduce、Reduce、Broadcast、ReduceScatter、AllGather 等常用通信原语,面向 PCIe 和 NVLink 做了专门优化,具有更高带宽、更快速度。NCCL 最初只支持单机多 GPU 通信,从 NCCL2 开始支持多机多 GPU 通信。

后文几张图演示了 NCCL 各个原语的具体作用以及相应的 API 封装。约定每个 rank 只对应一块 GPU,rank 后面数字编号与 GPU 设备编号保持一一对应。

Broadcast

广播原语,将某个 rank 上的数据拷贝到其他所有 rank(图中示例将 rank 2 内容广播给 rank0~3)。

11cebbc158f5157a359de687f5124476.png

NCCL API Broadcast 调用接口如下:

ncclResult_t ncclBroadcast(const void* sendbuff,    // 待广播数据地址                           void* recvbuff,          // 接收广播地址                           size_t count// 广播数据长度                           ncclDataType_t datatype, // 广播数据类型,可选:ncclInt8, ncclChar, ncclUint8, ncclInt32, ncclInt, ncclUint32, ncclInt64, ncclUint64, ncclFloat16, ncclHalf, ncclFloat32, ncclFloat, ncclFloat64, ncclDouble                           int root,                // 待广播设备编号                           ncclComm_t comm,         // NCCL 通信句柄                           cudaStream_t stream)     // 绑定的 CUDA 流

Reduce

归约原语,对所有 rank 数据做归约计算,结果放到其中一个 rank(图中示例将 rank0~3 数据归约后放到 rank2)。

9432aedd227d394fbbeadc0d965181b8.png

NCCL API  Reduce 调用接口如下:

ncclResult_t ncclReduce(const void* sendbuff,  // 待归约数据地址                        void* recvbuff,        // 归约结果存放地址                        size_t count,          // 归约数据长度                        ncclDataType_t datatype,  // 归约数据类型,可选:ncclInt8, ncclChar, ncclUint8, ncclInt32, ncclInt, ncclUint32, ncclInt64, ncclUint64, ncclFloat16, ncclHalf, ncclFloat32, ncclFloat, ncclFloat64, ncclDouble                        ncclRedOp_t op,        // 归约计算类型,可选:ncclSum(+), ncclProd(*), ncclMin, ncclMax                        int root,              // 归约结果存放 rank                        ncclComm_t comm,       // NCCL 通信句柄                        cudaStream_t stream)   // 绑定的 CUDA 流

AllReduce

完全归约原语,对所有 rank 数据做归约计算,结果放到每一个 rank。

注:AllReduce 等价于 Reduce+Broadcast。

5f9e31169dd645060e16d53d7b80d014.png

NCCL API All Reduce 调用接口如下:

ncclResult_t ncclAllReduce(const void* sendbuff, // 待归约数据地址                           void* recvbuff, // 归约结果存放地址                           size_t count// 归约数据长度                           ncclDataType_t datatype, // 归约数据类型,可选:ncclInt8, ncclChar, ncclUint8, ncclInt32, ncclInt, ncclUint32, ncclInt64, ncclUint64, ncclFloat16, ncclHalf, ncclFloat32, ncclFloat, ncclFloat64, ncclDouble                           ncclRedOp_t op, // 归约计算类型,可选:ncclSum(+), ncclProd(*), ncclMin, ncclMax                           ncclComm_t comm, // NCCL 通信句柄                           cudaStream_t stream) // 绑定的 CUDA 流

AllGather

完全汇聚原语,从 K 个 rank 各取长度为 N 的一段数据,结果按 rank 顺序汇聚到每个 rank,长度为 K*N。

737ce2a958e604284faf45c2e6a565e2.png

NCCL API  AllGather 调用接口如下:

ncclResult_t ncclAllGat
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号