1. 为什么要在 Ubuntu 上折腾 OpenVINO 的源码编译
1.1 从一次踩坑说起:预编译包解决不了的那 20%
如果你只是拿 OpenVINO 跑个官方 Demo、做个推理验证,那直接下预编译的 Release 包解压就能用,五分钟搞定。但真正落到项目里,情况往往没这么简单。我印象很深的一次,是给一个边缘侧设备做视觉推理部署,板子上的 Ubuntu 版本比较老,官方预编译包依赖的某些系统库版本对不上,装完一跑就报符号找不到;还有一次是团队需要针对特定 CPU 指令集做裁剪,把用不上的插件和前端全部去掉,减小部署体积——这种需求,预编译包根本满足不了,只能自己从源码编。
这就是源码编译 OpenVINO 的核心价值:你能控制它编出什么、依赖什么、装到哪里。尤其是 2021.4 LTS 这个版本,它是 OpenVINO 历史上一个相当重要的长期支持版本,很多工业项目和存量代码都锚定在这个版本上,而它又不像后来的版本那样把构建系统改得面目全非,所以至今仍有大量场景需要在 Ubuntu 上手动编译它。至于“旧版本编译”,更是绕不开的刚需——新版本改了 API、改了目录结构、改了依赖管理方式,老项目迁移成本高,很多时候只能把老版本重新编一遍。
这篇文章面向的是这样一类人:你手上有台 Ubuntu 机器(物理机、虚拟机、容器都行),需要装 OpenVINO 2021.4 LTS 或者更早的版本,官方包不适用或者你想定制,于是决定走源码编译这条路。我会把整个流程拆开讲透,包括依赖怎么装、CMake 参数怎么选、编译报错怎么排,以及那些官方文档里不会写、只有亲手编过才知道的坑。OpenVINO 源码编译这件事,说难不难,说简单也绝不简单,关键在于把每个环节的“为什么”搞清楚。
1.2 先搞清楚 OpenVINO 2021.4 的构建体系长什么样
动手之前,得先明白你要编的是个什么东西。OpenVINO 2021.4 的代码仓库结构和后来的版本差异很大,它采用的是“主仓库 + 子模块”的组织方式,核心推理引擎、各种前端(ONNX、PaddlePaddle、TensorFlow 等)、各种插件(CPU、GPU、MYRIAD、GNA)都放在一个大仓库里,通过 CMake 的开关来控制编不编。这一点很关键,因为它决定了你的编译选项策略:不是所有东西都要编,编得越多,依赖越复杂,出错概率越高。
2021.4 版本默认会去拉取一堆第三方依赖,包括但不限于 oneDNN(当时还叫 mkldnn)、ngraph、各种 protobuf、OpenCV 等。这些依赖有的通过 git submodule 管理,有的通过 CMake 的 ExternalProject 在配置阶段动态下载。这就带来一个很现实的问题:网络环境不好的时候,配置阶段就会卡死或者失败。所以老手编译 OpenVINO 的第一反应往往不是敲命令,而是先规划好依赖来源——是让它自己下,还是提前准备好离线包。这个思路上的准备,比后面任何一条命令都重要。
另外要提醒的是,2021.4 LTS 对编译器和系统版本是有要求的。官方推荐 Ubuntu 18.04 或 20.04,GCC 版本建议 7.5 以上,CMake 要求 3.13 以上(实际用 3.16 以上更稳)。如果你在更新的 Ubuntu(比如 22.04)上编这个老版本,大概率会遇到 GCC 版本过高导致的编译错误,这个后面会专门讲怎么处理。先把这些前置条件在心里过一遍,能省掉大量返工。
2. 编译前的环境准备与依赖梳理
2.1 系统基础依赖:一次装齐,别来回补
依赖这东西最烦人的地方在于,缺一个报一个,来回折腾。我的习惯是先把 2021.4 编译所需的基础包一次性装齐。下面这套命令是我在 Ubuntu 18.04/20.04 上反复验证过的,直接抄:
sudo apt update sudo apt install -y build-essential cmake git wget curl \ libpython3-dev python3-pip python3-numpy \ libgtk-3-dev libavcodec-dev libavformat-dev libswscale-dev \ libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev \ libdc1394-22-dev pkg-config这里每一项都不是随便列的。build-essential提供 GCC、G++、make 这一套编译工具链,是地基;cmake是构建系统入口;libpython3-dev和python3-numpy是为了编 Python 绑定,如果你只要 C++ 接口可以省,但大多数人还是要 Python 的;libtbb2/libtbb-dev是 Intel 线程构建块,OpenVINO 的并行计算依赖它;后面那一串libjpeg、libpng、libtiff、libavcodec等,是 OpenCV 相关依赖——2021.4 默认会编 OpenCV,如果你不想要 OpenCV,可以在 CMake 里关掉,但关之前想清楚你的项目用不用得到图像预处理。
提示:
libtbb2在部分较新的 Ubuntu 上包名可能变成libtbb12之类,装的时候如果提示找不到,用apt search tbb查一下实际包名再装,别硬套命令。
装完之后建议验证一下版本:gcc --version、cmake --version。GCC 如果高于 10,后面编 2021.4 很可能报错,这个在 2.3 节专门说。CMake 低于 3.13 的话,要么升级系统源里的 cmake,要么自己下一个新版二进制包放到 PATH 里,别用太老的版本硬编。
2.2 源码获取:分支、子模块与离线准备
OpenVINO 2021.4 的源码获取有两种方式:直接下官方发布的源码压缩包,或者 git clone 后切到对应 tag。我更推荐后者,因为子模块管理更清晰。命令大致是这样:
git clone https://github.com/openvinotoolkit/openvino.git cd openvino git checkout 2021.4 git submodule update --init --recursivegit submodule update --init --recursive这一步千万别漏,2021.4 的很多核心组件是以子模块形式挂着的,不拉子模块,配置阶段就会报找不到目录。这一步也是最容易卡住的地方——子模块分布在多个代码托管地址上,网络不稳的时候经常拉到一半断掉。我的经验是:分多次执行,断了就重跑,git 会续传已完成的子模块,不要一失败就从头来。
如果你所处的环境访问外网受限,那就得提前准备离线方案。思路是把源码包和所有子模块的仓库都提前在能联网的机器上 clone 好,打包拷过去,然后修改.gitmodules里的地址指向本地路径,或者直接用git config的url.<base>.insteadOf做地址重写。这个操作稍微有点绕,但一旦配好,后续编译就完全离线可控,特别适合内网构建环境。我见过太多人卡在“配置阶段一直转圈下载依赖”上,本质就是没做离线准备。
2.3 编译器版本的坑:GCC 太高反而编不过
这是编旧版本 OpenVINO 最经典的坑,必须单独拎出来说。2021.4 那个年代的代码,对 C++ 标准的处理比较保守,用 GCC 11、12 甚至更高版本去编,经常报一些莫名其妙的模板错误或者-Werror相关的警告被当成错误。典型报错长这样:
error: 'numeric_limits' is not a member of 'std'或者一堆关于std::filesystem的链接错误。这不是你代码写错了,是编译器太新、标准库行为变了。解决办法有两个:一是装一个老版本 GCC(比如 GCC 8 或 9),用update-alternatives切换默认编译器;二是在 CMake 里显式指定编译器路径,并适当放宽警告等级。
我一般选第一种,干净利落:
sudo apt install -y gcc-8 g++-8 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 80然后在 CMake 配置时显式传-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8,双保险。这里要解释一下为什么:OpenVINO 的 CMake 脚本里有些地方会硬编码检查编译器特性,如果默认gcc指向的是高版本,即使你传了gcc-8,某些子项目(尤其是第三方依赖)仍可能用默认编译器去编,导致混编出问题。所以切换默认编译器 + CMake 显式指定两手都要抓。
注意:切换 GCC 版本后,记得把
libstdc++的版本也确认一下,避免运行时链接到不匹配的标准库。用ldd检查编出来的二进制依赖哪个版本的 libstdc++,心里有数。
3. CMake 配置:参数选择决定成败
3.1 核心开关逐个讲:哪些该开,哪些该关
OpenVINO 2021.4 的 CMake 选项非常多,但真正影响你编译结果的就那么几个。我把最关键的列成表,方便对照:
| CMake 参数 | 作用 | 建议值 | 说明 |
|---|---|---|---|
CMAKE_BUILD_TYPE | 构建类型 | Release | 除非调试,否则一律 Release,Debug 编出来又大又慢 |
ENABLE_PYTHON | 是否编 Python 绑定 | ON | 需要 Python 接口就开,纯 C++ 项目可关 |
PYTHON_EXECUTABLE | 指定 Python 解释器 | 你的 python3 路径 | 多 Python 环境时必填,否则可能绑错版本 |
ENABLE_OPENCV | 是否编 OpenCV | 按需 | 不用图像处理就关,能省大量编译时间 |
ENABLE_MYRIAD | 是否编 MYRIAD 插件 | 按需 | 没有神经计算棒就关 |
ENABLE_GNA | 是否编 GNA 插件 | 按需 | 没有 GNA 硬件就关 |
ENABLE_SAMPLES | 是否编示例程序 | OFF | 部署场景不需要,省时间 |
ENABLE_TESTS | 是否编测试 | OFF | 除非你要跑单元测试,否则关掉 |
THREADING | 线程后端 | TBB | 默认 TBB,也可选 OMP,看你的环境 |
这张表的核心逻辑是:只编你真正需要的。我见过有人图省事全开,结果编了两个多小时还失败,最后发现是某个用不到的插件依赖没装。关掉不需要的插件和示例,不仅省时间,更重要的是减少依赖面,降低出错概率。比如ENABLE_SAMPLES=OFF和ENABLE_TESTS=OFF这两个,几乎在所有生产部署场景都该关掉,它们只在开发验证阶段有意义。
3.2 一个可直接复用的配置命令
把上面的思路落成一条命令,我在 Ubuntu 20.04 + GCC 8 环境下实测可用的配置是这样的:
mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=gcc-8 \ -DCMAKE_CXX_COMPILER=g++-8 \ -DENABLE_PYTHON=ON \ -DPYTHON_EXECUTABLE=$(which python3) \ -DENABLE_OPENCV=OFF \ -DENABLE_MYRIAD=OFF \ -DENABLE_GNA=OFF \ -DENABLE_SAMPLES=OFF \ -DENABLE_TESTS=OFF \ -DCMAKE_INSTALL_PREFIX=/opt/openvino-2021.4 \ ..这里CMAKE_INSTALL_PREFIX我特意指到/opt/openvino-2021.4,而不是默认的/usr/local。原因很实际:多版本共存。你机器上可能同时有别的 OpenVINO 版本,装到独立目录,用的时候通过环境变量切换,互不干扰,卸载也干净——直接删目录就行。这个习惯强烈建议养成,比装到系统目录再想办法清理强太多。
配置阶段如果一切正常,最后会打印一段 summary,列出哪些组件会被编译。一定要认真看这段 summary,它是你确认配置是否符合预期的最后一道关。如果发现某个你以为关掉的插件还在列表里,说明参数没生效,得回头查。
3.3 配置阶段最常见的三类报错
配置阶段挂掉,八成是这三类问题。第一类是依赖下载失败,报错里通常带ExternalProject或者git clone字样,本质是网络问题,解法就是前面说的离线准备或者重试。第二类是找不到某个库,比如Could NOT find TBB,说明系统依赖没装全,回去补libtbb-dev。第三类是Python 相关错误,比如找不到 numpy 或者 Python 版本不匹配,这时候要检查PYTHON_EXECUTABLE是否指对了,以及对应的 Python 环境里有没有装 numpy。
我踩过最隐蔽的一个坑是:系统里有多个 Python(系统自带 + conda 环境),CMake 自动探测到了 conda 的 Python,但那个环境里没装 numpy,配置就失败了。后来显式传-DPYTHON_EXECUTABLE=/usr/bin/python3才解决。所以多 Python 环境是配置阶段的高发区,一定要显式指定,别让它自己猜。
4. 编译与安装:耐心与监控并重
4.1 编译命令与并行度选择
配置通过后,编译就是make的事了。但这里有个细节:并行度-j怎么定。理论上是 CPU 核数,但 OpenVINO 编译时单个编译单元内存占用不小,核多内存少的机器直接-j$(nproc)很容易 OOM(内存溢出被系统杀掉)。我的经验公式是:每核至少留 2GB 内存。比如 8 核 16GB 的机器,-j8勉强,-j6更稳;如果是 4 核 8GB,老老实实-j3或-j4。
make -j6编译过程短则二十分钟,长则一两个小时,取决于你开了多少组件和机器性能。这期间建议开着htop或者top盯着内存,一旦发现内存快满,赶紧 Ctrl+C 降并行度重来,别等它被系统杀掉——被 OOM killer 杀掉后,有时候会留下半成品对象文件,下次编译行为诡异。我一般会在编译前先free -h看一眼可用内存,心里有个数。
提示:如果编译中途因为某个文件报错中断,修完问题后重新
make会从断点继续,不用make clean从头来。但如果改的是 CMake 配置,那就得重新跑 cmake 再 make。
4.2 安装与目录结构确认
编译完成后执行安装:
sudo make install装到之前指定的/opt/openvino-2021.4。装完别急着用,先确认目录结构对不对。2021.4 装完的典型结构是:bin/放可执行工具,lib/或lib/intel64/放动态库,include/放头文件,python/下是 Python 包。你可以ls一下确认关键文件在不在,比如lib/libinference_engine.so、python/python3.x/openvino这些。
这里有个容易忽略的点:Python 包是否真的装到了你期望的位置。2021.4 的 Python 绑定安装路径有时会跟着CMAKE_INSTALL_PREFIX走,有时又会装到系统 site-packages,取决于配置。装完用python3 -c "import openvino"试一下,如果导入失败,多半是路径问题,需要手动把安装目录下的 python 路径加到PYTHONPATH,或者用pip install装那个生成的 wheel 包(如果编了的话)。
4.3 环境变量配置:让系统找到它
装完不配环境变量,等于白装。2021.4 提供了一个setupvars.sh脚本,在安装目录下,source一下就能把LD_LIBRARY_PATH、PYTHONPATH、PATH都配好:
source /opt/openvino-2021.4/bin/setupvars.sh但每次开终端都 source 太麻烦,我一般把它写进~/.bashrc,或者更干净的做法是写个独立的环境脚本,需要时手动 source。这里要提醒的是:如果你机器上有多个 OpenVINO 版本,千万别把 setupvars 写死进 bashrc,否则会互相覆盖,排查起来很痛苦。用的时候手动 source 对应版本的脚本,是最不容易出错的方式。
验证安装是否成功,跑一个最简单的导入和版本查询:
python3 -c "from openvino.inference_engine import IECore; print(IECore().get_versions('CPU'))"能打印出 CPU 插件版本信息,就说明核心推理引擎装好了。如果报libxxx.so not found,回去检查LD_LIBRARY_PATH有没有包含安装目录的 lib 路径。
5. 常见问题排查与避坑实录
5.1 编译期报错速查表
把我在实际编译中遇到的高频问题整理成表,遇到报错先来这里对号入座:
| 报错关键词 | 可能原因 | 解决思路 |
|---|---|---|
numeric_limits is not a member of std | GCC 版本过高 | 切 GCC 8/9,或加-include limits |
Could NOT find TBB | 缺 TBB 开发包 | apt install libtbb-dev |
ExternalProject下载失败 | 网络问题 | 离线准备依赖或重试 |
undefined reference to std::filesystem | GCC 版本与标准库不匹配 | 统一编译器版本,检查 libstdc++ |
| Python 绑定导入失败 | PYTHONPATH 未配置 | source setupvars 或手动加路径 |
| 编译被 Killed | 内存不足 | 降-j并行度 |
protobuf相关编译错误 | 系统 protobuf 版本冲突 | 用 OpenVINO 自带的,别用系统的 |
这张表里,protobuf冲突是个隐蔽的坑。系统里如果装了某个版本的 protobuf 开发包,而 OpenVINO 2021.4 自带的 protobuf 版本和它对不上,编译时可能出现头文件和库版本不一致的诡异错误。我的处理原则是:尽量让 OpenVINO 用它自己拉下来的 protobuf,别让 CMake 探测到系统的。如果实在冲突,可以在配置时通过参数指定使用内置版本,或者临时把系统的 protobuf 开发包卸掉(编完再装回来)。
5.2 运行期问题:编过了不等于能用
编译通过只是第一步,运行期的问题同样不少。最常见的是动态库找不到,报错形如error while loading shared libraries: libinference_engine.so。这几乎百分百是LD_LIBRARY_PATH没配好,或者配了但顺序不对被别的库抢先了。用ldd检查你的可执行文件依赖解析情况,能快速定位。
另一个高频问题是插件加载失败。OpenVINO 的推理引擎是插件化架构,CPU、GPU 这些插件是独立的.so文件,运行时按需加载。如果插件目录没在搜索路径里,就会报Failed to load plugin。2021.4 的插件一般在安装目录的lib/intel64/下,确认这个路径在LD_LIBRARY_PATH里,或者通过IEPlugin显式指定插件路径。
还有一个容易被忽略的点:CPU 指令集兼容性。如果你在支持 AVX-512 的机器上编译,编出来的库可能默认用了 AVX-512 指令,拿到不支持的老 CPU 上跑就会非法指令崩溃。跨机器部署时,要么在目标机器上编,要么在配置时限制指令集。这个坑在边缘部署场景特别常见,编的时候没感觉,部署到老设备上直接崩。
5.3 几条用血换来的实操心得
第一条心得:编译前先备份一份干净的源码目录。源码编译过程中,CMake 会在源码树里生成一些文件,有时候配置错了想回退,直接删 build 目录重来最干净,但如果源码树被污染了,就得重新 clone。养成“源码目录只读、build 目录独立”的习惯,能省很多事。
第二条心得:日志一定要留。make -j6 2>&1 | tee build.log,把整个编译日志存下来。出问题的时候,日志里往前翻几百行,往往能找到真正的根因,而不是盯着最后那行报错瞎猜。我排查过一个编译失败,最后一行报的是链接错误,但真正的原因在几百行之前的一个警告被当成了错误。
第三条心得:别迷信官方文档的“一键脚本”。2021.4 官方提供了一些安装脚本,但那些脚本假设的环境往往和你的实际环境有出入,跑失败了反而更难排查。我建议把脚本拆开,一步步手动执行,每一步都确认结果,这样出问题你知道是哪一步。自动化是熟练之后的产物,不是入门时的拐杖。
6. 旧版本编译的差异化处理
6.1 更早版本的构建系统差异
如果你要编的比 2021.4 还早,比如 2020.x 甚至 2019.x,构建系统会有明显差异。早期版本的仓库结构、依赖管理方式、CMake 选项命名都可能不同。比如有些老版本还没有把 ngraph 完全整合进来,需要单独处理;有些版本的 Python 绑定构建方式不一样,不是通过 CMake 的ENABLE_PYTHON,而是单独的 setup 脚本。处理这类版本,核心原则是:以对应版本的官方构建文档为准,别拿 2021.4 的经验硬套。
具体操作上,切到对应 tag 后,先看仓库根目录的README.md和docs/下的构建说明,确认这个版本的依赖清单和构建步骤。老版本的依赖往往更少但也更“原始”,比如可能要求特定版本的 CMake 或者特定版本的 Boost。我编过一个 2019 年的版本,它要求 Boost 1.6x,而系统源里是新版 Boost,最后是手动编了个老 Boost 才过。这种历史包袱,只能一个版本一个版本地啃。
6.2 依赖版本锁定的策略
旧版本编译最大的挑战是依赖版本漂移。你系统里的库都是新的,但老代码期望的是老版本。除了前面说的编译器降级,还要注意这些:CMake 版本别太高(有些老 CMake 脚本在新 CMake 上行为变了)、Python 版本别太新(老版本可能不支持 Python 3.9+)、OpenSSL 等基础库的 API 变化也可能导致编译失败。
我的策略是:能锁就锁,锁不住就隔离。能通过参数锁定依赖版本的,就在 CMake 里锁死;锁不住的(比如系统级的基础库),考虑用容器或者 chroot 隔离出一个接近当年环境的构建环境。用 Docker 起一个 Ubuntu 18.04 基础镜像来编老版本 OpenVINO,是我目前觉得最省心的方式——环境干净、可复现、不污染宿主机。这个思路虽然多了一层容器,但换来的是“一次配好,到处能编”的确定性,非常值。
6.3 版本选择的一点个人建议
最后说点实在的:如果不是非编旧版本不可,尽量往 2021.4 LTS 靠。它是长期支持版本,社区资料相对全,坑也被踩得差不多了。更早的版本,很多问题在网上都搜不到答案,只能自己啃源码。如果项目允许,把老代码适配到 2021.4,长期看比死守一个更老的版本划算。当然,如果硬件或者下游依赖锁死了版本,那就老老实实按上面的隔离策略来,把构建环境做成可复现的镜像,一劳永逸。
我在实际项目里的体会是,源码编译 OpenVINO 这件事,第一次做会觉得步骤繁琐、报错莫名其妙,但把依赖、编译器、CMake 参数这三块理顺之后,后面再编任何版本都是同一套方法论换个参数而已。真正花时间的从来不是敲命令,而是搞清楚每个报错背后的原因。把构建日志留好、把环境隔离好、把参数显式写清楚,这三条做到了,编译成功率能上一个大台阶。