MXNet之ps-lite及parameter server原理
ps-lite框架是DMLC組自行實現的parameter server通信框架,是DMLC其他項目的核心,例如其深度學習框架MXNET的分布式訓練就依賴ps-lite的實現。
parameter server原理
在機器學習和深度學習領域,分布式的優化已經成了一種先決條件,因為單機已經解決不了目前快速增長的數據與參數帶來的問題。現實中,訓練數據的數量可能達到1TB到1PB之間,而訓練過程中的參數可能會達到\(10^9\)到\(10^{12}\)。而往往這些模型的參數需要被所有的worker節點頻繁的訪問,這就會帶來很多問題和挑戰:
- 訪問這些巨量的參數,需要大量的網絡帶寬支持;
- 很多機器學習算法都是連續型的,只有上一次迭代完成(各個worker都完成)之后,才能進行下一次迭代,這就導致了如果機器之間性能差距大(木桶理論),就會造成性能的極大損失;
- 在分布式中,容錯能力是非常重要的。很多情況下,算法都是部署到雲環境中的(這種環境下,機器是不可靠的,並且job也是有可能被搶占的)。
分布式系統中的同步與異步機制
parameter server架構
在parameter server中,每個 server 實際上都只負責分到的部分參數(servers共同維持一個全局的共享參數),而每個 work 也只分到部分數據和處理任務。
PS架構包括計算資源與機器學習算法兩個部分。其中計算資源分為兩個部分,參數服務器節點和工作節點:
- 參數服務器節點用來存儲參數
- 工作節點部分用來做算法的訓練
機器學習算法也分成兩個部分,即參數和訓練:
- 參數部分即模型本身,有一致性的要求,參數服務器也可以是一個集群,對於大型的算法,比如DNN,CNN,參數上億的時候,自然需要一個集群來存儲這么多的參數,因而,參數服務器也是需要調度的。
- 訓練部分自然是並行的,不然無法體現分布式機器學習的優勢。因為參數服務器的存在,每個計算節點在拿到新的batch數據之后,都要從參數服務器上取下最新的參數,然后計算梯度,再將梯度更新回參數服務器。
這種設計有兩種好處:
- 通過將機器學習系統的共同之處模塊化,算法實現代碼更加簡潔。
- 作為一個系統級別共享平台優化方法,PS結構能夠支持很多種算法。
從而,PS架構有五個特點:
- 高效的通信:異步通信不會拖慢計算
- 彈性一致:將模型一致這個條件放寬松,允許在算法收斂速度和系統性能之間做平衡。
- 擴展性強:增加節點無需重啟網絡
- 錯誤容忍:機器錯誤恢復時間短,Vector Clock容許網絡錯誤
- 易用性: 全局共享的參數使用向量和矩陣表示,而這些又可以用高性能多線程庫進行優化。
Push and Pull
在parameter server中,參數都是可以被表示成(key, value)的集合,比如一個最小化損失函數的問題,key就是feature ID,而value就是它的權值。對於稀疏參數,不存在的key,就可以認為是0。
把參數表示成k-v, 形式更自然, 易於理,更易於編程解。workers跟servers之間通過push與pull來通信的。worker通過push將計算好的梯度發送到server,然后通過pull從server更新參數。為了提高計算性能和帶寬效率,parameter server允許用戶使用Range Push跟Range Pull 操作。
Task:Synchronous and Asynchronous
Task也分為同步和異步,區別如下圖所示:
所以,系統性能跟算法收斂速率之間是存在一個平衡,你需要同時考慮:
- 算法對於參數非一致性的敏感度
- 訓練數據特征之間的關聯度
- 硬盤的存儲容量
考慮到用戶使用的時候會有不同的情況,parameter server 為用戶提供了多種任務依賴方式:
- Sequential:這里其實是 synchronous task,任務之間是有順序的,只有上一個任務完成,才能開始下一個任務。
- Eventual: 跟sequential相反,所有任務之間沒有順序,各自獨立完成自己的任務。
- Bounded Delay: 這是sequential 跟 eventual 之間的一個均衡,可以設置一個\(\tau\)作為最大的延時時間。也就是說,只有大於\(\tau\)之前的任務都被完成了,才能開始一個新的任務;極端的情況:
- \(\tau=0\),情況就是 Sequential;
- \(\tau=\infty\),情況就是 Eventual;
PS下的算法
算法1是沒有經過優化的直接算法和它的流程圖如下:
算法3中的KKT Filter可以是用戶自定義過濾:
對於機器學習優化問題比如梯度下降來說,並不是每次計算的梯度對於最終優化都是有價值的,用戶可以通過自定義的規則過濾一些不必要的傳送,再進一步壓縮帶寬消耗:
- 發送很小的梯度值是低效的:
因此可以自定義設置,只在梯度值較大的時候發送; - 更新接近最優情況的值是低效的:
因此,只在非最優的情況下發送,可通過KKT來判斷;
ps-lite實現
上面說了parameter server的原理,現在來看下這個是怎么實現的。ps-lite是DMLC實現parameter server的一個程序,也是MXNet的核心組件之一。
ps-lite角色
ps-lite包含三種角色:Worker、Server、Scheduler。具體關系如下圖:
圖9 三種角色的關系圖
Worker節點負責計算參數,並發參數push到Server,同時從Serverpull參數回來。
Server節點負責管理Worker節點發送來的參數,並“合並”,之后供各個Worker使用。
Scheduler節點負責管理Worker節點和Server節點的狀態,worker與server之間的連接是通過Scheduler的。
重要類
-
Postoffice是全局管理類,單例模式創建。主要用來配置當前node的一些信息,例如當前node是哪種類型(server,worker,scheduler),nodeid是啥,以及worker/server 的rank 到 node id的轉換。
-
Van是負責通信的類,是Postoffice的成員。Van中std::unordered_map<int, void*> senders_保存了node_id到連接的映射。Van只是定義了接口,具體實現是依賴ZMQ實現的ZMQVan,Van類負責建立起節點之間的互相連接(例如Worker與Scheduler之間的連接),並且開啟本地的receiving thread用來監聽收到的message。。
-
Customer用來通信,跟蹤request和response。每一個連接對應一個Customer實例,連接對方的id和Customer實例的id相同。
-
SimpleApp是一個基類;提供了發送接收int型的head和string型的body消息,以及注冊消息處理函數。它有2個派生類。
-
KVServer是SimpleApp的派生類,用來保存key-values數據。里面的Process()被注冊到Customer對象中,當Customer對象的receiving thread接受到消息時,就調用Process()對數據進行處理。
-
KVWorker是SimpleApp的派生類,主要有Push()和Pull(),它們最后都會調用Send()函數,Send()對KVPairs進行切分,因為每個Server只保留一部分參數,因此切分后的SlicedKVpairs就會被發送給不同的Server。切分函數可以由用戶自行重寫,默認為DefaultSlicer,每個SlicedKVPairs被包裝成Message對象,然后用van::send()發送。
-
KVPairs封裝了Key-Value結構,還包含了一個長度選項。
-
SArray是Shared array,像智能指針一樣共享數據,接口類似vector。
-
Node封裝了節點的信息,例如角色、ip、端口、是否是恢復節點。
-
Control封裝了控制信息,例如命令類型、目的節點、barrier_group的id、簽名。
-
Meta封裝了元數據,發送者、接受者、時間戳、請求還是相應等。
-
Message是要發送的信息,除了元數據外,還包括發送的數據。
運行腳本
為了更好地看到ps-lite的運行原理,我們先來看下它在本地運行的腳本:
#!/bin/bash
# set -x
if [ $# -lt 3 ]; then
echo "usage: $0 num_servers num_workers bin [args..]"
exit -1;
fi
export DMLC_NUM_SERVER=$1
shift
export DMLC_NUM_WORKER=$1
shift
bin=$1
shift
arg="$@"
# start the scheduler
export DMLC_PS_ROOT_URI='127.0.0.1'
export DMLC_PS_ROOT_PORT=8000
export DMLC_ROLE='scheduler'
${bin} ${arg} &
# start servers
export DMLC_ROLE='server'
for ((i=0; i<${DMLC_NUM_SERVER}; ++i)); do
export HEAPPROFILE=./S${i}
${bin} ${arg} &
done
# start workers
export DMLC_ROLE='worker'
for ((i=0; i<${DMLC_NUM_WORKER}; ++i)); do
export HEAPPROFILE=./W${i}
${bin} ${arg} &
done
wait
這個腳本主要做了兩件事,第一件是為不同的角色設置環境變量,第二件是在本地運行多個不同的角色。所以說ps-lite是要多個不同的進程(程序)共同合作完成工作的,ps-lite采取的是用環境變量來設置角色的配置。
test_simple_app流程
test_simple_app.cc是一人很簡單的app,其它復雜的流程原理這個程序差不多,所以我們就說說這個程序是怎么運行的。先來看下剛開始運行程序時,worker(W)\Server(S)\Scheduler(H)之間是怎么連接的,這里沒有寫Customer處理普通信息的流程。W\S\H代表上面腳本運行各個角色后在不同角色程序內的處理流程。
- W\S\H:初始化SimpleApp --> New Customer(綁定Process函數) --> Customer起一個Receiving線程
- W\S\H:初始化static PostOffice,全局都用同一個PostOffice --> Create(Van)用來做通信的發/發 --> 從環境變量中讀入配置 --> 確定不同的角色。
- W\S\H:Start() --> Van::Start(), my_node_/Scheduler的初始化
- W\S:綁定port並連接到同一個Scheduler
- W\S:發送信息到指定ID
- W\S\h:在van中起一個Reciving的線程
- H:收到信息並回發
- W\S: 收到信息
- W\S\H:Finalize()
Customer處理普通信息流程如下:
- H:app->requst() --> 放這個請求入到tracker_中 --> send(msg) --> app->wait()[等待收回發的信息]
- W/S:收到信息后放到recv_queue_中
- W/S:Customer的Reciving收到信息 --> call recv_handle_ --> process(recv)[處理信息] --> response_hadle_(recv) --> ReqHandle() --> response()[回發信息]
- H:收到回發的信息 --> 放入到recv_queue_中處理 --> 在Customer中的Reciving中處理
- H:當tracker_.first == tracker_.second時,釋放app->wait()
參考引用:
[1] http://blog.csdn.net/stdcoutzyx/article/details/51241868
[2] http://blog.csdn.net/cyh_24/article/details/50545780
[3] https://www.zybuluo.com/Dounm/note/529299
[4] http://blog.csdn.net/KangRoger/article/details/73307685
【防止爬蟲轉載而導致的格式問題——鏈接】:
http://www.cnblogs.com/heguanyou/p/7868596.html