golang调用paddle的infer c api

19 篇文章 0 订阅

百度的paddlepaddle有go版本的推理接口,通过cgo调用paddle c的推理库,在实际的api服务中,用go做推理服务器是有优势的。

开发环境
ubuntu 18.04
paddle 1.7.1
cuda 10.0
cudnn7
go 1.12
python 3.7

编译paddlepaddle gpu版本的c库

paddle默认提供的是c++版本的推理库,参考 https://www.paddlepaddle.org.cn/documentation/docs/zh/1.7/advanced_guide/inference_deployment/inference/build_and_install_lib_cn.html
但是golang如果要使用paddle的推理功能,就需要c版本的推理库,只能自行编译。相比之下,tensorflow会把c版本的库也编译好并且挂上官网,教程也比较完善,接口开放程度也更高。

环境准备

由于需要编译gpu版本的库,根据paddle的文档,ubuntu18.04暂时无法通过nvidia-docker编译,只能直接系统编译。需要提前安装好cuda 10.0、cudnn7和nccl

编译paddle

主要参考官方的文档,https://www.paddlepaddle.org.cn/documentation/docs/zh/1.7/install/compile/compile_Ubuntu.html

$ git clone https://github.com/paddlepaddle/paddle
# 建议使用git checkout切换到Paddle稳定的版本,如:
$ git checkout v1.7.1
$ mkdir build&&cd build
# 注意,需要编译预测功能模块 DON_INFER=ON, gpu也需要开启
$ cmake .. -DPY_VERSION=3.7 -DWITH_GPU=on -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DON_INFER=ON
# nproc命令可以获取到cpu的线程数
# 如果遇到open too many file的错误,需要手动设置一下 ulimit -S -n 4096, 我用4096基本可以编译完成
$ make -j$(nproc)

编译过程中,有一些资源是从百度对象存储的网站下载,有一些依赖库是从github下载,所以你需要有很好的网络,整个编译过程最糟心的就是网络。。

编译完成之后
先验证paddle的python版本的whl包,(我把cpu的版本也编译了,所以有两个)

 $ pwd 
/opt/cpp_work/Paddle/build
 $ tree python/dist 
python/dist
├── paddlepaddle-1.7.1-cp37-cp37m-linux_x86_64.whl
└── paddlepaddle_gpu-1.7.1-cp37-cp37m-linux_x86_64.whl

通过pip可以安装并进行验证。
同时,在build目录有fluid_inference_c_install_dir文件夹,这个就是golang调用paddle需要的输出,主主要是header文件个so文件

 $ pwd 
/opt/cpp_work/Paddle/build
 $ fluid_inference_c_install_dir/paddle
├── include
│   └── paddle_c_api.h
└── lib
    ├── libpaddle_fluid_c.a
    └── libpaddle_fluid_c.so

mnist 从训练到推理

其实paddle有go 推理api的使用文档,但是用的模型有点复杂了,对于刚接触推理api,可以选用一个简单的模型,这样容易校对输出。
希望可以通过这个例子,完整跑一下从训练到推理的过程。

python paddle训练

用的是mnist例子,mnist的paddle教程在
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.7/user_guides/simple_case/recognize_digits/README.cn.html
这个教程配套的代码位于
https://github.com/PaddlePaddle/book/tree/develop/02.recognize_digits

为了能复现结果,最初想建个github代码仓,发现文件太大上传不了,主要是paddle的运行库太大了,一百多M。所以整个文件夹上传百度云了。
链接: https://pan.baidu.com/s/1gNcZbRZe9UilmVjHeQ0a8Q 提取码: 9tai

.
├── demo
│   ├── mnist
│   │   ├── image
│   │   │   └── infer_3.png  # 测试图片
│   │   ├── input.json
│   │   ├── mnist_model_out # mnist的模型输出
│   │   │   ├── model
│   │   │   └── params
│   │   └── train.py
│   └── mnist.go # 我们要运行的例子
├── paddle # paddle的go infer api
│   ├── common.go
│   ├── config.go
│   ├── paddle_c   # paddle的c infer api和运行库
│   │   ├── paddle
│   │   │   ├── include
│   │   │   │   └── paddle_c_api.h
│   │   │   └── lib
│   │   │       ├── libpaddle_fluid_c.a
│   │   │       └── libpaddle_fluid_c.so
│   │   └── version.txt
│   ├── predictor.go
│   └── tensor.go
└── README.md

9 directories, 15 files

mnist的训练代码稍微做了一下修改,

def main(use_cuda, nn_type):
    model_filename = "model"
    params_filename = "params"
    save_dirname = "mnist_model_out"
 ...
    def load_image(file):
        im = Image.open(file).convert('L')
        im = im.resize((28, 28), Image.ANTIALIAS)
        im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32)
        im = im / 255.0 * 2.0 - 1.0
        with open("input.json", 'w') as f:  # 为了跟python版本的预测对照,把图片输出成了json格式的浮点数组
            dic = {}
            dic['index'] = im.tolist()
            # print("shape", dic['index'].shape)
            json.dump(dic, f)  
        return im

这里指定了模型和参数的输出,模型里面只有网络信息,参数就是网络对应的权重信息。
另外,我把一张测试图片输出为json格式的四维浮点数组,方便在go代码里面读取并且对照推理结果是否一致。

训练mnist的时候打开gpu,5个epoch,准确率约98.7%,日志输出如下:

$ python train.py --use_gpu true
W0404 23:04:23.936585 13501 device_context.cc:237] Please NOTE: device: 0, CUDA Capability: 61, Driver API Version: 10.0, Runtime API Version: 10.0
W0404 23:04:23.938866 13501 device_context.cc:245] device: 0, cuDNN Version: 7.5.
Pass 0, Epoch 0, Cost 4.069020
Pass 100, Epoch 0, Cost 0.141062
Pass 200, Epoch 0, Cost 0.121225
Pass 300, Epoch 0, Cost 0.109354
Pass 400, Epoch 0, Cost 0.195565
Pass 500, Epoch 0, Cost 0.090229
Pass 600, Epoch 0, Cost 0.025210
Pass 700, Epoch 0, Cost 0.021087
Pass 800, Epoch 0, Cost 0.087893
Pass 900, Epoch 0, Cost 0.031164
Test with Epoch 0, avg_cost: 0.07186726959814113, acc: 0.977109872611465
Pass 1000, Epoch 1, Cost 0.013190
Pass 1100, Epoch 1, Cost 0.041698
Pass 1200, Epoch 1, Cost 0.145324
Pass 1300, Epoch 1, Cost 0.047693
Pass 1400, Epoch 1, Cost 0.034643
Pass 1500, Epoch 1, Cost 0.065187
Pass 1600, Epoch 1, Cost 0.016131
Pass 1700, Epoch 1, Cost 0.020298
Pass 1800, Epoch 1, Cost 0.010305
Test with Epoch 1, avg_cost: 0.05416855023762602, acc: 0.9820859872611465
Pass 1900, Epoch 2, Cost 0.060299
Pass 2000, Epoch 2, Cost 0.002715
Pass 2100, Epoch 2, Cost 0.002510
Pass 2200, Epoch 2, Cost 0.061320
Pass 2300, Epoch 2, Cost 0.017619
Pass 2400, Epoch 2, Cost 0.008098
Pass 2500, Epoch 2, Cost 0.022577
Pass 2600, Epoch 2, Cost 0.047620
Pass 2700, Epoch 2, Cost 0.063488
Pass 2800, Epoch 2, Cost 0.019896
Test with Epoch 2, avg_cost: 0.04351866236196841, acc: 0.986265923566879
Pass 2900, Epoch 3, Cost 0.028613
Pass 3000, Epoch 3, Cost 0.040739
Pass 3100, Epoch 3, Cost 0.010376
Pass 3200, Epoch 3, Cost 0.035351
Pass 3300, Epoch 3, Cost 0.061645
Pass 3400, Epoch 3, Cost 0.034073
Pass 3500, Epoch 3, Cost 0.033242
Pass 3600, Epoch 3, Cost 0.045693
Pass 3700, Epoch 3, Cost 0.005410
Test with Epoch 3, avg_cost: 0.045277313895100986, acc: 0.9857683121019108
Pass 3800, Epoch 4, Cost 0.027858
Pass 3900, Epoch 4, Cost 0.002422
Pass 4000, Epoch 4, Cost 0.004470
Pass 4100, Epoch 4, Cost 0.001078
Pass 4200, Epoch 4, Cost 0.013567
Pass 4300, Epoch 4, Cost 0.204539
Pass 4400, Epoch 4, Cost 0.039368
Pass 4500, Epoch 4, Cost 0.001402
Pass 4600, Epoch 4, Cost 0.002093
Test with Epoch 4, avg_cost: 0.043786335517481324, acc: 0.9867635350318471
Best pass is 2, testing Avgcost is 0.04351866236196841
The classification accuracy is 98.63%
results [array([[3.1736151e-18, 5.3097874e-15, 6.5439537e-12, 1.0000000e+00,
        5.3166910e-18, 2.0112835e-10, 4.1340597e-19, 1.2986900e-08,
        3.1170368e-15, 1.4868624e-10]], dtype=float32)]
label [[[6 0 4 8 1 2 9 5 7 3]]]
Inference result of image/infer_3.png is: 3

golang调用模型推理

paddle给出的go参考代码,输入数据需要转成一维的数组,然后再调用reshape方法设置tensor,所以读取数据的代码,需要返回一维浮点数组

type MinistData struct {
	Index [][][][]float32
}

func ReadMnistData(filename string) []float32 {
	var data = new(MinistData)
	file_bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(file_bytes, data)
	if err != nil {
		panic(err)
	}

	res := make([]float32, 0)
	for _, line := range data.Index[0][0] {
		for _, v := range line {
			res = append(res, v)
		}
	}

	return res
}

paddle的推理api文档,在
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.7/advanced_guide/inference_deployment/inference/c_infer_cn.html
go也是调用c的binding,所以api基本跟c的是一致的。

由于paddle的动态链接库我没有安装到/usr/local/lib下面,运行代码的时候,需要设置LD_LIBRARY_PATH

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/go_work/src/paddle_cgo/paddle/paddle_c/paddle/lib/
$ go run mnist.go
gpu id:0 
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0404 23:22:55.816617 16054 pd_config.cc:42] mnist/mnist_model_out/model
I0404 23:22:55.816674 16054 pd_config.cc:44] mnist/mnist_model_out/model
============== paddle inference ==============
input num: 1
input name: [img]
output num: 1
output name: [img]
============== run inference =================
run duration:24.114866ms
============= parse output ===================
output_val: [[3.1736273e-18 5.3097675e-15 6.543929e-12 1 5.3166504e-18 2.0112796e-10 4.1339967e-19 1.2986801e-08 3.117025e-15 1.4868567e-10]]
v:  3.1736273e-18 5.3097675e-15 ...
1 10
1

主要关注 output_val这行输出,与mnist训练的日志对照 results的结果,可以看到两者的结果是一样的,第4个元素的结果是1,也就是数字3。

至此golang调用paddle的c推理api流程基本跑通。各个文件可以从百度云下载

链接: https://pan.baidu.com/s/1gNcZbRZe9UilmVjHeQ0a8Q 提取码: 9tai

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值