基于阿里云ECS GPU服务器的tensorflow模型部署方案

先声明下,本文属原创并付诸实践,过程中对我帮助最大的连接如下:

  1. www.cnblogs.com/techi/p/12994802.html
  2. www.jianshu.com/p/bd67b40e6b85

环境初始化

  1. 系统:ubuntu 16.04,安装完毕docker服务端以及客户端
  2. 显卡:P4*2,安装完毕GPU驱动、cuda、cudnn(因此不需要下载NVIDIA镜像)
  3. 名词定义
    镜像->镜像文件
    容器->实例化的镜像

安装tensorflow/serving:latest-gpu镜像

  1. 下载或者在线安装tensorflow/serving:latest-gpu
sudo docker pull tensorflow/serving:latest-gpu

若从本地镜像文件拉取到仓库:

sudo docker load -i xxxx.tar.gz(镜像文件)

查看仓库镜像文件,比如看到刚入库的镜像id为“860c279d2fec”:

sudo docker images 

修改下镜像名称与版本:

sudo docker tag 860c279d2fec(镜像ID) tensorflow/serving(镜像名):latest-gpu(版本号)
  1. 其他实用docker指令如下

镜像实例化 -> 容器:

sudo docker run -it tensorflow/serving:latest-gpu /bin/sh

查看容器清单:

sudo docker ps -a | grep tensorflow

查看某个容器的信息(端口等):

sudo docker inspect das2432esr32(容器ID)

进入容器:

sudo docker exec -it das2432esr32(容器ID) /bin/bash

容器化模型服务

单模型部署

  1. 部署
    一般来说,模型部署指令如下:
sudo docker run -d -p 8501:8501 --mount type=bind,source=xxxx/pb_model/kt002/,target=/models/my_model -e MODEL_NAME=my_model -t tensor-serving:latest-gpu
其中
source指定宿主机模型存放的位置
target指定容器内模型存放位置, 没有会新建
MODEL_NAME为模型服务后的名称,注意与target的对照关系
容器会开放两个端口:8500(gRPC),8501(rest-api),我只需要8501端口

但是考虑到我的主机被部署过K8S,且IP映射被设定,我就简单部署(坑)如下:

sudo docker run -d --network host --mount type=bind,source=xxxx/pb_model/kt002/,target=/models/my_model -e MODEL_NAME=my_model -t tensor-serving:latest-gpu
  1. 测试
    访问http://localhost:8501/v1/models/my_model:predict,注意模型服务的输入必须在"inputs"下的json,包含模型生成时的signatures,输出是"outputs"。

多模型部署

只考虑每个模型只有一个版本的情况

  1. 首先看下宿主机的模型文件结构

     xxxx/pb_model/
     			├── kt002/
     			│ 	└── 100
     			│ 		 ├── saved_model.pb
     			│ 		 └── variables
     			│ 			  ├── variables.data-00000-of-00001
     			│ 			  └── variables.index
     			│
     			├── model.config     # 配置文件
     			│
     			├── xyj_bx002/
     			│ 	└── 200
     			│ 		 ├── saved_model.pb
     			│ 		 └── variables
     			│ 			  ├── variables.data-00000-of-00001
     			│ 			  └── variables.index
    
  2. 生成配置文件model.config(名字随意),这个文件完全使能于容器,内容编辑如下:

     	model_config_list:{
     		config:{
     		  name:"kt",
     		  base_path:"/models/multi_model/kt002/",  # 容器内模型存储的path
     		  model_platform:"tensorflow"
     		},
     		config:{
     		  name:"xyj_bx",
     		  base_path:"/models/multi_model/xyj_bx002/", # 容器内模型存储的path
     		  model_platform:"tensorflow"
     		}
     	}
    
  3. 启动docker服务,注意宿主机端口可用

sudo docker run -d --network host --mount type=bind,source=xxxx/pb_model/,target=/models/multi_model  -t tensor-serving:latest-gpu --model_config_file=/models/multi_model/model.config

【指定固定的显卡】
需要配置容器的环境变量,上述部署指令添加:

-e NVIDIA_VISIBLE_DEVICES=0
  1. 测试
    访问http://xxx.xxx.xxx.xxx:8501/v1/models/xyj_bx:predict即可,注意事项同上述。需要注意的一个点,POST时,矩阵数据转json需要先把np.array变量通过.tolist(),再json.dumps()即可。
    再补充下吧,输入数据样式如下:
#----------------array转换为json----------------#
# 服务输入的json的key必须是inputs,模型本身的输入signatures是
# input_ids   <-1,64>
# input_mask  <-1,64>
send_dict = {'inputs':{'input_ids':0,'input_mask':0}} 
send_dict['inputs']['input_ids'] = input_token['input_ids'].tolist()
send_dict['inputs']['input_mask'] = input_token['input_mask'].tolist()
json.dumps(send_dict)

工程化部署

  1. Flask部署
    看实际情况了,模型部署的输入就是自己搭建神经网络的输入,本例是bert分类模型,输入是数据矩阵,我想做个中转服务把业务请求的文本信息,转换为数据矩阵,调用模型服务后,再解析成分类结果返回给业务。
    因为是demo,没有并发需要,因此采用flask做个简单点的部署。
import flask,requests,json
import tokenization
import numpy as np

server = flask.Flask(__name__)
#--------------设置--------------#
# 结果解析
flag = {0:'A类',1:'B类',2:'C类'}
# 模型选择
model_names = {'kt':'kt','bx':'xyj_bx','xyj':'xyj_bx'}


def tokenizer(text_list):
    """把text_list转换为维度的模型输入格式数据:
	input_ids   <-1,64>
	input_mask  <-1,64>
	"""
	# 转换代码,此处省略若干行
    return input_ids,input_mask

@server.route('/feeling', methods=['post'])
def feeling():

	# 接收请求,判断分类是否符合要求
    material = json.loads(request.get_data(as_text=True))
    if material['category'] not in model_names.keys() :
        return json.dumps({"result":"类型代码不符合:kt,xyj,bx"}, ensure_ascii=False)

    # 模型选择
    model_name = model_names[material['category']]
    
    # 结构化数据
    input_token = {}
	input_token['input_ids'],input_token['input_mask'] = tokenizer(material['sentence'])
	# 封装json
    send_dict = {'inputs':{'input_ids':0,'input_mask':0}}
    send_dict['inputs']['input_ids'] = input_token['input_ids'].tolist()
    send_dict['inputs']['input_mask'] = input_token['input_mask'].tolist()
    predict_request = json.dumps(send_dict)
	
	# 模型服务调用REAT-API
    url = 'http://xxx.xxx.xxx.xxx:8501/v1/models/%s:predict'%model_name
    response = requests.post(url, data=predict_request)
    result= response.json()  
    
    # 结果解析
    result = np.array(result['outputs'])
    result_predict_index = result.argmax(axis=1)
    result_predict_finally = [(flag[j],str(result[i][j])) for i,j in enumerate(result_predict_index)]
  
    return json.dumps({"result":result_predict_finally}, ensure_ascii=False)
    
if __name__ == '__main__':
    server.run(debug=True, port=9120, host='0.0.0.0')# 指定端口9120
    
  1. 写个客户端测试下:
import requests,json
from requests.adapters import HTTPAdapter

url = 'http://xxx.xxx.xxx.xxx:9120/feeling'

s = requests.Session()
s.keep_alive =False
s.mount(url, HTTPAdapter(max_retries=3))

data = {'category':'kt','sentence':["左侧下方一个直径2厘米的破损",
                      "有它和净化机一起开着",
                      "空调已按完",
                      "吱吱响",
                      "声音很小"]}
data_json = json.dumps(data)

try:
    r = s.post(url,data_json,timeout=5)
    print(r.text)

输出:

{"result": [["C类", "0.8891114"], ["B类", "0.627251387"], ["B类", "0.849841714"], ["C类", "0.852092803"], ["A类", "0.99754554"]]}

至此大功告成!
赶紧走,要尿裤子了!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值