入门培训4:使用Raintime运行SSD实例指南



  • 使用Raintime运行SSD模型

    目录

    I. 介绍

    入门培训3中,我们描述了如何使用plumber将TensorFlow模型导出到SG描述文件和权值数据。
    本教程主要关注如何使用raintime完成在板卡上的计算。

    raintime是运行在鲲云板卡上的计算图运行时(runtime)和卷积神经网络计算库。
    raintime接收plumber输出的SG描述和权值文件,根据SG描述部署计算过程并执行计算,通过权值文件初始化计算节点所依赖的数据。
    raintime可以根据SG描述所提供的每个节点的信息,分配不同的计算函数、数据类型、计算设备给不同的节点。
    raintime也提供了简洁的调用接口,仅需几行代码即可整合raintime的计算功能到目标应用中。

    本教程的目标是,以把SSD模型从原始的TensorFlow模型转换到板卡上的raintime的应用程序这一过程为例,讲述raintime的工作原理和使用方式。
    具体而言,该过程包括如下几步:

    1. 从原始的TensorFlow模型转换到SG描述和数据文件;
    2. 搭建应用程序框架,添加raintime的计算函数;
    3. 连接预处理和后处理函数,完成完整的应用程序设计;

    SSD即Single Shot Multibox Detector,它的优势是无需对数据进行两次分析,即可以在单次计算中同时实现目标分类目标检测,是一个常用的目标检测算法。
    本教程在最初发布于此处的SSD算法的基础上进行了部分调整。

    SG的基础概念请参见入门培训3

    本教程使用plumber1.2.1版本。

    II. 获取SSD模型的SG描述和数据

    本教程提供一份数据包Tutorial4.tar,解压该压缩包,可以得到目录Tutorial4,点击此处下载:Tutorial-4.tar

    其中包含:

    1. plumber-web-5b文件夹。该文件夹包含在硬件上执行所需要的模型SG定义文件face_5b_model_hdl_opt_dfg.pbtxt,模型权重参数./data/float_little/以及后处理参数./post_params
    2. SSD 应用程序样例example

    获取SG描述

    SG描述是由plumber自原始TensorFlow模型编译得到的。为了获得SG描述,需要按生成SG -> 优化SG -> 为硬件优化SG的顺序调用plumber_cli指令。请用户参考入门培训3

    数据文件

    本教程所用到的数据文件是指针对硬件执行调整过的权值数据,被包装在ssd_hdl_data.zip中。
    将其解压,可以得到一个data目录,其中包含:

    • float_little:所有浮点表示的权值数据,按照小端序存储;
    • fixed_little:所有被量化至低精度的定点数权值数据,也按小端序存储;
    • README.txt:数据生成时使用的配置信息;

    raintime一般只加载float_little中的数据文件,在运行过程中自动进行量化操作,将数据转换为低精度的数据格式。

    fixed_little中的数据一般用做测试比对的标准。

    本教程提供的数据的量化方式为:将32位的浮点数格式的数据,转换为16位的定点数格式,其小数位有9位,整数位有6位,符号位有1位。

    每一个数据目录中都包含有一个config.json文件,描述了该目录下的数据文件和模型中的权值之间的一一对应关系。raintime在加载数据时会利用其中的信息。

    III. 在应用程序中集成Raintime

    在上一章中我们获得了raintime需要使用的SG文件和权值数据文件,本章则描述如何在一个应用程序中集成raintime的功能。

    raintime是基于图结构来进行计算的:传给raintime计算图,即 SG 描述,它可以根据图的结构,即节点和边,来完成 SG 所定义的计算。
    每个节点都对应了一个计算过程,如卷积层、池化层、激活层等,并且规定了该节点的计算设备和数据类型。
    节点之间的有向边表示数据流:如果边从节点 A 指向节点 B ,则该边表示 A 的输出是 B 的输入。

    raintime是一个 C++ 库,因此只有通过 C++ 开发的应用程序才可集成raintime
    集成的方式与一般的 C++ 库无异:引用raintime的头文件、在应用程序代码中添加raintime的库函数、在编译时链接到raintime的库文件。
    接下来我们逐一描述各个步骤。

    1. Raintime的头文件

    Raintime相关的头文件均位于/usr/local/include/,其中包括raintimeplumber_irlibssdEigen
    其中,raintime的核心头文件为/usr/local/include/raintime/core.h,其中包含关键的raintime的库函数。

    2. 在应用程序中添加raintime的库函数

    raintime的核心库函数分为如下几类:

    • 加载 SG 描述:将本地的 SG 描述文件加载到内存中,成为可以被执行的SG对象
    • 加载权值数据:将本地的数据文件加载到内存中,在执行过程中被用于初始化计算图中的常量
    • 执行计算:构建执行器,以计算图对象和权值数据为参数,返回计算结果

    3. 加载 SG 描述

    raintime加载 SG 描述主要有如下几步:

    1. 初始化SGDef对象,解析本地的pbtxt文件并合并到初始化的SGDef对象中;
    2. 使用SGBuilderSGDef转换为SG对象;

    我们可以使用如下的代码块来完成上述加载流程:

    SGDef *LoadFromFile(const char *file_name) {
      auto sg_def = new SGDef();
    
      int fd = open(file_name, O_RDONLY);
      if (fd < 0) {
        fprintf(stderr, "Cannot open file %s\n", file_name);
        exit(1);
      }
    
      io::FileInputStream in(fd);
      in.SetCloseOnDelete(true);
    
      if (!TextFormat::Parse(&in, sg_def)) {
        fprintf(stderr, "Failed to parse file %s\n", file_name);
        exit(1);
      }
    
      return sg_def;
    }
    
    // in your main function
    SGDef *sg_def = LoadFromFile("xxx.pbtxt");
    SGBuilder builder(sg_def);
    SG *sg = builder.Build();
    

    LoadFromFile在后续raintime版本中会加入到核心库。

    plumber_ir中定义了SGDef的格式。

    4. 加载权值数据

    raintime的执行器以SGDataMap为主要的权值数据的存储数据结构。
    加载权值数据需要先构建SGDataMap,然后调用其LoadFromDir方法完成从本地数据目录加载数据的过程。
    如下所示:

    SGDataMap *data_map = new SGDataMap();
    data_map->LoadFromDir("xxx/float_little");
    

    5. 加载输入图片

    SSD 模型除了权值之外,还需要输入图片来完成计算。
    由于当前版本的raintime的接口依赖SGDataMap,输入图片也需要先封装到SGDataMap中。

    如下所示,该函数实现了加载输入图片并封装到SGDataMap的功能:

    SGDataMap* LoadFromMem(vector<float>& input_tensor) 
    {
    
      int length = input_tensor.size();
      // create the shared pointer to the image data to be stored in SGDataMap
      auto data = std::make_shared<std::vector<float>>(length);
      *data = input_tensor;
    
      // shape is not important in this case
      std::vector<int> shape;
    
      // create the SGDataMap
      auto data_map = new SGDataMap();
      SGDataMap::ValT val(shape, data);
      data_map->set("img_input", val);
    
      return data_map;
    }
    

    field为输入图片所对应的 SG 中的输入节点的名称。本教程提供的 SSD 模型的输入节点名称为 "img_input",因此这里的field应取值"img_input"
    输入节点可以在 SG 描述文件中,通过查找 op 值为 Input 的节点得到。

    6. 构建执行器并执行计算

    现阶段,我们在应用程序中已经集成了 SG 描述和权值数据的加载函数。接下来是关键的计算执行。

    raintime的计算都封装在执行器,即SGRunner中。执行器要分两步执行计算:构建(Build)和运行(Run)。
    构建过程会遍历SG的所有节点,根据节点之间的依赖关系调度节点的执行过程,为每个节点的计算准备上下文环境,并把权值数据加载到上下文环境中的常量中。
    运行过程则会把输入图片数据传入SG的输入节点,并依照构建好的执行顺序完成每个节点的计算。
    如下例所示:

    SGRunner runner;
    runner.Build(sg, const_data_map);
    SGDataMap * output = runner.Run(sg, const_data_map, input_data_map);
    

    构建与运行的分离的目的是尽可能减少核心计算之外的运行开销。
    使用执行器时,如果要执行多次计算,则只需构建一次、调用多次Run

    Run的返回结果为一个SGDataMap的对象,其中包含所有输出节点的输出数据。
    后续章节中会描述如何在我们的应用程序中利用该输出数据。

    7. 链接raintime

    我们提供的系统镜像中已经预装好了raintime的库,包括头文件与库文件,以及其他依赖库。

    用户可参考Tutorial-4/example/Tutorial-4/example/single_img/文件夹下的CMakeLists.txt文件。

    // include path
    include_directories(
      /usr/local/include/
      /usr/local/include/raintime/
      /usr/local/include/raintime/third_party/
      ${Protobuf_INCLUDE_DIRS}
      ${OpenCV_INCLUDE_DIRS}
    )
    
    // library path
    link_directories(
      /usr/local/lib/
    )
    
    // link raintime related libs to project
    target_link_libraries(ssd_5b_runner plumber_ir raintime ssd gflags glog pthread -lprotobuf ${OpenCV_LIBS})
    

    IV. 添加预处理和后处理函数

    上一章描述了一个基于raintime的应用程序应包含的核心组件。
    为了完成一个完整的应用程序,我们还需要添加与应用密切相关的业务逻辑。

    本教程的 SSD 模型所对应的业务逻辑包括图片的预处理和 SSD 输出结果的后处理。

    预处理的逻辑比较简单,仅需在输入图片的基础上进行计算,然后把处理过的输入图片加载到输入SGDataMap中即可。
    后处理函数则需要从输出SGDataMap中提取需要的内容,并转换到合适的输出结果。

    具体请参考example/single_img/ssd_5b_runner.cc的内容。

    V. 总结

    本教程主要描述了如何把原始的TensorFlow模型转换为可以在板卡上执行的应用程序。
    这一转换过程包括:利用plumber获取 SG 描述和权值数据,构建基于raintime的应用程序,完成应用程序的业务逻辑等。

    一个raintime的应用程序模板如下所示:

    // include raintime/core.h
    // ...
    
    // namespaces
    using namespace raintime;
    using namespace raintime::sg;
    using namespace plumber_ir;
    
    // some helper functions
    // ...
    
    // the main function
    int main(int argc, char *argv[]) {
      // load sg
      SGDef *sg_def = LoadFromFile("xxx.pbtxt");
      SGBuilder builder(sg_def);
      SG *sg = builder.Build();
      
      // load const data
      SGDataMap *const_data_map = new SGDataMap;
      const_data_map->LoadFromDir("xxx/float_little");
    
      // pre-processing
      auto img = PreProcess(/* ... */);
      auto shape = GetImageShape();
      auto input_data_map = LoadFromImage("img_input", img, shape);
    
      // build and run
      SGRunner runner;
      runner.Build(sg, const_data_map);
      SGDataMap *output = runner.Run(sg, const_data_map, input_data_map);
    
      // post-processing
      auto result = PostProcess(output);
    
      return 0;
    }
    

    总而言之,基于raintime开发的应用程序,尤其是基于卷积神经网络的视觉任务,一般都可以在上述模板的基础上进行设计。



  • 此回复已被删除!


  • @fangzhou10 好的,板子上的rainman是已经更新了吗?请问我应该用什么方式更新?



  • @shiny 新的raintime教程已经更新了,更新了tutorial4压缩文件。下周1/2还要确认一下板子raintime的版本。



  • 另外,镜像系统中的raintime是否有升级?每一次编译必须在板上系统进行吗?能否下载相应的库文件到工作机中编译,再将编译好的文件直接scp到板上?



  • 下载了tutorial_raintime with SSD.zip,解压后放到了rainman上的root文件夹里,然后再example文件夹中新建了一个Makefile文件,文件内容:

    PREFIX = /usr/local

    CFLAGS = -std=c++11 -I$(PREFIX)/include -I$(PREFIX)/include/raintime -I$(PREFIX)/include/raintime/third_party
    LFLAGS = -L$(PREFIX)/lib/ -lraintime -lplumber_ir -lglog -lprotobuf

    OBJS = ssd_6b_runner.o

    %.o: %.cc
    $(CXX) $(CFLAGS) -c $<

    ssd_6b_runner: ssd_6b_runner.o
    $(CXX) $(CFLAGS) $^ -o $@ $(LFLAGS)

    all: ssd_6b_runner

    .PHONY: clean

    clean:
    rm -f *.o ssd_6b_runner

    输入make指令后,报错没有plumber_ir/sg_def.pb.h文件

    将两个头文件中的#include "plumber_ir/sg_def.pb.h"都注释掉后,再输入make指令,依然报错,报错内容如下:

    g++ -std=c++11 -I/usr/local/include -I/usr/local/include/raintime -I/usr/local/include/raintime/third_party -c ssd_6b_runner.cc
    In file included from ssd_6b_runner.cc:26:0:
    ssd_utils.hh:45:39: error: 'SGDataMap' has not been declared
    OutputNodeType IdentifyOutputNodeType(SGDataMap::KeyT &key,
    ^
    ssd_utils.hh:45:56: error: 'key' was not declared in this scope
    OutputNodeType IdentifyOutputNodeType(SGDataMap::KeyT &key,
    ^
    ssd_utils.hh:46:39: error: 'SGDataMap' has not been declared
    SGDataMap::ValT &val);
    ^
    ssd_utils.hh:46:56: error: 'val' was not declared in this scope
    SGDataMap::ValT &val);
    ^
    ssd_utils.hh:46:59: error: expression list treated as compound expression in initializer [-fpermissive]
    SGDataMap::ValT &val);
    ^
    ssd_utils.hh:51:1: error: 'SGDef' does not name a type
    SGDef *LoadFromFile(const char *file_name);
    ^
    ssd_utils.hh:52:1: error: 'SGDataMap' does not name a type
    SGDataMap *LoadFromDataDir(const char *data_dir);
    ^
    ssd_utils.hh:53:1: error: 'SGDataMap' does not name a type
    SGDataMap LoadFromImageFile(const char image_file);
    ^
    ssd_6b_runner.cc:36:27: error: 'sg' is not a namespace-name
    using namespace raintime::sg;
    ^
    ssd_6b_runner.cc:36:29: error: expected namespace-name before ';' token
    using namespace raintime::sg;
    ^
    ssd_6b_runner.cc: In function 'int main(int, char
    )':
    ssd_6b_runner.cc:67:49: error: 'LoadFromFile' was not declared in this scope
    auto sg_def = LoadFromFile(FLAGS_pbtxt.c_str());
    ^
    ssd_6b_runner.cc:70:3: error: 'SGDataMap' was not declared in this scope
    SGDataMap *const_data_map;
    ^
    ssd_6b_runner.cc:70:14: error: 'const_data_map' was not declared in this scope
    SGDataMap *const_data_map;
    ^
    ssd_6b_runner.cc:72:67: error: 'LoadFromDataDir' was not declared in this scope
    const_data_map = LoadFromDataDir(FLAGS_coeff_path_real.c_str());
    ^
    ssd_6b_runner.cc:74:66: error: 'LoadFromDataDir' was not declared in this scope
    const_data_map = LoadFromDataDir(FLAGS_coeff_path_sim.c_str());
    ^
    ssd_6b_runner.cc:81:67: error: 'LoadFromImageFile' was not declared in this scope
    auto input_data_map = LoadFromImageFile(FLAGS_input_path.c_str());
    ^
    ssd_6b_runner.cc:99:22: error: expected type-specifier before 'SGBuilder'
    auto builder = new SGBuilder(sg_def);
    ^
    ssd_6b_runner.cc:103:21: error: expected type-specifier before 'SGRunner'
    auto runner = new SGRunner(FLAGS_sim_only);
    ^
    ssd_6b_runner.cc:128:19: error: unable to deduce 'auto&&' from 'output_keys'
    for (auto key : output_keys) LOG(INFO) << key << std::endl;
    ^
    ssd_6b_runner.cc:134:21: error: unable to deduce 'auto' from 'data_pair'
    auto data_val = data_pair;
    ^
    ssd_6b_runner.cc:139:70: error: 'IdentifyOutputNodeType' cannot be used as a function
    auto output_node_type = IdentifyOutputNodeType(data_key, data_val);
    ^
    ssd_6b_runner.cc:154:23: error: unable to deduce 'auto&&' from 'shape'
    for (auto dim : shape) num_elems *= dim;
    ^
    ssd_6b_runner.cc:182:10: error: type '<type error>' argument given to 'delete', expected pointer
    delete output_data_map;
    ^
    ssd_6b_runner.cc:193:10: error: type '<type error>' argument given to 'delete', expected pointer
    delete runner;
    ^
    Makefile:9: recipe for target 'ssd_6b_runner.o' failed
    make: *** [ssd_6b_runner.o] Error 1

    同时,我参照这个教程,想写一个能够运行的关于MNIST例子的c++文件,但是参照这个教程,每一次都报错没有sg的定义,不知道是不是在引用库文件上有遗漏。

    希望能够稍微详细解释一下引用库文件及makefile的编写。