摘要:在本教程中,详细介绍了如何将ONNX模型部署为独立的可执行文件。从环境准备开始,介绍了ONNX Runtime及其GPU版本的安装,确定CUDA和cuDNN版本的兼容性。给出了使用ONNX Runtime加载和推理模型,处理输入和输出数据的脚本示例。最后使用PyInstaller将推理脚本打包为可执行文件,并提供了应对常见错误的解决方案。
引言
在深度学习领域,主流框架如PyTorch和TensorFlow尽管功能强大,但它们的库往往庞大且复杂,这给模型的部署和跨平台应用带来了麻烦。为了解决这一问题,ONNX(Open Neural Network Exchange)应运而生。ONNX是由Facebook和微软最初开发的一个社区项目,目前由IBM、亚马逊(AWS)、谷歌等多家组织共同支持和开发ONNX项目。ONNX旨在创建一种开放的文件格式,使机器学习模型能够在不同的AI框架和硬件之间通用。
通过ONNX,可以轻松地在不同框架之间转换模型。例如,一个在PyTorch中训练的深度学习模型可以导出为ONNX格式,然后轻松导入到TensorFlow中,或者PyTorch转到ONNX。
除了在框架间的转换外,ONNX模型还可以与ONNX Runtime一起使用。ONNX Runtime是一个兼容多个框架的跨平台加速器,支持PyTorch、TensorFlow、TFLite、scikit-learn等框架ONNX Runtime。通过利用硬件特定的加速功能,ONNX Runtime优化了ONNX模型的执行效率,使模型能够在各种硬件平台(包括CPU、GPU和专用加速器)上高效运行ONNX Runtime硬件支持。
为了进一步提升模型的可移植性,并使其能够在没有Python环境的系统上独立运行,将模型打包为可执行文件是一个有效的解决方案。在本博客教程中,我们将使用PyInstaller,这是一款能够将Python脚本打包为独立可执行文件的工具。通过这一过程,将能够简化模型的发布和应用,使其在多种环境中轻松运行。
1. 环境准备
首先说明一下,如果不使用GPU,只是用CPU运行那么可以不太关注CUDA的版本,这种情况基本坑就比较少了。但在使用ONNX模型进行GPU推理时,选择正确的ONNX Runtime版本并确保与CUDA和cuDNN的兼容性是最重要的。博主见过很多到后面打包出各种错误折腾的,大多是在版本兼容上没有注意,埋下地雷而不自知。
1.1 安装ONNX Runtime-GPU的注意事项
ONNX Runtime的GPU加速依赖于CUDA和cuDNN,因此这些库的版本必须正确匹配。
-
CUDA版本兼容性:
- ONNX Runtime是基于特定的CUDA版本编译的。根据Nvidia CUDA的次版本兼容性原则,使用CUDA 11.8编译的ONNX Runtime版本兼容任何CUDA 11.x版本,而使用CUDA 12.x编译的ONNX Runtime版本则兼容任何CUDA 12.x版本【参考官方文档】ONNX Runtime GPU执行提供程序。
-
cuDNN版本兼容性:
- ONNX Runtime与cuDNN的版本需要精确匹配。例如,使用cuDNN 8.x编译的ONNX Runtime不兼容cuDNN 9.x,反之亦然。因此,需要根据CUDA和cuDNN的主版本来选择合适的ONNX Runtime包。例如,PyTorch 2.3使用cuDNN 8.x,而PyTorch 2.4或更高版本则使用cuDNN 9.x【参考官方文档】ONNX Runtime与cuDNN兼容性。
以上的话简单说起来就是,onnxruntime对于CUDA、cuDNN的支持是按版本来的。如下图所示,基本上onnxruntime1.18支持的只有CUDA11.8及以上版本,如果CUDA是11.6,那么最好装onnxruntime-1.14版本。
这个表格来自ONNX Runtime官方文档,所以建议像下面的安装方式。
-
CUDA 11.x 用户:
- 如果使用CUDA 11.8,建议安装ONNX Runtime 1.18.x版本,兼容cuDNN 8.x。
- 对于CUDA 11.6,建议使用ONNX Runtime 1.14或1.13版本。
-
CUDA 12.x 用户:
- 如果使用CUDA 12.x和cuDNN 9.x,安装ONNX Runtime 1.18.1。
- 如果使用CUDA 12.x和cuDNN 8.x,选择ONNX Runtime 1.18.0或1.17.x。
选择跟你系统中CUDA和cuDNN版本匹配的ONNX Runtime版本,以确保最佳兼容性和性能。
1.2 查看CUDA和cuDNN的版本
在Windows上,可以使用以下方法来查看CUDA和cuDNN的版本:
-
使用命令行:
打开命令提示符(Cmd)并输入以下命令:nvcc --version
这将显示CUDA编译器驱动程序的版本信息。我这里的CUDA版本是11.8(目前服务器用的比较多的型号,兼容很多框架)
在Windows的命令提示符中,可以用下面的命令查看cuDNN的版本系列:
for /d %i in ("C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v*") do findstr /C:"CUDNN_MAJOR" "%i\include\cudnn_version.h"
此命令将遍历C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\
目录下的所有版本目录,并在每个版本目录中的cudnn_version.h
文件中查找CUDNN_MAJOR
的定义。可以从图上看到我的CUDNN版本是8.x的,你可以核对一下自己的版本是否正确。
1.3 安装ONNX Runtime-GPU
为了成功运行和部署ONNX模型,首先需要正确安装ONNX Runtime。这里我们先用conda创建一个名为deploy_onnx的Python环境:
# name 环境名、3.x Python的版本
conda create -n deploy_onnx python==3.10
# 激活环境
activate deploy_onnx
根据硬件配置和需求,可以选择安装CPU版本或GPU版本的ONNX Runtime。以下是参考官网的详细安装步骤:
1. 安装 ONNX Runtime CPU 版本
如果只需要在CPU上运行ONNX模型,可以安装CPU版本的ONNX Runtime。该版本适用于没有GPU或者不需要GPU加速的情况。
pip install onnxruntime
2. 安装 ONNX Runtime GPU 版本
如果需要利用GPU进行加速推理,需要安装GPU版本的ONNX Runtime。根据所使用的CUDA版本,安装过程有所不同。
(1)安装 ONNX Runtime GPU (CUDA 11.x):如果你的系统使用CUDA 11.x,默认推荐使用CUDA 11.8进行安装。这个版本的ONNX Runtime已经预编译好,并且兼容CUDA 11.8。
pip install onnxruntime-gpu==1.18.0
(2)安装 ONNX Runtime GPU (CUDA 12.x):如果使用的是CUDA 12.x,并且cuDNN是8.x的,那么你可以安装onnxruntime-gpu==1.18.0
。需要使用特定的安装源来获取支持CUDA 12.x的ONNX Runtime版本:
pip install onnxruntime-gpu==1.18.0 --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
- 如果使用的是CUDA 12.x,并且cuDNN是9.x的,那么你可以安装
onnxruntime-gpu==1.18.1
或更高版本。需要使用特定的安装源来获取支持CUDA 12.x的ONNX Runtime版本:
pip install onnxruntime-gpu==1.18.1 --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
3. 验证安装
安装完成后,可以通过以下的Python命令检查ONNX Runtime是否安装成功,并确认其是否正确配置:
import onnxruntime as ort
print(ort.get_device()) # 输出 'CPU' 或 'GPU' 以确认设备
如果输出为’GPU’,则表明ONNX Runtime已正确配置为使用GPU进行推理。
4. 其他依赖库安装
在部署和打包ONNX模型时,还需要安装其他一些依赖库。以下是一些常见的库安装步骤:
-
安装PyInstaller:PyInstaller是用于将Python脚本打包成独立可执行文件的工具,适合在没有Python环境的系统上运行。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyInstaller
-
安装Pillow:Pillow是一个强大的图像处理库,许多与图像相关的任务都会用到它。因为项目涉及图像处理,所以需要安装这个库。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow
这些依赖库的安装可以根据项目的具体需求来选择,确保所有必要的库都已安装,这样才能顺利进行模型的部署和打包。
2. ONNX 模型部署示例
在成功安装了ONNX Runtime及其他必要的依赖库之后,接下来我们将介绍如何加载ONNX模型并进行推理。以下是一个完整的代码示例,通过该示例,可以搞定使用ONNX Runtime加载模型、进行图像预处理、执行推理并处理推理结果。
1. 导入必要的库
首先,我们需要导入一些关键的Python库,这些库包括onnxruntime
用于加载和运行ONNX模型,numpy
用于数据处理,cv2
(OpenCV)用于图像处理,os
用于文件路径操作,以及time
用于计算推理时间。
import os
import onnxruntime as ort
import numpy as np
import time
import cv2
2. 初始化模型路径和类别标签
我们需要指定ONNX模型的路径以及类别标签。这些标签用于标识模型可以检测到的物体类别。在这个例子中,我们使用了YOLOv8模型,并定义了一组常见物体的类别标签。
model_path = 'yolov8n.onnx'
names = [
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
# (此处省略部分类别以节省篇幅)
"vase", "scissors", "teddy bear", "hair drier", "toothbrush"
]
params = {
'conf_thres': 0.5, # 置信度阈值
'iou_thres': 0.4, # 非极大值抑制(NMS)的IoU阈值
}
3. 加载并验证输入图像路径
为了确保用户输入的图像路径是有效的,我们循环检查用户输入的路径,直到输入有效路径为止。
while True:
input_image_path = input("请输入图像文件的路径: ")
if os.path.isfile(input_image_path):
break
else:
print("文件路径不存在,请重新输入。")
output_image_path = 'output.jpg'
4. 加载ONNX模型
根据设备类型(CPU或GPU),我们选择合适的执行提供程序(Execution Provider)来加载ONNX模型。在这里,我们首先检查设备类型,并使用CUDAExecutionProvider
加载GPU模型或CPUExecutionProvider
加载CPU模型。
providers = ["CUDAExecutionProvider"] if ort.get_device() == "GPU" else ["CPUExecutionProvider"]
session = ort.InferenceSession(model_path, providers=providers)
model_inputs = session.get_inputs()
input_width = model_inputs[0].shape[2]
input_height = model_inputs[0].shape[3]
5. 预处理输入图像
为了将图像输入到模型中,需要对其进行预处理。主要步骤包括:读取图像、调整大小、归一化、变换维度等。
img = cv2.imread(input_image_path)
img_height, img_width = img.shape[:2]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_resized = cv2.resize(img_rgb, (input_width, input_height))
img_normalized = img_resized / 255.0
img_transposed = np.transpose(img_normalized, (2, 0, 1))
img_expanded = np.expand_dims(img_transposed, axis=0).astype(np.float32)
6. 模型推理
加载和预处理完图像后,我们将图像输入到ONNX模型中进行推理,并记录推理所需的时间。
start = time.time()
prediction = session.run(None, {model_inputs[0].name: img_expanded})
end = time.time()
print("Inference Time: {:.4f} seconds".format(end - start))
7. 后处理推理结果
推理完成后,我们需要对模型输出进行后处理。主要步骤包括:提取边界框、筛选有效检测结果、以及应用非极大值抑制(NMS)以去除重复的检测。
outputs = np.transpose(np.squeeze(prediction[0]))
rows = outputs.shape[0]
boxes = []
scores = []
class_ids = []
x_factor = img_width / input_width
y_factor = img_height / input_height
for i in range(rows):
classes_scores = outputs[i][4:]
max_score = np.amax(classes_scores)
if max_score >= params['conf_thres']:
class_id = np.argmax(classes_scores)
x, y, w, h = outputs[i][0], outputs[i][1], outputs[i][2], outputs[i][3]
left = int((x - w / 2) * x_factor)
top = int((y - h / 2) * y_factor)
width = int(w * x_factor)
height = int(h * y_factor)
class_ids.append(class_id)
scores.append(max_score)
boxes.append([left, top, width, height])
indices = cv2.dnn.NMSBoxes(boxes, scores, params['conf_thres'], params['iou_thres'])
8. 绘制和保存检测结果
最终,我们将检测结果绘制在原始图像上,并将处理后的图像保存到指定路径。
print("Detection Results:")
for i in indices.flatten():
box = boxes[i]
score = scores[i]
class_id = class_ids[i]
print(f"Class: {names[class_id]}, Score: {score:.2f}, Box: {box}")
for i in indices.flatten():
box = boxes[i]
score = scores[i]
class_id = class_ids[i]
left, top, width, height = box
cv2.rectangle(img, (left, top), (left + width, top + height), (255, 178, 102), 2)
cv2.putText(img, f"{names[class_id]}: {score:.2f}", (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 178, 102), 2)
cv2.imshow("Detection Results", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite(output_image_path, img)
print(f"Output saved to {output_image_path}")
以上代码是使用ONNX Runtime加载并推理ONNX模型的完整流程。运行一下效果如下:
这样检测出来的图像结果如下:
3. 将ONNX脚本打包为独立的可执行文件
在成功开发并测试了ONNX模型推理脚本后,接下来我们将考虑如何将该脚本打包为独立的可执行文件(.exe
),以便在没有Python环境的系统上运行。可以使用PyInstaller
工具来完成这项任务。为了确保打包后的可执行文件能够正常运行,这里尽量减少使用重度依赖库,并手动添加所需的动态链接库(DLL)。
1. 准备工作
首先,确保脚本尽可能少地依赖重度的第三方库。上面我们已经展示了如何编写一个依赖较少的ONNX模型推理脚本。接下来,我们将使用PyInstaller
来打包该脚本。
2. 创建 .spec 文件
PyInstaller
通过使用 .spec
文件来定制打包过程。我们可以指定哪些文件、库、以及DLL需要包含在打包的可执行文件中。以下是一个完整的 .spec
文件,我命名为了onnx_to_exe.spec,用于打包我们的ONNX推理脚本。
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['onnx_to_exe.py'], # 你的要打包的Python脚本名称
pathex=['I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages'], # 指定Python环境的路径
binaries=[], # 包含CUDA和onnxruntime的DLL文件
datas=[
('I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_cuda.dll','onnxruntime\\capi'),
('I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_shared.dll','onnxruntime\\capi'),
('I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_tensorrt.dll', 'onnxruntime\\capi'),
('C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.8\\bin\\zlibwapi.dll', 'onnxruntime\\capi'),
],
hiddenimports=['cv2', 'onnxruntime', 'numpy', 'numpy.core._multiarray_umath'], # 手动指定隐式导入的库
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[], # 可以在此排除不需要的库
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='onnx_to_exe', # 生成的可执行文件的名称
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True, # 如果需要使用UPX压缩可执行文件
upx_exclude=[],
runtime_tmpdir=None,
console=True, # 如果需要在控制台运行该程序
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
在使用PyInstaller
打包ONNX模型推理脚本时,要正确指定DLL文件的路径,没有指定在后面运行exe会出问题。以下是如何找到这些DLL文件路径并在.spec
文件中进行修改的简要说明:
onnxruntime_providers_cuda.dll
- 路径:该文件通常位于的Python环境中
site-packages
目录下的onnxruntime
子目录中。 - 查找方法:在命令行中导航到Python环境的
site-packages
目录,然后进入onnxruntime/capi
文件夹,即可找到onnxruntime_providers_cuda.dll
文件。- 示例路径:
I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_cuda.dll
- 示例路径:
onnxruntime_providers_shared.dll
- 路径:与
onnxruntime_providers_cuda.dll
位于同一目录,通常在onnxruntime/capi
文件夹中。 - 查找方法:与上述方法相同,查找
site-packages/onnxruntime/capi
目录下的onnxruntime_providers_shared.dll
文件。- 示例路径:
I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_shared.dll
- 示例路径:
onnxruntime_providers_tensorrt.dll
- 路径:同样位于
site-packages/onnxruntime/capi
目录中。 - 查找方法:进入
onnxruntime/capi
目录查找onnxruntime_providers_tensorrt.dll
文件。- 示例路径:
I:\\CondaEnv\\gpu\\yolov8_gpu\\Lib\\site-packages\\onnxruntime\\capi\\onnxruntime_providers_tensorrt.dll
- 示例路径:
zlibwapi.dll
(CUDA相关文件)
- 路径:该文件通常位于CUDA安装目录下的
bin
文件夹中。 - 查找方法:导航到CUDA安装目录(如
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8
),然后进入bin
文件夹查找zlibwapi.dll
文件。- 示例路径:
C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.8\\bin\\zlibwapi.dll
- 示例路径:
请你按照上面的介绍修改你的spec文件中的路径!
3. 打包脚本
在创建好 .spec
文件后,可以使用PyInstaller
来执行打包过程。使用以下命令来生成可执行文件:
pyinstaller onnx_to_exe.spec
这个命令会读取 onnx_to_exe.spec
文件中的配置,并生成独立的可执行文件。默认情况下,生成的文件会放置在 dist
文件夹中。
我们将onnx模型文件和jpg文件都复制到dist文件夹中,然后双击onnx_to_exe.exe
运行:
此时运行如下的界面
这样表明打包和运行成功了。这里注意的是,我们添加了多个dll文件,是为了在其他没有进行配置的电脑上仍然能够使用GPU运行。你可以不打包其中的dll文件,即在spec文件中删除那些dll的路径,这样打包后的exe文件会比较小。具体在其他电脑上运行的情况,根据是否有提示问题再解决。
4. 常见问题解决
(1)解决Could not load library cudnn_cnn_infer64_8.dll. Error code 193
问题
在使用onnxruntime-gpu
进行推理时,有时会遇到以下错误:
Could not load library cudnn_cnn_infer64_8.dll. Error code 193
也就是下面图中出现的:
这个错误通常是由于onnxruntime-gpu
在运行时依赖的cudnn_cnn_infer64_8.dll
文件无法加载,而这个文件需要zlibwapi.dll
库的支持。对于使用CUDA 11的版本,cuDNN
特别需要依赖这个库。然而,许多教程在安装cuDNN
时并没有提及要补充安装zlibwapi.dll
,因此可能导致这个问题的出现。
(2)下载并安装zlibwapi.dll
要解决这个问题,首先需要确保系统中安装了正确的zlibwapi.dll
文件。以下是下载和安装步骤:
-
下载
zlibwapi.dll
(64位版本):- 请从以下链接之一下载适用于64位系统的
zlibwapi.dll
:-
zlib for Windows - 在页面底部的“Related External Links”部分,找到“zlib for Windows 9x/NT/2000/XP/2003 (DLL version, plus related utilities)”的链接下载。找不到的话,也可以在DLL‑files.com下载。
-
直接下载:
- 64位版本下载
- 32位版本下载(注意,如果使用的是32位系统,需要使用此链接)
-
- 请从以下链接之一下载适用于64位系统的
-
解压并复制
zlibwapi.dll
:- 下载完成后,解压文件,将其中的
zlibwapi.dll
(64位系统请从dll_x64
文件夹中获取,32位系统请从dll32
文件夹中获取)复制到以下目录:
- 下载完成后,解压文件,将其中的
- 复制到
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin
(请根据实际安装的CUDA版本号调整路径)
2. 复制到C:\Windows\System32
(可以确保系统全局可访问)
- 添加系统环境变量:
- 确保将
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin
路径添加到系统的Path
环境变量中。这样可以确保系统在运行时能够找到并加载zlibwapi.dll
。
- 确保将
这样再次运行exe应该就能解决上面那个问题了。如果确认zlibwapi的操作和复制没有问题,还是出现这个问题,那么就可能是CUDA和cuDNN的版本没有匹配了,按照前面说的版本检查方法再查看一下。
总结
ONNX模型的高效部署和独立运行对于生产环境中的深度学习应用还是很重要得。通过本教程,可以掌握以下关键步骤:
-
环境准备:要正确安装ONNX Runtime,并确保CUDA和cuDNN版本的兼容性,尤其是在使用GPU加速时的细节处理。
-
模型部署:通过实际的代码示例,加载ONNX模型、进行推理,并对结果进行后处理。这个过程不仅涵盖了代码的编写,还涉及如何优化推理效率。
-
打包成可执行文件:使用PyInstaller将Python脚本打包成独立的可执行文件,以便在没有Python环境的系统上运行。博主定制了
.spec
文件,并解决在打包过程中可能遇到的问题。 -
问题解决:针对常见的运行时错误,如
Could not load library cudnn_cnn_infer64_8.dll. Error code 193
,我们提供了详细的解决方案,包括如何正确配置和安装所需的DLL文件。
通过以上这些步骤,将能够顺利地将ONNX模型从开发环境部署到生产环境,并在多种硬件平台上实现高效运行。在开发应用程序还是在优化深度学习模型的运行效率,上面这些技巧和工具希望有所参考。