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