模型部署技巧(一)

模型部署技巧(一)

以下内容是参考CUDA与TensorRT模型部署内容第六章,主要针对图像的前/后处理中的trick。

参考:
1.部署分类器-int8-calibration
2. cudnn安装地址
3. 如何查找Tensor版本,与cuda 和 cudnn匹配
4. timing cache


一. 前处理 preprocess

学习目标

  • 分析学习几种cv::Mat bgr2rgb 的方式,比较运行速度

1. 图像 BGR2RGB 在cpu中的方式

某些小图像前处理部分如果放在GPU上跑,并不能充分的硬件资源吃满,导致硬件资源比较浪费。
如果这种情况出现的话,我们可能会考虑把前处理放在CPU上,DNN的forward部分放在GPU上,进行异步的推理。

1.1 cv::cvtColor

void preprocess_cv_cvtcolor(cv::Mat src, cv::Mat tar){cv::cvtColor(src, tar, cv::COLOR_BGR2RGB);
}

1.2 .at 方式

void preprocess_cv_mat_at(cv::Mat src, cv::Mat tar){for (int i = 0; i < src.rows; i++) {for (int j = 0; j < src.cols; j++) {tar.at<cv::Vec3b>(i, j)[2] = src.at<cv::Vec3b>(i, j)[0];tar.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i, j)[1];tar.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i, j)[2];}}
}

1.3 cv::MatIterator_方式

void preprocess_cv_mat_iterator(cv::Mat src, cv::Mat tar){cv::MatIterator_<cv::Vec3b> src_it = src.begin<cv::Vec3b>();cv::MatIterator_<cv::Vec3b> tar_it = tar.begin<cv::Vec3b>();cv::MatIterator_<cv::Vec3b> end    = src.end<cv::Vec3b>();for (; src_it != end; src_it++, tar_it++) {(*tar_it)[2] = (*src_it)[0];(*tar_it)[1] = (*src_it)[1];(*tar_it)[0] = (*src_it)[2];}
}

1.4 .data方法

void preprocess_cv_mat_data(cv::Mat src, cv::Mat tar){int height   = src.rows;int width    = src.cols;int channels = src.channels();for (int i = 0; i < height; i ++) {for (int j = 0; j < width; j ++) {int index = i * width * channels + j * channels;tar.data[index + 2] = src.data[index + 0];tar.data[index + 1] = src.data[index + 1];tar.data[index + 0] = src.data[index + 2];}}
}

1.5 pointer

void preprocess_cv_pointer(cv::Mat src, cv::Mat tar){for (int i = 0; i < src.rows; i ++) {cv::Vec3b* src_ptr = src.ptr<cv::Vec3b>(i);cv::Vec3b* tar_ptr = tar.ptr<cv::Vec3b>(i);for (int j = 0; j < src.cols; j ++) {tar_ptr[j][2] = src_ptr[j][0];tar_ptr[j][1] = src_ptr[j][1];tar_ptr[j][0] = src_ptr[j][2];}}
}

结论:

  • 使用cv::Mat::at:速度最慢
  • 使用cv::MatIterator_ 速度中等
  • 使用cv::Mat.data
  • 使用cv::Mat.ptr: 速度最快

Tips.图像 BGR2RGB + norm + hwc2chw 最优方式

void preprocess_cv_pointer(cv::Mat src, float* tar, float* mean, float* std){int area = src.rows * src.cols;int offset_ch0 = area * 0;int offset_ch1 = area * 1;int offset_ch2 = area * 2;for (int i = 0; i < src.rows; i ++) {cv::Vec3b* src_ptr = src.ptr<cv::Vec3b>(i);for (int j = 0; j < src.cols; j ++) {tar[offset_ch2++] = (src_ptr[j][0] / 255.0f - mean[0]) / std[0];tar[offset_ch1++] = (src_ptr[j][1] / 255.0f - mean[1]) / std[1];tar[offset_ch0++] = (src_ptr[j][2] / 255.0f - mean[2]) / std[2];}}
}

二. 通用模型推理框架设计

2.1 worker类
根据模型的种类(分类、检测、分割)在构造函数中初始化一个模型,另外包含一个推理函数即可。

Worker::Worker(string onnxPath, logger::Level level, model::Params params) {m_logger = logger::create_logger(level);// 这里根据task_type选择创建的trt_model的子类,今后会针对detection, segmentation扩充if (params.task == model::task_type::CLASSIFICATION) m_classifier = model::classifier::make_classifier(onnxPath, level, params);}void Worker::inference(string imagePath) {if (m_classifier != nullptr) {m_classifier->load_image(imagePath);m_classifier->inference();}
}

2.2 model基类

成员变量包含模型参数集合,各类路径字符串,logger, timer等。

Model::Model(string onnx_path, logger::Level level, Params params) {m_onnxPath      = onnx_path;m_enginePath    = getEnginePath(onnx_path);m_workspaceSize = WORKSPACESIZE;m_logger        = make_shared<logger::Logger>(level);m_timer         = make_shared<timer::Timer>();m_params        = new Params(params);
}

成员函数有初始化模型,加载数据,推理,构建/加载/保存引擎,几个纯虚函数setup, 前/后处理cpu版本,前/后处理gpu版本)。
纯虚函数需要子类去具体实现。

setup负责分配host/device的memory, bindings, 以及创建推理所需要的上下文。由于不同task的input/output的tensor不一样,所以这里的setup需要在子类实现。

2.3 classifier 分类器子类
主要是针对model基类中的几个纯虚函数,进行具体实现。
Eg.

void Classifier::setup(void const* data, size_t size) {m_runtime     = shared_ptr<IRuntime>(createInferRuntime(*m_logger), destroy_trt_ptr<IRuntime>);m_engine      = shared_ptr<ICudaEngine>(m_runtime->deserializeCudaEngine(data, size), destroy_trt_ptr<ICudaEngine>);m_context     = shared_ptr<IExecutionContext>(m_engine->createExecutionContext(), destroy_trt_ptr<IExecutionContext>);m_inputDims   = m_context->getBindingDimensions(0);m_outputDims  = m_context->getBindingDimensions(1);// 考虑到大多数classification model都是1 input, 1 output, 这边这么写。如果像BEVFusion这种有多输出的需要修改CUDA_CHECK(cudaStreamCreate(&m_stream));m_inputSize     = m_params->img.h * m_params->img.w * m_params->img.c * sizeof(float);m_outputSize    = m_params->num_cls * sizeof(float);m_imgArea       = m_params->img.h * m_params->img.w;// 这里对host和device上的memory一起分配空间CUDA_CHECK(cudaMallocHost(&m_inputMemory[0], m_inputSize));CUDA_CHECK(cudaMallocHost(&m_outputMemory[0], m_outputSize));CUDA_CHECK(cudaMalloc(&m_inputMemory[1], m_inputSize));CUDA_CHECK(cudaMalloc(&m_outputMemory[1], m_outputSize));// //创建m_bindings,之后再寻址就直接从这里找m_bindings[0] = m_inputMemory[1];m_bindings[1] = m_outputMemory[1];
}

2.4 logger类
日志类,通过设置等级进行打印消息,相比于cout更清爽。

2.5 timer类
记录cpu和gpu的开始/结束时间,计算相应的时间差。

    m_timer->start_cpu();/* 处理程序 */m_timer->stop_cpu();m_timer->duration_cpu<timer::Timer::ms>("preprocess(CPU)");

2.6 process命名空间
process 命名空间下,定义了preprocess_resize_cpu, preprocess_resize_gpu等一些函数。


三. int8量化

3.1 创建calibrator类的时候需要继承nvinfer1里的calibrator,NVIDIA官方提供了以下五种:

  • nvinfer1::IInt8EntropyCalibrator2 是tensorRT 7.0引入的接口,实现基于熵的INT8量化校准器。(默认情况下优先使用它)
  • nvinfer1::IInt8MinMaxCalibrator
  • nvinfer1::IInt8EntropyCalibrator 是tensorRT 7.0之前的接口,实现基于熵的INT8量化校准器。(目前已被弃用)
  • nvinfer1::IInt8LegacyCalibrator(percentile)
  • nvinfer1::IInt8Calibrator(被弃用)

3.2 在calibrator类中需要实现的函数只需要四个:

int         getBatchSize() const noexcept override {return m_batchSize;};
bool        getBatch(void* bindings[], const char* names[], int nbBindings) noexcept override;
const void* readCalibrationCache(std::size_t &length) noexcept override;
void        writeCalibrationCache (const void* ptr, std::size_t legth) noexcept override;
  • getBatchSize: 获取calibration的batch大小,需要注意的是不同的batch size会有不同的校准效果。一般而言,越大越好。
  • getBatch获取的图像必须要和真正推理时所采用的预处理保持一直。不然dynamic range会不准
  • readCalibrationCache: 用来读取calibration table,也就是之前做calibration统计得到的各个layer输出tensor的dynamic range。实现这个函数可以让我们避免每次做int8推理的时候都需要做一次calibration
  • writeCalibrationCache: 将统计得到的dynamic range写入到calibration table中去

3.3 实现完了基本的calibrator之后,在build引擎的时候通过config指定calibrator就可以了。

shared_ptr<Int8EntropyCalibrator> calibrator(new Int8EntropyCalibrator(64, "calibration/calibration_list_imagenet.txt", "calibration/calibration_table.txt",3 * 224 * 224, 224, 224));
config->setInt8Calibrator(calibrator.get());

这里面的calibration_list_imagenet.txt使用的是ImageNet2012的test数据集的一部分。可以根据各自的情况去更改,注意batch_size 64需要改成能被calibration dataset的整除的数,否则core dump。

需要注意的是,如果calibrator改变了,或者模型架构改变了,需要删除掉calibration_table.txt来重新计算dynamic range。否则会报错

Tips.
实操生成过程中遇到的core dump情况,报出一个cudnn库加载版本不正确的警告。通过ldd ./bin/trt-infer 定位到libnvinfer.so.8 => /home/xx/opt/TensorRT-8.5.3.1/lib/libnvinfer.so.8 ,TensorRT版本与Makefile配置文件中指定的版本不一致
查看服务器动态库路径,先删除动态库其中被指定的TensorRT动态库路径,再指定自己的动态库路径

echo $LD_LIBRARY_PATH
export LD_LIBRARY_PATH=""  # 先清空
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/home/xxxpersonal/others/TensorRT-8.4.0.6/lib

四. Timing Cache

主要作用:它可以加快 engine 的创建过程,为了优化和加速内核选择过程。

如何使用 Timing Cache:

  • 创建和保存 Timing Cache: 在第一次构建 engine 时,TensorRT 会创建一个 timing cache。你可以将这个 timing cache 保存到文件中,以便未来复用。
  • 加载 Timing Cache: 在构建新的 engine 时,可以加载已经保存的 timing cache,从而避免重新进行时间消耗的内核调优过程。

五. trt-engine-explorer

trt-engine-explorer是NVIDIA官方提供的分析TensorRT优化后的推理引擎架构的工具包。链接在这里:

TensorRT tool: trt-engine-explorer

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

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

相关文章

MySQL--数据引擎详解

存储引擎 MySQL体系结构 连接层&#xff1a; 主要接收客户端的连接&#xff0c;然后完成一些链接的处理&#xff0c;以及认证授权的相关操作和安全方案&#xff0c;还要去检查是否超过最大连接数等等&#xff0c;比如在连接MySQL服务器时需要输入用户名&#xff0c;密码&#…

【含文档+PPT+源码】基于微信小程序的健康饮食食谱推荐平台的设计与实现

项目介绍 本课程演示的是一款基于微信小程序的健康饮食食谱推荐平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…

当OA闯入元宇宙:打卡、报销和会议的未来狂想

引言&#xff1a;虚实共生中的组织基因突变 元宇宙正以虚实共生的形态重构人类协作的底层逻辑。传统OA系统建立的物理规则——指纹打卡验证在场性、纸质票据堆砌信任链、会议室排期协调时空资源——在元宇宙的数字原野上迎来基因级重组。这场变革不仅是技术工具的迭代&#xf…

解决vscode cmake提示检测到 #include 错误

一、问题 cmake已经包含了动态库文件&#xff0c;依然提示“检测到 #include 错误。请更新 includePath。” 二、解决方案 Ctrl Shift P进入CPP编辑配置&#xff0c;然后在JSON中加入下面一行&#xff1a; "configurationProvider": "ms-vscode.cmake-tools&…

2024ICPC成都题解

文章目录 L. Recover Statistics(签到)J. Grand Prix of Ballance(模拟签到)A. Arrow a Row(构造)B. Athlete Welcome Ceremony(线性dp)G. Expanding Array(打表结论)I. Good Partitions(线段树)E. Disrupting Communications(换根dpLCA倍增)K. Magical Set(费用流) 题目链接 …

达梦数据库官方迁移工具SQLark:支持Oracle/MySQL/PostgreSQL迁移至达梦数据库!

SQLark 百灵连接是一款面向信创应用开发者的数据库开发和管理工具&#xff0c;由达梦数据历时三年自主研发&#xff0c;注册即可免费使用客户端&#xff08;官网链接www.sqlark.com&#xff09;。今天&#xff0c;我们将重点介绍SQLark的特色功能之一——数据迁移&#xff0c;该…

映射关系4

好&#xff01;我明白了&#xff01;&#x1f4a5; 你希望我在你的基础上&#xff0c;继续优化 insertPathWithIds&#xff0c;让它&#xff1a; • 支持每一级节点的 idPart 是字符串&#xff08;而不是int&#xff09;。 • 结构更清晰&#xff0c;更快拼接。 • 完全符合C98…

PDF Shaper v15.0

如今对PDF处理的软件很多都是只是单一的功能。PDF Shaper给你完全不同的体验&#xff0c;因为PDF Shaper是一款免费的PDF工具集合的软件。有了PDF Shaper&#xff0c;你以后再也不用下载其他处理PDF的软件了。PDF Shaper的功能有&#xff1a;合并&#xff0c;分割&#xff0c;加…

【Python爬虫基础篇】--4.Selenium入门详细教程

先解释&#xff1a;Selenium&#xff1a;n.硒&#xff1b;硒元素 目录 1.Selenium--简介 2.Selenium--原理 3.Selenium--环境搭建 4.Selenium--简单案例 5.Selenium--定位方式 6.Selenium--常用方法 6.1.控制操作 6.2.鼠标操作 6.3.键盘操作 6.4.获取断言信息 6.5.…

mysql8.0版本部署+日志清理+rsync备份策略

mysql安装&#xff1a;https://blog.csdn.net/qq_39399966/article/details/120205461 系统&#xff1a;centos7.9 数据库版本&#xff1a;mysql8.0.28 1.卸载旧的mysql,保证环境纯净 rpm -qa | grep mariadb mariadb-5.... rpm -e --nodeps 软件 rpm -e --nodeps mariadb-5.…

C#进阶学习(十七)PriorityQueue<TElement, TPriority>优先级队列的介绍

1. PriorityQueue是什么&#xff1f;作用是什么&#xff1f; 定义&#xff1a;PriorityQueue<TElement, TPriority> 是 C# (.NET 6 引入) 中的泛型优先级队列数据结构。 那么是什么是优先级队列呢&#xff1f;优先级队列是一种抽象数据结构&#xff0c;其核心特性是元素按…

如何查看和验证AWS CloudFront的托管区域ID

在使用AWS Route 53设置DNS记录时,正确识别CloudFront分发的托管区域ID是至关重要的。本文将详细介绍几种查看和验证CloudFront托管区域ID的方法,特别关注中国区CloudFront的特殊情况。 为什么托管区域ID很重要? 托管区域ID是AWS服务中的一个关键标识符。在创建指向CloudF…

kafka整合flume与DStream转换

一、Kafka整合flume cd /opt/software/flume/conf/ vi flume-kafka.conf a1.sourcesr1 a1.sinksk1 a1.channelsc1 a1.sources.r1.typespooldirt a1.sources.r1.spoolDir/root/flume-kafka a1.sinks.k1.typeorg.apache.flume.sink.kafka.KafkaSink a1.sinks.k1.kafka.to…

网络通讯【QTcpServer、QTcpSocket、QAbstractSocket】

目录 QTcpServer class简单描述成员函数和信号 QTcpSocket Class详细描述成员函数和信号 QAbstractSocket Class详细描述成员函数和信号成员函数说明文档 QT实现服务器和客户端通讯服务器端&#xff1a;通讯流程原代码 客户端通信流程原代码 QTcpServer class header: #includ…

大模型在肾癌诊疗全流程中的应用研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、大模型预测肾癌术前情况 2.1 基于影像组学的肾癌良恶性及分级预测 2.1.1 MRI 影像组学模型预测肾透明细胞癌分级 2.1.2 CT 影像深度学习模型鉴别肾肿物良恶性及侵袭性 2.2 大模型对手术风…

网络原理 - 11(HTTP/HTTPS - 2 - 请求)

目录 HTTP 请求&#xff08;Request&#xff09; 认识 URL URL 基本格式 关于 URL encode 认识方法&#xff08;method&#xff09; 1. GET 方法 2. POST 方法 认识请求“报头”&#xff08;header&#xff09; Host Content-Length Content-Type User-Agent&…

实现MySQL高可用性:从原理到实践

目录 一、概述 1.什么是MySQL高可用 2.方案组成 3.优势 二、资源清单 三、案例实施 1.修改主机名 2.安装MySQL数据库&#xff08;Master1、Master2&#xff09; 3.配置mysql双主复制 4.安装haproxy&#xff08;keepalived1、keepalived2&#xff09; 5.安装keepaliv…

CSS学习笔记8——表格

一、表格 1-1、创建表格 在Word文档中&#xff0c;如果要创建表格&#xff0c;只需插入表格&#xff0c;然后设定相应的行数和列数即可。然而在HTML网页中&#xff0c;所有的元素都是通过标签定义的&#xff0c;要想创建表格&#xff0c;就需要使用与表格相关的标签。使用标签…

爬虫学习笔记(一)

目的 通过编写程序爬取互联网上的优质资源 爬虫必须要使用python吗 非也~ 编程语言知识工具&#xff0c;抓取到数据才是目的&#xff0c;而大多数爬虫采用python语言编写的原因是python的语法比较简单&#xff0c;python写爬虫比较简单&#xff01;好用&#xff01;而且pyt…

大厂面试:MySQL篇

前言 本章内容来自B站黑马程序员java大厂面试题和小林coding 博主学习笔记&#xff0c;如果有不对的地方&#xff0c;海涵。 如果这篇文章对你有帮助&#xff0c;可以点点关注&#xff0c;点点赞&#xff0c;谢谢你&#xff01; 1.MySQL优化 1.1 定位慢查询 定位 一个SQL…