【深度学习】ONNX模型多线程快速部署【基础】

2023-09-21 11:49:26

【深度学习】ONNX模型CPU多线程快速部署【基础】

提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论


前言

之前的内容已经尽可能简单、详细的介绍CPU【Pytorch2ONNX】和GPU【Pytorch2ONNX】俩种模式下Pytorch模型转ONNX格式的流程,本博文根据自己的学习和需求进一步讲解ONNX模型的部署。onnx模型博主将使用PyInstaller进行打包部署,PyInstaller是一个用于将Python脚本打包成独立可执行文件的工具,【入门篇】中已经进行了最基本的使用讲解。之前博主在【快速部署ONNX模型】中分别介绍了CPU模式和GPU模式下onnx模型打包成可执行文件的教程,本博文将进一步介绍在CPU模式下使用多线程对ONNX模型进行快速部署。
系列学习目录:
【CPU】Pytorch模型转ONNX模型流程详解
【GPU】Pytorch模型转ONNX格式流程详解
【ONNX模型】快速部署
【ONNX模型】多线程快速部署


搭建打包环境

创建一个纯净的、没有多余的第三方库和模块的小型Python环境,抛开任何pytorch相关的依赖,只使用onnx模型完成测试。

# name 环境名、3.x Python的版本
conda create -n deploy python==3.10
# 激活环境
activate deploy 
# 安装onnx
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple onnx
# 安装GPU版
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple onnxruntime-gpu==1.15.0
# 下载安装Pyinstaller模块
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pyinstaller
# 根据个人情况安装包,博主这里需要安装piilow
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow

python多线程并发简单教程

多线程是一种并发编程的技术,通过同时执行多个线程来提高程序的性能和效率。python的内置模块提供了两个内置模块:thread和threading,thread是源生模块,是比较底层的模块,threading是扩展模块,是对thread做了一些封装,可以更加方便的被使用,所以只需要使用threading这个模块就能完成并发的测试。

基本教程

python3.x中通过threading模块有两种方法创建新的线程:通过threading.Thread(Target=executable Method)传递给Thread对象一个可执行方法(或对象);通过继承threading.Thread定义子类并重写run()方法。下面给出了俩种创建新线程方法的例子,读者可以运行一下加深理解。

  • 普通创建方式:threading.Thread进行创建多线程
    import threading
    import time
    
    def myTestFunc():
        # 子线程开始
        print("the current threading %s is runing" % (threading.current_thread().name))
        time.sleep(1)   # 休眠线程
        # 子线程结束
        print("the current threading %s is ended" % (threading.current_thread().name))
    
    # 主线程
    print("the current threading %s is runing" % (threading.current_thread().name))
    # 子线程t1创建
    t1 = threading.Thread(target=myTestFunc)
    # 子线程t2创建
    t2 = threading.Thread(target=myTestFunc)
    
    t1.start()  # 启动线程
    t2.start()
    
    t1.join()  # join是阻塞当前线程(此处的当前线程时主线程) 主线程直到子线程t1结束之后才结束
    t2.join()
    # 主线程结束
    print("the current threading %s is ended" % (threading.current_thread().name))
    
  • 自定义线程:继承threading.Thread定义子类创建多线
    import threading
    import time
    
    class myTestThread(threading.Thread):  # 继承父类threading.Thread
      def __init__(self, threadID, name, counter):
         threading.Thread.__init__(self)
         self.threadID = threadID
         self.name = name
    
      # 把要执行的代码写到run函数里面,线程在创建后会直接运行run函数
      def run(self):
         print("the current threading %s is runing" % (self.name))
         print_time(self.name,5*self.threadID)
         print("the current threading %s is ended" % (self.name))
    
    
    def print_time(threadName, delay):
         time.sleep(delay)
         print("%s process at: %s" % (threadName, time.ctime(time.time())))
    
    # 主线程
    print("the current threading %s is runing" % (threading.current_thread().name))
    
    # 创建新线程
    t1 = myTestThread(1, "Thread-1", 1)
    t2 = myTestThread(2, "Thread-2", 2)
    
    # 开启线程
    t1.start()
    t2.start()
    
    # 等待线程结束
    t1.join()
    t2.join()
    
    print("the current threading %s is ended" % (threading.current_thread().name))
    

ONNX模型多线程并发

博主采用的是基础教程中普通创建方式创建新线程:将推理流程单独指定成目标函数,而后创建线程对象并指定目标函数,同一个推理session被分配给多个线程,多个线程会共享同一个onnx模型,这是因为深度学习模型的参数通常存储在模型对象中的共享内存中,并且模型的参数在运行时是可读写的,每个线程可以独立地使用模型对象执行任务,并且线程之间可以共享模型的状态和参数。

import onnxruntime as ort
import numpy as np
from PIL import Image
import time
import datetime
import sys
import os
import threading

def composed_transforms(image):
    mean = np.array([0.485, 0.456, 0.406])  # 均值
    std = np.array([0.229, 0.224, 0.225])  # 标准差
    # transforms.Resize是双线性插值
    resized_image = image.resize((args['scale'], args['scale']), resample=Image.BILINEAR)
    # onnx模型的输入必须是np,并且数据类型与onnx模型要求的数据类型保持一致
    resized_image = np.array(resized_image)
    normalized_image = (resized_image/255.0 - mean) / std
    return np.round(normalized_image.astype(np.float32), 4)

def check_mkdir(dir_name):
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)

args = {
    'scale': 416,
    'save_results': True
}
def process_img(img_list,
                ort_session,
                image_path,
                mask_path,
                input_name,
                output_names):

    for idx, img_name in enumerate(img_list):
        img = Image.open(os.path.join(image_path, img_name + '.jpg')).convert('RGB')
        w, h = img.size
        #  对原始图像resize和归一化
        img_var = composed_transforms(img)
        # np的shape从[w,h,c]=>[c,w,h]
        img_var = np.transpose(img_var, (2, 0, 1))
        # 增加数据的维度[c,w,h]=>[bathsize,c,w,h]
        img_var = np.expand_dims(img_var, axis=0)
        start_each = time.time()
        prediction = ort_session.run(output_names, {input_name: img_var})
        time_each = time.time() - start_each
        # 除去多余的bathsize维度,NumPy变会PIL同样需要变换数据类型
        # *255替换pytorch的to_pil
        prediction = (np.squeeze(prediction[3]) * 255).astype(np.uint8)
        if args['save_results']:
            Image.fromarray(prediction).resize((w, h)).save(os.path.join(mask_path, img_name + '.jpg'))

def main():
    # 线程个数
    num_cores = 10
    # 保存检测结果的地址
    input = sys.argv[1]
    # providers = ["CUDAExecutionProvider"]
    providers = ["CPUExecutionProvider"]
    model_path = "PFNet.onnx"
    ort_session = ort.InferenceSession(model_path, providers=providers)  # 创建一个推理session
    input_name = ort_session.get_inputs()[0].name
    # 输出有四个
    output_names = [output.name for output in ort_session.get_outputs()]
    print('Load {} succeed!'.format('PFNet.onnx'))

    start = time.time()
    image_path = os.path.join(input, 'image')
    mask_path = os.path.join(input, 'mask')

    if args['save_results']:
        check_mkdir(mask_path)
    # 所有图片数量
    img_list = [os.path.splitext(f)[0] for f in os.listdir(image_path) if f.endswith('jpg')]

    # 每个线程被均匀分配的图片数量
    total_images = len(img_list)
    start_index = 0
    images_per_list = total_images // num_cores

    # 理解成线程池
    Thread_list = []
    for i in range(num_cores):
        end_index = start_index + images_per_list
        img_l = img_list[start_index:end_index]
        start_index = end_index
        # 分配线程
        t = threading.Thread(target=process_img, args=(img_l,ort_session, image_path, mask_path,input_name,output_names))
        # 假如线程池
        Thread_list.append(t)
        # 线程执行
        t.start()
    # 这里是为了阻塞主线程
    for t in Thread_list:
        t.join()
    end = time.time()
    print("Total Testing Time: {}".format(str(datetime.timedelta(seconds=int(end - start)))))
if __name__ == '__main__':
    main()

打包成可执行文件

  • 在cpu模式下打包可执行文件:
    pyinstaller -F run_t.py
    
  • 在gpu模式下打包可执行文件:
    pyinstaller -F run_t.py --add-binary "D:/ProgramData/Anaconda3_data/envs/deploy/Lib/site-packages/onnxruntime/capi/onnxruntime_providers_cuda.dll;./onnxruntime/capi" --add-binary "D:/ProgramData/Anaconda3_data/envs/deploy/Lib/site-packages/onnxruntime/capi/onnxruntime_providers_shared.dll;./onnxruntime/capi"
    

详细的过程和结果此前已经讲解过了,可以查看博主的博文【快速部署ONNX模型】。


总结

尽可能简单、详细的介绍ONNX模型多线程快速部署过程。

更多推荐

Mysql的基本查询练习

目录一、Create1.1单行数据+全列插入1.2多行数据+指定列插入1.3插入否则更新1.4替换二、Retrieve2.1全列查询2.2指定列查询2.3查询字段为表达式2.4为查询结果指定别名2.5结果去重2.6where条件2.6NULL的查询2.7结果排序三、Update四、Delete五、插入查询结果六、聚合函

学科知识图谱学习平台项目 :技术栈Java、Neo4j、MySQL等超详细教学

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实战掌握技能,助力用户更好利用CSDN平台,自主完成项目设计升级,提升自身的硬实力。专栏订阅:项目大全提升自身的硬实力[专栏详细介绍:项目设计

ARMv7处理器

本文档介绍常见的ARM架构,包括Cortex-A5,Cortex-A7,Cortex-A8,Cortex-A9,Cortex-A15.常见的术语DFT(DesignforTest),为了增强芯片可测性而采用的一种设计方法APB(AdvancedPeripheralBus),是一种低速外设总线接口,通常用于将外部设备(如

2.策略模式

UML图代码main.cpp#include"Strategy.h"#include"Context.h"voidtest(){Context*pContext=nullptr;/*StrategyA*/pContext=newContext(newStrategyA());pContext->contextInter

如何理解JavaScript定时器的4种写法-附带面试题讲解

在JavaScript里,我们已经会使用一些原生提供的方法来实现需要延时执行的操作代码,比如很多在线时钟的制作,图片轮播的实现,还有一些广告弹窗,但凡可以自动执行的东西,都是可以和定时器有关的。今天就来和大家分享一下,关于我们在JavaScript里经常会使用到的定时器方法在JavaScript里,我们要学习四个定时器

基于GBDT+Tkinter+穷举法按排队时间预测最优路径的智能导航推荐系统——机器学习算法应用(含Python工程源码)+数据集(三)

目录前言总体设计系统整体结构图系统流程图运行环境Python环境Pycharm环境Scikit-learnt模块实现1.数据预处理2.客流预测3.百度地图API调用4.GUI界面设计1)手绘地图导入2)下拉菜单设计3)复选框设计4)最短路径结果输出界面设计5)智能推荐结果输出设计6)界面展示5.路径规划6.智能推荐相关

【C++】内联函数 ③ ( C++ 编译器 不一定允许内联函数的内联请求 | 内联函数的优缺点 | 内联函数 与 宏代码片段对比 )

文章目录一、内联函数不一定成功1、内联函数的优缺点2、C++编译器不一定允许内联函数的内联请求3、是否内联决定权在编译器手中二、内联函数与宏代码片段对比1、内联函数2、宏代码片段一、内联函数不一定成功1、内联函数的优缺点"内联函数"不是在运行时调用的,"内联函数"是编译时将函数体对应的CPU指令直接嵌入到调用该函数的地

【表格插入小计行】el-table表格,数组对象中根据某字段插入小计行计算数据

前言功能解释:遇到的一个需求,是表格的tabledata数组。里面有科室医生还有很多消费指标等数据。然后需要我排序后把科室放在一起。然后在每个科室下面添加一行数据,是小计行。用于计算上面相同科室的所有数据汇总。然后最下面再来个合计行,加上所有的小计。效果图刚排序后是这样的表格样子,数据是我模拟的然后插入小计行后是这样的

JavaScript事件流:深入理解事件处理和传播机制

🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!目录引言1.事件流的发展流程1.1传统的DOM0级事件1.2DOM2级事件和addEventListener方法1.3W3CDOM3级事件1.4React与VirtualDOM2.事件流的属性2.1事件捕获阶段2.

《从菜鸟到大师之路 Redis 篇》

《从菜鸟到大师之路Redis篇》(一):Redis基础理论与安装配置Nosql数据库介绍是一种非关系型数据库服务,它能解决常规数据库的并发能力,比如传统的数据库的IO与性能的瓶颈,同样它是关系型数据库的一个补充,有着比较好的高效率与高性能。专注于key-value查询的redis、memcached、ttserver。

transformer大语言模型(LLM)部署方案整理

说明大模型的基本特征就是大,单机单卡部署会很慢,甚至显存不够用。毕竟不是谁都有H100/A100,能有个3090就不错了。目前已经有不少框架支持了大模型的分布式部署,可以并行的提高推理速度。不光可以单机多卡,还可以多机多卡。我自己没啥使用经验,简单罗列下给自己备查。不足之处,欢迎在评论区指出。框架名称出品方开源地址Fa

热文推荐