構(gòu)建具有出色性能的系統(tǒng)需要很好地理解算法和模型以捕獲問題的統(tǒng)計(jì)方面。同時(shí),對底層硬件至少有一點(diǎn)了解也是必不可少的。當(dāng)前部分不能替代有關(guān)硬件和系統(tǒng)設(shè)計(jì)的適當(dāng)課程。相反,它可以作為理解為什么某些算法比其他算法更有效以及如何實(shí)現(xiàn)良好吞吐量的起點(diǎn)。一個(gè)好的設(shè)計(jì)可以很容易地產(chǎn)生一個(gè)數(shù)量級的差異,反過來,這可以在能夠訓(xùn)練網(wǎng)絡(luò)(例如,在一周內(nèi))和根本不能(在 3 個(gè)月內(nèi),從而錯過最后期限)之間產(chǎn)生差異). 我們將從查看計(jì)算機(jī)開始。然后我們將放大以更仔細(xì)地查看 CPU 和 GPU。

圖 13.4.1每個(gè)程序員都應(yīng)該知道的延遲數(shù)。
不耐煩的讀者可以通過 圖 13.4.1來解決。它摘自 Colin Scott 的 互動帖子 ,該帖子很好地概述了過去十年的進(jìn)展。原始數(shù)字來自 Jeff Dean 2010 年在斯坦福的演講。下面的討論解釋了這些數(shù)字的一些基本原理,以及它們?nèi)绾沃笇?dǎo)我們設(shè)計(jì)算法。下面的討論是非常高層次和粗略的。它顯然不能替代適當(dāng)?shù)恼n程,而只是為統(tǒng)計(jì)建模人員提供足夠的信息以做出適當(dāng)?shù)脑O(shè)計(jì)決策。對于計(jì)算機(jī)體系結(jié)構(gòu)的深入概述,我們建議讀者參閱 (Hennessy 和 Patterson,2011 年)或最近關(guān)于該主題的課程,例如Arste Asanovic的課程。
13.4.1。電腦
大多數(shù)深度學(xué)習(xí)研究人員和從業(yè)者都可以使用具有相當(dāng)數(shù)量內(nèi)存、計(jì)算能力、某種形式的加速器(如 GPU)或其倍數(shù)的計(jì)算機(jī)。計(jì)算機(jī)由以下關(guān)鍵部件組成:
-
能夠執(zhí)行我們給它的程序(除了運(yùn)行操作系統(tǒng)和許多其他東西)的處理器(也稱為 CPU),通常由 8 個(gè)或更多內(nèi)核組成。
-
內(nèi)存 (RAM),用于存儲和檢索計(jì)算結(jié)果,例如權(quán)重向量和激活以及訓(xùn)練數(shù)據(jù)。
-
速度范圍為 1 GB/s 到 100 GB/s 的以太網(wǎng)網(wǎng)絡(luò)連接(有時(shí)是多個(gè))。在高端服務(wù)器上可以找到更高級的互連。
-
用于將系統(tǒng)連接到一個(gè)或多個(gè) GPU 的高速擴(kuò)展總線 (PCIe)。服務(wù)器有多達(dá) 8 個(gè)加速器,通常以高級拓?fù)溥B接,而桌面系統(tǒng)有 1 個(gè)或 2 個(gè),具體取決于用戶的預(yù)算和電源的大小。
-
耐用存儲設(shè)備,例如磁性硬盤驅(qū)動器、固態(tài)驅(qū)動器,在許多情況下使用 PCIe 總線連接。它提供訓(xùn)練數(shù)據(jù)到系統(tǒng)的高效傳輸,并根據(jù)需要存儲中間檢查點(diǎn)。
圖 13.4.2計(jì)算機(jī)組件的連接性。
如圖13.4.2所示,大多數(shù)組件(網(wǎng)絡(luò)、GPU 和存儲)都通過 PCIe 總線連接到 CPU。它由多個(gè)直接連接到 CPU 的通道組成。例如,AMD 的 Threadripper 3 有 64 個(gè) PCIe 4.0 通道,每個(gè)通道都能夠在兩個(gè)方向上進(jìn)行 16 Gbit/s 的數(shù)據(jù)傳輸。內(nèi)存直連CPU,總帶寬高達(dá)100GB/s。
當(dāng)我們在計(jì)算機(jī)上運(yùn)行代碼時(shí),我們需要將數(shù)據(jù)混洗到處理器(CPU 或 GPU)、執(zhí)行計(jì)算,然后將結(jié)果從處理器移回 RAM 和持久存儲。因此,為了獲得良好的性能,我們需要確保它可以無縫運(yùn)行,而不會有任何一個(gè)系統(tǒng)成為主要瓶頸。例如,如果我們不能足夠快地加載圖像,處理器將無事可做。同樣,如果我們不能足夠快地將矩陣移動到 CPU(或 GPU),它的處理元素就會餓死。最后,如果我們想通過網(wǎng)絡(luò)同步多臺計(jì)算機(jī),后者不應(yīng)該減慢計(jì)算速度。一種選擇是交錯通信和計(jì)算。讓我們更詳細(xì)地了解各種組件。
13.4.2。記憶
最基本的內(nèi)存用于存儲需要隨時(shí)訪問的數(shù)據(jù)。目前 CPU RAM 通常是 DDR4類型,每個(gè)模塊提供 20–25 GB/s 的帶寬。每個(gè)模塊都有一個(gè) 64 位寬的總線。通常使用成對的內(nèi)存模塊來允許多個(gè)通道。CPU 有 2 到 4 個(gè)內(nèi)存通道,即它們有 4 0GB/s 到 100 GB/s 的峰值內(nèi)存帶寬。每個(gè)通道通常有兩個(gè)庫。例如 AMD 的 Zen 3 Threadripper 有 8 個(gè)插槽。
盡管這些數(shù)字令人印象深刻,但它們確實(shí)只說明了部分情況。當(dāng)我們想從內(nèi)存中讀取一部分時(shí),我們首先需要告訴內(nèi)存模塊在哪里可以找到信息。也就是說,我們首先需要將地址發(fā)送到RAM。完成此操作后,我們可以選擇只讀取單個(gè) 64 位記錄或一長串記錄。后者稱為突發(fā)讀取. 簡而言之,向內(nèi)存發(fā)送地址并設(shè)置傳輸大約需要 100 ns(具體取決于所使用的內(nèi)存芯片的具體時(shí)序系數(shù)),隨后的每次傳輸僅需 0.2 ns。簡而言之,第一次閱讀的成本是后續(xù)閱讀的 500 倍!請注意,我們每秒最多可以執(zhí)行 10,000,000 次隨機(jī)讀取。這表明我們盡可能避免隨機(jī)內(nèi)存訪問,而是使用突發(fā)讀?。ê蛯懭耄?/font>
當(dāng)我們考慮到我們有多家銀行時(shí),事情就有點(diǎn)復(fù)雜了。每個(gè)銀行都可以在很大程度上獨(dú)立地讀取內(nèi)存。這意味著兩件事。一方面,隨機(jī)讀取的有效數(shù)量高達(dá) 4 倍,前提是它們均勻分布在內(nèi)存中。這也意味著執(zhí)行隨機(jī)讀取仍然是一個(gè)壞主意,因?yàn)橥话l(fā)讀取也快了 4 倍。另一方面,由于內(nèi)存對齊到 64 位邊界,最好將任何數(shù)據(jù)結(jié)構(gòu)對齊到相同的邊界。 當(dāng)設(shè)置了適當(dāng)?shù)臉?biāo)志時(shí),編譯器幾乎會 自動執(zhí)行此操作。我們鼓勵好奇的讀者復(fù)習(xí)一下關(guān)于 DRAM 的講座,例如Zeshan Chishti的講座。
GPU 內(nèi)存需要滿足更高的帶寬要求,因?yàn)樗鼈兊奶幚碓乇?CPU 多得多。總的來說,有兩種選擇可以解決這些問題。首先是使內(nèi)存總線顯著變寬。例如,NVIDIA 的 RTX 2080 Ti 具有 352 位寬的總線。這允許同時(shí)傳輸更多信息。其次,GPU 使用特定的高性能內(nèi)存。消費(fèi)級設(shè)備,例如 NVIDIA 的 RTX 和 Titan 系列通常使用GDDR6 總帶寬超過 500 GB/s 的芯片。另一種方法是使用 HBM(高帶寬內(nèi)存)模塊。它們使用非常不同的接口,并直接與專用硅晶圓上的 GPU 連接。這使得它們非常昂貴,而且它們的使用通常僅限于高端服務(wù)器芯片,例如 NVIDIA Volta V100 系列加速器。不出所料,GPU 內(nèi)存通常比 CPU 內(nèi)存小得多,因?yàn)榍罢叩某杀据^高。就我們的目的而言,它們的性能特征大體上相似,只是速度快得多。出于本書的目的,我們可以安全地忽略細(xì)節(jié)。它們僅在調(diào)整 GPU 內(nèi)核以實(shí)現(xiàn)高吞吐量時(shí)才重要。
13.4.3。貯存
我們看到 RAM 的一些關(guān)鍵特性是帶寬和 延遲。存儲設(shè)備也是如此,只是差異可能更加極端。
13.4.3.1。硬盤驅(qū)動器
硬盤驅(qū)動器(HDD) 已經(jīng)使用了半個(gè)多世紀(jì)。簡而言之,它們包含許多帶有磁頭的旋轉(zhuǎn)盤片,可以定位以在任何給定軌道上讀取或?qū)懭搿?/font>高端磁盤在 9 個(gè)盤片上最多可容納 16 TB。HDD 的主要優(yōu)點(diǎn)之一是它們相對便宜。它們的眾多缺點(diǎn)之一是它們通常的災(zāi)難性故障模式和相對較高的讀取延遲。
要理解后者,請考慮 HDD 以大約 7,200 RPM(每分鐘轉(zhuǎn)數(shù))旋轉(zhuǎn)的事實(shí)。如果它們快得多,它們就會因施加在盤片上的離心力而破碎。在訪問磁盤上的特定扇區(qū)時(shí),這有一個(gè)主要的缺點(diǎn):我們需要等到盤片旋轉(zhuǎn)到位(我們可以移動磁頭但不能加速實(shí)際磁盤)。因此,在請求的數(shù)據(jù)可用之前,它可能需要 8 毫秒以上的時(shí)間。一種常見的表達(dá)方式是 HDD 可以以大約 100 IOP(每秒輸入/輸出操作)的速度運(yùn)行。這個(gè)數(shù)字在過去二十年中基本保持不變。更糟糕的是,增加帶寬同樣困難(大約為 100–200 MB/s)。畢竟,每個(gè)磁頭都讀取一條比特軌道,因此,比特率僅與信息密度的平方根成比例。因此,HDD 正迅速降級為非常大的數(shù)據(jù)集的歸檔存儲和低級存儲。
13.4.3.2。固態(tài)硬盤
固態(tài)硬盤 (SSD) 使用閃存來持久存儲信息。這允許更快地訪問存儲的記錄。現(xiàn)代 SSD 可以以 100,000 到 500,000 IOP 的速度運(yùn)行,即比 HDD 快 3 個(gè)數(shù)量級。此外,它們的帶寬可以達(dá)到 1–3GB/s,即比 HDD 快一個(gè)數(shù)量級。這些改進(jìn)聽起來好得令人難以置信。實(shí)際上,由于 SSD 的設(shè)計(jì)方式,它們具有以下警告。
-
SSD 以塊(256 KB 或更大)的形式存儲信息。它們只能作為一個(gè)整體來寫,這會花費(fèi)大量時(shí)間。因此,SSD 上的按位隨機(jī)寫入性能非常差。同樣,寫入數(shù)據(jù)通常會花費(fèi)大量時(shí)間,因?yàn)楸仨氉x取、擦除塊,然后用新信息重寫。到目前為止,SSD 控制器和固件已經(jīng)開發(fā)出算法來緩解這種情況。盡管如此,寫入速度可能會慢得多,尤其是對于 QLC(四級單元)SSD。提高性能的關(guān)鍵是維護(hù)一個(gè) 操作隊(duì)列,盡可能在大塊中優(yōu)先讀取和寫入。
-
SSD 中的存儲單元磨損相對較快(通常在幾千次寫入后就已經(jīng)磨損)。磨損級保護(hù)算法能夠?qū)⑼嘶瘋鞑サ皆S多單元上。也就是說,不建議將 SSD 用于交換文件或日志文件的大量聚合。
-
最后,帶寬的大幅增加迫使計(jì)算機(jī)設(shè)計(jì)人員將 SSD 直接連接到 PCIe 總線。能夠處理此問題的驅(qū)動器稱為 NVMe(增強(qiáng)型非易失性內(nèi)存),最多可使用 4 個(gè) PCIe 通道。在 PCIe 4.0 上這相當(dāng)于高達(dá) 8GB/s。
13.4.3.3。云儲存
云存儲提供可配置的性能范圍。也就是說,存儲分配給虛擬機(jī)是動態(tài)的,無論是數(shù)量還是速度,都由用戶選擇。我們建議用戶在延遲太高時(shí)增加 IOP 的配置數(shù)量,例如,在使用許多小記錄進(jìn)行訓(xùn)練期間。
13.4.4。處理器
中央處理器 (CPU) 是任何計(jì)算機(jī)的核心部件。它們由許多關(guān)鍵組件組成:能夠執(zhí)行機(jī)器代碼的處理器內(nèi)核、連接它們的總線(特定拓?fù)湓谔幚砥餍吞枴⒋鷶?shù)和供應(yīng)商之間有很大差異),以及允許更高帶寬和更低延遲內(nèi)存的緩存訪問比從主存儲器讀取可能的訪問。最后,幾乎所有現(xiàn)代 CPU 都包含矢量處理單元,以輔助高性能線性代數(shù)和卷積,因?yàn)樗鼈冊诿襟w處理和機(jī)器學(xué)習(xí)中很常見。
圖 13.4.3 Intel Skylake 消費(fèi)者四核 CPU。
圖 13.4.3描繪了 Intel Skylake 消費(fèi)級四核 CPU。它有一個(gè)集成的 GPU、緩存和連接四個(gè)內(nèi)核的環(huán)形總線。以太網(wǎng)、WiFi、藍(lán)牙、SSD 控制器和 USB 等外圍設(shè)備要么是芯片組的一部分,要么直接連接 (PCIe) 到 CPU。
13.4.4.1。微架構(gòu)
每個(gè)處理器內(nèi)核都包含一組相當(dāng)復(fù)雜的組件。雖然各代和供應(yīng)商之間的細(xì)節(jié)有所不同,但基本功能幾乎是標(biāo)準(zhǔn)的。前端加載指令并嘗試預(yù)測將采用哪條路徑(例如,用于控制流)。然后將指令從匯編代碼解碼為微指令。匯編代碼通常不是處理器執(zhí)行的最低級別代碼。相反,復(fù)雜的指令可以被解碼成一組更底層的操作。這些然后由實(shí)際執(zhí)行核心處理。通常后者能夠同時(shí)執(zhí)行許多操作。例如, 圖 13.4.4的 ARM Cortex A77 核心能夠同時(shí)執(zhí)行多達(dá) 8 個(gè)操作。
圖 13.4.4 ARM Cortex A77 微架構(gòu)。
這意味著高效的程序可能能夠在每個(gè)時(shí)鐘周期執(zhí)行多條指令,前提是它們可以獨(dú)立執(zhí)行。并非所有單位生而平等。一些專注于整數(shù)指令,而另一些則針對浮點(diǎn)性能進(jìn)行了優(yōu)化。為了增加吞吐量,處理器還可以在分支指令中同時(shí)遵循多個(gè)代碼路徑,然后丟棄未采用的分支的結(jié)果。這就是為什么分支預(yù)測單元(在前端)很重要,以至于只追求最有希望的路徑。
13.4.4.2。矢量化
深度學(xué)習(xí)非常需要計(jì)算。因此,要使 CPU 適合機(jī)器學(xué)習(xí),需要在一個(gè)時(shí)鐘周期內(nèi)執(zhí)行許多操作。這是通過矢量單元實(shí)現(xiàn)的。它們有不同的名稱:在 ARM 上它們被稱為 NEON,在 x86 上它們(最近一代)被稱為 AVX2 單元。一個(gè)共同點(diǎn)是它們能夠執(zhí)行 SIMD(單指令多數(shù)據(jù))操作。圖 13.4.5顯示了在 ARM 上如何在一個(gè)時(shí)鐘周期內(nèi)添加 8 個(gè)短整數(shù)。
圖 13.4.5 128 位 NEON 矢量化。
根據(jù)體系結(jié)構(gòu)的選擇,此類寄存器的長度可達(dá) 512 位,最多可組合 64 對數(shù)字。例如,我們可能將兩個(gè)數(shù)字相乘并將它們與第三個(gè)數(shù)字相加,這也稱為融合乘加。英特爾的 OpenVino使用這些在服務(wù)器級 CPU 上實(shí)現(xiàn)深度學(xué)習(xí)的可觀吞吐量。但請注意,這個(gè)數(shù)字與 GPU 能夠?qū)崿F(xiàn)的目標(biāo)相比完全相形見絀。例如,NVIDIA 的 RTX 2080 Ti 擁有 4,352 個(gè) CUDA 核心,每個(gè)核心都可以隨時(shí)處理這樣的操作。
13.4.4.3。緩存
考慮以下情況:如上圖 13.4.3所示,我們有一個(gè)普通的 4 核 CPU 核,運(yùn)行頻率為 2 GHz。此外,假設(shè)我們的 IPC(每時(shí)鐘指令數(shù))計(jì)數(shù)為 1,并且這些單元啟用了 256 位寬度的 AVX2。我們進(jìn)一步假設(shè)至少有一個(gè)用于 AVX2 操作的寄存器需要從內(nèi)存中檢索。這意味著 CPU 消耗 4×256?bit=128?bytes每個(gè)時(shí)鐘周期的數(shù)據(jù)。除非我們能夠轉(zhuǎn)移 2×109×128=256×109每秒向處理器發(fā)送的字節(jié)數(shù),處理元素將餓死。不幸的是,這種芯片的內(nèi)存接口僅支持 20–40 GB/s 的數(shù)據(jù)傳輸,即低一個(gè)數(shù)量級。解決方法是盡可能避免從內(nèi)存加載新數(shù)據(jù),而是將其緩存在 CPU 本地。這是緩存派上用場的地方。通常使用以下名稱或概念:
-
寄存器嚴(yán)格來說不是緩存的一部分。他們幫助階段性指示。也就是說,CPU 寄存器是 CPU 可以以時(shí)鐘速度訪問而不會造成任何延遲損失的內(nèi)存位置。CPU 有幾十個(gè)寄存器。有效地使用寄存器取決于編譯器(或程序員)。例如 C 編程語言有一個(gè)
register
關(guān)鍵字。 -
L1 緩存是抵御高內(nèi)存帶寬需求的第一道防線。L1 緩存很小(典型大小可能為 32–64 KB)并且通常分為數(shù)據(jù)和指令緩存。當(dāng)在一級緩存中找到數(shù)據(jù)時(shí),訪問速度非???。如果在那里找不到它們,搜索將沿著緩存層次結(jié)構(gòu)向下進(jìn)行。
-
二級緩存是下一站。根據(jù)體系結(jié)構(gòu)設(shè)計(jì)和處理器大小,它們可能是獨(dú)占的。它們可能只能由給定的核心訪問或在多個(gè)核心之間共享。L2 緩存比 L1 更大(通常每個(gè)內(nèi)核 256–512 KB)并且更慢。此外,要訪問 L2 中的某些內(nèi)容,我們首先需要檢查以了解數(shù)據(jù)不在 L1 中,這會增加少量的額外延遲。
-
L3 緩存在多個(gè)內(nèi)核之間共享,并且可能非常大。AMD 的 Epyc 3 服務(wù)器 CPU 擁有分布在多個(gè)小芯片上的高達(dá) 256 MB 的緩存。更典型的數(shù)字在 4–8 MB 范圍內(nèi)。
預(yù)測接下來需要哪些存儲元件是芯片設(shè)計(jì)中的關(guān)鍵優(yōu)化參數(shù)之一。例如,建議以正向遍歷內(nèi)存,因?yàn)榇蠖鄶?shù)緩存算法將嘗試提前讀取而不是向后讀取。同樣,將內(nèi)存訪問模式保持在本地是提高性能的好方法。
添加緩存是一把雙刃劍。一方面,它們確保處理器內(nèi)核不會缺少數(shù)據(jù)。同時(shí),它們增加了芯片尺寸,占用了本來可以用于提高處理能力的面積。此外,高速緩存未命中的代價(jià)可能很高。考慮最壞的情況,錯誤共享,如圖13.4.6所示。當(dāng)處理器 1 上的線程請求數(shù)據(jù)時(shí),內(nèi)存位置緩存在處理器 0 上。為了獲得它,處理器 0 需要停止它正在做的事情,將信息寫回主內(nèi)存,然后讓處理器 1 從內(nèi)存中讀取它。在此操作期間,兩個(gè)處理器都在等待。這樣的代碼很可能運(yùn)行 得更慢與高效的單處理器實(shí)現(xiàn)相比,在多個(gè)處理器上。這是緩存大小存在實(shí)際限制(除了它們的物理大?。┑牧硪粋€(gè)原因。
圖 13.4.6虛假共享(圖片由英特爾提供)。
13.4.5。GPU 和其他加速器
可以毫不夸張地說,如果沒有 GPU,深度學(xué)習(xí)就不會成功。出于同樣的原因,GPU 制造商的財(cái)富因深度學(xué)習(xí)而大幅增加的說法也十分合理。硬件和算法的這種共同進(jìn)化導(dǎo)致了這樣一種情況,即無論好壞,深度學(xué)習(xí)都是更可取的統(tǒng)計(jì)建模范例。因此,有必要了解 GPU 和相關(guān)加速器(如 TPU)的具體優(yōu)勢 (Jouppi等人,2017 年)。
值得注意的是在實(shí)踐中經(jīng)常出現(xiàn)的區(qū)別:加速器針對訓(xùn)練或推理進(jìn)行了優(yōu)化。對于后者,我們只需要計(jì)算網(wǎng)絡(luò)中的前向傳播。反向傳播不需要存儲中間數(shù)據(jù)。此外,我們可能不需要非常精確的計(jì)算(FP16 或 INT8 通常就足夠了)。另一方面,在訓(xùn)練期間,所有中間結(jié)果都需要存儲以計(jì)算梯度。此外,累積梯度需要更高的精度以避免數(shù)值下溢(或溢出)。這意味著 FP16(或與 FP32 的混合精度)是最低要求。所有這些都需要更快、更大的內(nèi)存(HBM2 與 GDDR6)和更強(qiáng)的處理能力。例如,英偉達(dá)的 圖靈 T4 GPU 針對推理進(jìn)行了優(yōu)化,而 V100 GPU 更適合訓(xùn)練。
回憶一下圖 13.4.5中所示的矢量化。將矢量單元添加到處理器內(nèi)核使我們能夠顯著提高吞吐量。例如,在
評論