目录
- 创建流
- 打印 engine 信息
- 打印结果
- 内部流程
- 启动计时功能
- 加载变换矩阵并更新数据(重要)
- 内部实现
该系列文章与qwe、Dorothea一同创作,喜欢的话不妨点个赞。
在create_core
方法结束后,我们的视角回到了main.cpp中。继续来看接下来的流程。本章的重点在于pytorch导出的tensor数据的解析。
创建流
流用于表示一系列的命令(如内存传输命令和核函数执行命令)在 GPU 上的执行顺序。流中的命令按照它们被插入的顺序在 GPU 上执行,但不同流中的命令可以并行执行。这里主要用于更新数据、推理和可视化时使用。
打印 engine 信息
这里会打印出执行CUDA-BEVFusion时,终端打印的信息中的网络信息。
从这里我们能清楚的看到一下几点:
- 1)当前网络属于哪个部分。
- 2)网络输入和输出的个数,数据形状
输出的网络数量与onnx数量是一一对应的。
打印结果
内部流程
print
的具体实现在下方。
这部分用于打印这四个 engine 的信息,包含模型名称和绑定点的信息(输入输出是否为动态形状,输入输出节点索引、名称、维度和类型)。
启动计时功能
是否计时,这里设置为true
core
是CoreImplement
的一个实例,而CoreImplement
继承了类Core
在类Core
中set_timer是一个纯虚方法。
用于后续判断是否打印推理时每个模块的用时。
加载变换矩阵并更新数据(重要)
nv::Tensor
、nv::format
是 Nvidia 提供在src/common
中的工具- 下图 246 行之 249 行,加载一系列准备好的矩阵参数。
- 下图所加载的
.tensor
后缀的文件,均是从pytorch中导出,保存的二进制文件。
- 格式化字符串传入:调用
nv::format
函数,传入格式字符串"%s/camera2lidar.tensor"
和data
指针。这里的%s
是一个占位符,用于指示将在这个位置插入一个字符串。 - 变量参数处理:在
format
函数内部,函数首先定义一个字符数组buffer[2048]
作为存储结果的缓冲区。然后,它使用va_list vl
初始化可变参数列表,并通过va_start(vl, fmt)
宏开始访问这些参数。 - 字符串格式化:使用
vsnprintf
函数,将data
指针所指向的字符串(即"example-data"
)和格式字符串合并。vsnprintf
根据格式字符串"%s/camera2lidar.tensor"
替换%s
为data
指向的字符串,因此格式化后的字符串将变为"example-data/camera2lidar.tensor"
。 - 安全检查和内存管理:
vsnprintf
函数使用sizeof(buffer)
确保不会向buffer
写入超出其容量的数据,从而避免缓冲区溢出。这是一个重要的安全特性,确保即使格式化的字符串非常长,也不会导致内存损坏。
5. 返回结果:格式化后的字符串存储在buffer
中。format
函数最后将buffer
转换为std::string
类型并返回这个字符串。
- 加载结果,以 加载camera2lidar 为例:
通过上图,我们就可以发现,.tensor
后缀的文件存储的数据,可以分为数据头
,和数据
两个部分。
数据头
,即描述数据
信息的属性,例如shape、numel、ndim。描述数据
的信息。
数据
,即具体的数据的起始地址。
通过数据的读取,我们可以大致看一下作者是如何设计的。
内部实现
- 取文件前 3 个 int 字节大小的内容,第一个是 magic_number,类似识别码,第二个表示数据维度数量(ndims),第三个表示数据类型 id。
- 使用
dims
来存储每个维度的数值,使用shape
来储存形状,用于后续创建Tensor
。计算矩阵的总参数量volumn
,然后通过每个数据占用空间dtype
和总参数量volumn
来计算储存矩阵数据需要的空间bytes
。 - 读取文件中的数据,在
host
上使用容器host_data
来储存。 - 在
host
上创建Tensor
对象,并将数据拷贝到output
中。
- 小结:
-
- nvidia这个仓库的src/common中的tensor解析比较重要,
nv::Tensor
是一个通用的pytorch与c++数据联通的桥梁,值得一看。
- nvidia这个仓库的src/common中的tensor解析比较重要,