說明
最近碰到地一個問題是多機分布式訓練的時候總是起不了程序隧膘,但是每臺機器單獨起訓練程序總是沒有問題所宰,感覺這個問題經(jīng)常碰到,解決的辦法就是刪掉程序篮迎,重新再來,確保每一臺機器的程序一模一樣,這樣非常頭疼甜橱,因為不理解其中的原理逊笆,接下來準備把kv-store和ps-lite看看,把其中的原理弄弄明白岂傲,方便以后調(diào)試
下面這篇文章基于官https://mxnet.incubator.apache.org/tutorials/python/kvstore.html
分布式key value存儲
KVStore是一個數(shù)據(jù)共享的地方难裆,把它想象成一個跨設備(不同的GPU和機器)的對象就行了,每個設備可以推數(shù)據(jù)到該對象譬胎,也可以從該對象拉數(shù)據(jù)到本地
初始化
首先舉一個簡單的例子差牛,初始化一個(int, NDArray)類型的鍵值對,然后通過pull操作拿出這個值
import mxnet as mx
kv = mx.kv.create('local') # create a local kv store.
shape = (2,3)
kv.init(3, mx.nd.ones(shape)*2)
a = mx.nd.zeros(shape)
kv.pull(3, out = a)
print a
[[ 2. 2. 2.]
[ 2. 2. 2.]]
<NDArray 2x3 @cpu(0)>
初始化一個全部為2對2x3的數(shù)據(jù)作為值堰乔,用3作為這個值的key偏化,pull出來的結(jié)果和創(chuàng)建的時候是一樣的,可以證明kv-store確實存在了這個值镐侯,這里的key可以是其他值侦讨,不一定是整數(shù),我試過string是ok的
Push, Aggregate, and Update 推苟翻,聚合韵卤,更新
對于任何已經(jīng)初始化了的key,可以用一個形狀一樣的數(shù)據(jù)更新這個key:
kv.push(3, mx.nd.ones(shape)*8)
kv.pull(3, out = a) # pull out the value
b = mx.nd.ones(shape)
kv.pull(3, out = b)
print b
print a
[[ 8. 8. 8.]
[ 8. 8. 8.]]
<NDArray 2x3 @cpu(0)>
[[ 8. 8. 8.]
[ 8. 8. 8.]]
<NDArray 2x3 @cpu(0)>
這里2x3的8覆蓋掉原來的2崇猫,此外沈条,這里pull操作需要傳遞一個接收值(引用)的對象給它,這里我想把pull 的值接收到另外一個地方诅炉,傳遞了一個同樣形狀的b給它蜡歹,就能得到數(shù)據(jù)
數(shù)據(jù)可以從任何設備push上來,還可以對某一個key推送多個值涕烧,KVStore會把他們求和然后push聚合后對值月而,多個值用一個list表示,一般的使用是從多個gpu推送各自計算的梯度议纯,我沒有g(shù)pu父款,用多個數(shù)據(jù)替代:
#gpus = [mx.gpu(i) for i in range(4)]
#b = [mx.nd.ones(shape, gpu) for gpu in gpus]
b = [mx.nd.ones(shape), mx.nd.ones(shape), mx.nd.ones(shape)]
kv.push(3, b)
kv.pull(3, a)
print a
[[ 3. 3. 3.]
[ 3. 3. 3.]]
<NDArray 2x3 @cpu(0)>
每一次調(diào)用push, kvstore對像調(diào)用update
方法更新參數(shù),更新的方式可以自定義瞻凤,傳入更新的方式憨攒,先看效果:
def update(key, input, stored):
print "update on key: %d" % key
stored += input * 2
kv._set_updater(update)
kv.pull(3, out=a)
print a.asnumpy()
kv.push(3, mx.nd.ones(shape))
kv.pull(3, out=a)
print a.asnumpy()
[[ 3. 3. 3.]
[ 3. 3. 3.]]
update on key: 3
[[ 5. 5. 5.]
[ 5. 5. 5.]]
上面對一個stored的2x3的,全部是3的矩陣更新阀参,傳入的值是相同形狀的1浓恶,更新函數(shù)將傳入值乘以2再更新
在kvstore.py
文件中,找到了_set_updata__
方法结笨,本想看看默認的_updata
方法了包晰,目測進入C++方法湿镀,已經(jīng)不知道怎么看下去,大牛知道在哪個文件的哪個位置實現(xiàn)了這個默認的更新梯度的方法請和戳我
pull
上面已經(jīng)展示了推送多個值到一個key(從多個設備推送各自的值到一個key)伐憾,對于pull也一樣勉痴,同樣可以從一個值拉下來到不同的設備上
b = [mx.nd.ones(shape, gpu) for gpu in gpus]
kv.pull(3, out = b)
print b[1].asnumpy()
[[ 6. 6. 6.]
[ 6. 6. 6.]]
這個是官方的例子,執(zhí)行pull树肃,一個數(shù)據(jù)蒸矛,并將這個數(shù)據(jù)復制成多份,一次發(fā)送到每一個gpu上
處理鍵值對列表
以上介紹的都是針對單個key, kvstore也支持鍵值對列表的接口
針對單機:
keys = [5, 7, 9]
kv.init(keys, [mx.nd.ones(shape)]*len(keys))
kv.push(keys, [mx.nd.ones(shape)]*len(keys))
b = [mx.nd.zeros(shape)]*len(keys)
kv.pull(keys, out = b)
print b[1].asnumpy()
創(chuàng)建一個三個元素的list胸嘴,初始化到5雏掠,7,9三個key上劣像,創(chuàng)建一個相同形狀的數(shù)據(jù)list乡话,定義一個相同形狀的數(shù)據(jù)b用于得到輸出,輸出是一個list
針對多個設備:
b = [[mx.nd.ones(shape, gpu) for gpu in gpus]] * len(keys)
kv.push(keys, b)
update on key: 5
update on key: 7
update on key: 9
kv.pull(keys, out = b)
print b[1][1].asnumpy()
[[ 11. 11. 11.]
[ 11. 11. 11.]]
上面已經(jīng)介紹過聚合耳奕,如果一個ke對應的value是一個list绑青,系統(tǒng)會將他們先聚合,然后更新key所對應的value屋群,針對多個key的情況也一樣闸婴,更新的數(shù)據(jù)維度多一個
這些都是針對單機的,多機的文檔沒有介紹芍躏,我也想搞搞明白邪乍,哪位大神有知道好資料的請戳我