12.5 TensorRT API 使用

前言

在TensorRT的第二小节中,介绍了TensorRT模型计算图的构建通常有三种方式,分别是TensorRT API、parser、训练框架中trt工具

三种方式的便捷程度与灵活度是依次递增和递减的,TensorRT API最灵活,可自由设定网络每一层,任意编辑模型的任何层,本节就介绍

TensorRT API的使用。

TensorRT API 简介

TensorRT API是构建Network的一种方法,整个TensorRT的使用流程还是与第二节中介绍的一样,整体流程如下图所示

trt-workflow

这里不再赘述workflow内容,直接看TensorRT是如何构建network的,整体可分三个步骤:

  1. 创建network:network = builder.create_network()
  2. 逐层搭建网络:搭建网络层,并且赋予权重值
  3. 创建engine:engine = builder.build_engine(network, config)

由此可见,核心部分在于network提供了一系列网络层的搭建,这类似于pytorch中的nn.Module可以搭建各种网络层。

TensorRT官方提供的网络层可参考文档

下面介绍采用TensorRT API搭建常见网络层的方法。

TensorRT API 网络层创建

卷积层

add_convolution(self: tensorrt.tensorrt.INetworkDefinition, input: tensorrt.tensorrt.ITensor, num_output_maps: int, kernel_shape: tensorrt.tensorrt.DimsHW, kernel: tensorrt.tensorrt.Weights, bias: tensorrt.tensorrt.Weights = None)→ tensorrt.tensorrt.IConvolutionLayer

Parameters:

  • input – The input tensor to the convolution.

  • num_output_maps – The number of output feature maps for the convolution.

  • kernel_shape – The dimensions of the convolution kernel.

  • kernel – The kernel weights for the convolution.

  • bias – The optional bias weights for the convolution.

Returns:The new convolution layer, or None if it could not be created.

具体的创建案例代码解释如下:

  • 设置输入input:这个input如果是第一层的话,需要自行构建data;如果是中间层,则需要设置为上一层的输出ITensor,例如pool2.get_output(0)返回的是一个ITensor对象。
  • 设置卷积权重:在创建网络时,直接赋予卷积层的权重,这个权重通常通过wst文件获取,是一个字典,key是自行标识的名称,value是np.ndarray。
data = network.add_input('input', trt.float32, (3, 224, 224))
conv1 = network.add_convolution(input=data,
                                num_output_maps=64,
                                kernel_shape=(7, 7),
                                kernel=weight_map["conv1.weight"],
                                bias=trt.Weights())

fc1 = network.add_fully_connected(input=pool2.get_output(0),
                                  num_outputs=OUTPUT_SIZE,
                                  kernel=weight_map['fc.weight'],
                                  bias=weight_map['fc.bias'])

BN层

在TensorRT API中没有BN的实现,BN层需要通过add_scale层来实现,即通过减法、乘法的操作等价于实现BN层。

下面代码是实现过程,核心是手动计算出需要shift和scale的数值,然后创建add_scale层。

def addBatchNorm2d(network, weight_map, input, layer_name, eps):
    gamma = weight_map[layer_name + ".weight"]
    beta = weight_map[layer_name + ".bias"]
    mean = weight_map[layer_name + ".running_mean"]
    var = weight_map[layer_name + ".running_var"]
    var = np.sqrt(var + eps)

    scale = gamma / var
    shift = -mean / var * gamma + beta
    return network.add_scale(input=input,
                             mode=trt.ScaleMode.CHANNEL,
                             shift=shift,
                             scale=scale)

激活函数层

激活函数层通过add_activation,其中提供了type属性来设置不同的激活函数。

relu3 = network.add_activation(ew1.get_output(0), type=trt.ActivationType.RELU)

type中可设置的类型有十多种,具体参考IActivationLayer的文档

RELU : Rectified Linear activation
SIGMOID : Sigmoid activation
TANH : Hyperbolic Tangent activation
LEAKY_RELU : Leaky Relu activation: f(x) = x if x >= 0, f(x) = alpha * x if x < 0
ELU : Elu activation: f(x) = x if x >= 0, f(x) = alpha * (exp(x) - 1) if x < 0
SELU : Selu activation: f(x) = beta * x if x > 0, f(x) = beta * (alpha * exp(x) - alpha) if x <= 0
SOFTSIGN : Softsign activation: f(x) = x / (1 + abs(x))
SOFTPLUS : Softplus activation: f(x) = alpha * log(exp(beta * x) + 1)
CLIP : Clip activation: f(x) = max(alpha, min(beta, x))
HARD_SIGMOID : Hard sigmoid activation: f(x) = max(0, min(1, alpha * x + beta))
SCALED_TANH : Scaled Tanh activation: f(x) = alpha * tanh(beta * x)
THRESHOLDED_RELU : Thresholded Relu activation: f(x) = x if x > alpha, f(x) = 0 if x <= alpha

池化层

pool1 = network.add_pooling(input=relu1.get_output(0),
                            window_size=trt.DimsHW(3, 3),
                            type=trt.PoolingType.MAX)

全连接层

全连接层在v8.6.1之后进行了删除,不再支持直接创建,需要用add_matrix_multiply、add_elementwise间接实现。

这个问题在官方文档没有指引,是通过多种方法找到的替代方案,这里简单记录debug过程。

  1. 运行报错:AttributeError: 'tensorrt.tensorrt.INetworkDefinition' object has no attribute 'add_fully_connected'

  2. 各种文档找'add_fully_connected',无果;

  3. 官方文档查阅 ‘INetworkDefinition' 支持的方法,的确没了add_fully_connected,猜测肯定有对应方法替换,梳理所有支持的方法,找到最相关的方法是:add_matrix_multiply

  4. TRT github仓库搜索add_matrix_multiply, sample.py,发现了FC层实现方法:

    https://github.com/NVIDIA/TensorRT/blob/c0c633cc629cc0705f0f69359f531a192e524c0f/samples/python/network_api_pytorch_mnist/sample.py

具体方法如下:

def add_matmul_as_fc(net, input, outputs, w, b):
    assert len(input.shape) >= 3
    m = 1 if len(input.shape) == 3 else input.shape[0]
    k = int(np.prod(input.shape) / m)  # 输入大小: 2048
    assert np.prod(input.shape) == m * k
    n = int(w.size / k)  # 输出大小: 1000
    assert w.size == n * k
    assert b.size == n

    input_reshape = net.add_shuffle(input)
    input_reshape.reshape_dims = trt.Dims2(m, k)

    filter_const = net.add_constant(trt.Dims2(n, k), w)
    mm = net.add_matrix_multiply(
        input_reshape.get_output(0),
        trt.MatrixOperation.NONE,
        filter_const.get_output(0),
        trt.MatrixOperation.TRANSPOSE,
    )

    bias_const = net.add_constant(trt.Dims2(1, n), b)
    bias_add = net.add_elementwise(mm.get_output(0), bias_const.get_output(0), trt.ElementWiseOperation.SUM)

    output_reshape = net.add_shuffle(bias_add.get_output(0))
    output_reshape.reshape_dims = trt.Dims4(m, n, 1, 1)
    return output_reshape

V 8.6.1版本可用的方法如下:

fc1 = network.add_fully_connected(input=pool2.get_output(0),
                                  num_outputs=OUTPUT_SIZE,
                                  kernel=weight_map['fc.weight'],
                                  bias=weight_map['fc.bias'])

以上是常见的网络层介绍,更多网络层参见文档

tensorrtx

学习TensorRT API搭建网络模型,十分推荐根据tensorrtx的代码资料学习。

tensorrtx是一个基于TensorRT API构建常用的网络模型库,它完全基于TensorRT API搭建模型,这使得模型的构建具备高可调节性,也对理解、学习网络模型内部细节提供了很好的资料。

tensorrtx为模型提供了python和c++的代码,使用tensorrtx的工作流程如下:

  1. 获取训练好的模型权重文件,例如pytorch的pt文件,tensorflow的ckpt文件,MXNet的.params+.json等
  2. 将权重文件导出为.wts文件,wts文件TensorRT定义的一种模型参数文件格式
  3. 创建network,逐网络层搭建模型,并基于wts文件赋予网络层权重值
  4. 搭建engine,进行推理或序列化保存到磁盘。

wts 文件

这里遇到一个新的文件格式wts,wts是Weights的缩写,是由Nvidia定义的一种模型参数文件格式。

wts文件只包含模型的参数数据,不包含网络结构等其他信息。网络结构需要另外定义,在这里就需要用TensorRT API逐层定义。

通过使用wts文件分离参数和网络结构,可以方便地进行模型压缩、量化等优化,也可以跨框架部署模型,同时还可保护模型的结构不被泄露。

ResNet50 推理

接下来演示如何采用TensorRT API搭建ResNet50,并完成推理,代码来自tensorrxpytorchx

整体分两步:

第一步,通过pytorchx的代码获得resnet.wts。在本教程,resnet.wts的生成可通过配套代码实现

第二步,通过tensorrtx的代码搭建TensorRT的network,并且创建engine进行推理。在本教程,推理代码可通过配套代码实现

wts文件生成

上述代码已集成到配套章节代码中,先看生成wts的核心代码:

  • wts文件第一行表明整个文件有多少个权重;

  • 之后的每一行是一个权重名称及权重值的二进制形式。

    f = open(path_wts, 'w')
    f.write("{}\n".format(len(net.state_dict().keys())))
    for k, v in net.state_dict().items():
        print('key: ', k)
        print('value: ', v.shape)
        vr = v.reshape(-1).cpu().numpy()
        f.write("{} {}".format(k, len(vr)))
        for vv in vr:
            f.write(" ")
            f.write(struct.pack(">f", float(vv)).hex())
        f.write("\n")
    

以下是一个wts案例:

10
conv1.weight 150 be40ee1b bd20bab8 bdc4bc53 .......
conv1.bias 6 bd327058 .......
conv2.weight 2400 3c6f2220 3c693090 ......

network创建

接下来参照第一份配套代码,将engine的构建从直接加载文件改为基于network创建的形式

回顾之前直接读取磁盘上的engine文件,可以直接得到engine。

with open(model_path, 'rb') as ff, trt.Runtime(logger) as runtime:
    engine = runtime.deserialize_cuda_engine(ff.read())

接下来看network逐层创建的过程,主workflow是一样的,需要创建各模块,这里有两个重点。

  1. trt推荐采用显式batch,因此create_network的时候需要设置1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
  2. resnet50的创建,在函数build_model_by_trt_api中实现
def init_model(model_path):

    logger = trt.Logger(trt.Logger.WARNING)
    builder = trt.Builder(logger)
    network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
    network = build_model_by_trt_api(network, model_path)
    config = builder.create_builder_config()
    engine = builder.build_engine(network, config)
    context = engine.create_execution_context()
    context.set_input_shape("data", [1, 3, 224, 224])  # 绑定输入张量的形状

    return context, engine

build_model_by_trt_api函数中就是上文提到的TensorRT API的各网络层创建接口,这里就不一一赘述,到这里,整体的流程梳理完成。

def build_model_by_trt_api(network, model_path):
    weight_map = load_wts_file(model_path)  # 权重字典

    data = network.add_input("data", trt.float32, (1, 3, 224, 224))
    assert data

    conv1 = network.add_convolution(input=data,
                                    num_output_maps=64,
                                    kernel_shape=(7, 7),
                                    kernel=weight_map["conv1.weight"],
                                    bias=trt.Weights())

最终能得到如下图片,表明基于TensorRT API创建网络模型并推理是成功的。r50-output

tensorrtx还提供了非常多常见的网络模型,如果有需要使用 TensorRT API创建网络模型,建议优选tensorrtx中寻找参考案例。

小结

本小节介绍TensorRT API进行trt模型创建的流程及案例。首先介绍了TensorRT API创建网络层的方法及常用接口,随后介绍学习TensorRT API 非常好的代码库——tensorrtx,最后以一个resnet50的推理案例介绍基于TensorRT API 搭建模型的全流程。

通过本小节,可以了解TensorRT API 创建模型的过程与概念,更多案例及用法,请阅读:

Copyright © TingsongYu 2021 all right reserved,powered by Gitbook文件修订时间: 2024年04月26日21:48:10

results matching ""

    No results matching ""