cuDNN 5对RNN模型的性能优化
原文:Optimizing Recurrent Neural Networks in cuDNN 5
作者:Jeremy Appleyard
翻譯:趙屹華 審校:劉翔宇
責(zé)編:周建丁(zhoujd@csdn.net)
在GTC2016大會(huì)上,NVIDIA發(fā)布了最新版本的深度學(xué)習(xí)開(kāi)發(fā)包,其中包括了cuDNN 5。第五代cuDNN引入了新的特性,提升了性能,并且支持最新一代的NVIDIA Tesla P100 GPU。cuDNN的新特性包括:
- 使用Winograd卷積算法,計(jì)算前向、后向卷積速度更快;
- 支持3D FFT Tiling;
- 支持空間轉(zhuǎn)移網(wǎng)絡(luò);
- 更優(yōu)的性能,在Pascal GPU上使用半精度函數(shù)節(jié)省了內(nèi)存空間;
-
支持用于序列學(xué)習(xí)的LSTM遞歸神經(jīng)網(wǎng)絡(luò),速度提升6倍。
圖1:cuDNN 5 + Torch speedup vs. Torch-rnn ,M40, Intel:emoji: Xeon:emoji: Processor E5-2698。網(wǎng)絡(luò)模型A:RNN維度2560,輸出維度2560,1層,序列長(zhǎng)度200,批大小為64。網(wǎng)絡(luò)模型B:RNN維度256,輸入維度64,3層,批大小為64。網(wǎng)絡(luò)模型C:RNN維度256,輸入維度256,1層,批大小為32,序列長(zhǎng)度1000。
cuDNN 5的新特性之一就是它可以支持遞歸神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Networks)。RNN是各個(gè)領(lǐng)域用于序列學(xué)習(xí)的強(qiáng)大工具,從語(yǔ)音識(shí)別到圖像配字。若想簡(jiǎn)單了解RNNs,LSTM和序列學(xué)習(xí)的內(nèi)容,我推薦閱讀Tim Dettmers最近的文章Deep Learning in a Nutshell: Sequence Learning ,更深入的理解可以閱讀Soumith Chintala的文章Understanding Natural Language with Deep Neural Networks Using Torch。
我對(duì)cuDNN 5支持RNN的能力感到非常激動(dòng);我們投入了大量的精力來(lái)優(yōu)化它們?cè)贜VIDIA GPU上的性能,我在本文中將會(huì)介紹這些優(yōu)化的一部分細(xì)節(jié)。
cuDNN 5支持四種RNN模式:ReLU激活函數(shù)模式,tanh激活函數(shù)模式,門控遞歸單元(Gated Recurrent Units)和長(zhǎng)短期記憶(LSTM)。在這類,我將以LSTM網(wǎng)絡(luò)的性能為例,但大多數(shù)的優(yōu)化可以用在任意RNN模型。
第一步:優(yōu)化單次迭代
下列方程組表示了數(shù)據(jù)如何在LSTM單元正向傳播。圖2展示了LSTM單元的示意圖。
從計(jì)算的角度來(lái)看,這里需要8次矩陣的乘法運(yùn)算(GEMM)—— 有四次對(duì)輸入i,有四次對(duì)輸入h —— 以及大量逐點(diǎn)運(yùn)算。
這個(gè)案例分析的出發(fā)點(diǎn)是LSTM的逐步實(shí)現(xiàn)。對(duì)于每次迭代的每一層計(jì)算,系統(tǒng)調(diào)用cuBLAS sgemm分別來(lái)完成那8次GEMM運(yùn)算。人工編寫的CUDA內(nèi)核調(diào)用每個(gè)逐點(diǎn)運(yùn)算。這種方法的偽代碼如下所示:
for layer in layers:for iteration in iterations:perform 4 SGEMMs on input from last layerperform 4 SGEMMs on input from last iterationperform point-wise operations我在Tesla M40 GPU上逐步、逐層地記錄了運(yùn)行耗時(shí),作為對(duì)照數(shù)據(jù)。我的對(duì)照LSTM模型有512個(gè)隱藏單元,每批次的樣本數(shù)為64.對(duì)照組的性能很一般,在M40上只達(dá)到了大約350 GFLOPs。這個(gè)GPU的峰值性能是6000 GFLOPs,因此還有很大的優(yōu)化空間。我們開(kāi)始吧。
優(yōu)化1:組合GEMM操作
GPU有非常高的浮點(diǎn)數(shù)吞吐量峰值,但是需要大量的并行化才能達(dá)到這個(gè)峰值。你設(shè)計(jì)的任務(wù)越是并行化,能達(dá)到的性能越好。測(cè)試這段LSTM代碼后發(fā)現(xiàn),GEMM操作所用的CUDA thread block個(gè)數(shù)遠(yuǎn)遠(yuǎn)少于GPU的SM個(gè)數(shù),也就是說(shuō)GPU遠(yuǎn)未被充分利用。
GEMM往往在輸出矩陣維度上做并行化,每個(gè)線程計(jì)算很多輸出元素。在這里,8個(gè)輸出矩陣的每一個(gè)都有512x64個(gè)元素,只用到4個(gè)thread block。理想情況下block的運(yùn)行個(gè)數(shù)可以遠(yuǎn)大于GPU的SM個(gè)數(shù),需要最大化這個(gè)內(nèi)核的理論占用值,至少達(dá)到每個(gè)SM有4個(gè)block(或者總共96個(gè))。(參見(jiàn) CUDA Best Practices guide for more on occupancy)
如果n個(gè)獨(dú)立的矩陣乘法共用同一份輸入數(shù)據(jù),那么它們可以被合并為一個(gè)大的矩陣乘法,輸出結(jié)果擴(kuò)大n倍。因此,第一個(gè)優(yōu)化方法就是把遞歸階段的四次W矩陣操作合并為一次,并且把輸入數(shù)據(jù)的四次W矩陣操作也做合并。我們剩下了兩個(gè)矩陣乘法,而不是原來(lái)的八個(gè),但是每次結(jié)果擴(kuò)大了四倍,并行能力擴(kuò)大四倍(每個(gè)GMM有16個(gè)block)。這種優(yōu)化在大部分框架中都很常見(jiàn):很簡(jiǎn)單的變化確帶來(lái)了顯著的性能提升:代碼運(yùn)行速度大約翻倍。
優(yōu)化2:流式GEMMS
盡管GEMMs被合并了,性能仍舊收到缺少并行的限制:盡管從4個(gè)提升到16個(gè),但是我們的目標(biāo)是至少96個(gè)。剩余的兩個(gè)GEMM相互獨(dú)立,因此它們可以用CUDA stream并行計(jì)算。這又將并行的block個(gè)數(shù)翻倍,達(dá)到了32個(gè)。
優(yōu)化3:融合逐點(diǎn)運(yùn)算
圖3顯示目前大部分的時(shí)間消耗在了逐點(diǎn)運(yùn)算上。沒(méi)必要在獨(dú)立的內(nèi)核中進(jìn)行這些;將它們?nèi)诤系酵粋€(gè)內(nèi)核可以減少數(shù)據(jù)在全局內(nèi)存中的傳遞,并且大大減少了內(nèi)核加載的開(kāi)銷。
至此,我非常欣慰地看到單次迭代的性能改善:大部分的計(jì)算量在于GEMM,并且盡可能地做了并行計(jì)算。這種實(shí)現(xiàn)方法比對(duì)照組快了5倍,但是仍有提升的余地。
for layer in layers:for iteration in iterations:perform sgemm on input from last layer in stream Aperform sgemm on input from last iteration in stream Bwait for stream A and stream Bperform point-wise operations in one kernel第二步:優(yōu)化多次迭代
在RNN模型中,單次迭代的操作會(huì)被重復(fù)很多次。這也意味著很有必要讓這些重復(fù)操作有效率地執(zhí)行,即使需要先增加一部分開(kāi)銷。
優(yōu)化4:預(yù)轉(zhuǎn)置權(quán)重矩陣
在進(jìn)行一次GEMM計(jì)算時(shí),標(biāo)準(zhǔn)的BLAS接口允許我們對(duì)兩個(gè)輸入矩陣的任意一個(gè)做轉(zhuǎn)置。兩個(gè)矩陣是否轉(zhuǎn)置的四種組合中,其中某幾種組合會(huì)比其它幾種算得更快或者更慢。這取決于方程組到計(jì)算過(guò)程的映射方式,可能使用了較慢版本的GEMM。通過(guò)預(yù)先對(duì)權(quán)重矩陣的轉(zhuǎn)置操作,每一次迭代會(huì)略微快一些。盡管多了一步轉(zhuǎn)置操作的開(kāi)銷,但是開(kāi)銷也不大,所以如果在多次迭代中用到了轉(zhuǎn)置矩陣,也是值得的。
優(yōu)化5:合并輸入GEMMs
許多情況下,在RNN計(jì)算開(kāi)始之時(shí)所有的輸入就已經(jīng)就緒。也就是說(shuō)對(duì)這些輸入的矩陣運(yùn)算操作可以立即開(kāi)始。這也意味著它們能夠被合并為更大的GEMMs。盡管起初這似乎是件好事(合并的GEMMs有更好的并行化),遞歸GEMM的傳遞依賴于輸入GEMMs的完成度。因此需要我們做出取舍:合并輸入GEMMs使得操作的并行化程度更高,但也阻止了遞歸GEMMs的過(guò)程重疊。這里的最佳策略往往取決于RNN的超參數(shù)。在我們的例子里,合并兩個(gè)輸入GEMM是最合適的。
for layer in layers:transpose weight matricesfor iteration in iterations / combination size:perform sgemm on combined input from last layer in stream Afor sub-iteration in combination size:perform sgemm on input from last iteration in stream Bwait for stream A wait for stream Bfor sub-iteration in combination size;perform pointwise operations in one kernel第三步:優(yōu)化多個(gè)層次
最后一步是考慮層與層的優(yōu)化。這里仍然有大量的并行化空間。圖4顯示了RNN的依賴關(guān)系圖。某一層的第n次迭代僅依賴于該層的第n-1次迭代和前一層的第n次迭代,因此有可能在前一層結(jié)束之前開(kāi)始下一層的計(jì)算。這相當(dāng)有用;如果網(wǎng)絡(luò)有兩層,則并行能力提升一倍。
從一層網(wǎng)絡(luò)到四層網(wǎng)絡(luò),吞吐量大約提升了1.7倍:從2.3TFLOPs到3.9TFLOPs。此時(shí),并行化所帶來(lái)的收益已經(jīng)有限了。相比最初只有4個(gè)block的實(shí)現(xiàn)方法,這種方法可以同時(shí)運(yùn)行128個(gè)block。這足以充分利用M40的資源,達(dá)到近70%的峰值浮點(diǎn)性能,運(yùn)行速度比原來(lái)的快10倍。
下面的表格顯示了我所描述的每一次優(yōu)化之后的性能,以及相比于對(duì)照代碼的效率提升。
| Baseline | 349 | (1.0x) |
| Combined GEMMs | 724 | 2.1x |
| GEMM Streaming | 994 | 2.8x |
| Fused point-wise operations | 1942 | 5.5x |
| Matrix pre-transposition | 2199 | 6.3x |
| Combining Inputs | 2290 | 6.5x |
| Four layers | 3898 | 11.1x |
反向傳播
反向傳播梯度與值的正向傳播非常相似。一旦梯度被傳播,權(quán)重值將會(huì)被更新:不再有任何遞歸性的依賴。這使我們得到了一個(gè)非常大、非常高效的矩陣乘法。
總結(jié)
為了得到最好的性能,你需要經(jīng)常要更多地提高并行性,而不是直截了當(dāng)?shù)貙?shí)現(xiàn)方程。在cuDNN,我們將這些優(yōu)化用在四種常見(jiàn)的RNN模型。因此如果你正在序列學(xué)習(xí)中用到這些RNN模型,我強(qiáng)烈推薦你使用cuDNN 5。
總結(jié)
以上是生活随笔為你收集整理的cuDNN 5对RNN模型的性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 人脸识别技术大总结(1)——Face D
- 下一篇: 给ADAS泼冷水?不,是客观评价