• <xmp id="om0om">
  • <table id="om0om"><noscript id="om0om"></noscript></table>
  • 人工智能/深度學習

    使用 NVIDIA TensorRT 加速深度學習推理(更新)

    這篇文章于 2021 年 7 月 20 日更新,以反映 NVIDIA TensorRT 8 . 0 的更新。

    NVIDIA TensorRT 是一個用于深度學習推理的 SDK 。 TensorRT 提供了 API 和解析器,可以從所有主要的深度學習框架中導入經過訓練的模型。然后,它生成可在數據中心以及汽車和嵌入式環境中部署的優化運行時引擎。

    這篇文章簡單介紹了如何使用 TensorRT 。您將學習如何在 GPU 上部署深度學習應用程序,從而提高吞吐量并減少推理過程中的延遲。它使用 C ++示例來將您通過將 PyTorch 模型轉換成 ONX 模型并將其導入 TensorRT ,應用優化,并為數據中心環境生成高性能運行時引擎。

    TensorRT 同時支持 C ++和 Python ;如果您使用其中任何一個,此工作流討論可能會很有用。如果您喜歡使用 Python ,請參閱 TensorRT 文檔中的 使用 pythonapi

    深度學習應用于廣泛的應用領域,如自然語言處理、推薦系統、圖像和視頻分析。隨著越來越多的應用程序在生產中使用深度學習,對準確性和性能的要求導致了模型復雜性和規模的強勁增長。

    安全關鍵型應用程序(如 automotive )對深度學習模型預期的吞吐量和延遲提出了嚴格要求。這同樣適用于一些消費者應用程序,包括推薦系統。

    TensorRT 旨在幫助部署這些用例的深度學習。通過對每個主要框架的支持, TensorRT 通過強大的優化、降低精度的使用和高效的內存使用,幫助以低延遲處理大量數據。

    示例應用程序使用來自 Kaggle 的 腦 MRI 分割數據 的輸入數據來執行推斷。

    要求

    為了理解這篇文章,您需要一臺具有 CUDA 功能的 NVIDIA 計算機,或者一個具有 GPU 的云實例和一個 TensorRT 安裝。在 Linux 上,最容易入門的地方是從 GPU 容器注冊表(在 NGC 上)下載 GPU – 加速的 PyTorch 容器 和 TensorRT 集成。該鏈接將具有容器的更新版本,但為了確保本教程正常工作,我們指定了用于此文章的版本:

    # Pull PyTorch container
    docker pull nvcr.io/nvidia/pytorch:20.07-py3

    此容器具有以下規格:

    • Ubuntu 18 . 04 版
    • Python 3 . 6 . 10 版
    • CUDA 11 . 0 版
    • 火炬 1 . 6 . 0a
    • TensorRT 第 7 . 1 . 3 條

    因為在本演練中使用了 TensorRT 8 ,所以必須在容器中對其進行升級。下一步是下載 TensorRT 8 的 .deb 包( CUDA 11 . 0 , Ubuntu 18 . 04 ),并安裝以下要求:

    # Export absolute path to directory hosting TRT8.deb
    export TRT_DEB_DIR_PATH=$HOME/trt_release? # Change this path to where you’re keeping your .deb file
     ?
    # Run container
    docker run --rm --gpus all -ti --volume $TRT_DEB_DIR_PATH:/workspace/trt_release --net host nvcr.io/nvidia/pytorch:20.07-py3
     ?
    # Update TensorRT version to 8
    dpkg -i nv-tensorrt-repo-ubuntu1804-cuda11.0-trt8.0.0.3-ea-20210423_1-1_amd64.deb
    apt-key add /var/nv-tensorrt-repo-ubuntu1804-cuda11.0-trt8.0.0.3-ea-20210423/7fa2af80.pub
     ?
    apt-get update
    apt-get install -y libnvinfer8 libnvinfer-plugin8 libnvparsers8 libnvonnxparsers8
    apt-get install -y libnvinfer-bin libnvinfer-dev libnvinfer-plugin-dev libnvparsers-dev
    apt-get install -y tensorrt
     ?
    # Verify TRT 8.0.0 installation
    dpkg -l | grep TensorRT 

    簡單 TensorRT 示例

    以下是此示例應用程序的四個步驟:

    1. 將預訓練圖像分割 PyTorch 模型轉化為 ONNX 。
    2. 將 ONNX 模型導入 TensorRT 。
    3. 應用優化并生成引擎。
    4. 對 GPU 進行推斷。

    導入 ONNX 模型包括從磁盤上保存的文件加載它,并將其從本機框架或格式轉換為 TensorRT 網絡。 ONNX 是表示深度學習模型的標準,使它們能夠在框架之間傳輸。

    許多框架,如 CAFE2 , ChanER , CNTK , PaddlePaddle , PyTorch 和 MXNET 支持 ONNX 格式。接下來,根據輸入模型、目標平臺和其他指定的配置參數,構建優化的 TensorRT 引擎。最后一步是向 TensorRT 引擎提供輸入數據以執行推理。

    應用程序在 TensorRT 中使用以下組件:

    • ONNX 解析器: 將經過 PyTorch 訓練的模型轉換為 ONNX 格式作為輸入,并在 TensorRT 中填充網絡對象。
    • 建設者: 接受 TensorRT 中的網絡并生成針對目標平臺優化的引擎。
    • Engine: 獲取輸入數據,執行推斷,并發出推斷輸出。
    • Logger: 與生成器和引擎關聯,以在構建和推理階段捕獲錯誤、警告和其他信息。

    將預訓練圖像分割 PyTorch 模型轉化為 ONNX

    從 NGC 注冊表成功安裝 PyTorch 容器 并用 TensorRT 8 . 0 升級后,運行以下命令下載運行此示例應用程序所需的所有內容(示例代碼、測試輸入數據和參考輸出)。然后,更新依賴項并用提供的 makefile 編譯應用程序。

    >> sudo apt-get install libprotobuf-dev protobuf-compiler # protobuf is a prerequisite library
    >> git clone --recursive https://github.com/onnx/onnx.git # Pull the ONNX repository from GitHub?
    >> cd onnx
    >> mkdir build && cd build?
    >> cmake .. # Compile and install ONNX
    >> make # Use the ‘-j’ option for parallel jobs, for example, ‘make -j $(nproc)’?
    >> make install?
    >> cd ../..
    >> git clone https://github.com/parallel-forall/code-samples.git
    >> cd code-samples/posts/TensorRT-introduction
    Modify $TRT_INSTALL_DIR in the Makefile.
    >> make clean && make # Compile the TensorRT C++ code
    >> cd ..
    >> wget https://developer.download.nvidia.com/devblogs/speeding-up-unet.7z // Get the ONNX model and test the data
    >> sudo apt install p7zip-full
    >> 7z x speeding-up-unet.7z # Unpack the model data into the unet folder    
    >> cd unet
    >> python create_network.py #Inside the unet folder, it creates the unet.onnx file

    將 PyTorch 訓練的 UNet 模型轉換為 ONNX ,如下面的代碼示例所示:

    import torch
    from torch.autograd import Variable
    import torch.onnx as torch_onnx
    import onnx
    def main():
    ??? input_shape = (3, 256, 256)
    ??? model_onnx_path = "unet.onnx"
    ??? dummy_input = Variable(torch.randn(1, *input_shape))
    ??? model = torch.hub.load('mateuszbuda/brain-segmentation-pytorch', 'unet',
    ????? in_channels=3, out_channels=1, init_features=32, pretrained=True)
    ??? model.train(False)
    ??? 
    ??? inputs = ['input.1']
    ??? outputs = ['186']
    ??? dynamic_axes = {'input.1': {0: 'batch'}, '186':{0:'batch'}}
    ??? out = torch.onnx.export(model, dummy_input, model_onnx_path, input_names=inputs, output_names=outputs, dynamic_axes=dynamic_axes)
    
    if __name__=='__main__':
    ??? main() 

    接下來,準備輸入數據進行推斷。從 Kaggle 目錄下載所有圖像。將文件名中沒有\ u 掩碼的任意三個圖像和 brain-segmentation-pytorch 存儲庫中的 utils . py 文件復制到/ unet 目錄。準備三張圖片作為本文后面的輸入數據。要準備輸入\ u 0 . pb 和輸出\ u 0 . pb 文件以供以后使用,請運行以下代碼示例:

    import torch 
    import argparse
    import numpy as np
    from torchvision import transforms? ? ? ? ? ? ? ? ??? 
    from skimage.io import imread
    from onnx import numpy_helper
    from utils import normalize_volume
    def main(args):
    ??? model = torch.hub.load('mateuszbuda/brain-segmentation-pytorch', 'unet',
    ????? in_channels=3, out_channels=1, init_features=32, pretrained=True)
    ??? model.train(False)
    ??? 
    ??? filename = args.input_image
    ??? input_image = imread(filename)
    ??? input_image = normalize_volume(input_image)
    ??? input_image = np.asarray(input_image, dtype='float32')
    ??? 
    ??? preprocess = transforms.Compose([
    ????? transforms.ToTensor(),
    ??? ])
    ??? input_tensor = preprocess(input_image)
    ??? input_batch = input_tensor.unsqueeze(0)
    ??? 
    ??? tensor1 = numpy_helper.from_array(input_batch.numpy())
    ??? with open(args.input_tensor, 'wb') as f:
    ??????? f.write(tensor1.SerializeToString())
    ??? if torch.cuda.is_available():
    ??????? input_batch = input_batch.to('cuda')
    ??????? model = model.to('cuda')
    ??? with torch.no_grad():
    ??????? output = model(input_batch)
    ??? 
    ??? tensor = numpy_helper.from_array(output[0].cpu().numpy())
    ??? with open(args.output_tensor, 'wb') as f:
    ??????? f.write(tensor.SerializeToString())
    if __name__=='__main__':
    ??? parser = argparse.ArgumentParser()
    ??? parser.add_argument('--input_image', type=str)
    ??? parser.add_argument('--input_tensor', type=str, default='input_0.pb')
    ??? parser.add_argument('--output_tensor', type=str, default='output_0.pb')
    ??? args=parser.parse_args()
    ??? main(args) 

    要生成用于推斷的已處理輸入數據,請運行以下命令:

    >> pip install medpy #dependency for utils.py file
    >> mkdir test_data_set_0
    >> mkdir test_data_set_1
    >> mkdir test_data_set_2
    >> python prepareData.py --input_image your_image1 --input_tensor test_data_set_0/input_0.pb --output_tensor test_data_set_0/output_0.pb ? # This creates input_0.pb and output_0.pb
    >> python prepareData.py --input_image your_image2 --input_tensor test_data_set_1/input_0.pb --output_tensor test_data_set_1/output_0.pb ? # This creates input_0.pb and output_0.pb
    >> python prepareData.py --input_image your_image3 --input_tensor test_data_set_2/input_0.pb --output_tensor test_data_set_2/output_0.pb ? # This creates input_0.pb and output_0.pb 

    就這樣,您已經準備好輸入數據來執行推斷。

    將 ONNX 模型導入 TensorRT ,生成引擎,進行推理

    使用經過訓練的模型和作為輸入傳遞的輸入數據運行示例應用程序。數據作為 ONNX protobuf 文件提供。示例應用程序將從 TensorRT 生成的輸出與同一文件夾中 ONNX . pb 文件中可用的參考值進行比較,并在提示符處匯總結果。

    導入 UNet ONNX 模型并生成引擎可能需要幾秒鐘的時間。它還生成便攜式灰度圖( PGM )格式的輸出圖像,作為 output . PGM 。

     >> cd to code-samples/posts/TensorRT-introduction-updated
     >> ./simpleOnnx path/to/unet/unet.onnx fp32 path/to/unet/test_data_set_0/input_0.pb # The sample application expects output reference values in path/to/unet/test_data_set_0/output_0.pb
     ...
     ...
     : --------------- Timing Runner: Conv_40 + Relu_41 (CaskConvolution)
     : Conv_40 + Relu_41 Set Tactic Name: volta_scudnn_128x128_relu_exp_medium_nhwc_tn_v1 Tactic: 861694390046228376
     : Tactic: 861694390046228376 Time: 0.237568
     ...
     : Conv_40 + Relu_41 Set Tactic Name: volta_scudnn_128x128_relu_exp_large_nhwc_tn_v1 Tactic: -3853827649136781465
     : Tactic: -3853827649136781465 Time: 0.237568
     : Conv_40 + Relu_41 Set Tactic Name: volta_scudnn_128x64_sliced1x2_ldg4_relu_exp_large_nhwc_tn_v1 Tactic: -3263369460438823196
     : Tactic: -3263369460438823196 Time: 0.126976
     : Conv_40 + Relu_41 Set Tactic Name: volta_scudnn_128x32_sliced1x4_ldg4_relu_exp_medium_nhwc_tn_v1 Tactic: -423878181466897819
     : Tactic: -423878181466897819 Time: 0.131072
     : Fastest Tactic: -3263369460438823196 Time: 0.126976
     : >>>>>>>>>>>>>>> Chose Runner Type: CaskConvolution Tactic: -3263369460438823196
     ...
     ...
     INFO: [MemUsageChange] Init cuDNN: CPU +1, GPU +8, now: CPU 1148, GPU 1959 (MiB)
     : Total per-runner device memory is 79243264
     : Total per-runner host memory is 13840
     : Allocated activation device memory of size 1459617792
     Inference batch size 1 average over 10 runs is 2.21147ms
     Verification: OK
     INFO: [MemUsageChange] Init cuBLAS/cuBLASLt: CPU +0, GPU +0, now: CPU 1149, GPU 3333 (MiB) 

    就是這樣,您有一個應用程序,它是用 TensorRT 優化的,并且運行在您的 GPU 上。圖 2 顯示了一個示例測試用例的輸出。

    下面是在前面的示例應用程序中使用的幾個關鍵代碼示例。

    下面代碼示例中的 main 函數首先聲明一個 CUDA 引擎來保存網絡定義和經過訓練的參數。引擎是在 SimpleOnnx::createEngine 函數中生成的,該函數將 ONNX 模型的路徑作為輸入。

     // Declare the CUDA engine
     SampleUniquePtr<nvinfer1::ICudaEngine> mEngine{nullptr};
     ...
     // Create the CUDA engine
     mEngine = SampleUniquePtr<nvinfer1::ICudaEngine>   (builder->buildEngineWithConfig(*network, *config));

    SimpleOnnx::buildEngine 函數解析 ONNX 模型并將其保存在 network 對象中。要處理 U-Net 模型的輸入圖像和形狀張量的動態輸入維度,必須從 建設者 類創建優化配置文件,如下面的代碼示例所示。

    優化配置文件 允許您設置配置文件的最佳輸入、最小和最大尺寸。構建器選擇內核,該內核將導致輸入張量維度的最低運行時間,并且對最小和最大維度范圍內的所有輸入張量維度都有效。它還將網絡對象轉換為 TensorRT 引擎。

    下面代碼示例中的 setMaxBatchSize 函數用于指定 TensorRT 引擎期望的最大批處理大小。 setMaxWorkspaceSize 函數允許您在引擎構建階段增加 GPU 內存占用。

     bool SimpleOnnx::createEngine(const SampleUniquePtr<nvinfer1::IBuilder>& builder)
     {
     ??? // Create a network using the parser.
     ??? const auto explicitBatch = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
     ??? auto network = SampleUniquePtr<nvinfer1::INetworkDefinition>(builder->createNetworkV2(explicitBatch));
     ??? ...
     ??? auto parser= SampleUniquePtr<nvonnxparser::IParser>(nvonnxparser::createParser(*network, gLogger));
     ??? auto parsed = parser->parseFromFile(mParams.onnxFilePath.c_str(), static_cast<int>(nvinfer1::ILogger::Severity::kINFO));
     ??? auto config = SampleUniquePtr<nvinfer1::IBuilderConfig>(builder->createBuilderConfig());
     ??? 
     ??? auto profile = builder->createOptimizationProfile();
     ??? profile->setDimensions("input.1", OptProfileSelector::kMIN, Dims4{1, 3, 256, 256});
     ??? profile->setDimensions("input.1", OptProfileSelector::kOPT, Dims4{1, 3, 256, 256});
     ??? profile->setDimensions("input.1", OptProfileSelector::kMAX, Dims4{32, 3, 256, 256});
     ??? config->addOptimizationProfile(profile);
     ??? ...
     ??? // Setup model precision.
     ??? if (mParams.fp16)
     ??? {
     ??????? config->setFlag(BuilderFlag::kFP16);
     ??? }
     ??? // Build the engine.
     ??? mEngine = SampleUniquePtr<nvinfer1::ICudaEngine>(builder->buildEngineWithConfig(*network, *config));
     ??? ...
     ??? return true;
     } 

    創建引擎之后,創建一個 執行上下文 來保存在推斷過程中生成的中間激活值。下面的代碼演示如何創建執行上下文。

     // Declare the execution context
     SampleUniquePtr<nvinfer1::IExecutionContext> mContext{nullptr};
     ...
     // Create the execution context
     mContext = SampleUniquePtr<nvinfer1::IExecutionContext>(mEngine->createExecutionContext());

    此應用程序在下面的代碼示例中所示的函數 launchInference 中異步地對 GPU 發出推斷請求。輸入從主機( CPU )復制到 launchInference 內的設備( GPU )。然后使用 enqueueV2 函數執行推斷,并異步復制回結果。

    該示例使用 CUDA 流來管理 GPU 上的異步工作。異步推理執行通常通過重疊計算來提高性能,因為它最大限度地提高了 GPU 利用率。 enqueueV2 函數將推斷請求放在 CUDA 流上,并將運行時批處理大小、指向輸入和輸出的指針以及用于內核執行的 CUDA 流作為輸入。從主機到設備的異步數據傳輸使用 cudaMemcpyAsync 執行,反之亦然。

    void SimpleOnnx::launchInference(IExecutionContext* context, cudaStream_t stream, vector<float> const& inputTensor, vector<float>& outputTensor, void** bindings, int batchSize)
     {
     ??? int inputId = getBindingInputIndex(context);
     ??? cudaMemcpyAsync(bindings[inputId], inputTensor.data(), inputTensor.size() * sizeof(float), cudaMemcpyHostToDevice, stream);
     ??? context->enqueueV2(bindings, stream, nullptr);
     ??? cudaMemcpyAsync(outputTensor.data(), bindings[1 - inputId], outputTensor.size() * sizeof(float), cudaMemcpyDeviceToHost, stream);
     }

    調用 launchInference 后使用 cudaStreamSynchronize 函數可確保在訪問結果之前完成 GPU 計算。可以使用 ICudaEngine 類中的函數來查詢輸入和輸出的數量以及每個輸入和輸出的值和維度。最后,該示例將引用輸出與 TensorRT 生成的推論進行比較,并將差異打印到提示符。

    有關類的更多信息,請參見 TensorRT 類列表 。完整的代碼示例在 simpleOnnx \ u 1 . cpp 中。

    批量輸入

    此應用程序示例需要一個輸入,并在對其執行推斷后返回輸出。實際應用程序通常通過批量輸入來實現更高的性能和效率。一批形狀和大小相同的輸入可以在神經網絡的不同層上并行計算。

    較大的批處理通常能夠更有效地使用 GPU 資源。例如,在 Volta 和 Turing GPU s 上,使用 32 的倍數的批量大小在較低的精度下可能特別快和有效,因為 TensorRT 可以使用特殊的核來進行矩陣乘法和利用張量核的完全連接層。

    使用以下代碼在命令行上將圖像傳遞給應用程序。在本例中,在命令行上作為輸入參數傳遞的圖像(. pb 文件)的數量決定批大小。使用 test \ u data \ u set \從所有目錄獲取所有輸入的\ u 0 . pb 文件。下面的命令不是只讀取一個輸入,而是讀取文件夾中所有可用的輸入。

    當前,下載的數據有三個輸入目錄,因此批大小為 3 。此版本的示例對應用程序進行概要分析,并將結果打印到提示符。有關更多信息,請參閱下一節“配置應用程序”。

     >> ./simpleOnnx path/to/unet/unet.onnx fp32 path/to/unet/test_data_set_*/input_0.pb # Use all available test data sets.
     ...
     INFO: [MemUsageChange] Init cuDNN: CPU +1, GPU +8, now: CPU 1148, GPU 1806 (MiB)
     : Total per-runner device memory is 79243264
     : Total per-runner host memory is 13840
     : Allocated activation device memory of size 1459617792
     Inference batch size 3 average over 10 runs is 4.99552ms

    要在一個推理過程中處理多個圖像,請對應用程序進行一些更改。首先,在循環中收集所有圖像(. pb 文件),作為應用程序中的輸入:

    for (int i = 2; i < argc; ++i)
       input_files.push_back(string{argv[i]}); 

    接下來,使用 setMaxBatchSize 函數指定 TensorRT 引擎期望的最大批處理大小。然后,生成器通過選擇在目標平臺上使其性能最大化的算法,生成一個針對該批處理大小進行優化的引擎。雖然引擎不接受較大的批處理大小,但允許在運行時使用較小的批處理大小。

    maxBatchSize 值的選擇取決于應用程序以及任何給定時間的預期推斷流量(例如,圖像數)。通常的做法是構建多個針對不同批量大小優化的引擎(使用不同的 maxBatchSize 值),然后在運行時選擇最優化的引擎。

    未指定時,默認批大小為 1 ,這意味著引擎不會處理大于 1 的批大小。請按以下代碼示例所示設置此參數:

     builder->setMaxBatchSize(batchSize); 

    分析應用程序

    現在您已經看到了一個示例,下面是如何衡量它的性能。網絡推斷的最簡單的性能度量是向網絡呈現輸入和返回輸出之間經過的時間,稱為 延遲

    對于嵌入式平臺上的許多應用程序,延遲是至關重要的,而消費者應用程序需要服務質量。較低的延遲使這些應用程序更好。此示例使用 GPU 上的時間戳來測量應用程序的平均延遲。在 CUDA 中有許多方法可以分析您的應用程序。有關詳細信息,請參閱 如何在 CUDA C / C 中實現性能指標++

    CUDA 為 創造destroy記錄 事件提供輕量級事件 API 函數,并計算它們之間的時間。應用程序可以在 CUDA 流中記錄事件,一個在開始推斷之前,另一個在推斷完成之后,如下面的代碼示例所示。

    在某些情況下, MIG 不關心在推理開始之前和推理完成之后在 GPU 和 CPU 之間傳輸數據所需的時間。現有的技術可以將數據預取到 GPU ,并與數據傳輸重疊計算,這可以顯著隱藏數據傳輸開銷。函數 cudaEventElapsedTime 測量 CUDA 流中遇到的這兩個事件之間的時間。

    使用以下代碼示例在 SimpleOnnx::infer 中計算延遲:

      // Number of times to run inference and calculate average time
     constexpr int ITERATIONS = 10;
     ...
     bool SimpleOnnx::infer()
     {
     ??? CudaEvent start;
     ??? CudaEvent end;
     ??? double totalTime = 0.0;
     ??? CudaStream stream;
     ??? for (int i = 0; i < ITERATIONS; ++i)
     ??? {
     ??????? float elapsedTime;
     ??????? // Measure time it takes to copy input to GPU, run inference and move output back to CPU.
     ??????? cudaEventRecord(start, stream);
     ??????? launchInference(mContext.get(), stream, mInputTensor, mOutputTensor, mBindings, mParams.batchSize);
     ??????? cudaEventRecord(end, stream);
     ??????? // Wait until the work is finished.
     ??????? cudaStreamSynchronize(stream);
     ??????? cudaEventElapsedTime(&elapsedTime, start, end);
     ??????? totalTime += elapsedTime;
     ??? }
     ??? cout << "Inference batch size " << mParams.batchSize << " average over " << ITERATIONS << " runs is " << totalTime / ITERATIONS << "ms" << endl;
     ??? return true;
     }

    許多應用程序對大量輸入數據進行推斷,這些數據是為脫機處理而積累和成批處理的。對于這些應用程序來說,每秒可能的最大推斷數(稱為 throughput )是一個有價值的指標。

    您可以通過為更大的特定批大小生成優化的引擎來度量吞吐量,運行推斷,并度量每秒可以處理的批數。使用每秒批數和批大小來計算每秒推斷數,但這超出了本文的范圍。

    優化應用程序

    既然您知道了如何批量運行推斷并分析應用程序,那么就對其進行優化吧。 TensorRT 的關鍵優勢在于其靈活性和技術的使用,包括混合精度、所有 GPU 平臺上的高效優化,以及跨多種模型類型進行優化的能力。

    在本節中,我們將介紹一些技術來提高吞吐量和減少應用程序的延遲。有關詳細信息,請參閱 TensorRT 性能最佳實踐

    以下是一些常見的技巧:

    • 使用混合精度計算
    • 更改工作區大小
    • 重復使用 TensorRT 發動機

    使用混合精度計算

    TensorRT 默認情況下使用 FP32 算法進行推理,以獲得最高的推理精度。但是,在許多情況下,可以使用 FP16 和 INT8 精度進行推理,對結果的準確性影響最小。

    使用降低的精度來表示模型使您能夠在內存中擬合更大的模型,并在降低精度的數據傳輸要求較低的情況下實現更高的性能。您還可以將 FP32 和 FP16 精度的計算與 TensorRT 混合,稱為混合精度,或者對權重、激活和執行層使用 INT8 量化精度。

    對于支持快速 FP16 數學的設備,通過將 setFlag(BuilderFlag::kFP16) 參數設置為 true 來啟用 FP16 內核。

     if (mParams.fp16)
     {
     ??? config->setFlag(BuilderFlag::kFP16);
     }

    setFlag(BuilderFlag::kFP16) 參數向生成器表明,較低的計算精度是可以接受的。 TensorRT 使用 FP16 優化內核,如果它們在所選配置和目標平臺上表現更好。

    啟用此模式后,可以在 FP16 或 FP32 中指定權重,并自動轉換為適當的計算精度。您還可以靈活地為輸入和輸出張量指定 16 位浮點數據類型,這超出了本文的范圍。

    更改工作區大小

    TensorRT 允許您在引擎構建階段使用 setMaxWorkspaceSize 參數增加 GPU 內存占用。增加限制可能會影響可以同時共享 GPU 的應用程序數。將此限制設置得太低可能會過濾掉幾個算法,并創建一個次優引擎。 TensorRT 只分配所需的內存,即使 IBuilder::setMaxWorkspaceSize 中設置的內存量要高得多。因此,應用程序應該允許 TensorRT 生成器提供盡可能多的工作空間。 TensorRT 分配的空間不超過此值,通常更少。

    本例使用 1GB ,這允許 TensorRT 選擇任何可用的算法。

     // Allow TensorRT to use up to 1 GB of GPU memory for tactic selection
     constexpr size_t MAX_WORKSPACE_SIZE = 1ULL << 30; // 1 GB worked well for this example
     ...
     // Set the builder flag
     config->setMaxWorkspaceSize(MAX_WORKSPACE_SIZE); 

    重復使用 TensorRT 發動機

    構建引擎時, builder 對象為所選平臺和配置選擇最優化的內核。從網絡定義文件構建引擎可能非常耗時,并且不應在每次執行推斷時重復,除非模型、平臺或配置發生更改。

    圖 3 顯示了您可以在生成引擎之后轉換引擎的格式,并將其存儲在磁盤上以供以后重用,稱為 序列化引擎 。當您將引擎從磁盤加載到內存并繼續使用它進行推斷時,就會發生反序列化。

    圖 3 .序列化和反序列化 TensorRT 引擎。

    運行時對象反序列化引擎。

    SimpleOnnx::buildEngine 函數首先嘗試加載并使用引擎(如果存在)。如果引擎不可用,它將在當前目錄中創建并保存名為 unet_batch4.engine 的引擎。在本例嘗試構建一個新引擎之前,如果該引擎在當前目錄中可用,它將選擇該引擎。

    要強制使用更新的配置和參數構建新引擎,請在重新運行代碼示例之前,使用 make clean_engines 命令刪除磁盤上存儲的所有現有序列化引擎。

     bool SimpleOnnx::buildEngine()
     {
     ??? auto builder = SampleUniquePtr<nvinfer1::IBuilder>(nvinfer1::createInferBuilder(gLogger));
     ??? string precison = (mParams.fp16 == false) ? "fp32" : "fp16";
     ??? string enginePath{getBasename(mParams.onnxFilePath) + "_batch" + to_string(mParams.batchSize)
     ????????????????????? + "_" + precison + ".engine"};
     ??? string buffer = readBuffer(enginePath);
     ??? 
     ??? if (buffer.size())
     ??? {
     ??????? // Try to deserialize engine.
     ??????? SampleUniquePtr<nvinfer1::IRuntime> runtime{nvinfer1::createInferRuntime(gLogger)};
     ??????? mEngine = SampleUniquePtr<nvinfer1::ICudaEngine>(runtime->deserializeCudaEngine(buffer.data(), buffer.size(), nullptr));
     ??? }
     ??? if (!mEngine)
     ??? {
     ??????? // Fallback to creating engine from scratch.
     ??????? createEngine(builder);
     ?
     ???? ???if (mEngine)
     ??????? {
     ??????????? SampleUniquePtr<IHostMemory> engine_plan{mEngine->serialize()};
     ??????????? // Try to save engine for future uses.
     ??????????? writeBuffer(engine_plan->data(), engine_plan->size(), enginePath);
     ??????? }
     ??? }
     ??? return true;
     }

    現在您已經學習了如何使用 TensorRT 加速簡單應用程序的推理。在這篇文章中,我們用 TensorRT 8 測試了 NVIDIA Titan V GPU s 的早期性能。

    下一步

    現實世界的應用程序有更高的計算需求,更大的深度學習模型、更多的數據處理需求和更嚴格的延遲限制。 TensorRT 為計算量大的深度學習應用程序提供了高性能優化,是非常寶貴的推理工具。

    希望這篇文章讓您熟悉了使用 TensorRT 獲得驚人性能所需的關鍵概念。以下是一些應用您所學知識、使用其他模型以及通過更改本文中介紹的參數來探索設計和性能權衡的影響的方法。

    1. TensorRT 支持矩陣 為 TensorRT api 、解析器和層提供了受支持的特性和軟件。這個例子使用 C ++, TensorRT 同時提供 C ++和 Python API 。要運行這個帖子中包含的示例應用程序,請參閱 TensorRT 開發者指南 中的 API 和 Python 和 C ++代碼示例。
    2. 使用參數 setFp16Mode 將模型的允許精度更改為 true / false ,并分析應用程序以查看性能差異。
    3. 更改運行時用于推斷的批處理大小,并查看它如何影響模型和數據集的性能(延遲、吞吐量)。
    4. maxbatchsize 參數從 64 改為 4 ,可以看到在前五個內核中選擇了不同的內核。使用 nvprof 查看評測結果中的內核。

    本文中沒有涉及的一個主題是使用 INT8 精度在 TensorRT 中精確地執行推理。 TensorRT 可以轉換 FP32 網絡,以使用 INT8 降低的精度進行部署,同時最小化精度損失。為了實現這一目標,可以使用訓練后量化和量化感知訓練 TensorRT 對模型進行量化。有關詳細信息,請參閱 利用 TensorRT 量化感知訓練實現 INT8 推理的 FP32 精度

    有許多資源可以幫助您加速圖像/視頻、語音應用程序和推薦系統的應用程序。這些工具包括代碼示例、自主進度的深度學習研究所實驗室和教程,以及用于分析和調試應用程序的開發人員工具。

    如果您對 TensorRT 有問題,請先檢查 NVIDIA TensorRT 開發者論壇 ,看看 TensorRT 社區的其他成員是否有解決方案。 NVIDIA 注冊的開發人員也可以在 開發人員計劃 頁面上提交 bug 。

    ?

    +3

    標簽

    人人超碰97caoporen国产