全部產品
Search
文件中心

:使用AIACC-Training MXNet版

更新時間:Jul 01, 2024

由於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檢查多機中是否存在啟動並執行機器。