8.7.tensorRT高级(3)封装系列-调试方法、思想讨论

目录

    • 前言
    • 1. 模型调试技巧
    • 总结

前言

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

本次课程学习 tensorRT 高级-调试方法、思想讨论

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

在这里插入图片描述

1. 模型调试技巧

这节我们学习模型的调试技巧,debug 方法

调试法则

1. 善用 python 工作流,联合 python/cpp 一起进行问题调试(python 工作流比较完善,把 C++ 作为处理工具,Python 作为分析可视化工具)

2. 去掉前后处理情况下,确保 onnx 与 pytorch 结果一致,排除所有因素。这一点 engine 通常是能够保证的。例如都输入全为 5 的张量,必须使得输出之间差距小于 1e-4,确保中间没有例外情况发生

3. 预处理一般很难保证完全一样,考虑把 pytorch 的预处理结果储存文件,c++ 加载后推理,得到的结果应该差异小于 1e-4(尤其是写插件的时候)

4. 考虑把 python 模型推理后的结果储存为文件,先用 numpy 写一遍后处理。然后用 c++ 复现

5. 如果出现 bug,应该把 tensor 从 c++ 中储存文件后,放到 python 上调试查看。避免在 c++ 中 debug

不要急着写 C++,多用 python 调试好

在之前的多线程 yolov5 的代码中,我们在 tensor 封装中拥有两个函数,一个是 save_to_file 可以将我们的 tensor 保存成二进制文件,另一个是 load_from_file 可以从二进制文件中读入 tensor,它们的定义如下:

bool Tensor::save_to_file(const std::string& file) const{

    if(empty()) return false;

    FILE* f = fopen(file.c_str(), "wb");
    if(f == nullptr) return false;

    int ndims = this->ndims();
    unsigned int head[3] = {0xFCCFE2E2, ndims, static_cast<unsigned int>(dtype_)};
    fwrite(head, 1, sizeof(head), f);
    fwrite(shape_.data(), 1, sizeof(shape_[0]) * shape_.size(), f);
    fwrite(cpu(), 1, bytes_, f);
    fclose(f);
    return true;
}

bool Tensor::load_from_file(const std::string& file){

    FILE* f = fopen(file.c_str(), "rb");
    if(f == nullptr){
        INFOE("Open %s failed.", file.c_str());
        return false;
    }

    unsigned int head[3] = {0};
    fread(head, 1, sizeof(head), f);

    if(head[0] != 0xFCCFE2E2){
        fclose(f);
        INFOE("Invalid tensor file %s, magic number mismatch", file.c_str());
        return false;
    }

    int ndims = head[1];
    auto dtype = (TRT::DataType)head[2];
    vector<int> dims(ndims);
    fread(dims.data(), 1, ndims * sizeof(dims[0]), f);

    this->dtype_ = dtype;
    this->resize(dims);

    fread(this->cpu(), 1, bytes_, f);
    fclose(f);
    return true;
}

save_to_file 函数用于将 Tensor 对象保存到指定的文件中,首先写入一个包含魔术数字、维度数量和数据类型的头部,接着写入 Tensor 的形状,最后写入 Tensor 的数据

load_from_file 函数则用于从指定的文件中加载 Tensor 对象,首先读取并验证文件的头部以获取 Tensor 的维度数量和数据类型,接着读取 Tensor 的形状和数据,并将这些信息设置到当前的 Tensor 对象中。

以上是 C++ 中 tensor 的保持和加载,在 python 中我们同样可以实现,具体实现如下:

import numpy as np

def load_tensor(file):
    
    with open(file, "rb") as f:
        binary_data = f.read()

    magic_number, ndims, dtype = np.frombuffer(binary_data, np.uint32, count=3, offset=0)
    assert magic_number == 0xFCCFE2E2, f"{file} not a tensor file."
    
    dims = np.frombuffer(binary_data, np.uint32, count=ndims, offset=3 * 4)

    if dtype == 0:
        np_dtype = np.float32
    elif dtype == 1:
        np_dtype = np.float16
    else:
        assert False, f"Unsupport dtype = {dtype}, can not convert to numpy dtype"
        
    return np.frombuffer(binary_data, np_dtype, offset=(ndims + 3) * 4).reshape(*dims)


def save_tensor(tensor, file):

    with open(file, "wb") as f:
        typeid = 0
        if tensor.dtype == np.float32:
            typeid = 0
        elif tensor.dtype == np.float16:
            typeid = 1
        elif tensor.dtype == np.int32:
            typeid = 2
        elif tensor.dtype == np.uint8:
            typeid = 3

        head = np.array([0xFCCFE2E2, tensor.ndim, typeid], dtype=np.uint32).tobytes()
        f.write(head)
        f.write(np.array(tensor.shape, dtype=np.uint32).tobytes())
        f.write(tensor.tobytes())

Python 版本的实现其实和 C++ 版本没有什么区别

load_tensor 函数用于从指定的文件中读取二进制数据,首先解析头部以获取魔术数字、维度数量和数据类型,然后根据读取到的信息解析 tensor 的形状和数据,最后返回形状和数据类型都已经设置好的 numpy 数组。

save_tensor 函数则用于将 numpy 数组保存到指定的文件中,首先将魔术数字、数组的维度数量和数据类型编码为一个二进制头部,接着写入数组的形状,最后写入数组的数据。

那现在我们就来走一个流程,在 python 中保持一个 tensor,在 C++ 中进行加载,Python 代码如下:

def save_tensor(tensor, file):

    with open(file, "wb") as f:
        typeid = 0
        if tensor.dtype == np.float32:
            typeid = 0
        elif tensor.dtype == np.float16:
            typeid = 1
        elif tensor.dtype == np.int32:
            typeid = 2
        elif tensor.dtype == np.uint8:
            typeid = 3

        head = np.array([0xFCCFE2E2, tensor.ndim, typeid], dtype=np.uint32).tobytes()
        f.write(head)
        f.write(np.array(tensor.shape, dtype=np.uint32).tobytes())
        f.write(tensor.tobytes())

data = np.arange(100, dtype=np.float32).reshape(10, 10, 1)
save_tensor(data, "data.tensor")

C++ 代码如下:

#include "trt-tensor.hpp"

int main(){

    TRT::Tensor tensor;
    tensor.load_from_file("../data.tensor");
    
    float* ptr = tensor.cpu<float>();
    INFO("tensor.shape = %s, dtype = %d", tensor.shape_string(), tensor.type());

    for(int i = 0; i < tensor.count(); ++i){
        INFO("%d -> = %f", i, prt[i]);
    }

    return 0;
}

执行效果如下:

在这里插入图片描述

图1-1 python存储C++加载

可以看到结果和我们预期的一样,没有损失,我们可以把它的类型换成 uint8 再来看下,运行效果如下:

在这里插入图片描述

图1-2 python存储C++加载(uint8)

可以看到也没有问题,那这边是 python 保存 c++ 读取没有问题,接下来我们来看下 c++ 保存,python 读取

yolov5.cpp 中
214/216 行
    
input->save_to_file("input.tensor")
output->save_to_file("output.tensor")

运行如下:

在这里插入图片描述

图1-3 C++存储

接下来我们去 python 中去加载保存的 input 和 output,代码如下:

import numpy as np

def load_tensor(file):
    
    with open(file, "rb") as f:
        binary_data = f.read()

    magic_number, ndims, dtype = np.frombuffer(binary_data, np.uint32, count=3, offset=0)
    assert magic_number == 0xFCCFE2E2, f"{file} not a tensor file."
    
    dims = np.frombuffer(binary_data, np.uint32, count=ndims, offset=3 * 4)

    if dtype == 0:
        np_dtype = np.float32
    elif dtype == 1:
        np_dtype = np.float16
    else:
        assert False, f"Unsupport dtype = {dtype}, can not convert to numpy dtype"
        
    return np.frombuffer(binary_data, np_dtype, offset=(ndims + 3) * 4).reshape(*dims)

input = load_tensor("workspace/input.tensor")
output = load_tensor("workspace/output.tensor")

print(input.shape, output.shape)

# 恢复成源图像
image = input * 255
image = image.transpose(0, 2, 3, 1)[0].astype(np.uint8)[..., ::-1]

import cv2
cv2.imwrite("image.jpg", image)
print("save done.")

运行效果如下:

在这里插入图片描述

图1-4 python加载

在这里插入图片描述

图1-5 python恢复出的图像

可以看到我们恢复出来的效果完全一样,说明中间是没有问题的

这边我们讲解了怎么 save tensor,怎么 load tensor,怎么和 C++ 去做交互,自己也可以去进行封装。

最后我们来看下实现一个模型的流程:

1. 先把代码跑通 predict,单张图作为输入。屏蔽一切与该目标不符的东西,可以修改删除任意多余的东西

2. 自行写一个 python 程序,简化 predict 的流程,掌握 predict 所需要的最小依赖和最少代码

3. 如果第二步比较困难,则可以考虑直接在 pred = model(x) 这个步骤上研究,例如直接在此处写 torch.onnx.export(model, (pred,) …),或者直接把 pred 的结果储存下来研究等等

4. 把前处理、后处理分析出来并实现一个最简化版本

5. 利用简化版本进行 debug、理解分析。然后考虑预处理后处理的合理安排,例如是否可以把部分后处理放到 onnx 中

6. 导出 onnx,在 C++ 上先复现预处理部分,使得其结果和 python 接近(大多数时候并不能得到一样的结果)

7. 把 python 上的 pred 结果储存后,使用 C++ 读取并复现所需要的后处理部分。确保结果正确

8. 把前后处理与 onnx 对接起来,形成完整的推理

总结

本次课程学习了调试方法,首先我们封装了 tensor 保存和加载,这点可以保证 python 与 c++ 之间的交互。然后我们对实现一个模型的流程进行了讨论,拿到一个新的项目后我们先要做的是跑通单张图片的 predict,然后可以自行实现一个简易的 predict 程序,也可以考虑直接导出 onnx 或者把 pred 预测结果保存下来,接着我们通过 debug 分析把预处理、后处理抽出来,导出 onnx。现在 C++ 上复现预处理,看结果和 python 是否接近,另外把 python 存储的 pred 利用 c++ 读取复现后处理,最后把整个对接起来,完成推理。

这是杜老师推荐的工作流,可以简化过程,并且方便开发调试

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

知虾shopee数据分析工具:shopee出单的商机利器

当今数字化时代&#xff0c;数据已经成为商业成功的关键要素之一。而Shopee作为东南亚最大的电商平台之一&#xff0c;其强大的数据分析工具正为商家提供了宝贵的市场洞察和决策支持。本文将深入探讨Shopee数据分析工具如何帮助商家抓住商机并取得成功。 洞察消费者需求&#x…

P21~22 第六章 储能元件——电容存储电场能,电感存储磁场能

1、电容元件 a定义 b线性时不变电容元件 c电容的电压与电流关系 i有限则u有限 注意理解面积 d电容的功率和储能 e例一 跃变就是指物体的物理量从有限值变为无限值的过程。 分析上图例题&#xff1a;对于电源波形要吃负无穷到正无穷去刻画。即时间轴要铺满。 有有图控制电…

sql:SQL优化知识点记录(四)

&#xff08;1&#xff09;explain之ref介绍 type下的ref是非唯一性索引扫描具体的一个值 ref属性 例如&#xff1a;ti表先加载&#xff0c;const是常量 t1.other_column是个t1表常量 test.t1.ID&#xff1a;test库t1表的ID字段 t1表引用了shared库的t2表的col1字段&#x…

python可视化matplotlib——绘制正弦和余弦

这是一个使用matplotlib库绘制正弦和余弦函数曲线的代码示例。代码中导入了需要的库&#xff0c;并设置了x轴和y轴的标签字体为华文楷体。然后&#xff0c;使用numpy生成一组x轴上的值t&#xff0c;并使用正弦函数生成对应的y轴值s&#xff0c;再使用余弦函数生成对应的y轴值z。…

Java学数据结构(1)——抽象数据类型ADT 表List、栈Stack和队列Qeue

目录 引出抽象数据类型&#xff08;abstract data type,ADT&#xff09;表ListArrayList,Vector, LinkedListArrayList手动实现与分析Vector的分析&#xff08;线程安全&#xff09;LinkedList 的手动实现与分析 栈stack—后进先出java中stack源码分析栈的应用&#xff1a;检查…

创邻科技张晨:图数据库,激活数据要素的新基建

“数据经济时代&#xff0c;数据要素产业链的各细分领域均蕴含机遇&#xff0c;图技术作为网络协同和数据智能的底层发动机&#xff0c;将深度掘金数字中国价值潜能”。 8月22日&#xff0c;在2023中国&#xff08;南京&#xff09;国际软件产品和信息服务交易博览会的信息技术…

Day48|leetcode 198.打家劫舍、213.打家劫舍II、打家劫舍|||

leetcode 198.打家劫舍 题目链接&#xff1a;198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划&#xff0c;偷不偷这个房间呢&#xff1f;| LeetCode&#xff1a;198.打家劫舍_哔哩哔哩_bilibili 题目概述 你是一个专业的小偷&#xff0c;…

【Android】TextView适配文本大小并保证中英文内容均在指定的UI 组件内部

问题 现在有一个需求&#xff0c;在中文环境下textView没有超过底层的组件限制&#xff0c;但是一切换到英文环境就超出了&#xff0c;这个如何解决呢&#xff1f;有啥例子吗&#xff1f; 就像这样子的。 解决 全部代码如下&#xff1a; <?xml version"1.0"…

汽车电子笔记之:AUTOSAR方法论及基础概念

目录 1、AUTOSAR方法论 2、AUTOSAR的BSW 2.1、MCAL 2.2、ECU抽象层 2.3、服务层 2.4、复杂驱动 3、AUTOSAR的RTE 4、AUTOSAR的应用层 4.1、SWC 4.2、AUTOSAR的通信 4.3、AUTOSAR软件接口 1、AUTOSAR方法论 AUTOSAR为汽车电子软件系统开发过程定义了一套通用的技术方法…

腾讯云coding平台平台inda目录遍历漏洞复现

前言 其实就是一个python的库可以遍历到&#xff0c;并不能遍历到别的路径下&#xff0c;后续可利用性不大&#xff0c;并且目前这个平台私有部署量不多&#xff0c;大多都是用腾讯云在线部署的。 CODING DevOps 是面向软件研发团队的一站式研发协作管理平台&#xff0c;提供…

基于Ubuntu坏境下的Suricata坏境搭建

目录 Suricata环境安装 第一步、在 Ubuntu 端点安装 Suricata 1、加入Suricata源 2、更新安装包 3、下载SuricataSuricata 第二步、下载并提取新兴威胁 Suricata 规则集 1、在tmp文件夹下载 Suricata 规则集 如果发现未安装curl&#xff0c;使用apt安装即可&#xff1a;…

QT 消息对话框按钮显示

前言 搞QT嘛&#xff0c;大多数都是军工。都要国产化&#xff0c;而且消息对话框的按钮的英文也不是很得劲&#xff0c;所以需要汉化。使用静态函数的按钮就是显示英文&#xff0c;汉化的代码如下。 void Widget::on_pushButton_clicked() {QMessageBox box(QMessageBox::Inf…

MySQL 条件查询 Emoji 表情符号却返回多条数据【包含其它表情符号】的问题解决 - COLLATION 字符序的选择

1、问题出现 在APP客户端输入搜索文章的关键字时&#xff0c;不小心输入来了一个 emoji 表情符号&#xff0c;提示出错了&#xff0c;在后台查询错误日志信息&#xff0c;提示查询出现了2条相同的记录&#xff1a; Caused by: org.hibernate.NonUniqueResultException: query …

LNMT与动静分离

目录 一、LNMT 一、部署tomcat 二、部署nginx 三、部署mariadb 四、配置nginx 二、操作流程及步骤 一、在第一台机器上进入 vim /etc/nginx/nginx.conf 更改配置文件 二、并查看端口是否成功启动 三、验证 四、再次来到网页验证 五、动静分离&#xff08;修改配置…

HTTP 框架修炼之道 | 青训营

Powered by:NEFU AB-IN 文章目录 HTTP 框架修炼之道 | 青训营 走进 HTTP 协议HTTP 框架的设计与实现应用层中间件层路由设计协议层 传输层&#xff08;网络层&#xff09;1. BIO&#xff08;Blocking I/O&#xff09;:2. NIO&#xff08;Non-blocking I/O&#xff09;:区别&…

设计模式入门笔记

1 设计模式简介 在IT这个行业&#xff0c;技术日新月异&#xff0c;可能你今年刚弄懂一个编程框架&#xff0c;明年它就不流行了。 然而即使在易变的IT世界也有很多几乎不变的知识&#xff0c;他们晦涩而重要&#xff0c;默默的将程序员划分为卓越与平庸两类。比如说&#xff…

【C++】开源:Box2D动力学库配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Box2D动力学库配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c…

MATLAB软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 MATLAB是Matrix Laboratory的缩写&#xff0c;是一款由美国MathWorks公司开发的商业数学软件。它主要用于进行数值计算、数据分析、可视化、算法开发、模拟仿真等多个领域。MATLAB具有高度的灵活性和开放性&#xff0c;可以为用…

SpringBoot异步方法支持注解@Async应用

SpringBoot异步方法支持注解Async应用 1.为什么需要异步方法&#xff1f; 合理使用异步方法可以有效的提高执行效率 同步执行(同在一个线程中): 异步执行(开启额外线程来执行): 2.SpringBoot中的异步方法支持 在SpringBoot中并不需要我们自己去创建维护线程或者线程池来异…

C#搭建WebSocket服务实现通讯

在学习使用websocket之前我们先了解一下websocket&#xff1a; WebSocket是一种在单个TCP连接上进行全双工通信的通信协议。与HTTP协议不同&#xff0c;它允许服务器主动向客户端发送数据&#xff0c;而不需要客户端明确地请求。这使得WebSocket非常适合需要实时或持续通信的应…