我的NVIDIA开发之旅——实例分割模型YOLACT的TensorRT API模型搭建与推断加速实战

DataXujing 2022-06-07 16:35:43
加精

✍YOLACT模型结构详解

YOLACT是2019年发表在ICCV的实例分割模型,其主要通过两个并行的子网络来实现实例分割。(1)Prediction Head分支生成各个anchor的类别置信度、位置回归参数以及mask的掩码系数;(2)Protonet分支生成一组原型mask。然后将原型mask和mask的掩码系数相乘,从而得到图片中的每一个目标的mask。论文中还提出了新的NMS算法:Fast-NMS, 和朴素的NMS算法相比只有轻微的精度损失,但却大大提升了分割的速度。YOLACT是一个one-stage模型,它和two-stage模型(Mask R-CNN等)相比起来,速度更快但精度稍差一些。

img

  • Backbone: YOLACT模型输入的图像大小为 $550 \times 550$ , 采用的Backbone为ResNet-101,作者团队开源的实现中还是用了ResnNet-50和DarkNet-53。
  • FPN: P3-P7是FPN网络,它是由C5经过1个卷积层得到P5,然后对P5采用双线性插值使特征图扩大一倍,与经过卷积的C4相加得到P4,相同的方法可得到P3。然后对P5进行卷积和下采样得到P6,对P6进行同样的卷积和下采样得到P7,从而建立FPN网络。接下来是并行的操作,P3被送入Protonet, P3-P7被同时送入Prediction Head中。(采用FPN的优点是模型学习到更丰富的特征,更有利于分割不同大小的目标)
  • Protonet分支: Protonet分支的网络是若干卷积组成的,其输入是P3,输出的mask维度是 $138 \times 138 \times k (k=32)$ ,即32个Prototypes mask,每个的大小是 $138 \times 138$ 。(这里可以把Prototypes mask理解为目标检测中的anchor,但是这些Prototypes mask是可学习的)

img

  • Prediction Head: 它是在RetinaNet的基础上改进得到的,采用共享卷积网络,从而可以提高速度,达到实时分割的目的。它的输入是P3-P7共5个特征图,每个特征图先生成anchor,每个像素点生成3个anchor,比例分别为1:1,1:2,2:1。5个特征图的anchor的基本大小分别是24,48,96,192,384。基本大小根据不同比例进行调整,确保anchor的面积相等。

img

这里以P3为例进行解释说明。假设P3的维度是 $W3 \times H3 \times 256$ ,那么它的anchor个数就是 $a3=W3 \times H3 \times 3$ 。接下来Prediction Head为其生成3类输出:类别置信度,由于COCO中共81类(包括背景), 所以其维度为 $a3 \times 81$ ; 位置偏移,维度为 $a3 \times 4$ ; mask置信度,维度为 $a3 \times 32$ 。对于P4-P7进行的操作是相同的,最后将这些结果进行拼接,标记为 $a=a3+a4+a5+a6+a7$ , 得到全部的置信度维度为 $a \times 81$ ; 全部的位置偏移维度为 $a \times 4$ ; 全部的mask置信度维度为 $a \times 32$ 。

  • Fast-NMS: 通过Prediction Head分支得到很多anchor,可以在anchor上加入位置偏移得到RoI。由于RoI存在重叠,NMS是常用的后处理办法。而YOLACT提出了一种新的筛选算法Fast-NMS, 其在保证精度的前提下,减少了筛选时间

img

熟悉了YOLACT,下面我们将使用TensorRT API重新搭建该网络,并将网络的NMS后处理部分使用TensorRT Plugin的方式通过TensorRT API加入到TRT Engine中,实现YOLACT的端到端的推断加速。

✍为什么选择TensorRT API方式搭建网络?

TensorRT是NVIDIA用于高效实现已训练好的深度学习模型的推理过程的SDK,其包含推理优化器和运行时环境和必要一些工具比如:trtexec, onnx-graphsurgeon,polygraphy等优化工具。其可以使深度学习模型能以更高的吞吐量和更低的延迟进行推断,并提供了完整的C++和Python的API和帮助文档。

img

  1. 使用TensorRT的基本流程包括构建TensorRT Engine和运行TensorRT Engine两个阶段:
  • 构建TensorRT Engine:
static Logger gLogger(ILogger::Severity::kERROR);   //1.log
IBuilder * builder = createInferBuilder(gLogger);   //2.builder and config
INetworkDefinition * network = builder->createNetworkV2(1U << int(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH));
IOptimizationProfile *profile = builder->createOptimizationProfile();
IBuilderConfig * config  = builder->createBuilderConfig();
config->setMaxWorkspaceSize(1 << 30);
// dynamic shape
ITensor *inputTensor = network->addInput("inputT0", DataType::kFLOAT, Dims3 {-1, -1, -1});
profile->setDimensions(inputTensor->getName(), OptProfileSelector::kMIN, Dims3 {1, 1, 1});
profile->setDimensions(inputTensor->getName(), OptProfileSelector::kOPT, Dims3 {3, 4, 5});
profile->setDimensions(inputTensor->getName(), OptProfileSelector::kMAX, Dims3 {6, 8, 10});
config->addOptimizationProfile(profile);

//3.network
IIdentityLayer *identityLayer = network->addIdentity(*inputTensor);
network->markOutput(*identityLayer->getOutput(0));
//4.build engine
IHostMemory *engineString = builder->buildSerializedNetwork(*network, *config);
//5.save engine
std::ofstream engineFile(trtFile, std::ios::binary);
engineFile.write(static_cast<char *>(engineString->data()), engineString->size());
  • 运行TensorRT Engine:
//1.创建 engine和 context
IRuntime *runtime {createInferRuntime(gLogger)};
engine = runtime->deserializeCudaEngine(engineString->data(), engineString->size());
IExecutionContext *context = engine->createExecutionContext();
context->setBindingDimensions(0, Dims3 {3, 4, 5});

//2.准备数据
// 涉及数据host to device的copy
int  inputSize = 3 * 4 * 5, outputSize = 1;
Dims outputShape = context->getBindingDimensions(1);
for (int i = 0; i < outputShape.nbDims; i++)
{
    outputSize *= outputShape.d[i];
}
std::vector<float>  inputH0(inputSize, 1.0f), outputH0(outputSize, 0.0f);
std::vector<void *> bufferD = {nullptr, nullptr};
cudaMalloc(&bufferD[0], sizeof(float) * inputSize);
cudaMalloc(&bufferD[1], sizeof(float) * outputSize);
for (int i = 0; i < inputSize; i++)
{
    inputH0[i] = (float)i;
}

cudaMemcpy(bufferD[0], inputH0.data(), sizeof(float) * inputSize, cudaMemcpyHostToDevice);
//3.执行推理(Execute),支持同步和异步
context->executeV2(bufferD.data());
//4.推理结果device to host的copy
cudaMemcpy(outputH0.data(), bufferD[1], sizeof(float) * outputSize, cudaMemcpyDeviceToHost);

print(inputH0, context->getBindingDimensions(0), std::string(engine->getBindingName(0)));
print(outputH0, context->getBindingDimensions(1), std::string(engine->getBindingName(1)));
//5.释放显存
cudaFree(bufferD[0]);
cudaFree(bufferD[1]);
  1. 使用TensorRT的方式

可以通过以下几种方式使用TensorRT

  • 使用训练框架自带的TensorRT接口比如TF-TRT和Torch-TensorRT,该方式简单灵活,训练和部署框架统一,不需要书写Plugin。
  • 使用Parser方式,目前主流的方式是将TensorFlow,Pytorch等训练框架训练的模型转换为ONNX中间格式,TensorRT通过ONNXParser构建模型进行推理加速,当然TensorRT也支持专门针对于TensorFlow的UFFParser和Caffe的CaffeParser,但在未来将被废弃统一转换到ONNXParser。该方式流程成熟,因ONNX通用性强并且方便网络的调整和修改,效率和性能适中,且TensorRT开发了对应的工具比如onnx-graphsurgeon可以方便高效的对ONNX进行修改等操作,是推荐的方式。
  • 使用TensorRT原生API搭建网络的方式,该方式也是本文针对于YOLACT采用的TensorRT推理加速方式,该方式优化性能是最优的,并且可以比较精细化的控制网络,兼容性也是最好的,这也是我们为什么选择采用TensorRT API的方式优化YOLACT的原因。

img

✍YOLACT用到的TensorRT API介绍

TensorRT API提供了相对完整的训练框架中出现的算子的实现,比如常用的卷积,池化,激活,切片,拼接,循环和Plugin调用的API等, 比较好的一点是TensorRT提供了比较全面和详细的API说明文档,并且近期NVIDIA的团队也开源了针对于中文的cookbook。关于详细的API支持的Layer和OP可以参考API说明文档, 这里仅列出YOLACT使用到的部分Layer的API

LayerAPI说明链接
input层network.add_input()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_input#tensorrt.INetworkDefinition.add_input
卷积层network.add_convolution()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_convolution#tensorrt.INetworkDefinition.add_convolution
激活层network.add_activation()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_activation#tensorrt.INetworkDefinition.add_activation
池化层network.add_pooling()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_pooling#tensorrt.INetworkDefinition.add_pooling
逐元素运算层network.add_elementwise()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_elementwise#tensorrt.INetworkDefinition.add_elementwise
resize层network.add_resize()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_resize#tensorrt.INetworkDefinition.add_resize
shuffle层network.add_shuffle()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_shuffle#tensorrt.INetworkDefinition.add_shuffle
concat层network.add_concatenation()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_concatenation#tensorrt.INetworkDefinition.add_concatenation
constant层network.add_constant()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_constant#tensorrt.INetworkDefinition.add_constant
slice层network.add_slice()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_slice#tensorrt.INetworkDefinition.add_slice
plugin层network.add_plugin_v2()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=add_plugin_v2#tensorrt.INetworkDefinition.add_plugin_v2
output层network.mark_output()https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Graph/Network.html?highlight=mark_output#tensorrt.INetworkDefinition.mark_output

下面我们将用到上表中描述的Layer API构建YOLACT并实现TensorRT的加速。

✍YOLACT的TensorRT API实现

我们将从构建阶段和运行阶段两个部分说明如何通过TensorRT API搭建YOLACT并实现推理加速,因部分代码过长,整个项目的完整代码已经寄放在Github:https://github.com/DataXujing/yolact_tensorrt_api ,可以在Github获取完整项目代码。

  • 构建阶段
  1. 加载模型权重,建立Logger,建立Builder和BuilderConfig
import os
import numpy as np
import tensorrt as trt
import pycuda.autoinit
import pycuda.driver as cuda

params = np.load("./yolact_weights.npz")
logger = trt.Logger(trt.Logger.VERBOSE) 

trt.init_libnvinfer_plugins(logger, '')
PLUGIN_CREATORS = trt.get_plugin_registry().plugin_creator_list

EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
builder = trt.Builder(logger)
network = builder.create_network(EXPLICIT_BATCH)

config = builder.create_builder_config()
config.max_workspace_size = 1 << 30
  1. 创建Network计算图的内容, 该过程主要封装为一个方法yolact_trt_api,并使用上表中的TensorRT API进行逐层搭建,因代码过长,完整代码可以在Github:https://github.com/DataXujing/yolact_tensorrt_api获得。
def yolact_trt_api(network,config):
    #0.input: yolact_input  1x3x550x550  output: yolact_input
    yolact_input = network.add_input("yolact_input",trt.DataType.FLOAT,(1,3,550,550))
    origin_image_w = network.add_input("origin_image_w",trt.DataType.FLOAT,(1,1,1))
    origin_image_h = network.add_input("origin_image_h",trt.DataType.FLOAT,(1,1,1))
    #1.Conv_0: w: 661 [64,3,7,7] 
    #          b: 662 [64]               input: yolact_input, output: 660
    conv_0 = network.add_convolution(input=yolact_input,num_output_maps=64,kernel_shape=(7, 7), kernel=params["661"],bias=params["662"])
    conv_0.stride = (2,2)
    conv_0.padding = (3,3)
    conv_0.dilation = (1,1)
    conv_0.num_groups= 1
    #2.Relu_1: input: 660, output: 357
    relu_1 = network.add_activation(input=conv_0.get_output(0), type=trt.ActivationType.RELU)

    #3.MaxPool_2: input:357,output:358
    maxpool_2 =  network.add_pooling(input=relu_1.get_output(0), type=trt.PoolingType.MAX, window_size=(3, 3))
    maxpool_2.stride = (2,2)
    maxpool_2.padding = (1,1)
    
    #=========== 此处省略N行代码=================
    
    # ***添加plugin***
    # 这里我们用了一个NMS的Plugin,该Plugin是我们基于TensorRT的BatchedNMS Plugin做的修改,我们称为MdcBatchedNMS Plugin
    # 因Plugin的介绍不是本博文的重点,相关Plugin可以通过Github联系小编
    mdcbatchednms_trt = network.add_plugin_v2(inputs=[reshape_box.get_output(0),softmax_244.get_output(0)], plugin=get_trt_plugin("MdcBatchedNMS_TRT"))

    mdcbatchednms_trt.get_output(0).name = "num_detections"
    network.mark_output(mdcbatchednms_trt.get_output(0))

    mdcbatchednms_trt.get_output(1).name = "nmsed_boxes"
    network.mark_output(mdcbatchednms_trt.get_output(1))

    mdcbatchednms_trt.get_output(2).name = "nmsed_scores"
    network.mark_output(mdcbatchednms_trt.get_output(2))

    mdcbatchednms_trt.get_output(3).name = "nmsed_classes"
    network.mark_output(mdcbatchednms_trt.get_output(3))

    mdcbatchednms_trt.get_output(4).name = "nmsed_indices"
    network.mark_output(mdcbatchednms_trt.get_output(4))
    return network, config

# MdcBatchedNMS_TRT plugin层的构建
def get_trt_plugin(plugin_name):
    plugin = None
    for plugin_creator in PLUGIN_CREATORS:
        if plugin_creator.name == plugin_name:
            shareLocation = trt.PluginField("shareLocation", np.array([1],dtype=np.int64), trt.PluginFieldType.INT32)
            backgroundLabelId = trt.PluginField("backgroundLabelId", np.array([0],dtype=np.int64), trt.PluginFieldType.INT32)
            numClasses = trt.PluginField("numClasses", np.array([3],dtype=np.int64), trt.PluginFieldType.INT32)
            topK = trt.PluginField("topK", np.array([1000],dtype=np.int64), trt.PluginFieldType.INT32)
            keepTopK = trt.PluginField("keepTopK", np.array([20],dtype=np.int64), trt.PluginFieldType.INT32)
            scoreThreshold = trt.PluginField("scoreThreshold", np.array([0.95],dtype=np.float32), trt.PluginFieldType.FLOAT32)
            iouThreshold = trt.PluginField("iouThreshold", np.array([0.30],dtype=np.float32), trt.PluginFieldType.FLOAT32)
            isNormalized = trt.PluginField("isNormalized", np.array([0],dtype=np.int64), trt.PluginFieldType.INT32)
            clipBoxes = trt.PluginField("clipBoxes", np.array([0],dtype=np.int64), trt.PluginFieldType.INT32)
            scoreBits = trt.PluginField("scoreBits", np.array([16],dtype=np.int64), trt.PluginFieldType.INT32)
            plugin_version = trt.PluginField("plugin_version", np.array(["1"],dtype=np.string_), trt.PluginFieldType.CHAR)

            field_collection = trt.PluginFieldCollection([shareLocation,backgroundLabelId,numClasses,topK,keepTopK,scoreThreshold,iouThreshold,
                isNormalized,clipBoxes,scoreBits,plugin_version])
            plugin = plugin_creator.create_plugin(name=plugin_name, field_collection=field_collection)
    return plugin
  1. 构建生成TensorRT Engine
network,config = yolact_trt_api(network,config)
plan = builder.build_serialized_network(network, config)
# engine = runtime.deserialize_cuda_engine(plan)
#save engine
with open("yolact_api.engine", "wb") as f:
    f.write(plan)
  • 运行阶段
  1. 创建Engine
#load engine
if os.path.exists("yolact_api.engine"):
    # If a serialized engine exists, load it instead of building a new one.
    print("Reading engine from file {}".format("yolact_api.engine"))
    with open("yolact_api.engine", "rb") as f, trt.Runtime(logger) as runtime:
        engine = runtime.deserialize_cuda_engine(f.read())    
  1. 创建context
context = engine.create_execution_context()
  1. 数据准备
stream = cuda.Stream()
input_data = np.arange(1*3*550*550,dtype=np.float32).reshape(1,3,550,550)
input_data_w = np.array([1920],dtype=np.float32).reshape(1,1,1)
input_data_h = np.array([1080],dtype=np.float32).reshape(1,1,1)

inputH0 = np.ascontiguousarray(input_data.reshape(-1))
inputD0 = cuda.mem_alloc(inputH0.nbytes)

inputH1 = np.ascontiguousarray(input_data_w.reshape(-1))
inputD1 = cuda.mem_alloc(inputH1.nbytes)

inputH2 = np.ascontiguousarray(input_data_h.reshape(-1))
inputD2 = cuda.mem_alloc(inputH2.nbytes)

# 内存到显存的数据copy
cuda.memcpy_htod_async(inputD0,inputH0,stream)
cuda.memcpy_htod_async(inputD1,inputH1,stream)
cuda.memcpy_htod_async(inputD2,inputH2,stream)
  1. 执行推理(Execute)
context.execute_async_v2([int(inputD0),int(inputD1),int(inputD2),int(outputD0),int(outputD1),int(outputD2),int(outputD3),int(outputD4),int(outputD5),int(outputD6)],stream.handle)
  1. 推理结果获取
cuda.memcpy_dtoh_async(outputH0,outputD0,stream)
cuda.memcpy_dtoh_async(outputH1,outputD1,stream)
cuda.memcpy_dtoh_async(outputH2,outputD2,stream)
cuda.memcpy_dtoh_async(outputH3,outputD3,stream)
cuda.memcpy_dtoh_async(outputH4,outputD4,stream)
cuda.memcpy_dtoh_async(outputH4,outputD5,stream)
cuda.memcpy_dtoh_async(outputH4,outputD6,stream)

stream.synchronize()

按照上述过程即可完整的基于TensorRT构建YOLACT并进行加速推断,其过程可以迁移到其他网络的搭建和构建,过程是一致的。

✍YOLACT模型加速推断结果展示

通过TensorRT API的方式我们主要实现了如下内容:

  • 基于TensorRT API重新搭建了YOLACT并构建TensorRT Engine,提供了完整的API模型搭建,构建Engine,运行Engine的过程和代码。并提供了部分TensorRT API的使用方法。
  • 基于TensorRT API和TensorRT Plugin实现了YOLACT的后处理, 实现YOLACT的端到端的推断加速,这里的端到端是指基于input数据调用TensorRT Engine得到的output结果不需要做任何额外的操作可以直接绘制在原图得到预测结果,实现真正的端到端推断。

img

✍参考链接

[1]. https://arxiv.org/pdf/1904.02689.pdf
[2]. https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html
[3]. https://docs.nvidia.com/deeplearning/tensorrt/api/c_api
[4]. https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/
[5]. https://developer.nvidia.com/nvidia-tensorrt-download
[6]. https://github.com/NVIDIA/trt-samples-for-hackathon-cn/tree/master/cookbook
[7]. https://github.com/dbolya/yolact/
[8]. https://blog.csdn.net/wh8514/article/details/105520870
[9]. https://github.com/DataXujing/yolact_tensorrt_api

...全文
1727 2 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
qwmwysr 2023-06-01
  • 打赏
  • 举报
回复

img

陈小春学安全 2023-01-03
  • 打赏
  • 举报
回复

很不错的内容,干货满满,已支持师傅,期望师傅能输出更多干货,并强烈给师傅五星好评

另外,如果可以的话,期待师傅能给正在参加年度博客之星评选的我一个五星好评,您的五星好评都是对我的支持与鼓励(帖子中有大额红包惊喜哟,不要忘记评了五星后领红包哟)
⭐ ⭐ ⭐ ⭐ ⭐ 博主信息⭐ ⭐ ⭐ ⭐ ⭐
博主:橙留香Park
本人原力等级:5
链接直达:https://bbs.csdn.net/topics/611387568
微信直达:Blue_Team_Park
⭐ ⭐ ⭐ ⭐ ⭐ 五星必回!!!⭐ ⭐ ⭐ ⭐ ⭐

点赞五星好评回馈小福利:抽奖赠书 | 总价值200元,书由君自行挑选(从此页面参与抽奖的同学,只需五星好评后,参与抽奖)

1,339

社区成员

发帖
与我相关
我的任务
社区描述
NVIDIA 开发者技术交流
人工智能 企业社区
社区管理员
  • nvdev
  • 活动通知
  • AI_CUDA_Training
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧