为 OpenVINO 新增 Paddle 算子转换支持
介绍
在这篇教程中,你将会一步一步学习如何为 OpenVINO 新增 Paddle 算子转换支持。PaddlePaddle 是最受欢迎的国产深度学习框架之一,虽然 OpenVINO 已经支持了 Paddle 离线模型的载入以及转换为 IR 格式,但是随着框架的迭代更新,会不断的构建新的算子,甚至修改部分算子的 API 接口,所以需要开发者添加新的算子映射支持,从而使 Paddle 以及 OpenVINO 更加易用。
第一步:Fork
首先至 OpenVino 官方仓库,然后点击 fork 按钮,生成自己目录下的仓库,比如 https://github.com/USERNAME/openvino
第二步:克隆
将远程仓库 clone 到本地:
$ git clone https://https://github.com/USERNAME/openvino
$ cd openvino
第三步:创建本地分支
不建议直接在 master 分支上直接进行开发,一般从 master 分支上创建新分支。
$ git checkout -b BRANCHNAME
BRANCHNAME 建议起一个有意义的分支名字
第四步:开始正式开发
在正式开发前,我们先尝试从源码编译 OpenVINO
4.1 尝试从源码编译
4.1.1 更新子模块仓库源码
$ git submodule update --init --recursive
如果位于中国地区拉取仓库比较慢,可以尝试使用一下脚本进行拉取
$ cd openvino
$ chmod +x scripts/submodule_update_with_gitee.sh
$ ./scripts/submodule_update_with_gitee.sh
4.1.2 下载、build 依赖
$ chmod +x install_build_dependencies.sh
$ ./install_build_dependencies.sh
4.1.3 构建项目
export OPENVINO_BASEDIR=`pwd`
$ mkdir build
$ cd build
$ cmake \
-DCMAKE_BUILD_TYPE= Release -DCMAKE_INSTALL_PREFIX="${OPENVINO_BASEDIR}/openvino_dist" \
-DPYTHON_EXECUTABLE=$(which python3) \
-DENABLE_MYRIAD=OFF \
-DENABLE_VPU=OFF \
-DENABLE_PYTHON=ON \
-DNGRAPH_PYTHON_BUILD_ENABLE=ON \
-DENABLE_DEBUG_CAPS=ON \
-DENABLE_CPU_DEBUG_CAPS=ON \
-DENABLE_TESTS=ON \
..
关于编译的选择项可以查看 此处。
4.1.4 编译、安装
$ make -j$(nproc); make install
上述便完成了 openvino 的编译安装过程。
若完成了以上步骤,便可以继续下面的正式开发了。
注意:每次修改代码后需要再次编译安装
4.2 新增算子转换(以 gather_nd 算子为例)
新增一个算子的转换我们需要在下列文件中添加对应文件或代码
-
在 src/frontends/paddle/src/op/frontends/paddle/src/op 添加算子映射的实现
-
在 src/frontends/paddle/src/op_table.cpp 中注册该算子映射
-
在 src/core/tests/frontend/paddle/test_models/gen_scripts 添加该算子的单测实例生成脚本
本教程也会以上面顺序进行介绍
4.2.1 算子映射的实现
首先在 Paddle Docs 查看该算子的用法,再根据算子旁边的 源代码 链接跳转至该算子的 Python 前端代码,如下图:
下面是截取的部分代码,并简要说明其作用
helper = LayerHelper('gather_nd', **locals())
dtype = helper.input_dtype()
output = helper.create_variable_for_type_inference(dtype)
helper.append_op(
type="gather_nd",
inputs={"X": x, "Index": index},
outputs={"Out": output},
)
return output
LayerHelper
是一个用于创建 OP 输出变量、向静态图 program
中添加 OP 的辅助工具类。在这里我们实例了一个 gather_nd 算子,并将输入 Tensor,输出 Tensor, 以两个字典的形式,作为参数添加 OP
所以通过这部分代码我们可以知道在 Paddle 这边 gather_nd 通过输入 inputs 字典,字典中有 “X” ,“Index” 字段。
而其他的算子的参数并非也是 Tensor 输入的,而是作为 attributes 字典输入的,例如 shard_index 则是以 attr 字典输入的
同时查阅 openvino 的 算子表 可以知道 openvino 有 GatherND 算子,于是我们可以尝试写出如下转换代码
...
NamedOutputs gather_nd(const NodeContext& node) {
const auto data_node = node.get_input("X");
const auto index_node = node.get_input("Index");
return node.default_single_output_mapping({std::make_shared<default_opset::GatherND>(data_node, index_node)},
{"Out"});
...
关于 NodeContext 的说明可以查看 openvino官方文档关于前端拓展的部分
4.2.2 注册算子
在 src/frontends/paddle/src/op_table.cpp 文件中注册该算子。
...
OP_CONVERTER(gather_nd);
...
std::map<std::string, CreatorFunction> get_supported_ops() {
return {
{"gather_nd", op::gather_nd},
};
加入自己新增的算子即可
4.2.3 新增单测
为了测试自己新加入的单测是否完成转换或者精度要求,需要添加单元测试
具体在 src/core/tests/frontend/paddle/test_models/gen_scripts 文件夹中新建一个单测文件
例如:generate_gather_nd.py
在编写单元测试过程中请尽量全面,如输入类型的单测,输入与输入之间关系的单测(如果存在的话),输入值为特殊值的情况 (空数组,负值,零值) 等
4.2.4 注册单测实例
在完成单测的编写后,需要在 op_fuzzy.cpp 中注册单测 src/core/tests/frontend/paddle/op_fuzzy.cpp
例如:
std::string("gather_nd_float32"),
std::string("gather_nd_int64"),
std::string("gather_nd_int32"),
std::string("gather_nd_empty"),
std::string("gather_nd_low_index"),
std::string("gather_nd_high_rank1"),
std::string("gather_nd_high_rank2"),