1.预备知识
2.样例程序介绍
3.运行代码
3.1.能在笔记本上成功运行 mobilenet_ssd 样例代码。
3.2.理解 Tengine 的工作方式。
3.3.能在 RK3399 上运行该程序。
3.4.能在 RK3399 上运行该程序并使用 GPU 加速
1.预备知识
CLion:一款优秀的C语言开发工具。主要是有免费的社区版本,并且能支持cmake文件的编译。最大的福利就是支持Ubuntu安装。
环境安装:0#00 Tengine 的安装
2.样例程序介绍
样例程序位于tengine目录下," tengine安装路径/examples/mobilenet_ssd/"
我们使用 CLion 打开该项目。
1.选中mobilenet_ssd文件夹
2.右键->open with(打开方式)->Other Applications->View All Applications->CLion(使用CLion打开该文件夹。)
打开该文件的目的:
1.能在笔记本上成功运行 mobilenet_ssd 样例代码。
2.理解 Tengine 的工作方式。
3.能在 RK3399 上运行该程序。
4.能在 RK3399 上运行该程序并使用 GPU 加速
注:当前阶段并不去理解 SSD 的工作方式,因为官方已经为我们提供训练好的 caffe 模型( prototxt + caffemodel )
从百度云中下载 caffe 模型 Tengine model zoo (密码: 57vb)
找到:
- MobileNetSSD_deploy.caffemodel
- MobileNetSSD_deploy.prototxt
将其存放在tengine路径下/models/
3.运行代码
TODO:1.能在笔记本上成功运行 mobilenet_ssd 样例代码。
如果直接使用 CLion 对该程序进行编译,会产生报错信息,类似缺少(找不到)头文件。
/usr/bin/cmake --build /home/lee/Documents/tengine/examples/mobilenet_ssd/cmake-build-debug --target MSSD -- -j4
......(省略)
/common.cpp:4:27: fatal error: tengine_c_api.h: 没有那个文件或目录
compilation terminated.
CMakeFiles/MSSD.dir/build.make:86: recipe for target 'CMakeFiles/MSSD.dir/home/lee/Documents/tengine/examples/common/common.cpp.o' failed
make[3]: *** [CMakeFiles/MSSD.dir/home/lee/Documents/tengine/examples/common/common.cpp.o] Error 1
make[3]: *** Waiting for unfinished jobs....
/home/lee/Documents/tengine/examples/mobilenet_ssd/mssd.cpp:32:27: fatal error: tengine_c_api.h: 没有那个文件或目录
compilation terminated.
CMakeFiles/MSSD.dir/build.make:62: recipe for target 'CMakeFiles/MSSD.dir/mssd.cpp.o' failed
......(省略)
原因在官方给的 gpu_cpu_mssd.md 中提到:
cd example/mobilenet_ssd
cmake -DTENGINE_DIR=/home/firefly/tengine .
make
所以问题出在 -DTENGINE_DIR 指定 Tengine 的安装路径。
解决办法:
在 CMAKE 文件中加入该行。
cmake_minimum_required (VERSION 2.8)
project(MSSD)
add_definitions(-std=c++11)
# TODO:设置 TENGINE_DIR 的路径
set( TENGINE_DIR /home/lee/Documents/tengine )
set( INSTALL_DIR ${TENGINE_DIR}/install/)
set( TENGINE_LIBS tengine)
那为什么不使用官方的方法,在CMAKE中加入一个选项呢?
这是我的一些尝试得到的经验:
1.不方便,每次 CMAKE 加入该行,有时我们使用的 tengine 路径和官方不一样,CMAKE 不会报错,修改的时候,要删除一些 CAMKE 产生的内容,才能重新 CMAKE。
2.无法利用 CLion 提供的优势,CLion 每次 CMAKE 会产生一个类似 build 的文件夹,方便管理,不需要再手动删除了。
3.直接编写,能为后续报错信息提供一些思路,有时候使用 ”-DTENGINE_DIR“ 进行指定路径 启动的减号是有中/英文字符区别的,会产生莫名奇妙的错误。
再次编译cmake文件就会没有报错信息了,注意:使用的 Tengine 路径需要修改为适合自己环境特征的。
运行该程序:
所以我们已经完全能够 运行 该 样例 了。
任务1:完成。
TODO:2.理解 Tengine 的工作方式。
任务1:只是单纯的运行程序,测试结果,和 example 差不多,类似于 我们安装的 Tengine 是否能够正常工作。
任务2:已知 MobileNet_SSD 能正常工作的前提下,进行代码的分析。
如果对于 Tengine 一无所知的话,可以利用 "MobileNet_SSD"进行理解,并且可以充分的利用 CLion 这个工具,进行函数查找,OAID 为 Tengine 编写的 "tengine_c_api.h" 文件提供了丰富的注释信息,我们可以将其改为中文,以便确认我们使用了哪些函数。
我的经历总结如下:
TODO:1. 调用 init_tengine() 对 tengine进行初始化
TODO:2. 调用 create_graph 将 训练好的模型 导入到 图(网络)中
TODO:3.1通过 get_graph_input_tensor 获取输入的 tensor
TODO:3.2用 set_tensor_shape 设置输入Tensor的shape
TODO: 4. 调用 prerun_graph 函数预启动图(类似 malloc,申请资源)
TODO: 5.1.向 input_data 写入输入的数据,
TODO: 5.2.并调用 set_tensor_buffer 把数据转移到输入Tensor上
TODO: 6. 调用 run_graph 运行图(做一次前向传播)
TODO: 7.1 向 input_data 写入输入的数据
TODO: 8. 调用 run_graph 运行图(做一次前向传播)
TODO: 9.1.调用 get_graph_output_tensor 获取输出Tensor
TODO: 9.2.并用 get_tensor_shape 取得 tensor 的shape
TODO: 9.3 利用get_tensor_buffer 取得输出 tensor 的内容
最后在退出程序前依次释放各个申请的动态空间
TODO: 9.4 释放 out_tensor 所占空间
TODO: 3.3 释放 input_tensor 所占空间
TODO: 2.2 调用 destroy_graph() 来释放资源
TODO: 1.1 释放 tengine
很明显,我们使用 tengine 只是用来进行前向计算(forward),并不需要后向反馈(backward)。
所以程序很有模板的感觉,这也为使用 tengine 降低了难度。
一些总结:tengine 和我常常使用的 PyTorch 进行前向推导并不太相同,
假设 模型为 Net,输入为 input,输出为 output。
PyTorch:output = Net(input)
tengine:
1. 导入模型:
load_model(Net,,"caffe", proto文件路径, model_file文件路径)
2.给模型输入:
get_graph_input_tensor()
3.获取模型输出
get_graph_output_tensor()
有点面向对象的操作。
接下来开始阅读示例代码(main函数):
1.定义默认参数变量名称
// root_path 为 Tengine 的文件路径
const std::string root_path = get_root_path();
// proto_file 为我们设置的 prototxt 文件路径
std::string proto_file;
// model_file 为我们设置的 model 文件路径
std::string model_file;
// image_file 为我们设置的 image 文件路径
std::string image_file;
// 设置保存的 生成图片 路径名称
std::string save_name="save.jpg";
// device 使用设备默认为空(用于graph)
const char * device=nullptr;
2.处理命令行的输入处理
int res;
// 与命令行输入相关, 指定可输入参数(-p -m -i -hd)
/*
* -p prototxt 文件路径 默认:"models/MobileNetSSD_deploy.prototxt"
* -m model 文件路径 默认:"models/MobileNetSSD_deploy.caffemodel"
* -i image 文件路径 默认:"tests/images/ssd_dog.jpg"
* -h help 信息
* -d device 设置 默认:为空
*/
while( ( res=getopt(argc,argv,"p:m:i:hd:"))!= -1)
{
switch(res)
{
case 'p':
proto_file=optarg;
break;
case 'm':
model_file=optarg;
break;
case 'i':
image_file=optarg;
break;
case 'd':
device=optarg;
break;
case 'h':
std::cout << "[Usage]: " << argv[0] << " [-h]\n"
<< " [-p proto_file] [-m model_file] [-i image_file]\n";
return 0;
default:
break;
}
}
3.设置 命令行参数默认信息
// prototxt 默认路径
if(proto_file.empty())
{
proto_file = root_path + DEF_PROTO;
std::cout << "proto file not specified,using " << proto_file << " by default\n";
}
// caffemodel 默认路径
if(model_file.empty())
{
model_file = root_path + DEF_MODEL;
std::cout << "model file not specified,using " << model_file << " by default\n";
}
// 使用图片的 默认路径
if(image_file.empty())
{
image_file = root_path + DEF_IMAGE;
std::cout << "image file not specified,using " << image_file << " by default\n";
}
开始使用 OAID 提供的 tengine_c_api.h。(建议配合 ctrl+鼠标点开对应的函数名称,进行查看作用)
4.完成TODO1,2
// TODO:1. 调用 init_tengine() 对 tengine进行初始化
if(init_tengine() < 0)
{
std::cout << " init tengine failed\n";
return 1;
}
// 检查库文件是否高于0.9
if(request_tengine_version("0.9") != 1)
{
std::cout << " request tengine version failed\n";
return 1;
}
// 确认 模型文件存在
if(!check_file_exist(proto_file) or (!check_file_exist(model_file) or !check_file_exist(image_file)))
{
return 1;
}
// TODO:2. 调用 create_graph 将 训练好的模型 导入到 图(网络)中
graph_t graph = create_graph(nullptr, "caffe", proto_file.c_str(), model_file.c_str());
// 判断是否创建 graph 成功
if(graph == nullptr)
{
std::cout << "Create graph failed\n";
std::cout << " ,errno: " << get_tengine_errno() << "\n";
return 1;
}
// 设置 运行 的设备
if(device != nullptr)
{
set_graph_device(graph, device);
}
5.设置我们处理图片的信息
在 SSD 论文中,对于图形的输入需要先进行 reshape。
// 输入图片信息
// img 的高(height)
int img_h = 300;
// img 的宽(width)
int img_w = 300;
// img 的大小(size)
int img_size = img_h * img_w * 3;
// 为输入图片申请空间,有mallloc,所以需要释放(free)
float *input_data = (float *)malloc(sizeof(float) * img_size);
int node_idx=0;
int tensor_idx=0;
6.对于模型输入的预处理,为了得到输入的入口
// TODO:1. 调用 init_tengine() 对 tengine进行初始化
if(init_tengine() < 0)
{
std::cout << " init tengine failed\n";
return 1;
}
// 检查库文件是否高于0.9
if(request_tengine_version("0.9") != 1)
{
std::cout << " request tengine version failed\n";
return 1;
}
// 确认 模型文件存在
if(!check_file_exist(proto_file) or (!check_file_exist(model_file) or !check_file_exist(image_file)))
{
return 1;
}
// TODO:2. 调用 create_graph 将 训练好的模型 导入到 图(网络)中
graph_t graph = create_graph(nullptr, "caffe", proto_file.c_str(), model_file.c_str());
// 判断是否创建 graph 成功
if(graph == nullptr)
{
std::cout << "Create graph failed\n";
std::cout << " ,errno: " << get_tengine_errno() << "\n";
return 1;
}
// 设置 运行 的设备
if(device != nullptr)
{
set_graph_device(graph, device);
}
7.多次重复,获取平均值(设置重复属性)
// 设置重复次数,求平均值
int repeat_count = 1;
// getenv 获取环境变量
const char* repeat = std::getenv("REPEAT_COUNT");
if(repeat)
/* strtoul: (str to ul)将字符类型转为unsigned long类型,
* 10:十进制 NULL:结束符
*/
repeat_count = std::strtoul(repeat, NULL, 10);
8.实际输入属性
//TODO: 5.1.向 input_data 写入输入的数据,
// 将 图片 的路径名转化为 图片内容的矩阵,并赋值给 input_data
get_input_data_ssd(image_file, input_data, img_h, img_w);
//TODO: 5.2.并调用 set_tensor_buffer 把数据转移到输入Tensor上
set_tensor_buffer(input_tensor, input_data, img_size * 4);
9.实际运行
//TODO: 6. 调用 run_graph 运行图(做一次前向传播)
ret = run_graph(graph, 1);
if(ret != 0)
{
std::cout << "Run graph failed, errno: " << get_tengine_errno() << "\n";
return 1;
}
10.利用重复获取平均运行时间
struct timeval t0, t1;
float total_time = 0.f;
for(int i = 0; i < repeat_count; i++)
{
//TODO: 7.1 向 input_data 写入输入的数据,
get_input_data_ssd(image_file, input_data, img_h, img_w);
// 获取当前值,并赋值给 t0(开始值)
gettimeofday(&t0, NULL);
//TODO: 8. 调用 run_graph 运行图(做一次前向传播)
run_graph(graph, 1);
gettimeofday(&t1, NULL);
float mytime = ( float )((t1.tv_sec * 1000000 + t1.tv_usec) - (t0.tv_sec * 1000000 + t0.tv_usec)) / 1000;
total_time += mytime;
}
std::cout << "--------------------------------------\n";
std::cout << "repeat " << repeat_count << " times, avg time per run is " << total_time / repeat_count << " ms\n";
11.获取输出信息
//TODO: 9.1.调用 get_graph_output_tensor 获取输出Tensor
tensor_t out_tensor = get_graph_output_tensor(graph, 0, 0); //"detection_out");
/*
* out_dim[4]中元素的含义
* [0]:批次:1张图
* [1]:检测到目标个数:3个目标
* [2]:outdata 的 Box 6 个信息:
* 0. 属于的类别(下标)
* 1. 属于该类别的score
* 2. 左上角点(x)相对于宽的百分比
* 3. 左上角点(y)相对于高的百分比
* 4. 右上角点(x)相对于宽的百分比
* 5. 右上角点(y)相对于高的百分比
* [3]:1 一行
*/
int out_dim[4];
//TODO: 9.2.并用 get_tensor_shape 取得 tensor 的shape
ret = get_tensor_shape(out_tensor, out_dim, 4);
if(ret <= 0)
{
std::cout << "get tensor shape failed, errno: " << get_tengine_errno() << "\n";
return 1;
}
//TODO:9.3 利用get_tensor_buffer 取得输出 tensor 的内容
float* outdata = ( float* )get_tensor_buffer(out_tensor);
12.利用 输出 和 OpenCV 进行画图
int num = out_dim[1];
//设置阈值,是否为检测目标
float show_threshold = 0.5;
// 通过 outdata 对 image 进行绘制边框和label信息,并保存
post_process_ssd(image_file, show_threshold, outdata, num, save_name);
12.程序结束,记得释放内存和资源
//TODO: 9.4 释放 out_tensor 所占空间
release_graph_tensor(out_tensor);
//TODO: 3.1 释放 input_tensor 所占空间
release_graph_tensor(input_tensor);
// 释放 graph 执行所占用的资源
ret = postrun_graph(graph);
if(ret != 0)
{
std::cout << "Postrun graph failed, errno: " << get_tengine_errno() << "\n";
return 1;
}
free(input_data);
//TODO: 2.2 调用 destroy_graph() 来释放资源
destroy_graph(graph);
//TODO: 1.1 释放 tengine
release_tengine();
任务2:完成
TODO:3.能在 RK3399 上运行该程序。
RK3399上 tengine 的路径可能会与 笔记本电脑 不同。使用不同的 github 网址,下载下来的 tengine 内容虽然一样,但是 文件名称可能会不一样。比如 tengine-master,tengine,Tengine。(这些都是小细节)
并且 RK3399 上没有 CLion,所以我们需要手动进行 cmake。
过程如下:(建议修改前进行 备份 )
1.修改 "CMakeLists.txt" 文件
# 设置 tengine 路径
set( TENGINE_DIR /home/firefly/tengine )
2.创建 build 文件,目的是为了放置 cmake 文件设置错误,删除文件的时候,不确定要删除哪些文件。
1.在 mobilenet_ssd 目录下创建 build 文件
cd tengine/examples/mobilenet_ssd
mkdir build
cd build
2.进行 cmake,前一级目录( cmake 目的是为了生成 make文件)
cmake ..
返回信息
""
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /usr/local (found version "3.4.2")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lee/Documents/tengine/examples/mobilenet_ssd/build
""
3.进行make
make
返回信息
""
Scanning dependencies of target MSSD
[ 33%] Building CXX object CMakeFiles/MSSD.dir/mssd.cpp.o
[ 66%] Building CXX object CMakeFiles/MSSD.dir/home/lee/Documents/tengine/examples/common/common.cpp.o
[100%] Linking CXX executable MSSD
[100%] Built target MSSD
""
4.执行可执行文件
firefly@firefly:~/Tengine/examples/mobilenet_ssd/build$ ./MSSD
/home/firefly/Tengine/examples/mobilenet_ssd/build/MSSD
proto file not specified,using /home/firefly/Tengine/models/MobileNetSSD_deploy.prototxt by default
model file not specified,using /home/firefly/Tengine/models/MobileNetSSD_deploy.caffemodel by default
image file not specified,using /home/firefly/Tengine/tests/images/ssd_dog.jpg by default
tensor: detection_out created by node: detection_out is not consumed
add the node: detection_out into output list
load model done!
--------------------------------------
repeat 1 times, avg time per run is 197.33 ms
detect result num: 3
dog :100%
BOX:( 138.509 , 209.394 ),( 324.57 , 541.314 )
car :100%
BOX:( 467.315 , 72.8045 ),( 687.269 , 171.128 )
bicycle :100%
BOX:( 107.395 , 140.657 ),( 574.212 , 415.188 )
======================================
[DETECTED IMAGE SAVED]: [save.jpg](https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxcheckurl?requrl=http%3A%2F%2Fsave.jpg&skey=%40crypt_f9d3b6a2_596aae6997f00ed042e8dccce68cd50a&deviceid=e183140456749306&pass_ticket=OlSyJ0Ia1IhtysLjA0CCvzP6XXavJ0bmawua8PAWQ1GErsg5SDT81873sBxClMpl&opcode=2&scene=1&username=@f7db13e1c47e0fed7e506d9d48739f7e)
======================================
Release Graph Executor for graph graph
Release workspace default resource
打开图片进行检查。(图片在 mobilenet_ssd/build/save.jpg 目录下)
任务3:完成
TODO:4.能在 RK3399 上运行该程序并使用 GPU 加速。
在 RK3399 上使用 GPU ,在 OAID 上有相关的教程gpu_cpu_mssd。
需要注意的是如果之前已经编译过 tengine ,不能直接使用GPU。需要重新编译(但是我还是失败了)。
1.清除 make 信息
make clean
2.删除 与 驱动相关的文件
rm -r build/driver
我使用GPU时,重新下载 tengine。(即删除原有的 tengine 目录)
下面我从 0 开始讲解如何 设置 RK3399 的 GPU 使用。
1.从 github 中拷贝 ComputeLibrary
git clone https://github.com/ARM-software/ComputeLibrary.git
git checkout v18.05
2.编译 ComputeLibrary
apt-get install scons
scons Werror=1 -j4 debug=0 asserts=1 neon=0 opencl=1 embed_kernels=1 os=linux arch=arm64-v8a
3.修改 tengine 的 makefile.config 文件
1.使用Arm64
# Set the target arch
CONFIG_ARCH_ARM64=y
2.使用GPU
# Enable GPU support by Arm Computing Library
CONFIG_ACL_GPU=y
3.不使用BLAS
# Use BLAS as the operator implementation
# CONFIG_ARCH_BLAS=y
4.指定ACL库的路径(注意路径是否正确)
# Set the path of ACL
ACL_ROOT=/home/firefly/ComputeLibrary
5.使用caffe模型
# Enable other serializers
CONFIG_CAFFE_SERIALIZER=y
4.进行 make
make
5.进行安装
make install
6.检查
./build/tests/bin/bench_sqz -d acl_opencl
对我们的 样例 进行修改。
1.修改 "CMakeLists.txt" 文件
cd examples/mobilenet_ssd
leafpad CMakeLists.txt
1.加入 tengine 路径
# 设置 tengine 路径
set( TENGINE_DIR /home/firefly/tengine )
2.添加 build 文件
mkdir build
cd build
3.进行 cmake 编译
cmake ..
4.进行 make 编译
make
5.设置使用 GPU 参数(将GPU频率设置为 800000000)
sudo su
echo "performance" >/sys/devices/platform/ff9a0000.gpu/devfreq/ff9a0000.gpu/governor
cat /sys/devices/platform/ff9a0000.gpu/devfreq/ff9a0000.gpu/cur_freq
命令行输入以下信息(设置临时环境变量)
#
export GPU_CONCAT=0
# 使能GPU fp16
export ACL_FP16=1
# 设置重复次数
export REPEAT_COUNT=5
# 运行
taskset 0x1 ./MSSD -d acl_opencl
显示如下:
firefly@firefly:~/tengine/examples/mobilenet_ssd/build$ export GPU_CONCAT=0
firefly@firefly:~/tengine/examples/mobilenet_ssd/build$ export ACL_FP16=1
firefly@firefly:~/tengine/examples/mobilenet_ssd/build$ export REPEAT_COUNT=5
firefly@firefly:~/tengine/examples/mobilenet_ssd/build$ taskset 0x1 ./MSSD -d acl_opencl
/home/firefly/tengine/examples/mobilenet_ssd/build/MSSD
proto file not specified,using /home/firefly/tengine/models/MobileNetSSD_deploy.prototxt by default
model file not specified,using /home/firefly/tengine/models/MobileNetSSD_deploy.caffemodel by default
image file not specified,using /home/firefly/tengine/tests/images/ssd_dog.jpg by default
ACL Graph Initialized
Driver: ACLGraph probed 1 devices
tensor: detection_out created by node: detection_out is not consumed
add the node: detection_out into output list
load model done!
--------------------------------------
repeat 5 times, avg time per run is 165.676 ms
detect result num: 3
dog :100%
BOX:( 138.419 , 209.091 ),( 324.504 , 541.568 )
car :100%
BOX:( 467.356 , 72.9224 ),( 687.269 , 171.123 )
bicycle :100%
BOX:( 107.053 , 140.221 ),( 574.472 , 415.248 )
======================================
[DETECTED IMAGE SAVED]: [save.jpg]
======================================
Release Graph Executor for graph graph
Release workspace default resource
完成任务4。
附比较 RK3399 和 笔记本 时间上的差距
1.RK3399 不使用 GPU 的情况下:
repeat 1 times, avg time per run is 197.33 ms
2.RK3399 使用 GPU的情况下:
repeat 5 times, avg time per run is 165.676 ms
3.笔记本使用 BLAS 库的情况下
repeat 1 times, avg time per run is 133.117 ms
GPU 的使用,对RK3399有明显的加速。
与官方给的 mssd 。