当前位置: 首页 > news >正文

jetson orin nano super AI模型部署之路(五)tensorrt C++ api介绍

我们基于tensorrt-cpp-api这个仓库介绍。这个仓库的代码是一个非常不错的tensorrt的cpp api实现,可基于此开发自己的项目。

我们从src/main.cpp开始按顺序说明。

一、首先是声明我们创建tensorrt model的参数。

 // Specify our GPU inference configuration optionsOptions options;// Specify what precision to use for inference// FP16 is approximately twice as fast as FP32.options.precision = Precision::FP16;// If using INT8 precision, must specify path to directory containing// calibration data.options.calibrationDataDirectoryPath = "";// Specify the batch size to optimize for.options.optBatchSize = 1;// Specify the maximum batch size we plan on running.options.maxBatchSize = 1;// Specify the directory where you want the model engine model file saved.options.engineFileDir = ".";

这里Options是在src/engine.h中声明的一个结构体


// 网络的选项配置
struct Options {// 用于 GPU 推理的精度模式// 可选值:FP32(单精度浮点)、FP16(半精度浮点)、INT8(8位量化)Precision precision = Precision::FP16;// 如果选择 INT8 精度,必须提供校准数据集的目录路径std::string calibrationDataDirectoryPath;// 用于计算 INT8 校准数据的批量大小// 应设置为 GPU 能支持的最大批量大小int32_t calibrationBatchSize = 128;// 优化的批量大小// 表示引擎在构建时会针对该批量大小进行优化int32_t optBatchSize = 1;// 最大允许的批量大小// 表示引擎支持的最大批量大小int32_t maxBatchSize = 16;// GPU 设备索引// 用于指定在哪个 GPU 上运行推理int deviceIndex = 0;// 引擎文件保存的目录// 用于存储 TensorRT 引擎文件std::string engineFileDir = ".";// 最大允许的输入宽度// 默认为 -1,表示期望固定的输入大小int32_t maxInputWidth = -1;// 最小允许的输入宽度// 默认为 -1,表示期望固定的输入大小int32_t minInputWidth = -1;// 优化的输入宽度// 默认为 -1,表示期望固定的输入大小int32_t optInputWidth = -1;
};

二、构建tensorrt推理的engine

Engine<float> engine(options);

这里Engine是一个模板类,继承字IEngine类。IEngine 是一个抽象接口类,定义了 TensorRT 推理引擎的核心功能。 它包括构建和加载网络、运行推理、获取输入和输出张量维度等功能。 通过模板参数 T,可以支持不同的数据类型(如 float 或 int)。 具体的实现需要在派生类中完成。

IEngine类的实现在include/interfaces/IEngine.h中。

template <typename T>
class IEngine {
public:// 虚析构函数,确保派生类的资源能够正确释放virtual ~IEngine() = default;// 构建并加载 ONNX 模型到 TensorRT 引擎文件// 参数:// - onnxModelPath: ONNX 模型文件的路径// - subVals: 输入数据的减法均值,用于归一化// - divVals: 输入数据的除法均值,用于归一化// - normalize: 是否对输入数据进行归一化// 返回值:// - 如果成功构建并加载网络,返回 true;否则返回 falsevirtual bool buildLoadNetwork(std::string onnxModelPath, const std::array<float, 3> &subVals = {0.f, 0.f, 0.f},const std::array<float, 3> &divVals = {1.f, 1.f, 1.f}, bool normalize = true) = 0;// 从磁盘加载 TensorRT 引擎文件到内存// 参数:// - trtModelPath: TensorRT 引擎文件的路径// - subVals: 输入数据的减法均值,用于归一化// - divVals: 输入数据的除法均值,用于归一化// - normalize: 是否对输入数据进行归一化// 返回值:// - 如果成功加载网络,返回 true;否则返回 falsevirtual bool loadNetwork(std::string trtModelPath, const std::array<float, 3> &subVals = {0.f, 0.f, 0.f},const std::array<float, 3> &divVals = {1.f, 1.f, 1.f}, bool normalize = true) = 0;// 运行推理// 参数:// - inputs: 输入数据,格式为 [input][batch][cv::cuda::GpuMat]// - featureVectors: 输出数据,格式为 [batch][output][feature_vector]// 返回值:// - 如果推理成功,返回 true;否则返回 falsevirtual bool runInference(const std::vector<std::vector<cv::cuda::GpuMat>> &inputs, std::vector<std::vector<std::vector<T>>> &featureVectors) = 0;// 获取输入张量的维度// 返回值:// - 输入张量的维度列表,类型为 nvinfer1::Dims3virtual const std::vector<nvinfer1::Dims3> &getInputDims() const = 0;// 获取输出张量的维度// 返回值:// - 输出张量的维度列表,类型为 nvinfer1::Dimsvirtual const std::vector<nvinfer1::Dims> &getOutputDims() const = 0;
};

再来看Engine类的实现。

template <typename T>
class Engine : public IEngine<T> {
public:// 构造函数,初始化引擎选项Engine(const Options &options);// 析构函数,释放 GPU 缓冲区~Engine();// 构建并加载 ONNX 模型到 TensorRT 引擎文件// 将模型缓存到磁盘以避免重复构建,并加载到内存中// 默认情况下,输入数据会被归一化到 [0.f, 1.f]// 如果需要 [-1.f, 1.f] 的归一化,可以通过 subVals 和 divVals 参数设置bool buildLoadNetwork(std::string onnxModelPath, const std::array<float, 3> &subVals = {0.f, 0.f, 0.f},const std::array<float, 3> &divVals = {1.f, 1.f, 1.f}, bool normalize = true) override;// 从磁盘加载 TensorRT 引擎文件到内存// 默认情况下,输入数据会被归一化到 [0.f, 1.f]// 如果需要 [-1.f, 1.f] 的归一化,可以通过 subVals 和 divVals 参数设置bool loadNetwork(std::string trtModelPath, const std::array<float, 3> &subVals = {0.f, 0.f, 0.f},const std::array<float, 3> &divVals = {1.f, 1.f, 1.f}, bool normalize = true) override;// 运行推理// 输入格式:[input][batch][cv::cuda::GpuMat]// 输出格式:[batch][output][feature_vector]bool runInference(const std::vector<std::vector<cv::cuda::GpuMat>> &inputs, std::vector<std::vector<std::vector<T>>> &featureVectors) override;// 工具函数:调整图像大小并保持宽高比,通过在右侧或底部填充来实现// 适用于需要将检测坐标映射回原始图像的场景(例如 YOLO 模型)static cv::cuda::GpuMat resizeKeepAspectRatioPadRightBottom(const cv::cuda::GpuMat &input, size_t height, size_t width,const cv::Scalar &bgcolor = cv::Scalar(0, 0, 0));// 获取输入张量的维度[[nodiscard]] const std::vector<nvinfer1::Dims3> &getInputDims() const override { return m_inputDims; };// 获取输出张量的维度[[nodiscard]] const std::vector<nvinfer1::Dims> &getOutputDims() const override { return m_outputDims; };// 工具函数:将三维嵌套的输出数组转换为二维数组// 适用于批量大小为 1 且有多个输出特征向量的情况static void transformOutput(std::vector<std::vector<std::vector<T>>> &input, std::vector<std::vector<T>> &output);// 工具函数:将三维嵌套的输出数组转换为一维数组// 适用于批量大小为 1 且只有一个输出特征向量的情况static void transformOutput(std::vector<std::vector<std::vector<T>>> &input, std::vector<T> &output);// 工具函数:将输入从 NHWC 格式转换为 NCHW 格式,并应用归一化和均值减法static cv::cuda::GpuMat blobFromGpuMats(const std::vector<cv::cuda::GpuMat> &batchInput, const std::array<float, 3> &subVals,const std::array<float, 3> &divVals, bool normalize, bool swapRB = false);private:// 构建网络bool build(std::string onnxModelPath, const std::array<float, 3> &subVals, const std::array<float, 3> &divVals, bool normalize);// 将引擎选项序列化为字符串std::string serializeEngineOptions(const Options &options, const std::string &onnxModelPath);// 获取设备名称列表void getDeviceNames(std::vector<std::string> &deviceNames);// 清除 GPU 缓冲区void clearGpuBuffers();// 输入数据的归一化、缩放和均值减法参数std::array<float, 3> m_subVals{};std::array<float, 3> m_divVals{};bool m_normalize;// 存储输入和输出 GPU 缓冲区的指针std::vector<void *> m_buffers;std::vector<uint32_t> m_outputLengths{};std::vector<nvinfer1::Dims3> m_inputDims;std::vector<nvinfer1::Dims> m_outputDims;std::vector<std::string> m_IOTensorNames;int32_t m_inputBatchSize;// TensorRT 运行时和推理上下文std::unique_ptr<nvinfer1::IRuntime> m_runtime = nullptr;std::unique_ptr<Int8EntropyCalibrator2> m_calibrator = nullptr;std::unique_ptr<nvinfer1::ICudaEngine> m_engine = nullptr;std::unique_ptr<nvinfer1::IExecutionContext> m_context = nullptr;// 引擎选项const Options m_options;// TensorRT 日志器Logger m_logger;
};// 构造函数:初始化引擎选项
template <typename T>
Engine<T>::Engine(const Options &options) : m_options(options) {}// 析构函数:清除 GPU 缓冲区
template <typename T>
Engine<T>::~Engine() { clearGpuBuffers(); }

相较于抽象接口类IEngine,Engine类添加了resizeKeepAspectRatioPadRightBottom、transformOutput、blobFromGpuMats这几个static的public函数。

    // 工具函数:调整图像大小并保持宽高比,通过在右侧或底部填充来实现// 适用于需要将检测坐标映射回原始图像的场景(例如 YOLO 模型)static cv::cuda::GpuMat resizeKeepAspectRatioPadRightBottom(const cv::cuda::GpuMat &input, size_t height, size_t width,const cv::Scalar &bgcolor = cv::Scalar(0, 0, 0));
    // 工具函数:将三维嵌套的输出数组转换为二维数组// 适用于批量大小为 1 且有多个输出特征向量的情况static void transformOutput(std::vector<std::vector<std::vector<T>>> &input, std::vector<std::vector<T>> &output);// 工具函数:将三维嵌套的输出数组转换为一维数组// 适用于批量大小为 1 且只有一个输出特征向量的情况static void transformOutput(std::vector<std::vector<std::vector<T>>> &input, std::vector<T> &output);// 工具函数:将输入从 NHWC 格式转换为 NCHW 格式,并应用归一化和均值减法static cv::cuda::GpuMat blobFromGpuMats(const std::vector<cv::cuda::GpuMat> &batchInput, const std::array<float, 3> &subVals,const std::array<float, 3> &divVals, bool normalize, bool swapRB = false);

在类中,static 修饰的成员函数是 静态成员函数,它与普通成员函数有以下区别:

(1)静态成员函数不依赖于类的实例 静态成员函数属于类本身,而不是类的某个实例。 调用静态成员函数时,不需要创建类的对象,可以直接通过类名调用。

(2)静态成员函数不能访问非静态成员变量 静态成员函数没有 this 指针,因此无法访问类的非静态成员变量或非静态成员函数。 静态成员函数只能访问类的静态成员变量或调用其他静态成员函数。

在这里为什么使用 static?

(1)工具函数的设计 transformOutput 是一个工具函数,用于将三维嵌套的输出数组转换为二维数组。 它的功能与类的实例无关,因此设计为静态函数更合理。 这样可以直接通过类名调用,而不需要创建类的对象。

(2)提高代码的可读性和效率 将与类实例无关的函数声明为静态函数,可以明确表示该函数不依赖于类的状态。 静态函数的调用效率通常比非静态函数更高,因为它不需要隐式传递 this 指针。

第二个修改是对于getInputDims和getOutputDims函数的处理。在抽象接口类IEngine中,其实现方式是

virtual const std::vector<nvinfer1::Dims3> &getInputDims() const = 0;

在Engine类中,其声明变成了

    // 获取输入张量的维度[[nodiscard]] const std::vector<nvinfer1::Dims3> &getInputDims() const override { return m_inputDims; };

[[nodiscard]] 是 C++17 引入的一个属性,用于提示调用者不要忽略函数的返回值。 如果调用者忽略了带有 [[nodiscard]] 属性的函数的返回值,编译器会发出警告。

在这段代码中,getInputDims() 返回的是输入张量的维度信息。如果调用者忽略了这个返回值,可能会导致程序逻辑错误。 使用 [[nodiscard]] 可以提醒开发者注意返回值的重要性。

例如engine.getInputDims(); // 如果没有使用返回值,编译器会发出警告

override 是 C++11 引入的关键字,用于显式声明一个函数是从基类继承并重写的虚函数。 如果函数没有正确地重写基类中的虚函数(例如函数签名不匹配),编译器会报错。

它可以防止由于函数签名错误而导致的意外行为。 在这段代码中,getInputDims() 是从基类 IEngine 中继承并重写的虚函数。

第一个 const: 表示返回的引用是常量,调用者不能修改返回的 std::vector。 第二个 const: 表示该成员函数不会修改类的成员变量,保证函数是只读的。

需要注意的是,在src/engine.h中,使用了模版类的实现,这有点要注意:


// 忽略之前的所有代码// 构造函数:初始化引擎选项
template <typename T>
Engine<T>::Engine(const Options &options) : m_options(options) {}// 析构函数:清除 GPU 缓冲区
template <typename T>
Engine<T>::~Engine() { clearGpuBuffers(); }// Include inline implementations
#include "engine/EngineRunInference.inl"
#include "engine/EngineUtilities.inl"
#include "engine/EngineBuildLoadNetwork.inl"

在文件的最后,才include了这几个头文件。

EngineRunInference.inl: 包含 runInference 函数的实现。

EngineUtilities.inl: 包含工具函数(如 transformOutput)的实现。

EngineBuildLoadNetwork.inl: 包含 buildLoadNetwork 和 loadNetwork 函数的实现。

将 #include 头文件放在文件末尾的原因是为了包含 内联实现文件(.inl 文件),这种设计通常用于模板类或需要将实现与声明分离的情况下。

  1. .inl 文件的作用 .inl 文件通常用于存放模板类或函数的实现。 在 C++ 中,模板类或模板函数的实现必须在编译时可见,因此不能像普通类那样将实现放在 .cpp 文件中。 为了保持代码的清晰和模块化,开发者会将模板类的实现从头文件中分离出来,放入 .inl 文件中。
  2. 为什么将 .inl 文件放在文件末尾? 2.1 避免重复定义 如果在头文件的开头包含 .inl 文件,可能会导致重复定义的问题,尤其是在头文件被多次包含时。 将 .inl 文件放在头文件末尾,可以确保模板类的声明先被处理,然后再处理实现部分。

2.2 确保依赖的声明已完成 .inl 文件中的实现可能依赖于头文件中声明的类或函数。 将 .inl 文件放在头文件末尾,可以确保所有必要的声明都已完成,避免编译错误。

2.3 提高代码的可读性 将 .inl 文件放在头文件末尾,可以将类的声明和实现逻辑分开,增强代码的可读性和可维护性。 开发者可以在头文件中快速查看类的接口,而不需要直接阅读实现细节。 3. 为什么不直接放在 .cpp 文件中? 模板类或模板函数的实现不能放在 .cpp 文件中,因为模板的具体类型是在编译时实例化的,而 .cpp 文件是在链接阶段处理的。 如果将模板实现放在 .cpp 文件中,编译器在实例化模板时将无法找到实现,导致链接错误。

三、tensorrt推理Engine的具体实现

3.1 buildLoadNetwork实现

buildLoadNetwork在include/engine/EngineBuildLoadNetwork.inl中实现。

template <typename T>
bool Engine<T>::buildLoadNetwork(std::string onnxModelPath, const std::array<float, 3> &subVals, const std::array<float, 3> &divVals,bool normalize) {const auto engineName = serializeEngineOptions(m_options, onnxModelPath);const auto engineDir = std::filesystem::path(m_options.engineFileDir);std::filesystem::path enginePath = engineDir / engineName;spdlog::info("Searching for engine file with name: {}", enginePath.string());if (Util::doesFileExist(enginePath)) {spdlog::info("Engine found, not regenerating...");} else {if (!Util::doesFileExist(onnxModelPath)) {auto msg = "Could not find ONNX model at path: " + onnxModelPath;spdlog::error(msg);throw std::runtime_error(msg);}spdlog::info("Engine not found, generating. This could take a while...");if (!std::filesystem::exists(engineDir)) {std::filesystem::create_directories(engineDir);spdlog::info("Created directory: {}", engineDir.string());}auto ret = build(onnxModelPath, subVals, divVals, normalize);if (!ret) {return false;}}return loadNetwork(enginePath, subVals, divVals, normalize);
}

其中,最重要的是build函数的实现。build的实现比较长,该函数的作用是从一个 ONNX 模型文件构建 TensorRT 引擎。 它会解析 ONNX 模型文件,设置优化配置(如动态批量大小、动态输入宽度、精度模式等),并最终生成一个序列化的 TensorRT 引擎文件。

其完整实现如下代码,后面会对其中重要代码展开解释,一并写在代码注释上。

3.2 build函数实现(最重要的函数)

template <typename T>
bool Engine<T>::build(std::string onnxModelPath, const std::array<float, 3> &subVals, const std::array<float, 3> &divVals, bool normalize) {// Create our engine builder.//创建 TensorRT 构建器和网络// nvinfer1::createInferBuilder:// 创建一个 TensorRT 构建器(IBuilder),用于构建和优化网络。// m_logger 是一个自定义的日志器,用于记录 TensorRT 的日志信息。auto builder = std::unique_ptr<nvinfer1::IBuilder>(nvinfer1::createInferBuilder(m_logger));if (!builder) {return false;}// Define an explicit batch size and then create the network (implicit batch// size is deprecated). More info here:// https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#explicit-implicit-batch// builder->createNetworkV2:// 创建一个网络定义(INetworkDefinition),用于描述神经网络的结构。// 使用 kEXPLICIT_BATCH 标志表示显式批量大小(TensorRT 7.0 之后推荐使用)。auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);auto network = std::unique_ptr<nvinfer1::INetworkDefinition>(builder->createNetworkV2(explicitBatch));if (!network) {return false;}// Create a parser for reading the onnx file.//  创建 ONNX 解析器并解析模型// nvonnxparser::createParser:// 创建一个 ONNX 解析器(IParser),用于将 ONNX 模型解析为 TensorRT 的网络定义。auto parser = std::unique_ptr<nvonnxparser::IParser>(nvonnxparser::createParser(*network, m_logger));if (!parser) {return false;}// We are going to first read the onnx file into memory, then pass that buffer// to the parser. Had our onnx model file been encrypted, this approach would// allow us to first decrypt the buffer.// 读取 ONNX 文件:// 将 ONNX 模型文件读入内存缓冲区(buffer),然后传递给解析器。// 这种方式允许对模型文件进行预处理(如解密)。std::ifstream file(onnxModelPath, std::ios::binary | std::ios::ate);std::streamsize size = file.tellg();file.seekg(0, std::ios::beg);std::vector<char> buffer(size);if (!file.read(buffer.data(), size)) {auto msg = "Error, unable to read engine file";spdlog::error(msg);throw std::runtime_error(msg);}// parser->parse:// 解析 ONNX 模型文件,将其转换为 TensorRT 的网络定义。// ONNX 是一种常见的模型格式,TensorRT 提供了专门的解析器来支持 ONNX 模型。// 通过将文件读入内存,可以灵活处理加密或压缩的模型文件。// Parse the buffer we read into memory.auto parsed = parser->parse(buffer.data(), buffer.size());if (!parsed) {return false;}// Ensure that all the inputs have the same batch sizeconst auto numInputs = network->getNbInputs();if (numInputs < 1) {auto msg = "Error, model needs at least 1 input!";spdlog::error(msg);throw std::runtime_error(msg);}// 检查输入的批量大小// TensorRT 要求所有输入的批量大小必须一致。const auto input0Batch = network->getInput(0)->getDimensions().d[0];for (int32_t i = 1; i < numInputs; ++i) {if (network->getInput(i)->getDimensions().d[0] != input0Batch) {auto msg = "Error, the model has multiple inputs, each with differing batch sizes!";spdlog::error(msg);throw std::runtime_error(msg);}}// Check to see if the model supports dynamic batch size or notbool doesSupportDynamicBatch = false;if (input0Batch == -1) {doesSupportDynamicBatch = true;spdlog::info("Model supports dynamic batch size");} else {spdlog::info("Model only supports fixed batch size of {}", input0Batch);// If the model supports a fixed batch size, ensure that the maxBatchSize// and optBatchSize were set correctly.if (m_options.optBatchSize != input0Batch || m_options.maxBatchSize != input0Batch) {auto msg = "Error, model only supports a fixed batch size of " + std::to_string(input0Batch) +". Must set Options.optBatchSize and Options.maxBatchSize to 1";spdlog::error(msg);throw std::runtime_error(msg);}}const auto input3Batch = network->getInput(0)->getDimensions().d[3];bool doesSupportDynamicWidth = false;if (input3Batch == -1) {doesSupportDynamicWidth = true;spdlog::info("Model supports dynamic width. Using Options.maxInputWidth, Options.minInputWidth, and Options.optInputWidth to set the input width.");// Check that the values of maxInputWidth, minInputWidth, and optInputWidth are validif (m_options.maxInputWidth < m_options.minInputWidth || m_options.maxInputWidth < m_options.optInputWidth ||m_options.minInputWidth > m_options.optInputWidth|| m_options.maxInputWidth < 1 || m_options.minInputWidth < 1 || m_options.optInputWidth < 1) {auto msg = "Error, invalid values for Options.maxInputWidth, Options.minInputWidth, and Options.optInputWidth";spdlog::error(msg);throw std::runtime_error(msg);}}//设置优化配置auto config = std::unique_ptr<nvinfer1::IBuilderConfig>(builder->createBuilderConfig());if (!config) {return false;}//设置优化配置,包括动态批量大小和动态输入宽度。// 使用 IOptimizationProfile 定义输入的最小、优化和最大维度。// 为什么要这么写?// TensorRT 允许为动态输入设置优化配置,以便在推理时根据实际输入调整性能。// 通过设置优化配置,可以在性能和灵活性之间取得平衡。// Register a single optimization profilenvinfer1::IOptimizationProfile *optProfile = builder->createOptimizationProfile();for (int32_t i = 0; i < numInputs; ++i) {// Must specify dimensions for all the inputs the model expects.const auto input = network->getInput(i);const auto inputName = input->getName();const auto inputDims = input->getDimensions();int32_t inputC = inputDims.d[1];int32_t inputH = inputDims.d[2];int32_t inputW = inputDims.d[3];int32_t minInputWidth = std::max(m_options.minInputWidth, inputW);// Specify the optimization profile`if (doesSupportDynamicBatch) {optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kMIN, nvinfer1::Dims4(1, inputC, inputH, minInputWidth));} else {optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kMIN,nvinfer1::Dims4(m_options.optBatchSize, inputC, inputH, minInputWidth));}if (doesSupportDynamicWidth) {optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kOPT,nvinfer1::Dims4(m_options.optBatchSize, inputC, inputH, m_options.optInputWidth));optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kMAX,nvinfer1::Dims4(m_options.maxBatchSize, inputC, inputH, m_options.maxInputWidth));} else {optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kOPT,nvinfer1::Dims4(m_options.optBatchSize, inputC, inputH, inputW));optProfile->setDimensions(inputName, nvinfer1::OptProfileSelector::kMAX,nvinfer1::Dims4(m_options.maxBatchSize, inputC, inputH, inputW));}}config->addOptimizationProfile(optProfile);// Set the precision levelconst auto engineName = serializeEngineOptions(m_options, onnxModelPath);if (m_options.precision == Precision::FP16) {// Ensure the GPU supports FP16 inferenceif (!builder->platformHasFastFp16()) {auto msg = "Error: GPU does not support FP16 precision";spdlog::error(msg);throw std::runtime_error(msg);}config->setFlag(nvinfer1::BuilderFlag::kFP16);} else if (m_options.precision == Precision::INT8) {if (numInputs > 1) {auto msg = "Error, this implementation currently only supports INT8 ""quantization for single input models";spdlog::error(msg);throw std::runtime_error(msg);}// Ensure the GPU supports INT8 Quantizationif (!builder->platformHasFastInt8()) {auto msg = "Error: GPU does not support INT8 precision";spdlog::error(msg);throw std::runtime_error(msg);}// Ensure the user has provided path to calibration data directoryif (m_options.calibrationDataDirectoryPath.empty()) {auto msg = "Error: If INT8 precision is selected, must provide path to ""calibration data directory to Engine::build method";throw std::runtime_error(msg);}config->setFlag((nvinfer1::BuilderFlag::kINT8));const auto input = network->getInput(0);const auto inputName = input->getName();const auto inputDims = input->getDimensions();const auto calibrationFileName = engineName + ".calibration";m_calibrator = std::make_unique<Int8EntropyCalibrator2>(m_options.calibrationBatchSize, inputDims.d[3], inputDims.d[2],m_options.calibrationDataDirectoryPath, calibrationFileName, inputName,subVals, divVals, normalize);config->setInt8Calibrator(m_calibrator.get());}// CUDA stream used for profiling by the builder.cudaStream_t profileStream;Util::checkCudaErrorCode(cudaStreamCreate(&profileStream));config->setProfileStream(profileStream);// Build the engine// If this call fails, it is suggested to increase the logger verbosity to// kVERBOSE and try rebuilding the engine. Doing so will provide you with more// information on why exactly it is failing.// 构建引擎并保存到磁盘// 使用 builder->buildSerializedNetwork 构建序列化的 TensorRT 引擎。// 将引擎保存到磁盘,以便后续加载和使用。// 为什么要这么写?// 构建引擎是一个耗时操作,将引擎保存到磁盘可以避免重复构建。// 序列化的引擎文件可以直接加载到内存中,节省时间。std::unique_ptr<nvinfer1::IHostMemory> plan{builder->buildSerializedNetwork(*network, *config)};if (!plan) {return false;}// Write the engine to diskconst auto enginePath = std::filesystem::path(m_options.engineFileDir) / engineName;std::ofstream outfile(enginePath, std::ofstream::binary);outfile.write(reinterpret_cast<const char *>(plan->data()), plan->size());spdlog::info("Success, saved engine to {}", enginePath.string());Util::checkCudaErrorCode(cudaStreamDestroy(profileStream));return true;
}

函数的作用

解析 ONNX 模型文件。

设置优化配置(动态批量大小、动态输入宽度等)。

设置精度模式(FP16 或 INT8)。

构建 TensorRT 引擎并保存到磁盘。

涉及的 TensorRT API

点击jetson orin nano super AI模型部署之路(五)tensorrt C++ api介绍查看全文

相关文章:

jetson orin nano super AI模型部署之路(五)tensorrt C++ api介绍

我们基于tensorrt-cpp-api这个仓库介绍。这个仓库的代码是一个非常不错的tensorrt的cpp api实现&#xff0c;可基于此开发自己的项目。 我们从src/main.cpp开始按顺序说明。 一、首先是声明我们创建tensorrt model的参数。 // Specify our GPU inference configuration optio…...

excel函数操作案例

需求分析1&#xff1a;学习时间与最终成绩之间的关系 问题&#xff1a;学习时间的长短是否对学生的最终成绩有显著影响&#xff1f; 操作步骤&#xff1a;选择"study_hours"和"final_grade"列完整数据&#xff0c;选择散点图 单击B&#xff0c;按住ctrl键…...

各种音频产品及场景总结

本文记录和总结各种音频产品以及音频场景&#xff0c;比如音箱、耳机、对讲机、录音笔、助听器、声卡等等。 蓝牙耳机 蓝牙耳机现在已经很普及了&#xff0c;主要功能就是连着手机等设备然后播放音频&#xff0c;所以&#xff0c;肯定要有扬声器模块&#xff1b;然后还可以接打…...

Java后端开发day46--多线程(二)

&#xff08;以下内容全部来自上述课程&#xff09; 多线程 1. Lock锁 虽然我们可以理解同步代码块和同步方法的锁对象问题&#xff0c; 但是我们并没有直接看到在哪里加上了锁&#xff0c;在哪里释放了锁&#xff0c; 为了更清晰的表达如何加锁和释放锁&#xff0c;JDK5以…...

U盘制作系统盘(含U盘恢复)

✅ 准备工作 1. 一个至少 8GB 容量的 U 盘 注意&#xff1a;U 盘将被格式化&#xff0c;请提前备份数据。 2. 一台可以联网的 Windows 电脑 &#x1f4e5; 下载官方制作工具&#xff08;推荐&#xff09; 1. 打开微软官网下载页面&#xff1a; &#x1f449; Windows 11 下载…...

如何阅读、学习 Linux 2 内核源代码 ?

学习Linux 2内核源代码是深入理解操作系统工作原理的绝佳途径&#xff0c;但这无疑是一项极具挑战性的任务。下面为你提供一套系统的学习方法和建议&#xff1a; 一、扎实基础知识 操作系统原理 透彻掌握进程管理、内存管理、文件系统、设备驱动等核心概念。推荐阅读《操作系…...

【字符函数和字符串函数】

【字符函数和字符串函数】 字符分类函数字符转换函数函数的使用strcpy的使用strcat的实现strcmp的实现strncpy&#xff0c;strncat,strncmpstrstrstrtok的使用strerror 1.函数的使用 2.部分函数的模拟实现&#xff08;工作原理&#xff09; 字符分类函数 ag1. #include<std…...

[学习]RTKLib详解:rtksvr.c与streamsvr.c

本文是 RTKLlib详解 系列文章的一篇&#xff0c;目前该系列文章还在持续总结写作中&#xff0c;以发表的如下&#xff0c;有兴趣的可以翻阅。 [学习] RTKlib详解&#xff1a;功能、工具与源码结构解析 [学习]RTKLib详解&#xff1a;pntpos.c与postpos.c [学习]RTKLib详解&…...

QMK键盘固件开发全解析:QMK 固件开发的最新架构和规范(2025最新版)

QMK键盘固件开发全解析&#xff1a;QMK 固件开发的最新架构和规范&#xff08;2025最新版&#xff09; &#x1f4da; 前言概述 QMK(Quantum Mechanical Keyboard)作为目前开源键盘固件领域的"扛把子"&#xff0c;凭借其强大的功能和活跃的社区支持&#xff0c;已经…...

c++——二叉树进阶

1. 内容安排说明 二叉树在前面C数据结构阶段已经讲过&#xff0c;本节取名二叉树进阶是因为&#xff1a; 1. map和set特性需要先铺垫二叉搜索树&#xff0c;而二叉搜索树也是一种树形结构 2. 二叉搜索树的特性了解&#xff0c;有助于更好的理解map和set的特性 3. 二叉树中部…...

PyTorch API 3 - mps、xpu、backends、导出

文章目录 torch.mpsMPS 性能分析器MPS 事件 torch.xpu随机数生成器流与事件内存管理 torch.mtia流与事件 torch.mtia.memory元设备元张量操作惯用法 torch.backendstorch.backends.cputorch.backends.cudatorch.backends.cudnntorch.backends.cusparselttorch.backends.mhatorc…...

QTableWidget实现多级表头、表头冻结效果

最终效果&#xff1a; 实现思路&#xff1a;如果只用一个表格的话写起来比较麻烦&#xff0c;可以考虑使用两个QTableWidget组合&#xff0c;把复杂的表头一个用QTableWidget显示&#xff0c;其他内容用另一个QTableWidget。 #include "mainwindow.h" #include &qu…...

比 Mac 便笺更好用更好看的便利贴

在苹果电脑上&#xff0c;有自带的便签软件&#xff0c;但问题这个官方应用已经年久失修&#xff0c;界面跟最新的系统完全不搭。像同步、清单等功能也没有。 最近找到了一款更好看好用的桌面便利贴 - Desktop Note。这款应用在超过26个的效率榜排在前10。以下几个点是我认为做…...

【python】json解析:invalid literal for int() with base 10: ‘\“\“‘“

invalid literal for int() with base 10: ‘“”’" 从提供的 JSON 数据中&#xff0c;我可以看到导致 "invalid literal for int() with base 10: \"\"" 错误的具体情况&#xff1a; 错误分析 在 deal_resp 部分中发现了错误信息&#xff1a; &…...

超详细Kokoro-82M本地部署教程

经测试&#xff0c;Kokoro-82M的语音合成速度相比于其他tts非常的快&#xff0c;本文给出Windows版详细本地部署教程。 这里提供原始仓库进行参考&#xff1a;https://github.com/hexgrad/kokoro 一、依赖安装 1.新建conda环境 conda create --n kokoro python3.12 conda a…...

Day28 -js开发01 -JS三个实例:文件上传 登录验证 购物商城 ---逻辑漏洞复现 及 判断js的payload思路

本篇利用3个实例 来引出前端验证的逻辑漏洞 一、文件上传 实例&#xff1a;利用JS实现 【1】代码实现 js&#xff1a;文件后缀筛选 php&#xff1a;文件保存 00x1 先利用js文件上传 就利用之前php原生写的upload.html的模板&#xff0c;再加上script的后缀过滤。 <!…...

宝塔服务安装使用的保姆级教程

宝塔介绍&#xff1a; 宝塔面板&#xff08;BT Panel&#xff09; 是一款 国产的服务器运维管理面板&#xff0c;主要用于简化 Linux/Windows 服务器的网站、数据库、FTP、防火墙等管理操作。它通过图形化界面&#xff08;Web端&#xff09;和命令行工具&#xff08;bt 命令&a…...

(四)YOLO_World-SAM-GraspNet的mujoco抓取仿真(操作记录)

一、创建虚拟环境 这里直接克隆之前项目的环境 &#xff08;二&#xff09;Graspnet在mujoco的仿真复现&#xff08;操作记录&#xff09;_graspnet仿真-CSDN博客 conda create -n graspnet --clone mujoco_graspnet conda activate graspnet 二、安装额外的环境包 pip in…...

Git Github Tutorial

Git & Github Tutorial 教程地址&#xff1a;Git & GitHub Tutorial | Visualized Git Course for Beginner & Professional Developers in 2024 git自动跟踪每个代码更改&#xff0c;允许多个人无缝处理同一个项目&#xff0c;让成员浏览项目历史纪录 1.检查gi…...

提高工作效率的新选择[特殊字符]——Element Plus UI库

在现代前端开发中&#xff0c;UI库的重要性不言而喻。它们不仅加速开发过程&#xff0c;还提高了应用的可维护性&#xff0c;形成了一致的用户体验。今天我们就来介绍一款由Element团队打造的Vue.js 3 UI库——Element Plus。 一、Element Plus&#xff1a;Vue.js 3的全新UI库…...

深入理解 TCP:重传机制、滑动窗口、流量控制与拥塞控制

TCP&#xff08;Transmission Control Protocol&#xff09;是一个面向连接、可靠传输的协议&#xff0c;支撑着绝大多数互联网通信。在实现可靠性的背后&#xff0c;TCP 引入了多个关键机制&#xff1a;重传机制、滑动窗口、流量控制 和 拥塞控制。这些机制共同协作&#xff0…...

从0开始学习大模型--Day05--理解prompt工程

提示词工程原理 N-gram&#xff1a;通过统计&#xff0c;计算N个词共同出现的概率&#xff0c;从而预测下一个词是什么。 深度学习模型&#xff1a;有多层神经网络组成&#xff0c;可以自动从数据中学习特征&#xff0c;让模型通过不断地自我学习不断成长&#xff0c;直到模型…...

全栈开发实战:FastAPI + React + MongoDB 构建现代Web应用

在Web开发领域&#xff0c;技术栈的选型直接影响着开发效率和系统性能。FARM&#xff08;FastAPI, React, MongoDB&#xff09;技术栈凭借其高性能、灵活架构和简洁语法&#xff0c;逐渐成为全栈开发的热门选择。本文将通过实际项目案例&#xff0c;详解如何从零搭建一个完整的…...

深入解析进程地址空间:从虚拟到物理的奇妙之旅

深入解析进程地址空间&#xff1a;从虚拟到物理的奇妙之旅 前言 各位小伙伴&#xff0c;还记得我们之前探讨的 fork 函数吗&#xff1f;当它返回两次时&#xff0c;父子进程中同名变量却拥有不同值的现象&#xff0c;曾让我们惊叹于进程独立性与写时拷贝的精妙设计。但你是否…...

Python教程(四)——数据结构

目录 1. 列表1.1 用列表实现堆栈1.2 用列表实现队列1.3 列表推导式1.4 嵌套的列表推导式 2. del语句3. 元组和序列4. 集合5. 字典6. 循环的技巧7. 深入条件控制8. 序列和其他类型的比较参考 1. 列表 方法含义list.append(x)在列表末尾添加一项&#xff0c;类似于a[len(a):] […...

Spring Cloud: Nacos

Nacos Nacos是阿里巴巴开源的一个服务发现&#xff0c;配置管理和服务管理平台。只要用于分布式系统中的微服务注册&#xff0c;发现和配置管理&#xff0c;nacos是一个注册中心的组件 官方仓库&#xff1a;https://nacos.io/ Nacos的下载 Releases alibaba/nacos 在官网中…...

基于 Q-learning 的城市场景无人机三维路径规划算法研究,可以自定义地图,提供完整MATLAB代码

一、引言 随着无人机技术的不断发展&#xff0c;其在城市环境中的应用越来越广泛&#xff0c;如物流配送、航拍测绘、交通监控等。然而&#xff0c;城市场景具有复杂的建筑布局、密集的障碍物以及多变的飞行环境&#xff0c;给无人机的路径规划带来了巨大的挑战。传统的路径规…...

Block Styler——字符串控件

字符串控件的应用 参考官方帮助案例&#xff1a;&#xff08;这个方式感觉更好&#xff0c;第二种方式也可以&#xff09;E:\NX1980\UGOPEN\SampleNXOpenApplications\C\BlockStyler\ColoredBlock 普通格式&#xff1a; 读取&#xff1a; //方法一 string0->GetProperti…...

【比赛真题解析】篮球迷

本次给大家分享一道比赛的题目:篮球迷。 洛谷链接:U561543 篮球迷 题目如下: 【题目描述】 众所周知,jimmy是个篮球迷。众所周知,Jimmy非常爱看NBA。 众所周知,Jimmy对NBA冠军球队的获奖年份和队名了如指掌。 所以,Jimmy要告诉你n个冠军球队的名字和获奖年份,并要求你…...

WPF之集合绑定深入

文章目录 引言ObservableCollection<T>基础什么是ObservableCollectionObservableCollection的工作原理基本用法示例ObservableCollection与MVVM模式ObservableCollection的局限性 INotifyCollectionChanged接口深入接口定义与作用NotifyCollectionChangedEventArgs详解自…...

第五天 车载系统安全(入侵检测、OTA安全) 数据加密(TLS/SSL、国密算法)

前言 随着汽车智能化程度不断提升&#xff0c;车载系统安全已成为行业关注焦点。本文将从零开始&#xff0c;带大家系统学习车载系统安全的核心技术&#xff0c;重点解析入侵检测、OTA安全、数据加密三大领域。即使没有安全背景&#xff0c;也能通过本文建立起完整的汽车网络安…...

采用SqlSugarClient创建数据库实例引发的异步调用问题

基于SqlSugar编写的多个WebApi接口&#xff0c;项目初始化时采用单例模式注册SqlSugarClient实例对象&#xff0c;前端页面采用layui布局&#xff0c;并在一个按钮事件中通过Ajax连续调用多个WebApi接口获取数据。实际运行时点击按钮会随机报下面几种错误&#xff1a; Execute…...

unity通过transform找子物体只能找子级

unity通过transform找子物体只能找子级&#xff0c;孙级以及更低级别都找不到&#xff0c;只能找到自己的下一级 如果要获取孙级以下的物体&#xff0c;最快的方法还是直接public挂载...

Dockers部署oscarfonts/geoserver镜像的Geoserver

Dockers部署oscarfonts/geoserver镜像的Geoserver 说实话&#xff0c;最后发现要选择合适的Geoserver镜像才是关键&#xff0c;所以所以所以…&#x1f437; 推荐oscarfonts/geoserver的镜像&#xff01; 一开始用kartoza/geoserver镜像一直提示内存不足&#xff0c;不过还好…...

AtCoder AT_abc405_d ABC405D - Escape Route

前言 BFS 算法在 AtCoder 比赛中还是会考的&#xff0c;因为不常练习导致没想到&#xff0c;不仅错误 TLE 了很多&#xff0c;还影响了心态&#xff0c;3 发罚时后才 AC。 思路 首先&#xff0c;我们把所有位置和出口的距离算出来&#xff08;用 BFS&#xff09;&#xff0c…...

Redis-x64-3.0.500

E:\Workspace_zwf\Redis-x64-3.0.500 redis.windows.conf...

CUDA编程——性能优化基本技巧

本文主要介绍下面三种技巧&#xff1a; 使用 __restrict__ 让编译器放心地优化指针访存想办法让同一个 Warp 中的线程的访存 Pattern 尽可能连续&#xff0c;以利用 Memory coalescing使用 Shared memory 0. 弄清Kernael函数是Compute-bound 还是 Memory-bound 先摆出一个知…...

图像卷积初识

目录 一、卷积的概念 1、常见卷积核示例 二、使用 OpenCV 实现卷积操作 1、代码说明 2、运行说明 一、卷积的概念 在图像处理中&#xff0c;卷积是一种通过滑动窗口&#xff08;卷积核&#xff09;对图像进行局部计算的操作。卷积核是一个小的矩阵&#xff0c;它在图像上…...

K8S服务的请求访问转发原理

开启 K8s 服务异常排障过程前&#xff0c;须对 K8s 服务的访问路径有一个全面的了解&#xff0c;下面我们先介绍目前常用的 K8s 服务访问方式&#xff08;不同云原生平台实现方式可能基于部署方案、性能优化等情况会存在一些差异&#xff0c;但是如要运维 K8s 服务&#xff0c;…...

VSCode-插件:codegeex:ai coding assistant / 清华智普 AI 插件

一、官网 https://codegeex.cn/ 二、vscode 安装插件 点击安装即可&#xff0c;无需复杂操作&#xff0c;国内软件&#xff0c;无需科学上网&#xff0c;非常友好 三、智能注释 输入 // 或者 空格---后边自动出现注释信息&#xff0c;&#xff0c;按下 Tab 键&#xff0c;进…...

Kubernetes生产实战(十四):Secret高级使用模式与安全实践指南

一、Secret核心类型解析 类型使用场景自动管理机制典型字段Opaque (默认)自定义敏感数据需手动创建data字段存储键值对kubernetes.io/dockerconfigjson私有镜像仓库认证kubelet自动更新.dockerconfigjsonkubernetes.io/tlsTLS证书管理Cert-Manager可自动化tls.crt/tls.keykube…...

【验证码】⭐️集成图形验证码实现安全校验

&#x1f4a5;&#x1f4a5;✈️✈️欢迎阅读本文章❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;本篇文章阅读大约耗时5分钟。 ⛳️motto&#xff1a;不积跬步、无以千里 &#x1f4cb;&#x1f4cb;&#x1f4cb;本文目录如下&#xff1a;&#x1f381;&#x1f381;&am…...

iOS瀑布流布局的实现(swift)

在iOS开发中&#xff0c;瀑布流布局&#xff08;Waterfall Flow&#xff09;是一种常见的多列不等高布局方式&#xff0c;适用于图片、商品展示等场景。以下是基于UICollectionView实现瀑布流布局的核心步骤和优化方法&#xff1a; 一、实现原理 瀑布流的核心在于动态计算每个…...

TGRS | FSVLM: 用于遥感农田分割的视觉语言模型

论文介绍 题目&#xff1a;FSVLM: A Vision-Language Model for Remote Sensing Farmland Segmentation 期刊&#xff1a;IEEE Transactions on Geoscience and Remote Sensing 论文&#xff1a;https://ieeexplore.ieee.org/document/10851315 年份&#xff1a;2025 单位…...

#Redis黑马点评#(四)优惠券秒杀

目录 一 生成全局id 二 添加优惠券 三 实现秒杀下单 方案一&#xff08;会出现超卖问题&#xff09; 方案二&#xff08;解决了超卖但是错误率较高) 方案三&#xff08;解决了错误率较高和超卖但是会出现一人抢多张问题) 方案四&#xff08;解决一人抢多张问题“非分布式…...

https,http1,http2,http3的一些知识

温故知新&#xff0c;突然有人问我项目中&#x1f914;有使用http3么&#xff0c;一下不知从何说起&#xff0c;就有了这篇文章的出现。 https加密传输&#xff0c;ssltls https 验证身份 提供加密&#xff0c;混合加密 &#xff1a; 对称加密 非对称加密 原理&#xff1a…...

《设计数据密集型应用》——阅读小记

设计数据密集型应用 这本书非常推荐看英语版&#xff0c;如果考过了CET-6就可以很轻松的阅读这本书。 当前计算机软件已经不是单体的时代了&#xff0c;分布式系统&#xff0c;微服务现在是服务端开发的主流&#xff0c;如果没有读过这本书&#xff0c;则强力建议读这本书。 …...

SpringCloud之Gateway基础认识-服务网关

0、Gateway基本知识 Gateway 是在 Spring 生态系统之上构建的 API 网关服务&#xff0c;基于 Spring &#xff0c;Spring Boot 和 Project Reactor 等技术。 Gateway 旨在提供一种简单而有效的方式来对 API 进行路由&#xff0c;以及提供一些强大的过滤器功能&#xff0c;例如…...

MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器

在 MySQL 数据库的日常运维中&#xff0c;日志是定位问题、优化性能、数据恢复的核心工具。无论是排查服务器启动异常&#xff0c;还是分析慢查询瓶颈&#xff0c;亦或是通过二进制日志恢复误删数据&#xff0c;日志都扮演着 “数据库黑匣子” 的角色。本文将深入解析 MySQL 的…...

单脉冲前视成像多目标分辨算法——论文阅读

单脉冲前视成像多目标分辨算法 1. 论文的研究目标及实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法及公式解析2.1 核心思路2.2 关键公式与模型2.2.1 单脉冲雷达信号模型2.2.2 匹配滤波输出模型2.2.3 多目标联合观测模型2.2.4 对数似然函数与优化2.2.5 MDL准则目…...