Skip to content

Latest commit

 

History

History
131 lines (70 loc) · 12.3 KB

TensorRT如何工作.md

File metadata and controls

131 lines (70 loc) · 12.3 KB

TensorRT如何工作

本章提供了有关 TensorRT 工作原理的更多详细信息。

5.1. Object Lifetimes

TensorRT 的 API 是基于类的,其中一些类充当其他类的工厂。对于用户拥有的对象,工厂对象的生命周期必须跨越它创建的对象的生命周期。例如, NetworkDefinitionBuilderConfig类是从构建器类创建的,这些类的对象应该在构建器工厂对象之前销毁。

此规则的一个重要例外是从构建器创建引擎。创建引擎后,您可以销毁构建器、网络、解析器和构建配置并继续使用引擎。

5.2. Error Handling and Logging

创建 TensorRT 顶级接口(builderruntimerefitter)时,您必须提供LoggerC++Python )接口的实现。记录器用于诊断和信息性消息;它的详细程度是可配置的。由于记录器可用于在 TensorRT 生命周期的任何时间点传回信息,因此它的生命周期必须跨越应用程序中对该接口的任何使用。实现也必须是线程安全的,因为 TensorRT 可以在内部使用工作线程。

对对象的 API 调用将使用与相应顶级接口关联的记录器。例如,在对ExecutionContext::enqueue()的调用中,执行上下文是从引擎创建的,该引擎是从运行时创建的,因此 TensorRT 将使用与该运行时关联的记录器。

错误处理的主要方法是ErrorRecorde ( C++ , Python ) 接口。您可以实现此接口,并将其附加到 API 对象以接收与该对象关联的错误。对象的记录器也将传递给它创建的任何其他记录器 - 例如,如果您将错误记录器附加到引擎,并从该引擎创建执行上下文,它将使用相同的记录器。如果您随后将新的错误记录器附加到执行上下文,它将仅接收来自该上下文的错误。如果生成错误但没有找到错误记录器,它将通过关联的记录器发出。

请注意,CUDA 错误通常是异步的 - 因此,当执行多个推理或其他 CUDA 流在单个 CUDA 上下文中异步工作时,可能会在与生成它的执行上下文不同的执行上下文中观察到异步 GPU 错误。

5.3 Memory

TensorRT 使用大量设备内存,即 GPU 可直接访问的内存,而不是连接到 CPU 的主机内存。由于设备内存通常是一种受限资源,因此了解 TensorRT 如何使用它很重要。

5.3.1. The Build Phase

在构建期间,TensorRT 为时序层实现分配设备内存。一些实现可能会消耗大量临时内存,尤其是在使用大张量的情况下。您可以通过构建器的maxWorkspace属性控制最大临时内存量。这默认为设备全局内存的完整大小,但可以在必要时进行限制。如果构建器发现由于工作空间不足而无法运行的适用内核,它将发出一条日志消息来指示这一点。

You can control the maximum amount of temporary memory through the memory pool limits of the builder config. The workspace size defaults to the full size of device global memory but can be restricted when necessary.

然而,即使工作空间相对较小,计时也需要为输入、输出和权重创建缓冲区。 TensorRT 对操作系统因此类分配而返回内存不足是稳健的,但在某些平台上,操作系统可能会成功提供内存,随后内存不足killer进程观察到系统内存不足,并终止 TensorRT .如果发生这种情况,请在重试之前尽可能多地释放系统内存。

在构建阶段,通常在主机内存中至少有两个权重拷贝:来自原始网络的权重拷贝,以及在构建引擎时作为引擎一部分包含的权重拷贝。此外,当 TensorRT 组合权重(例如卷积与批量归一化)时,将创建额外的临时权重张量。

5.3.2. The Runtime Phase

在运行时,TensorRT 使用相对较少的主机内存,但可以使用大量的设备内存。

引擎在反序列化时分配设备内存来存储模型权重。由于序列化引擎几乎都是权重,因此它的大小非常接近权重所需的设备内存量。

ExecutionContext使用两种设备内存:

  • 一些层实现所需的持久内存——例如,一些卷积实现使用边缘掩码,并且这种状态不能像权重那样在上下文之间共享,因为它的大小取决于层输入形状,这可能因上下文而异。该内存在创建执行上下文时分配,并在其生命周期内持续。
  • 暂存内存,用于在处理网络时保存中间结果。该内存用于中间激活张量。它还用于层实现所需的临时存储,其边界由IBuilderConfig::setMaxWorkspaceSize()控制。

您可以选择通过ICudaEngine::createExecutionContextWithoutDeviceMemory()创建一个没有暂存内存的执行上下文,并在网络执行期间自行提供该内存。这允许您在未同时运行的多个上下文之间共享它,或者在推理未运行时用于其他用途。 ICudaEngine::getDeviceMemorySize()返回所需的暂存内存量。

构建器在构建网络时发出有关执行上下文使用的持久内存和暂存内存量的信息,严重性为 kINFO 。检查日志,消息类似于以下内容:

[08/12/2021-17:39:11] [I] [TRT] Total Host Persistent Memory: 106528
[08/12/2021-17:39:11] [I] [TRT] Total Device Persistent Memory: 29785600
[08/12/2021-17:39:11] [I] [TRT] Total Scratch Memory: 9970688

默认情况下,TensorRT 直接从 CUDA 分配设备内存。但是,您可以将 TensorRT 的IGpuAllocatorC++Python )接口的实现附加到构建器或运行时,并自行管理设备内存。如果您的应用程序希望控制所有 GPU 内存并子分配给 TensorRT,而不是让 TensorRT 直接从 CUDA 分配,这将非常有用。

TensorRT 的依赖项( cuDNNcuBLAS )会占用大量设备内存。 TensorRT 允许您通过构建器配置中的TacticSourcesC++Python )属性控制这些库是否用于推理。请注意,某些层实现需要这些库,因此当它们被排除时,网络可能无法编译。

The cudnnContext and cublasContext handles are passed to the plugins using IPluginV2Ext::attachToContext() if the appropriate tactic sources are set.

CUDA 基础设施和 TensorRT 的设备代码也会消耗设备内存。内存量因平台、设备和 TensorRT 版本而异。您可以使用cudaGetMemInfo来确定正在使用的设备内存总量。

注意:由于 CUDA 无法控制统一内存设备上的内存,因此cudaGetMemInfo返回的结果在这些平台上可能不准确。

5.3.3. CUDA Lazy Loading

CUDA延迟加载是一种CUDA功能,可以显著减少TensorRT的峰值GPU和主机内存使用,并加快TensorRT初始化,而性能影响可以忽略不计(<1%)。内存使用和初始化时间的节省取决于模型、软件堆栈、GPU平台等。它通过设置环境变量CUDA_MODULE_LOADING=LAZY来启用。有关更多信息,请参阅NVIDIA CUDA文档。

5.3.4. 二级持久缓存管理

NVIDIA Ampere和更高版本的体系结构支持二级缓存持久性,该功能允许在选择一条缓存线进行逐出时对二级缓存线进行优先级排序以进行保留。 TensorRT可以使用它在缓存中保留激活,减少DRAM流量和功耗。
缓存分配是按执行上下文进行的,使用上下文的setPersistentCacheLimit方法启用。所有上下文(和使用此功能的其他组件)之间的总持久缓存不应超过cudaDeviceProp::persistengL2CacheMaxSize。有关更多信息,请参阅NVIDIA CUDA最佳实践指南。

5.4. Threading

一般来说,TensorRT 对象不是线程安全的。预期的运行时并发模型是不同的线程将在不同的执行上下文上操作。上下文包含执行期间的网络状态(激活值等),因此在不同线程中同时使用上下文会导致未定义的行为。 为了支持这个模型,以下操作是线程安全的:

  • 运行时或引擎上的非修改操作。
  • 从 TensorRT 运行时反序列化引擎。
  • 从引擎创建执行上下文。
  • 注册和注销插件。

在不同线程中使用多个构建器没有线程安全问题;但是,构建器使用时序来确定所提供参数的最快内核,并且使用具有相同 GPU 的多个构建器将扰乱时序和 TensorRT 构建最佳引擎的能力。使用多线程使用不同的 GPU 构建不存在此类问题。

5.5. Determinism

TensorRT builder 使用时间来找到最快的内核来实现给定的运算符。时序内核会受到噪声的影响——GPU 上运行的其他工作、GPU 时钟速度的波动等。时序噪声意味着在构建器的连续运行中,可能不会选择相同的实现。

AlgorithmSelector ( C++ , Python )接口允许您强制构建器为给定层选择特定实现。您可以使用它来确保构建器从运行到运行选择相同的内核。有关更多信息,请参阅算法选择和可重现构建部分。

一旦构建了引擎,它就是确定性的:在相同的运行时环境中提供相同的输入将产生相同的输出。

5.6. 运行时选项

TensorRT提供了多个运行库来满足各种用例。运行TensorRT引擎的C++应用程序应该链接到以下其中一个:

  • 默认运行时是主库(libnvinfer.so/.dll)
  • 精简运行库(libnvinfer_lean.so/.dll)比默认库小得多,并且只包含运行版本兼容引擎所需的代码。它有一些限制;主要是,不能改装refit或序列化serialize engines
  • 调度运行时(libnvinfer_dispatch.so/.dll)是一个小型填充程序库,可以加载精简运行时,并将调用重定向到该库。调度运行时能够加载精简运行时的旧版本,并与构建器的适当配置一起,可用于提供新版本的TensorRT和旧plan文件之间的兼容性。使用 调度运行时 与手动加载 精简运行时 几乎相同,但它检查API是否由加载的精简运行时实现,并在可能的情况下执行一些参数映射以支持API更改。

精简运行时包含的运算符实现比默认运行时更少。由于TensorRT在构建时选择运算符实现,因此需要通过启用版本兼容性来指定应为精简运行时构建引擎。它可能比默认运行时构建的引擎稍慢。
精简运行时包含调度运行时的所有功能,默认运行时包含精简运行时的全部功能。

TensorRT provides Python packages corresponding to each of the above libraries:

tensorrt

A Python package. It is the Python interface for the default runtime.

tensorrt_lean

A Python package. It is the Python interface for the lean runtime.

tensorrt_dispatch

A Python package. It is the Python interface for the dispatch runtime.

Python applications that run TensorRT engines should import one of the above packages to load the appropriate library for their use case.

5.7. 兼容性

默认情况下,只有在与用于序列化引擎的相同OS、CPU架构、GPU模型和TensorRT版本一起使用时,才能保证序列化引擎正常工作。请参阅版本兼容性和硬件兼容性部分,了解如何放宽对TensorRT版本和GPU型号的限制。