赞
踩
tensorRT是NVIDIA推出的用于深度学习模型加速的工具库,将Yolov5/Yolov8与TensorRT结合使用,可以在NVIDIA的GPU上进行高效推理。使用C++中使用TensorRT加载和运行Yolov5/Yolov8模型流程主要分为以下三步,
- 导出Yolov5/Yolov8的ONNX格式模型。
- 将ONNX模型序列化,首先实例化Logger,然后创建Builder/Network对象,使用Parser解析ONNX模型构建Network,再设置Config参数优化网络,最终转换为序列化模型,保存为TensorRT引擎。
- 加载引擎并反序列化模型,为输入分配内存,首先拷贝模型输入数据(HostToDevice)执行模型推理,然后拷贝模型输出数据(DeviceToHost)解析结果。
附上代码链接:
完整代码:https://download.csdn.net/download/qq_36801705/89633517
环境配置:https://blog.csdn.net/qq_36801705/article/details/141056249
参考链接:https://mp.weixin.qq.com/s/tWd-o4sRV6EMOAd1b4WFHQ
python export.py --weights yolov5s.pt --include onnx --imgsz 640 640
tensorRT所有的接口都存放在命名空间nvinfer1中,首先要实例化ILogger接口,然后再创建IBuilder对象。
class Logger : public nvinfer1::ILogger {
public:
explicit Logger(nvinfer1::ILogger::Severity severity =
nvinfer1::ILogger::Severity::kWARNING)
: severity_(severity) {}
void log(nvinfer1::ILogger::Severity severity,
const char* msg) noexcept override {
if (severity <= severity_) {
std::cerr << msg << std::endl;
}
}
nvinfer1::ILogger::Severity severity_;
}gLogger;
创建builder对象后,继续构建模型的网络结构,代码如下:
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(gLogger);
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
由于ONNX模型是现成的,所以这里采用onnx解析器直接从ONNX模型中解析出模型的网络结构。ONNX解析器接口被封装在头文件NvOnnxParser.h中,命名空间为nvonnxparser。创建ONNX解析器对象并加载模型的代码如下:
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
// onnx文件解析类
// 将onnx文件解析,并填充rensorRT网络结构
nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, gLogger);
// 解析onnx文件
parser->parseFromFile(onnx_file_path.c_str(), 2);
for (int i = 0; i < parser->getNbErrors(); ++i) {
std::cout << "load error: " << parser->getError(i)->desc() << std::endl;
}
模型解析成功后,需要创建一个IBuilderConfig对象来告诉TensorRT该如何对模型进行优化,这一步可以设置工作空间的最大容量与模型的数据精度,TensorRT默认的数据精度为FP32,还可以根据硬件平台是否支持选择FP16或者INT8:
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
// 设置最大工作空间大小。
config->setMaxWorkspaceSize(1 << 30); //2的30次方的大小
// 设置模型输出精度
if (usefp)
config->setFlag(nvinfer1::BuilderFlag::kFP16);
else
config->setFlag(nvinfer1::BuilderFlag::kINT8);
另外还可以对动态模型预设定尺寸:
// 动态模型预设尺寸,可根据自己实际情况设置。 if (mInputDims.d[0] < 1) { nvinfer1::Dims minInputSize = Dims4(1, 3, 640, 640); nvinfer1::Dims medInputSize = Dims4(1, 3, 640, 640); nvinfer1::Dims maxInputSize = Dims4(1, 3, 640, 640); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMIN, minInputSize); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kOPT, medInputSize); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMAX, maxInputSize); } else { profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMIN, mInputDims); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kOPT, mInputDims); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMAX, mInputDims); }
最后则是启动引擎优化模型,优化后的序列化模型被保存到IHostMemory对象中,可以保存到本地磁盘(.engine文件),方便下次直接加载省去优化的时间。
完整代码:
void onnx_to_engine(std::string onnx_file_path, std::string engine_file_path, bool usefp) { // 构建器,获取cuda内核目录以获取最快的实现 // 用于创建config、network、engine的其他对象的核心类 nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(gLogger); const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH); // 显式批处理 // 解析onnx网络文件 // tensorRT模型类 nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch); // onnx文件解析类 // 将onnx文件解析,并填充rensorRT网络结构 nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, gLogger); // 解析onnx文件 parser->parseFromFile(onnx_file_path.c_str(), 2); for (int i = 0; i < parser->getNbErrors(); ++i) { std::cout << "load error: " << parser->getError(i)->desc() << std::endl; } cout << "tensorRT load mask onnx model successfully!!!...\n" << endl; // 创建推理引擎 // 创建生成器配置对象。 nvinfer1::IBuilderConfig* config = builder->createBuilderConfig(); // 设置最大工作空间大小。 config->setMaxWorkspaceSize(1 << 30); //2的30次方的大小 // 设置模型输出精度 if (usefp) config->setFlag(nvinfer1::BuilderFlag::kFP16); else config->setFlag(nvinfer1::BuilderFlag::kINT8); auto profile = builder->createOptimizationProfile(); assert(network->getNbInputs() == 1); nvinfer1::Dims mInputDims = network->getInput(0)->getDimensions(); auto mInputName = network->getInput(0)->getName(); assert(mInputDims.nbDims == 4); // 动态模型预设尺寸,可根据自己实际情况设置。 if (mInputDims.d[0] < 1) { nvinfer1::Dims minInputSize = Dims4(1, 3, 640, 640); nvinfer1::Dims medInputSize = Dims4(1, 3, 640, 640); nvinfer1::Dims maxInputSize = Dims4(1, 3, 640, 640); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMIN, minInputSize); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kOPT, medInputSize); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMAX, maxInputSize); } else { profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMIN, mInputDims); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kOPT, mInputDims); profile->setDimensions(mInputName, nvinfer1::OptProfileSelector::kMAX, mInputDims); } config->addOptimizationProfile(profile); // 创建推理引擎 nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config); // 将推理引擎保存到本地 std::cout << "try to save engine file now....." << std::endl; std::ofstream engine_file(engine_file_path, std::ios::binary); if (!engine_file) { std::cerr << "could not open plan output file" << std::endl; return; } // 将模型转化为文件流数据 nvinfer1::IHostMemory* engine_stream = engine->serialize(); // 将文件保存到本地 engine_file.write(reinterpret_cast<const char*>(engine_stream->data()), engine_stream->size()); // 销毁创建的对象 engine_stream->destroy(); engine->destroy(); network->destroy(); parser->destroy(); std::cout << "convert onnx model to TensorRT engine model successfully!" << std::endl; }
模型推理流程如下所示:
首先要从本地加载.engine文件以及反序列化
char* serialized_engine{ nullptr }; //char* serialized_engine==nullptr; 开辟空指针后 要和new配合使用 size_t serialized_size{ 0 }; std::ifstream file(modelPath, std::ios::binary); if (file.good()) { std::cout << "load engine success!" << std::endl; file.seekg(0, file.end); serialized_size = file.tellg(); file.seekg(0, file.beg); serialized_engine = new char[serialized_size]; file.read(serialized_engine, serialized_size); file.close(); } else { std::cout << "load engine failed!" << std::endl; std::abort(); } Logger logger; // 反序列化引擎 initLibNvInferPlugins(&logger, ""); this->runtime = nvinfer1::createInferRuntime(logger); // 推理引擎 // 保存模型的模型结构、模型参数以及最优计算kernel配置; // 不能跨平台和跨TensorRT版本移植 this->engine = runtime->deserializeCudaEngine(serialized_engine, serialized_size);
然后通过createExecutionContext()函数创建一个IExecutionContext对象来管理推理的过程。由于模型的推理是在GPU上进行的,所以会存在搬运输入、输出数据的操作,因此有必要在GPU上创建内存区域用于存放输入、输出数据。模型输入、输出的尺寸可以通过ICudaEngine对象的接口获取,根据这些信息我们可以先为模型分配输入、输出缓存区:
// 由engine创建,可创建多个对象,进行多推理任务
this->context = engine->createExecutionContext(); int kInputIndex = engine->getBindingIndex(kInputNodeName); auto kIutputDims = engine->getBindingDimensions(kInputIndex); this->kInputSize = 1; this->kInputShape = {std::max((int)kIutputDims.d[0], 1), (int)kIutputDims.d[1], (int)kIutputDims.d[2], (int)kIutputDims.d[3] }; for(int i=0; i< kInputShape.size();i++) { kInputSize *= kInputShape[i]; } kOutputIndexDet = engine->getBindingIndex(this->kOutputNodeDet); auto kOutputDimsDet = engine->getBindingDimensions(kOutputIndexDet); kOutputShapeDet = { std::max((int)kOutputDimsDet.d[0], 1), (int)kOutputDimsDet.d[1], (int)kOutputDimsDet.d[2]}; this-> kOutputSizeDet = 1; for (int i = 0; i < kOutputShapeDet.size(); i++) { kOutputSizeDet *= kOutputShapeDet[i]; } 由于seg和det任务的输出维度是不一样的,同一种任务yoloV8和yoloV5的输出的维度也存在区别,以检测任务为例,V5的输出维度[1,25200,85],V8维度为[1,84,8400],因此可以根据输出维度判断模型和任务的类别: this->yolov8 = kOutputShapeDet[1] < kOutputShapeDet[2] ? true : false; this->netWidth = kOutputShapeDet[1] < kOutputShapeDet[2] ? kOutputShapeDet[1] : kOutputShapeDet[2]; int numOutputs = engine->getNbBindings(); if (numOutputs > 2) { this->instSeg = true; std::cout << "Instance Segmentation" << std::endl; kOutputIndexSeg = engine->getBindingIndex(this->kOutputNodeSeg); auto kOutputIDimsSeg = engine->getBindingDimensions(kOutputIndexSeg); vector<int>kOutputIShapeSeg = { std::max((int)kOutputIDimsSeg.d[0], 1), (int)kOutputIDimsSeg.d[1], (int)kOutputIDimsSeg.d[2], (int)kOutputIDimsSeg.d[3] }; /*kOutputIShapeSeg[2] = kOutputIShapeSeg[2] <= 1 ? this->widthSeg : kOutputIShapeSeg[2]; kOutputIShapeSeg[3] = kOutputIShapeSeg[3] <= 1 ? this->heightSeg : kOutputIShapeSeg[3];*/ this->kOutputSizeSeg = 1; for (int i = 0; i < kOutputIShapeSeg.size(); i++) { kOutputSizeSeg *= kOutputIShapeSeg[i]; } } else std::cout << "Object Detection" << std::endl;
根据预设的大小申请输入输出内存,调用IExecutionContext对象的enqueueV2()函数进行异步地推理操作。模型推理成功后,其输出数据被拷贝到buffer中然后按照对应的输出数据排布规则解析即可。
void Yolo::ainfer(std::vector<float>& input, float* outputs, float* outputsSeg) { void* buffers[3]; // 创建GPU显存输出缓冲区 CheckCuda(cudaMalloc(&buffers[kInputIndex], batchSize * kInputSize * sizeof(float))); CheckCuda(cudaMalloc(&buffers[kOutputIndexDet], batchSize * kOutputSizeDet * sizeof(float))); if (this->instSeg) CheckCuda(cudaMalloc(&buffers[kOutputIndexSeg], batchSize * kOutputSizeSeg * sizeof(float))); // 创建输入cuda流 cudaStream_t stream; CheckCuda(cudaStreamCreate(&stream)); // 输入数据由内存到GPU显存 CheckCuda(cudaMemcpyAsync(buffers[kInputIndex], input.data(), batchSize * kInputSize * sizeof(float), cudaMemcpyHostToDevice, stream)); // 模型推理 this->context->enqueueV2(buffers, stream, nullptr); // 将GPU数据同步到CPU中 CheckCuda(cudaMemcpyAsync(outputs, buffers[kOutputIndexDet], batchSize * kOutputSizeDet * sizeof(float), cudaMemcpyDeviceToHost, stream)); if (this->instSeg) CheckCuda(cudaMemcpyAsync(outputsSeg, buffers[kOutputIndexSeg], batchSize * kOutputSizeSeg * sizeof(float), cudaMemcpyDeviceToHost, stream)); cudaStreamSynchronize(stream); // Release stream and buffers cudaStreamDestroy(stream); CheckCuda(cudaFree(buffers[kInputIndex])); CheckCuda(cudaFree(buffers[kOutputIndexDet])); if (this->instSeg) CheckCuda(cudaFree(buffers[kOutputIndexSeg])); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。