【ディープラーニング】ONNXモデルCPUマルチスレッド高速導入【基礎】
ヒント: このブロガーは、著名人からの多くのブログ投稿を選択し、それらが効果的であるか個人的にテストしました。彼は自分のメモを共有し、一緒に研究して議論するようみんなに勧めています。
記事ディレクトリ
序文
前回のコンテンツでは、CPU [ Pytorch2ONNX ] と GPU [ Pytorch2ONNX ]の 2 つのモードで Pytorch モデルを ONNX 形式に変換するプロセスをできるだけ簡単かつ詳細に紹介しましたが、このブログ投稿では ONNX モデル ベースのデプロイメントについてさらに説明します。私自身の学習とニーズに基づいて。onnx モデル ブロガーは、パッケージ化とデプロイメントに PyInstaller を使用します。PyInstaller は、Python スクリプトを独立した実行可能ファイルにパッケージ化するために使用されるツールです。最も基本的な使用法は [入門]で説明されています。以前、ブロガーは[ONNX モデルの迅速な展開] で、onnx モデルを CPU モードおよび GPU モードの実行可能ファイルにパッケージ化するチュートリアルを紹介しましたが、このブログ投稿では、マルチ スレッドを使用して ONNX モデルを CPU モードで迅速に展開する方法をさらに紹介します。
シリーズ学習ディレクトリ:
[CPU] Pytorch モデルから ONNX モデルへのプロセスの詳細説明
[GPU] Pytorch モデルから ONNX 形式へのプロセスの詳細説明
[ONNX モデル] 迅速なデプロイメント
[ONNX モデル] マルチスレッドの迅速なデプロイメント
[ONNX モデル] Opencv Nxに電話をかける
パッケージング環境を構築する
冗長なサードパーティのライブラリやモジュールを使用せずに純粋な小規模な 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 の組み込みモジュールには、スレッドとスレッドの 2 つの組み込みモジュールがあります。スレッドはソース モジュールであり、比較的低レベルのモジュールです。スレッドは拡張モジュールです。スレッドをカプセル化し、より便利に使用できます。同時実行テストは、スレッド モジュールを使用して完了できます。
基本チュートリアル
python3.x のスレッド モジュールを通じて新しいスレッドを作成するには、threading.Thread (Target=executable Method) を通じて実行可能メソッド (またはオブジェクト) を Thread オブジェクトに渡す方法と、threading.Thread を継承してサブクラスを定義する方法の 2 つがあります。 run() メソッドをオーバーライドします。新しいスレッドを作成する 2 つの方法の例を以下に示します。読者はこれらを実行して理解を深めることができます。
- 通常の作成方法: 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 モデルのマルチスレッド同時実行性
ブロガーは、基本チュートリアルの通常の作成方法で、対象関数として推論処理を指定し、スレッドオブジェクトを作成して対象関数を指定するという通常の作成方法で、同じ推論セッションを複数のスレッドに割り当て、複数のスレッドに割り当てます。同じスレッドを共有します。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 モデルのクイック展開] を参照してください。画像の数が多い場合、マルチスレッドの実行速度は以前の 2 倍以上になります。
要約する
ONNX モデルのマルチスレッドの迅速な展開プロセスを、できるだけシンプルかつ詳細な方法で紹介します。