onnxruntime-win-x64-1.23.1.zip:C++离线推理部署实战与避坑指南

发布时间:2026/10/31 6:23:04
onnxruntime-win-x64-1.23.1.zip:C++离线推理部署实战与避坑指南

简介:微软官方发布的 onnxruntime 1.23.1 Windows x64 CPU 预编译包,属于安装包类资源,专为需要快速集成 ONNX 模型推理能力的 C/C++ 或 Python 开发者准备。ONNX Runtime 是微软开源的跨平台推理引擎,可将 PyTorch、TensorFlow 等训练的模型高效部署到本地,并显著提升推理性能。该版本为 CPU 版,解压即用,特别适合内网环境或官方下载不稳定的情况下作为备份。压缩包共 26 个文件,约 74.48 MB,包含 14 个头文件、2 个动态库及配套导入库、2 个调试符号文件,以及说明文档、许可证、第三方声明和版本标识等,目录按头文件目录与库目录清晰组织,便于 Visual Studio 等工程直接引用。目前已有 47 人学习下载,可用作团队内部共享的离线资源。从工程角度看,头文件与库文件分离的结构使依赖关系一目了然,可快速接入 CMake 或 Visual Studio 项目;同时附带版本核对信息,有助于确认环境一致性、快速定位配置问题。无论是快速原型验证还是生产环境部署,这套预编译包都能直接复用,为开发者节省大量构建时间。

1. onnxruntime-win-x64-1.23.1.zip:我为什么建议直接用它而不是自己编译 ONNX Runtime

onnxruntime 1.23.1 的 Windows x64 官方发布包,是一个解压就能用的 C++ 推理运行时。很多人会把 ONNX 和 ONNX Runtime 混为一谈:ONNX 是模型格式,ONNX Runtime 才是真正加载模型、执行算子、管理内存的引擎。这个 zip 解决的是“拿到 .onnx 模型,如何在 Windows x64 进程里把推理跑起来”,适合两类人:一是被 CMake 和头文件缠住的 C++ 开发者,二是内网环境装不了 pip 包、只能用官方二进制的部署人员。

它不是安装向导,解压后没有 setup.exe。核心是 dll、头文件和导入库,相当于把运行时组件交给你,由你决定是拷进工程、加 PATH,还是用 CMake 链接。接下来按“拆包 → 接入 → 验证 → 排坑”的顺序,把边界、参数和容易翻车的地方逐个过一遍。

2. 解压后的真实结构:onnxruntime.dll、头文件、lib 文件各归各位

第一次解开 onnxruntime-win-x64-1.23.1.zip,你大概率会看到 include、lib、bin 三个目录,外加 LICENSE、README 和 ThirdPartyNotices。第一反应别去翻 README,先把 dll 拷贝到工程里试试能不能加载,比什么都管用。这个包本质上是一个“绿色”运行时,所有组件都集中在解压目录里,只要机器上有对应版本的 VC++ 运行库,它就能跑。下面按我常用的排查顺序过一遍。

2.1 官方 zip 里典型布局速览

路径内容接入时怎么处理
bin/onnxruntime.dll核心推理动态库运行时必须能被进程找到,拷贝到 exe 旁或加入 PATH
include/onnxruntime_c_api.hC API 声明C 工程 include 该目录
include/onnxruntime_cxx_api.hC++ API 封装C++ 工程 include,内部会再拉取 C API 声明
lib/onnxruntime.lib导入库链接时传给 linker,实际调用的还是 bin 里的 dll
LICENSE / README许可与说明保留即可,商用分发时要带着 LICENSE

官方每个 release 版本的目录结构基本稳定,但 include 下有少量辅助头文件,比如 onnxruntime_utils.h、onnxruntime_float16.h 等。写业务代码时只 include onnxruntime_cxx_api.h 就足够,不用逐个关心辅助头文件。lib 目录下这个 onnxruntime.lib 不是完整的静态库,它只负责把符号搬运给可执行文件,真正运行时还是找 bin/onnxruntime.dll。所以工程里就算 link 成功,运行阶段 dll 找不到一样会崩,这个问题在第五章会单独讲。

2.2 运行时依赖:不是解压就能跑,缺的是 VC++ 运行库

很多人在这一步以为 zip 是“免安装”,结果双击 demo 直接报“VCRUNTIME140.dll 找不到”。这不是 onnxruntime 文件损坏,而是 Windows 上没有对应的 VC++ 运行库。官方二进制使用 MD 动态链接模式,依赖 msvcp140.dll、vcruntime140.dll、vcruntime140_1.dll 等。如果机器上装过 Visual C++ Redistributable 2015-2022 x64,这些文件就有;没有就去微软官网下载对应版本。

还可以用 PowerShell 先看一下 dll 是否能加载:

$dll = Get-Item .\bin\onnxruntime.dll $dll.VersionInfo | Select-Object FileVersion, ProductVersion, FileDescription

这段命令读的是 Windows PE 文件里的版本资源。如果能看到 FileVersion 前缀是 1.23.1,说明文件本身完整;如果报错,多数是文件下载不完整或路径不对。版本资源里的 FileVersion 常带内部 build 号,只要前缀是 1.23.1 就能认定是目标发行版。

2.3 先分清 CPU 版还是 GPU 版,别被“win-x64”骗了

这个包的名字里没有 gpu,也没有 dml,所以它是纯 CPU 的 ONNX Runtime。有些项目跑起来性能不符合预期,最后发现用的是 CPU provider。如果你要 CUDA 或者 DirectML,需要另外找 onnxruntime-gpu-win-x64 或 onnxruntime-directml-win-x64 的对应版本,打包结构和接入方式类似,但额外依赖 CUDA/cuDNN 或 DirectML.dll。

确认当前 dll 支持哪些执行提供程序,可以在程序里打印 Ort::GetAvailableProviders(),这个最后一张会再演示。日常做法是,先用这个 CPU 包把整条推理链路跑通,再根据硬件换 GPU 包。这样排错时目标更干净,不会一上来就陷入“CUDA 版本对不上”的泥潭。

2.4 include、lib、bin 三者的关系与 x64 边界

很多从 Java/Python 转过来的同学,第一次看到 import library 会以为 lib 里是完整实现。其实 onnxruntime.lib 只记录导出符号的位置,真正算力在 dll 里。编译期链接器用 lib 找到符号名,运行期 loader 按 dll 的导出表加载函数地址。这意味着你把 dll 挪走,exe 就会在启动阶段报“找不到入口点”或“找不到 onnxruntime.dll”。

另外要特别记住:这个包是 x64 的,工程目标平台必须是 x64 而不是 Win32。VS 默认新项目有时候是 x86,Debug|x86 下去链接,会直接报“无法解析的外部符号”,或者运行时报“试图加载格式不正确的程序”。我一般会在 CMake 里显式写set(CMAKE_GENERATOR_PLATFORM x64),避免生成 32 位目标。

提示:如果想把 dll 打进局域网分发包,不要只拷 onnxruntime.dll,还要带上 LICENSE。ONNX Runtime 的 MIT 许可要求保留版权声明。

3. 在 C++ 工程里接住 1.23.1:CMake 链接与最小推理示例

这一章解决的是“怎么把 zip 变成实际能跑的 exe”。我见过太多人卡在第三行代码之前,不是头文件找不到,就是 lib 和 dll 对不上。这里给出一个我常用的、离线可用的接入方案,不依赖 vcpkg,也不依赖 NuGet。

3.1 为什么不建议在这个场景下用 vcpkg 或 NuGet 包

vcpkg 安装 onnxruntime 会牵扯到工具链、版本匹配、编译缓存,第一次装可能要等很久。NuGet 的 Microsoft.ML.OnnxRuntime 主要面向 .NET,对纯 C++ 工程来说引入了一堆不必要的东西。而这个官方 zip 本身就是微软为 Windows 原生分发准备的二进制包,当你需要锁定 1.23.1 这个精确版本,或者在内网离线交付,直接解压 zip 是最可控的路线。

3.2 CMakeLists.txt 最小配置

假设解压后的目录叫onnxruntime-win-x64-1.23.1,并且放在工程的third_party下面。CMake 可以这样写:

cmake_minimum_required(VERSION 3.18) project(ort_demo CXX) set(CMAKE_CXX_STANDARD 17) set(ORT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/onnxruntime-win-x64-1.23.1") include_directories("${ORT_ROOT}/include") link_directories("${ORT_ROOT}/lib") add_executable(demo main.cpp) target_link_libraries(demo PRIVATE onnxruntime) # 运行前把 dll 拷到 exe 旁边,避免“找不到 onnxruntime.dll” add_custom_command(TARGET demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORT_ROOT}/bin/onnxruntime.dll" "$<TARGET_FILE_DIR:demo>")

这里link_directories不加也可以,改成在target_link_libraries里写完整路径更稳:

target_link_libraries(demo PRIVATE "${ORT_ROOT}/lib/onnxruntime.lib")

两种方式都行,关键是 lib 路径和 include 路径指向同一个 1.23.1 解压目录。我习惯把 dll 通过 POST_BUILD 自动拷贝,这样每次重新编译后 exe 旁边都会有最新的 dll,不会出现“本机能跑,换台机器就崩”的玄学问题。

3.3 第一个推理代码:Ort::Session 读取模型并输出张量

下面这个例子假设你有一个model.onnx,输入是 shape 为[1, 784]的 float 张量。重点看 session 创建和 run 的调用方式:

#include <onnxruntime_cxx_api.h> #include <vector> #include <iostream> int main() { Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "ort-demo"); Ort::SessionOptions opts; opts.SetIntraOpNumThreads(2); opts.SetGraphOptimizationLevel(ORT_ENABLE_ALL); Ort::Session session(env, L"model.onnx", opts); Ort::AllocatorWithDefaultOptions allocator; auto input_name = session.GetInputNameAllocated(0, allocator); auto output_name = session.GetOutputNameAllocated(0, allocator); const char* input_names[] = { input_name.get() }; const char* output_names[] = { output_name.get() }; std::vector<int64_t> input_shape = { 1, 784 }; std::vector<float> input_data(1 * 784, 0.5f); std::vector<Ort::Value> input_tensors; input_tensors.push_back(Ort::Value::CreateTensor<float>( allocator, input_data.data(), input_data.size(), input_shape.data(), input_shape.size())); auto output_tensor = session.Run(Ort::RunOptions{nullptr}, input_names, input_tensors.data(), 1, output_names, 1); auto output_info = output_tensor[0].GetTensorTypeAndShapeInfo(); auto output_shape = output_info.GetShape(); std::cout << "output dims: " << output_shape.size() << std::endl; }

逻辑不复杂:先创建 Env 和 SessionOptions,再创建 Session;然后通过 session 拿到输入输出名字;最后组装输入张量并执行 Run。注意CreateTensor<float>是浅拷贝视图,它不复制数据,只是把input_data的指针交给 ORT,所以input_data的生命周期必须覆盖session.Run调用。我在项目里见过把 input_data 定义在 lambda 里、运行完就析构的情况,输出结果直接乱掉,这是典型的“指针悬空”血泪经验。

3.4 参数说明:线程数、图优化级别与 RunOptions

SessionOptions 里这几个参数最值得调:

参数取值影响
SetIntraOpNumThreads2 / 4 / 0控制单次推理内部算子并行线程数,0 表示用默认值
SetGraphOptimizationLevelORT_ENABLE_ALL / ORT_ENABLE_BASIC / ORT_DISABLE_ALL图优化等级,生产环境一般用 ORT_ENABLE_ALL
SetInterOpNumThreads1 / 2控制多个节点之间的并行度,简单模型设 1 更好

Ort::RunOptions{nullptr}传空指针表示默认 Run 参数。它其实还能设置自定义 terminate 标志、内存分配等,日常推理用默认值就够。如果模型对实时性要求高,我一般会固定SetIntraOpNumThreads(2),然后用 benchmark 脚本测延迟,不盲目堆线程。线程数设太高,小模型反而会因为线程切换变慢,这个坑做服务端推理时特别容易遇到。

4. Python 侧不想装 wheel?用 ctypes 调 DLL 并对比两条路线

看到这里你可能会问:Python 不是有pip install onnxruntime==1.23.1吗,为什么还要碰这个 zip?答案是内网部署。很多生产环境不允许直接从网上下载 wheel,但允许拷贝官方二进制压缩包。这一章就演示如何用 Python 侧直接加载这个 zip 里的 dll,至少能把版本串读出来,确认二进制能跑。

4.1 为什么会有“pip 没网但手头只有 zip”的场景

内网隔离、离线交付、堡垒机审核,这些场景下 pip install 会卡在“找不到包”。手头只有这个 zip 时,Python 侧也有办法:用 ctypes 加载 dll,走 C API。ONNX Runtime 的 dll 导出最底层入口OrtGetApiBase(),这个函数返回一个OrtApiBase结构体,结构体里有版本字符串函数和获取 API 表的函数。我们不需要调用完整推理功能,只要验证 dll 能加载、能得到 1.23.1 版本串,就说明部署环境没问题。

4.2 ctypes 加载 onnxruntime.dll 并读出版本串

代码可以直接跑,前提是本机已经装了 VC++ 运行库:

import ctypes dll = ctypes.CDLL(r"D:\libs\onnxruntime-win-x64-1.23.1\bin\onnxruntime.dll") class OrtApiBase(ctypes.Structure): _fields_ = [ ("GetVersionString", ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_void_p)), ("GetApi", ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_uint32)), ] dll.OrtGetApiBase.restype = ctypes.POINTER(OrtApiBase) api_base_ptr = dll.OrtGetApiBase() version_ptr = api_base_ptr.contents.GetVersionString( ctypes.cast(api_base_ptr, ctypes.c_void_p) ) print(version_ptr.decode())

运行后如果打印1.23.1或类似前缀,说明 dll 被成功加载且底层 API 入口可用。这里最关键的是OrtApiBase的结构定义:第一个字段必须是GetVersionString,第二个是GetApi,因为 C API 暴露的就是这个内存布局。一旦顺序错了,读出来的就是乱码或野指针,直接崩溃。

4.3 两条路线对比

路线优点缺点
zip + ctypes不依赖 pip,离线可用,二进制可单独分发要手动维护 C API 结构体,开发效率低
pip 安装 onnxruntime wheel开箱即用,有完整 numpy 绑定和 Python 异常内网无镜像时难安装,版本冲突管理麻烦

如果你的内网其实有 PyPI 镜像,我仍然建议pip install onnxruntime==1.23.1。ctypes 这条路适合“临时验证环境”和“只读版本号”的场景,真正写业务代码还是 wheel 省心。

4.4 什么时候必须用 zip

需要把 ONNX Runtime 和 C++ 业务代码打进同一个交付包时,zip 是必须的。因为 wheel 里面是单独的 dll,还要从 site-packages 里抠出来,反倒不如直接分发官方 zip 干净。还有一种场景是动态库名冲突:系统里已经有另一个版本的 onnxruntime.dll,用 wheel 时会因为 PATH 顺序加载到旧版本。这时候把 zip 里的 dll 和 exe 放同一目录,Windows 会优先加载可执行文件目录下的 dll,避免“装对了版本却用的是旧库”的隐性 bug。

5. 避坑与常见问题:Windows x64 上部署 1.23.1 的五个真实翻车点

这一章写的都是实际项目里反复出现的问题。每条我都按“现象 → 原因 → 解决”记录,方便你直接对照。

5.1 翻车点一:exe 启动就报“找不到 onnxruntime.dll”

现象:编译链接都成功,Start debugging后立刻弹窗,提示找不到 onnxruntime.dll。

原因:链接器只用了 lib 导入库,运行期进程没去解压目录找 dll。Windows 的 DLL 搜索顺序依次是 exe 所在目录、系统目录、PATH 目录,你如果没把 dll 放进去,loader 自然找不到。

解决:把 bin/onnxruntime.dll 拷贝到 exe 输出目录,或者在系统 PATH 中加入解压目录的 bin。我推荐用第三章里的 POST_BUILD 自动拷贝,避免每次手动复制漏掉。

5.2 翻车点二:提示缺少 VCRUNTIME140.dll / msvcp140.dll

现象:exe 已经抱着 dll 跑了,但换一台干净的 Windows Server 就报缺 VCRUNTIME140.dll。

原因:目标机器没装 Visual C++ Redistributable。ONNX Runtime 官方二进制依赖 VC++ 运行库,这不是 ORT 自己能打包解决的问题。

解决:在交付说明里明确要求先安装 VC++ 2015-2022 x64 运行库。也可以在部署脚本里检测Get-Item C:\Windows\System32\vcruntime140.dll,不存在就先安装。

5.3 翻车点三:拿到的是 CPU 包,却指望它跑 GPU

现象:程序能跑,但打开任务管理器发现 GPU 占用为 0,推理耗时很高。

原因:onnxruntime-win-x64-1.23.1.zip是 CPU 版。名字里的 win-x64 只说明平台,不代表包含 CUDA 或 DirectML 执行提供程序。

解决:先打印Ort::GetAvailableProviders(),如果列表里只有 CPUExecutionProvider,就老老实实换 GPU 包。需要 CUDA 就找对应 CUDA/cuDNN 版本的 gpu 包,需要通用加速就找 directml 包。

5.4 翻车点四:模型 opset 太新或带自定义算子,session 加载失败

现象:代码编译没问题,但Ort::Session构造函数抛异常,日志显示“unsupported opset”或“kernel not found”。

原因:模型导出时用了比当前 ORT 支持的 ONNX opset 更高的版本,或者模型里带了 Pytorch 导出时的自定义算子。1.23.1 支持较新的官方 opset,但自定义算子必须注册对应的 Custom Op。

解决:先用 Python 检查模型 opset:onnx.load("model.onnx").opset_import,再用合适的 onnx 版本重新导出。如果是自定义算子,要么在 C++ 里注册实现,要么把相关逻辑转成标准算子。

5.5 翻车点五:调用 ORT_API 时崩溃,根因是 ABI 不匹配

现象:程序起来后,调用 session.Run 偶尔崩溃或输出随机张量,Debug 模式下断点位置没有规律。

原因:最常见的是编译器/运行库版本不一致,比如用老版本 MSVC 编译的 exe 链接新版本 ORT,或者 C++ 异常处理模式没对齐。ONNX Runtime 官方二进制是用较新 MSVC 编译的,你工程如果用了过旧工具集,就会踩 ABI 兼容问题。

解决:把工程工具集升级到与 VC++ 2015-2022 Redistributable 对应的版本,至少用 VS2019 或 VS2022 重新编译。另一个不常见但要记住的是:不要把 onnxruntime.dll 和自己的 dll 混在同一个静态链接边界上,尽量保持 exe 直接链接 ORT,不要让中间层传递 Ort::Value。

6. 让 1.23.1 证明自己:版本串验证与 EP 列表检查的小技巧

拿到一个新解压的 ONNX Runtime,不要马上去写推理代码,先花三分钟验证“这个 dll 确实是 1.23.1 且支持我要的 provider”。我每次接入新版本都会强制走一遍这台机器的双检查。

6.1 PowerShell 快速读版本资源

(Get-Item .\bin\onnxruntime.dll).VersionInfo | Format-List FileVersion, ProductVersion

文件版本号前缀如果是 1.23.1,说明二进制就是预期版本。这个检查也能发现下载损坏:如果版本资源读不出来,多半是 zip 没下完整。

6.2 C++ 打印版本串与可用 provider

#include <onnxruntime_cxx_api.h> #include <iostream> int main() { Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "verify"); auto providers = Ort::GetAvailableProviders(); for (const auto& p : providers) { std::cout << "Provider: " << p << std::endl; } }

这段代码会在控制台输出类似CPUExecutionProvider的列表。如果连CPUExecutionProvider都没有,说明 dll 没加载成功,问题在 PATH 或 VC++ 运行库;如果 provider 列表里有CUDAExecutionProvider,但这个 zip 包名没有 gpu,那你看的肯定是别的目录。

从那以后,我每次拿到新的 onnxruntime 发行包,都会强制走一遍版本串 + provider 列表检查,确认 dll 能加载才继续写业务代码。希望帮到你。

本文还有配套的精品资源,点击获取