简单实验uwsgi+flask 部署caffe模型

石思淼
2023-12-01

【背景】

    模型训练好了之后怎么部署在线上,让大家直接通过网络就能使用?网络上给出的一般方案就是 Flask + Nginx + uWSGI,当然这里的 nginx 可以替换成类似 Apache ,Flask 也可以考虑用 Django,但就简单、高效而言还是首先 Flask + Nginx + uWSGI。Nginx 在这一方案中主要是起反向代理服务器的作用,我暂且只是做一个小实验,所以没将 Nginx 考虑进来,直接拿 Flask + uWSGI 开刷。简单说明一下什么是 Flask?什么又是 uWSGI?

    如果你想用 Python 语言快速实现一个网站 或 Web 服务,你选 Flask 就对了。Flask 是一个使用 Python 编写的轻量级 Web 应用框架,较其它同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合 MVC 模式进行开发,一个小型的研发团队在短时间内就可以完成功能丰富的中小型网站或 Web 服务的实现。在回答 uWSGI 是什么之前,先得了解一下 WSGI 是什么?WSGI 全称是 Web Server Gateway Interface,或者 Python Web Server Gateway Interface,是为 Python 语言定义的描述 Web 服务器(web server) 如何与Web 应用程序(web application) 通信的一种规范。Flask就是运行在 WSGI 协议之上的 web 框架,它主要是一个 Web Application,但同时也自带 WSGI Server。话虽如此,但 Flask 框架只是简单实现了WSGI server,一般只用于调试,真要在生产环境下用最好还是用更专业的 WSGI server。现在可以回答 uWSGI 是什么了,uWSGI 是一个 Web 服务器,它实现了 WSGI 、uwsgi(同 WSGI 一样,是一种通信协议)、http 等协议。

【安装】

我的环境:

Ubuntu 16.04

Python 2.7

#flask

pip install flask --user 

#uwsgi

你可以使用 pip 安装,也可以使用源码安装,反正都比较简单。我是使用的源码安装。

-- pip 安装 --

pip install uwsgi --user 

-- 源码安装 --

wget https://files.pythonhosted.org/packages/e7/1e/3dcca007f974fe4eb369bf1b8629d5e342bb3055e2001b2e5340aaefae7a/uwsgi-2.0.18.tar.gz

tar -xzf uwsgi-2.0.18.tar.gz

python setup.py build

make #会生成一个 uwsgi 二进制文件

cp uwsgi /usr/bin #将二进制文件 copy 到一个 写入在$PATH 环境变量中的公共目录下

【配置】

这里配置主要就是针对 uwsgi 进行参数设置,首先我新建了一个叫saas 的项目目录,在目录下新建一个 wsgi.ini 文件。

[uwsgi]
#允许主进程的存在,由它来管理其它进程,其它 uwsgi 进程都是訪 master 进程的子进程,如果 kill 掉这个 master 进程,相当于重启所有的 uwsgi 进程.
master=true
#项目 运行目录,在 app 加载前切换到訪目录
chdir=/home/zuosi/saas
#因为我现在只用到 uwsgi 和 flask,所以这里就用 http 就行了,不要用啥 socket 之类的.
http=127.0.0.1:6789
#要加载的WSGI 模型及应用名称
module=saas:app
logto=/home/zuosi/saas/wsgi.log
#开启的工作进程数 量,processes 同 workers 是一个意思
processes=4
#代码有更新的时候自动重新加载,这个在开发的时候很有用
py-autoreload=true
#状态检查服务
stats=127.0.0.1:10000

【应用】

在启动 flask 之前先创建一个简单的 flask 应用。

from flask import Flask
import cv2

#要创建的 flask 实例的名字,注意与 wsgi.ini 中保持一致
app = Flask(__name__) 

@app.route('/test')
def hello_world():
    return "hello, world"

将訪文件保存 wsgi.py,即模块的名字,注意与 wsgi.ini 中设置的名字保持一致。现在启动 uwsgi 试试。

$ uwsgi -i wsgi.init 
[uWSGI] getting INI configuration from wsgi.ini

查看wsgi.log 日志文件会发现如下信息。

*** Starting uWSGI 2.0.18 (64bit) on [Mon Nov  4 23:26:27 2019] ***
compiled with version: 5.4.0 20160609 on 02 November 2019 04:58:49
os: Linux-4.4.0-142-generic #168-Ubuntu SMP Wed Jan 16 21:00:45 UTC 2019
nodename: ky-cs-18
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 12
current working directory: /home/zuosi/saas
detected binary path: /home/zuosi/.local/bin/uwsgi
chdir() to /home/zuosi/saas
your processes number limit is 127269
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on 127.0.0.1:6789 fd 4
uwsgi socket 0 bound to TCP address 127.0.0.1:39432 (port auto-assigned) fd 3
Python version: 2.7.12 (default, Oct  8 2019, 14:14:10)  [GCC 5.4.0 20160609]
Python main interpreter initialized at 0xc6b2d0
python threads support enabled
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 364520 bytes (355 KB) for 4 cores
*** Operational MODE: preforking ***

WSGI app 0 (mountpoint='') ready in 6 seconds on interpreter 0xc6b2d0 pid: 93102 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 93102)
spawned uWSGI worker 1 (pid: 93131, cores: 1)
spawned uWSGI worker 2 (pid: 93132, cores: 1)
spawned uWSGI worker 3 (pid: 93133, cores: 1)
spawned uWSGI worker 4 (pid: 93134, cores: 1)
*** Stats server enabled on 127.0.0.1:10000 fd: 58 ***
spawned uWSGI http 1 (pid: 93135)

项目已经启动成功了。现在我们来做一下简单的测试,通过 http 访问一下 test 接口。

$ curl 127.1:6789/test
hello, world!

初步实验已经成功了,现在进入下一步,把 caffe 模型加载进来,提供一个模型 infer 的接口。

from flask import Flask
import os
import cv2
import caffe

caffe.set_mode_gpu() #设置 gpu 模式
proto='deploy.prototxt'
model='my.caffemodel'
net = caffe.Net(proto, model, caffe.TEST)

app = Flask(__name__)

@app.route('/test')
def hello_world():
    return "hello, world!"

#就读一个本地的测试图片再使用 caffe 模型进行 infer,打印输出结果并返回 ok
@app.route('/infer', methods=['GET'])
def infer():
    path = 'test.jpg'
    img = cv2.imread(path)
    data = img.transpose(2,0,1)/255.0

    net.blobs['data'].data[...] = data
    out = net.forward()['prob']
    print(out.tolist())
    return 'ok'

重新启动一下 uwsgi,并测试 infer 接口。

$ uwsgi --ini wsgi.ini

启动没问题

$ curl 127.1:6789/infer

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">

<title>500 Internal Server Error</title>

<h1>Internal Server Error</h1>

<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

[zuosi@ky-cs-18 saas]$curl 127.1:6789/infer

curl: (52) Empty reply from server

e.......报错了,查看一下日志信息。

[pid: 93366|app: 0|req: 1/1] 127.0.0.1 () {28 vars in 296 bytes} [Mon Nov  4 23:34:15 2019] GET /infer => generated 290 bytes in 36 msecs (HTTP/1.1 500) 2 headers in 84 bytes (1 switches on core 0)
F1104 23:35:07.865353 93366 syncedmem.cpp:19] Check failed: error == cudaSuccess (3 vs. 0)  initialization error
*** Check failure stack trace: ***
    @     0x7f6cd29df5cd  google::LogMessage::Fail()
    @     0x7f6cd29e1433  google::LogMessage::SendToLog()
    @     0x7f6cd29df15b  google::LogMessage::Flush()
    @     0x7f6cd29e1e1e  google::LogMessageFatal::~LogMessageFatal()
    @     0x7f6cd3599715  caffe::SyncedMemory::MallocHost()
    @     0x7f6cd3599ae0  caffe::SyncedMemory::to_cpu()
    @     0x7f6cd359ae4d  caffe::SyncedMemory::mutable_cpu_data()
    @     0x7f6cd49d09c0  boost::python::objects::caller_py_function_impl<>::operator()()
    @     0x7f6cd24935cd  boost::python::objects::function::call()
    @     0x7f6cd24937c8  (unknown)
    @     0x7f6cd249b613  boost::python::handle_exception_impl()
    @     0x7f6cd2490999  (unknown)
    @     0x7f6d4f3792b3  PyObject_Call
    @     0x7f6d4f37a69f  PyObject_CallFunction
    @     0x7f6d4f403be9  _PyObject_GenericGetAttrWithDict
    @     0x7f6d4f3156df  PyEval_EvalFrameEx
    @     0x7f6d4f45011c  PyEval_EvalCodeEx
    @     0x7f6d4f3a64ad  (unknown)
    @     0x7f6d4f3792b3  PyObject_Call
    @     0x7f6d4f31318c  PyEval_EvalFrameEx
    @     0x7f6d4f319084  PyEval_EvalFrameEx
    @     0x7f6d4f319084  PyEval_EvalFrameEx
    @     0x7f6d4f319084  PyEval_EvalFrameEx
    @     0x7f6d4f45011c  PyEval_EvalCodeEx
    @     0x7f6d4f3a63b0  (unknown)
    @     0x7f6d4f3792b3  PyObject_Call
    @     0x7f6d4f3ed46c  (unknown)
    @     0x7f6d4f3792b3  PyObject_Call
    @     0x7f6d4f39a535  (unknown)
    @     0x7f6d4f3792b3  PyObject_Call
    @     0x7f6d4f44f547  PyEval_CallObjectWithKeywords
    @           0x47c711  python_call
DAMN ! worker 4 (pid: 93366) died, killed by signal 6 :( trying respawn ...

Respawned uWSGI worker 4 (new pid: 93381)

报了 CUDA 也就是 GPU 方面的错。

好,开始排错。我试着将 caffe设置成 cpu 模式,将 caffe.set_mode_gpu()改为 caffe.set_mode_cpu(),重新测试。

curl 127.1:6789/infer

ok

测试通过,打印 infer 结果也没有问题,真是 GPU 的锅?别这么快下结论。我将 caffe 的模式改回 GPU,同时对 wsgi.ini参数进行修改,master 改为 false,也就是不允许 master 进程。

uwsgi]
#允许主进程的存在,由它来管理其它进程,其它 uwsgi 进程都是訪 master 进程的子进程,如果 kill 掉这个 master 进程,相当于重启所有的 uwsgi 进程.
master=false
#项目 运行目录,在 app 加载前切换到訪目录
chdir=/home/zuosi/saas
#因为我现在只用到 uwsgi 和 flask,所以这里就用 http 就行了,不要用啥 socket 之类的.
http=127.0.0.1:6789
#要加载的WSGI 模型及应用名称
module=saas:app
logto=/home/zuosi/saas/wsgi.log
#开启的工作进程数 量,processes 同 workers 是一个意思
processes=4
#代码有更新的时候自动重新加载,这个在开发的时候很有用
py-autoreload=true
#状态检查服务
stats=127.0.0.1:10000

再来测试一下,这次连着测试100次。

$for i in `seq 1 100`;do curl 127.1:6789/infer;echo -e "\t";done
curl: (52) Empty reply from server
curl: (52) Empty reply from server
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
curl: (52) Empty reply from server	
ok	
ok	
ok	
ok	
...

有三次失败,往后再怎么测试都会成功。

如果再将 wsgi.ini 中的 processes 由4改为1,现在整个 uwsgi就剩一个工作进程了(没有 master 了)。

uwsgi]
#允许主进程的存在,由它来管理其它进程,其它 uwsgi 进程都是訪 master 进程的子进程,如果 kill 掉这个 master 进程,相当于重启所有的 uwsgi 进程.
master=false
#项目 运行目录,在 app 加载前切换到訪目录
chdir=/home/zuosi/saas
#因为我现在只用到 uwsgi 和 flask,所以这里就用 http 就行了,不要用啥 socket 之类的.
http=127.0.0.1:6789
#要加载的WSGI 模型及应用名称
module=saas:app
logto=/home/zuosi/saas/wsgi.log
#开启的工作进程数 量,processes 同 workers 是一个意思
processes=1
#代码有更新的时候自动重新加载,这个在开发的时候很有用
py-autoreload=true
#状态检查服务
stats=127.0.0.1:10000

再测试100次。

$for i in `seq 1 100`;do curl 127.1:6789/infer;echo -e "\t";done
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
ok	
...

全部成功!

当局着谜,旁观者清,谁来解惑?

【参考】

http://qaru.site/questions/13659229/check-failed-error-cudasuccess-3-vs-0-initialization-error-check-failure-stack-trace

 类似资料: