3、ONNX Runtime源码编译:从源码编译ONNX Runtime、配置ARM架构选项、编译优化选项详解
好,咱们进入实战环节。源码编译这事儿,说实话,很多同学一听就头大。我刚开始接触ONNX Runtime时也这么想——能直接用pip装,干嘛要自己编译?
但后来在ARM设备上踩了坑才明白:预编译包是给通用场景用的,想要榨干硬件性能,必须自己动手编译。尤其是ARM架构,不同厂商的CPU、NPU差异巨大,预编译包根本照顾不到。
3.1 为什么非要自己编译?
说白了,就三个理由:
- 性能最大化:预编译包用的是最保守的指令集,你的ARM CPU支持NEON?支持SVE?预编译包可不管这些。
- 硬件适配:比如Rockchip的NPU、华为的昇腾,这些加速器需要专门的Execution Provider,预编译包不包含。
- 裁剪体积:嵌入式设备存储空间金贵,你不需要的算子、后端,完全可以去掉。
3.2 编译前的准备工作
嗯,这里要注意。编译环境没搭好,后面全是坑。我建议你准备一台Linux机器,或者用Docker也行。
必备工具链:
- CMake(3.18以上)
- GCC/G++(ARM交叉编译需要对应的交叉工具链)
- Python 3.6+(用于生成编译配置)
- Git(拉取源码)
如果你是在ARM设备上本地编译(比如树莓派、Jetson),那直接装gcc就行:
sudo apt-get install build-essential cmake python3-dev
如果是交叉编译——比如在x86电脑上编译ARM版本——那你需要下载对应的交叉编译器。我个人习惯用Linaro的工具链:
# 以aarch64为例
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
tar -xf gcc-linaro-*.tar.xz
export PATH=$PATH:/path/to/gcc-linaro/bin
3.3 拉取源码与配置ARM架构选项
源码从GitHub拉,建议用稳定版本,别追最新。我一般用v1.15.x或v1.16.x:
git clone --recursive https://github.com/microsoft/onnxruntime.git
cd onnxruntime
git checkout v1.16.0
接下来是重头戏——配置ARM架构选项。ONNX Runtime用build.sh脚本封装了CMake配置,但咱们得搞清楚每个参数的含义。
关键ARM架构参数:
| 参数 | 说明 | 我的建议 |
|---|---|---|
--arm |
启用ARM架构编译 | 必须指定 |
--arm64 |
启用ARM64/aarch64编译 | 64位设备用这个 |
--arm_arch |
指定ARM架构版本,如armv7、armv8 | 根据你的CPU来 |
--arm_neon |
启用NEON指令集优化 | 强烈建议开启 |
--arm_softfp |
使用软浮点ABI | 老设备才用,新设备用硬浮点 |
举个例子,在树莓派4(ARM64,支持NEON)上编译:
./build.sh --config Release \
--arm64 \
--arm_arch armv8.2-a \
--arm_neon \
--parallel 4
你想想看,--arm_arch armv8.2-a这个参数,就是告诉编译器:我的CPU支持ARMv8.2-A架构,你可以放心用这个架构的指令。如果设低了,性能浪费;设高了,编译出来的二进制可能在老CPU上跑不起来。
3.4 编译优化选项详解
这部分是真正拉开差距的地方。ONNX Runtime的编译优化,说白了就是三件事:指令集优化、链接优化、运行时优化。
3.4.1 指令集优化
除了NEON,ARM还有SVE(可伸缩向量扩展)。不过SVE在服务器级CPU上才常见,嵌入式设备基本还是NEON的天下。
在build.sh里,你可以通过--cmake_extra_defines传递额外的CMake参数:
--cmake_extra_defines CMAKE_CXX_FLAGS="-march=armv8.2-a+fp16+rcpc+dotprod"
这个+fp16、+dotprod是啥?
fp16:半精度浮点支持,对模型推理很有用dotprod:点积指令,加速卷积运算rcpc:宽松的一致性模型,提升内存访问效率
我建议你查一下目标CPU的/proc/cpuinfo,看看支持哪些特性,然后针对性开启。
3.4.2 链接优化
嵌入式设备内存有限,链接优化能减小二进制体积。我最常用的是:
--cmake_extra_defines CMAKE_EXE_LINKER_FLAGS="-s -flto"
-s:strip符号表,体积能小20%左右-flto:链接时优化,能跨文件做优化,但编译时间会变长
不过要注意,-flto有时会引入奇怪的bug。我曾经在某个项目里开了LTO,结果模型推理结果不对,排查了两天才发现是LTO优化过度了。所以,生产环境建议先不开LTO,测试通过后再尝试。
3.4.3 运行时优化
编译时还可以指定运行时优化策略:
--enable_mlas \
--enable_onnxruntime_threadpool \
--enable_parallel
enable_mlas:启用MLAS(Microsoft Linear Algebra Subprograms),这是ORT自带的线性代数库,对ARM NEON做了专门优化enable_onnxruntime_threadpool:线程池,多核CPU必备enable_parallel:并行执行算子,适合大模型
--num_threads 3。
3.5 完整编译示例
好了,咱们把上面的知识点串起来。假设你有一块RK3588开发板(ARM64,Cortex-A76+A55,支持NEON和dotprod),完整的编译命令如下:
./build.sh --config Release \
--arm64 \
--arm_arch armv8.2-a \
--arm_neon \
--parallel 8 \
--cmake_extra_defines \
CMAKE_CXX_FLAGS="-march=armv8.2-a+fp16+rcpc+dotprod" \
CMAKE_EXE_LINKER_FLAGS="-s" \
--enable_mlas \
--enable_onnxruntime_threadpool \
--enable_parallel \
--num_threads 4
编译完成后,生成的文件在build/Linux/Release/目录下。你会看到libonnxruntime.so和onnxruntime可执行文件。
验证一下是否编译成功:
./build/Linux/Release/onnxruntime --help
如果能看到帮助信息,恭喜你,编译成功了。
--arm或--arm64以及交叉编译器路径。
3.6 编译后的验证与测试
编译完别急着部署,先跑个简单的模型验证一下。我一般用ONNX官方提供的测试模型:
# 下载一个简单的模型
wget https://github.com/onnx/models/raw/main/vision/classification/squeezenet/model/squeezenet1.0-8.onnx
# 用编译好的ORT跑推理
./build/Linux/Release/onnxruntime \
--model_path squeezenet1.0-8.onnx \
--log_level 2
看看输出有没有错误。如果有Unsupported operator之类的提示,说明你编译时裁剪了某些算子,需要重新编译。
另外,我建议用perf工具看看性能:
perf stat ./build/Linux/Release/onnxruntime \
--model_path squeezenet1.0-8.onnx \
--iterations 100
重点关注instructions per cycle(IPC)这个指标。ARM Cortex-A系列,IPC在1.5以上说明优化得不错。如果低于1.0,那你的编译选项可能有问题。
嗯,源码编译这块儿就讲这么多。说白了,编译ORT就像炒菜——食材(源码)都一样,但火候(编译选项)不同,味道天差地别。多试几次,找到最适合你设备的配置组合。