摘要:到目前為止我們依然遺留了一個對在單機(jī)上使用深度學(xué)習(xí)框架來說最重要的問題如何利用,也包括利用多個進(jìn)行訓(xùn)練。中使用對輸入數(shù)據(jù)進(jìn)行切分,使用合并多個卡上的計算結(jié)果。總結(jié)如何利用多個卡進(jìn)行訓(xùn)練對復(fù)雜模型或是大規(guī)模數(shù)據(jù)集上的訓(xùn)練任務(wù)往往是必然的選擇。
到目前為止我們依然遺留了一個對在單機(jī)上使用深度學(xué)習(xí)框架來說最重要 的問題:如何利用 GPU, 也包括利用多個 GPU 進(jìn)行訓(xùn)練。深度學(xué)習(xí)模型的訓(xùn)練往往非常耗時,在較大數(shù)據(jù)集上訓(xùn)練或是訓(xùn)練復(fù)雜模型往往會借助于 GPU 強(qiáng)大的并行計算能力。 如何能夠讓模型運行在單個/多個 GPU 上,充分利用多個 GPU 卡的計算能力,且無需關(guān)注框架在多設(shè)備、多卡通信實現(xiàn)上的細(xì)節(jié)是這一篇要解決的問題。?
這一篇我們以 RNN 語言模型為例。RNN 語言模型在 第三篇已經(jīng)介紹過,這一篇我們維持原有的模型結(jié)構(gòu)不變,在以下兩處對第三節(jié)原有的例子進(jìn)行改建:?
1. 為 PaddleFluid 和 TensorFlow 模型添加上多 GPU 卡運行的支持。?
2. 使用 TensorFlow 的 dataset API 為 TensorFlow 的 RNN 語言模型重寫數(shù)據(jù)讀取 部分,以提高 I/O 效率。?
請注意,這一篇我們主要關(guān)于 如何利用多 GPU 卡進(jìn)行訓(xùn)練,請盡量在有多 塊 GPU 卡的機(jī)器上運行本節(jié)示例。
如何使用代碼
本篇文章配套有完整可運行的代碼, 請隨時從 github [1] 上獲取代碼。代碼包括以下幾個文件:
在執(zhí)行訓(xùn)練任務(wù)前,請首先進(jìn)入 data 文件夾,在終端執(zhí)行下面的命令進(jìn)行訓(xùn)練數(shù)據(jù)下載以及預(yù)處理。
sh download.sh
在終端運行以下命令便可以使用默認(rèn)結(jié)構(gòu)和默認(rèn)參數(shù)運行 PaddleFluid 訓(xùn)練序列標(biāo)注模型。
python train_fluid_model.py
在終端運行以下命令便可以使用默認(rèn)結(jié)構(gòu)和默認(rèn)參數(shù)運行 TensorFlow 訓(xùn)練序列標(biāo)注模型。
python train_tf_model.py
數(shù)據(jù)并行與模型并行
這一篇我們僅考慮單機(jī)多設(shè)備情況,暫不考慮網(wǎng)絡(luò)中的不同計算機(jī)。當(dāng)我們單機(jī)上有多種計算設(shè)備(包括 CPU,多塊不同的 GPU 卡),我們希望能夠充分利用這些設(shè)備一起完成訓(xùn)練任務(wù),常用的并行方式分為三種: ?
模型并行( model parallelism ):不同設(shè)備(GPU/CPU 等)負(fù)責(zé)網(wǎng)絡(luò)模型的不同部分 例如,神經(jīng)網(wǎng)絡(luò)模型的不同網(wǎng)絡(luò)層被分配到不同的設(shè)備,或者同一層內(nèi)部的不同參數(shù)被分配到不同設(shè)備。?
每個設(shè)備都只有一部分 模型;不同設(shè)備之間會產(chǎn)生通信開銷 ;
?
神經(jīng)網(wǎng)絡(luò)的計算本身有一定的計算依賴,如果計算本身存在依賴無法并行進(jìn)行,不同設(shè)備之間可能會產(chǎn)生等待。
數(shù)據(jù)并行( data parallelism ):不同的設(shè)備有同一個模型的多個副本,每個設(shè)備分配到不同的數(shù)據(jù),然后將所有機(jī)器的計算結(jié)果按照某種方式合并。?
每個計算設(shè)備都有一份完整的模型各自計算,指定某個設(shè)備作為 controller,將多個設(shè)備的計算結(jié)果進(jìn)行合并;
?
在神經(jīng)網(wǎng)絡(luò)中,通常需要合并的是多個設(shè)備計算的梯度,梯度合并后再進(jìn)行 clipping,計算正則,計算更新量,更新參數(shù)等步驟;
?
較大化計算效率的關(guān)鍵是盡可能降低串行避免計算設(shè)備的等待。?
混合并行(Hybrid parallelism):既有模型并行,又有數(shù)據(jù)并行。?
模型并行往往使用在模型大到單個計算設(shè)備已經(jīng)無法存儲整個模型(包括模型本身和計算過程中產(chǎn)生的中間結(jié)果)的場景,或是模型在計算上天然就存在多個 沒有強(qiáng)計算依賴的部分,那么很自然的可以將這些沒有計算依賴的部分放在不同設(shè)備上并行地進(jìn)行計算。
然而,隨著計算設(shè)備的不斷增多,模型并行較難以一種通用的可擴(kuò)展的方法達(dá)到接近線性加速的效果。一方面如何重疊(overlap)計算開銷與跨設(shè)備通信開銷依賴于對系統(tǒng)硬件豐富的知識和經(jīng)驗,另一方面神經(jīng)網(wǎng)絡(luò)計算的依賴性 會讓模型的拆分隨著設(shè)備的增加越發(fā)困難。
數(shù)據(jù)并行中每一個設(shè)備都維護(hù)了完整的模型,與模型并行相比往往會耗費更多的存儲空間。但數(shù)據(jù)并行的優(yōu)點是:通用性很好,適用于所有可能的神經(jīng)網(wǎng)絡(luò)模型結(jié)構(gòu)。同樣地,隨著設(shè)備數(shù)目的增加通信代價也會越來越高,一般情況下在 2~8 卡時依然可以做到接近線性加速比。
需要注意的是,隨著越來越多設(shè)備的加入,數(shù)據(jù)并行會導(dǎo)致 batch size 增大,一個 epoch 內(nèi)參數(shù)更新次數(shù)減少,往往都需要對學(xué)習(xí)率,學(xué)習(xí)率 decay 進(jìn)行再調(diào)參,否則可能會引起學(xué)習(xí)效果的下降。?
鑒于在使用中的通用性和有效性,這一篇中我們主要介紹更加通用的數(shù)據(jù)并行方法。非?;\統(tǒng)的,數(shù)據(jù)并行遵從一下的流程,其中一個 | 代表一個計算設(shè)備:
| ? ? ? ? ? 1. 將模型參數(shù)拷貝到不同的設(shè)備
| ? ? ? ? ? 2. 對輸入數(shù)據(jù)均勻切分到不同的計算設(shè)備
|||| ? ? ? ?3. 多個設(shè)備并行進(jìn)行前向計算
|||| ? ? ? ?4. 多個設(shè)備形象進(jìn)行反向計算
| ? ? ? ? ? 5. 多個設(shè)備計算的梯度在主卡合并
| ? ? ? ? ? 6. 計算參數(shù)更新量,更新參數(shù)
| ? ? ? ? ? to 1
PaddleFluid使用多GPU卡進(jìn)行訓(xùn)練
在 PaddleFluid 中使用多個 GPU 卡以數(shù)據(jù)并行的方式訓(xùn)練需要引入 parallel_do 原語。顧名思義, parallel_do 會負(fù)責(zé)數(shù)據(jù)的切分,在多個設(shè)備上并行地執(zhí)行一段相同的計算,最后合并計算結(jié)果。
與 ParallelDo 函數(shù)功能相近的函數(shù)還有 ParallelExecutor,大家也可以自行嘗試一下。ParallelExecutor 的具體使用方式可以參考 API 文檔:
http://www.paddlepaddle.org/docs/develop/api/fluid/en/executor.html
圖 1 是 parallel_do 的原理示意圖:?
圖1. PaddleFluid中的Parallel do
下面我們來看看如何使用 parallel_do 讓我們在第三篇中實現(xiàn)的 RNN LM 可在多個 GPU 上訓(xùn)練 ,下面是核心代碼片段,完整代碼請參考 rnnlm_fluid.py。
places = fluid.layers.get_places()
pd = fluid.layers.ParallelDo(places)
with pd.do():
? ? word_ = pd.read_input(word)
? ? lbl_ = pd.read_input(lbl)
? ? prediction, cost = self.__network(word_, lbl_)
? ? pd.write_output(cost)
? ? pd.write_output(prediction)
cost, prediction = pd()
avg_cost = fluid.layers.mean(x=cost)
調(diào)用 places = fluid.layers.get_places() 獲取所有可用的計算設(shè)備 ??梢酝ㄟ^設(shè)置 CUDA_VISIBLE_DEVICES 來控制可見 GPU 的數(shù)據(jù)。?
?pd = fluid.layers.ParallelDo(places) 指定將在 那些設(shè)備上并行地執(zhí)行。?
?parallel_do 會構(gòu)建一段 context,在其中定義要并行執(zhí)行的計算,調(diào)用 pd.read_input 切分輸入數(shù)據(jù),在 parallel_do 的 context 之外調(diào)用 pd() 獲取合并后的最終計算結(jié)果。
with pd.do():
? x_ = pd.read_input(x) ?# 切分輸入數(shù)據(jù) x
? y_ = pd.read_input(y) ?# 切分輸入數(shù)據(jù) y
? # 定義網(wǎng)絡(luò)
? cost = network(x_, y_)
? pd.write_output(cost)
cost = pd() ?# 獲取合并后的計算結(jié)果
TensorFlow中使用多GPU卡進(jìn)行訓(xùn)練
在 TensorFlow 中,通過調(diào)用 with tf.device() 創(chuàng)建一段 device context,在這段 context 中定義所需的計算,那么這 些計算將運行在指定的設(shè)備上。?
TensorFlow 中實現(xiàn)多卡數(shù)據(jù)并行有多種方法,常用的包括單機(jī) ParameterServer 模式;Tower 模式 [2],甚至也 可以使用的 nccl [3] all reduce 系列 op 來實現(xiàn)梯度的聚合。這里我們以 Tower 模式為基礎(chǔ),介紹一種簡單易用的多 GPU 上的數(shù)據(jù)并行方式。下面是核心代碼片段,完整代碼請參考 rnnlm_tensorflow.py。
def make_parallel(fn, num_gpus, **kwargs):
? ? ? ? in_splits = {}
? ? ? ? for k, v in kwargs.items():
? ? ? ? ? ? in_splits[k] = tf.split(v, num_gpus)
? ? ? ? out_split = []
? ? ? ? for i in range(num_gpus):
? ? ? ? ? ? with tf.device(tf.DeviceSpec(device_type="GPU", device_index=i)):
? ? ? ? ? ? ? ? with tf.variable_scope(
? ? ? ? ? ? ? ? ? ? ? ? tf.get_variable_scope(), reuse=tf.AUTO_REUSE):
? ? ? ? ? ? ? ? ? ? out_i = fn(**{k: v[i] for k, v in in_splits.items()})
? ? ? ? ? ? ? ? ? ? out_split.append(out_i)
? ? ? ? return tf.reduce_sum(tf.add_n(out_split)) / tf.to_float(
? ? ? ? ? ? self.batch_size)
?make_parallel 的第一個參數(shù)是一個函數(shù),也就是我們自己定義的如何創(chuàng)建神經(jīng)網(wǎng)絡(luò)模型函數(shù)。第二個參數(shù)指定 GPU 卡數(shù),數(shù)據(jù)將被平均地分配給這些 GPU。除此之外的參數(shù)將以 keyword argument 的形式傳入,是神經(jīng)網(wǎng)絡(luò)的輸入層 Tensor 。?
在定義神經(jīng)網(wǎng)絡(luò)模型時,需要創(chuàng)建 varaiable_scope ,同時指定 reuse=tf.AUTO_REUSE ,保證多個 GPU 卡上的可學(xué)習(xí)參數(shù)會是共享的。?
?make_parallel 中使用 tf.split op 對輸入數(shù)據(jù) Tensor 進(jìn)行切分,使用 tf.add_n 合并多個 GPU 卡上的計算結(jié)果。
一些情況下同樣可以使用 tf.concat 來合并多個卡的結(jié)算結(jié)果,這里因為使用了 dataset api 為 dynamic rnn feed 數(shù)據(jù),在定義計算圖時 batch_size 和 max_sequence_length 均不確定,無法使用 tf.concat 。?
下面是對 make_parallel 的調(diào)用,從中可看到如何使用 make_parallel 方法。
self.cost = self.make_parallel(
? ? ? ? ? ? self.build_model,
? ? ? ? ? ? len(get_available_gpus()),
? ? ? ? ? ? curwd=curwd,
? ? ? ? ? ? nxtwd=nxtwd,
? ? ? ? ? ? seq_len=seq_len)
除了調(diào)用 make_parallel 之外,還有一處修改需要注意:在定義優(yōu)化方法時,需要將 colocate_gradients_with_ops 設(shè)置為 True,保證前向 Op 和反向 Op 被放置在相同的設(shè)備上進(jìn)行計算。
optimizer.minimize(self.cost, colocate_gradients_with_ops=True)
總結(jié)
如何利用多個 GPU 卡進(jìn)行訓(xùn)練對復(fù)雜模型或是大規(guī)模數(shù)據(jù)集上的訓(xùn)練任務(wù)往往是必然的選擇。鑒于在使用中的有效性和通用性,這一節(jié)我們主要介紹了在 PaddleFluid 和 TensorFlow 上通過數(shù)據(jù)并行使用多個 GPU 卡最簡單的方法。
這一篇所有可運行的例子都可以在 04_rnnlm_data_parallelism [4] 找到,更多實現(xiàn)細(xì)節(jié)請參考具體的代碼。值得注意的是,不論是 PaddleFluid 還是 TensorFlow 都還有其他多種利用多計算設(shè)備提高訓(xùn)練并行度的方法。請大家隨時關(guān)注官方的文檔。
參考文獻(xiàn)
[1]. 本文配套代碼
https://github.com/JohnRabbbit/TF2Fluid/tree/master/04_rnnlm_data_parallelism
[2]. Tower模式?
https://github.com/tensorflow/models/blob/master/tutorials/image/cifar10/cifar10_multi_gpu_train.py
[3]. nccl
https://www.tensorflow.org/api_docs/python/tf/contrib/nccl
[4]. 04_rnnlm_data_parallelism
https://github.com/JohnRabbbit/TF2Fluid/tree/master/04_rnnlm_data_parallelism
聲明:文章收集于網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系小編及時處理,謝謝!
歡迎加入本站公開興趣群商業(yè)智能與數(shù)據(jù)分析群
興趣范圍包括各種讓數(shù)據(jù)產(chǎn)生價值的辦法,實際應(yīng)用案例分享與討論,分析工具,ETL工具,數(shù)據(jù)倉庫,數(shù)據(jù)挖掘工具,報表系統(tǒng)等全方位知識
QQ群:81035754
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/4785.html
閱讀 2580·2023-04-25 17:33
閱讀 659·2021-11-23 09:51
閱讀 2966·2021-07-30 15:32
閱讀 1413·2019-08-29 18:40
閱讀 1957·2019-08-28 18:19
閱讀 1476·2019-08-26 13:48
閱讀 2253·2019-08-23 16:48
閱讀 2285·2019-08-23 15:56