人工智能/深度学习

使用 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 。

 

Tags