让Paddle Lite遇上Node.js

最近想找到自己未来的方向,也在一直在关注我们手机和电脑上面的程序是用什么语言或者平台来开发,不同的平台的体验又怎么样?在桌面端,我很惊讶地发现Electron技术正在崛起,可能我们正在使用的代码编辑器、聊天软件、游戏都是基于Electron进行开发的。

在一般情况下,我们开发一个客户端,做一套漂亮的UI出来可能比较困难。而对于习惯于使用Node.js做后端开发的朋友来说,上手桌面客户端开发可能成本也比较高。那么Electron技术就是一个非常棒的解决方案,这款开源的技术允许开发者使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序,客户端UI完全和网页一样,非常方便,还能够在多平台上面运行。

那么在桌面客户端上面,我们能否让开发者做到本地部署Paddle Lite进行推理呢?答案是肯定的。因为Paddle Lite对于C++提供了支持,并且在v2.6.0版本中,能够在Windows中编译。这给了我们将Paddle Lite封装成Node.js的C++插件提供了可能。如果能够成功移植,那我们开发桌面应用的时候,可以允许在客户端上完成图片分类等任务,相信Paddle Lite的轻量化模型运行在常规PC上,能够有不错的表现。对于其他Node.js场景来说,比如网站后端,我们也可以直接使用Paddle Lite进行推理。

于是我做了一个Demo出来,其本质上是将Paddle Lite的C++ API封装为PaddleLite类,这个类目前提供了两个方法,分别是set_model_fileinfer_float。在此之上,我使用N-API来编写Node.js插件,将其组合起来,允许Node.js调用Paddle Lite的C++ API。

效果

你可以直接在Paddle Node的Release界面下载编译好的文件,其本身由三个文件组成:

  1. paddlenode.node - 编译后的Node.js模块
  2. libiomp5md.dll - OpenMP DLL
  3. mklml.dll - MKL数学核心库

我们从官方开放的模型库中下载到mobilenet_v1模型,并使用OPT进行转换,这里我们根据文档选择使用paddlelitepython库进行转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 引用Paddlelite预测库
from paddlelite.lite import *

# 1. 创建opt实例
opt=Opt()
# 2. 指定输入模型地址
opt.set_model_dir("./mobilenet_v1")
# 3. 指定转化类型: arm、x86、opencl、xpu、npu
opt.set_valid_places("x86")
# 4. 指定模型转化类型: naive_buffer、protobuf
opt.set_model_type("naive_buffer")
# 4. 输出模型地址
opt.set_optimize_out("mobilenetv1_opt")
# 5. 执行模型优化
opt.run()

得到了mobilenetv1_opt.nb模型文件后,我们在Node.js中进行推理:

1
2
3
4
5
var addon = require('./paddlenode')
var arr = new Array(150528)
for(var i=0; i<arr.length; i++) arr[i]=1;
addon.set_model_file("./mobilenetv1_opt.nb")
addon.infer_float(arr,[1, 3, 224, 224])

这里我们输入全1的数组进行模拟,set_model_file方法直接对应Paddle Lite中的set_model_from_file,infer_float的第一个参数是我们要传入的数据,第二个是传入数据的尺寸。如果尺寸各个元素乘积大小和传入数据的大小不同,将会抛出一个错误。

之后我们会得到一个1001维的数组:

image-20200705234457009

其中0号元素为结果向量的大小,方便进行遍历,其他元素即为模型本身的输出。

手动编译

如果你决定手动编译,首先我们需要从Paddle Lite的Release中找到x86的预编译结果,目前最新版本是v2.6.1。下载下来之后定位到binding.gyp,将lite_dir变量设定为预编译库文件夹的绝对路径,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
'variables': {
'lite_dir%': 'C:/Users/Li/Desktop/Exp/inference_lite_lib.win.x86.MSVC.C++_static.py37.full_publish',
},
"targets": [
{
'target_name': "paddlenode",
'sources': ["paddlelib.h","paddlelib.cc","paddlenode.cc"],
'defines': [
],
'include_dirs': [
"<(lite_dir)/cxx/include",
"<(lite_dir)/third_party/mklml/include"
],
'libraries': [
"-l<(lite_dir)/cxx/lib/libpaddle_api_light_bundled.lib",
"-l<(lite_dir)/third_party/mklml/lib/libiomp5md.lib",
"-l<(lite_dir)/third_party/mklml/lib/mklml.lib",
"-lshlwapi.lib"
]
}
]
}

之后定位到我们的源码所在目录,确保你已经安装好了node-gyp和windows-build-tools,运行:

1
node-gyp configure build

即可生成最终结果,但是记得从预编译库中复制两个dll动态链接库到编译结果目录。因为官方发布的为Release版lib文件,这里如果使用debug版会导致不匹配的错误。

原理

实际上这个项目在Paddle Lite的C++ Demo上套了一层壳,我们最需要关注的是怎么将N-API和C的对象互相转换,在Node.js的官方文档中给出了非常多的函数和解释,在此基础上做转换即可。

这里给出一些函数的解释:

  • napi_define_properties - 定义资源
  • napi_get_cb_info - 获取调用的信息
  • napi_throw_error - 抛出错误
  • napi_typeo - 获取napi_value的类型
  • napi_get_value_string_utf8 - 将napi_value转换为utf8字符串
  • napi_get_array_length - 获取napi_value对应的数组长度
  • napi_get_value_double - 获取napi_value对应的双精度数组元素
  • napi_get_value_int32 - 将napi_value转换为32位整型
  • napi_get_value_double - 将napi_value转换为双精度浮点数
  • napi_create_double - 将双精度浮点数转换为napi_value

还有一些函数大体作用相同,仅仅作为转换作用。

写在最后

飞桨的生态发展得非常棒,在发文前已经推出了Paddle.js,使得在用户浏览器中进行推理成为可能。而Paddle Node项目从另一个角度为Node.js提供可能。希望飞桨能够做得越来越好,进一步降低用户使用门槛,飞桨的中文生态给国内开发者和入门者给到更多便利。非常感谢。

参考链接

  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载需要标明作者,并以超链接的方式指向原文。

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2020 Kevin Li
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~