面试记录:多卡并行的原理是什么
前言
这道题偏原理,不过为了让讲的东西不这么枯燥,我们在原理中穿插一点具体技术。
概览
本质上,多卡并行的出现,就是考虑到数据与中断反复通过CPU进行调度的时候,会产生高频的延迟。更不用说大批量数据进去计算的时候,总线有速度上线,CPU有取数上限,内存有访问上限,各种各样的IO限制了瓶颈。因此,多卡并行现阶段主要是通过RDMA协议,直接走主板上的PCI-E总线进行数据传输,从而避免了CPU的调度。
那我们就从从宇宙大爆炸开始……
当然,只是开个玩笑。我们先从很远很远的地方开始,然后再慢慢递进。毕竟,多卡并行并不是几个技术栈就能讲明白的。
算力平台部署
算力平台部署就是一个面向用户的场景,我们可能有很多张卡,甚至一张卡通过MIG去分成了不同的实例,整卡或者实例之间采用一些技术手段进行通信,才是一个完整的算力平台。
我们先不考虑什么GPU Stack、NVIDIA DevOps等成套的解决方案,我们直接从零开始。
首先,我们反正就是拿到了两张A100,然后可以开机并进入系统,并且nvidia-smi是正常列出两台设备的。到这一步没什么问题。
接下来,我们要考虑的,首先就是如何让用户通过Web端申请到一部分算力,并对外提供服务。
简单地说,就是像这样:
graph TD A[卡1]-->C[服务] B[卡2]-->C
docker
至于什么docker原理啊使用啊什么的这里就全跳过了。
总之就是,你看到上面图1的内容,就会想起docker里面的gpus参数。我们在nvidia-docker中配置了gpus参数,那么docker就会将GPU设备映射到容器中,然后docker会调用nvidia-container-runtime,将GPU设备映射到CUDA中。
相当于,对于任何一台机器,我们都是用nvidia-docker强行讲所有可用设备虚拟化了。虚拟化了之后,我们后续的服务也就脱离开了硬件本身的各种调度,直接使用硬件驱动所提供的服务。
MIG
当然,这样做肯定是有缺陷的。尤其是,部分人可能用过docker将一张卡虚拟化成了三个服务,每次都指向了同一个卡,显存也就直接爆炸,死机也就很经常了。
于是呢,MIG就是一个解决方案,将一张卡区分出多个实例,然后再想办法虚拟化。
P.S.:消费级显卡是没有这种技术的,只有专业计算卡才有。
本质上来说,也就是让docker虚拟化的时候,避免了一次占用了整张卡,显存存数据的时候也就彻底实现了一张卡分配给多个人用。
那么,怎么用呢?
首先,我们先启用显卡的MIG功能:
1 | $ sudo nvidia-smi -i 0 -mig 1 |
这句话就是启用GPU:0的MIG功能(配置MIG为enable,标志位是
然后,我们再分解(这里直接就将A100分解为多个
1 | $ sudo nvidia-smi mig -i 0 -cgi 19,19,19,19,19,19,19 -C |
这里的参数稍微多一些,大概是这样:
| 参数 | 全写行为 | 含义 |
|---|---|---|
mig |
MIG子命令 |
表示要操作MIG相关配置 |
-i 0 |
--id 0 |
选择GPU0 |
-cgi |
--create-gpu-instance |
创建GPU Instance组合 |
19,19,19,19,19,19,19 |
配置profile IDs |
1g.5gb的GI Profile |
-C |
--compute-instance |
在创建 GI 后,自动为每个 GI 创建 CI |
聪明的你肯定发现了:为什么拆5G的实例的时候,只能写
这就涉及到硬件底层了。因为A100只有GPC,也就是Graphics Processing Cluster,每个GPC就是一个处理单元,所以只能拆成
拆完之后,我们就可以查看一下具体的实例UUID了:
1 | $ nvidia-smi -L |
他将输出这么些玩意儿:
1 | GPU 0: A100 (UUID: GPU-XXXXXX) |
这些内容都将暴露在/proc/driver/nvidia/mig/*里面。
k8s
首先,通过nvidia-device-plugin,就可以去上面这个暴露的接口找到硬件信息,然后返回MIG对象。
拿到对象后,继续在对象基础上使用k8s API server,注册资源类型。在注册的时候,主要依赖nvidia-device-plugin会主动汇集并上报资源信息,这样核心的kubelet才能够感知到,因为它本身无法解析GPU资源信息,而只能够识别CPU、Memory这类基础资源。只有nvidia-device-plugin上报后,GPU资源信息才能够转换成k8s资源对象。
注册之后,再经过GPU Feature Discovery,读取node GPU拓扑,并打上标签。到这一步,就可以直接用k8s的命令查看节点信息了。
最后,通过GPU Operator,将MIG对象转换成GPU Instance对象,并返回给k8s。
到这里,k8s才有显卡的调度权限。
好了,到这里,多卡并行在应用层的思路就介绍完了。接下来就是底层数据了。
NCCL
NCCL是NVIDIA的GPU通信库,NCCL提供了GPU通信的接口,并且提供了GPU通信的实现。