MXNet之ps-lite及parameter server原理


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也是有可能被搶占的)。

分布式系統中的同步與異步機制

synchronous

圖1 在同步的機制下,系統運行的時間是由最慢的worker節點與通信時間決定的

asynchronous

圖2 在異步的機制下,每個worker不能等待其它workers完成再運行下一次迭代。這樣可以提高效率,但從迭代次數的角度來看,會減慢收斂的速度。

parameter server架構

在parameter server中,每個 server 實際上都只負責分到的部分參數(servers共同維持一個全局的共享參數),而每個 work 也只分到部分數據和處理任務。

ps_struct

圖3 parameter server的架構圖,server 節點可以跟其他 server 節點通信,每個server負責自己分到的參數,server group 共同維持所有參數的更新。server manager node 負責維護一些元數據的一致性,比如各個節點的狀態,參數的分配情況等;worker 節點之間沒有通信,只跟自己對應的server進行通信。每個worker group有一個task scheduler,負責向worker分配任務,並且監控worker的運行情況。當有新的worker加入或者退出,task scheduler 負責重新分配任務。

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也分為同步和異步,區別如下圖所示:

圖4 如果iter1需要在iter0 computation,push跟pull都完成后才能開始,那么就是Synchronous,反之就是Asynchronous。Asynchronous能夠提高系統的效率(因為節省了很多等待的過程),但是,它可能會降低算法的收斂速率;

所以,系統性能跟算法收斂速率之間是存在一個平衡,你需要同時考慮:

  • 算法對於參數非一致性的敏感度
  • 訓練數據特征之間的關聯度
  • 硬盤的存儲容量

考慮到用戶使用的時候會有不同的情況,parameter server 為用戶提供了多種任務依賴方式:

consistency

圖5 三種不同的依賴方式
  • Sequential:這里其實是 synchronous task,任務之間是有順序的,只有上一個任務完成,才能開始下一個任務。
  • Eventual: 跟sequential相反,所有任務之間沒有順序,各自獨立完成自己的任務。
  • Bounded Delay: 這是sequential 跟 eventual 之間的一個均衡,可以設置一個\(\tau\)作為最大的延時時間。也就是說,只有大於\(\tau\)之前的任務都被完成了,才能開始一個新的任務;極端的情況:
    • \(\tau=0\),情況就是 Sequential;
    • \(\tau=\infty\),情況就是 Eventual;

PS下的算法

算法1是沒有經過優化的直接算法和它的流程圖如下:

Algorithm1

圖6 算法1

Algorithm_1_1

圖7 算法1的流程

Algorithm3

圖8 優化算法1后的算法3。

算法3中的KKT Filter可以是用戶自定義過濾:
對於機器學習優化問題比如梯度下降來說,並不是每次計算的梯度對於最終優化都是有價值的,用戶可以通過自定義的規則過濾一些不必要的傳送,再進一步壓縮帶寬消耗:

  1. 發送很小的梯度值是低效的:
    因此可以自定義設置,只在梯度值較大的時候發送;
  2. 更新接近最優情況的值是低效的:
    因此,只在非最優的情況下發送,可通過KKT來判斷;

ps-lite實現

上面說了parameter server的原理,現在來看下這個是怎么實現的。ps-lite是DMLC實現parameter server的一個程序,也是MXNet的核心組件之一。

ps-lite角色

ps-lite包含三種角色:Worker、Server、Scheduler。具體關系如下圖:

s_w_h

圖9 三種角色的關系圖

Worker節點負責計算參數,並發參數push到Server,同時從Serverpull參數回來。
Server節點負責管理Worker節點發送來的參數,並“合並”,之后供各個Worker使用。
Scheduler節點負責管理Worker節點和Server節點的狀態,worker與server之間的連接是通過Scheduler的。

重要類

class

圖10 重要類的關系圖
  • 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM