全部产品
Search
文档中心

GPU 云服务器:使用AIACC-Training MXNet版

更新时间:Nov 02, 2023

由于MXNet支持KVStore和Horovod两种分布式训练方式,因此AIACC-Training 1.5能够支持使用KVStore的方式对MXNet分布式训练进行加速,同时支持Horovod的分布式训练方式,并且能够无缝兼容Horovod的API版本。

快速启用

代码适配与运行

适配AIACC-Training的方式与Horovod一致,如果您之前的训练代码是使用Horovod进行分布式训练,只需要替换import一行即可。替换内容如下:

import perseus.mxnet as hvd

示范用例

AIACC-Training软件包中为您提供了示例代码。您可以通过以下操作体验训练过程。

  1. 进入示例代码目录。

    cd `echo $(python -c "import perseus; print(perseus)") | cut -d\' -f 4 | sed "s/\_\_init\_\_\.py//"`examples/
  2. 启动分布式训练。

    以启动单机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。

  1. 使用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})
  2. 将进程绑定至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))
  3. 启动分布式训练任务。

    与其它框架类似,Perseus采用MPI的启动方式,并且启动单机多卡与多机多卡的方式基本一致,不再支持原生MXNet下的单机多卡在单一Process中的模式。以下操作以启动4机8卡分布式训练任务为例,带您体验启动过程。

    1. 准备训练脚本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 …
    2. 执行如下命令,启动训练脚本。

      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接口的分布式训练代码。

  1. 在main函数的开头部分,执行如下命令,初始化Perseus Horovod模块。

    说明

    请务必在使用其他Perseus API之前进行调用。

    hvd.init()
  2. 将当前进程绑定对应的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)
  3. 创建Optimizer。

    通常情况下,模型的学习率需要增大hvd.size()倍。

    说明

    部分模型不需要增大学习率,如BERT模型,具体请根据训练收敛情况作判断。

    learning_rate = ...
    optimizer_params = {'learning_rate': learning_rate * hvd.size()}
    opt = mx.optimizer.create(optimizer, **optimizer_params)
  4. 广播参数。

    # Horovod: fetch and broadcast parameters
    params = net.collect_params()
    if params is not None:
        hvd.broadcast_parameters(params)
  5. 重载Optimizer。

    # Horovod: create DistributedTrainer, a subclass of gluon.Trainer
    trainer = hvd.DistributedTrainer(params, opt)
  6. 启动训练。

    以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能够在忽略某些训练性能的情况下,提高收敛精度的上限。

操作步骤

  1. 使用perseus-MXNet-sync-bn.patch补丁。

    patch -p1 < perseus-mxnet-sync-bn.patch
  2. 编译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
  3. 调用SyncBatchNorm模型。

    Perseus的SyncBatchNorm实现基于原始MXNet官方代码,因此兼容SyncBatchNorm的原始使用方法,只需将名称从SyncBatchNorm修改为PerseusSyncBatchNorm,并增加参数comm_scope用于修改模式,如mx.gluon.contrib.nn.PerseusSyncBatchNorm(comm_scope=0), mx.sym.contrib.PerseusSyncBatchNorm(comm_scope=0)

  4. 修改模式。

    模式说明如下:

    • 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的精度对比,测试效果图如下:aiacc-sbn

如上图所示,从第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检查多机中是否存在运行的机器。