AI部署平台搭建-1

Highlights

方便关键词搜索,使用python语言,在nacos上注册服务,通过web端口访问服务,模型统一成onnx格式,实现异步、多进程队列进行推理,最终以exe形式或docker镜像进行部署。

  • python语言

  • nacos注册

  • Web端口

  • onnx模型转换

  • 异步执行

  • 多进程队列

  • exe部署

  • docker部署

1. AI模型转换(待用)

通过深度学习训练模型,转换成onnx模型,统一模型格式,方便推理。另外后面补充triton进行推理,速度会有质的提升。

# pth模型转化成onnx

import torch

def convert_model(model_name):
    full_net = torch.load(model_name)
    dummy_input = torch.randn(1, 3, 32, 80, 160, device='cuda')
    with torch.no_grad():
        model = full_net.to("cuda")
        torch.onnx.export(model,
                          dummy_input.float(),
                          model_name.replace('.pth', '-small.onnx'),
                          opset_version=13,
                          input_names=['input'],
                          output_names=['output0', 'output1']
                          )


if __name__ == '__main__':
    model = 'models/model_best_metrics.pth'
    convert_model(model)

2. 加载服务信息

将服务信息以json格式保存,便于实施部署,只需修改json文件的信息。

{
    "serviceAIName": "AI-service",
    "serviceAIIp": "172.16.84.152",
    "serviceAIPort": "4003",
    "serviceAIGroupName": "k8s",
    "serviceAINameSpace": "1",
    "serviceMediaIp": "172.16.84.152",
    "serviceMediaName": "application",
    "serviceMediaGroupName": "k8s",
    "serviceMediaNameSpace": "1"
}
# 加载服务的json信息,包括name,ip,port
def load_service_info():
    # 读取json服务信息
    with open('service_info.json', "rb") as f:
        json_data = json.load(f)
    service_dict = json_data
    serviceAI_name = service_dict['serviceAIName']
    serviceAI_ip = service_dict['serviceAIIp']
    serviceAI_port = service_dict['serviceAIPort']
    serviceAI_group = service_dict['serviceAIGroupName']
    serviceAI_namespace = service_dict['serviceAINameSpace']

    serviceMedia_ip = service_dict['serviceMediaIp']
    serviceMedia_name = service_dict['serviceMediaName']
    serviceMedia_group = service_dict['serviceMediaGroupName']
    serviceMedia_namespace = service_dict['serviceMediaNameSpace']
    return (serviceAI_name, serviceAI_ip, serviceAI_port, serviceAI_group, serviceAI_namespace,
            serviceMedia_ip, serviceMedia_name, serviceMedia_group, serviceMedia_namespace)

3. nacos注册

首先,去nacos官网下载nacos,启动nacos服务,网上有教程。然后,就可以按照以下步骤进行nacos注册。

注:按照这种方式注册的服务,15s之后服务会掉,所以需要设置一个心跳检测。

# 服务注册
def service_register():
    url = (f"http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName={serviceAI_name}"
           f"&ip={serviceAI_ip}&port={serviceAI_port}&groupName={serviceAI_group}")
    res = requests.post(url)
    print("完成注册")
#服务检测(每5秒心跳一次)
def service_beat():
    while True:
        url = (f"http://127.0.0.1:8848/nacos/v1/ns/instance/beat?serviceName={serviceAI_name}"
               f"&ip={serviceAI_ip}&port={serviceAI_port}&groupName={serviceAI_group}")
        res = requests.put(url)
        # print("已注册服务,执行心跳服务,续期服务响应状态: {}".format(res.status_code))
        time.sleep(5)
import threading

service_register()
# 5秒以后,异步执行service_beat()方法,这样服务就不会掉了
threading.Timer(5, service_beat).start()

4. web端口服务

将写的任务,可以通过端口访问。

from flask import Flask
from gevent.pywsgi import WSGIServer

app = Flask(__name__)

@app.route('/')
def hello():
    return "Web Deploy"

http_server = WSGIServer((serviceAI_ip, int(serviceAI_port)), app)
http_server.serve_forever()

5. 多线程队列

首先,设置队列操作,最多执行任务maxsize。多线程操作使用threading.Thread实现。在线程运行过程中,加入队列put和get,可以实现队列先进入的任务运行完,才会执行后面的任务。

import queue
import threading

# 队列,FIFO,先进先出
q = queue.Queue(maxsize=2)

# 多线程操作
class myThread(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url

    def run(self):
        q.put(self.name)
        print("开始线程:" + self.name)
        # 模型推理函数inference_func
        inference_func(self.url)
        q.get()
        print("退出线程:" + self.name)

thread1 = myThread(url)
thread1.start()

6. 异步操作

收到url,立马返回一个值。然后进程在后台运行,运行结束将结果post到接收端服务(/post_urls)。使用myThread在后台运行,同时可以多进程运行。

# 收到url链接,立马返回给服务code=200,即收到的信号。
@app.route('/predict', methods=["GET", "POST"])
def predict():
    url = request.json['url']
    print(url)
    thread1 = myThread(url)
    thread1.start()

    if url:
        return jsonify({"code": 200, "msg": "获取到url地址"})
    else:
        return jsonify({"code": 504, "msg": "没有获取到url地址"})
# 将媒体服务注册到nacos,我们通过媒体服务名获取ip和port
def service_post(data):
    # 通过nacos服务名获取服务ip和port,127.0.0.1改为媒体服务上的ip,8848是nacos固定的端口号
    client = nacos.NacosClient(f'http://{serviceMedia_ip}:8848')
    instances = client.list_naming_instance(service_name=serviceMedia_name, group_name=serviceMedia_group)['hosts']
    print(instances)
    # 打印服务实例的 IP 和端口
    ip = instances[0].get('ip')
    port = instances[0].get('port')

    url_service = f'http://{ip}:{port}/post_urls'
    url_results = requests.post(url_service, json=data)
    print("res", url_results)

完整代码

代码
import json
import os
import queue
import threading
import time
import nacos
import cv2
import requests
import onnxruntime
import numpy as np
from flask import Flask, request, jsonify
from gevent.pywsgi import WSGIServer

app = Flask(__name__)
q = queue.Queue(maxsize=2)


@app.route('/')
def hello():
    return "Web Deploy"


# 读取url视频
def read_video(video_url, save_flag=True):
    '''
    :param video_url: 视频的url路径
    :param save_flag: 是否保存视频到本地temp路径
    :return:
    '''
    cap = cv2.VideoCapture(video_url)
    res_list = list()
    # 检查视频文件是否成功打开
    if not cap.isOpened():
        print("Error: Could not open video.")

    # 获取视频的帧率、宽度和高度
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    # 设置保存视频的编解码器和输出参数
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')  # 使用H.264编码器
    output_video_path = 'temp/test.mp4'
    out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))

    while cap.isOpened():
        ret, frame = cap.read()
        if ret:
            res_list.append(frame)
            if save_flag:
                out.write(frame)
        else:
            break
    # 释放资源
    cap.release()
    out.release()
    return res_list

# torch模型转化成onnx,提取shape, name等信息推理
def onnx_inference(frame_list, onnx_model):
    '''
    :param frame_list: 读取视频帧组成的list
    :param onnx_model: 转换后的onnx模型
    :return:
    '''
    session = onnxruntime.InferenceSession(onnx_model, providers=['CUDAExecutionProvider'])
    input_name = session.get_inputs()[0].name
    output_name0 = session.get_outputs()[0].name
    output_name1 = session.get_outputs()[1].name
    input_shape = session.get_inputs()[0].shape
    print(input_shape)

    res_array = np.array([frame_list])
    res_image = np.transpose(res_array, (0, 4, 1, 2, 3))
    input_array = np.resize(res_image, input_shape)

    input_array = input_array.astype(np.float32)

    pred_left, pred_right = session.run([output_name0, output_name1], {input_name: input_array})

    pred_left = np.argmax(pred_left, axis=1)
    pred_right = np.argmax(pred_right, axis=1)

    return pred_left, pred_right

# 将媒体服务注册到nacos,我们通过媒体服务名获取ip和port
def service_post(data):
    # 通过nacos服务名获取服务ip和port,127.0.0.1改为媒体服务上的ip,8848是nacos固定的端口号
    client = nacos.NacosClient(f'http://{serviceMedia_ip}:8848')
    instances = client.list_naming_instance(service_name=serviceMedia_name, group_name=serviceMedia_group)['hosts']
    print(instances)
    # 打印服务实例的 IP 和端口
    ip = instances[0].get('ip')
    port = instances[0].get('port')

    url_service = f'http://{ip}:{port}/post_urls'
    url_results = requests.post(url_service, json=data)
    print("res", url_results)

# 后台并行推理完成,将结果post到媒体服务端
def inference_func(url):
    data = {"success": False}
    os.makedirs('temp', exist_ok=True)
    frame_list = read_video(url)

    model_dir = "models/model_best_metrics.onnx"
    predictions = onnx_inference(frame_list, model_dir)
    predictions = [str(i) for i in predictions]
    data['predictions'] = list(predictions)
    data['success'] = True
    service_post(data)

# 加载服务的json信息,包括name,ip,port
def load_service_info():
    # 读取json服务信息
    with open('service_info.json', "rb") as f:
        json_data = json.load(f)
    service_dict = json_data
    serviceAI_name = service_dict['serviceAIName']
    serviceAI_ip = service_dict['serviceAIIp']
    serviceAI_port = service_dict['serviceAIPort']
    serviceAI_group = service_dict['serviceAIGroupName']
    serviceAI_namespace = service_dict['serviceAINameSpace']

    serviceMedia_ip = service_dict['serviceMediaIp']
    serviceMedia_name = service_dict['serviceMediaName']
    serviceMedia_group = service_dict['serviceMediaGroupName']
    serviceMedia_namespace = service_dict['serviceMediaNameSpace']
    return (serviceAI_name, serviceAI_ip, serviceAI_port, serviceAI_group, serviceAI_namespace,
            serviceMedia_ip, serviceMedia_name, serviceMedia_group, serviceMedia_namespace)

class myThread(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url

    def run(self):
        q.put(self.name)
        print("开始线程:" + self.name)
        inference_func(self.url)
        q.get()
        print("退出线程:" + self.name)

@app.route('/predict', methods=["GET", "POST"])
def predict():
    # url = service_get()[0]
    # url = request.form['url']
    url = request.json['url']
    print(url)
    thread1 = myThread(url)
    thread1.start()

    if url:
        return jsonify({"code": 200, "msg": "获取到url地址"})
    else:
        return jsonify({"code": 504, "msg": "没有获取到url地址"})

# 服务注册
def service_register():
    url = (f"http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName={serviceAI_name}"
           f"&ip={serviceAI_ip}&port={serviceAI_port}&groupName={serviceAI_group}")
    res = requests.post(url)
    print("完成注册")

#服务检测(每5秒心跳一次)
def service_beat():
    while True:
        url = (f"http://127.0.0.1:8848/nacos/v1/ns/instance/beat?serviceName={serviceAI_name}"
               f"&ip={serviceAI_ip}&port={serviceAI_port}&groupName={serviceAI_group}")
        res = requests.put(url)
        # print("已注册服务,执行心跳服务,续期服务响应状态: {}".format(res.status_code))
        time.sleep(5)


if __name__ == '__main__':
    '''
    1. 配置service_info信息
        serviceName: AI推理服务的名称,可自行设置
        serviceAIIp: AI推理服务的ip,windows通过命令ipconfig查看,linux通过命令ifconfig查看
        serviceAIPort: AI推理服务的端口号,可自行设置,跟别的没有冲突就可以
        serviceAIGroupName: AI推理服务的group name
        serviceAINameSpace: AI推理服务的space name
        
        serviceMediaIp: 媒体服务的ip
        serviceMediaName: 媒体服务的名称
        serviceMediaGroupName: 媒体服务的group name
        serviceMediaNameSpace: 媒体服务的space name
    2. 函数service_post
        post_urls字符串由提供,即结果存放的位置
    3. 配置inference_func
        model_dir是onnx模型的存放位置
    '''
    service_info = load_service_info()
    serviceAI_name = service_info[0]
    serviceAI_ip = service_info[1]
    serviceAI_port = service_info[2]
    serviceAI_group = service_info[3]
    serviceAI_namespace = service_info[4]

    serviceMedia_ip = service_info[5]
    serviceMedia_name = service_info[6]
    serviceMedia_group = service_info[7]
    serviceMedia_namespace = service_info[8]

    print("start")
    service_register()
    # 5秒以后,异步执行service_beat()方法
    threading.Timer(5, service_beat).start()

    http_server = WSGIServer((serviceAI_ip, int(serviceAI_port)), app)
    http_server.serve_forever()
posted @ 2024-07-23 16:56  小吕同学吖  阅读(40)  评论(0)    收藏  举报