Deep Learning:MXNet 基於docker 容器的分佈式訓練實踐

引言

MXNet supports distributed training enabling us to leverage multiple machines for faster training.
 
MXNet支持分佈式培訓,使我們能夠利用多臺機器進行更快速的培訓。這段話來自於 MXNet官網 ,說明了MXNet 支持跨越設備運行。
 
How to Start Distributed Training?
那麼MXNet是怎樣實現分佈式訓練的?
 
仔細閱讀官方文檔,官方文檔給出了一個示例並寫了這樣一段話:
For distributed training of this example, we would do the following:
If the mxnet directory which contains the script image_classification.py is accessible to all machines in the cluster (for example if they are on a network file system), we can run:

../../tools/launch.py -n 3 -H hosts --launcher ssh python image_classification.py --dataset cifar10 --model vgg11 --epochs 1 --kvstore dist_sync

這是個使用 ssh 方式進行分佈式訓練的例子。

其中使用的測試代碼,來源於 https://github.com/apache/incubator-mxnet

注意,這裏的 launch.py 是一個非常重要的工具,如果仔細閱讀 python 源碼,會發現正是由於執行它才能實現分佈式訓練,它目前支持了5種分佈式或併發訓練方式:

launch 方式

–launcher denotes the mode of communication. The options are:

  • ssh if machines can communicate through ssh without passwords. This
    is the default launcher mode.
  • mpi if Open MPI is available
  • sge for Sun Grid Engine
  • yarn for Apache Yarn
  • local for launching all processes on the same local machine. This can be used for debugging purposes.

官網介紹了5種方式進行分佈式或並行訓練。這些方式都是以類似於集羣管理的方式進行分佈式訓練。如果閱讀整個測試代碼,你可能還會發現其他幾種集羣管理方式,如mesos,不知道什麼原因,官網沒有介紹,也沒有明確說支持 mesos 調度。

實際上,跟 tensorflow 或者 pytorch 進行分佈式訓練稍有不同。
在tensorflow 或者 pytorch 進行分佈式訓練,可能需要自己手動 或者 通過 mpi 工具起 不同角色,如 tensorflow 中的 ps 和 worker ,pytorch 中的 rank 。
而 MXNet 起不同的角色,全部都交給 launch.py 做完了。相當於對普通用戶進行了一定程度上的屏蔽。
 
MXNet 這樣做有一定的好處,普通用戶只需要關注 訓練腳本 的編寫,而不需要關注 分佈式計算 集羣 如何運作。相對應的,當用戶想拋開 launch.py 進行多機器多節點分佈式訓練 時,這也會成爲弊病。因爲MXNet 官網並沒有仔細介紹如何手動啓動 分佈式訓練。

官方單機併發訓練

export COMMAND='python example/gluon/image_classification.py --dataset cifar10 --model vgg11 --epochs 1 --kvstore dist_sync'
DMLC_ROLE=server DMLC_PS_ROOT_URI=127.0.0.1 DMLC_PS_ROOT_PORT=9092 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND &
DMLC_ROLE=server DMLC_PS_ROOT_URI=127.0.0.1 DMLC_PS_ROOT_PORT=9092 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND &
DMLC_ROLE=scheduler DMLC_PS_ROOT_URI=127.0.0.1 DMLC_PS_ROOT_PORT=9092 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND &
DMLC_ROLE=worker DMLC_PS_ROOT_URI=127.0.0.1 DMLC_PS_ROOT_PORT=9092 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND &
DMLC_ROLE=worker DMLC_PS_ROOT_URI=127.0.0.1 DMLC_PS_ROOT_PORT=9092 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND

這是MXNet官方給出的在單節點機器,起一個 scheduler ,兩個 worker, 兩個 server 進行分佈式訓練。但是實際情況中,我們希望將 上述三種角色, 以在不同主機上進行啓動,進行大規模硬件資源進行分佈式訓練。
 
另外,我們還希望通過僅僅提供硬件資源,以雲計算的方式提供服務,用戶之間具備隔離。也就是我們希望所有計算服務,以 docker 容器化方式啓動。通過指定 worker 和 server 的數量以及要使用的資源,來啓動容器。
 
如此一來,我們不能使用官方的 推薦 的 集羣管理方式進行分佈式訓練。我們真正需要實現的是,launch.py 實現的功能,並以自動化的方式在分佈式集羣上 啓動 docker 容器, 進行分佈式訓練。
 
所有的用戶可以創建自己的 docker image,push 到分佈式 image 倉庫,並掛載不同的存儲,達到用戶隔離的目的。

這個 例子中用的 cifar10 數據 在執行過程中下載非常慢,我把它 放在https://download.csdn.net/download/github_37320188/11584523 , (由於沒有積分了,小收幾個積分用用,不能老做公益呀)

實踐部分

如果已經嘗試過繞過 launch.py 進行分佈式訓練,最直接的方法是修改官方給出的 單機 併發訓練 腳本。往上翻 可以看到。
 
實際上,該腳本通過設置環境變量的方式,設置 不同 角色的參數,然後執行 python 腳本。
官方對相關參數已經有部分解釋。

  • DMLC_ROLE: Specifies the role of the process. This can be server,
    worker or scheduler. Note that there should only be one scheduler.
    When DMLC_ROLE is set to server or scheduler, these processes start
    when mxnet is imported.
  • DMLC_PS_ROOT_URI: Specifies the IP of the scheduler
  • DMLC_PS_ROOT_PORT: Specifies the port that the scheduler listens to
  • DMLC_NUM_SERVER: Specifies how many server nodes are in the cluster
  • DMLC_NUM_WORKER: Specifies how many worker nodes are in the cluster

 
但是嘗試修改參數過後,如果成功找到方向的話,會發現,官方給出的環境變量是不夠的。

解決思路

這裏提供 3 個解決思路去進行解決這個問題。

  1. 仔細閱讀 launch.py 的源碼,這是官方給的示例,雖然不清楚什麼原因官方沒有仔細解釋這部分功能、並將說明寫的再詳細些。但是 這部分確實實現了分佈式 訓練,代碼中對所有必須的環境變量進行了export,具有非常重要的參考的價值。
  2. 如果沒有 完整 閱讀 launch.py 的耐心或者準備,你可以想辦法打印出以 ssh 方式分佈式訓練時 export 的環境變量,但不保證能找到所有的。
  3. 最後,網絡上有一些開源的產品,做了類似自己實現 MXNet 分佈式訓練,不依賴 launch.py ,並以雲計算的方式提供服務。但是不保證他們在文檔方面說的很詳細,不過能實現說明研究過 手動進行分佈式訓練 。這裏推薦一個 https://github.com/bytedance/byteps,來源於 Bytedance

實際操作

一開始我打印出以 ssh 方式分佈式訓練時 export 的環境變量,用來確定了確實是忽略了一些環境變量。
 
然後,我纔開始閱讀 launch.py 的代碼,整個官方源碼包其他的代碼,找到了一些官方說明沒有提到的變量。
 
操作一波

  • 首先,假設,我們要起一個分佈式訓練,包括 1 個 scheduler,2 個 worker,2 個 server。
  • 把他們起來兩臺機器上,機器 1 的 ip 爲 192.168.61.55,機器 2 的 ip 爲 192.168.61.56
  • 機器 1 上起 scheduler ,1 個 worker, 1 個 server
  • 機器 2 上 起 1 個 worker, 1 個 server
# 機器 1 輸入
export COMMAND='python3 /mnt/mxnet-test/incubator-mxnet/example/gluon/image_classification.py --dataset cifar10 --model vgg11 --epochs 1 --kvstore dist_sync'
DMLC_ROLE=scheduler DMLC_PS_ROOT_URI=192.168.61.55 DMLC_PS_ROOT_PORT=9091 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 $COMMAND &
DMLC_ROLE=server DMLC_PS_ROOT_URI=192.168.61.55 DMLC_NODE_HOST=192.168.61.55 DMLC_PS_ROOT_PORT=9091 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 DMLC_SERVER_ID=0 $COMMAND &
DMLC_ROLE=worker DMLC_PS_ROOT_URI=192.168.61.55 DMLC_NODE_HOST=192.168.61.55 DMLC_PS_ROOT_PORT=9091 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 DMLC_WORKER_ID=0 $COMMAND

# 機器 2 輸入
export COMMAND='python3 /mnt/mxnet-test/incubator-mxnet/example/gluon/image_classification.py --dataset cifar10 --model vgg11 --epochs 1 --kvstore dist_sync'
DMLC_ROLE=server DMLC_PS_ROOT_URI=192.168.61.55 DMLC_NODE_HOST=192.168.61.56 DMLC_PS_ROOT_PORT=9091 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 DMLC_SERVER_ID=1 $COMMAND &
DMLC_ROLE=worker DMLC_PS_ROOT_URI=192.168.61.55 DMLC_NODE_HOST=192.168.61.56 DMLC_PS_ROOT_PORT=9091 DMLC_NUM_SERVER=2 DMLC_NUM_WORKER=2 DMLC_WORKER_ID=1 $COMMAND

python 版本爲 3.6.8
scheluder 不需要跟 任何其他 角色 綁定到 同一個 節點,也能獨立運行。
甚至 可以把 這 5 個服務起在 5臺機器上
DMLC_PS_ROOT_PORT 指定的是 起 scheduler 的機器上未被分配的可用端口
/mnt/mxnet-test/ 是一個分佈式存儲掛載目錄

這裏看到的是,相對於 官網 給到的介紹 多了幾個環境變量:

  • DMLC_NODE_HOST 啓動 worker 或者 server 標示自己所在主機的地址,可以是 ip 也可以是 ib 卡的地址
  • DMLC_SERVER_ID 啓動 server 是標記 index ,從 0 開始 計數,不可重複
  • DMLC_WORKER_ID 啓動 worker 是標記 index ,從 0 開始 計數,不可重複

如果你使用了ib 卡,除了直接指定 DMLC_NODE_HOST=ib卡的地址 ,還可通過 DMLC_INTERFACE 環境變量指定
除此之外,指定 DMLC_PS_ROOT_URI 和 DMLC_NODE_HOST 不能使用主機名,必須使用 ip 地址 或者 ib 卡的地址。原因是 MXNet 的通信依賴於 zmq ,其不支持 主機名。具體原因或者代碼可以網上查閱

docker 分佈式訓練

如果已經通過了上述 多機 分佈式訓練。後面就會變的稍微容易些。
首先,需要一個支持 python3 的 image,並且 支持 mxnet。你可以 在 docker hub 上找到一個

docker pull loongc/mxnet:v1

操作一波

  • 假設 要啓動 5 個容器,1 個運行 scheduler,2 個 worker, 2 個 server
  • 準備兩臺機器,機器 1 的 ip 爲 192.168.61.55, 機器2 的 ip 爲 192.168.61.56
  • 機器 1 上起 scheduler 容器,1 個 worker 容器,1 個server 容器
  • 機器 2 上起 1 個 worker 容器, 1 個 server 容器
# 機器 1 上的 啓動 shell 腳本 mxnet-test.sh
docker run -d --env-file /tmp/mxnet_env/worker  \
--name worker_1 \
-v /mnt/mxnet-test/incubator-mxnet:/incubator-mxnet \
-w /incubator-mxnet/example/gluon/ \
--net=host loongc/mxnet:v1 \
python3 image_classification.py --dataset cifar10 --model vgg11 --epochs 10 --kvstore dist_sync

docker run -d --env-file /tmp/mxnet_env/server  \
--name server_1 \
-v /mnt/mxnet-test/incubator-mxnet:/incubator-mxnet \
-w /incubator-mxnet/example/gluon/ \
--net=host loongc/mxnet:v1 \
python3 image_classification.py --dataset cifar10 --model vgg11 --epochs 10 --kvstore dist_sync

docker run -d --env-file /tmp/mxnet_env/scheduler  \
--name scheduler \
-v /mnt/mxnet-test/incubator-mxnet:/incubator-mxnet \
-w /incubator-mxnet/example/gluon/ \
--net=host loongc/mxnet:v1 \
python3 image_classification.py --dataset cifar10 --model vgg11 --epochs 10 --kvstore dist_sync

這裏在機器 1 上啓動了 3 個容器。
其中 的 /tmp/mxnet_env/worker , /tmp/mxnet_env/server , /tmp/mxnet_env/scheduler 文件 中寫的是環境變量

這裏舉個例子

# 機器1 上的 scheduler 配置 /tmp/mxnet_env/scheduler
DMLC_ROLE=scheduler
DMLC_PS_ROOT_URI=192.168.61.55
DMLC_PS_ROOT_PORT=9091
DMLC_NUM_SERVER=2
DMLC_NUM_WORKER=2

# 機器1 上的 worker 配置 /tmp/mxnet_env/worker 
DMLC_ROLE=worker
DMLC_PS_ROOT_URI=192.168.61.55
DMLC_PS_ROOT_PORT=9091
DMLC_WORKER_ID=0
DMLC_NODE_HOST=192.168.61.55
DMLC_NUM_SERVER=2
DMLC_NUM_WORKER=2

# 機器1 上的 server 配置 /tmp/mxnet_env/server
DMLC_ROLE=server
DMLC_PS_ROOT_URI=192.168.61.55
DMLC_PS_ROOT_PORT=9091
DMLC_NODE_HOST=192.168.61.55
DMLC_SERVER_ID=0
DMLC_NUM_SERVER=2
DMLC_NUM_WORKER=2

這些是機器 1 上的配置, 機器 2 上的配置很相似
區別在於:

  1. 機器 2 上的啓動腳本 mxnet-test.sh 不需要啓動 scheduler 容器,直接拿 機器 1 上腳本 改改 刪掉 scheduler 部分
  2. 機器 2 上不需要 /tmp/mxnet_env/scheduler 配置腳本
  3. 機器 2 上 /tmp/mxnet_env/worker 和 /tmp/mxnet_env/server 兩個文件中 DMLC_NODE_HOST 修改成機器2 的地址,DMLC_WORKER_ID、DMLC_SERVER_ID 修改爲 1 (從 0 開始 計數)

分別啓動兩臺機器上的 啓動腳本 mxnet-test.sh ,無嚴格先後順序。可以查看 /mnt/mxnet-test/incubator-mxnet/example/gluon/image-classification.log 查看執行情況。

結論

已經實現了手動分佈式 訓練,那麼離 自動化 實現分佈式訓練就不困難了。
 
可以通過 k8s 或者 mesos 進行集羣管理,通過調用 api 啓動 指定數目的 worker 和 server 容器,並指定硬件資源,確定是否使用 ib 卡等。
 
如果有更好的實現 分佈式訓練 MXNet ,歡迎留言討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章