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 npdef 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.float32elif dtype == 1:np_dtype = np.float16else: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 = 0if tensor.dtype == np.float32:typeid = 0elif tensor.dtype == np.float16:typeid = 1elif tensor.dtype == np.int32:typeid = 2elif tensor.dtype == np.uint8:typeid = 3head = 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 = 0if tensor.dtype == np.float32:typeid = 0elif tensor.dtype == np.float16:typeid = 1elif tensor.dtype == np.int32:typeid = 2elif tensor.dtype == np.uint8:typeid = 3head = 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 npdef 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.float32elif dtype == 1:np_dtype = np.float16else: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://xiahunao.cn/news/1620999.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

remove logo now注册码

点击下载来源&#xff1a;remove logo now注册码 remove logo now注册码是用来激活破解一款非常好用的视频水印去除工具remove logo now的激活注册码&#xff0c;也可以说是序列号。我们知道这款软件由于涉及版权问题&#xff0c;需要收费购买使用&#xff0c;试用版只能免费试…

idea 2019.1.3注册码(亲测可用)

注册码&#xff1a; YZVR7WDLV8-eyJsaWNlbnNlSWQiOiJZWlZSN1dETFY4IiwibGljZW5zZWVOYW1lIjoiamV0YnJhaW5zIGpzIiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVFbWFpbCI6IiIsImxpY2Vuc2VSZXN0cmljdGlvbiI6IkZvciBlZHVjYXRpb25hbCB1c2Ugb25seSIsImNoZWNrQ29uY3VycmVudFVzZSI6ZmFsc2Us…

sqlyog 注册码

姓 名&#xff08;Name&#xff09;&#xff1a;ttrar 序 列 号&#xff08;Code&#xff09;&#xff1a;8d8120df-a5c3-4989-8f47-5afc79c56e7c 或者&#xff08;OR&#xff09; 姓 名&#xff08;Name&#xff09;&#xff1a;ttrar 序 列 号&#xff08;Code&#xff0…

STL-常用容器-list容器(双向循环链表)

1 list基本概念 功能&#xff1a;将数据进行链式存储 链表&#xff08;list&#xff09;是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的。 链表的组成&#xff1a;链表由一系列结点组成 结点的组成&#xff1a;一个是存…

2023年6月GESP C++ 三级试卷解析

2023年6月GESP C 三级试卷解析 一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 1.高级语言编写的程序需要经过以下&#xff08; &#xff09;操作&#xff0c;可以生成在计算机上运行的可执行代码。 A.编辑 B.保存 C.调试 D.编译 【答案】D 【考纲知识点…

自动化测试 —— Pytest测试框架

01 | 简介 Pytest是一个非常成熟的全功能的Python测试框架&#xff0c;主要有以下特点&#xff1a; 简单灵活&#xff0c;容易上手&#xff0c;文档丰富 支持参数化&#xff0c;可以细粒度地控制测试用例 支持简单的单元测试与复杂的功能测试&#xff0c;还可以用来做Seleni…

C/C++ 个人笔记

仅供个人复习&#xff0c; C语言IO占位符表 %d十进制整数(int)%ldlong%lldlong long%uunsigned int%o八进制整型%x十六进制整数/字符串地址%c单个字符%s字符串%ffloat&#xff0c;默认保留6位%lfdouble%e科学计数法%g根据大小自动选取f或e格式&#xff0c;去掉无效0 转义符表…

从零开发HarmonyOS(鸿蒙)手机小游戏——数字华容道

HarmonyOS&#xff08;鸿蒙&#xff09;手机第一个小游戏app——数字华容道 前言概述正文创建项目实现初始界面布局实现数字的随机打乱实现滑动或点击调换数字实现游戏成功界面 源代码结语 前言 2月16号HarmonyOS2.0手机开发者Beta版已经发布了&#xff0c;作为“18N”战略的重…

【180929】败走华容道游戏源码

这是一款基于WPF非常经典的华容道游戏源码。 古老的中国游戏&#xff0c;以其变化多端、百玩不厌的特点与魔方、独立钻石棋一起被国外智力专家并称 为“智力游戏界的三个不可思议”。它与七巧板、九连环等中国传统益智玩具还有个代名词叫作“中国的难题”。 游戏玩法&#xff1…

华容道小游戏

华容道代码&#xff1a; package ytu.jsj.com.cn; import java.awt.*; import java.awt.event.*; import javax.swing.*;public class Hua_Rong_Road extends JFrame implements MouseListener,KeyListener,ActionListener{Person person[]new Person[10];JButton left,right,a…

【180927】华容道游戏源码

本源码是一个采用winform进行开发的华容道游戏源码&#xff0c;华容道游戏作为一个经典游戏&#xff0c;各部分设计都恰到好处&#xff0c;非常巧妙&#xff0c;因此成为世界游戏界的三大不可思议。华容道游戏源于三国时期著名的历史故事。东汉末年&#xff0c;曹操、孙权以及刘…

28连局华容道游戏

华容道游戏取材于三国时期,关羽在华容道放走曹操的故事。这个游戏一共28局,是根据一款手机游戏编写的,希望大家能够喜欢。一个益智类的小游戏,大家可以在工作之余作为消遣之乐。 玩不过去没有关系,我们可以把游戏进度存储起来,下次玩的时候读取进度文件就可以了,我们可以…

华容道游戏VB.NET 2010 版28连局

这一版的华容道游戏,有玩法演示,局数、步数、时间提示完善。新增加了关数名字。 托动处理的更加完美,当鼠标手动图片后图片消失,鼠标变成你托动的图片,产生一种立体的感觉。 请大家查看新版的华容道游戏VB.NET 2010 版28连局。 下载地址:http://blog.sina.com.cn/…

经典华容道游戏(含bfs求解)

前言 这是数据结构的课设作业&#xff0c;也是我第一次写的千行代码小游戏。 学习EasyX图形库写完程序一共用了三天时间&#xff0c;可以说是相当熬人了。 当然&#xff0c;第一次写&#xff0c;难免不怎么好&#xff0c;比如无动画、无音乐、丑得不行的ui等等&#xff0c;体…

206. 反转链表 (简单系列)

给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例 3&#xff1a; 输…

DebugInfo 模块功能系统介绍 文本上色 文本与表格对齐 分隔线 秒表计算器 语义日期

背景 今天系统性的为大家介绍一下 DebugInfo 模块。这个模块提供了一些丰富的基本功能的封装&#xff0c;希望能给有需要的人带来些许帮助。 文本上色 DebugInfo 模块引入了 colorama提供文本颜色支持。 # -*- coding:UTF-8 -*-# region 引入必要依赖 from DebugInfo.DebugI…

数字时钟器

数字时钟器 一、实现功能&#xff1a; 实现基本的时钟样式实现时间日期的文本显示实现仿真秒针走动时候的音效支持设置时间&#xff0c;点击时钟中间设置时间&#xff0c;点击边缘恢复系统时间。 二、效果截屏&#xff1a; 三、github源码地址 GitHub - LxNoMi/DigitalClock…

FPGA数字时钟

FPGA数字时钟1 本代码借鉴了一些&#xff0c;网上资源。 1.设计目的 &#xff08;1&#xff09;掌握数字电子钟的设计方法&#xff1b; &#xff08;2&#xff09;掌握常用数字集成电路的功能和使用&#xff1b; &#xff08;3&#xff09;巩固数字电路理论知识&#xff0c;掌握…

HSP高度敏感的人应该了解的内容(附图书资源)

推荐图书:《高敏感是种天赋》 在爆炸的信息海中希望你慢慢看&#xff0c;细细品~ 什么是高敏感人群&#xff1f; &#xff08;Highly Sensitive Person&#xff0c;简称HSP&#xff09;是指那些对外界刺激和情绪变化非常敏感&#xff0c;容易受到情绪的影响&#xff0c;并且需…

基于Arduino的多功能数字时钟

实现功能&#xff1a; 显示时间、日期和星期断电保存时间通过按钮设置时间、日期整点响铃自定义闹钟显示温度自定义报警温度按键功能&#xff1a;按选择键进入设置时间功能&#xff1b;同时按 - 键进入闹钟和报警温度设置功能&#xff1b;再按选择键光标跳动&#xff0c;光标…