作者:卢畅,英特尔 openvino 工具套件领航者联盟成员,ppde
1. 前言
我是飞桨黑客马拉松第五期 openvino 赛题获奖者——为 openvino 添加了对 paddle 2.5 的支持。在此记录下来贡献的过程,希望有更多的同学可以参与到 openvino 的社区建设当中来。我在贡献代码的过程中,也遇到了一些问题,在此,非常感谢英特尔的技术老师们非常耐心地指导我,帮助我解决了问题!
那么,接下来就让我们正式进入正题!
2. 介绍
2.1openvino 是什么?
openvino 是英特尔推出的一款深度学习推理框架,它可以将训练好的模型转换为 openvino 支持的 ir 格式,从而可以在 openvino 的推理引擎上进行推理。
openvino 支持多种深度学习框架,包括 paddle、tensorflow、pytorch 等。
2.2任务说明
在这个任务完成之前,openvino 只支持 paddle 2.4 的版本,由于 paddle 2.5 的一些接口变动,openvino 无法直接支持 paddle 2.5。同时,由于 paddle 2.4 版本并不支持 python 3.11,因此 openvino 默认关闭了对 paddle 的支持,需要手动开启,在手动开启后,又会遇到无法编译出 paddle 相关单侧的问题。
本任务的目标是为 openvino 添加对 paddle 2.5 的支持,并确保 openvino 可以正常编译出 paddle 相关单侧且线上 ci 均可通过。
3. 开发过程
3.1问题分析
在任务开始之前,openvino 开启对 paddle 的支持后主要会遇到两个问题:
1api名称变动导致的编译报错,如:
paddle.fluid.layers.elementwise_add -> paddle.add
2api名称变动导致的编译报错,如:paddle.fluid.layers.elementwise_add -> paddle.add
针对上面这两个问题,主要的解决方案如下:
1将老 api 与新 api 名称映射
2修改名称/属性变动的 api
3修复因 op 行为变动导致的单侧报错
3.2将老 api 与新 api 名称映射
由于 paddle 2.5 版本在 api 层面发生了较大的变化,因此需要将老 api 与新 api 名称进行映射,这样 openvino 中的代码就可以使用新 api 名称,从而解决 api 名称变动导致的编译报错问题。该问题可参考 paddle 官网的 api 映射表。
链接:
https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/model_convert/convert_from_older_versions/paddle_api_mapping_cn.html#paddle-1-8-paddle-2-0-api
为了兼容老版本的 api,openvino 中的代码需要同时支持新 api 与老 api,因此需要在 generate_xxx.py 中进行相应修改。
with paddle.static.program_guard(paddle.static.program(), paddle.static.program()): node_x = paddle.static.data(name='x', shape=x.shape, dtype=x.dtype) node_i = paddle.full(shape=[1], fill_value=0, dtype='int64', name='i') if paddle.__version__ >= '2.0.0': node_i = paddle.add(node_i, node_x) else: paddle.fluid.layers.nn.elementwise_add(node_i, node_x) node_ten = paddle.full(shape=[1], fill_value=10, dtype='int64', name='ten')
左滑查看更多
代码中的paddle.fluid.layers.nn.elementwise_add 就是老版本的 api,而 paddle.add 就是新版本的 api。
3.3修改名称/属性变动的 api
对于部分 api 接口,老版本与新版本的名称或属性发生了变化,因此需要给 openvino 中的代码进行相应的修改。比如 paddle.fluid.layers.relu6(x, threshold=6.0, name=none) 和 paddle.nn.functional.relu6(x, name=none) 的属性发生了变化。
可以看到,paddle.fluid.dygraph.relu6 中的 threshold 属性在新版本中被删除了。
这种情况下需要确认 python 源码中是否修改底层 c++ 源码,如果是修改了 c++ 源码,那么需要在 openvino 的op源码中进行相应的修改。如果没有修改 c++ 源码,那么只需要对应修改 python 源码即可。
一般情况下,底层 c++ 源码不会修改,python 层一般是修改属性的名称,修改属性的默认值,删除某个属性等。
比如新版本 relu6 在 paddle 的 python 端的实现如下:
def relu6(x, name=none): threshold = 6.0 if in_dynamic_or_pir_mode(): return _c_ops.relu6(x) check_variable_and_dtype( x, 'x', ['float16', 'uint16', 'float32', 'float64'], 'relu6' ) helper = layerhelper('relu6', **locals()) out = helper.create_variable_for_type_inference(x.dtype) helper.append_op( type='relu6', inputs={'x': x}, outputs={'out': out}, attrs={'threshold': threshold}, ) return out
左滑查看更多
通过实现代码可以看到,新版本的 relu6 在 python 端并没有修改 c++ 源码,只是删除了 threshold 属性,在调用 c++ 源码时,将 threshold 属性设置为了默认值 6.0。
因此,对于这种情况,只需要修改 openvino 中的 python 单侧代码即可,不需要修改 c++ 源码。openvino 在进行模型转化的时候是对底层 op 进行转化,因此只要 paddle 没有修改底层 op 的行为,那么 openvino 就不需要修改 op 相关的代码。
3.4修复因 op 行为变动导致的单侧报错
在 paddle 2.5 版本中,部分 op 的行为发生了变化,导致 openvino 中的单侧报错。比如 paddle.argmax 新增了 0-d tensor 的支持,但是 openvino 中的 op 并没有对应的修改。想要修复这种问题,需要结合单侧报错的具体情况进行相应的修改。
在介绍如何修复单侧报错之前,先介绍一下 openvino 的算子支持机制。
3.4.1 openvino 算子支持机制
接下来我们先看一下 openvino 中的算子支持机制。
通过 paddle 官方提供的 topk_v2 样例进行说明:
// copyright (c) 2018-2021 intel corporation// spdx-license-identifier: apache-2.0#include default_opset.hpp#include openvino/frontend/paddle/node_context.hppnamespace ov {namespace frontend {namespace paddle {namespace op {namedoutputs top_k_v2(const nodecontext& node) { auto x = node.get_input(x); output k_expected_node; if (node.has_input(k)) { auto k_variable = node.get_input(k); auto k_var_node = std::make_shared(k_variable, element::i32); k_expected_node = std::make_shared(k_var_node); } else { const auto k_expected = node.get_attribute(k, 1); k_expected_node = default_opset::i32, {}, {k_expected}); } auto axis = node.get_attribute(axis, -1); bool sorted = node.get_attribute(sorted, true); bool largest = node.get_attribute(largest, true); std::string sort_type = sorted ? value : none; std::string mode = largest ? max : min; auto node_topk = std::make_shared(x, k_expected_node, axis, mode, sort_type); namedoutputs named_outputs; named_outputs[out] = outputvector{node_topk->output(0)}; named_outputs[indices] = outputvector{node_topk->output(1)}; return named_outputs;}} // namespace op} // namespace paddle} // namespace frontend} // namespace ov
左滑查看更多
在 openvino 中,一般来说每个算子都是一个单独的文件,比如 topk_v2 算子对应的文件就是 topk_v2.cpp。在这个文件中,我们可以看到 top_k_v2 函数,这个函数就是 openvino 中的 topk_v2 算子的实现。
在这个函数中,我们可以看到 auto x = node.get_input(x);,这个函数就是获取输入的 tensor,auto node_topk = std::make_shared(x, k_expected_node, axis, mode, sort_type); 这个函数就是创建 topk_v2 算子,named_outputs[out] = outputvector{node_topk->output(0)}; 这个函数就是获取输出的 tensor。
每个op 都可以映射为一个图结构,数据根据图结构在不同的计算节点之间流通和计算,而node便定义了图结构中的数据节点,通过实现每一个node,便可以通过组合实现更多的算子。
op 转换的代码需要写在 src/frontends/paddle/src/op/ 目录下,并在 src/frontends/paddle/src/op_table.cpp 中进行注册。
单测代码需要写在 src/core/tests/frontend/paddle/test_models/gen_scripts 目录中,并在 src/core/tests/frontend/paddle/op_fuzzy.cpp 中进行注册。
3.4.2 修复因 op 行为变动导致的单侧报错
下面以 paddle.argmax 为例,介绍如何修复因 op 行为变动导致的单侧报错。
修复此类问题一般只能见招拆招,需要结合单侧报错的具体情况进行相应的修改。比如 paddle.argmax 新增了 0-d tensor 的支持,但是 openvino 中的 op 并没有对应的修改。因此,我们需要在 openvino 中的 op 中添加对 0-d tensor 的支持。经过对代码的分析我们可以发现,openvino 中该 op 是通过 std::make_shared(node_reshape, k, axis, max, index, index_element_type); 实现的,但是 topk 并没有对 0-d tensor 进行支持。我们可以判断 output_size 是否为 0,如果为 0,那么就组合一个 slice 节点返回即可。以下是修改后的代码:
namedoutputs argmax(const nodecontext& node) { auto data = node.get_input(x); bool flatten = node.get_attribute(flatten); const element::type& index_element_type = element::i64; const output k = ov::i64, {}, {1}); if (!flatten) { auto axis = node.get_attribute(axis); const auto axis_to_remove = ov::u64, shape{}, {axis}); auto node_topk = std::make_shared(data, k, axis, max, index, index_element_type); const auto reshaped_indices = std::make_shared(node_topk->output(1), axis_to_remove); return node.default_single_output_mapping( {std::make_shared(reshaped_indices, element::i64)}, {out}); } else { int64_t axis = 0; const output reshape_flatten = ov::i64, {1}, {-1}); auto node_reshape = std::make_shared(data, reshape_flatten, true); auto node_topk = std::make_shared(node_reshape, k, axis, max, index, index_element_type); const auto output_info = node.get_output_port_infos(out); // 获取输出的维度 size_t output_size = output_info[0].second.size(); // 如果输出的维度为0,那么就组合一个slice节点返回 if (output_size == 0) { auto out = std::make_shared(node_topk->output(1)); return node.default_single_output_mapping({std::make_shared(out, element::i64)}, {out}); } else { return node.default_single_output_mapping( {std::make_shared(node_topk->output(1), element::i64)}, {out}); } }}
左滑查看更多
除了 argmax 之外,还有一些 op 也需要进行相应的修改:
• p_norm
• reduce_ops
• matmul_v2
• elementwise_floordiv
具体的修改可以参考 pr
4. 总结
这次的黑客松活动,让我对 openvino 有了更深入的了解。
openvino 的工程师们非常热心,对于社区的问题都会非常耐心的解答。我也是第一次在 pr 页面有 144 次的 conversation。
整个 pr 的周期大概是 3 个月,期间经历了很多次的修改,最终才能够被合并。在这次的活动中,我也学到了很多知识,比如 openvino 的算子支持机制,op 的单侧测试等。
希望有更多的同学可以参与到 openvino 的社区建设当中来,为 openvino 的发展及开源社区的建设贡献自己的力量!
宏的缺陷与内联函数的引入
小小镜片背后有着不一样的大千世界,小米VR眼镜PLAY2将带你体验!
腾讯任天堂Switch将开启周年庆典
智慧零售驱动家电消费,618已经锁定核心关键词
vivoNEX2真机曝光 背部发光月环设计很神奇
为OpenVINO添加对Paddle 2.5的支持
sp9010输入电压过低怎么办
小米生态链企业华米科技在今天发布了2018年第二季度及半年业绩成绩单
常用几种继电器检测与选用
锂电UPS揭开了5G时代数据中心的电能解决方案
安防行业划开人工智能时代帷幕,生态与场景成为关键
健身房中的智能显示屏带你体验什么是智能健身
关于Simulink的建模自动化的分析和说明以及应用
如何帮助零售行业的公司应用AI和机器学习来提高其盈利能力和可持续性
Shell命令编程实践指南
PLC触摸屏的作用_触摸屏是怎样控制PLC的
手机与大屏电视哪个才是MiniLED的核心赛道
9种Linux下常用的9种代码比对工具
ABAT100蓄电池在各行业中的应用
白光LED照明电路的应用实例