由于MXNet支持KVStore和Horovod两种分布式训练方式,因此AIACC-Training 1.5能够支持使用KVStore的方式对MXNet分布式训练进行加速,同时支持Horovod的分布式训练方式,并且能够无缝兼容Horovod的API版本。
快速启用
代码适配与运行
适配AIACC-Training的方式与Horovod一致,如果您之前的训练代码是使用Horovod进行分布式训练,只需要替换import一行即可。替换内容如下:
import perseus.mxnet as hvd
- 完整的适配过程,请参见适配基于Horovod的API。
- 如果您的训练代码基于KVStore进行分布式训练,修改代码的具体操作,请参见适配基于KVStore的API。
示范用例
AIACC-Training软件包中为您提供了示例代码。您可以通过以下操作体验训练过程。
- 进入示例代码目录。
cd `echo $(python -c "import perseus; print(perseus)") | cut -d\' -f 4 | sed "s/\_\_init\_\_\.py//"`examples/
- 启动分布式训练。
以启动单机8卡的MNiST训练模型为例,示例命令如下:
perseusrun -np 8 -H localhost:8 python $examples_path/mxnet_mnist.py
适配MXNet
适配基于KVStore的API
为了支持InsightFace中特殊的数据+模型并行,Perseus KVStore增加了如下API:
- local_rank:返回当前GPU worker在本节点内部的编号,以此编号来建立对应的gpu context。您可以在Python内部,直接使用local_rank作为当前的GPU ID来创建context,相较于在启动的Shell脚本中获得当前的GPU编号作为参数传入Python的示范用例,此方式更为便捷。
- init(key_name, ndarray, param_only = false): init方法中增加参数param_only。取值说明如下:
- true:表示其他参数的同步。当需要一次性同步如feature map数据、AllReduce精度数据等参数时,或者当不使用KVStore进行参数更新时,需要添加param_only参数并设置为true。
- false:表示普通的梯度同步。
- push(key_name, ndarray, op = PerseusOp.Sum):push方法中增加了用于同步Softmax layer的输出参数op。该参数的取值范围是Sum、Max、Min。默认值为Sum。
- 使用Perseus KVStore。
您需要参考如下示例修改自身训练代码,新增+后代码import perseus mxnet module,并删除-后代码,替换为KVStore的生成。示例如下:
diff --git a/example/image-classification/common/fit.py b/example/image-classification/common/fit.py index 9412b6f..3a6e9a0 100755 --- a/example/image-classification/common/fit.py +++ b/example/image-classification/common/fit.py @@ -22,6 +22,7 @@ import time import re import math import mxnet as mx +import perseus.mxnet as perseus_kv def _get_lr_scheduler(args, kv): @@ -146,7 +147,8 @@ def fit(args, network, data_loader, **kwargs): # kvstore - kv = mx.kvstore.create(args.kv_store) + kv = perseus_kv.create(args.kv_store) if args.kv_store == dist_sync_perseus else mx.kvstore.create(args.kv_store) if args.gc_type != 'none': kv.set_gradient_compression({'type': args.gc_type, 'threshold': args.gc_threshold})
- 将进程绑定至GPU卡。
AIACC-Training通过重载KVStore实现了对MXNet分布式训练的支持,在API上与原生KVStore基本兼容,使用AIACC-Training后,您只需要对模型代码中的ctx设定稍作修改,将单进程绑定至单张GPU卡上即可。
以如下代码片段为例,使用Perseus KVStore新增的API local_rank,将当前process绑定到kv.local_rank所对应的GPU卡上。
ctx = [] cvd = os.environ['DEVICES'].strip() if 'perseus' in args.kv_store: import perseus.mxnet as perseus ctx.append(mx.gpu(kv.local_rank))
- 启动分布式训练任务。
与其它框架类似,Perseus采用MPI的启动方式,并且启动单机多卡与多机多卡的方式基本一致,不再支持原生MXNet下的单机多卡在单一Process中的模式。以下操作以启动4机8卡分布式训练任务为例,带您体验启动过程。
- 准备训练脚本config.sh。
由于该脚本要使用mpirun命令运行,因此需要使用MPI的环境变量来推导此进程所对应的GPU设备ID,然后设定此环境变量,并将此ID作为参数传递到module代码中去创建对应的ctx。脚本示例如下:
#!/bin/sh let GPU=OMPI_COMM_WORLD_RANK % OMPI_COMM_WORLD_LOCAL_SIZE export OMP_NUM_THREADS=4 MXNET_VISIBLE_DEVICE=$GPU python train_imagenet.py \ --network resnet \ --num-layers 50 \ --kv-store dist_sync_perseus \ --gpus $GPU …
- 执行如下命令,启动训练脚本。
mpirun -np 32 -npernode 8 -hostfile mpi_host.txt ./config.sh
其中,mpi_host.txt为普通的MPI machinefile,与MXNet的SSHLauncher的host file类似,简单示例如下:
192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4
开始训练之后,每个GPU都是一个单独的进程,有各自的输出。查看多机情况下每个机器的输出,总性能为所有单进程性能的总和。
开源版本MXNet会默认占据系统所有的CPU资源,因此在最初的启动阶段,会占用较多的CPU时间,启动速度较慢。您可以通过设置以下环境变量解决该问题。
export MXNET_USE_OPERATOR_TUNING=0 export MXNET_USE_NUM_CORES_OPERATOR_TUNING=1 export OMP_NUM_THREADS=1
适配基于Horovod的API
本小节介绍如何使用Horovod兼容API进行MXNet分布式训练的基本步骤,以下操作为原始训练代码适配到AIACC-Traninig的一般过程。
AIACC-Training for MXNet支持Horovod API。适配AIACC-Training的方式与Horovod一致,如果您之前是使用Horovod进行分布式训练,只需替换import模块即可。替换后的内容如下:
import perseus.mxnet as hvd
如果您的训练代码是非分布式代码,可以参考以下操作步骤将训练代码升级为Horovod接口的分布式训练代码。
- 在main函数的开头部分,执行如下命令,初始化Perseus Horovod模块。
说明:请务必在使用其他Perseus API之前进行调用。
hvd.init()
- 将当前进程绑定对应的GPU卡。
# rank and size rank = hvd.rank() num_workers = hvd.size() local_rank = hvd.local_rank() # Horovod: pin GPU to local rank context = mx.gpu(local_rank)
- 创建Optimizer。
通常情况下,模型的学习率需要增大hvd.size()倍。
说明:部分模型不需要增大学习率,如BERT模型,具体请根据训练收敛情况作判断。
learning_rate = ... optimizer_params = {'learning_rate': learning_rate * hvd.size()} opt = mx.optimizer.create(optimizer, **optimizer_params)
- 广播参数。
# Horovod: fetch and broadcast parameters params = net.collect_params() if params is not None: hvd.broadcast_parameters(params)
- 重载Optimizer。
# Horovod: create DistributedTrainer, a subclass of gluon.Trainer trainer = hvd.DistributedTrainer(params, opt)
- 启动训练。
以4机8卡的训练为例,启动命令示例如下:
mpirun -np 32 -npernode 8 -hostfile mpi_host.txt ./train.sh
其中,mpi_host.txt为普通的MPI machinefile,与MXNet的SSHLauncher的host file类似,简单示例如下:
192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4
开始训练之后,每个GPU都是一个单独的进程,有各自的输出。类比多机情况下每个机器的输出,总性能为所有单process性能的总和。
使用SyncBatchNorm
Perseus的SyncBatchNorm实现基于MXNet官方代码src/operator/contrib/sync_batch_norm-inl.h的计算逻辑,并通过加载libperseus_MXNet.so调用Perseus通信的API,在operator内部实现SyncBatchNorm,且支持单机local模式以及全局global模式。
背景信息
针对object-detection等小Batch Size场景,继续使用每个GPU单独的BatchNorm计算的mean和var信息有较大的偏差,会带来一定的精度损失。通过使用SyncBatchNorm可以弥补对统计信息的内部偏移,真正发挥理论上BN层的作用,即使在大规模分布式的情况下也能达到更高的期望精度。相较于原始BatchNorm,SyncBatchNorm能够在忽略某些训练性能的情况下,提高收敛精度的上限。
操作步骤
- 使用perseus-MXNet-sync-bn.patch补丁。
patch -p1 < perseus-mxnet-sync-bn.patch
- 编译MXNet源码。
make USE_OPENCV=1 USE_BLAS=openblas USE_CUDA=1 USE_CUDA_PATH=/usr/local/cuda USE_CUDNN=1 USE_DIST_KVSTORE=1 USE_NCCL=1 USE_LIBJPEG_TURBO=1 MPI_ROOT=/usr/local -j24
- 调用SyncBatchNorm模型。
Perseus的SyncBatchNorm实现基于原始MXNet官方代码,因此兼容SyncBatchNorm的原始使用方法,只需将名称从SyncBatchNorm修改为PerseusSyncBatchNorm,并增加参数comm_scope用于修改模式,如mx.gluon.contrib.nn.PerseusSyncBatchNorm(comm_scope=0), mx.sym.contrib.PerseusSyncBatchNorm(comm_scope=0)。
- 修改模式。
模式说明如下:
- local:局部平均,即每次forward、backward计算均值和方差后,只在单机内部的GPU上分别进行同步。默认为此模式,参数设置为PerseusSyncBatchNorm(comm_scope=0)。
- global:全局平均,即每次forward、backward计算的均值和方差在全局进行同步,需要修改BN定义的参数为PerseusSyncBatchNorm(comm_scope=1)。
测试精度
以单卡Batch Size为2,单机8卡的训练为例,使用基于GluonCV实现的Faster RCNN模型适配Perseus,然后进行原始BatchNorm与PerseusSyncBatchNorm的精度对比,测试效果图如下:
如上图所示,从第1个Epoch开始,到第20个Epoch结束,PerseusSyncBatchNorm达到的精度均高于BatchNorm,最高的mAP从31.3%提升至34.6%。
常见问题
额外显存占用问题
以单机8卡为例,0号卡被其余7个process均占用了200 MB到500 MB显存,从而导致0号卡显存被耗尽。
该问题的根因在于MXNet内部的cpu_pinned memory分配机制默认使用0号卡。您可以参考上文步骤2,重新绑定GPU卡。
运行时显示Undefined symbols
运行时显示NDArray相关的symbol没有定义,即Undefined symbols。
该问题由于pip安装了1.4之前版本的MXNet,导致没有导出libMXNet.so中对于Perseus必要的symbol。您可以将MXNet升级到1.4或以上版本,也可以重新编译安装MXNet。
启动速度较慢
您可以尝试以下方法改善该问题:
- 检查CPU的负载,若占比很高可尝试进行以下设置:
export MXNET_USE_OPERATOR_TUNING=0 export MXNET_USE_NUM_CORES_OPERATOR_TUNING=1 export OMP_NUM_THREADS=1
- 减小preprocess的线程数。
Perseus下训练模式为单process、单GPU,如果默认线程数设置过大,可以根据单节点的GPU数目,等比例缩小preprocess的线程数。例如preprocess的线程数默认值为24,而单节点GPU数目为8,那么您可以将preprocess的线程数降低为3或4。
单机时正常,多机时异常退出
可能是由于人为导致多机中存在正在运行训练的机器,则运行多机会发生create cusolver handle failed报错,您可以使用mpirun执行nvidia-smi检查多机中是否存在运行的机器。
好啦!小弹的分享到此为止。我们更欢迎您分享您对阿里云产品的设想、对功能的建议或者各种吐槽,请扫描提交问卷并获得社区积分或精美礼品一份。https://survey.aliyun.com/apps/zhiliao/P4y44bm_8
【扫码填写上方调研问卷】
欢迎每位来到弹性计算的开发者们来反馈问题哦~