當前位置:
首頁 > 最新 > 高性能深度學習支持引擎實戰——TensorRT

高性能深度學習支持引擎實戰——TensorRT

雲棲君導讀:隨著傳統的高性能計算和新興的深度學習在百度、京東等大型的互聯網企業的普及發展,作為訓練和推理載體的GPU也被越來越多的使用。NVDIA本著讓大家能更好地利用GPU,使其在做深度學習訓練的時候達到更好的效果的目標,推出了支持高性能深度學習支持引擎——TensorRT。

本場技術沙龍回顧地址:http://click.aliyun.com/m/46060/

演講嘉賓簡介:

李曦鵬,現任英偉達devTech團隊高級軟體工程師,畢業於中國科學院過程工程研究所,擁有近10年CUDA/HPC編程經驗。主要負責深度神經網路和高性能計算的性能優化。

本次的分享主要分為兩部分:

一、TensorRT理論介紹:基礎介紹TensorRT是什麼;做了哪些優化;為什麼在有了框架的基礎上還需要TensorRT的優化引擎。

二、TensorRT高階介紹:對於進階的用戶,出現TensorRT不支持的網路層該如何處理;低精度運算如fp16,大家也知道英偉達最新的v100帶的TensorCore支持低精度的fp運算,包括上一代的Pascal的P100也是支持fp16運算,當然我們針對這種推斷(Inference)的版本還支持int8,就是說我們用8位的整型來取代原來的fp32做計算,大家可以想像速度上肯定會有很大提升,但是也會需要進行一些額外的工作。

隨著傳統的高性能計算和新興的深度學習在百度、京東等大型的互聯網企業的普及發展,作為訓練和推理載體的GPU也被越來越多的使用。我們團隊的目標是讓大家能更好地利用GPU,使其在做深度學習訓練的時候達到更好的效果。

一、TensorRT理論解釋

TensorRT項目立項的時候名字叫做GPU Inference Engine(簡稱GIE),Tensor表示數據流動以張量的形式。所謂張量大家可以理解為更加複雜的高維數組,一般一維數組叫做Vector(即向量),二維數組叫做Matrix,再高緯度的就叫Tensor,Matrix其實是二維的Tensor。在TensoRT中,所有的數據都被組成最高四維的數組,如果對應到CNN中其實就是,N表示batch size,即多少張圖片或者多少個推斷(Inference)的實例;C表示channel數目;H和W表示圖像或feature maps的高度和寬度。TR表示的是Runtime。

下圖是NVDIA針對深度學習平台的一系列完整的解決方案(官網有更新版本)。如果大家對深度學習有些了解的話可能會知道,它分為訓練和部署兩部分,訓練部分首先也是最重要的是構建網路結構,準備數據集,使用各種框架進行訓練,訓練要包含validation和test的過程,最後對於訓練好的模型要在實際業務中進行使用。訓練的操作一般在線下,實時數據來之後在線訓練的情況比較少,大多數情況下數據是離線的,已經收集好的,數據更新不頻繁的一天或一周一收集,數據更新頻繁的可能幾十分鐘,在線下有大規模的集群開始對數據或模型進行更新,這樣的訓練需要消耗大量的GPU,相對而言一般會給一個比較大的batchsize,因為它的實時性要求相對較低,一般訓練模型給的是128,甚至有些極端的1024,大的batch的好處是可以充分的利用GPU設備。但是到推斷(Inference)的時候就是不同的概念了,推斷(Inference)的時候只需要做一個前向計算,將輸入通過神經網路得出預測的結果。而推斷(Inference)的實際部署有多種可能,可能部署在Data Center(雲端數據中心),比如說大家常見的手機上的語音輸入,目前都還是雲端的,也就是說你的聲音是傳到雲端的,雲端處理好之後把數據再返回來;還可能部署在嵌入端,比如說嵌入式的攝像頭、無人機、機器人或車載的自動駕駛,當然車載的自動駕駛可能是嵌入式的設備,也可能是一台完整的主機,像這種嵌入式或自動駕駛,它的特點是對實時性要求很高。同樣的,Data Center也是對實時性要求很高,做一個語音識別,不能說說完了等很長時間還沒有返回,所以在線的部署最大的特點是對實時性要求很高,它對latency非常敏感,要我們能非常快的給出推斷(Inference)的結果。做一個不同恰當的比方,訓練(Training)這個階段如果模型比較慢,其實是一個砸錢可以解決的問題,我們可以用更大的集群、更多的機器,做更大的數據並行甚至是模型並行來訓練它,重要的是成本的投入。而部署端不只是成本的問題,如果方法不得當,即使使用目前最先進的GPU,也無法滿足推斷(Inference)的實時性要求。因為模型如果做得不好,沒有做優化,可能需要二三百毫秒才能做完一次推斷(Inference),再加上來回的網路傳輸,用戶可能一秒後才能得到結果。在語音識別的場景之下,用戶可以等待;但是在駕駛的場景之下,可能會有性命之庾。

在部署階段,latency是非常重要的點,而TensorRT是專門針對部署端進行優化的,目前TensorRT支持大部分主流的深度學習應用,當然最擅長的是CNN(卷積神經網路)領域,但是的TensorRT 3.0也是有RNN的API,也就是說我們可以在裡面做RNN的推斷(Inference)。

最典型的應用是圖片的分類,這也是最經典的,實際上也是深度學習目前解決的比較好的一些問題。其他的例如,圖片的語義分割、目標檢測等都是以圖片分類網路為基礎進行改進的。目標檢測是比較典型的例子(如下圖),訓練(Training)是對已經打好框的圖片進行前向計算,得出的框和實際的框(ground truth)進行對比,然後再做後向更新,更新模型。真正做推斷(Inference)的時候,比如一個攝像頭,基本上要保證是實時的,也就是說起碼要保證每秒25-30幀的速度,鑒於實際應用可能是二三十路攝像頭同時進來的數據,這時候必須保證一塊卡做到實時處理,還是比較有挑戰性的工作。

總結一下推斷(Inference)和訓練(Training)的不同:

1. 推斷(Inference)的網路權值已經固定下來,無後向傳播過程,因此可以

1、模型固定,可以對計算圖進行優化

2、輸入輸出大小固定,可以做memory優化(注意:有一個概念是fine-tuning,即訓練好的模型繼續調優,只是在已有的模型做小的改動,本質上仍然是訓練(Training)的過程,TensorRT沒有fine-tuning

2. 推斷(Inference)的batch size要小很多,仍然是latency的問題,因為如果batch size很大,吞吐可以達到很大,比如每秒可以處理1024個batch,500毫秒處理完,吞吐可以達到2048,可以很好地利用GPU;但是推斷(Inference)不能做500毫秒處理,可以是8或者16,吞吐降低,沒有辦法很好地利用GPU.

3. 推斷(Inference)可以使用低精度的技術,訓練的時候因為要保證前後向傳播,每次梯度的更新是很微小的,這個時候需要相對較高的精度,一般來說需要float型,如FP32,32位的浮點型來處理數據,但是在推斷(Inference)的時候,對精度的要求沒有那麼高,很多研究表明可以用低精度,如半長(16)的float型,即FP16,也可以用8位的整型(INT8)來做推斷(Inference),研究結果表明沒有特別大的精度損失,尤其對CNN。更有甚者,對Binary(二進位)的使用也處在研究過程中,即權值只有0和1。目前FP16和INT8的研究使用相對來說比較成熟。低精度計算的好處是一方面可以減少計算量,原來計算32位的單元處理FP16的時候,理論上可以達到兩倍的速度,處理INT8的時候理論上可以達到四倍的速度。當然會引入一些其他額外的操作,後面的講解中會詳細介紹FP18和INT8;另一方面是模型需要的空間減少,不管是權值的存儲還是中間值的存儲,應用更低的精度,模型大小會相應減小。

下圖展示的是TensorRT的效果,當然這是一個比較極端的例子,因為該例中使用的是最先進的GPU卡V100,V100添加了專門針對深度學習優化的TensorCore,TensorCore可以完成4×4矩陣的半精度乘法,也就是可以完成一個4×4的FP16矩陣和另外一個4×4的FP16矩陣相乘,當然可以再加一個矩陣(FP16 或FP32),得到一個FP32或者FP16的矩陣的過程。TensorCore在V100上理論峰值可以達到120 Tflops.(開個玩笑,電影終結者中整個天網的計算能力相當於兩塊V100)。回到圖中,先看一下如果只是用CPU來做推斷(Inference),首先它的吞吐只能達到140,也就是說每秒只能處理140張圖片,同時整個處理過程需要有14ms的延遲,也就是說用戶提交請求後,推斷(Inference)階段最快需要14ms才能返回結果;如果使用V100,在TensorFlow中去做推斷(Inference),大概是6.67ms的延時,但是吞吐只能達到305;如果使用V100加TensorRT,在保證延遲不變的情況下,吞吐可以提高15倍,高達5700張圖片每秒,這個差別是很大的。十幾倍的吞吐的提升實際上是在保證延遲的情況下成本的縮減 。

回到TensorRT的主題,之前大家普遍存在的一個疑問是在訓練過程中可以使用不同的框架,為什麼推斷(Inference)不能用各種框架,比如TensorFlow等。當然是可以用的,但是問題是靈活性和性能是一種trade-off的關係,這是在做深度學習或訓練過程中經常會遇到的一個問題。比如像TensorFlow的設計初衷是為各種各樣的操作來做準備的,在早期的框架,例如Caffe中很多前後處理並不在框架裡面完成,而是通過額外的程序或腳本處理,但是TensorFlow支持將所有的操作放入框架之中來完成,它提供了操作(Operation)級別的支持,使得靈活性大大提高,但是靈活性可能是以犧牲效率為代價的。TensorFlow在實現神經網路的過程中可以選擇各種各樣的高級庫,如用nn來搭建,tf.nn中的convolution中可以加一個卷積,可以用slim來實現卷積,不同的卷積實現效果不同,但是其對計算圖和GPU都沒有做優化,甚至在中間卷積演算法的選擇上也沒有做優化,而TensorRT在這方面做了很多工作。

在講TensorRT做了哪些優化之前, 想介紹一下TensorRT的流程, 首先輸入是一個預先訓練好的FP32的模型和網路,將模型通過parser等方式輸入到TensorRT中,TensorRT可以生成一個Serialization,也就是說將輸入串流到內存或文件中,形成一個優化好的engine,執行的時候可以調取它來執行推斷(Inference)。

如上圖所示TensorRT整個過程可以分三個步驟,即模型的解析(Parser),Engine優化和執行(Execution)。暫時拋開TensorRT,如果讓大家從頭寫一個深度學習模型的前向過程,具體過程應該是

首先實現NN的layer,如卷積的實現,pooling的實現。

管理memory,數據在各層之間如何流動。

推斷(Inference)的engine來調用各層的實現。

以上三個步驟在TendorRT都已經實現好了,用戶需要做的是如何將網路輸入到TensorRT中。目前TensorRT支持兩種輸入方式:

一種是Parser的方式,即模型解析器,輸入一個caffe的模型,可以解析出其中的網路層及網路層之間的連接關係,然後將其輸入到TensorRT中,但是TensorRT是如何知道這些連接關係呢?答案是API。

API介面可以添加一個convolution或pooling。而Parser是解析模型文件,比如TensorFlow轉換成的uff,或者是caffe的模型,再用API添加到TensorRT中,構建好網路。構建好後就可以做優化。

a、考慮到一個情況,如果有一個網路層不支持,這個有可能,TensorRT只支持主流的操作,比如說一個神經網路專家開發了一個新的網路層,新型卷積和以前的卷積都不一樣,TensorRT是不知道是做什麼的。比如說最常見的檢測網路,有一些網路層也是不支持的,這個時候涉及到customer layer的功能,即用戶自定義層,構建用戶自定義層需要告訴TensorRT該層的連接關係和實現方式,這樣TensorRT才能去做。

b、目前API支持兩種介面實現方式,一種是C++,另一種是Python,Python介面可能在一些快速實現上比較方便一些。

c、Parser目前有三個,一個是caffe Parser,這個是最古老的也是支持最完善的;另一個是uff,這個是NV定義的網路模型的一種文件結構,現在TensorFlow可以直接轉成uff;另外下一個版本3.5或4.0會支持的onnx,是Facebook主導的開源的可交換的各個框架都可以輸出的,有點類似於文檔編輯中的word格式或AutoCAD中CAD的格式,雖然是由一個公司提出,但是有希望成為一個標準,各個APP去支持這個標準。像pytorch和caffe 2都是支持這個格式的,這個目前只在NGC (NVDIA GPU Cloud)上支持,但是下一個版本發行都會支持。如果某個公司新推出一個特別火的框架不支持怎麼辦,仍然可以採用API的方式,一層一層的添加進去,告訴TensorRT連接關係,這也是OK的。

模型解析後,engine會進行優化,具體的優化稍後會介紹。得到優化好的engine可以序列化到內存(buffer)或文件(file),讀的時候需要反序列化,將其變成engine以供使用。然後在執行的時候創建context,主要是分配預先的資源,engine加context就可以做推斷(Inference)。

以上是TensorRT的整個過程,大家在疑惑TensorRT是否支持TensorFlow,首先大家寫的網路計算層可能都是支持的,但是有些網路層可能不支持,在不支持的情況下可以用customer layer的方式添加進去,但是有時候為了使用方便,可能沒辦法一層一層的去添加,需要用模型文件形式,這個取決於Parser是否完全支持。相對而言,大家在框架有過比較後會發現,caffe這個框架的特點是非常不靈活,如果要添加一個新的網路層,需要修改源代碼;TensorFlow的優點卻是非常的靈活。

剛才講到TensorRT所做的優化,總結下來主要有這麼幾點:

第一,也是最重要的,它把一些網路層進行了合併。大家如果了解GPU的話會知道,在GPU上跑的函數叫Kernel,TensorRT是存在Kernel的調用的。在絕大部分框架中,比如一個卷積層、一個偏置層和一個reload層,這三層是需要調用三次cuDNN對應的API,但實際上這三層的實現完全是可以合併到一起的,TensorRT會對一些可以合併網路進行合併;再比如說,目前的網路一方面越來越深,另一方面越來越寬,可能並行做若干個相同大小的卷積,這些卷積計算其實也是可以合併到一起來做的。

第二,比如在concat這一層,比如說這邊計算出來一個1×3×24×24,另一邊計算出來1×5×24×24,concat到一起,變成一個1×8×24×24的矩陣,這個叫concat這層這其實是完全沒有必要的,因為TensorRT完全可以實現直接接到需要的地方,不用專門做concat的操作,所以這一層也可以取消掉。

第三,Kernel可以根據不同的batch size 大小和問題的複雜程度,去選擇最合適的演算法,TensorRT預先寫了很多GPU實現,有一個自動選擇的過程。

第四,不同的batch size會做tuning。

第五,不同的硬體如P4卡還是V100卡甚至是嵌入式設備的卡,TensorRT都會做優化,得到優化後的engine。

下圖是一個原始的GoogleNet的一部分,首先input後會有多個卷積,卷積完後有Bias和ReLU,結束後將結果concat(連接拼接)到一起,得到下一個input。

以上的整個過程可以做些什麼優化呢?首先是convolution, Bias和ReLU這三個操作可以合併成CBR,合併後的結果如下所示,其中包含四個1×1的CBR,一個3×3的CBR和一個5×5的CBR。

接下來可以繼續合併三個相連的1×1的CBR為一個大的1×1的CBR(如下圖),這個合併就可以更好地利用GPU。

繼而concat層可以消除掉,直接連接到下一層的next input(如下圖)。

另外還可以做並發(Concurrency),如下圖左半部分(max pool和1×1 CBR)與右半部分(大的1×1 CBR,3×3 CBR和5×5 CBR)彼此之間是相互獨立的兩條路徑,本質上是不相關的,可以在GPU上通過並發來做,來達到的優化的目標。

二、TensorRT高級特徵介紹

前面介紹了TesorRT的基礎,更多信息可以查詢官網,或者反饋給我。接下來和大家分享一些TensorRT比較高級的特徵,這塊主要針對有一定經驗或者做過一些線上部署的人。

1. 插件支持

首先TensorRT是支持插件(Plugin)的,或者前面提到的Customer layer的形式,也就是說我們在某些層TensorRT不支持的情況下,最主要是做一些檢測的操作的時候,很多層是該網路專門定義的,TensorRT沒有支持,需要通過Plugin的形式自己去實現。實現過程包括如下兩個步驟:

首先需要重載一個IPlugin的基類,生成自己的Plugin的實現,告訴GPU或TensorRT需要做什麼操作,要構建的Plugin是什麼樣子,其實就是類似於開發一個應用軟體的插件,需要在上面實現什麼功能。

其次要將插件添加到合適的位置,在這裡是要添加到網路里去。

注意,只有TensorRT 2.1和更高的版本支持插件的功能(該視頻講的時候的版本是3.0.2,支持插件功能)。

2. 低精度支持

低精度指的是之前所說過的FP16和INT8,其中FP16主要是Pascal P100和V100(tensor core)這兩張卡支持;而INT8主要針對的是 P4和P40這兩張卡,P4是專門針對線上做推斷(Inference)的小卡,和IPhone手機差不多大,75瓦的一張卡,功耗和性能非常好。

3. Python介面和更多的框架支持

TensorRT目前支持Python和C++的API,剛才也介紹了如何添加,Model importer(即Parser)主要支持Caffe和Uff,其他的框架可以通過API來添加,如果在Python中調用pyTouch的API,再通過TensorRT的API寫入TensorRT中,這就完成了一個網路的定義。

TensorRT去做推斷(Inference)的時候是不再需要框架的,用Caffe做推斷(Inference)需要Caffe這個框架,TensorRT把模型導進去後是不需要這個框架的,Caffe和TensorFlow可以通過Parser來導入,一開始就不需要安裝這個框架,給一個Caffe或TensorFlow模型,完全可以在TensorRT高效的跑起來。

三、用戶自定義層

使用插件創建用戶自定義層主要分為兩個步驟:

第一步是創建使用IPlugin介面創建用戶自定義層,IPlugin是TensorRT中預定義的C++抽象類,用戶需要定義具體實現了什麼。

第二步是將創建的用戶自定義層添加到網路中,如果是Caffe的模型,不支持這一層,將名字改成IPlugin是可以識別的,當然還需要一些額外的操作,說明這一層的操作是對應哪個Plugin的實現;而對於Uff是不支持Plugin的Parser,也就是說TensorFlow的模型中有一個Plugin的話,是不能從模型中識別出來的,這時候需要用到addPlugin()的方法去定義網路中Plugin的相關信息。

IPlugin介面中需要被重載的函數有以下幾類:

確定輸出:一個是通過int getNbOutput()得到output輸出的數目,即用戶所定義的一層有幾個輸出。另一個是通過Dims getOutputDimensions (int index, const Dims* inputs, int nbInputDims) 得到整個輸出的維度信息,大家可能不一定遇到有多個輸出,一般來講只有一個輸出,但是大家在做檢測網路的時候可能會遇到多個輸出,一個輸出是實際的檢測目標是什麼,另一個輸出是目標的數目,可能的過個輸出需要設定Dimension的大小。

層配置:通過void configure() 實現構建推斷(Inference) engine時模型中相應的參數大小等配置,configure()只是在構建的時候調用,這個階段確定的東西是在運行時作為插件參數來存儲、序列化/反序列化的。

資源管理:通過void Initialize()來進行資源的初始化,void terminate()來銷毀資源,甚至中間可能會有一些臨時變數,也可以使用這兩個函數進行初始化或銷毀。需要注意的是,void Initialize()和void terminate()是在整個運行時都被調用的,並不是做完一次推斷(Inference)就去調用terminate。相當於在線的一個服務,服務起的時候會調用void Initialize(),而服務止的時候調用void terminate(),但是服務會進進出出很多sample去做推斷(Inference)。

執行(Execution):void enqueue()來定義用戶層的操作

序列化和反序列化:這個過程是將層的參數寫入到二進位文件中,需要定義一些序列化的方法。通過size_t getSerializationSize()獲得序列大小,通過void serialize()將層的參數序列化到緩存中,通過PluginSample()從緩存中將層參數反序列化。需要注意的是,TensorRT沒有單獨的反序列化的API,因為不需要,在實習構造函數的時候就完成了反序列化的過程

從Caffe Parser添加Plugin:首先通過Parsernvinfer1::IPlugin* createPlugin()實現nvcaffeparser1::IPlugin 介面,然後傳遞工廠實例到ICaffeParser::parse(),Caffe的Parser才能識別

運行時創建插件:通過IPlugin* createPlugin()實現nvinfer1::IPlugin介面,傳遞工廠實例到IInferRuntime::deserializeCudaEngine()

四、用戶自定義層-YOLOv2實例

我們用一個例子YOLOv2來給大家講一下完整的流程:

準備:首先要準備 Darknet framework(https://github.com/pjreddie/darknet.git),它是一個非常小眾的cfg的形式,然後需要準備需要訓練的數據集(VOC 2007 & VOC 2012),測試的指令如下:

./darknet detector test cfg/voc.data cfg/yolo-voc-relu.cfg

backup/yolo-voc-relu_final.weights data/dog.jpg

模型轉換:如下圖所示,根據darknet的配置文件生成caffe的prototxt文件,注意使用ReLu而不是leaky-ReLu;另外darknet中存儲順序不同,首先存儲偏移;darknet的配置文件中padding的意義不同,pad = 1表示有padding,darknet中padding的大小是Kernel的大小除以2。

以下是darknet cuDNN和TensorRT FP32的性能對比,FP32是4.8ms,而Darknet是11.3ms。

五、低精度的推斷(Inference)

TensorRT通過使用Pascal GPU低精度的技術,實現高性能。以下是FP16和INT8兩種類型的性能對比。

1. FP16 推斷(Inference)

TensorRT支持高度自動化的FP16推斷(Inference),解析模型要將模型的的數據類型設置為DataType::kHALF,同時通過builder- >setHalf2Mode(true)指令將推斷(Inference)設置為FP16的模式。需要注意兩點,一點是FP16推斷(Inference)不需要額外的輸入,只需要輸入預先訓練好的FP32模型,另一點是目前只有Tesla P100/V100支持原生的FP16。

下圖展示了將模型從FP32轉換成FP16,並以FP16的形式存儲的過程:

2. INT8 推斷(Inference)

對於INT8 推斷(Inference),需要生成一個校準表來量化模型。接下來主要關注INT8推斷(Inference)的幾個方面,即:如何生成校準表,如何使用校準表,和INT8推斷(Inference)實例。

1、如何生成校準表?

校準表的生成需要輸入有代表性的數據集, 對於分類任務TensorRT建議輸入五百張到一千張有代表性的圖片,最好每個類都要包括。生成校準表分為兩步:第一步是將輸入的數據集轉換成batch文件;第二步是將轉換好的batch文件喂到TensorRT中來生成基於數據集的校準表,可以去統計每一層的情況。

2、如何使用校準表?

校準這個過程如果要跑一千次是很昂貴的,所以TensorRT支持將其存入文檔,後期使用可以從文檔載入,其中存儲和載入的功能通過兩個方法來支持,即writeCalibrationCache和readCalibrationCache。最簡單的實現是從write()和read()返回值,這樣就必須每次執行都做一次校準。如果想要存儲校準時間,需要實現用戶自定義的write/read方法,具體的實現可以參考TensorRT中的simpleINT8實例。

3、 INT8推斷(Inference)實例

通過下圖的實例可以發現,在YOLOv2實例中,使用TensorRT INT8做推斷(Inference)的性能可以達到2.34ms。

下圖展示了用ResNet50中FP32和INT8的性能對比,可以發現,對於P4卡,在bachsize是64的時候,INT8推斷(Inference)大概可以達到1720fps,相對於FP32有3.6倍的加速,這個是相當可觀的。

至於大家關心的精度問題對比(如下圖),INT8通過用5張圖,10張圖,50張圖去修正,精度相差基本上都是在百分之零點零幾,這個效果是非常好的。

最後總結一下TensorRT的優點:

TensorRT是一個高性能的深度學習推斷(Inference)的優化器和運行的引擎

TensorRT支持Plugin,對於不支持的層,用戶可以通過Plugin來支持自定義創建;

TensorRT使用低精度的技術獲得相對於FP32二到三倍的加速,用戶只需要通過相應的代碼來實現。

end


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 雲棲社區 的精彩文章:

理解卷積神經網路的利器:9篇重要的深度學習論文
AI系列免費講座:深度學習應用篇

TAG:雲棲社區 |