8.1.tensorRT高级(3)封装系列-模型编译过程封装,简化模型编译代码

目录

    • 前言
    • 1. 模型编译过程封装
    • 2. 问答环节
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 高级-模型编译过程封装,简化模型编译代码

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

在这里插入图片描述

1. 模型编译过程封装

我们来开始学习 tensorRT 的封装

1. 对 tensorRT 的封装,更像是对推理引擎的封装

2. 封装的意义在于对技术的标准化、工具化,能够使得使用时更加便利,效率更高,定制更多的默认行为

3. 封装推理引擎的思想,还可以应用到更多其它地方,嵌入式等等。由于大多推理引擎提供的默认方式不够友好,对其进行包装,能够很好的使得自己的代码具有复用性,一套代码多处用

4. 还可以实现同样的封装,通过简单的配置,切换不同的推理后端,这都取决于需求

5. 我们的唯一目的就是让工作更简单,让代码复用性更强,让技术可以沉淀

这节课我们主要学习对 builder 进行封装,使得编译的接口足够简单

我们来看代码,首先是关于 cuda 小工具的封装,代码如下:

cuda-tools.hpp

#ifndef CUDA_TOOLS_HPP
#define CUDA_TOOLS_HPP#include <cuda_runtime.h>
#include <string>#define checkRuntime(call) CUDATools::check_runtime(call, #call, __LINE__, __FILE__)#define checkKernel(...)                                                                             \__VA_ARGS__;                                                                                     \do{cudaError_t cudaStatus = cudaPeekAtLastError();                                               \if (cudaStatus != cudaSuccess){                                                                  \INFOE("launch failed: %s", cudaGetErrorString(cudaStatus));                                  \}} while(0);namespace CUDATools{bool check_runtime(cudaError_t e, const char* call, int iLine, const char *szFile);bool check_device_id(int device_id);int current_device_id();std::string device_description();// 自动切换当前的deviceid,并在析构的时候切换回去class AutoDevice{public:AutoDevice(int device_id = 0);virtual ~AutoDevice();private:int old_ = -1;};
}#endif // CUDA_TOOLS_HPP

cuda-tools.cpp


/**  系统关于CUDA的功能函数*/#include "cuda-tools.hpp"
#include <stdio.h>
#include <stdarg.h>
#include <string>
#include <simple-logger.hpp>using namespace std;namespace CUDATools{bool check_runtime(cudaError_t e, const char* call, int line, const char *file){if (e != cudaSuccess) {INFOE("CUDA Runtime error %s # %s, code = %s [ %d ] in file %s:%d", call, cudaGetErrorString(e), cudaGetErrorName(e), e, file, line);return false;}return true;}bool check_device_id(int device_id){int device_count = -1;checkRuntime(cudaGetDeviceCount(&device_count));if(device_id < 0 || device_id >= device_count){INFOE("Invalid device id: %d, count = %d", device_id, device_count);return false;}return true;}static std::string format(const char* fmt, ...) {va_list vl;va_start(vl, fmt);char buffer[2048];vsnprintf(buffer, sizeof(buffer), fmt, vl);return buffer;}string device_description(){cudaDeviceProp prop;size_t free_mem, total_mem;int device_id = 0;checkRuntime(cudaGetDevice(&device_id));checkRuntime(cudaGetDeviceProperties(&prop, device_id));checkRuntime(cudaMemGetInfo(&free_mem, &total_mem));return format("[ID %d]<%s>[arch %d.%d][GMEM %.2f GB/%.2f GB]",device_id, prop.name, prop.major, prop.minor, free_mem / 1024.0f / 1024.0f / 1024.0f,total_mem / 1024.0f / 1024.0f / 1024.0f);}int current_device_id(){int device_id = 0;checkRuntime(cudaGetDevice(&device_id));return device_id;}AutoDevice::AutoDevice(int device_id){cudaGetDevice(&old_);checkRuntime(cudaSetDevice(device_id));}AutoDevice::~AutoDevice(){checkRuntime(cudaSetDevice(old_));}
}

CUDA 工具集的封装,首先是两个宏定义:

checkRuntime(call): 这是一个用于检查 CUDA 运行时函数调用的宏。它会记录函数的名称、文件名和行号,并在发生错误时输出错误信息。

checkKernel(…): 这个宏用于检查 CUDA 核函数的执行。它首先执行核函数,然后检查其执行是否有任何错误,如果有,它将输出错误信息。

命名空间 CUDATools 中实现了一些函数和类:

  • check_runtime:这个函数检查 CUDA 运行时错误,并打印详细的错误信息,包括文件名、行号、错误代码和错误字符串
  • check_device_id: 这个函数检查给定的设备 ID 是否有效,即是否在有效的范围内
  • current_device_id:这个函数返回当前 CUDA 设备的 ID
  • AutoDevice 类:这是一个 RAII 风格的类,用于自动设置和恢复 CUDA 设备。当你创建这个类的对象时,它会设置指定的设备 ID(默认为0),并在其析构函数中恢复原始的设备 ID

我们再来看下 logger 日志的封装,代码如下:

simple-logger.hpp

#ifndef SIMPLE_LOGGER_HPP
#define SIMPLE_LOGGER_HPP#include <stdio.h>#define INFOD(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Debug, __VA_ARGS__)
#define INFOV(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Verbose, __VA_ARGS__)
#define INFO(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Info, __VA_ARGS__)
#define INFOW(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Warning, __VA_ARGS__)
#define INFOE(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Error, __VA_ARGS__)
#define INFOF(...)			SimpleLogger::__log_func(__FILE__, __LINE__, SimpleLogger::LogLevel::Fatal, __VA_ARGS__)namespace SimpleLogger{enum class LogLevel : int{Debug   = 5,Verbose = 4,Info    = 3,Warning = 2,Error   = 1,Fatal   = 0};void set_log_level(LogLevel level);LogLevel get_log_level();void __log_func(const char* file, int line, LogLevel level, const char* fmt, ...);};  // SimpleLogger#endif // SIMPLE_LOGGER_HPP

simple-logger.cpp


#include "simple-logger.hpp"
#include <string>
#include <stdarg.h>using namespace std;namespace SimpleLogger{static LogLevel g_level = LogLevel::Info;const char* level_string(LogLevel level){switch (level){case LogLevel::Debug: return "debug";case LogLevel::Verbose: return "verbo";case LogLevel::Info: return "info";case LogLevel::Warning: return "warn";case LogLevel::Error: return "error";case LogLevel::Fatal: return "fatal";default: return "unknow";}}void set_log_level(LogLevel level){g_level = level;}LogLevel get_log_level(){return g_level;}string file_name(const string& path, bool include_suffix){if (path.empty()) return "";int p = path.rfind('/');p += 1;//include suffixif (include_suffix)return path.substr(p);int u = path.rfind('.');if (u == -1)return path.substr(p);if (u <= p) u = path.size();return path.substr(p, u - p);}string time_now(){char time_string[20];time_t timep;							time(&timep);							tm& t = *(tm*)localtime(&timep);sprintf(time_string, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);return time_string;}void __log_func(const char* file, int line, LogLevel level, const char* fmt, ...){if(level > g_level) return;va_list vl;va_start(vl, fmt);char buffer[2048];auto now = time_now();string filename = file_name(file, true);int n = snprintf(buffer, sizeof(buffer), "[%s]", now.c_str());if (level == LogLevel::Fatal or level == LogLevel::Error) {n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[31m%s\033[0m]", level_string(level));}else if (level == LogLevel::Warning) {n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[33m%s\033[0m]", level_string(level));}else if (level == LogLevel::Info) {n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[35m%s\033[0m]", level_string(level));}else if (level == LogLevel::Verbose) {n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[34m%s\033[0m]", level_string(level));}else {n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level));}n += snprintf(buffer + n, sizeof(buffer) - n, "[%s:%d]:", filename.c_str(), line);vsnprintf(buffer + n, sizeof(buffer) - n, fmt, vl);fprintf(stdout, "%s\n", buffer);if(level == LogLevel::Fatal || level == LogLevel::Error){fflush(stdout);abort();}}
};

上面是一个简单的日志工具封装。通过定义不同的宏,它为我们提供了方便的日志记录功能

  • 我们是通过使用一些宏 (INFOD, INFOV, INFO, INFOW, INFOE, INFOF),开发者可以轻松地在代码中添加日志消息。
  • 每个宏都将当前的文件名 (__FILE__)、行号 (__LINE__)、日志级别以及日志消息传递给 __log_func 函数。
  • __log_func 是执行实际日志记录的函数。它使用变参列表 (va_list) 和 vsnprintf 函数格式化日志消息,根据日志级别,它为日志消息添加颜色

这个日志工具提供了一个简单而有效的方法来在应用程序中添加日志记录功能。其设计允许开发者轻松地添加、修改和控制日志消息。对于使用者来说找 bug 也好找,对于大型项目来讲 logger 这种基本组件肯定是要有的,不然到时候出了问题调试都操心得很

我们来看下核心的 tensorRT 模型编译的封装部分,代码如下:

trt_builder.hpp

#ifndef TRT_BUILDER_HPP
#define TRT_BUILDER_HPP#include <string>
#include <vector>
#include <functional>namespace TRT {enum class Mode : int {FP32,FP16};const char* mode_string(Mode type);bool compile(Mode mode,unsigned int maxBatchSize,const std::string& source,const std::string& saveto,const size_t maxWorkspaceSize = 1ul << 30                // 1ul << 30 = 1GB);
};#endif //TRT_BUILDER_HPP

trt_builder.cpp


#include "trt_builder.hpp"#include <cuda_runtime_api.h>
#include <cublas_v2.h>
#include <NvInfer.h>
#include <NvInferPlugin.h>
//#include <NvCaffeParser.h>
#include <onnx-tensorrt/NvOnnxParser.h>
#include <string>
#include <vector>
#include <iostream>
#include <memory>
#include <sstream>
#include <assert.h>
#include <stdarg.h>
#include "cuda-tools.hpp"
#include "simple-logger.hpp"
#include <chrono>using namespace nvinfer1;
using namespace std;   
//using namespace nvcaffeparser1  ;class Logger : public ILogger {
public:virtual void log(Severity severity, const char* msg) noexcept override {if (severity == Severity::kINTERNAL_ERROR) {INFOE("NVInfer INTERNAL_ERROR: %s", msg);abort();}else if (severity == Severity::kERROR) {INFOE("NVInfer: %s", msg);}else  if (severity == Severity::kWARNING) {INFOW("NVInfer: %s", msg);}else  if (severity == Severity::kINFO) {INFOD("NVInfer: %s", msg);}else {INFOD("%s", msg);}}
};static Logger gLogger;namespace TRT {static string join_dims(const vector<int>& dims){stringstream output;char buf[64];const char* fmts[] = {"%d", " x %d"};for(int i = 0; i < dims.size(); ++i){snprintf(buf, sizeof(buf), fmts[i != 0], dims[i]);output << buf;}return output.str();}bool save_file(const string& file, const void* data, size_t length){FILE* f = fopen(file.c_str(), "wb");if (!f) return false;if (data && length > 0){if (fwrite(data, 1, length, f) not_eq length){fclose(f);return false;}}fclose(f);return true;}static string format(const char* fmt, ...) {va_list vl;va_start(vl, fmt);char buffer[10000];vsprintf(buffer, fmt, vl);return buffer;}static string dims_str(const nvinfer1::Dims& dims){return join_dims(vector<int>(dims.d, dims.d + dims.nbDims));}static const char* padding_mode_name(nvinfer1::PaddingMode mode){switch(mode){case nvinfer1::PaddingMode::kEXPLICIT_ROUND_DOWN: return "explicit round down";case nvinfer1::PaddingMode::kEXPLICIT_ROUND_UP: return "explicit round up";case nvinfer1::PaddingMode::kSAME_UPPER: return "same supper";case nvinfer1::PaddingMode::kSAME_LOWER: return "same lower";case nvinfer1::PaddingMode::kCAFFE_ROUND_DOWN: return "caffe round down";case nvinfer1::PaddingMode::kCAFFE_ROUND_UP: return "caffe round up";}return "Unknow padding mode";}static const char* pooling_type_name(nvinfer1::PoolingType type){switch(type){case nvinfer1::PoolingType::kMAX: return "MaxPooling";case nvinfer1::PoolingType::kAVERAGE: return "AveragePooling";case nvinfer1::PoolingType::kMAX_AVERAGE_BLEND: return "MaxAverageBlendPooling";}return "Unknow pooling type";}static const char* activation_type_name(nvinfer1::ActivationType activation_type){switch(activation_type){case nvinfer1::ActivationType::kRELU: return "ReLU";case nvinfer1::ActivationType::kSIGMOID: return "Sigmoid";case nvinfer1::ActivationType::kTANH: return "TanH";case nvinfer1::ActivationType::kLEAKY_RELU: return "LeakyRelu";case nvinfer1::ActivationType::kELU: return "Elu";case nvinfer1::ActivationType::kSELU: return "Selu";case nvinfer1::ActivationType::kSOFTSIGN: return "Softsign";case nvinfer1::ActivationType::kSOFTPLUS: return "Parametric softplus";case nvinfer1::ActivationType::kCLIP: return "Clip";case nvinfer1::ActivationType::kHARD_SIGMOID: return "Hard sigmoid";case nvinfer1::ActivationType::kSCALED_TANH: return "Scaled tanh";case nvinfer1::ActivationType::kTHRESHOLDED_RELU: return "Thresholded ReLU";}return "Unknow activation type";}static string layer_type_name(nvinfer1::ILayer* layer){switch(layer->getType()){case nvinfer1::LayerType::kCONVOLUTION: return "Convolution";case nvinfer1::LayerType::kFULLY_CONNECTED: return "Fully connected";case nvinfer1::LayerType::kACTIVATION: {nvinfer1::IActivationLayer* act = (nvinfer1::IActivationLayer*)layer;auto type = act->getActivationType();return activation_type_name(type);}case nvinfer1::LayerType::kPOOLING: {nvinfer1::IPoolingLayer* pool = (nvinfer1::IPoolingLayer*)layer;return pooling_type_name(pool->getPoolingType());}case nvinfer1::LayerType::kLRN: return "LRN";case nvinfer1::LayerType::kSCALE: return "Scale";case nvinfer1::LayerType::kSOFTMAX: return "SoftMax";case nvinfer1::LayerType::kDECONVOLUTION: return "Deconvolution";case nvinfer1::LayerType::kCONCATENATION: return "Concatenation";case nvinfer1::LayerType::kELEMENTWISE: return "Elementwise";case nvinfer1::LayerType::kPLUGIN: return "Plugin";case nvinfer1::LayerType::kUNARY: return "UnaryOp operation";case nvinfer1::LayerType::kPADDING: return "Padding";case nvinfer1::LayerType::kSHUFFLE: return "Shuffle";case nvinfer1::LayerType::kREDUCE: return "Reduce";case nvinfer1::LayerType::kTOPK: return "TopK";case nvinfer1::LayerType::kGATHER: return "Gather";case nvinfer1::LayerType::kMATRIX_MULTIPLY: return "Matrix multiply";case nvinfer1::LayerType::kRAGGED_SOFTMAX: return "Ragged softmax";case nvinfer1::LayerType::kCONSTANT: return "Constant";case nvinfer1::LayerType::kRNN_V2: return "RNNv2";case nvinfer1::LayerType::kIDENTITY: return "Identity";case nvinfer1::LayerType::kPLUGIN_V2: return "PluginV2";case nvinfer1::LayerType::kSLICE: return "Slice";case nvinfer1::LayerType::kSHAPE: return "Shape";case nvinfer1::LayerType::kPARAMETRIC_RELU: return "Parametric ReLU";case nvinfer1::LayerType::kRESIZE: return "Resize";}return "Unknow layer type";}static string layer_descript(nvinfer1::ILayer* layer){switch(layer->getType()){case nvinfer1::LayerType::kCONVOLUTION: {nvinfer1::IConvolutionLayer* conv = (nvinfer1::IConvolutionLayer*)layer;return format("channel: %d, kernel: %s, padding: %s, stride: %s, dilation: %s, group: %d", conv->getNbOutputMaps(),dims_str(conv->getKernelSizeNd()).c_str(),dims_str(conv->getPaddingNd()).c_str(),dims_str(conv->getStrideNd()).c_str(),dims_str(conv->getDilationNd()).c_str(),conv->getNbGroups());}case nvinfer1::LayerType::kFULLY_CONNECTED:{nvinfer1::IFullyConnectedLayer* fully = (nvinfer1::IFullyConnectedLayer*)layer;return format("output channels: %d", fully->getNbOutputChannels());}case nvinfer1::LayerType::kPOOLING: {nvinfer1::IPoolingLayer* pool = (nvinfer1::IPoolingLayer*)layer;return format("window: %s, padding: %s",dims_str(pool->getWindowSizeNd()).c_str(),dims_str(pool->getPaddingNd()).c_str());   }case nvinfer1::LayerType::kDECONVOLUTION:{nvinfer1::IDeconvolutionLayer* conv = (nvinfer1::IDeconvolutionLayer*)layer;return format("channel: %d, kernel: %s, padding: %s, stride: %s, group: %d", conv->getNbOutputMaps(),dims_str(conv->getKernelSizeNd()).c_str(),dims_str(conv->getPaddingNd()).c_str(),dims_str(conv->getStrideNd()).c_str(),conv->getNbGroups());}case nvinfer1::LayerType::kACTIVATION:case nvinfer1::LayerType::kPLUGIN:case nvinfer1::LayerType::kLRN:case nvinfer1::LayerType::kSCALE:case nvinfer1::LayerType::kSOFTMAX:case nvinfer1::LayerType::kCONCATENATION:case nvinfer1::LayerType::kELEMENTWISE:case nvinfer1::LayerType::kUNARY:case nvinfer1::LayerType::kPADDING:case nvinfer1::LayerType::kSHUFFLE:case nvinfer1::LayerType::kREDUCE:case nvinfer1::LayerType::kTOPK:case nvinfer1::LayerType::kGATHER:case nvinfer1::LayerType::kMATRIX_MULTIPLY:case nvinfer1::LayerType::kRAGGED_SOFTMAX:case nvinfer1::LayerType::kCONSTANT:case nvinfer1::LayerType::kRNN_V2:case nvinfer1::LayerType::kIDENTITY:case nvinfer1::LayerType::kPLUGIN_V2:case nvinfer1::LayerType::kSLICE:case nvinfer1::LayerType::kSHAPE:case nvinfer1::LayerType::kPARAMETRIC_RELU:case nvinfer1::LayerType::kRESIZE:return "";}return "Unknow layer type";}static bool layer_has_input_tensor(nvinfer1::ILayer* layer){int num_input = layer->getNbInputs();for(int i = 0; i < num_input; ++i){auto input = layer->getInput(i);if(input == nullptr)continue;if(input->isNetworkInput())return true;}return false;}static bool layer_has_output_tensor(nvinfer1::ILayer* layer){int num_output = layer->getNbOutputs();for(int i = 0; i < num_output; ++i){auto output = layer->getOutput(i);if(output == nullptr)continue;if(output->isNetworkOutput())return true;}return false;}  template<typename _T>shared_ptr<_T> make_nvshared(_T* ptr){return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});}const char* mode_string(Mode type) {switch (type) {case Mode::FP32:return "FP32";case Mode::FP16:return "FP16";default:return "UnknowTRTMode";}}static nvinfer1::Dims convert_to_trt_dims(const std::vector<int>& dims){nvinfer1::Dims output{0};if(dims.size() > nvinfer1::Dims::MAX_DIMS){INFOE("convert failed, dims.size[%d] > MAX_DIMS[%d]", dims.size(), nvinfer1::Dims::MAX_DIMS);return output;}if(!dims.empty()){output.nbDims = dims.size();memcpy(output.d, dims.data(), dims.size() * sizeof(int));}return output;}static string align_blank(const string& input, int align_size, char blank = ' '){if(input.size() >= align_size) return input;string output = input;for(int i = 0; i < align_size - input.size(); ++i)output.push_back(blank);return output;}static long long timestamp_now() {return chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();}static double timestamp_now_float() {return chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() / 1000.0;}bool compile(Mode mode,unsigned int maxBatchSize,const string& source,const string& saveto,const size_t maxWorkspaceSize) {INFO("Compile %s %s.", mode_string(mode), source.c_str());auto builder = make_nvshared(createInferBuilder(gLogger));if (builder == nullptr) {INFOE("Can not create builder.");return false;}auto config = make_nvshared(builder->createBuilderConfig());if (mode == Mode::FP16) {if (!builder->platformHasFastFp16()) {INFOW("Platform not have fast fp16 support");}config->setFlag(BuilderFlag::kFP16);}shared_ptr<INetworkDefinition> network;//shared_ptr<ICaffeParser> caffeParser;const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);network = make_nvshared(builder->createNetworkV2(explicitBatch));shared_ptr<nvonnxparser::IParser> onnxParser = make_nvshared(nvonnxparser::createParser(*network, gLogger));if (onnxParser == nullptr) {INFOE("Can not create parser.");return false;}if (!onnxParser->parseFromFile(source.c_str(), 1)) {INFOE("Can not parse OnnX file: %s", source.c_str());return false;}auto inputTensor = network->getInput(0);auto inputDims = inputTensor->getDimensions();INFO("Input shape is %s", join_dims(vector<int>(inputDims.d, inputDims.d + inputDims.nbDims)).c_str());INFO("Set max batch size = %d", maxBatchSize);INFO("Set max workspace size = %.2f MB", maxWorkspaceSize / 1024.0f / 1024.0f);INFO("Base device: %s", CUDATools::device_description().c_str());int net_num_input = network->getNbInputs();INFO("Network has %d inputs:", net_num_input);vector<string> input_names(net_num_input);for(int i = 0; i < net_num_input; ++i){auto tensor = network->getInput(i);auto dims = tensor->getDimensions();auto dims_str = join_dims(vector<int>(dims.d, dims.d+dims.nbDims));INFO("      %d.[%s] shape is %s", i, tensor->getName(), dims_str.c_str());input_names[i] = tensor->getName();}int net_num_output = network->getNbOutputs();INFO("Network has %d outputs:", net_num_output);for(int i = 0; i < net_num_output; ++i){auto tensor = network->getOutput(i);auto dims = tensor->getDimensions();auto dims_str = join_dims(vector<int>(dims.d, dims.d+dims.nbDims));INFO("      %d.[%s] shape is %s", i, tensor->getName(), dims_str.c_str());}int net_num_layers = network->getNbLayers();INFO("Network has %d layers:", net_num_layers);for(int i = 0; i < net_num_layers; ++i){auto layer = network->getLayer(i);auto name = layer->getName();auto type_str = layer_type_name(layer);auto input0 = layer->getInput(0);if(input0 == nullptr) continue;auto output0 = layer->getOutput(0);auto input_dims = input0->getDimensions();auto output_dims = output0->getDimensions();bool has_input = layer_has_input_tensor(layer);bool has_output = layer_has_output_tensor(layer);auto descript = layer_descript(layer);type_str = align_blank(type_str, 18);auto input_dims_str = align_blank(dims_str(input_dims), 18);auto output_dims_str = align_blank(dims_str(output_dims), 18);auto number_str = align_blank(format("%d.", i), 4);const char* token = "      ";if(has_input)token = "  >>> ";else if(has_output)token = "  *** ";INFOV("%s%s%s %s-> %s%s", token, number_str.c_str(), type_str.c_str(),input_dims_str.c_str(),output_dims_str.c_str(),descript.c_str());}builder->setMaxBatchSize(maxBatchSize);config->setMaxWorkspaceSize(maxWorkspaceSize);auto profile = builder->createOptimizationProfile();for(int i = 0; i < net_num_input; ++i){auto input = network->getInput(i);auto input_dims = input->getDimensions();input_dims.d[0] = 1;profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kMIN, input_dims);profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kOPT, input_dims);input_dims.d[0] = maxBatchSize;profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kMAX, input_dims);}// not need// for(int i = 0; i < net_num_output; ++i){// 	auto output = network->getOutput(i);// 	auto output_dims = output->getDimensions();// 	output_dims.d[0] = 1;// 	profile->setDimensions(output->getName(), nvinfer1::OptProfileSelector::kMIN, output_dims);// 	profile->setDimensions(output->getName(), nvinfer1::OptProfileSelector::kOPT, output_dims);// 	output_dims.d[0] = maxBatchSize;// 	profile->setDimensions(output->getName(), nvinfer1::OptProfileSelector::kMAX, output_dims);// }config->addOptimizationProfile(profile);// error on jetson// auto timing_cache = shared_ptr<nvinfer1::ITimingCache>(config->createTimingCache(nullptr, 0), [](nvinfer1::ITimingCache* ptr){ptr->reset();});// config->setTimingCache(*timing_cache, false);// config->setFlag(BuilderFlag::kGPU_FALLBACK);// config->setDefaultDeviceType(DeviceType::kDLA);// config->setDLACore(0);INFO("Building engine...");auto time_start = timestamp_now();auto engine = make_nvshared(builder->buildEngineWithConfig(*network, *config));if (engine == nullptr) {INFOE("engine is nullptr");return false;}INFO("Build done %lld ms !", timestamp_now() - time_start);// serialize the engine, then close everything downauto seridata = make_nvshared(engine->serialize());return save_file(saveto, seridata->data(), seridata->size());}
}; //namespace TRTBuilder

模型编译封装内容主要可分为以下几部分:(from chatGPT)

1. 日志处理

  • 一个自定义的 Logger 类,该类继承自 ILogger,用于处理 TensorRT 的日志消息。
  • 根据消息的严重性,它将打印不同类型的日志消息。

2. 实用函数

  • join_dims:格式化张量的维度为字符串。
  • save_file:将给定数据保存到文件。
  • format:一个简单的字符串格式化函数。
  • dims_str:将 nvinfer1::Dims 对象转换为字符串表示。

3. compile 编译函数

4. 其他功能:文件中还包含一些其他辅助功能和工具,如时间戳获取、CUDA错误处理等。

5. 错误处理:在整个编译过程中,都有对可能出现的错误的检查,如解析错误、文件写入错误等,并在出错时返回相应的错误信息。

上述封装提供了一个从 ONNX 模型到优化后的 TensorRT 推理引擎的完整流程。其主要目的是简化 TensorRT 的使用,让用户只需调用一个函数即可完成模型的导入、优化和序列化。

我们来重点关注 compile 函数中的内容,主要有以下几部分:

1. TensorRT 构建器初始化

函数首先创建一个 TensorRT 构建器实例,并为其设置日志。这个构建器是 TensorRT 框架中用来创建推理引擎的主要组件。

auto builder = make_nvshared(nvinfer1::createInferBuilder(gLogger));

2. 配置设置

设置 TensorRT 构建器的最大批处理大小和最大工作空间大小。这些参数对于控制推理引擎的资源使用和性能至关重要。

builder->setMaxBatchSize(maxBatchSize);
builder->setMaxWorkspaceSize(maxWorkspaceSize);

3. 精度模式

根据传入的模式(FP16或FP32)来设置构建器的精度模式。如果选择 FP16 模式,构建器会使用半精度浮点数进行计算,这可能会提高性能但牺牲一些精度。

if (mode == Mode::FP16) {builder->setFp16Mode(true);
}

4. 导入ONNX模型

使用 TensorRT 的 ONNX 解析器导入模型。这里从文件中读取 ONNX 模型并将其解析成 TensorRT 可以理解的格式。

auto parser = make_nvshared(nvonnxparser::createParser(*network, gLogger));
if (!parser->parseFromFile(source.c_str(), static_cast<int>(ILogger::Severity::kWARNING))) {return false;
}

5. 优化配置文件

为网络的输入创建优化配置文件。这些配置文件定义了输入的不同大小,从而使 TensorRT 能够为不同大小的输入优化引擎。

auto profile = builder->createOptimizationProfile();
for (int i = 0; i < net_num_input; ++i) {auto input = network->getInput(i);auto input_dims = input->getDimensions();input_dims.d[0] = 1;profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kMIN, input_dims);profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kOPT, input_dims);input_dims.d[0] = maxBatchSize;profile->setDimensions(input->getName(), nvinfer1::OptProfileSelector::kMAX, input_dims);
}
config->addOptimizationProfile(profile);

6. 构建引擎

使用前面的配置和网络信息构建 TensorRT 引擎。这是一个耗时的过程,因为 TensorRT 会尝试多种优化技术来提高模型的推理速度。

auto engine = make_nvshared(builder->buildEngineWithConfig(*network, *config));

7. 序列化引擎

最后,构建好的引擎被序列化,这样就可以在没有原始模型和配置的情况下重新加载它。序列化后的引擎被保存到文件中。

auto seridata = make_nvshared(engine->serialize());
return save_file(saveto, seridata->data(), seridata->size());

这个模型编译的封装代码涵盖了从原始 ONNX 模型到优化后的 TensorRT 推理引擎的整个过程。其简化了 TensorRT 模型编译过程,让用户只需要调用一个函数即可完成模型的编译工作。

二话不说先去执行一下,执行效果如下所示:

在这里插入图片描述

图1-1 builder封装案例

可以看到它会打印一个时间,然后是日志级别,然后是文件名和行号,这是 logger 起到的作用

在编译过程我们也可以获取到它的详细信息,带网络结构的详细信息,如下所示:

在这里插入图片描述

图1-2 详细网络结构信息查看

以上就是关于 builder 的封装,虽然有很多废代码对于性能的提升没有半毛钱关心,但是它有利于我们调试,

2. 问答环节

以下内容是来自于博主与 chatGPT 之间的对话。

问题1:我比较好奇宏定义 checkRuntime 中像 call #call __LINE__ __FILE__ 这种变量没有定义,代码是如何识别的呢?

回答:这些是 C/C++ 预处理器的特性。在宏定义中,您可以使用一些特殊的宏运算符来实现额外的功能。让我们详细解释 checkRuntime 宏中使用的这些元素:

1. call:

  • 在宏定义中,call 是一个宏参数。当您使用这个宏并传入一个参数时,call 会被替换为该参数的值。例如,如果您写 checkRuntime(cudaMalloc(…));,那么在宏扩展中,所有的 call 都会被替换为 cudaMalloc(…)

2. #call:

  • # 是一个宏运算符,称为 “字符串化” 运算符。当它出现在宏定义中并放在一个宏参数前面时,它会将该参数转换为一个字符串字面量。因此,#call 会将 call 参数转换为一个字符串。例如,如果 callcudaMalloc(…),那么 #call 就是 “cudaMalloc(…)”

3. __LINE__ 和 __FILE__:

  • 这些是预定义的宏。它们不需要手动定义。
  • __LINE__ 会被替换为当前的源代码行号。
  • __FILE__ 会被替换为当前的源文件名。

所以,当在代码中写 checkRuntime(cudaMalloc(…)); 时,预处理器会将其扩展为:

CUDATools::check_runtime(cudaMalloc(...), "cudaMalloc(...)", 当前行号, "当前文件名");

这样就可以在 check_runtime 函数中获取到调用的具体函数、行号和文件名,从而使错误报告更加详细和有用。

问题2:在checkKernel中 … 和 __VA_ARGS__ 这两个变量代表又代表啥意思呢?

回答:在 C++ 宏中,__VA_ARGS__ 用于处理可变数量的参数。这是 C++11 引入的变参宏(Variadic Macros)的一部分。

1.

  • 在宏定义的参数列表中, 用于表示该宏可以接受可变数量的参数。
  • 使用 允许您为宏提供任意数量的参数

2. __VA_ARGS__:

  • 在宏的替换部分,您可以使用 __VA_ARGS__ 来引用传递给宏的可变参数。
  • __VA_ARGS__ 将被替换为传递给宏的所有参数(除了已经明确命名的参数之外)。

允许 checkKernel 接受任何数量的参数。然后,宏的替换部分使用 __VA_ARGS__ 来执行这些参数。这允许您直接在 checkKernel 宏前放置 CUDA 核函数调用。

因此当你写下面的代码时:

checkKernel(myKernel<<<blocks, threads>>>(arg1, arg2));

宏会被扩展为:

myKernel<<<blocks, threads>>>(arg1, arg2);
do {cudaError_t cudaStatus = cudaPeekAtLastError();if (cudaStatus != cudaSuccess){INFOE("launch failed: %s", cudaGetErrorString(cudaStatus));}
} while(0);

因此,__VA_ARGS__ 允许您为宏提供可变数量的参数,并在宏的定义中使用这些参数。这为宏提供了更大的灵活性,并允许它们处理各种不同的情况。

总结

本次课程学习了 builder 的封装,tensorRT 的模型编译过程其实很多地方都是通用的,我们完全可以将其封装起来。首先我们封装了一个 cuda-tools 的小工具,里面包含检查 cuda runtime 函数以及 cuda kernel 核函数是否执行成功的定义,另外包含了一些 device 信息的小工具;然后我们封装了一个 logger 日志文件,这个日志文件可以输出对应的时间、日志级别、文件名、行号等,非常有利于我们的调试工作;最后是我们的核心,关于 builder 的封装,我们重点关注了 compile 函数的实现,也就是把我们平时的 tensorRT 编译模型的过程给封装了起来,其中我们通过 logger 日志工具输出了许多信息,甚至可以打印出网络结构的详细信息,非常实用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/66930.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建Docker容器监控系统(cadvisor+influxDB+grafana)

目录 一、部署 1、安装docker-cd 2、阿里云镜像加速 3、下载组件镜像 4、创建自定义网络 5、创建influxdb容器 6、创建Cadvisor 容器 7、创建granafa容器 一、部署 1、安装docker-cd [rootlocalhost ~]# iptables -F [rootlocalhost ~]# setenforce 0 setenforce: SELi…

学习笔记整理-JS-01-语法与变量

文章目录 一、语法与变量1. 初识JavaScript2. JavaScript的历史3. JavaScript与ECMAScript的关系4. JavaScript的体系5. JavaScript的语言风格和特性 二、语法1. JavaScript的书写位置2. 认识输出语句3. REPL环境&#xff0c;交互式解析器4. 变量是什么5. 重点内容 一、语法与变…

NPM包的安装、更新、卸载

目录 1、下载安装全局包 2、解决全局安装包时的EACCES权限错误 2.1 重新安装NPM 2.2 手动更改npm的默认目录 3、更新从注册表下载的包 3.1 更新本地包 3.2 更新全局安装的软件包 3.3 确定哪些全局包需要更新 3.4 更新单个全局包 3.5 更新所有全局安装的软件包 4、在项…

Synopsys EDA数字设计与仿真

参考如下文章安装Synopsys EDA开发工具 https://blog.csdn.net/tugouxp/article/details/132255002?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132255002%22%2C%22source%22%3A%22tugouxp%22%7D Synopsys EDA工具的结构 下…

机器学习终极指南:特征工程(01/2) — 第 -2 部分

西姆兰吉特辛格 一、介绍 欢迎来到“机器学习终极指南”的第二部分。在第一部分中&#xff0c;我们讨论了探索性数据分析 &#xff08;EDA&#xff09;&#xff0c;这是机器学习管道中的关键步骤。在这一部分中&#xff0c;我们将深入研究特征工程&#xff0c;这是机器学习过程…

nginx负载均衡(反向代理)

nginx负载均衡 负载均衡&#xff1a;由反向代理来实现。 nginx的七层代理和四层代理&#xff1a; 七层是最常用的反向代理方式&#xff0c;只能配置在nginx配置文件的http模块当中&#xff0c;而且配置方法名称&#xff1a;upstream模块&#xff0c;不能写在server模块中&#…

app专项测试:app弱网测试

目录 弱网测试背景 网络测试要点 弱网测试关注指标 弱网测试工具 fiddler模拟网络延时场景 网络设置参考 Network Emulator Toolkit模拟网络丢包场景&#xff08;windows网络&#xff09; APP弱网测试 弱网使用工具&#xff1a; app弱网测试要点 APP网络测试要点 网络…

数据结构【哈夫曼树】

哈夫曼树 哈夫曼树的概念哈夫曼树的构造构造算法的实现哈夫曼树应用哈夫曼编码哈夫曼编码的算法实现 哈夫曼树的概念 最优二叉树也称哈夫曼 (Huffman) 树&#xff0c;是指对于一组带有确定权值的叶子结点&#xff0c;构造的具有最小带权路径长度的二叉树。权值是指一个与特定结…

Keburnetes 存储卷 volumes

K8S 的 存储卷 volumes emptyDir 可实现Pod中的容器之间共享目录数据&#xff0c;但emptyDir存储卷没有持久化数据的能力&#xff0c;存储卷会随着Pod生命周期结束而一起删除 &#xff08;一个pod中创建了docker1 docker2两个容器&#xff0c;他们都挂载这个emptyDir&#xff0…

07-2_Qt 5.9 C++开发指南_二进制文件读写(stm和dat格式)

文章目录 1. 实例功能概述2. Qt预定义编码文件的读写2.1 保存为stm文件2.2 stm文件格式2.3 读取stm文件 3. 标准编码文件的读写3.1 保存为dat文件3.2 dat文件格式3.3 读取dat文件 4. 框架及源码4.1 可视化UI设计4.2 mainwindow.cpp 1. 实例功能概述 除了文本文件之外&#xff…

线程记录(2)

1.线程状态 NEW : 分配内存地址&#xff0c;创建线程 RUNNABLE&#xff1a;&#xff08;就绪/运行&#xff09;调用start()之后&#xff08;/没有调度CPU调度&#xff09; BLOCKED&#xff1a;还未拿到锁&#xff0c;等待、被阻塞&#xff08;拿到synchronized失败状态&…

多元最短路(Floyd)

是一个基于动态规划的全源最短路算法。它可以高效地求出图上任意两点之间的最短路 时间复杂度 O(n^3) 状态转移方程 f[i][j]min(f[i][j],f[i][k]f[k][j]) 核心代码 void floyd(){for(int k1;k<n;k)for(int i1;i<n;i)for(int j1;j<n;j)s[i][j]min(s[i][j],s[i][k…