深度學習演算法優化系列十七 | TensorRT介紹,安裝及如何使用?
- 2020 年 3 月 6 日
- 筆記
1. 前言
由於前期OpenVINO的分享已經基本做完了,筆者也可以成功的在CPU和Intel神經棒上完整的部署一些工作了,因此開始來學習TensorRT啦。先聲明一下我使用的TensorRT版本是TensorRT-6.0.1.5 。
2. 介紹
TensorRT是NVIDIA推出的一個高性能的深度學習推理框架,可以讓深度學習模型在NVIDIA GPU上實現低延遲,高吞吐量的部署。TensorRT支援Caffe,TensorFlow,Mxnet,Pytorch等主流深度學習框架。TensorRT是一個C++庫,並且提供了C++ API和Python API,主要在NVIDIA GPU進行高性能的推理(Inference)加速。
Figure1. TensorRT是一個高性能的神經網路推理優化器和運行時引擎
當前,TensorRT6.0已經支援了很多深度學習框架如Caffe/TensorFlow/Pytorch/MxNet。對於Caffe和TensorFlow的網路模型解析,然後與TensorRT中對應的層進行一一映射,然後TensorRT可以針對NVIDIA的GPU進行優化並進行部署加速。不過,對於Caffe2,Pytorch,MxNet,Chainer,CNTK等深度學習框架訓練的模型都必須先轉為ONNX的通用深度學習模型,然後對ONNX模型做解析。另外TensorFlow/MxNet/MATLAB都已經將TensorRT集成到框架中去了。
ONNX是一種針對機器學習所設計的開放式的文件格式,用於存儲訓練好的模型。它使得不同的人工智慧框架(如Pytorch, MXNet)可以採用相同格式存儲模型數據並交互。ONNX的規範及程式碼主要由微軟,亞馬遜 ,Facebook 和 IBM 等公司共同開發,以開放源程式碼的方式託管在Github上。目前官方支援載入ONNX模型並進行推理的深度學習框架有:Caffe2, PyTorch, MXNet,ML.NET,TensorRT 和 Microsoft CNTK,並且 TensorFlow 也非官方的支援ONNX。—維基百科
TensorRT是一個可編程的推理加速器
ONNX/TensorFlow/Custom Framework等模型的工作方式如下:
ONNX Workflow V1
3. TensorRT支援的Layer
3.1 Caffe
這些是Caffe框架中支援的OP。
- BatchNormalization。
- BNLL。
- Clip。
- Concatenation。
- Convolution。
- Crop。
- Deconvolution。
- Dropout。
- ElementWise。
- ELU。
- InnerProduct。
- Input。
- LeakyReLU。
- LRN。
- Permute。
- Pooling。
- Power。
- Reduction。
- ReLU,TanH,和Sigmoid。
- Reshape。
- SoftMax。
- Scale。
3.2 TensorFlow
這些是TensorFlow中支援的OP。
- Add, Sub, Mul, Div, Minimum and Maximum。
- ArgMax。
- ArgMin。
- AvgPool。
- BiasAdd。
- Clip。
- ConcatV2。
- Const。
- Conv2d。
- ConvTranspose2D。
- DepthwiseConv2dNative。
- Elu。
- ExpandDims。
- FusedBatchNorm。
- Identity。
- LeakyReLU。
- MaxPool。
- Mean。
- Negative, Abs, Sqrt, Recip, Rsqrt, Pow, Exp and Log。
- Pad is supported if followed by one of these TensorFlow layers: Conv2D, DepthwiseConv2dNative, MaxPool, and AvgPool.
- Placeholder
- ReLU, TanH, and Sigmoid。
- Relu6。
- Reshape。
- ResizeBilinear, ResizeNearestNeighbor。
- Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Asinh, Acosh, Atanh, Ceil and Floor。
- Selu。
- Slice。
- SoftMax。
- Softplus。
- Softsign。
- Transpose。
3.3 ONNX
因為篇幅有限,就不列舉了,可以自己去看官方文檔。
除了上面列舉的層,如果我們的網路中有自定義的Layer,這個時候咋辦呢?TensorRT中有一個 Plugin 層,這個層提供了 API 可以由用戶自己定義TensorRT不支援的層。 如下圖所示:
TensorRT自定義層
4. 為什麼TensorRT能讓模型跑的快?
這一問題的答案就隱藏下面這張圖中:
TensorRT優化訓練好的神經網路模型以產生可部署的運行時推理引擎
從圖上可以看到,TensorRT主要做了下面幾件事,來提升模型的運行速度。
- TensorRT支援FP16和INT8的計算。我們知道深度學習在訓練的時候一般是應用32位或者16位數據,TensorRT在推理的時候可以降低模型參數的位寬來進行低精度推理,以達到加速推斷的目的。這在後面的文章中是重點內容,筆者經過一周的研究,大概明白了TensorRT INT8量化的一些細節,後面會逐漸和大家一起分享討論。
- TensorRT對於網路結構進行重構,把一些能夠合併的運算合併在了一起,針對GPU的特性做了優化。大家如果了解GPU的話會知道,在GPU上跑的函數叫Kernel,TensorRT是存在Kernel的調用的。在絕大部分框架中,比如一個卷積層、一個偏置層和一個reload層,這三層是需要調用三次cuDNN對應的API,但實際上這三層的實現完全是可以合併到一起的,TensorRT會對一些可以合併網路進行合併;再比如說,目前的網路一方面越來越深,另一方面越來越寬,可能並行做若干個相同大小的卷積,這些卷積計算其實也是可以合併到一起來做的。(加粗的話轉載自參考鏈接1)。
- 然後Concat層是可以去掉的,因為TensorRT完全可以實現直接接到需要的地方。
- Kernel Auto-Tuning:網路模型在推理計算時,是調用GPU的CUDA核進行計算的。TensorRT可以針對不同的演算法,不同的網路模型,不同的GPU平台,進行 CUDA核的調整,以保證當前模型在特定平台上以最優性能計算。
- Dynamic Tensor Memory 在每個tensor的使用期間,TensorRT會為其指定顯示記憶體,避免顯示記憶體重複申請,減少記憶體佔用和提高重複使用效率。
- 不同的硬體如P4卡還是V100卡甚至是嵌入式設備的卡,TensorRT都會做優化,得到優化後的engine。
下面是一個原始的Inception Block,首先input
後會有多個卷積,卷積完後有Bias
和ReLU
,結束後將結果concat
到一起,得到下一個input
。我們一起來看一下使用TensorRT後,這個原始的計算圖會被優化成了什麼樣子。
首先,在沒有經過優化的時候Inception Block如Figure1所示:
Figure1,原始的Inception Block
第二步,對於網路結構進行垂直整合,即將目前主流神經網路的conv、BN、Relu三個層融合為了一個層,所謂CBR,合併後就成了Figure2中的結構。
Figure2,垂直Fuse
第三步,TensorRT還可以對網路做水平組合,水平組合是指將輸入為相同張量和執行相同操作的層融合一起,下面的Figure3即是將三個相連的的CBR為一個大的的CBR。
Figure3,水平Fuse
最後,對於concat層,將contact層的輸入直接送入下面的操作中,不用單獨進行concat後在輸入計算,相當於減少了一次傳輸吞吐,然後就獲得了如Figure4所示的最終計算圖。
Figure4,最終計算圖
除了計算圖和底層優化,最重要的就是低精度推理了,這個後面會細講的,我們先來看一下使用了INT8低精度模式進行推理的結果展示:包括精度和速度。來自NIVIDA提供的PPT。
TensorRT INT8量化在主流網路上的精度對比
TensorRT INT8量化在主流網路上的速度對比
5. TensorRT的安裝
我是用的是TensorRT-6.0.1.5,由於我在Windows10上使用的,所以就去TensorRT官網https://developer.nvidia.com/tensorrt
下載TensorRT6的Windows10安裝包,這裡我選擇了Cuda9.0的包,這也就意味著我必須要安裝Cudnn7.5及其以上,我這裡選擇了Cudnn7.6進行了安裝。關於Cuda和Cudnn的安裝就不說了,非常簡單。安裝TensorRT具體有以下步驟:
- 下載TensorRT-6.0.1.5安裝包並解壓。
- 將lib文件夾下面的dll(如下圖所示,)都拷貝到cuda文件夾下的bin目錄下,我是默認安裝了cuda9.0,所以我的cuda下的bin目錄的路徑是:
C:Program FilesNVIDIA GPU Computing ToolkitCUDAv9.0bin
。
TensorRT lib目錄
- 然後這就安裝成功了,為了驗證你有沒有成功,請使用VSCode 2015/2017打開
sample_mnist.sln
解決法案,我的目錄是:F:TensorRT-6.0.1.5samplessampleMNIST
。 - 打開VS工程屬性,將目標平台版本改成8.1以及平台工具及改成Visual Studio 2015(v140)。然後用Release編譯,這樣你就會在
F:TensorRT-6.0.1.5bin
下面生成一個sample_mnist.exe
了。
sampleMNIST工程屬性
- 進入
F:TensorRT-6.0.1.5datamnist
文件夾,打開裡面的README.md
,下載MNIST數據集到這個文件夾下並解壓,實際上只用下載train-images-idx3-ubyte.gz
和train-labels-idx1-ubyte.gz
就可以了。然後執行generate_pgms.py
這個python文件,就會在這個文件夾下獲得0-9.pgm
10張圖片,數字分別是0-9
。 - 打開命令行測試一下上面的demo。命令如下:
>F:TensorRT-6.0.1.5binsample_mnist.exe --datadir=F:TensorRT-6.0.1.5datamnist
獲得的結果如下:
2.pnm得到的預測結果位3,預測正確
6. TensorRT使用流程
這裡先看一下TensorRT最簡單的使用流程,後面複雜的應用部署也是以這個為基礎的,在使用TensorRT的過程中需要提供以下文件(以Caffe為例):
- 模型文件(*.prototxt)
- 權重文件(*.caffemodel)
- 標籤文件(把數據映射成name字元串)。
TensorRT的使用包括兩個階段,Build
和Deployment
。
6.1 Build
Build階段主要完成模型轉換(從Caffe/TensorFlow/Onnx->TensorRT),在轉換階段會完成前面所述優化過程中的計算圖融合,精度校準。這一步的輸出是一個針對特定GPU平台和網路模型的優化過的TensorRT模型。這個TensorRT模型可以序列化的存儲到磁碟或者記憶體中。存儲到磁碟中的文件叫plan file
,這個過程可以用下圖來表示:
Build
下面的程式碼展示了一個簡單的Build過程。注意這裡的程式碼注釋是附錄的第2個鏈接提供的,TensorRT版本是2.0,。然後我觀察了下TensorRT6.0的程式碼,雖然介面有一些變化但Build->Deployment這個流程是通用,所以就轉載它的程式碼解釋來說明問題了。
//創建一個builder IBuilder* builder = createInferBuilder(gLogger); // parse the caffe model to populate the network, then set the outputs // 創建一個network對象,不過這時network對象只是一個空架子 INetworkDefinition* network = builder->createNetwork(); //tensorRT提供一個高級別的API:CaffeParser,用於解析Caffe模型 //parser.parse函數接受的參數就是上面提到的文件,和network對象 //這一步之後network對象裡面的參數才被填充,才具有實際的意義 CaffeParser parser; auto blob_name_to_tensor = parser.parse(「deploy.prototxt」, trained_file.c_str(), *network, DataType::kFLOAT); // 標記輸出 tensors // specify which tensors are outputs network->markOutput(*blob_name_to_tensor->find("prob")); // Build the engine // 設置batchsize和工作空間,然後創建inference engine builder->setMaxBatchSize(1); builder->setMaxWorkspaceSize(1 << 30); //調用buildCudaEngine時才會進行前述的層間融合或精度校準優化方式 ICudaEngine* engine = builder->buildCudaEngine(*network);
在上面的程式碼中使用了一個高級API:CaffeParser,直接讀取 caffe的模型文件,就可以解析,也就是填充network對象。解析的過程也可以直接使用一些低級別的C++API,例如:
ITensor* in = network->addInput(「input」, DataType::kFloat, Dims3{…}); IPoolingLayer* pool = network->addPooling(in, PoolingType::kMAX, …);
解析了Caffe的模型之後,必須要指定輸出Tensor,設置batch大小和設置工作空間。其中設置工作空間是進行上面所述的計算圖融合優化的必須步驟。
6.2 Deployment
Deploy階段就是完成前向推理過程了,上面提到的Kernel Auto-Tuning 和 Dynamic Tensor Memory應該是也是在這個步驟中完成的。這裡將Build過程中獲得的plan文件首先反序列化,並創建一個 runtime engine,然後就可以輸入數據,然後輸出分類向量結果或檢測結果。這個過程可以用下圖來表示:
Deployment
下面的程式碼展示了一個簡單的Deploy過程,這裡沒有包含反序列化和測試時的batch流的獲取。可以看到程式碼還是相當複雜的,特別是包含了一些CUDA編程的知識。
// The execution context is responsible for launching the // compute kernels 創建上下文環境 context,用於啟動kernel IExecutionContext *context = engine->createExecutionContext(); // In order to bind the buffers, we need to know the names of the // input and output tensors. //獲取輸入,輸出tensor索引 int inputIndex = engine->getBindingIndex(INPUT_LAYER_NAME), int outputIndex = engine->getBindingIndex(OUTPUT_LAYER_NAME); //申請GPU顯示記憶體 // Allocate GPU memory for Input / Output data void* buffers = malloc(engine->getNbBindings() * sizeof(void*)); cudaMalloc(&buffers[inputIndex], batchSize * size_of_single_input); cudaMalloc(&buffers[outputIndex], batchSize * size_of_single_output); //使用cuda 流來管理並行計算 // Use CUDA streams to manage the concurrency of copying and executing cudaStream_t stream; cudaStreamCreate(&stream); //從記憶體到顯示記憶體,input是讀入記憶體中的數據;buffers[inputIndex]是顯示記憶體上的存儲區域,用於存放輸入數據 // Copy Input Data to the GPU cudaMemcpyAsync(buffers[inputIndex], input, batchSize * size_of_single_input, cudaMemcpyHostToDevice, stream); //啟動cuda核計算 // Launch an instance of the GIE compute kernel context.enqueue(batchSize, buffers, stream, nullptr); //從顯示記憶體到記憶體,buffers[outputIndex]是顯示記憶體中的存儲區,存放模型輸出;output是記憶體中的數據 // Copy Output Data to the Host cudaMemcpyAsync(output, buffers[outputIndex], batchSize * size_of_single_output, cudaMemcpyDeviceToHost, stream)); //如果使用了多個cuda流,需要同步 // It is possible to have multiple instances of the code above // in flight on the GPU in different streams. // The host can then sync on a given stream and use the results cudaStreamSynchronize(stream);
6.3 TensorRT 6.0 的Deployment
隨著TensorRT的版本迭代,前向推理的程式碼變成越來越簡單,基本上不需要我們操心了,我們來感受一下Mnist數字識別網路的推理程式碼。
bool SampleMNIST::infer() { // Create RAII buffer manager object samplesCommon::BufferManager buffers(mEngine, mParams.batchSize); auto context = SampleUniquePtr<nvinfer1::IExecutionContext>(mEngine->createExecutionContext()); if (!context) { return false; } // Pick a random digit to try to infer srand(time(NULL)); const int digit = rand() % 10; // Read the input data into the managed buffers // There should be just 1 input tensor assert(mParams.inputTensorNames.size() == 1); if (!processInput(buffers, mParams.inputTensorNames[0], digit)) { return false; } // Create CUDA stream for the execution of this inference. cudaStream_t stream; CHECK(cudaStreamCreate(&stream)); // Asynchronously copy data from host input buffers to device input buffers buffers.copyInputToDeviceAsync(stream); // Asynchronously enqueue the inference work if (!context->enqueue(mParams.batchSize, buffers.getDeviceBindings().data(), stream, nullptr)) { return false; } // Asynchronously copy data from device output buffers to host output buffers buffers.copyOutputToHostAsync(stream); // Wait for the work in the stream to complete cudaStreamSynchronize(stream); // Release stream cudaStreamDestroy(stream); // Check and print the output of the inference // There should be just one output tensor assert(mParams.outputTensorNames.size() == 1); bool outputCorrect = verifyOutput(buffers, mParams.outputTensorNames[0], digit); return outputCorrect; }
7. 使用了TensorRT的優化方式效果
使用tensorRT與使用CPU相比,獲得了40倍的加速,與使用TensorFlow在GPU上推理相比,獲得了18倍的加速。
8. 總結
這篇是我的第一篇講解TensorRT的文章,主要介紹了TensorRT,以及如何安裝和使用TensorRT,之後會用例子來更詳細的講解。謝謝大家閱讀到這裡啦。
9. 參考
- https://blog.csdn.net/qq_33869371/article/details/87929419
- https://arleyzhang.github.io/articles/7f4b25ce/
- http://on-demand.gputechconf.com/gtc/2017/presentation/s7310-8-bit-inference-with-tensorrt.pdf