尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ONNX Runtime Win-x64 压缩包详解:C++ 动态库部署与推理调优

ONNX Runtime Win-x64 压缩包详解:C++ 动态库部署与推理调优 简介ONNX Runtime 1.18.0 的 Windows x64 压缩包专为需要在 C 工程中接入 ONNX 模型推理的开发者准备。该资源是可直接解压使用的 ONNX Runtime CPU 库包含 12 个头文件、2 个动态链接库和 2 个导入库等共 25 个文件压缩包约 58.75MB。头文件提供了 onnxruntime_cxx_api.h 等核心接口LIB 与 DLL 分别用于编译链接和运行时加载在 Visual Studio 中配置好 include 和 lib 路径后即可调用 C API 完成会话创建、模型加载与 CPU 推理。借助 ONNX Runtime 的多线程执行、内存管理优化及针对 x64 CPU 的微调即使不依赖 GPU 也能获得良好性能。包内还附带 README、许可证和版本文档便于查阅与合规使用。该资源已有 755 人学习适合需要构建离线推理服务或桌面 AI 应用的中高级 C 开发者。1. 拿到 onnxruntime-win-x64-1.18.0.zip 先别急着解压拿到这个 zip 别以为是装完就能跑的安装包onnxruntime-win-x64-1.18.0.zip 是 ONNX Runtime 1.18.0 在 Windows x64 上的原生 C/C 发布包。官方做成压缩包而不是安装器是为了让 C 服务、桌面工具和自研引擎有一个纯本地、无 pip 依赖的运行时。生产环境里出现它通常不是调 Python 接口而是 Release 构建直接链接 onnxruntime.dll把 ONNX 模型以毫秒级延迟跑在用户自己的 CPU 上。它解决两个问题C 按稳定 ABI 调用 ONNX 算子构建产物不用塞进 Python 解释器。适合 C 后端、插件系统和免 Python 的桌面软件。注意 1.18.0 的 win-x64 普通包面向现代指令集CPU 太老会非法指令那种环境要换成带 sse2 后缀的包。2. 解压 onnxruntime-win-x64-1.18.0.zip目录结构与链接环境2.1 目录结构bin/lib/include 各管什么事解压后根目录名称就是 onnxruntime-win-x64-1.18.0内部核心是三个文件夹。bin 是运行期唯一必须存在的目录里面有 onnxruntime.dll它承担算子注册、图调度和内存管理的全部逻辑。include 目录提供两套头文件onnxruntime_c_api.h 是纯 C ABIonnxruntime_cxx_api.h 是 C RAII 封装两者在不同编译器之间是二进制兼容的尤其是 C ABI 可以在 Rust、C# 甚至 Python ctypes 里直接加载。lib 目录里是 MSVC 格式的 onnxruntime.lib 导入库不参与运行只在链接时把符号映射到 dll。文件阶段作用bin/onnxruntime.dll运行动态库本体必须随 exe 发布include/onnxruntime_c_api.h编译C API 原型、OrtStatus 等类型定义include/onnxruntime_cxx_api.h编译基于 C API 的封装生命周期自动管理lib/onnxruntime.lib链接导入库生成 exe 时告诉链接器怎么引 dll如果你下载的是 GPU 专用包或附带了 DirectML 的包bin 下还会多出 onnxruntime_providers_cuda.dll、onnxruntime_providers_dml.dll 之类的执行提供程序动态库。ONNX Runtime 的架构里CPU EP执行提供程序编译在主库内而 CUDA、DML、TensorRT 以工厂插件形式存在。判断一个包是不是纯 CPU 包最直接的办法就是看 bin 下有没有 provider 相关 dll。2.2 先检查环境VC 运行库与 CPU 指令集第一个坑是 DLL 的依赖链。onnxruntime.dll 在发布时依赖 Microsoft Visual C Redistributable 2015-2022 x64机器上没有装齐会弹“找不到 VCRUNTIME140.dll 或 msvcp140.dll”。目标机器是瘦客户机或 Windows Server Core 时安装桌面版可能不够用 extract 方式把 vc_redist.x64.exe 里的运行库文件拷到应用目录更省心。我一般在部署脚本里先执行vc_redist.x64.exe /install /quiet /norestart再复制 onnxruntime.dll。第二个坑在指令集。1.18.0 的 win-x64 标准包默认按较新的 SIMD 指令集编译2011 年 Sandy Bridge 之前的老工控机有可能加载后直接崩溃。怀疑时先看 CPU 型号wmic cpu get name如果输出的是 Core2、老 Atom 这类不带 AVX 的型号直接去下载onnxruntime-win-x64-sse2-1.18.0.zip不要跟标准包较劲。特别提醒这个 zip 里的 DLL 不是自包含的bin 目录里除 onnxruntime.dll 之外的任何 dll 都要保持在同一目录不要只挑主库复制走否则运行期会在加载 provider 时报告找不到模块。2.3 用 CMake 和 MSVC 把它接到现有工程拿到 zip 后最稳妥的做法是当成预编译第三方库CMake 3.18 以上可以直接写set(ORT_ROOT C:/third_party/onnxruntime-win-x64-1.18.0) add_executable(demo main.cpp) target_include_directories(demo PRIVATE ${ORT_ROOT}/include) target_link_directories(demo PRIVATE ${ORT_ROOT}/lib) target_link_libraries(demo PRIVATE onnxruntime)这段配置里target_link_libraries写的是 onnxruntime 而不是完整路径CMake 会顺着target_link_directories找到 onnxruntime.lib生成 Visual Studio 工程后链接器拿到符号并生成对 onnxruntime.dll 的导入表。要注意 CMake 不会自动把 onnxruntime.dll 复制到输出目录需要自己加一条 post-build 复制命令或者把 bin 目录加入 PATH 再启动 exe。等价的手工 cl 编译命令是cl /EHsc /I C:\third_party\onnxruntime-win-x64-1.18.0\include main.cpp /link /LIBPATH:C:\third_party\onnxruntime-win-x64-1.18.0\lib onnxruntime.lib这里/EHsc表示 C 异常处理模式/LIBPATH指定导入库所在目录。还会遇到一个常见边界头文件里的函数按 __cdecl 导出类型尺寸跟随指针宽度所以 x86 的 exe 不能链 x64 的 lib。这个 zip 名字里的 win-x64 已经限定死 ABI编译目标必须是 x64不要用 Win32 配置去试。3. 用 onnxruntime 动态库跑通第一次推理3.1 从版本号验证动态库身份工程搭好后的第一件事不是写完整推理而是先打印一行版本号验证你链接的确实是 1.18.0。很多人程序跑起来就以为完事却不知道自己不小心把旧版本的 onnxruntime.dll 留在了 exe 同目录。Windows 加载 DLL 时exe 所在目录优先于系统 PATH所以发布目录里多出来的历史版本会悄悄覆盖意图。#include onnxruntime_cxx_api.h #include iostream int main() { std::cout ONNX Runtime Ort::GetVersionString() \n; return 0; }Ort::GetVersionString()是onnxruntime_cxx_api.h里暴露的全局函数实现会转发到 C API 的OrtGetVersionString()返回 1.18.0 这样的版本号。如果这里的输出和预期不一致不要改代码先检查 exe 同目录和工作目录里有没有第二份 dll。Windows 下常见的“链接了新版却跑旧逻辑”十有八九是这个原因。3.2 构造 Session 并填充输入张量用 onnxruntime 动态库的标准流程是创建 Env配置 SessionOptions加载模型取输入输出名构造输入张量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(4); opts.SetGraphOptimizationLevel(ORT_ENABLE_ALL); Ort::Session session(env, Lyolov8n.onnx, opts); auto in_name_ptr session.GetInputNameAllocated(0, Ort::AllocatorWithDefaultOptions()); auto out_name_ptr session.GetOutputNameAllocated(0, Ort::AllocatorWithDefaultOptions()); std::vectorconst char* input_names{in_name_ptr.get()}; std::vectorconst char* output_names{out_name_ptr.get()}; auto input_info session.GetInputTypeInfo(0).GetTensorTypeAndShapeInfo(); auto shape input_info.GetShape(); auto elem_type input_info.GetElementType(); std::vectorint64_t input_shape{1, 3, 224, 224}; std::vectorfloat input(1 * 3 * 224 * 224, 0.5f); auto mem Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); Ort::Value tensor Ort::Value::CreateTensorfloat(mem, input.data(), input.size(), input_shape.data(), input_shape.size()); Ort::RunOptions run_options; auto outputs session.Run(run_options, input_names.data(), tensor, 1, output_names.data(), output_names.size()); auto out_info outputs.front().GetTensorTypeAndShapeInfo(); auto out_shape out_info.GetShape(); float* out_data outputs.front().GetTensorMutableDatafloat(); for (int64_t i 0; i out_shape[1] i 5; i) { std::cout score[ i ] out_data[i] \n; } return 0; }这段代码做了两件关键的事in_name_ptr和out_name_ptr这两个AllocatedStringPtr不能写成临时变量直接传进 Run否则字符串会在 Run 之前被析构input_names里留下野指针输出名向量里的指针必须保持到 Run 结束。至于GetInputTypeInfo返回的 shape 只是为了调试真正建张量时按模型文档规定的形状写即可。3.3 动态维度与类型检查的坑很多 ONNX 模型把 batch 维标成 -1上面的代码把 shape 写死为{1,3,224,224}没有问题因为 ONNX Runtime 在创建 Session 时会把动态维度绑定到具体值。但同一个 Session 想在进程里跑不同 batch最好每轮都显式创建对应形状的张量不要复用旧张量。1.18.0 的 CPU 内存池默认开启连续推理时内存不会反复 malloc但如果输入形状频繁跳跃内存池会保留多块大 buffer导致 RSS 看起来偏高。模型的输入类型需要和 C 侧对齐ONNX 的 tensor 类型枚举里1 代表 float327 代表 int64。如果模型输入是 uint8光把 vector 改成 float 没有用类型不匹配的报错出现在第一次 Run 而不是创建 Session 时因为 ONNX Runtime 把类型检查延迟到了执行阶段。看到 “Input type mismatch” 第一反应应该是去读模型输入信息而不是改模型路径。4. onnxruntime-win-x64-1.18.0 的推理参数与性能调优4.1 SessionOptions 里最常改的 6 个参数同样一份模型SessionOptions 没设对延迟能差三倍。1.18.0 里最值得关注的参数是下面这几个参数/方法默认建议SetGraphOptimizationLevelORT_ENABLE_ALL保持 ALL调试算子时临时降为 BASICSetIntraOpNumThreads0自动按物理核数设别用逻辑线程数SetInterOpNumThreads0有 branch 并行时设 2~4SetExecutionModeORT_SEQUENTIAL多入口多分支模型改成 ORT_PARALLELEnableCpuMemArena开启保持开启EnableProfiling关闭调优时开启产出 chrome trace json常用写法和说明Ort::SessionOptions opts; opts.SetIntraOpNumThreads(std::thread::hardware_concurrency() / 2); opts.SetInterOpNumThreads(2); opts.SetExecutionMode(ORT_SEQUENTIAL); opts.SetGraphOptimizationLevel(ORT_ENABLE_ALL); opts.EnableProfiling(ort_profiling);SetIntraOpNumThreads控制单个算子内部的并行线程数适合 Conv、MatMul 这类计算密集算子SetInterOpNumThreads控制不同节点之间的并行度适合有分支结构的模型。超线程带来的逻辑线程数并不等于物理并行能力把hardware_concurrency()直接用满反而会因为内存带宽争抢拉高延迟。我的常见做法是先设成物理核数的一半再用真实输入跑一轮 benchmark。4.2 图优化级别和 Run 预热的作用ORT_ENABLE_ALL 会把 QDQ、LayerNorm、Attention 这类结构做融合减少节点间内存搬运。1.18 的 CPU EP 对 Transformer 类模型的融合算子覆盖比早期版本更完整但代价是 Session 初始化时间变长图优化在启动时会把整个模型扫描几轮。如果模型很大第一次 Run 还会触发权重重排因此性能测试绝不能只跑一轮要至少预热 5 次再计时for (int i 0; i 5; i) { auto outputs session.Run(run_options, input_names.data(), tensor, 1, output_names.data(), output_names.size()); } auto start std::chrono::steady_clock::now(); auto outputs session.Run(run_options, input_names.data(), tensor, 1, output_names.data(), output_names.size()); auto end std::chrono::steady_clock::now();预热不仅是为了让 Cache 热起来ONNX Runtime 的 arena 内存分配器会在首次 Run 时申请一块可复用内存之后的 Run 都在这块内存池里分配。不预热的话第一次 Run 的耗时里混着 malloc 和权重转换的成本数据完全不可比。4.3 用 profiler 看真实瓶颈EnableProfiling(ort_profiling)开启后Session 关闭或进程退出时会在当前工作目录生成onnxruntime_profile_*.json。这个文件是给 Chrome 的 tracing 工具看的拖进chrome://tracing后可以直接看到每个算子 Kernel 的耗时和 CPU 线程占用。定位瓶颈时先看三个阶段Input Copy、Session Run、Output Copy。很多应用把前处理和模型推理写在同一个线程里profiler 上看到的算子耗时明明只有 3ms整体帧耗时却有 20ms多出来的基本都在数据从 numpy 或 cv::Mat 拷贝到 Tensor 的过程。优化方向通常是直接让 onnxruntime 的输入张量指向原始 buffer而不是先 memcpy 到临时 vector 再交给 CreateTensor。注意当 CpuMemArena 开启后连续两次 Run 如果使用同一块输入内存模型内部可能原地改写输入需要让出所有权不能边读边传。5. 从 win-x64 排错到 Jetson Orin NX 的跨平台迁移5.1 三个高频启动失败现场第一个是 0xc000007b几乎都是 x86/x64 混用。exe 是 x64但某个依赖的 dll 是 32 位或者反过来。这个 zip 包既然叫 win-x64整个进程链路都得是 x64。第二个是找不到 VCRUNTIME140.dll。豁免了 VC 运行库缺失的机器需要把 vc_redist.x64.exe 打进安装包或者在启动脚本里检测注册表HKLM\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64。第三个是 illegal instruction。在老的 CPU 上跑了 AVX 版动态库进程在初始化阶段直接崩。解决方法是换onnxruntime-win-x64-sse2-1.18.0.zip。判断方法很简单同一套程序在主流办公机上正常在工控机上必现崩溃十有八九是这个原因不要先去查代码。5.2 在 Jetson Orin NX 上部署时Windows zip 能帮你确认什么开发时习惯 Windows 这个 zip 包部署目标却是 Jetson Orin NX这时候 zip 的价值在于提前确认模型本身的问题。Orin NX 上通常用的是onnxruntime-gpuPython wheel 或 Linux aarch64 的 tgz 包执行提供程序是 CUDA EP 和 TensorRT EP和 Windows CPU 包行为不完全一样。建议在 Windows 上把SetGraphOptimizationLevel设为ORT_DISABLE_ALL跑一遍再在 Orin NX 上用 CPU EP 跑一遍两者的输出差异应该控制在 float 误差范围内。跨平台时最容易翻车的不是算子精度而是图优化重排后某个中间张量被复写导致顺序相关的代码拿到错误结果。用 onnxruntime 动态库做跨平台回归时可以把 ONNX 模型自带的测试数据导成 npy 文件在两端分别跑同一组输入再用np.allclose设置rtol1e-3, atol1e-5做断言把这组断言纳入 CI至少在换包、换卡和换优化等级之前先跑一遍比任何口头承诺都可靠。本文还有配套的精品资源点击获取
返回列表