序言
前段时间给我们运维同事的服务器上部署了一个ocr服务接口,模型使用的是nvidia 1650ti的显卡推理,速度非常可观,毫秒级别响应。但是最近运维又找到我,说服务器的显卡好像出了问题,我检查一看:好家伙,显卡居然烧了。这张显卡为公司服务了好几年,毕竟“陈年老卡”,上一任同事传下来的,不知道传了几代,如今也算圆寂了。好了,不瞎扯,说会正题,没了显卡,我这模型可咋整?现阶段只能用cpu推理了,但是模型的速度可咋优化?这可难不倒我,检查了一下这台服务器上的cpu,发现了Intel的,这就好办了 , 果断使用openvino加速。
OpenVINO 是英特尔基于自身现有的硬件平台开发的一种可以加快高性能计算机视觉和深度学习视觉应用开发速度工具套件,支持各种英特尔平台的硬件加速器上进行深度学习,并且允许直接异构执行。OpenVINO相比TVM和libtorch在intelx86的CPU端还是有很大优势的,可以说在X86上推断速度没有什么框架可以媲美OpenVINO。
一、docker下载
因为我们运维服务器上的服务用的都是docker来部署,所以这里也选择了使用docker,关于docker部署的好处就不多说了,简单来说呢有五点好处:持续部署、版本控制、可移植性、隔离性和安全性。
需要使用到openvino的docker容器,所以需要去docker hub商城找一下:
我这里选择了一个2021.4的版本,复制红框中的语句直接在命令行中运行pull下来(默认已经安装好了docker,可以使用docker语句命令)
因为镜像有点大,下的稍微慢一点,耐心等待,pull完后在终端中输入:sudo docker images可以查看pull下来的镜像信息:
5.38G,基础镜像实属有点大,不过不要紧,只要功能满足我们的就可以,下载下来后,可以使用如下命名创建一个docker容器并进去查看容器内的内容:
sudo docker run -it --name openvino openvino/ubuntu18_dev:2021.4 /bin/bash
跟着我的命令往下看,首先近来后会提示:[setupvars.sh] OpenVINO environment initialized,环境初始化成功,默认进到openvino的工作文件夹,这时候我们可以查看文件夹内的内容,其中我上面命令终止的地方是模型转换的地方,在该目录中,openvino支持多种模型转换为IR模型。可以看到不支持pytorch直接转,所以可以先将pytorch模型转成onnx模型再转换,但是最新的openvino是可以支持直接加载onnx模型的,所以也可以不用转,我在代码中就是直接加载onnx模型。
接下来可以看下python环境的情况,输入pip list,查看已经有的环境包:
可以看到两个主流的深度学习框架都已经安装好,可以直接使用,另外可能看不到opencv-python这个包,这个大可放心,openvino的镜像中若是没有opencv的包,那不是滑天下之大稽。不用我们自己安装,镜像中自带的opencv包据说比我们平时安装的效率还要更高。另外如果没有你需要的包,也可自己pip安装,和ubuntu安装无异,当然也可以使用dockerfile安装,往下会提到。
二、docker上开发
前面说到了docker的环境检查,那怎么在docker中开发?其实比较简单,docker内的环境与主机的ubuntu环境基本上操作是一致的,只需要把文件copy进去或者将外部文件映射进去即可,我比较习惯的是用文件映射的方式。要映射一个文件到docker中,只需要修改一下刚才的docker启动命令即可:
sudo docker run -it --name openvino openvino/ubuntu18_dev:2021.4 /bin/bash # 之前的
sudo docker run -it --name openvino -v {work_dir目录}:/app -w /app openvino/ubuntu18_dev:2021.4 /bin/bash # 修改后的
上面命令增加了-v 和-w的参数,分别是文件映射和设定工作目录,-v将本地的文件映射到/app的目录下,-w则是设定/app为工作目录,因为之前提到的,默认进去的目录是openvino工作目录,再次进入的时候,即可以看到你主机外面的文件在docker中了:
这时候在外面开发时,当修改了外部文件内容,docker里的文件也会跟着变化,修改完只需在docker中运行即可,当然你也可以直接将文件copy进去,然后用pycharm自带的远程开发进行代码开发,这个我没有尝试过,就不讲了,网上有文章,可以自己搜来学习。就和ubuntu开发时没啥差别。
openvino支持python,然后可以编写python的openvino推理代码进行测试,这里我对用了repvgg做主干的crnn识别模型进行速度测试对比:
上面的是torch推理,下面是openvino推理,可以看到即便是没使用任何框架加速的情况下,repvgg做主干的crnn在cpu上也能达到20ms左右的推理速度,完全可以不用加速都可以使用,但是作为一名有追求的算法工程师,不能满足于现状,必须要多尝试,不放过任何一个可以加速的方案,所以在使用了openvino加速推理后,除去首次加载模型的耗时,每次推理能够达到了7ms左右,速度提升了接近3倍,这还是建立在openvino不是动态推理的情况下,因为openvino对动态尺度的推理不太友好,所以每次推理的时候都会padding到固定的长宽,而torch推理是动态尺度的推理,对于openvino来说是不公平的,如果图片较短,而设定的固定宽过长,那就会多出了很多冗余的部分,增加了推理的时耗。不过即便这样,也能达到了接近3倍左右的加速,达到了毫秒级别,效果还是很不错的,感兴趣的话可以在自己的模型上测试。
如果不知道怎么写推理代码的话可以参考yolox的openvino部署代码进行修改
三、docker部署应用接口
前提:准备好你的flask部署服务代码。
当我们需要docker去部署应用的时候,需要将容器构建打包,发给别人,docker构建打包有两种方式,一种是基于dockerfile,一种是docker commit,两者的区别可以看这篇文章docker commit 和docker build (实战使用以及区别)。
通常来说,docker commit比较容易上手,不过需要进到容器中配置需要的环境,pip、apt安装需要的包,全手动安装,安装完后推出到容器外面,直接执行:
docker commit docker_name images_name:v1
其中docker_name是刚才你运行配置的那个容器的名字,images_name是新镜像的名字,v1是版本号。
例如我这里有一个配置好的容器,我想把它构建成新的镜像:
只需要运行:
docker commit openvino openvino:v1
就得到了新的image:
然后再使用保存命令docker save -o openvino.tar openvino:v1打包成tar文件,再把得到openvino.tar文件发给别人即可
第二种dockerfile的方式虽然上手稍微难一些,但是思路比较清晰,dockerfile本身就是一个比较详细的构建文档,有这个文档就可以清楚的知道新构建的镜像经历了怎样的变化。没有黑箱操作的困扰了,后期的维护更为方便了,可扩展性强。
以上面的例子再给出dockerfile的示例,新建一个DockerFile文件:
# 基于的基础镜像
FROM openvino/ubuntu18_dev:2021.4
# 维护者信息
MAINTAINER cai 1079863482@qq.com
# 代码添加到 app 文件夹
# ADD $PWD /app
COPY ./requirements.txt ./
# 设置 /app 文件夹是工作目录
# WORKDIR /app
#管理员权限
USER root
# 安装相关支持
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 声明镜像内服务监听的端口
EXPOSE 5050
# docker启动后运行的命令
CMD /bin/bash -c "source /opt/intel/openvino/bin/setupvars.sh"
ENTRYPOINT ["gunicorn", "-c", "gunicorn_conf.py", "run_openvino:app"]
其中requirements.txt文件是需要pip安装的包,这个大家跑代码的时候应该是比较熟悉的。然后也可以把代码在构建的时候加到容器中,相当于把代码也打包进去了,但是我比较喜欢以文件映射的方式运行,因为有时候需要修改一些配置,所以我没有把代码直接打包进去。
最后注意CMD那条命令,这是openvino容器的一个大坑,如果不执行这条语句的话,容器内的openvino环境是不会初始化的,也就是无法调用openvino的接口,这样容器是会起不来的;然后ENTRYPOINT里就是flask服务拉起的命令了,这个用flask和gunicorn部署过的应该知道的。
配置完后最后执行构建,注意后面的点不要忽略,代表当前目录:
docker build -t openvino:v2 .
就得到了v2版本的镜像:
同样的可以使用save命令进行打包:
docker save -o openvino.tar openvino:v2
那么这个tar文件怎么运行呢,很简单,只需要在目标机器上加载出来即可:
sudo docker load -i openvino.tar
然后启动容器命令,第一种commit构建的启动方式,因为没有把容器启动命令打包进去,所以需要手动添加启动命令:
sudo docker run -itd -v $PWD:/app -w /app -p 5050:5050 --name test --restart=always --env LANG=C.UTF-8 openvino:v1 /bin/bash -c "source /opt/intel/openvino/bin/setupvars.sh && gunicorn -c gunicorn_conf.py run_openvino:app"
其中-p是端口映射,–env LANG=C.UTF-8是设置容器内编码方式,不设置这个的话如果程序中有中文输出会报错,–restart=always 为重启后docker自启动命令,不加的话当主机重启时,docker环境不会重启,这在生产环境上是很危险的。
第二种dockerfile构建的容器的启动方式:
sudo docker run -itd -v $PWD:/app -w /app -p 5050:5050 --name test --restart=always --env LANG=C.UTF-8 openvino:v2
不需要添加启动命令,只需要将文件和端口做好映射即可,服务的启动命令在构建时已经打包进去了。然后查看一下容器有没有启动成功:
使用如下命令测试下我的接口性能(性能不够的话可以再加个nginx):
ab -c 10 -n 100 http://192.168.1.101:5050/ocr/recognition
上面的语句表示有10个并发访问,每秒总共有100个请求,http://192.168.1.101:5050/ocr/recognition表示请求的目标URL,在此情况下进行压力测试。
通过上图,测试结果一目了然,ab测试得出吞吐率为:Requests per second: 3356.38[#/sec](mean),这行显示的是服务每秒钟能处理几个请求。因为我拉起服务的时候开了4个线程,所以性能还是不错的。
使用postman测试接口,发送的是图片base64的信息,返回json识别情况,响应速度也还不错: