前言:深度学习模型经过前期的训练调优评估,最终得到一个精度速度满足要求的模型(.pth, .ckpt,或者.onnx等等格式),但模型要实际用起来,还得部署起来,部署分为在移动端芯片上和服务器上。在移动端芯片部署通常还需要对模型进行格式转换,转换为芯片指定格式才能在芯片上进行推理,比如瑞芯微芯片的.rknn格式,海思芯片的.wk格式,推理代码通常都是c++代码编写。而在服务器上进行部署由于可以利用强大的GPU算力,对模型加速要求不是很高,为了适当提速,通常将模型转换为TensorRT格式,利用Nvidia对算子做的加速实现减少模型推理耗时;服务器上部署也有C++代码实现和Python代码实现,但都是基于后端支撑,提供api服务供使用者调用,本文是基于服务器上部署深度学习模型,利用python代码实现,提供flask接口供使用者调用。
文章目录
- 一、制作Docker镜像
- 1、下载nvidia/cuda的Ubuntu镜像
- 2、运行容器,进入容器后安装必要的工具,及运行工程必要的环境
- 3、在宿主机上写test_api.py测试能否正常调用flask接口,并且能收到正常响应
- 4、退出docker容器,用docker commit将容器提交成工程环境镜像
- 二、了解web服务框架
- 三、gunicorn启动服务
- 四、nginx配置
- 五、supervisor
- 六、其他让程序故障重启的方式
在部署之前,需要对代码做功能性调试,这里不细细展开,这是前提,调试大概分为以下几个步骤,根据实际需要做调整:
1、整个算法工程用python加载正常模型能跑通,且已经改写成flask接口形式,运行能正常监听端口,用postman软件测试能正常返回结果;
2、若需要对模型进行加密,需要先将模型进行加密并删除原来未加密的.pt模型;代码中在加载模型部分需加入对模型进行解密的代码,这就做完了对模型进行加解密的步骤;
3、若需要对整个工程启动加入license验证(用于匹配物理机,防止将算法拷贝到其他物理机上运行),则在这一步改写代码进行license验证;
4、若需要对每个接口进行权限验证,则需改写接口函数的代码进行验证;
5、若需对整个工程代码进行加密,则放在最后一步做,保留加密后的代码;
6、运行加密后的代码,用postman软件测试能正常返回结果即可。
一、制作Docker镜像
深度学习模型部署基本都需要gpu推理,所以需要cuda环境,深度学习框架我这里选择的是pytorch;宿主机上,我的工程路径是 /mvdata/centos199/lishanlu/projects/test_project
1、下载nvidia/cuda的Ubuntu镜像
可到 https://hub.docker.com/r/nvidia/cuda 页面上直接下载,也可用docker pull命令在终端上直接拉取对应版本镜像;
docker pull nvidia/cuda:11.2.0-cudnn8-devel-ubuntu18.04
(这里选的是Ubuntu18.04的cuda11.2带cudnn加速的开发环境,有完整的cuda编译文件)
2、运行容器,进入容器后安装必要的工具,及运行工程必要的环境
docker run -d --name dnn_inference -v /mvdata/centos199/lishanlu/projects/test_project:/home/work/project -p 8200:8203 nvidia/cuda:11.2.0-cudnn8-devel-ubuntu18.04
(以代码挂载的方式启动容器,并将容器的8203端口映射到宿主机的8200端口,容器名字为dnn_inference,这个命令是后台启动的容器)
注意:docker run启动可以指定gpu,但只是在docker19.0版本以后才支持 --gpus
进入容器:docker exec -it dnn_inference /bin/bash
进入容器后就可以进行调试环境了:
# 安装必备软件
apt-get update
apt-get upgrade -y -q
apt-get install vim
apt-get install net-tools
apt-get install inetutils-ping
apt-get install curl
# 安装anaconda,将宿主机上的anaconda安装文件拷贝到容器中(新开一个终端)
sudo docker cp ./Anaconda3-2022.10-Linux-x86_64.sh dnn_inference:/home/work/(将宿主机当前目录下的anconda3安装文件复制到容器/home/work/目录下)
# 切换到容器中的终端,进行安装
cd /home/work
bash Anaconda3-2022.10-Linux-x86_64.sh (安装同宿主机上一样,一路yes)
# 然后就可以创建虚拟环境了
conda create -n inference_env python=3.8
# 激活虚拟环境
source activate inference_env
# 安装运行my_testproject工程需要的库,深度学习模型一般需要pytorch,tensorflow,opencv,PIL,numpy,后端部署需要flask,gunicorn等等;
conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=11.3 -c pytorch
python -m pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install gunicorn -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
# 进入到挂载代码路径,运行程序,若报错缺少哪些包就继续安装
cd /home/work/project
python server.py
3、在宿主机上写test_api.py测试能否正常调用flask接口,并且能收到正常响应
url = "http://192.168.103.xxx:8200/person_detetion"
files = {'file': open('./TestDataImage/6.jpg', 'rb')}
r = requests.post(url, files=files)
#print('========== res:', r.text)
#msg = r.json()['msg']
response = r.json()['result']
#print(msg)
print(response)
4、退出docker容器,用docker commit将容器提交成工程环境镜像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
-a,--author="" Author
-m,--message="" Commit message
-p,--pause=true Pause container during commit
docker commit -a "lishanlu" -m "Deeplearning model deploy environment." -p dnn_inference dnn_deploy/inference_env:v1.0
运行docker commit命令后,通过docker images就可以看到 dnn_deploy/inference_env:v1.0这个镜像了
二、了解web服务框架
下图为web服务大致部署架构图,web应用服务采用Nginx+supervisor+(u)WSGI+flask/django部署;客户端指移动设备App,Web浏览器或者第三方应用,它们通过Nginx映射后提供的ip地址和端口实现对web应用服务的调用,这里nginx充当反向代理,负载均衡的作用,把请求分发到后面的flask/django服务,提高响应速度。uWSGI搭配Nginx实现响应速度快、内存占用率低、高可用定制、自带详尽日志等功能,支持平滑重启。Flask完全兼容WSGI (WebServer Gateway Interface )标准,便于搭建微服务框架,完全基于Unicode编码,无须处理编码问题。
三、gunicorn启动服务
为什么需要gunicorn启动应用?
从上图可以看出完整的web服务框架在nginx和web应用直接是存在一个uWSGI的,这里的gunicorn就属于WSGI,虽然flask自身也能提供web服务,但当请求数量增加时还是不稳定的,从下图启动flask服务弹出的警告就可以看出:
可以看到,flask自身应用后弹出了一条警告,提示当前处理开发模式下的服务器,不要在生产环境使用它,而是要使用一个生成环境下的WSGI服务器。WSGI(Web Server Gateway Interface)- Web服务器网关接口。是为 Python 语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。通过查看flask文档可以发现,推荐采用的WSGI就有如下几种:
gunicorn是基于unix系统,被广泛应用的高性能的Python WSGI HTTP Server。用来解析HTTP请求的网关服务。它通常是在进行反向代理、负载均衡(如 nginx)和一个web 应用(比如 Django 或者 Flask)之间。
在使用gunicorn启动web应用时,gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。使用命令行启动gunicorn有两种方式获取配置项,一种是在命令行配置,一种是在配置文件中获取。
命令行配置如下:
gunicorn -w 1 -b 0.0.0.0:8203 --access-logfile logs/gunicorn_access.log --error-logfile logs/gunicorn_error.log server:app -D
# -w 代表启用几个进程
# -b 代表监听端口号
# --access-logfile 指定access日志目录
# --error-logfile 指定error日志目录
# server:app 其中server是web应用服务的文件名,app代表Flask程序实例名
# -D 代表后台启动程序
启动后可通过命令 ps -ef | grep gunicorn 查看gunicorn进程信息
对于工作模式,默认是sync,即同步模式。这种模式就是说在调用的时候,必须等待调用返回结果后,决定后续的行为。而要切换成异步模型,使用gevent,此时还需要pip来安装gevent。
python -m pip install gevent -i https://pypi.tuna.tsinghua.edu.cn/simple
gunicorn -w 1 -b 0.0.0.0:8203 -k 'gevent' --access-logfile logs/gunicorn_access.log --error-logfile logs/gunicorn_error.log server:app -D
gunicorn以配置文件方式启动,通过 -c 参数指定配置文件,配置文件必须是.py格式
cat guni_conf.py
查看配置文件如下:
import os
import gevent.monkey
gevent.monkey.patch_all()
import multiprocessing
bind = '0.0.0.0:8200'
workers = 1
timeout = 100
backlog = 1024
worker_class = 'gevent'
worker_connections = 1000
daemon = True
debug = False
proc_name = 'gunicorn_openmv'
pidfile = '/root/openmv/logs/gunicorn.pid'
errorlog = '/root/openmv/logs/gunicorn_error.log'
accesslog = '/root/openmv/logs/gunicorn_access.log'
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s "%(f)s" "%(a)s"'
loglevel = 'warning'
x_forwarded_for_header = 'X-FORWARDED-FOR'
gunicorn -c guni_conf.py server:app -D
至此基本的gunicorn+flask异步服务部署就实现了。
四、nginx配置
在web服务中,nginx这里充当反向代理服务器和负载均衡的作用,用nginx做反向代理和负载均衡非常简单,支持两个用法,1个proxy,1个upstream,分别用来做反向代理和负载均衡。
示例配置如下:cat nginx_conf.txt
user root; #设置nginx服务的系统使用用户
worker_processes 8; #工作进程数(和cpu核心数保持一致)
error_log /home/lishanlu/openmv/logs/nginx_error.log info; #nginx的错误日志
pid /home/lishanlu/openmv/logs/nginx.pid; #nginx服务启动时候pid
events {
worker_connections 1024; #每个进程允许最大连接数
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_body_buffer_size 10M;
client_max_body_size 100M;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /home/lishanlu/openmv/logs/nginx_access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
#include /etc/nginx/conf.d/*.conf;
upstream city_server{
server 192.168.103.221:8200;
server 192.168.103.223:8200;
} # 负载均衡这两个服务
server {
listen 9602;
server_name localhost;
#charset koi8-r;
location / {
proxy_pass http://city_server;
}
location ^~ /new/ {
proxy_pass http://192.168.103.221:8201/;
}
location ^~ /ocr_ {
proxy_pass http://192.168.103.221:5000;
}
}
}
上述配置文件,对于city_server服务,部署在了局域网221和223两天宿主机上,nginx配置对他们做了负载均衡,nginx对外暴露的端口是9602;当nginx接收到请求时,会分析请求的接口地址形式,像类似于/person_detect这种直接转发到city_server,走负载均衡分发到两台服务的其中一台,类似于/new/face_restore这种会转发到221服务器上8201端口的应用服务,类似于/ocr_开头的请求,会转发到221服务器上5000端口的应用服务。
启动nginx服务可以通过命令nginx -c ./nginx_conf.txt
这一步做完就完全打通了web服务的链路,浏览器——>nginx——>uWSGI(采用gunicorn)——>flask应用,接下来就是要保证服务的高可用了。
五、supervisor
supervisor 是一个用 python 语言编写的进程管理工具,它可以很方便的监听、启动、停止、重启一个或多个进程。当一个进程意外被杀死,supervisor 监听到进程死后,可以很方便的让进程自动恢复,不再需要程序员或系统管理员自己编写代码来控制。
安装supervisor:
# 进入inference_env虚拟环境
source activate inference_env
pip install supervisor
# 可以看到supervisord可执行文件在anaconda3/envs/inference_env/bin目录下(同时还有supervisorctl,echo_supervisord_conf)
验证是否安装成功,执行命令:echo_supervisord_conf,会打印出supervisor的样例配置信息。(证明supervisord,supervisorctl,echo_supervisord_conf都加入了环境变量)
然后就是配置文件:
[program:gunicorn_processing]
command=/home/jovyan/anaconda3/envs/inference_env/bin/gunicorn -w 1 -b :8200 server:app
directory=/home/jovyan/project/test_project
autostart=true
autorestart=true
user=root
redirect_stderr=true
详细supervisor请参考:https://xugaoxiang.com/2019/12/04/supervisor/
六、其他让程序故障重启的方式
1、通过docker --restart=always命令启动容器
2、写一个sh文件,检测程序是否存在,若不存在则重启;将运行该sh的命令加入到crontab中
3、docker启动容器的时候就指定一个sh文件,sh文件循环查询应用服务是否存在,不存在则拉起
具体详细过程请参考:怎样使程序开机自启动和程序挂掉自动重启
我这里是采用的第3种,sh文件如下,cat run.sh
#!/bin/bash
while true
do
exist_id=`ps -ef|grep "gunicorn"|grep -v grep|wc -l`
if [ $exist_id -eq 0 ]
then
echo "gunicorn not running, now run it..."
cd /home/work/project/test_project
PYTHONIOENCODING=utf-8 gunicorn -c guni_conf.py service:app
fi
usleep 30000000
done
启动容器命令如下:sudo docker run -d --restart=always --name dnn_inference --gpus "all" --privileged -v /mvdata/centos199/lishanlu/projects/test_project:/home/work/project -p 8200:8203 dnn_deploy/inference_env:v1.0 /bin/bash /home/work/project/test_project/run.sh
综上所述,我这里采用的架构如下图所示:
参考:
https://xugaoxiang.com/2020/07/21/flask-12-deployment/
https://www.cnblogs.com/mokundong/p/deploy-deeplearning-model-on-flask.html
https://www.zmrenwu.com/tutorials/hellodjango-blog-tutorial/materials/74/
https://zhuanlan.zhihu.com/p/488458470