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

news2024/9/23 3:29:52

目录

    • 前言
    • 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.coloradmin.cn/o/936322.html

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

相关文章

Go语言入门记录:从基础到变量、函数、控制语句、包引用、interface、panic、go协程、Channel、sync下的waitGroup和Once等

程序入口文件的包名必须是main&#xff0c;但主程序文件所在文件夹名称不必须是main&#xff0c;即我们下图hello_world.go在main中&#xff0c;所以感觉package main写顺理成章&#xff0c;但是如果我们把main目录名称改成随便的名字如filename也是可以运行的&#xff0c;所以…

hiredis的安装与使用

hiredis的介绍 Hiredis 是一个用于 C 语言的轻量级、高性能的 Redis 客户端库。它提供了一组简单易用的 API&#xff0c;用于与 Redis 数据库进行交互。Hiredis 支持 Redis 的所有主要功能&#xff0c;包括字符串、哈希、列表、集合、有序集合等数据结构的读写操作&#xff0c…

Git企业开发控制理论和实操-从入门到深入(五)|标签管理

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

python+协同过滤算法实现简单的图书推荐系统

背景介绍 当我们做一些推荐系统网站时&#xff0c;通常需要合适的推荐算法&#xff0c;下面给大家介绍推荐系统中经典的推荐算法——协同过滤算法。在本文中通过Python语言&#xff0c;以一个图书推荐系统为案例&#xff0c;最终实现一个基于用户对图书的评分而对指定的用户个…

带你启用window10专业版系统自带的远程桌面

启用window10专业版系统自带的远程桌面 文章目录 启用window10专业版系统自带的远程桌面前言1.找到远程桌面的开关2. 找到“应用”项目3. 打开需要远程操作的电脑远程桌面功能 总结 前言 Windows操作系统作为应用最广泛的个人电脑操作系统&#xff0c;在我们身边几乎随处可见。…

Feign在实际项目中使用详解

Feign在实际项目中使用详解 简介一 Feign客户端应该如何提供&#xff1f;二 Feign调用的接口要不要进行包装&#xff1f;2.1.问题描述2.2.问题解决 三 Feign如何抓取业务生产端的业务异常&#xff1f;3.1.分析3.2.Feign捕获不到异常3.3.异常被额外封装3.4.解决方案 案例源码 简…

Visual Studio2022史诗级更新,增加多个提高生产力的功能

Visual Studio 2022发布了17.7x版&#xff0c;这次更新中&#xff0c;增加多个提高生产力的功能以及性能进一步改进。 如果要体验新功能&#xff0c;需要将Visual Studio 2022的版本升级到17.7及以上 下面我们看看新增的功能以及改进的功能&#xff01; 目录 文件比较自动修复代…

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; 输…

【数据结构与算法篇】 手撕八大排序算法之选择排序

​&#x1f47b;内容专栏&#xff1a; 《数据结构与算法篇》 &#x1f428;本文概括&#xff1a;选择排序包括直接选择排序与堆排序&#xff0c;本篇讲述直接选择排序与堆排序的思想及实现、复杂度及稳定性的分析。 &#x1f43c;本文作者&#xff1a; 花 蝶 &#x1f438;发布…

CSS中的margin与padding

目录 一、margin 1.概念及作用 2.基本语法 3.margin的用法 二、padding 1.介绍 2.基本语法及要求 3. 用法 4.内边距和元素宽度 讲这些之前&#xff0c;先看一张图&#xff0c;便于理解 一、margin 1.概念及作用 CSS margin 属性用于在任何定义的边框之外&#xff0c;…

K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(上)

温故知新 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 基本环境信息&#x1f4d7;机器信息&#x1f4d7;软件信息&#x1f4d7;部署用户kubernetes &#x1f4da;第三章 Kubernetes各组件部署&#x1f4d7;安装kube…

二分队列+决策单调性优化dp:P6246

https://www.luogu.com.cn/problem/P6246 决策单调性 若 d p i dp_i dpi​ 由 j j j 转移&#xff0c;则 d p i 1 dp_{i1} dpi1​ 转移点 k k k 满足 k ≥ j k\ge j k≥j 发现决策点满足单调&#xff0c;但遍历的点不满足单调&#xff0c;不能用双指针&#xff0c;考虑…

计算机组成原理学习笔记-精简复习版

一、计算机系统概述 计算机系统硬件软件 计算机硬件的发展&#xff1a; 第一代计算机&#xff1a;(使用电子管)第二代计算机&#xff1a;(使用晶体管)第三代计算机&#xff1a;(使用较小规模的集成电路)第四代计算机&#xff1a;(使用较大规模的集成电路) 冯诺依曼体系结构…

VSCode连接服务器

Pycharm连接服务器参考我的另一篇文章Pycharm远程连接服务器_pycharm进入服务器虚拟环境终端_Jumbo星的博客-CSDN博客 本质上Pycharm和VSCode都只是IDE&#xff0c;没有什么好坏之分。但是因为Pycharm连接服务器&#xff08;准确来说是部署&#xff09;需要买professional。而…

5 群起集群

1.在启动集群之前&#xff0c;先配置workers,有几个节点就配置几个 [atguiguhadoop102 hadoop]$ vim /opt/module/hadoop-3.1.3/etc/hadoop/workers在该文件中增加如下内容&#xff1a; hadoop102 hadoop103 hadoop104 注意&#xff1a;该文件中添加的内容结尾不允许有空格&a…

权限提升-手工-系统权限提升

权限提升基础信息 1、具体有哪些权限需要我们了解掌握的&#xff1f; 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 2、以上常见权限获取方法简要归类说明&#xff1f; 后台权限&#xff1a;SQL注入,数…

检测链表中是否存在环

题目、解析和代码 题目&#xff1a;给定一个单链表&#xff0c;判断其中是否有环的存在 解析&#xff1a;这里使用两个遍历速度不一样的结点进行判断&#xff0c;一个慢结点从首结点开始遍历&#xff0c;这个结点每次只遍历一个结点&#xff1b;一个快结点从第二个结点进行遍历…

设计模式之八:迭代器与组合模式

有许多方法可以把对象堆起来成为一个集合&#xff08;Collection&#xff09;&#xff0c;比如放入数组、堆栈或散列表中。若用户直接从这些数据结构中取出对象&#xff0c;则需要知道具体是存在什么数据结构中&#xff08;如栈就用peek&#xff0c;数组[]&#xff09;。迭代器…

WinPlan经营大脑:精准预测,科学决策,助力企业赢得未来

近年,随着国内掀起数字化浪潮,“企业数字化转型”成为大势所趋下的必选项。但数据显示,大约79%的中小企业还处于数字化转型初期,在“企业经营管理”上存在着巨大的挑战和风险。 WinPlan经营大脑针对市场现存的企业经营管理难题,提供一站式解决方案,助力企业经营管理转型…

设计模式--代理模式

笔记来源&#xff1a;尚硅谷Java设计模式&#xff08;图解框架源码剖析&#xff09; 代理模式 1、代理模式的基本介绍 1&#xff09;代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标对象2&#xff09;这样做的好处是…