五. TensorRT API的基本使用-infer-model

前言

自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考

本次课程我们来学习课程第五章—TensorRT API 的基本使用,一起来学习手动实现一个 infer

课程大纲可以看下面的思维导图

在这里插入图片描述

0. 简述

本小节目标:手动实现 infer 完成模型的推理

今天我们来讲第五章节第三小节—5.3-load-model 这个案例,上个小节我们自己手动实现了一个 build,这个小节我们主要是自己手动实现一个 infer 完成模型的推理过程

下面我们开始本次课程的学习🤗

1. 案例运行

在正式开始课程之前,博主先带大家跑通 5.3-infer-model 这个小节的案例🤗

源代码获取地址:https://github.com/kalfazed/tensorrt_starter.git

首先大家需要把 tensorrt_starter 这个项目给 clone 下来,指令如下:

git clone https://github.com/kalfazed/tensorrt_starter.git

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/14 日,若有改动请参考最新

整个项目后续需要使用的软件主要有 CUDA、cuDNN、TensorRT、OpenCV,大家可以参考 Ubuntu20.04软件安装大全 进行相应软件的安装,博主这里不再赘述

假设你的项目、环境准备完成,下面我们来一起运行 5.3 小节案例代码

开始之前我们需要创建几个文件夹,在 tensorrt_starter/chapter5-tensorrt-api-basics/5.3-infer-model 小节中创建一个 models 文件夹,接着在 models 文件夹下创建 onnx 和 engine 文件夹,总共三个文件夹需要创建

创建完后 5.3 小节整个目录结构如下:

在这里插入图片描述

接着我们需要执行 python 文件创建一个 ONNX 模型,先进入到 5.3 小节中:

cd tensorrt_starter/chapter5-tensorrt-api-basics/5.3-infer-model

执行如下指令:

python src/python/generate_onnx.py

Note:大家需要准备一个虚拟环境,安装好 torch、onnx、onnxsim 等第三方库

输出如下:

在这里插入图片描述

生成好的 onnx 模型文件保存在 models/onnx 文件夹下,大家可以查看

接着我们需要利用 ONNX 生成对应的 engine,在此之前我们需要修改下整体的 Makefile.config,指定一些库的路径:

# tensorrt_starter/config/Makefile.config
# CUDA_VER                    :=  11
CUDA_VER                    :=  11.6
    
# opencv和TensorRT的安装目录
OPENCV_INSTALL_DIR          :=  /usr/local/include/opencv4
# TENSORRT_INSTALL_DIR        :=  /mnt/packages/TensorRT-8.4.1.5
TENSORRT_INSTALL_DIR        :=  /home/jarvis/lean/TensorRT-8.6.1.6

Note:大家查看自己的 CUDA 是多少版本,修改为对应版本即可,另外 OpenCV 和 TensorRT 修改为你自己安装的路径即可

接着我们就可以来执行编译,指令如下:

make -j64

输出如下:

在这里插入图片描述

接着执行:

./trt-infer

输出如下:

在这里插入图片描述

在这里插入图片描述

我们这里读取一个模型,然后将输入数据打印出来,通过模型的推理拿到对应的输出,将输出数据也打印出来,我们的模型是使用的上节课的 onnx,就是一个 Linear 层

我们在执行下 python 的推理,对比二者是否相同:

在这里插入图片描述

可以看到 C++ 和 Python 的推理结果都是一致的,说明我们的 infer 是没有问题的

如果大家能够看到上述输出结果,那就说明本小节案例已经跑通,下面我们就来看看具体的代码实现

2. 代码分析

2.1 main.cpp

我们先从 main.cpp 看起:

#include <iostream>
#include <memory>

#include "model.hpp"
#include "utils.hpp"

using namespace std;

int main(int argc, char const *argv[])
{
    Model model("models/onnx/sample.onnx");
    if(!model.build()){
        LOGE("fail in building model");
        return 0;
    }
    if(!model.infer()){
        LOGE("fail in infering model");
        return 0;
    }
    return 0;
}

与上节 build 案例相比 main 函数中多了一个 infer 的接口,这个也是仿照着官方 MNIST 案例去写的

2.2 model.cpp

我们来看 infer 接口中具体做了些什么操作,其实 infer 需要做的事情主要包括以下几个部分:

  • 1. 读取 model,创建 runtime,engine,context
  • 2. 把数据进行 host-> device 传输
  • 3. 使用 context 推理
  • 4. 把数据进行 device->host 传输

首先我们来看第一部分:

/* 1. 读取model => 创建runtime, engine, context */
if (!fileExists(mEnginePath)) {
    LOGE("ERROR: %s not found", mEnginePath.c_str());
    return false;
}

/* 反序列化从文件中读取的数据以unsigned char的vector保存*/
vector<unsigned char> modelData;
modelData = loadFile(mEnginePath);

Logger logger;
auto runtime     = make_unique<nvinfer1::IRuntime>(nvinfer1::createInferRuntime(logger));
auto engine      = make_unique<nvinfer1::ICudaEngine>(runtime->deserializeCudaEngine(modelData.data(), modelData.size()));
auto context     = make_unique<nvinfer1::IExecutionContext>(engine->createExecutionContext());

auto input_dims   = context->getBindingDimensions(0);
auto output_dims  = context->getBindingDimensions(1);

LOG("input dim shape is:  %s", printDims(input_dims).c_str());
LOG("output dim shape is: %s", printDims(output_dims).c_str());

我们先查看文件是否存在,接着通过 loadFile 函数加载文件中的数据保存到 modelData 中,然后实例化一个 logger,再通过 nvinfer1::createInferRuntime 实例化一个 runtime 对象,接着从 runtime 反序列化创建 engine,再从 engine 创建 context,到此为止准备工作就已经完成了

接着我们通过 context 的 getBindingDimensions 这个 API 获取到输入和输出的 dims,接着将其打印出来

Note:在 TensorRT 中,Binding 是指模型输入和输出张量与内存的关联,在实际使用中,我们需要确保为每个输入和输出绑定正确分配内存,并通过索引管理这些绑定。

下一部分开始做数据传递:

/* 2. host->device的数据传递 */
cudaStream_t stream;
cudaStreamCreate(&stream);

/* host memory上的数据*/
float input_host[] = {0.0193, 0.2616, 0.7713, 0.3785, 0.9980, 0.9008, 0.4766, 0.1663, 0.8045, 0.6552};
float output_host[5];

/* device memory上的数据*/
float* input_device = nullptr;
float* weight_device = nullptr;
float* output_device = nullptr;

int input_size = 10;
int output_size = 5;

/* 分配空间, 并传送数据从host到device*/
cudaMalloc(&input_device, sizeof(input_host));
cudaMalloc(&output_device, sizeof(output_host));
cudaMemcpyAsync(input_device, input_host, sizeof(input_host), cudaMemcpyKind::cudaMemcpyHostToDevice, stream);

和之前 MNIST 案例一样,我们先定义一个 stream,接着定义下 input 数据,这里是为了方便测试所以与 python 中的数据保持一致,定义 output 变量,接着我们在 device 上定义一些变量,包括 input_device、weight_device、output_device,然后通过 cudaMalloc 分配空间,通过 cudaMemcpyAsync 将 host 上的 input 数据拷贝到 device 上

接着第三步开始推理:

/* 3. 模型推理, 最后做同步处理 */
float* bindings[] = {input_device, output_device};
bool success = context->enqueueV2((void**)bindings, stream, nullptr);

我们将输入和输出绑定起来,然后送到模型中去执行推理

最后我们做数据传递,将 device 上的推理结果传递到 host 上:

/* 4. device->host的数据传递 */
cudaMemcpyAsync(output_host, output_device, sizeof(output_host), cudaMemcpyKind::cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);

LOG("input data is:  %s", printTensor(input_host, input_size).c_str());
LOG("output data is: %s", printTensor(output_host, output_size).c_str());
LOG("finished inference");

数据传递通过 stream 来做一个通过,接着将得到的推理结果打印出来

整个 infer 比较简单,因为没有涉及到预处理和后处理,大家简单了解下就行

2.3 其它

在 src/python 文件夹下还有一个 generate_onnx.py 的脚本文件,其内容如下:

import torch
import torch.nn as nn
import torch.onnx
import onnxsim
import onnx
import os

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(in_features=10, out_features=5, bias=False)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, mean=0., std=1.)
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.wdight, 1)
                nn.init.constant_(m.bias, 0)
    
    def forward(self, x):
        x = self.linear(x)
        return x

def setup_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True

def export_norm_onnx(input, model):
    current_path = os.path.dirname(__file__)
    file = current_path + "/../../models/onnx/sample.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 15)
    print("Finished normal onnx export")

    # check the exported onnx model
    model_onnx = onnx.load(file)
    onnx.checker.check_model(model_onnx)

    # use onnx-simplifier to simplify the onnx
    print(f"Simplifying with onnx-simplifier {onnxsim.__version__}...")
    model_onnx, check = onnxsim.simplify(model_onnx)
    assert check, "assert check failed"
    onnx.save(model_onnx, file)

def eval(input, model):
    output = model(input)
    print("from infer------")
    print(input)
    print(output)

if __name__ == "__main__":
    setup_seed(1)

    input = torch.tensor([[0.0193, 0.2616, 0.7713, 0.3785, 0.9980, 0.9008, 0.4766, 0.1663, 0.8045, 0.6552]])
    model = Model()

    export_norm_onnx(input, model)
    eval(input, model)

它就是创建了一个非常简单的 ONNX 模型,其中包含一个 Linear 节点,如下所示:

在这里插入图片描述

然后准备了一些输入数据进行验证测试

总结

本次课程我们主要在 5.2 小节的案例上增加了 infer 接口完成了模型的推理,整个过程还是比较简单的,首先读取 model 创建 runtime、engine、context,接着把输入数据从 host 上传输到 device 上,然后使用 context 进行推理,最后把推理好的数据从 device 上传输到 host 上打印显示。这里并没有涉及到复杂的预处理和后处理所以整个过程比较清晰,大家了解下就行

OK,以上就是 5.3 小节案例的全部内容了,下节我们来学习 5.4 小节来打印观察下经过 TensorRT 优化前后模型的结构

下载链接

参考

好的,这是一个带参数的命令行程序。以下是一个简单的实现: ```c++ #include <iostream> #include <string> #include <vector> #include <algorithm> int main(int argc, char* argv[]) { // 定义参数变量 std::string model_file; std::string input_file; std::string output_file; std::string infer_order; std::string dump_dir; // 解析命令行参数 for (int i = 1; i < argc; i++) { std::string arg = argv[i]; if (arg.substr(0, 8) == "--model=") { model_file = arg.substr(8); } else if (arg.substr(0, 8) == "--input=") { input_file = arg.substr(8); } else if (arg.substr(0, 9) == "--output=") { output_file = arg.substr(9); } else if (arg.substr(0, 14) == "--infer_order=") { infer_order = arg.substr(14); } else if (arg.substr(0, 7) == "--dump=") { dump_dir = arg.substr(7); } } // 检查必要参数是否存在 if (model_file.empty() || input_file.empty() || output_file.empty() || infer_order.empty()) { std::cerr << "Usage: esim_tool --model=<model.bin> --input=<ifmap.bin> --output=<ofmap.bin> --infer_order=<depthfirst|breadthfirst|random|parallel> [--dump=dump_dir]\n"; return 1; } // 执行模型推理 std::cout << "Model file: " << model_file << "\n"; std::cout << "Input file: " << input_file << "\n"; std::cout << "Output file: " << output_file << "\n"; std::cout << "Infer order: " << infer_order << "\n"; if (!dump_dir.empty()) { std::cout << "Dump directory: " << dump_dir << "\n"; } // 检查infer_order是否合法 std::vector<std::string> valid_orders = {"depthfirst", "breadthfirst", "random", "parallel"}; if (std::find(valid_orders.begin(), valid_orders.end(), infer_order) == valid_orders.end()) { std::cerr << "Invalid infer_order: " << infer_order << "\n"; return 1; } // 执行模型推理... return 0; } ``` 该程序使用 `argc` 和 `argv[]` 从命令行获取参数,并检查必要参数是否存在。如果参数不正确,程序将显示用法信息并退出。否则,程序将显示参数信息并执行模型推理(注意:这里只是一个示例,实际上需要编写更多的代码实现模型推理)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值