乐趣区

关于人工智能:速度与技能的较量飞桨黑客松-OpenVINO™-任务获奖者经验分享

黑客松流动介绍

飞桨黑客马拉松是一项兼具编程乐趣和挑战一项流动。通过该流动,咱们可能接触并参加到企业建设的大型开源我的项目,晋升集体编程能力,加强开源社区互动,推动开源生态倒退。第四期飞桨黑客马拉松由 深度学习技术及利用国家工程钻研核心主办,飞桨承办,英特尔作为顶级资助方,为咱们带来了 OpenVINO™ 算子映射和减少 Notebook Demo 等工作。算子映射可能将 PaddlePaddle 模型中的算子映射到 OpenVINO™ 中,实现模型在两个框架之间的转换,最终达到在 PaddlePaddle 训练模型、OpenVINO™ 部署推理的成果,在两个框架之间搭起模型转换的桥梁。

OpenVINO™ 介绍

OpenVINO™ 工具套件是一款由英特尔公司开发的反对疾速开发视觉、语音辨认和自然语言解决利用的工具包。它采纳了最新的人工智能神经网络模型,包含 卷积神经网络、循环神经网络和注意力机制网络等,以实现高效的计算机视觉和深度学习利用。
OpenVINO™ 的次要性能包含:在边缘侧反对卷积神经网络的推理减速;在英特尔 CPU、GPU、FPGA 等设施上的混合执行 / 异构计算执行;通过大量的预训练模型库减速从产品原型到市场化的过程;反对传统的计算机视觉规范库中的操作,如 OpenCV 和 OpenCL 等。

总之,OpenVINO™ 是一个弱小的工具包,能够帮忙开发者疾速构建和优化计算机视觉和深度学习利用,进步利用性能和商业价值。

(阐明:Generated by 文心一言,Prompt:介绍一下 OpenVINO™ 工具套件)

环境配置

环境搭建

首先 Fork 一份 OpenVINO™ 的 GitHub 仓库,并将其克隆到本地(Fork 后 OpenVINO™toolkit 该当替换为本人的用户名,新建分支等 git 操作在此不再赘述):

git clone https://github.com/OpenVINOtoolkit/OpenVINO.git

OpenVINO™ 为咱们提供了一份编译构建的文档,详情见链接:https://github.com/openvinotoolkit/openvino/blob/master/docs/dev/build.md

咱们能够依据本身开发环境的不同查看对应平台的构建文档。我比拟举荐应用 Docker 创立一个开发容器,这里我抉择应用 PaddlePaddle 提供的开发镜像,外面曾经内置了罕用的编译工具链,开箱即用十分不便。

docker run \
  -it \
  --gpus=all \
  --name=fisher_OpenVINO \
  --net=host \
  -v `pwd`:`pwd` \
  paddlepaddle/paddle:latest-dev-cuda11.7-cudnn8.4-trt8.4-gcc8.2 \
  /bin/bash

局部参数解析:–gpus=all:将物理机上的 GPU 挂载到容器中,如本机无 GPU 可不挂载 –name=fisher_OpenVINO:给本人创立的容器起一个好记的名字 –net=host:间接应用主机的网络,不便在容器中应用主机的代理 -v pwd:pwd:将主机以后的门路间接挂载到容器对应门路中,不便 clangd 插件对 compile_commands.json 文件进行剖析,提供语法查看、代码跳转等性能

编译

依据构建文档中的步骤,咱们初始化 git 子模块、装置构建依赖工具包、配置编译选项,以下是我的编译选项,可依据集体须要进行批改,其中要害的参数是 -DENABLE_TESTS=ON,该参数将关上测试模块的编译,以便后续开发实现后进行本地测试。

cmake .. \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="{project_base_dir}/build/OpenVINO_dist" \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
  -DENABLE_CLANG_FORMAT=ON \
  -DENABLE_MYRIAD=OFF \
  -DENABLE_VPU=OFF \
  -DENABLE_PYTHON=ON \
  -DNGRAPH_PYTHON_BUILD_ENABLE=ON \
  -DENABLE_DEBUG_CAPS=ON \
  -DENABLE_TESTS=ON \
  -DENABLE_INTEL_GPU=OFF \
  -DENABLE_WHEEL=ON

编译实现后,在 bin/intel64/Release/ 中可能找到 PaddlePaddle 相干的测试程序:

增加映射

接口对齐

确认编译没有问题后,接下来便是真正的算子映射开发了。咱们首先须要查阅文档,将 PaddlePaddle 算子和 OpenVINO™ 算子的性能、输出、属性等对齐,以 silu 为例,咱们在 PaddlePaddle 文档中很容易找到其相干文档:接下来便是在 OpenVINO™ 的文档中找到 silu 相干的文档,通过搜寻发现没有任何后果。此时有两种可能:

  • 在 OpenVINO™ 中,有性能完全相同,但命名不同的算子。
  • OpenVINO™ 中没有该算子的实现,咱们须要应用现有的算子进行组合,组合后的算子可能实现雷同的性能。

于是咱们能够沿着这两种可能去寻找解决办法:办法一 :在激活函数中寻找名字不同,性能雷同或类似的算子。通过一番查找,我在 PaddlePaddle 的文档中找到了一个计算公式和 silu 一样的激活函数 swish,swish 在 OpenVINO™ 文档中的定义如下图所示,显然当 β=1 时,就能齐全对应上了,问题解决。 办法二:通过现有的组合算子实现。仔细观察 silu 的计算公式,能够发现 silu(x) = x * sigmoid(x),咱们也能够应用 multiply、sigmoid 两个算子组合实现雷同的性能。因为曾经存在能够间接映射的算子,咱们就没必要将应用该办法了,在此仅提供一个实现思路,如遇到无奈间接进行映射的算子,该思路能够作为一个参考。

映射实现

新建映射文件 src/frontends/paddle/src/op/silu.cpp,将算子映射实现搁置在以下命名空间域中:

namespace ov {
namespace frontend {
namespace paddle {
namespace op {// 算子映射的具体实现}  // namespace op
}  // namespace paddle
}  // namespace frontend
}  // namespace ov

实现算子映射的具体逻辑:

NamedOutputs silu(const NodeContext& node) {const auto x = node.get_input("X");
    return node.default_single_output_mapping({std::make_shared<default_opset::Swish>(x)},
      {"Out"}
    );
}

在以上代码中,咱们从计算节点中获取输出变量 X,将其作为输出调用 Swish,将输入写回到计算节点的 Out 变量中,实现了 silu 算子的映射。在其余的算子中,输出的变量和属性可能不止一个,咱们能够通过查看  PaddlePaddle 源码中的 OpMaker 或 paddle/fluid/operators/compat/op.pbtxt 获取变量名和属性名,以下是 Paddle Silu 算子的变量名和属性名:

注册映射

在 src/frontends/paddle/src/op_table.cpp 中注册该算子映射,该文件中蕴含了 PaddlePaddle 到 OpenVINO™ 的所有映射,咱们如法炮制即可。

OP_CONVERTER(silu);

{"silu", op::silu},

单测用例结构

创立文件 src/frontends/paddle/tests/test_models/gen_scripts/generate_silu.py,用于生成 silu 相干的测试用例,在编写测试用例时,咱们须要从以下几个维度去思考单测用例,尽量做到全笼罩:Shape(动态、动静、1D~4D-Tensor)、数据类型(int32、float32 等)、算子属性等。

def silu(name: str, x, data_type, use_static=True):
    # 启用 Paddle 动态图模式
    pdpd.enable_static()
    with pdpd.static.program_guard(pdpd.static.Program(), pdpd.static.Program()):
        if use_static:
          # 动态 Shape 作为输出
            node_x = pdpd.static.data(name='input_x', shape=x.shape, dtype=data_type)
        else:
          # 动静 Shape 作为输出
            node_x = pdpd.static.data(name='input_x', shape=[1, 1, -1, -1], dtype=data_type)
        # Paddle silu
        out = pdpd.nn.functional.silu(x=node_x, name='silu')
        # 应用 CPU 计算
        cpu = pdpd.static.cpu_places(1)
        exe = pdpd.static.Executor(cpu[0])
        # startup program will call initializer to initialize the parameters.
        exe.run(pdpd.static.default_startup_program())
        # 执行器执行并保留模型
        outs = exe.run(feed={'input_x': x}, fetch_list=[out])
        saveModel(name, exe, feedkeys=['input_x'], fetchlist=[out], inputs=[x], outputs=[outs[0]], target_dir=sys.argv[1])
    return outs[0]


def main():
    x1 = np.random.randn(2,).astype('float32')
    silu("silu_static_test1", x1, 'float32', True)
    # More static shape test cases
    x5 = np.random.randn(1, 1, 32, 32).astype('float32')
    silu("silu_dynamic_test1", x5, 'float32', False)
    # More dynamic shape test cases


if __name__ == "__main__":
    main()

以上脚本将会为每一个测试用例导出并保留一个模型,OpenVINO™ 读取、转换、运行该模型,通过比对计算结果判断测试是否通过。

注册单测

在 src/frontends/paddle/tests/op_fuzzy.cpp 中注册单测,单测的名称就是在上一步中保留的模型名称。

std::string("silu_static_test1"),
std::string("silu_static_test2"),
std::string("silu_static_test3"),
std::string("silu_static_test4"),
std::string("silu_dynamic_test1"),
std::string("silu_dynamic_test2"),
std::string("silu_dynamic_test3"),
std::string("silu_dynamic_test4"),

编译并测试

从新编译并在 bin/intel64/Release/ 中运行以下测试,能够通过 – gtest_filter 参数筛选想要运行的单测,咱们只减少了 silu 算子的映射和单测,因而咱们只测试这一部分。

./paddle_tests --gtest_filter=*silu*

测试实现,没有问题之后,应用 pre-commit  格式化代码,依据 OpenVINO™ 奉献指南 的要求提 PR,期待相应的研发查看即可。https://github.com/openvinotoolkit/openvino/blob/master/CONTRIBUTING.md

总结

算子映射工作可能帮忙咱们理解算子的具体性能,了解模型在不同的 AI 框架中是如何进行转换的,对于老手来说是一份贵重的学习教训。以下是我在开发过程中总结进去的一点教训,心愿可能帮忙到大家:

  • 首次编译尽量应用代理,应用代理可能防止一部分网络起因造成的仓库克隆失败等问题
  • 多翻翻文档,许多算子的性能是雷同或类似的,多看文档也可能意识各种算子的性能,造成基本概念
  • 多学习他人的思路、写法,本人想出的组合算子实现可能比较复杂,参考学习他人的思路和写法可能给本人带来一些改良的灵感
退出移动版