Skip to main content

为 OpenVINO 新增 Paddle 算子转换支持

· 9 min read
PuQing
AI, CVer, Pythoner, Half-stack Developer

介绍

在这篇教程中,你将会一步一步学习如何为 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
info

BRANCHNAME 建议起一个有意义的分支名字

第四步:开始正式开发

在正式开发前,我们先尝试从源码编译 OpenVINO

4.1 尝试从源码编译

4.1.1 更新子模块仓库源码

$ git submodule update --init --recursive
info

如果位于中国地区拉取仓库比较慢,可以尝试使用一下脚本进行拉取

$ 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 的编译安装过程。

若完成了以上步骤,便可以继续下面的正式开发了。

info

注意:每次修改代码后需要再次编译安装

4.2 新增算子转换(以 gather_nd 算子为例)

新增一个算子的转换我们需要在下列文件中添加对应文件或代码

本教程也会以上面顺序进行介绍

4.2.1 算子映射的实现

首先在 Paddle Docs 查看该算子的用法,再根据算子旁边的 源代码 链接跳转至该算子的 Python 前端代码,如下图:

image.png

下面是截取的部分代码,并简要说明其作用

    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” 字段。

info

而其他的算子的参数并非也是 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/*
info

此处的 * 是通配符,若只想测试添加的算子可以例如:gather_nd 的写法进行测试

image.png

在测试完毕后有一个单测发生了报错,根据提示说明 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"
info

commit 信息尽量有意义且能说明每次的修改内容

推送至远端

$ git fetch upstream
$ git pull upstream BRANCHNAME

开启一个 PR 并填写 title ,为了方便 review,请附上自己单侧的通过情况的截图以及 Paddle 算子对应算子的实现

image.png

至此便完成了 openvino gather_nd 算子对于 Paddle 的转换支持 🎉

总结

本文章简单介绍了如何向 OpenVINO 新增一个 Paddle 算子转换的流程,学习到了如何在 OpenVINO 中添加算子转换实现以及添加对应的单测,本任务难度适中,建议尝试。但是在实现映射的过程中,需要注意在不同尺度,不同组合方式的输入精度对齐的问题,可以查看 OpenVINO 算子实现,以及 Paddle算子 的实现。