简介:这是 onnxruntime 1.23.1 官方发布的 Windows x64 平台 CPU 版本预编译包,面向需要在本地的开发环境中直接集成模型推理能力的算法工程师与后端开发者。由于官方渠道下载不稳定,作者将完整压缩包备份于此,方便离线或受限网络下快速获取稳定版本。压缩包内共 26 个文件,总大小 74.48MB,主要内容为头文件、动态链接库和静态导入库,并附调试符号、项目说明、隐私声明、第三方许可及版本信息,完整覆盖接口声明、工程链接、运行时依赖等开发要素。文件按 include 与 lib 等标准目录清晰组织,可直接对接现有项目,用于图像分类、目标检测、自然语言处理等典型模型场景的本地 CPU 推理,省去自行编译源码和逐项配置依赖的繁琐步骤。目前已有 48 人学习/下载,是一份适合快速搭建推理环境的即用型资源。
1. onnxruntime-win-x64-1.23.1.zip 是什么:一个能让 ONNX 模型在 Windows 上跑起来的关键压缩包
如果你手头有一个训练好的 PyTorch 或 TensorFlow 模型,想把它塞进 Windows 桌面程序里做实时推理,大概率会下载这个onnxruntime-win-x64-1.23.1.zip。它看起来就是个普通 zip 包,解压后却能直接给你一整套 ONNX Runtime 的运行时环境:动态链接库、导入库、头文件,以及几个必要的工具。它能解决的核心问题是——把 ONNX 模型高效地跑在 Windows x64 机器上,省去自己编译整个推理引擎的折腾。适合正在做模型部署、桌面端 AI 工具、或者刚接触 ONNX 生态的工程师。这篇文章不聊概念,直接从你下载这个 zip 之后的第一步讲起,一直讲到最后上线验证,把常见坑都踩一遍。
2. 先看懂 onnxruntime 的运行时结构与版本选择:为什么 win-x64 1.23.1 值得用?
2.1 onnxruntime 的核心组件:会话、环境与执行提供程序
拿到 zip 包之前,先要明白 onnxruntime 运行时是怎么组织的。整个架构里,最核心的是三个东西:Ort::Env环境对象、Ort::SessionOptions选项对象、以及Ort::Session会话对象。环境负责管理线程池和日志级别;会话选项控制图优化、内存分配、执行提供程序等;会话则是加载模型并执行推理的实际载体。执行提供程序(Execution Provider,简称 EP)是 onnxruntime 的性能关键——同一个模型可以在 CPU、CUDA、TensorRT、DirectML 等不同 EP 上运行,而win-x64这个 zip 包里默认编译的是 CPU EP 和 DirectML EP 的支持,这也是它在 Windows 上尤其方便的原因:即使没有 NVIDIA 显卡,也能通过 DirectML 调用 GPU。
理解这一点对后续使用非常重要。很多人把 onnxruntime 当成一个黑匣子,直接InferenceSession一加载就开始用,遇到性能瓶颈不知道往哪个方向调。实际上,调参的入口就藏在这几个对象里:线程数在Ort::SessionOptions中设置,图优化级别也在其中;而 EP 的选择则决定了Session创建后底层走哪条执行路径。
2.2 win-x64 zip 包里到底装了哪些文件:dll、lib、头文件的作用
解压onnxruntime-win-x64-1.23.1.zip后,你会看到一个典型的二进制发布目录:lib文件夹下有onnxruntime.dll、onnxruntime.lib,以及静态库onnxruntime_static.lib;include文件夹下是整套 C/C++ 头文件,核心是onnxruntime_cxx_api.h和onnxruntime_c_api.h;此外还有bin文件夹,里面放着几个辅助工具,比如模型转换和性能评测用的命令行程序。
这里有个容易混淆的点:onnxruntime.dll是运行时动态库,程序跑起来后加载的就是它;onnxruntime.lib是导入库,给 C++ 链接器用的,它不包含实际代码,只是告诉链接器“这些符号在 dll 里”。如果你选择静态链接,才会用到onnxruntime_static.lib,但静态链接的坑更多,后面会专门讲。另一个值得注意的是,zip 包里还有一个onnxruntime_providers_shared.dll,它是多个 EP 共享的底层库,某些场景下如果只拷贝了主 dll 而漏了这个,运行时就会报找不到提供程序。
2.3 1.23.1 版本的实际体验:升级点与值得注意的变化
版本号 1.23.1 属于 ONNX Runtime 的 1.23.x 稳定序列。从我实际部署的体验来看,这个版本最直观的变化是 API 保持稳定,没有破坏性改动,也就是说,如果你从 1.16 或 1.18 升级过来,现有代码基本不需要改,只需要替换 zip 包并重新链接。另一个感受明显的是,在 CPU 推理上,1.23 系列对算子的图优化做得更激进了一点,特别是在 Transformer 结构的模型上,某些场景的推理延迟能降低 5% 到 15%,但这跟模型结构、输入长度都有关系,不是所有情况都包优化。
不过,我建议你在决定升级前先做一个对照测试:把旧版 zip 里的 dll 和新版 dll 分别加载,跑同一个 ONNX 模型,对比输出张量的最大误差。ONNX Runtime 在不同版本间对算子实现的数值细节可能会有微小差异,对于浮点模型,这种差异通常在1e-6量级以内,但如果是量化模型,差异可能会稍大。这个测试很重要,能帮你避免“升级后发现线上结果跟原来不一样”这种尴尬情况。
3. 把 onnxruntime-win-x64-1.23.1.zip 配置到工程里:解压、CMake 链接与第一个推理程序
3.1 解压与目录规划:这个 zip 不是解压就能直接用
很多人以为把 zip 包解压到项目里就完事了,实际上还有两个必要步骤:一是把解压后的目录放到一个稳定的路径下,不要放在项目临时目录里,因为之后 IDE、链接器、运行时都需要引用它;二是明确区分“开发时需要的文件”和“运行时需要的文件”。开发时需要include和lib文件夹,而最终部署给用户时,只需要bin下的几个 dll,以及你模型文件本身。
我一般的做法是在项目根目录下建一个third_party/onnxruntime文件夹,把 zip 包解压进去,目录结构变成:
third_party/onnxruntime/ ├── include/ ├── lib/ └── bin/然后在 CMake 里通过ONNXRUNTIME_ROOT变量指向这个目录。这样不管换机器还是换版本,只需要替换整个文件夹即可。如果你在 Windows 上想用命令行解压,PowerShell 下这样做:
Expand-Archive -Path .\onnxruntime-win-x64-1.23.1.zip -DestinationPath .\third_party\onnxruntime注意Expand-Archive的参数:-Path是 zip 包的路径,-DestinationPath是解压目标目录。如果目标目录已存在,会报错,所以先确认没有同名目录。解压完成后,检查一下include下是否有onnxruntime_cxx_api.h,这是最关键的头文件,缺了它后面编译直接失败。
3.2 用 CMake 最小工程链接 onnxruntime 动态库
这里给一个最简的 CMakeLists.txt,它可以编译出一个能加载 ONNX 模型并做一次推理的控制台程序。假设你的源码文件是main.cpp,CMake 配置如下:
cmake_minimum_required(VERSION 3.16) project(ort_demo) set(CMAKE_CXX_STANDARD 17) set(ONNXRUNTIME_ROOT "third_party/onnxruntime") include_directories(${ONNXRUNTIME_ROOT}/include) link_directories(${ONNXRUNTIME_ROOT}/lib) add_executable(ort_demo main.cpp) target_link_libraries(ort_demo PRIVATE onnxruntime)这段配置的关键有两点:第一,link_directories指向lib文件夹,让链接器能找到onnxruntime.lib;第二,target_link_libraries里的onnxruntime是导入库文件名去掉扩展名后的名字,它会把依赖信息写入生成的可执行文件,运行时 Windows 会去可执行文件同目录或系统路径里找onnxruntime.dll。如果直接编译但运行时提示找不到 dll,说明你把 exe 和 dll 放到了不同目录,最简单的解决方式是把third_party/onnxruntime/bin里的所有 dll 复制到 exe 旁边。
接下来是main.cpp,用 C++ API 加载一个 ONNX 模型并跑一次前向:
#include <onnxruntime_cxx_api.h> #include <vector> #include <iostream> int main() { Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "demo"); Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(4); session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); const wchar_t* model_path = L"model.onnx"; Ort::Session session(env, model_path, session_options); Ort::AllocatorWithDefaultOptions allocator; Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); std::vector<int64_t> input_shape{1, 3, 224, 224}; std::vector<float> input_data(1 * 3 * 224 * 224, 0.5f); Ort::Value input_tensor = Ort::Value::CreateTensor<float>( memory_info, input_data.data(), input_data.size(), input_shape.data(), input_shape.size()); const char* input_names[] = {"input"}; const char* output_names[] = {"output"}; std::vector<Ort::Value> output_tensor = session.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor, 1, output_names, 1); auto* output = output_tensor[0].GetTensorMutableData<float>(); std::cout << "first output: " << output[0] << std::endl; return 0; }这段代码做了几件事:先创建环境和会话选项,设定线程数为 4,开启全部图优化;然后加载model.onnx模型;接着用 CPU 分配器创建一个形状为{1,3,224,224}的输入张量,值全部初始化为 0.5;最后调用session.Run执行推理,拿到输出张量并打印第一个值。参数说明:Ort::Env的第一个参数是日志级别,ORT_LOGGING_LEVEL_WARNING表示只输出警告和错误,避免调试时刷屏;SetIntraOpNumThreads(4)控制算子内并行线程数,不是越大越好,后面避坑章会细说;SetGraphOptimizationLevel有三个可选值,ORT_DISABLE_ALL、ORT_ENABLE_BASIC、ORT_ENABLE_ALL,生产环境一般用ORT_ENABLE_ALL。
3.3 在 Python 侧调用 zip 包里的 onnxruntime:环境变量与 DLL 搜索顺序
如果你是 Python 用户,不需要手动处理 CMake,但同样会遇到动态库搜索问题。用pip install onnxruntime安装的是预编译 wheel 包,它自带 dll;但如果你特意下载了onnxruntime-win-x64-1.23.1.zip想手动管理版本,可以通过设置PATH环境变量让 Python 找到 dll:
$env:PATH = "D:\third_party\onnxruntime\bin;" + $env:PATH python -c "import onnxruntime; print(onnxruntime.__version__)"Python 侧使用本身很简单:
import onnxruntime as ort import numpy as np session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name output_name = session.get_outputs()[0].name input_array = np.random.rand(1, 3, 224, 224).astype(np.float32) result = session.run([output_name], {input_name: input_array}) print(result[0].shape)这里的核心陷阱在于providers参数。如果你不传,onnxruntime 会按默认顺序尝试所有可用 EP,默认顺序通常是 CPU 优先。但如果你手动指定["CPUExecutionProvider"],必须确保前面没有拼错字符串,否则会抛异常。session.run的第一个参数是输出名列表,这里传入[output_name]表示只要这一个输出,比返回所有输出更省内存。np.random.rand生成的是[0,1)均匀分布的随机数,如果模型对输入数值范围敏感,记得改成真实数据的预处理结果。
4. onnxruntime 部署避坑与排查:从加载失败到结果不对的实战记录
4.1 加载 dll 失败:找不到 onnxruntime.dll 的现象、原因与解决
现象:程序编译通过,但一运行就弹窗“找不到 onnxruntime.dll”,或者控制台直接退出无提示。原因:Windows 在加载可执行文件时,会按固定顺序搜索依赖的 dll:exe 所在目录、系统目录、PATH环境变量。如果你没有把onnxruntime.dll放到 exe 同目录,也没设置PATH,就会报这个错。解决:最稳妥的做法是把third_party/onnxruntime/bin下的所有 dll(包括onnxruntime.dll和onnxruntime_providers_shared.dll)复制到 exe 输出目录,或者用 CMake 的post-build命令自动复制:
add_custom_command(TARGET ort_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${ONNXRUNTIME_ROOT}/bin $<TARGET_FILE_DIR:ort_demo>)这段命令会在编译完成后,把 zip 包里的整个bin目录复制到可执行文件旁,避免漏掉 dll。注意$<TARGET_FILE_DIR:ort_demo>是生成器表达式,能自动取得目标文件所在目录,即使 CMake 更改了输出路径也不会失效。另外,如果你用的是 DLL 加载方式即LoadLibrary动态加载,还需要提前调用SetDefaultDllDirectories和AddDllDirectory,否则可能被 DLL 搜索顺序坑到。
4.2 推理结果与预期不符:输入张量形状和内存布局的检查点
现象:模型能跑,但输出结果跟 PyTorch 里跑出来的对不上,甚至全是 NaN 或 0。原因大概率有几个:输入张量的形状和数据类型不匹配;输入数据的存储顺序(NCHW vs NHWC)不对;没有做预处理或归一化;模型本身是量化模型但你喂了浮点数据。我建议你按顺序检查四个点:第一,用session.get_inputs()查看模型期望的输入名字、形状和类型;第二,对比 Python 里 numpy 数组的dtype是否与模型要求一致,ONNX 模型常见的是float32,如果你用float64传进去,onnxruntime 会做隐式转换,但性能会下降;第三,确认你的数据是C 顺序(行优先)还是F 顺序(列优先),ONNX Runtime 的CreateTensor默认按 C 顺序解释内存;第四,检查输入数值范围,许多图像模型要求输入在[0,1]或归一化后的均值为 0 的分布,直接用[0,255]的像素值喂进去,结果往往很差。
解决方法是写一个最小复现脚本,先用 numpy 生成一个全 1 或全 0 的输入,跑一遍看输出是否稳定。如果全 1 输入能正常输出,再逐步加入真实数据,缩小问题范围。
4.3 性能始终上不去:线程数、图优化与执行提供程序的取舍
现象:CPU 推理速度离预期差很远,或者多线程反而更慢。原因:SetIntraOpNumThreads设置过大会导致线程上下文切换开销超过并行收益。我见过有人为了压榨性能把线程数设成 CPU 逻辑核数的一半,结果延迟反而翻倍,这是典型的“多线程幻觉”。另一个原因是图优化级别设置得不够,ORT_ENABLE_BASIC和ORT_ENABLE_ALL的差别在复杂模型上非常明显,尤其是 Transformer 结构的算子融合。
解决思路:先跑一次ORT_ENABLE_ALL和线程数为 1 的基准测试,再逐步调线程数,用 profiler 查看瓶颈。onnxruntime 提供了内置的 profiling 功能,通过session_options.EnableProfiling("profile_prefix")就能开启,会生成一个 JSON 文件,里面能看清每个算子耗时。如果发现某个大算子比如 MatMul 或 Attention 耗时不正常,再考虑是否换 EP,比如在 Windows 上使用 DirectML EP 让 GPU 参与计算,代码改动只有两行:把providers参数加上"DmlExecutionProvider"。这里要注意,DirectML EP 在首次运行时会做编译初始化,真正的加速是从第二次推理才开始的,如果只测一次,会误以为它比 CPU 还慢。
4.4 用 onnxruntime 自带日志和模型转换工具定位问题
现象:模型加载失败,报错信息只有一句“invalid model file”,没有更多线索。原因:模型文件本身可能损坏,或者 ONNX 算子版本与当前运行时不兼容。解决:用 zip 包自带的工具先行诊断。解压目录里的bin下通常会有一个onnxruntime_test或onnxruntime_perf_test工具,你可以用命令行直接加载模型看是否有错误输出:
onnxruntime_perf_test.exe -e cpu -i "path\to\model.onnx" -r 10如果工具能正常跑,说明模型和运行时兼容;如果工具报错,再去查看模型是用哪个版本的 exporter 导出的。常见不兼容原因是模型里包含了超过当前 onnxruntime 支持的算子集版本,比如用opset 21导出的模型,在 1.23.1 上可能不支持。解决方式有两个:一是换一个更高版本的 onnxruntime(但你已经锁定了 1.23.1 的话就不太好办);二是在导出模型时用opset_version=19重新导出,因为 ONNX Runtime 1.23.x 对较旧的 opset 支持反而更成熟。另外,检查 ONNX 模型本身的合法性可以借助 Python 侧的onnx.checker,但这个工具不在 zip 包里,如果你有安装 Python 环境,顺手跑一遍会更稳。
5. 进阶用法:把 onnxruntime-win-x64 动态库做成稳定服务的关键两招
5.1 链接方式选择:zip 包里的 lib 和 dll 分别用在什么时候
很多新手费解:为什么onnxruntime.lib和onnxruntime_static.lib都存在,到底该用哪个?我的建议是:默认用动态链接,也就是用onnxruntime.dll+onnxruntime.lib。动态链接的好处是升级方便,替换 zip 包后重新编译一次即可,而且多个进程可以共享同一份 dll 内存;缺点是部署时必须带上 dll,且存在 dll 被换的风险。静态链接则把所有代码焊进 exe,部署文件少了,但 exe 体积会增大 20MB 左右,且如果 onnxruntime 内部有跨模块的全局状态,静态链接可能引发初始化顺序问题。我在实际项目中踩过这种坑:用了静态库后,程序退出时偶尔崩溃,后来查证是 onnxruntime 内部线程池的析构顺序与主程序其他全局对象冲突。所以除非你有绝对的单文件分发需求,否则不要轻易静态链接。
当你决定动态链接后,还需要注意 dll 的位数必须与目标程序一致。win-x64的 zip 包不能用于 x86 程序,如果项目里混用了 32 位依赖,链接可能成功但运行时崩溃。检查方法是打开 Visual Studio 命令行,运行dumpbin /headers onnxruntime.dll,查看machine字段是否显示x64。
5.2 多模型并发与内存分配:两个影响线上稳定性的 API
如果你的服务需要同时加载多个模型,不要为每个请求都创建Ort::Session,因为一个 Session 会持有自己的线程池和内存分配器,多会话并行会导致线程总数失控。更合理的做法是:按模型类型复用 Session,每个 Session 内部用互斥锁或队列串行执行推理;如果确实需要同一模型多并发,则创建多个 Session,但把每个 Session 的线程数调小,比如 IntrraOp 线程设成 1,然后用外部线程池做并行。
另一个容易忽略的 API 是Ort::Allocator的自定义分配。默认情况下 onnxruntime 使用系统内存分配器,在高频推理场景下会产生大量小内存块,导致内存碎片。你可以通过Ort::SessionOptions::AddInitializer为模型绑定额外的内存,或者在创建输入张量时复用同一块缓冲区,避免每帧都重新分配。例如在视频流处理中,预先分配一个固定大小的 float 数组,每次推理前把新帧数据拷贝进去,再用CreateTensor指向这块内存,推理结束后不要销毁Ort::Value,这样能显著减少 GC 压力。这一点在 C++ 端尤其重要,因为Ort::Value持有内存所有权,如果频繁创建销毁,会引发内存分配器的反复调用。
5.3 验证部署正确性:用同一份模型对比 CPU 与 GPU 输出
在把服务推上线之前,一定要做一次“输出一致性验证”。具体做法是:用同一个 onnxruntime zip 包,但分别用 CPU EP 和 DirectML EP(或 CUDA EP)运行同一份模型、同一份输入数据,比较输出张量的绝对误差。误差阈值取决于你对模型的容错能力,通常浮点模型要求最大绝对误差不超过1e-3,量化模型则可以放宽到1e-2。如果误差超过这个范围,不要急着上线,优先检查模型里有没有非确定性算子(比如某些 TopK、MaxPool 在 GPU 上可能返回随机顺序的索引),或者是否因为图优化路径不同导致算子融合规则不一样。这个验证过程写成一个脚本,每次更换 onnxruntime 版本后都跑一遍,能帮你避免很多诡异的线上事故。
6. 验证部署正确性:用同一份模型对比不同执行提供程序输出
这一章讲一个具体可执行的最小验证方法,也作为你收到 zip 包后收尾前必做的动作。假设你已经在 Windows 上配好了onnxruntime-win-x64-1.23.1.zip,现在要确认它能正确运行你的模型。在 Python 侧,可以这样写:
import onnxruntime as ort import numpy as np model_path = "model.onnx" input_data = np.random.rand(1, 3, 224, 224).astype(np.float32) session_cpu = ort.InferenceSession(model_path, providers=["CPUExecutionProvider"]) session_dml = ort.InferenceSession(model_path, providers=["DmlExecutionProvider"]) input_name = session_cpu.get_inputs()[0].name out_cpu = session_cpu.run(None, {input_name: input_data})[0] out_dml = session_dml.run(None, {input_name: input_data})[0] print("max abs diff:", np.max(np.abs(out_cpu - out_dml)))这段代码的核心是同时建两个 Session,分别指定 CPU 和 DirectML,然后对同一输入计算输出的最大绝对误差。注意session_dml的DmlExecutionProvider首次运行会自动完成 GPU 初始化,如果机器没有 GPU,onnxruntime 会抛异常,此时就把这一行删掉,只验证 CPU 稳定性。
我自己的习惯是,每次从官网下载新版 zip 包后,第一件事不是直接替换生产环境的 dll,而是跑一遍这个对比脚本。如果误差符合预期,再替换;如果误差异常,就用旧版 dll 对比,确认是不是模型本身的随机性。这套流程帮我避免了不少“升级完一切正常,上线后用户反馈结果不对”的翻车事故。希望帮到你。
本文还有配套的精品资源,点击获取