欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

计图MPI分布式多卡

程序员文章站 2022-07-12 21:37:54
...

计图MPI分布式多卡
计图分布式基于MPI(Message Passing Interface),主要阐述使用计图MPI,进行多卡和分布式训练。目前计图分布式处于测试阶段。
计图MPI安装
计图依赖OpenMPI,用户可以使用如下命令安装OpenMPI:
sudo apt install openmpi-bin openmpi-common libopenmpi-dev
计图会自动检测环境变量中是否包含mpicc,如果计图成功的检测到了mpicc,输出如下信息:
[i 0502 14:09:55.758481 24 init.py:203] Found mpicc(1.10.2) at /usr/bin/mpicc
如果计图没有在环境变量中找到mpi,用户也可以手动指定mpicc的路径告诉计图,添加环境变量即可:export mpicc_path=/you/mpicc/path
OpenMPI安装完成以后,用户无需修改代码,需要做的仅仅是修改启动命令行,计图就会用数据并行的方式,自动完成并行操作。

单卡训练代码

python3.7 -m jittor.test.test_resnet

分布式多卡训练代码

mpirun -np 4 python3.7 -m jittor.test.test_resnet

指定特定显卡的多卡训练代码

CUDA_VISIBLE_DEVICES=“2,3” mpirun -np 2 python3.7 -m jittor.test.test_resnet
便捷性的背后,计图的分布式算子的支撑,计图支持的mpi算子后端会使用nccl进行进一步的加速。计图所有分布式算法的开发,均在Python前端完成,让分布式算法的灵活度增强,开发分布式算法的难度也大大降低。
基于这些mpi算子接口,研发团队已经集成了如下三种分布式相关的算法:
• 分布式数据并行加载
• 分布式优化器
• 分布式同步批归一化层
用户在使用MPI进行分布式训练时,计图内部的Dataset类会自动并行分发数据,需要注意的是Dataset类中设置的Batch size是所有节点的batch size之和,也就是总batch size,不是单个节点接收到的batch size。
MPI接口
目前MPI开放接口如下:
• jt.mpi: 计图的MPI模块,当计图不在MPI环境下时,jt.mpi == None, 用户可以用这个判断是否在mpi环境下。
• jt.Module.mpi_param_broadcast(root=0): 将模块的参数从root节点广播给其他节点。
• jt.mpi.mpi_reduce(x, op=‘add’, root=0): 将所有节点的变量x使用算子op,reduce到root节点。如果op是’add’或者’sum’,该接口会把所有变量求和,如果op是’mean’,该接口会取均值。
计图MPI分布式多卡

• jt.mpi.mpi_broadcast(x, root=0): 将变量x从root节点广播到所有节点。
计图MPI分布式多卡

• jt.mpi.mpi_all_reduce(x, op=‘add’): 将所有节点的变量x使用一起reduce,并且吧reduce的结果再次广播到所有节点。如果op是’add’或者’sum’,该接口会把所有变量求和,如果op是’mean’,该接口会取均值。
计图MPI分布式多卡

实例:MPI实现分布式同步批归一化层
下面的代码是使用计图实现分布式同步批,归一化层的实例代码,在原来批归一化层的基础上,只需增加三行代码,就可以实现分布式的batch norm,添加的代码如下:

将均值和方差,通过all reduce同步到所有节点

if self.sync and jt.mpi:
xmean = xmean.mpi_all_reduce(“mean”)
x2mean = x2mean.mpi_all_reduce(“mean”)
注:计图内部已经实现了同步的批归一化层,用户不需要自己实现
分布式同步批归一化层的完整代码:
class BatchNorm(Module):
def init(self, num_features, eps=1e-5, momentum=0.1, affine=None, is_train=True, sync=True):
assert affine == None

    self.sync = sync
    self.num_features = num_features
    self.is_train = is_train
    self.eps = eps
    self.momentum = momentum
    self.weight = init.constant((num_features,), "float32", 1.0)
    self.bias = init.constant((num_features,), "float32", 0.0)
    self.running_mean = init.constant((num_features,), "float32", 0.0).stop_grad()
    self.running_var = init.constant((num_features,), "float32", 1.0).stop_grad()

def execute(self, x):
    if self.is_train:
        xmean = jt.mean(x, dims=[0,2,3], keepdims=1)
        x2mean = jt.mean(x*x, dims=[0,2,3], keepdims=1)
        # 将均值和方差,通过all reduce同步到所有节点
        if self.sync and jt.mpi:
            xmean = xmean.mpi_all_reduce("mean")
            x2mean = x2mean.mpi_all_reduce("mean")

        xvar = x2mean-xmean*xmean
        norm_x = (x-xmean)/jt.sqrt(xvar+self.eps)
        self.running_mean += (xmean.sum([0,2,3])-self.running_mean)*self.momentum
        self.running_var += (xvar.sum([0,2,3])-self.running_var)*self.momentum
    else:
        running_mean = self.running_mean.broadcast(x, [0,2,3])
        running_var = self.running_var.broadcast(x, [0,2,3])
        norm_x = (x-running_mean)/jt.sqrt(running_var+self.eps)
    w = self.weight.broadcast(x, [0,2,3])
    b = self.bias.broadcast(x, [0,2,3])
    return norm_x * w + b