为 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"),
4.2.5 重新编译以及运行单测
$ make -j$(nproc); make install
$ cd bin/intel64/Release
$ ./paddle_tests --gtest_filter=PaddleFuzzyOpTest/FrontEndFuzzyOpTest.testOpFuzzy/*
此处的 * 是通配符,若只想测试添加的算子可以例如:gather_nd 的写法进行测试
在测试完毕后有一个单测发生了报错,根据提示说明 openvino 是不支持 rank=0 的数组输入的,所以我们需要对用户的输入数据进行检查。
4.2.6 映射实现的优化
根据单侧我们需要不断迭代自己的算子映射的实现,就本例子为例,我们可以对于输入张量进行检查:
...
auto shape = index_node.get_partial_shape();
if (shape.is_static() && shape.rank().get_length() == 0)
PADDLE_OP_CHECK(node, false, "zero 'indices' input rank is not allowed for gather_nd");
...
这里我们对输入的 index 进行形状检查,并进行报错提示。PADDLE_OP_CHECK 是一个宏定义,当第二个参数是 false 时,将第三个输入作为报错信息。
4.2.7 PR 的提交
在完成上述过程后我们便可以尝试提交一个 PR 啦。
在提交之前先将代码提交并同步至远端
$ git status
$ git add changfile
$ git commit -m "add a paddle op gather_op"
commit 信息尽量有意义且能说明每次的修改内容
推送至远端
$ git fetch upstream
$ git pull upstream BRANCHNAME
开启一个 PR 并填写 title ,为了方便 review,请附上自己单侧的通过情况的截图以及 Paddle 算子对应算子的实现
至此便完成了 openvino gather_nd 算子对于 Paddle 的转换支持 🎉
总结
本文章简单介绍了如何向 OpenVINO 新增一个 Paddle 算子转换的流程,学习到了如何在 OpenVINO 中添加算子转换实现以及添加对应的单测,本任务难度适中,建议尝试。但是在实现映射的过程中,需要注意在不同尺度,不同组合方式的输入精度对齐的问题,可以查看 OpenVINO 算子实现,以及 Paddle算子 的实现。