[转载] 6. TensorRT 进阶用法

原文地址:
https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#advanced

其官方翻译版:
https://developer.nvidia.com/zh-cn/blog/tensorrt-advanced-usage-cn/

建议两者对照着看

6.1. The Timing Cache

To reduce the builder time, TensorRT creates a layer timing cache to keep the layer-profiling information during the builder phase. The information it contains is specific to the targeted builder devices, CUDA and TensorRT versions, and BuilderConfig parameters that can change the layer implementation such as BuilderFlag::kTF32 or BuilderFlag::kREFIT.

If there are other layers with the same I/O tensor configuration and layer parameters, the TensorRT builder skips profiling and reuses the cached result for the repeated layers. If a timing query misses in the cache, the builder times the layer and updates the cache.

The timing cache can be serialized and deserialized. You can load a serialized cache from a buffer using IBuilderConfig::createTimingCache:

ITimingCache* cache = 
 config->createTimingCache(cacheFile.data(), cacheFile.size());

Setting the buffer size to 0 creates a new empty timing cache.

You then attach the cache to a builder configuration before building.

config->setTimingCache(*cache, false);

During the build, the timing cache can be augmented with more information as a result of cache misses. After the build, it can be serialized for use with another builder.

IHostMemory* serializedCache = cache->serialize();

If there is no timing cache attached to a builder, the builder creates its own temporary local cache and destroys it when it is done.

The cache is incompatible with algorithm selection (refer to the Algorithm Selection and Reproducible Builds(6.3节) section). It can be disabled by setting the BuilderFlag.

config->setFlag(BuilderFlag::kDISABLE_TIMING_CACHE);

Note: The timing cache supports the most frequently used layer types: Convolution, Deconvolution, Pooling, SoftMax, MatrixMultiply, ElementWise, Shuffle, and tensor memory layout conversion. More layer types will be added in future releases.

为了减少构建时间,TensorRT 创建了一个层时缓存(layer timing cache),以在构建阶段保存层分析信息(layer-profile information)。该信息与目标构建器设备信息、CUDA 和 TensorRT 版本,以及可以更改层实现的 BuilderConfig 参数相关,例如BuilderFlag::kTF32BuilderFlag::kREFIT

如果有其他层具有相同的 I/O 张量配置和层参数,则 TensorRT 构建器会跳过分析并重用重复层的缓存结果。如果缓存中的计时查询未命中,则构建器会对该层计时并更新缓存。

时序缓存可以被序列化和反序列化。您可以通过IBuilderConfig::createTimingCache从缓冲区加载序列化缓存:

ITimingCache* cache = 
 config->createTimingCache(cacheFile.data(), cacheFile.size());

将缓冲区大小设置为0会创建一个新的空时序缓存。

然后,在构建之前将缓存附加到构建器配置。

config->setTimingCache(*cache, false);

在构建期间,由于缓存未命中,时序缓存可以增加更多信息。在构建之后,它可以被序列化以与另一个构建器一起使用。

IHostMemory* serializedCache = cache->serialize();

如果构建器没有附加时间缓存,构建器会创建自己的临时本地缓存并在完成时将其销毁。

缓存与算法选择不兼容(请参阅算法选择和可重现构建部分[在6.3])。可以通过设置BuilderFlag来禁用它。

config->setFlag(BuilderFlag::kDISABLE_TIMING_CACHE);

注意:时序缓存支持最常用的层类型:Convolution、Deconvolution、Pooling、SoftMax、MatrixMultiply、ElementWise、Shuffle 和张量内存布局转换(tensor memory layout conversion)。 未来版本中将添加更多图层类型。

6.2. Refitting an Engine

TensorRT 引擎可以直接用新的权重 而 无需重新build,但是,在构建时必须指定这样做的选项:

...
config->setFlag(BuilderFlag::kREFIT) 
builder->buildSerializedNetwork(network, config);

稍后,您可以创建一个Refitter对象:

ICudaEngine* engine = ...;
IRefitter* refitter = createInferRefitter(*engine,gLogger)

然后更新权重。例如,要更新名为“MyLayer”的卷积层的卷积核权重:

Weights newWeights = ...;
refitter->setWeights("MyLayer",
                     WeightsRole::kKERNEL,
                     newWeights);

新的权重应该与用于构建引擎的原始权重具有相同的长度和类型。如果出现问题,例如错误的层名称或权重长度或类型发生变化, setWeights返回 false

由于引擎的优化,如果您更改一些权重,您可能还必须提供一些其他权重。该接口可以告诉需要提供哪些额外的权重。

您可以使用 INetworkDefinition::setWeightsName() 在构建时命名权重——ONNX 解析器使用此 API 将权重与 ONNX 模型中使用的名称相关联。然后,您可以使用setNamedWeights更新权重:

Weights newWeights = ...;
refitter->setNamedWeights("MyWeights", newWeights);

setNamedWeightssetWeights可以同时使用,即,您可以通过setNamedWeights更新具有名称的权重,并通过setWeights更新那些未命名的权重。

这通常需要两次调用IRefitter::getMissing,首先获取必须提供的权重对象的数量,然后获取它们的层和角色(roles)。

// 首先获取必须提供的权重对象的数量
const int32_t n = refitter->getMissing(0, nullptr, nullptr);
std::vector<const char*> layerNames(n);
std::vector<WeightsRole> weightsRoles(n);
// 然后获取它们的层和角色(roles)
refitter->getMissing(n, layerNames.data(), 
                        weightsRoles.data());

或者,要获取所有缺失权重的名称,请运行:

const int32_t n = refitter->getMissingWeights(0, nullptr);
std::vector<const char*> weightsNames(n);
refitter->getMissingWeights(n, weightsNames.data());

您可以按任何顺序提供缺失的权重:

for (int32_t i = 0; i < n; ++i)
    refitter->setWeights(layerNames[i], weightsRoles[i],
                         Weights{...});

返回的缺失权重集是完整的,从某种意义上说,仅提供缺失的权重不会产生对任何更多权重的需求。

提供所有权重后,就可以更新引擎了:

bool success = refitter->refitCudaEngine();
assert(success);

如果 refit 返回 false,请检查日志以获取诊断信息,可能是关于仍然缺失的权重。 然后,您可以删除refitter:

delete refitter;

更新后的引擎 就像是 用新权重更新的网络构建的引擎 一样。

要查看引擎中的所有可改装权重,请使用refitter->getAll(...)refitter->getAllWeights(...) ;类似于上面使用getMissinggetMissingWeights的方式。

6.3. Algorithm Selection and Reproducible Builds

TensorRT 优化器的默认行为是选择全局最小化引擎执行时间的算法。它通过测试每个实现的时间来做到这一点,但有时,当实现具有相似的时间时,系统噪声可能会决定在构建器的选择。

不同的实现通常会使用不同的浮点值累加顺序(order of accumulation of floating point values),两种实现可能使用不同的算法,甚至以不同的精度运行。因此,构建器的不同调用通常会导致引擎返回不同的结果。

有时,确定性构建或重新创建早期构建的算法选择很重要。通过提供IAlgorithmSelector接口的实现并使用setAlgorithmSelector将其附加到构建器配置,您可以手动指导算法选择。

方法IAlgorithmSelector::selectAlgorithms接收一个AlgorithmContext ,其中包含有关层算法要求的信息,以及一组满足这些要求的Algorithm。它返回 TensorRT 应该为层(Layer)实现的算法集。

构建器将从这些算法中选择一种可以最小化网络全局运行时间的算法。如果未返回任何选项并且BuilderFlag::kREJECT_EMPTY_ALGORITHMS未设置,则 TensorRT 将其解释为意味着任何算法都可以用于该层。要覆盖此行为并在返回空列表时报错,请设置BuilderFlag::kREJECT_EMPTY_ALGORITHMSS标志。

在 TensorRT 完成对给定配置文件的网络优化后,它会调用reportAlgorithms,它可用于记录为每一层做出的最终选择。

要使构建一个确定性的 TensorRT 模型,selectAlgorithms 只应该返回一个选择。
要重现早期构建中的选择,请使用reportAlgorithms记录该构建中的选择,并在selectAlgorithms中返回它们。

sampleAlgorithmSelector演示了如何使用算法选择器在构建器中实现确定性和可重复性。

注意:

  • 算法选择中的 “层(Layer)” 概念与INetworkDefinition中的ILayer不同。由于融合优化,前者中的 “层(Layer)” 可以等同于多个网络层的集合。
  • selectAlgorithms中选择最快的算法可能对整个网络来说并不是最优的,因为它可能会增加格式转化的开销。
  • 如果 TensorRT 发现该层是空操作,则 IAlgorithm 的时间在 selectAlgorithms 中为 0
  • reportAlgorithms 不向 IAlgorithm 提供 selectAlgorithms的时间和工作空间信息

6.4. Creating A Network Definition From Scratch

除了使用解析器 (eg: 用来解析 onnx 模型的解析器),您还可以通过网络定义 API 将网络直接定义到 TensorRT。此场景假设每层权重已在主机内存中准备好在网络创建期间传递给 TensorRT。

以下示例创建了一个简单的网络,其中包含 Input、Convolution、Pooling、 MatrixMultiply、Shuffle 、Activation 和 Softmax 层。

6.4.1. C++

C++版本的大家看原文档吧:
https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#c-advanced

6.4.2. Python

此部分对应的代码可以在network_api_pytorch_mnist中找到。

这个例子使用一个辅助类来保存一些关于模型的元数据:

class ModelData(object):
    INPUT_NAME = "data"
    INPUT_SHAPE = (1, 1, 28, 28)
    OUTPUT_NAME = "prob"
    OUTPUT_SIZE = 10
    DTYPE = trt.float32

在此示例中,权重是从 Pytorch MNIST 模型导入的。

weights = mnist_model.get_weights()

创建记录器、构建器和网络类。

TRT_LOGGER = trt.Logger(trt.Logger.ERROR)
builder = trt.Builder(TRT_LOGGER)
EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(common.EXPLICIT_BATCH)

kEXPLICIT_BATCH标志的更多信息,请参阅显式与隐式批处理部分。

接下来,为网络创建输入张量,指定张量的名称、数据类型和形状。

input_tensor = network.add_input(name=ModelData.INPUT_NAME, 
                                 dtype=ModelData.DTYPE, 
                                 shape=ModelData.INPUT_SHAPE)

添加一个卷积层,指定输入、输出图的数量、内核形状、权重、偏差和步幅:

conv1_w = weights['conv1.weight'].numpy()
conv1_b = weights['conv1.bias'].numpy()
conv1 = network.add_convolution(input=input_tensor, 
                                num_output_maps=20, 
                                kernel_shape=(5, 5), 
                                kernel=conv1_w, 
                                bias=conv1_b)
conv1.stride = (1, 1)

添加一个池化层,指定输入(前一个卷积层的输出)、池化类型、窗口大小和步幅:

pool1 = network.add_pooling(input=conv1.get_output(0), 
                            type=trt.PoolingType.MAX, 
                            window_size=(2, 2))
pool1.stride = (2, 2)

添加下一对卷积和池化层:

conv2_w = weights['conv2.weight'].numpy()
conv2_b = weights['conv2.bias'].numpy()
conv2 = network.add_convolution(pool1.get_output(0), 50, (5, 5), conv2_w, conv2_b)
conv2.stride = (1, 1)

pool2 = network.add_pooling(conv2.get_output(0), trt.PoolingType.MAX, (2, 2))
pool2.stride = (2, 2)

添加一个 Shuffle 层来重塑输入,为矩阵乘法做准备:

batch = input.shape[0]
mm_inputs = np.prod(input.shape[1:])
input_reshape = net.add_shuffle(input)
input_reshape.reshape_dims = trt.Dims2(batch, mm_inputs)

不熟悉 np.prod 的老铁看看这个:https://blog.csdn.net/fu6543210/article/details/80222984,就是将里边儿的元素全乘起来

现在,添加一个 MatrixMultiply 层。在这里,模型导出器提供了转置权重,因此为这些权重指定了kTRANSPOSE选项。

filter_const = net.add_constant(trt.Dims2(nbOutputs, k), weights["fc1.weight"].numpy())
mm = net.add_matrix_multiply(input_reshape.get_output(0), 
                             trt.MatrixOperation.NONE, 
                             filter_const.get_output(0), 
                             trt.MatrixOperation.TRANSPOSE);

添加偏差, 其将在 Batch 维度广播:

bias_const = net.add_constant(trt.Dims2(1, nbOutputs), 
                              weights["fc1.bias"].numpy())
bias_add = net.add_elementwise(mm.get_output(0), 
                               bias_const.get_output(0), 
                               trt.ElementWiseOperation.SUM)

添加 Relu 激活层:

relu1 = network.add_activation(input=fc1.get_output(0), 
                               type=trt.ActivationType.RELU)

添加最后的全连接层,并将该层的输出标记为整个网络的输出:

fc2_w = weights['fc2.weight'].numpy()
fc2_b = weights['fc2.bias'].numpy()
fc2 = network.add_fully_connected(relu1.get_output(0), 
                                  ModelData.OUTPUT_SIZE, 
                                  fc2_w, 
                                  fc2_b)

fc2.get_output(0).name = ModelData.OUTPUT_NAME
network.mark_output(tensor=fc2.get_output(0))

变量network表示 MNIST 模型已经被创建完毕.
可以参阅构建引擎和执行推理部分,了解如何构建引擎并使用此网络运行推理。
内容在这篇博客:
TensorRT 的 Python 接口解析

哦,我整的有些潦草了hhhh, 下一节是最重要的一节

6.5. Reduced Precision

这节比较重要,之前已经单独列出:
TensorRT 中 Reduced Precision: 125830788

6.6. I/O Formats

6.7. Compatibility of Serialized Engines

6.8. Explicit Versus Implicit Batch

这节比较重要,之前已经单独列出:
TensorRT 中的 Explicit 与 Implicit Batch: 125821270

6.9. Sparsity

6.10. Empty Tensors

6.11. Reusing Input Buffers

6.12. Engine Inspector


以上的坑,之后再填吧…
s**t !!! 我现在的主要工作应该是看文档,然后实践,而不是翻译或者copy …

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值