(第一部分 機(jī)器學(xué)習(xí)基礎(chǔ))
第01章 機(jī)器學(xué)習(xí)概覽
第02章 一個(gè)完整的機(jī)器學(xué)習(xí)項(xiàng)目(上)
第02章 一個(gè)完整的機(jī)器學(xué)習(xí)項(xiàng)目(下)
第03章 分類
第04章 訓(xùn)練模型
第05章 支持向量機(jī)
第06章 決策樹
第07章 集成學(xué)習(xí)和隨機(jī)森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí))
第9章 啟動(dòng)和運(yùn)行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強(qiáng)化學(xué)習(xí)(上)
第16章 強(qiáng)化學(xué)習(xí)(下)
在第 11 章,我們討論了幾種可以明顯加速訓(xùn)練的技術(shù):更好的權(quán)重初始化钟些,批量標(biāo)準(zhǔn)化,復(fù)雜的優(yōu)化器等等眉反。 但是貌嫡,即使采用了所有這些技術(shù)脆侮,在具有單個(gè) CPU 的單臺(tái)機(jī)器上訓(xùn)練大型神經(jīng)網(wǎng)絡(luò)可能需要幾天甚至幾周的時(shí)間藏畅。
在本章中瓮增,我們將看到如何使用 TensorFlow 在多個(gè)設(shè)備(CPU 和 GPU)上分配計(jì)算并將它們并行運(yùn)行(參見圖 12-1)别厘。 首先虱饿,我們會(huì)先在一臺(tái)機(jī)器上的多個(gè)設(shè)備上分配計(jì)算,然后在多臺(tái)機(jī)器上的多個(gè)設(shè)備上分配計(jì)算触趴。
與其他神經(jīng)網(wǎng)絡(luò)框架相比氮发,TensorFlow 對(duì)分布式計(jì)算的支持是其主要亮點(diǎn)之一。 它使您可以完全控制如何跨設(shè)備和服務(wù)器分布(或復(fù)制)您的計(jì)算圖冗懦,并且可以讓您以靈活的方式并行和同步操作爽冕,以便您可以在各種并行方法之間進(jìn)行選擇。
我們來看一些最流行的方法來并行執(zhí)行和訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)披蕉,這讓我們不再需要等待數(shù)周才能完成訓(xùn)練算法颈畸,而最終可能只會(huì)等待幾個(gè)小時(shí)。 這不僅可以節(jié)省大量時(shí)間没讲,還意味著您可以更輕松地嘗試各種模型眯娱,并經(jīng)常重新訓(xùn)練模型上的新數(shù)據(jù)。
還有其他很好的并行化例子爬凑,包括當(dāng)我們?cè)谖⒄{(diào)模型時(shí)可以探索更大的超參數(shù)空間徙缴,并有效地運(yùn)行大規(guī)模神經(jīng)網(wǎng)絡(luò)。
但我們必須先學(xué)會(huì)走路才能跑步嘁信。 我們先從一臺(tái)機(jī)器上的幾個(gè) GPU 上并行化簡(jiǎn)單圖形開始于样。
一臺(tái)機(jī)器上多設(shè)備
只需添加 GPU 顯卡到單個(gè)機(jī)器,您就可以獲得主要的性能提升潘靖。 事實(shí)上百宇,在很多情況下,這就足夠了秘豹。 你根本不需要使用多臺(tái)機(jī)器携御。 例如,通常在單臺(tái)機(jī)器上使用 8 個(gè) GPU既绕,而不是在多臺(tái)機(jī)器上使用 16 個(gè) GPU(由于多機(jī)器設(shè)置中的網(wǎng)絡(luò)通信帶來的額外延遲)啄刹,可以同樣快地訓(xùn)練神經(jīng)網(wǎng)絡(luò)。
在本節(jié)中凄贩,我們將介紹如何設(shè)置您的環(huán)境誓军,以便 TensorFlow 可以在一臺(tái)機(jī)器上使用多個(gè) GPU 卡。 然后疲扎,我們將看看如何在可用設(shè)備上進(jìn)行分布操作昵时,并且并行執(zhí)行它們捷雕。
安裝
為了在多個(gè) GPU 卡上運(yùn)行 TensorFlow,首先需要確保 GPU 卡具有 NVidia 計(jì)算能力(大于或等于3.0)壹甥。 這包括 Nvidia 的 Titan救巷,Titan X,K20 和 K40(如果你擁有另一張卡句柠,你可以在 https://developer.nvidia.com/cuda-gpus 查看它的兼容性)浦译。
提示:
如果您不擁有任何 GPU 卡,則可以使用具有 GPU 功能的主機(jī)服務(wù)器溯职,如 Amazon AWS精盅。 在 ?igaAvsec 的博客文章中,提供了在 Amazon AWS GPU 實(shí)例上使用 Python 3.5 設(shè)置 TensorFlow 0.9 的詳細(xì)說明谜酒。將它更新到最新版本的 TensorFlow 應(yīng)該不會(huì)太難叹俏。 Google 還發(fā)布了一項(xiàng)名為 Cloud Machine Learning 的云服務(wù)來運(yùn)行 TensorFlow 圖表。 2016 年 5 月僻族,他們宣布他們的平臺(tái)現(xiàn)在包括配備張量處理器(TPU)的服務(wù)器她肯,專門用于機(jī)器學(xué)習(xí)的處理器,比許多 GPU 處理 ML 任務(wù)要快得多鹰贵。 當(dāng)然晴氨,另一種選擇只是購買你自己的 GPU 卡。 Tim Dettmers 寫了一篇很棒的博客文章(http://timdettmers.com/2018/11/05/which-gpu-for-deep-learning/)來幫助你選擇碉输,他會(huì)定期更新它籽前。
您必須下載并安裝相應(yīng)版本的 CUDA 和 cuDNN 庫(如果您使用的是 TensorFlow 1.0.0,則為 CUDA 8.0 和 cuDNN 5.1)敷钾,并設(shè)置一些環(huán)境變量枝哄,以便 TensorFlow 知道在哪里可以找到 CUDA 和 cuDNN。 詳細(xì)的安裝說明可能會(huì)相當(dāng)迅速地更改阻荒,因此最好按照 TensorFlow 網(wǎng)站上的說明進(jìn)行操作挠锥。
Nvidia 的 CUDA 允許開發(fā)者使用支持 CUDA 的 GPU 進(jìn)行各種計(jì)算(不僅僅是圖形加速)。 Nvidia 的 CUDA 深度神經(jīng)網(wǎng)絡(luò)庫(cuDNN)是針對(duì) DNN 的 GPU 加速原語庫侨赡。 它提供了常用 DNN 計(jì)算的優(yōu)化實(shí)現(xiàn)蓖租,例如激活層,歸一化羊壹,前向和后向卷積以及池化(參見第 13 章)蓖宦。 它是 Nvidia Deep Learning SDK 的一部分(請(qǐng)注意,它需要?jiǎng)?chuàng)建一個(gè) Nvidia 開發(fā)者帳戶才能下載它)油猫。 TensorFlow 使用 CUDA 和 cuDNN 來控制 GPU 卡并加速計(jì)算(見圖 12-2)稠茂。
您可以使用nvidia-smi
命令來檢查 CUDA 是否已正確安裝情妖。 它列出了可用的 GPU 卡以及每張卡上運(yùn)行的進(jìn)程:
$ nvidia-smi
Wed Sep 16 09:50:03 2016
+------------------------------------------------------+
| NVIDIA-SMI 352.63 Driver Version: 352.63 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GRID K520 Off | 0000:00:03.0 Off | N/A |
| N/A 27C P8 17W / 125W | 11MiB / 4095MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
最后睬关,您必須安裝支持 GPU 的 TensorFlow诱担。 如果你使用virtualenv
創(chuàng)建了一個(gè)獨(dú)立的環(huán)境,你首先需要激活它:
$ cd $ML_PATH # Your ML working directory (e.g., $HOME/ml)
$ source env/bin/activate
然后安裝合適的支持 GPU 的 TensorFlow 版本:
$ pip3 install --upgrade tensorflow-gpu
現(xiàn)在您可以打開一個(gè) Python shell 并通過導(dǎo)入 TensorFlow 并創(chuàng)建一個(gè)會(huì)話來檢查 TensorFlow 是否正確檢測(cè)并使用 CUDA 和 cuDNN:
>>> import tensorflow as tf
I [...]/dso_loader.cc:108] successfully opened CUDA library libcublas.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcudnn.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcufft.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcuda.so.1 locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcurand.so locally
>>> sess = tf.Session()
[...]
I [...]/gpu_init.cc:102] Found device 0 with properties:
name: GRID K520
major: 3 minor: 0 memoryClockRate (GHz) 0.797
pciBusID 0000:00:03.0
Total memory: 4.00GiB
Free memory: 3.95GiB
I [...]/gpu_init.cc:126] DMA: 0
I [...]/gpu_init.cc:136] 0: Y
I [...]/gpu_device.cc:839] Creating TensorFlow device
(/gpu:0) -> (device: 0, name: GRID K520, pci bus id: 0000:00:03.0)
看起來不錯(cuò)电爹!TensorFlow 檢測(cè)到 CUDA 和 cuDNN 庫蔫仙,并使用 CUDA 庫來檢測(cè) GPU 卡(在這種情況下是 Nvidia Grid K520 卡)。
管理 GPU 內(nèi)存
默認(rèn)情況下藐不,TensorFlow 會(huì)在您第一次運(yùn)行圖形時(shí)自動(dòng)獲取所有可用 GPU 中的所有 RAM匀哄,因此當(dāng)?shù)谝粋€(gè)程序仍在運(yùn)行時(shí)秦效,您將無法啟動(dòng)第二個(gè) TensorFlow 程序雏蛮。 如果你嘗試,你會(huì)得到以下錯(cuò)誤:
E [...]/cuda_driver.cc:965] failed to allocate 3.66G (3928915968 bytes) from device: CUDA_ERROR_OUT_OF_MEMORY
一種解決方案是在不同的 GPU 卡上運(yùn)行每個(gè)進(jìn)程阱州。 為此挑秉,最簡(jiǎn)單的選擇是設(shè)置CUDA_VISIBLE_DEVICES
環(huán)境變量,以便每個(gè)進(jìn)程只能看到對(duì)應(yīng)的 GPU 卡苔货。 例如犀概,你可以像這樣啟動(dòng)兩個(gè)程序:
$ CUDA_VISIBLE_DEVICES=0,1 python3 program_1.py
# and in another terminal:
$ CUDA_VISIBLE_DEVICES=3,2 python3 program_2.py
程序 #1 只會(huì)看到 GPU 卡 0 和 1(分別編號(hào)為 0 和 1),程序 #2 只會(huì)看到 GPU 卡 2 和 3(分別編號(hào)為 1 和 0)夜惭。 一切正常工作(見圖 12-3)姻灶。
另一種選擇是告訴 TensorFlow 只抓取一小部分內(nèi)存。 例如诈茧,要使 TensorFlow 只占用每個(gè) GPU 內(nèi)存的 40%产喉,您必須創(chuàng)建一個(gè)ConfigProto
對(duì)象,將其gpu_options.per_process_gpu_memory_fraction
選項(xiàng)設(shè)置為 0.4敢会,并使用以下配置創(chuàng)建session
:
config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.4
session = tf.Session(config=config)
現(xiàn)在像這樣的兩個(gè)程序可以使用相同的 GPU 卡并行運(yùn)行(但不是三個(gè)曾沈,因?yàn)?code>3×0.4> 1)。 見圖 12-4鸥昏。
如果在兩個(gè)程序都運(yùn)行時(shí)運(yùn)行nvidia-smi
命令,則應(yīng)該看到每個(gè)進(jìn)程占用每個(gè)卡的總 RAM 大約 40%:
$ nvidia-smi
[...]
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| 0 5231 C python 1677MiB |
| 0 5262 C python 1677MiB |
| 1 5231 C python 1677MiB |
| 1 5262 C python 1677MiB |
[...]
另一種選擇是告訴 TensorFlow 只在需要時(shí)才抓取內(nèi)存吏垮。 為此障涯,您必須將config.gpu_options.allow_growth
設(shè)置為True
。但是膳汪,TensorFlow 一旦抓取內(nèi)存就不會(huì)釋放內(nèi)存(以避免內(nèi)存碎片)像樊,因此您可能會(huì)在一段時(shí)間后內(nèi)存不足。 是否使用此選項(xiàng)可能難以確定旅敷,因此一般而言生棍,您可能想要堅(jiān)持之前的某個(gè)選項(xiàng)。
好的媳谁,現(xiàn)在你已經(jīng)有了一個(gè)支持 GPU 的 TensorFlow 安裝涂滴。 讓我們看看如何使用它友酱!
設(shè)備布置操作
TensorFlow 白皮書介紹了一種友好的動(dòng)態(tài)布置器算法,該算法能夠自動(dòng)將操作分布到所有可用設(shè)備上柔纵,并考慮到以前運(yùn)行圖中所測(cè)量的計(jì)算時(shí)間缔杉,估算每次操作的輸入和輸出張量的大小, 每個(gè)設(shè)備可用的 RAM搁料,傳輸數(shù)據(jù)進(jìn)出設(shè)備時(shí)的通信延遲或详,來自用戶的提示和約束等等。 不幸的是郭计,這種復(fù)雜的算法是谷歌內(nèi)部的霸琴,它并沒有在 TensorFlow 的開源版本中發(fā)布。它被排除在外的原因似乎是昭伸,由用戶指定的一小部分放置規(guī)則實(shí)際上比動(dòng)態(tài)放置器放置的更有效梧乘。 然而,TensorFlow 團(tuán)隊(duì)正在努力改進(jìn)它庐杨,并且最終可能會(huì)被開放选调。
在此之前,TensorFlow都是簡(jiǎn)單的放置灵份,它(如其名稱所示)非橙士埃基本。
簡(jiǎn)單放置
無論何時(shí)運(yùn)行圖形填渠,如果 TensorFlow 需要求值尚未放置在設(shè)備上的節(jié)點(diǎn)弦聂,則它會(huì)使用簡(jiǎn)單放置器將其放置在未放置的所有其他節(jié)點(diǎn)上。 簡(jiǎn)單放置尊重以下規(guī)則:
- 如果某個(gè)節(jié)點(diǎn)已經(jīng)放置在圖形的上一次運(yùn)行中的某個(gè)設(shè)備上揭蜒,則該節(jié)點(diǎn)將保留在該設(shè)備上横浑。
- 否則,如果用戶將一個(gè)節(jié)點(diǎn)固定到設(shè)備上(下面介紹)屉更,則放置器將其放置在該設(shè)備上徙融。
- 否則,它默認(rèn)為 GPU#0瑰谜,如果沒有 GPU欺冀,則默認(rèn)為 CPU。
正如您所看到的萨脑,將操作放在適當(dāng)?shù)脑O(shè)備上主要取決于您隐轩。 如果您不做任何事情,整個(gè)圖表將被放置在默認(rèn)設(shè)備上渤早。 要將節(jié)點(diǎn)固定到設(shè)備上职车,您必須使用device()
函數(shù)創(chuàng)建一個(gè)設(shè)備塊。 例如,以下代碼將變量a
和常量b
固定在 CPU 上悴灵,但乘法節(jié)點(diǎn)c
不固定在任何設(shè)備上扛芽,因此將放置在默認(rèn)設(shè)備上:
with tf.device("/cpu:0"):
a = tf.Variable(3.0)
b = tf.constant(4.0)
c = a * b
其中,"/cpu:0"
設(shè)備合計(jì)多 CPU 系統(tǒng)上的所有 CPU积瞒。 目前沒有辦法在特定 CPU 上固定節(jié)點(diǎn)或僅使用所有 CPU 的子集川尖。
記錄放置位置
讓我們檢查一下簡(jiǎn)單的放置器是否遵守我們剛剛定義的布局約束條件。 為此茫孔,您可以將log_device_placement
選項(xiàng)設(shè)置為True
叮喳;這告訴放置器在放置節(jié)點(diǎn)時(shí)記錄消息。例如:
>>> config = tf.ConfigProto()
>>> config.log_device_placement = True
>>> sess = tf.Session(config=config)
I [...] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GRID K520, pci bus id: 0000:00:03.0)
[...]
>>> x.initializer.run(session=sess)
I [...] a: /job:localhost/replica:0/task:0/cpu:0
I [...] a/read: /job:localhost/replica:0/task:0/cpu:0
I [...] mul: /job:localhost/replica:0/task:0/gpu:0
I [...] a/Assign: /job:localhost/replica:0/task:0/cpu:0
I [...] b: /job:localhost/replica:0/task:0/cpu:0
I [...] a/initial_value: /job:localhost/replica:0/task:0/cpu:0
>>> sess.run(c)
12
Info
中以大寫字母I
開頭的行是日志消息缰贝。 當(dāng)我們創(chuàng)建一個(gè)會(huì)話時(shí)馍悟,TensorFlow 會(huì)記錄一條消息,告訴我們它已經(jīng)找到了一個(gè) GPU 卡(在這個(gè)例子中是 Grid K520 卡)揩瞪。 然后赋朦,我們第一次運(yùn)行圖形(在這種情況下篓冲,當(dāng)初始化變量a
時(shí))李破,簡(jiǎn)單布局器運(yùn)行,并將每個(gè)節(jié)點(diǎn)放置在分配給它的設(shè)備上壹将。正如預(yù)期的那樣嗤攻,日志消息顯示所有節(jié)點(diǎn)都放在"/cpu:0"
上,除了乘法節(jié)點(diǎn)诽俯,它以默認(rèn)設(shè)備"/gpu:0"
結(jié)束(您可以先忽略前綴:/job:localhost/replica:0/task:0;
我們將在一會(huì)兒討論它)妇菱。 注意,我們第二次運(yùn)行圖(計(jì)算c
)時(shí)暴区,由于 TensorFlow 需要計(jì)算的所有節(jié)點(diǎn)c
都已經(jīng)放置闯团,所以不使用布局器。
動(dòng)態(tài)放置功能
創(chuàng)建設(shè)備塊時(shí)仙粱,可以指定一個(gè)函數(shù)房交,而不是設(shè)備名稱。TensorFlow 會(huì)調(diào)用這個(gè)函數(shù)來進(jìn)行每個(gè)需要放置在設(shè)備塊中的操作伐割,并且該函數(shù)必須返回設(shè)備的名稱來固定操作候味。 例如,以下代碼將固定所有變量節(jié)點(diǎn)到"/cpu:0"
(在本例中只是變量a
)和所有其他節(jié)點(diǎn)到"/gpu:0"
:
def variables_on_cpu(op):
if op.type == "Variable":
return "/cpu:0"
else:
return "/gpu:0"
with tf.device(variables_on_cpu):
a = tf.Variable(3.0)
b = tf.constant(4.0)
c = a * b
您可以輕松實(shí)現(xiàn)更復(fù)雜的算法隔心,例如以循環(huán)方式用GPU鎖定變量白群。
操作和內(nèi)核
對(duì)于在設(shè)備上運(yùn)行的 TensorFlow 操作,它需要具有該設(shè)備的實(shí)現(xiàn)硬霍;這被稱為內(nèi)核帜慢。 許多操作對(duì)于 CPU 和 GPU 都有內(nèi)核,但并非全部都是。 例如粱玲,TensorFlow 沒有用于整數(shù)變量的 GPU 內(nèi)核侍咱,因此當(dāng) TensorFlow 嘗試將變量i放置到 GPU#0 時(shí),以下代碼將失斆茚!:
>>> with tf.device("/gpu:0"):
... i = tf.Variable(3)
[...]
>>> sess.run(i.initializer)
Traceback (most recent call last):
[...]
tensorflow.python.framework.errors.InvalidArgumentError: Cannot assign a device to node 'Variable': Could not satisfy explicit device specification
請(qǐng)注意楔脯,TensorFlow 推斷變量必須是int32
類型,因?yàn)槌跏蓟凳且粋€(gè)整數(shù)胯甩。 如果將初始化值更改為 3.0 而不是 3昧廷,或者如果在創(chuàng)建變量時(shí)顯式設(shè)置dtype = tf.float32
,則一切正常偎箫。
軟放置
默認(rèn)情況下木柬,如果您嘗試在操作沒有內(nèi)核的設(shè)備上固定操作,則當(dāng) TensorFlow 嘗試將操作放置在設(shè)備上時(shí)淹办,您會(huì)看到前面顯示的異常眉枕。 如果您更喜歡 TensorFlow 回退到 CPU,則可以將allow_soft_placement
配置選項(xiàng)設(shè)置為True
:
with tf.device("/gpu:0"):
i = tf.Variable(3)
config = tf.ConfigProto()
config.allow_soft_placement = True
sess = tf.Session(config=config)
sess.run(i.initializer) # the placer runs and falls back to /cpu:0
到目前為止怜森,我們已經(jīng)討論了如何在不同設(shè)備上放置節(jié)點(diǎn)速挑。 現(xiàn)在讓我們看看 TensorFlow 如何并行運(yùn)行這些節(jié)點(diǎn)。
并行運(yùn)行
當(dāng) TensorFlow 運(yùn)行圖時(shí)副硅,它首先找出需要求值的節(jié)點(diǎn)列表姥宝,然后計(jì)算每個(gè)節(jié)點(diǎn)有多少依賴關(guān)系。 然后 TensorFlow 開始求值具有零依賴關(guān)系的節(jié)點(diǎn)(即源節(jié)點(diǎn))恐疲。 如果這些節(jié)點(diǎn)被放置在不同的設(shè)備上腊满,它們顯然會(huì)被并行求值。 如果它們放在同一個(gè)設(shè)備上培己,它們將在不同的線程中進(jìn)行求值碳蛋,因此它們也可以并行運(yùn)行(在單獨(dú)的 GPU 線程或 CPU 內(nèi)核中)。
TensorFlow 管理每個(gè)設(shè)備上的線程池以并行化操作(參見圖 12-5)省咨。 這些被稱為 inter-op 線程池肃弟。 有些操作具有多線程內(nèi)核:它們可以使用其他線程池(每個(gè)設(shè)備一個(gè))稱為 intra-op 線程池(下面寫成內(nèi)部線程池)。
例如茸炒,在圖 12-5 中愕乎,操作A
,B
和C
是源操作壁公,因此可以立即進(jìn)行求值感论。 操作A
和B
放置在 GPU#0 上,因此它們被發(fā)送到該設(shè)備的內(nèi)部線程池紊册,并立即進(jìn)行并行求值比肄。 操作A正好有一個(gè)多線程內(nèi)核; 它的計(jì)算被分成三部分快耿,這些部分由內(nèi)部線程池并行執(zhí)行。 操作C
轉(zhuǎn)到 GPU#1 的內(nèi)部線程池芳绩。
一旦操作C
完成掀亥,操作D
和E
的依賴性計(jì)數(shù)器將遞減并且都將達(dá)到 0,因此這兩個(gè)操作將被發(fā)送到操作內(nèi)線程池以執(zhí)行妥色。
您可以通過設(shè)置inter_op_parallelism_threads
選項(xiàng)來控制內(nèi)部線程池的線程數(shù)搪花。 請(qǐng)注意,您開始的第一個(gè)會(huì)話將創(chuàng)建內(nèi)部線程池嘹害。 除非您將use_per_session_threads
選項(xiàng)設(shè)置為True
撮竿,否則所有其他會(huì)話都將重用它們。 您可以通過設(shè)置intra_op_parallelism_threads
選項(xiàng)來控制每個(gè)內(nèi)部線程池的線程數(shù)笔呀。
控制依賴關(guān)系
在某些情況下幢踏,即使所有依賴的操作都已執(zhí)行,推遲對(duì)操作的求值可能也是明智之舉许师。例如房蝉,如果它使用大量?jī)?nèi)存,但在圖形中只需要更多內(nèi)存微渠,則最好在最后一刻對(duì)其進(jìn)行求值搭幻,以避免不必要地占用其他操作可能需要的 RAM。 另一個(gè)例子是依賴位于設(shè)備外部的數(shù)據(jù)的一組操作敛助。 如果它們?nèi)客瑫r(shí)運(yùn)行粗卜,它們可能會(huì)使設(shè)備的通信帶寬達(dá)到飽和屋确,并最終導(dǎo)致所有等待 I/O纳击。 其他需要傳遞數(shù)據(jù)的操作也將被阻止。 順序執(zhí)行這些通信繁重的操作將是比較好的攻臀,這樣允許設(shè)備并行執(zhí)行其他操作焕数。
推遲對(duì)某些節(jié)點(diǎn)的求值,一個(gè)簡(jiǎn)單的解決方案是添加控制依賴關(guān)系刨啸。 例如堡赔,下面的代碼告訴 TensorFlow 僅在求值完a
和b
之后才求值x
和y
:
a = tf.constant(1.0)
b = a + 2.0
with tf.control_dependencies([a, b]):
x = tf.constant(3.0)
y = tf.constant(4.0)
z = x + y
顯然,由于z
依賴于x
和y
设联,所以求值z
也意味著等待a
和b
進(jìn)行求值善已,即使它并未顯式存在于control_dependencies()
塊中。 此外离例,由于b
依賴于a
换团,所以我們可以通過在[b]
而不是[a,b]
上創(chuàng)建控制依賴關(guān)系來簡(jiǎn)化前面的代碼,但在某些情況下宫蛆,“顯式比隱式更好”艘包。
很好!現(xiàn)在你知道了:
- 如何以任何您喜歡的方式在多個(gè)設(shè)備上進(jìn)行操作
- 這些操作如何并行執(zhí)行
- 如何創(chuàng)建控制依賴性來優(yōu)化并行執(zhí)行
是時(shí)候?qū)⒂?jì)算分布在多個(gè)服務(wù)器上了!
多個(gè)服務(wù)器的多個(gè)設(shè)備
要跨多臺(tái)服務(wù)器運(yùn)行圖形想虎,首先需要定義一個(gè)集群卦尊。 一個(gè)集群由一個(gè)或多個(gè) TensorFlow 服務(wù)器組成,稱為任務(wù)舌厨,通常分布在多臺(tái)機(jī)器上(見圖 12-6)岂却。 每項(xiàng)任務(wù)都屬于一項(xiàng)作業(yè)。 作業(yè)只是一組通常具有共同作用的任務(wù)裙椭,例如跟蹤模型參數(shù)(例如淌友,參數(shù)服務(wù)器通常命名為"ps"
,parameter server)或執(zhí)行計(jì)算(這樣的作業(yè)通常被命名為"worker"
)骇陈。
以下集群規(guī)范定義了兩個(gè)作業(yè)"ps"
和"worker"
震庭,分別包含一個(gè)任務(wù)和兩個(gè)任務(wù)。 在這個(gè)例子中你雌,機(jī)器A托管著兩個(gè) TensorFlow 服務(wù)器(即任務(wù))器联,監(jiān)聽不同的端口:一個(gè)是"ps"
作業(yè)的一部分,另一個(gè)是"worker"
作業(yè)的一部分婿崭。 機(jī)器B僅托管一臺(tái) TensorFlow 服務(wù)器拨拓,這是"worker"
作業(yè)的一部分。
cluster_spec = tf.train.ClusterSpec({
"ps": [
"machine-a.example.com:2221", # /job:ps/task:0
],
"worker": [
"machine-a.example.com:2222", # /job:worker/task:0
"machine-b.example.com:2222", # /job:worker/task:1
]})
要啟動(dòng) TensorFlow 服務(wù)器氓栈,您必須創(chuàng)建一個(gè)服務(wù)器對(duì)象渣磷,并向其傳遞集群規(guī)范(以便它可以與其他服務(wù)器通信)以及它自己的作業(yè)名稱和任務(wù)編號(hào)。 例如授瘦,要啟動(dòng)第一個(gè)輔助任務(wù)醋界,您需要在機(jī)器 A 上運(yùn)行以下代碼:
server = tf.train.Server(cluster_spec, job_name="worker", task_index=0)
每臺(tái)機(jī)器只運(yùn)行一個(gè)任務(wù)通常比較簡(jiǎn)單,但前面的例子表明 TensorFlow 允許您在同一臺(tái)機(jī)器上運(yùn)行多個(gè)任務(wù)(如果需要的話)提完。 如果您在一臺(tái)機(jī)器上安裝了多臺(tái)服務(wù)器形纺,則需要確保它們不會(huì)全部嘗試抓取每個(gè) GPU 的所有 RAM,如前所述徒欣。 例如逐样,在圖12-6中,"ps"
任務(wù)沒有看到 GPU 設(shè)備打肝,想必其進(jìn)程是使用CUDA_VISIBLE_DEVICES =""
啟動(dòng)的脂新。 請(qǐng)注意,CPU由位于同一臺(tái)計(jì)算機(jī)上的所有任務(wù)共享粗梭。
如果您希望進(jìn)程除了運(yùn)行 TensorFlow 服務(wù)器之外什么都不做争便,您可以通過告訴它等待服務(wù)器使用join()
方法來完成,從而阻塞主線程(否則服務(wù)器將在您的主線程退出)楼吃。 由于目前沒有辦法阻止服務(wù)器始花,這實(shí)際上會(huì)永遠(yuǎn)阻止:
server.join() # blocks until the server stops (i.e., never)
開始一個(gè)會(huì)話
一旦所有任務(wù)啟動(dòng)并運(yùn)行(但還什么都沒做)妄讯,您可以從位于任何機(jī)器上的任何進(jìn)程(甚至是運(yùn)行中的進(jìn)程)中的客戶機(jī)上的任何服務(wù)器上打開會(huì)話,并使用該會(huì)話像普通的本地會(huì)議一樣酷宵。比如:
a = tf.constant(1.0)
b = a + 2
c = a * 3
with tf.Session("grpc://machine-b.example.com:2222") as sess:
print(c.eval()) # 9.0
這個(gè)客戶端代碼首先創(chuàng)建一個(gè)簡(jiǎn)單的圖形亥贸,然后在位于機(jī)器 B(我們稱之為主機(jī))上的 TensorFlow 服務(wù)器上打開一個(gè)會(huì)話,并指示它求值c
浇垦。 主設(shè)備首先將操作放在適當(dāng)?shù)脑O(shè)備上炕置。 在這個(gè)例子中,因?yàn)槲覀儧]有在任何設(shè)備上進(jìn)行任何操作男韧,所以主設(shè)備只將它們?nèi)糠旁谒约旱哪J(rèn)設(shè)備上 - 在這種情況下是機(jī)器 B 的 GPU 設(shè)備朴摊。 然后它只是按照客戶的指示求值c
,并返回結(jié)果此虑。
主機(jī)和輔助服務(wù)
客戶端使用 gRPC 協(xié)議(Google Remote Procedure Call)與服務(wù)器進(jìn)行通信甚纲。 這是一個(gè)高效的開源框架,可以調(diào)用遠(yuǎn)程函數(shù)朦前,并通過各種平臺(tái)和語言獲取它們的輸出介杆。它基于 HTTP2,打開一個(gè)連接并在整個(gè)會(huì)話期間保持打開狀態(tài)韭寸,一旦建立連接就可以進(jìn)行高效的雙向通信春哨。
數(shù)據(jù)以協(xié)議緩沖區(qū)的形式傳輸,這是另一種開源 Google 技術(shù)恩伺。 這是一種輕量級(jí)的二進(jìn)制數(shù)據(jù)交換格式赴背。
TensorFlow 集群中的所有服務(wù)器都可能與集群中的任何其他服務(wù)器通信,因此請(qǐng)確保在防火墻上打開適當(dāng)?shù)亩丝凇?/p>
每臺(tái) TensorFlow 服務(wù)器都提供兩種服務(wù):主服務(wù)和輔助服務(wù)晶渠。 主服務(wù)允許客戶打開會(huì)話并使用它們來運(yùn)行圖形凰荚。 它協(xié)調(diào)跨任務(wù)的計(jì)算,依靠輔助服務(wù)實(shí)際執(zhí)行其他任務(wù)的計(jì)算并獲得結(jié)果乱陡。
固定任務(wù)的操作
通過指定作業(yè)名稱浇揩,任務(wù)索引,設(shè)備類型和設(shè)備索引憨颠,可以使用設(shè)備塊來鎖定由任何任務(wù)管理的任何設(shè)備上的操作。 例如积锅,以下代碼將a
固定在"ps"
作業(yè)(即機(jī)器 A 上的 CPU)中第一個(gè)任務(wù)的 CPU爽彤,并將b
固定在"worker"
作業(yè)的第一個(gè)任務(wù)管理的第二個(gè) GPU (這是 A 機(jī)上的 GPU#1)。 最后缚陷,c
沒有固定在任何設(shè)備上适篙,所以主設(shè)備將它放在它自己的默認(rèn)設(shè)備上(機(jī)器 B 的 GPU#0 設(shè)備)。
with tf.device("/job:ps/task:0/cpu:0")
a = tf.constant(1.0)
with tf.device("/job:worker/task:0/gpu:1")
b = a + 2
c = a + b
如前所述箫爷,如果您省略設(shè)備類型和索引嚷节,則 TensorFlow 將默認(rèn)為該任務(wù)的默認(rèn)設(shè)備; 例如聂儒,將操作固定到"/job:ps/task:0"
會(huì)將其放置在"ps"
作業(yè)(機(jī)器 A 的 CPU)的第一個(gè)任務(wù)的默認(rèn)設(shè)備上。 如果您還省略了任務(wù)索引(例如硫痰,"/job:ps"
)衩婚,則 TensorFlow 默認(rèn)為"/task:0"
。如果省略作業(yè)名稱和任務(wù)索引效斑,則 TensorFlow 默認(rèn)為會(huì)話的主任務(wù)非春。
跨多個(gè)參數(shù)服務(wù)器的分片變量
正如我們很快會(huì)看到的那樣,在分布式設(shè)置上訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí)缓屠,常見模式是將模型參數(shù)存儲(chǔ)在一組參數(shù)服務(wù)器上(即"ps"
作業(yè)中的任務(wù))奇昙,而其他任務(wù)則集中在計(jì)算上(即 ,"worker"
工作中的任務(wù))敌完。 對(duì)于具有數(shù)百萬參數(shù)的大型模型储耐,在多個(gè)參數(shù)服務(wù)器上分割這些參數(shù)非常有用,可以降低飽和單個(gè)參數(shù)服務(wù)器網(wǎng)卡的風(fēng)險(xiǎn)滨溉。 如果您要將每個(gè)變量手動(dòng)固定到不同的參數(shù)服務(wù)器弧岳,那將非常繁瑣。 幸運(yùn)的是业踏,TensorFlow 提供了replica_device_setter()
函數(shù)禽炬,它以循環(huán)方式在所有"ps"
任務(wù)中分配變量。 例如勤家,以下代碼將五個(gè)變量引入兩個(gè)參數(shù)服務(wù)器:
with tf.device(tf.train.replica_device_setter(ps_tasks=2):
v1 = tf.Variable(1.0) # pinned to /job:ps/task:0
v2 = tf.Variable(2.0) # pinned to /job:ps/task:1
v3 = tf.Variable(3.0) # pinned to /job:ps/task:0
v4 = tf.Variable(4.0) # pinned to /job:ps/task:1
v5 = tf.Variable(5.0) # pinned to /job:ps/task:0
您不必傳遞ps_tasks
的數(shù)量腹尖,您可以傳遞集群spec = cluster_spec
,TensorFlow 將簡(jiǎn)單計(jì)算"ps"
作業(yè)中的任務(wù)數(shù)伐脖。
如果您在塊中創(chuàng)建其他操作热幔,則不僅僅是變量,TensorFlow 會(huì)自動(dòng)將它們連接到"/job:worker"
讼庇,默認(rèn)為第一個(gè)由"worker"
作業(yè)中第一個(gè)任務(wù)管理的設(shè)備绎巨。 您可以通過設(shè)置worker_device
參數(shù)將它們固定到其他設(shè)備,但更好的方法是使用嵌入式設(shè)備塊蠕啄。 內(nèi)部設(shè)備塊可以覆蓋在外部塊中定義的作業(yè)场勤,任務(wù)或設(shè)備。 例如:
with tf.device(tf.train.replica_device_setter(ps_tasks=2)):
v1 = tf.Variable(1.0) # pinned to /job:ps/task:0 (+ defaults to /cpu:0)
v2 = tf.Variable(2.0) # pinned to /job:ps/task:1 (+ defaults to /cpu:0)
v3 = tf.Variable(3.0) # pinned to /job:ps/task:0 (+ defaults to /cpu:0)
[...]
s = v1 + v2 # pinned to /job:worker (+ defaults to task:0/gpu:0)
with tf.device("/gpu:1"):
p1 = 2 * s # pinned to /job:worker/gpu:1 (+ defaults to /task:0)
with tf.device("/task:1"):
p2 = 3 * s # pinned to /job:worker/task:1/gpu:1
這個(gè)例子假設(shè)參數(shù)服務(wù)器是純 CPU 的歼跟,這通常是這種情況和媳,因?yàn)樗鼈冎恍枰鎯?chǔ)和傳送參數(shù),而不是執(zhí)行密集計(jì)算哈街。
(負(fù)責(zé)這章的人沒有全部完成留瞳,等整理完最后一章,我再來補(bǔ)上)
(第一部分 機(jī)器學(xué)習(xí)基礎(chǔ))
第01章 機(jī)器學(xué)習(xí)概覽
第02章 一個(gè)完整的機(jī)器學(xué)習(xí)項(xiàng)目(上)
第02章 一個(gè)完整的機(jī)器學(xué)習(xí)項(xiàng)目(下)
第03章 分類
第04章 訓(xùn)練模型
第05章 支持向量機(jī)
第06章 決策樹
第07章 集成學(xué)習(xí)和隨機(jī)森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí))
第9章 啟動(dòng)和運(yùn)行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強(qiáng)化學(xué)習(xí)(上)
第16章 強(qiáng)化學(xué)習(xí)(下)