乐于分享
好东西不私藏

Ubuntu 24.04 从源码编译安装 ONNX 与 ONNX Runtime

Ubuntu 24.04 从源码编译安装 ONNX 与 ONNX Runtime

Ubuntu 24.04 从源码编译安装 ONNX 与 ONNX Runtime

目标:让同学在 Ubuntu 24.04 上从源码构建并安装:

  • • ONNX Python 包
  • • ONNX C++ 库与头文件
  • • ONNX Runtime Python wheel
  • • ONNX Runtime C++ 动态库与头文件
  • 最终完成 Linux 环境 ONNX 开发环境的定制化搭建

0. 概念

  • • ONNX:模型交换格式、operator schema、protobuf 表达。
  • • ONNX Runtime:模型执行引擎。

1. 版本与目录约定

export ONNX_VERSION=v1.22.0
export
 ORT_VERSION=v1.25.1
export
 PROTOBUF_VERSION=v5.29.2

建议目录:

export ROOT=$HOME/onnx_lab
export
 SRC=$ROOT/src
export
 BUILD=$ROOT/build
export
 INSTALL=$ROOT/install
export
 WHEELHOUSE=$ROOT/wheelhouse
export
 VENV=$ROOT/venv

2. 安装系统依赖

sudo apt update

sudo
 apt install -y \
  build-essential \
  git \
  curl \
  wget \
  ca-certificates \
  pkg-config \
  ninja-build \
  cmake \
  python3 \
  python3-dev \
  python3-venv \
  python3-pip \
  patchelf \
  zlib1g-dev \
  libssl-dev \
  libffi-dev \
  libbz2-dev \
  liblzma-dev \
  libreadline-dev \
  libsqlite3-dev \
  uuid-dev

检查工具版本:

gcc --version
g++ --version
cmake --version
python3 --version
git --version
ninja --version

建议最低状态:

gcc >= 10
g++ >= 10
cmake >= 3.28
python >= 3.11

Ubuntu 24.04 通常是:

GCC 13
Python 3.12
CMake 3.28.x

3. 创建工作目录与 Python 虚拟环境

export ROOT=$HOME/onnx_lab
export
 SRC=$ROOT/src
export
 BUILD=$ROOT/build
export
 INSTALL=$ROOT/install
export
 WHEELHOUSE=$ROOT/wheelhouse
export
 VENV=$ROOT/venv

mkdir
 -p "$SRC" "$BUILD" "$INSTALL" "$WHEELHOUSE"

创建 venv:

python3 -m venv "$VENV"
source
 "$VENV/bin/activate"

升级 Python 构建工具:

python -m pip install --upgrade pip setuptools wheel build packaging
python -m pip install numpy protobuf typing_extensions ml_dtypes nanobind pybind11 cmake ninja

确认当前 Python 来自 venv:

which python
python --version
python -c "import sys; print(sys.executable)"

预期路径类似:

/home/cpp/onnx_lab/venv/bin/python

4. 从源码构建 Protobuf

ONNX 的核心文件格式基于 Protobuf。
为了减少系统 Protobuf、pip Protobuf、源码 Protobuf 混用,本手册单独构建一份静态 Protobuf 给 ONNX 使用。

cd "$SRC"

git clone --recursive https://github.com/protocolbuffers/protobuf.git
cd
 protobuf
git checkout "$PROTOBUF_VERSION"
git submodule update --init --recursive

配置:

cmake -S . -B "$BUILD/protobuf" \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$INSTALL/protobuf" \
  -Dprotobuf_BUILD_SHARED_LIBS=OFF \
  -Dprotobuf_BUILD_TESTS=OFF \
  -Dprotobuf_BUILD_EXAMPLES=OFF \
  -DCMAKE_POSITION_INDEPENDENT_CODE=ON

编译安装:

cmake --build "$BUILD/protobuf" --parallel
cmake --install "$BUILD/protobuf"

设置当前 shell 环境:

export PATH="$INSTALL/protobuf/bin:$PATH"
export
 CMAKE_PREFIX_PATH="$INSTALL/protobuf:${CMAKE_PREFIX_PATH:-}"
export
 LD_LIBRARY_PATH="$INSTALL/protobuf/lib:${LD_LIBRARY_PATH:-}"

检查:

which protoc
protoc --version

预期类似:

libprotoc 29.2

5. 从源码构建并安装 ONNX Python 包

cd "$SRC"

git clone --recursive https://github.com/onnx/onnx.git
cd
 onnx
git checkout "$ONNX_VERSION"
git submodule update --init --recursive

先清理已有 ONNX,避免误用 pip wheel:

python -m pip uninstall -y onnx

设置 ONNX 构建参数:

export ONNX_PROTOC_EXECUTABLE="$INSTALL/protobuf/bin/protoc"
export
 CMAKE_PREFIX_PATH="$INSTALL/protobuf:${CMAKE_PREFIX_PATH:-}"

export
 CMAKE_ARGS="-DONNX_USE_LITE_PROTO=ON \
-DONNX_USE_PROTOBUF_SHARED_LIBS=OFF \
-DONNX_CUSTOM_PROTOC_EXECUTABLE=$INSTALL/protobuf/bin/protoc \
-DCMAKE_PREFIX_PATH=$INSTALL/protobuf"

构建 wheel:

python -m build --wheel --no-isolation

安装 wheel:

python -m pip install --force-reinstall dist/onnx-*.whl

验证:

python - <<'PY'
import onnx
print
("onnx =", onnx.__version__)
print
("onnx path =", onnx.__file__)
PY

预期路径应该位于当前 venv 中。


6. 从源码构建并安装 ONNX C++ 库

ONNX Python 包用于 Python 侧模型构造、检查、序列化。
如果你希望课堂上也展示 ONNX 的 C 侧模型结构,可以单独安装 ONNX C 库。

cd "$SRC/onnx"

配置 C++ 构建:

cmake -S . -B "$BUILD/onnx-cpp" \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$INSTALL/onnx" \
  -DCMAKE_PREFIX_PATH="$INSTALL/protobuf" \
  -DONNX_BUILD_PYTHON=OFF \
  -DONNX_BUILD_TESTS=OFF \
  -DONNX_USE_LITE_PROTO=ON \
  -DONNX_USE_PROTOBUF_SHARED_LIBS=OFF \
  -DONNX_CUSTOM_PROTOC_EXECUTABLE="$INSTALL/protobuf/bin/protoc" \
  -DCMAKE_CXX_STANDARD=17

编译安装:

cmake --build "$BUILD/onnx-cpp" --parallel
cmake --install "$BUILD/onnx-cpp"

检查安装结果:

find "$INSTALL/onnx" -maxdepth 3 -type f | head -50
find "$INSTALL/onnx" -maxdepth 5 -type f | grep -E 'onnx.*Config|\.so$|\.a$|\.h$' | head -50

通常应看到:

include/onnx
lib
lib/cmake/ONNX

7. ONNX C++ 最小验证

创建测试目录:

mkdir -p "$ROOT/smoke_onnx_cpp"
cd
 "$ROOT/smoke_onnx_cpp"

创建 main.cpp

#include <onnx/onnx_pb.h>
#include <iostream>


int main()
{
    onnx::ModelProto model;
    model.set_ir_version(10);

    std::cout << "ONNX C++ smoke test OK\n";
    std::cout << "ir_version = " << model.ir_version() << "\n";
    return
 0;
}

创建 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project
(onnx_cpp_smoke LANGUAGES CXX)

set
(CMAKE_CXX_STANDARD 17)
set
(CMAKE_CXX_STANDARD_REQUIRED ON)

if
(NOT DEFINED ONNX_ROOT)
  message
(FATAL_ERROR "Please pass -DONNX_ROOT=/path/to/install/onnx")
endif
()

if
(NOT DEFINED PROTOBUF_ROOT)
  message
(FATAL_ERROR "Please pass -DPROTOBUF_ROOT=/path/to/install/protobuf")
endif
()

list
(APPEND CMAKE_PREFIX_PATH "${ONNX_ROOT}" "${PROTOBUF_ROOT}")

find_package
(Protobuf CONFIG REQUIRED)
find_package
(ONNX CONFIG REQUIRED)

add_executable
(onnx_cpp_smoke main.cpp)

if
(TARGET ONNX::onnx)
  target_link_libraries
(onnx_cpp_smoke PRIVATE ONNX::onnx ONNX::onnx_proto)
elseif
(TARGET onnx)
  target_link_libraries
(onnx_cpp_smoke PRIVATE onnx onnx_proto)
else
()
  message
(FATAL_ERROR "Cannot find ONNX CMake targets")
endif
()

配置、编译、运行:

cmake -S . -B build \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DONNX_ROOT="$INSTALL/onnx" \
  -DPROTOBUF_ROOT="$INSTALL/protobuf"

cmake --build build --parallel
./build/onnx_cpp_smoke

预期输出:

ONNX C++ smoke test OK
ir_version = 10

如果运行时报找不到 .so,临时设置:

export LD_LIBRARY_PATH="$INSTALL/onnx/lib:$INSTALL/protobuf/lib:${LD_LIBRARY_PATH:-}"

然后重试。


8. 从源码构建 ONNX Runtime:Python wheel + C++ 动态库

获取源码:

cd "$SRC"

git clone --recursive https://github.com/microsoft/onnxruntime.git
cd
 onnxruntime
git checkout "$ORT_VERSION"
git submodule update --init --recursive

确保没有装过其他 ORT 包:

python -m pip uninstall -y onnxruntime onnxruntime-gpu onnxruntime-training

构建 CPU:

./build.sh \
  --config Release \
  --update \
  --build \
  --parallel \
  --build_shared_lib \
  --build_wheel \
  --compile_no_warning_as_error

9. 安装 ONNX Runtime Python wheel

ORT wheel 通常位于:

$SRC/onnxruntime/build/Linux/Release/dist

安装:

cd "$SRC/onnxruntime"
python -m pip install --force-reinstall build/Linux/Release/dist/onnxruntime-*.whl

验证:

python - <<'PY'
import onnxruntime as ort
print
("onnxruntime =", ort.__version__)
print
("providers =", ort.get_available_providers())
PY

预期至少包含:

CPUExecutionProvider

10. 整理 ONNX Runtime C++ 安装目录

ORT 构建产物通常在:

$SRC/onnxruntime/build/Linux/Release

创建安装目录:

mkdir -p "$INSTALL/onnxruntime/include"
mkdir
 -p "$INSTALL/onnxruntime/lib"

复制头文件:

cp -a "$SRC/onnxruntime/include/onnxruntime" "$INSTALL/onnxruntime/include/"

复制动态库和静态导入相关文件:

find "$SRC/onnxruntime/build/Linux/Release" \
  -maxdepth 3 \
  \( -name "libonnxruntime.so*" -o -name "onnxruntime*.so*" \) \
  -exec cp -a {} "$INSTALL/onnxruntime/lib/" \;

检查:

find "$INSTALL/onnxruntime/include" -type f | grep "onnxruntime_cxx_api.h"
find "$INSTALL/onnxruntime/lib" -maxdepth 1 -type f -o -type l

应该看到:

onnxruntime_c_api.h
onnxruntime_cxx_api.h
libonnxruntime.so

设置运行时库路径:

export LD_LIBRARY_PATH="$INSTALL/onnxruntime/lib:${LD_LIBRARY_PATH:-}"

也可以写入当前 venv 的激活脚本:

cat >> "$VENV/bin/activate" <<EOF

# ONNX / ONNX Runtime lab paths
export PATH="$INSTALL/protobuf/bin:\$PATH"
export LD_LIBRARY_PATH="$INSTALL/onnxruntime/lib:$INSTALL/onnx/lib:$INSTALL/protobuf/lib:\${LD_LIBRARY_PATH:-}"
export CMAKE_PREFIX_PATH="$INSTALL/onnxruntime:$INSTALL/onnx:$INSTALL/protobuf:\${CMAKE_PREFIX_PATH:-}"
EOF

重新激活:

deactivate
source
 "$VENV/bin/activate"

11. Python 端完整验证:构造 ONNX 模型并用 ORT CPU 推理

创建验证目录:

mkdir -p "$ROOT/smoke_python"
cd
 "$ROOT/smoke_python"

创建 verify_python.py

import numpy as np
import
 onnx
import
 onnxruntime as ort
from
 onnx import helper, TensorProto

print
("onnx =", onnx.__version__)
print
("onnxruntime =", ort.__version__)
print
("providers =", ort.get_available_providers())

X = helper.make_tensor_value_info("x", TensorProto.FLOAT, [None, 3])
Y = helper.make_tensor_value_info("y", TensorProto.FLOAT, [None, 3])

one = helper.make_tensor("one", TensorProto.FLOAT, [1], [1.0])
node = helper.make_node("Add", ["x", "one"], ["y"])

graph = helper.make_graph(
    [node],
    "add_one_graph"
,
    [X],
    [Y],
    [one],
)

model = helper.make_model(
    graph,
    producer_name="onnx_lab",
    opset_imports=[helper.make_operatorsetid("", 13)],
)

# 使用保守 IR version,避免 ONNX 生成的模型过新而 ORT 无法加载。

model.ir_version = 10

onnx.checker.check_model(model)
onnx.save(model, "add_one.onnx")

sess = ort.InferenceSession(
    "add_one.onnx"
,
    providers=["CPUExecutionProvider"],
)

x = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]], dtype=np.float32)

y = sess.run(None, {"x": x})[0]

print
("input:")
print
(x)
print
("output:")
print
(y)

expected = x + 1.0
np.testing.assert_allclose(y, expected)

print
("Python ONNX + ONNX Runtime CPU smoke test OK")

运行:

python verify_python.py

预期输出包含:

CPUExecutionProvider
Python ONNX + ONNX Runtime CPU smoke test OK

12. C++ 端完整验证:用 ONNX Runtime C++ API 推理

复用 Python 端生成的模型:

$ROOT/smoke_python/add_one.onnx

创建 C++ 测试目录:

mkdir -p "$ROOT/smoke_ort_cpp"
cd
 "$ROOT/smoke_ort_cpp"
cp
 "$ROOT/smoke_python/add_one.onnx" .

创建 main.cpp

#include <onnxruntime_cxx_api.h>

#include <array>

#include <iostream>

#include <vector>


int main()
{
    Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "ort_cpp_smoke")
;

    Ort::SessionOptions session_options;
    session_options.SetIntraOpNumThreads(1);
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_BASIC);

    const
 char* model_path = "add_one.onnx";

    Ort::Session session(env, model_path, session_options)
;

    std::array<float, 6> input_data{
        1.0f
, 2.0f, 3.0f,
        4.0f
, 5.0f, 6.0f
    };

    std::array<int64_t, 2> input_shape{2, 3};

    Ort::MemoryInfo memory_info =
        Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);

    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[] = {"x"};
    const
 char* output_names[] = {"y"};

    auto
 outputs = session.Run(
        Ort::RunOptions{nullptr},
        input_names,
        &input_tensor,
        1
,
        output_names,
        1

    );

    float
* y = outputs[0].GetTensorMutableData<float>();

    std::cout << "output:\n";
    for
 (size_t i = 0; i < input_data.size(); ++i) {
        std::cout << y[i] << " ";
    }
    std::cout << "\n";

    if
 (y[0] != 2.0f || y[5] != 7.0f) {
        std::cerr << "Unexpected result\n";
        return
 1;
    }

    std::cout << "C++ ONNX Runtime CPU smoke test OK\n";
    return
 0;
}

创建 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project
(ort_cpp_smoke LANGUAGES CXX)

set
(CMAKE_CXX_STANDARD 20)
set
(CMAKE_CXX_STANDARD_REQUIRED ON)

if
(NOT DEFINED ORT_ROOT)
  message
(FATAL_ERROR "Please pass -DORT_ROOT=/path/to/install/onnxruntime")
endif
()

add_executable
(ort_cpp_smoke main.cpp)

target_include_directories
(ort_cpp_smoke PRIVATE
  "${ORT_ROOT}/include/onnxruntime/core/session"

)

target_link_directories
(ort_cpp_smoke PRIVATE
  "${ORT_ROOT}/lib"

)

target_link_libraries
(ort_cpp_smoke PRIVATE onnxruntime)

set_target_properties
(ort_cpp_smoke PROPERTIES
  BUILD_RPATH "${ORT_ROOT}/lib"
  INSTALL_RPATH "${ORT_ROOT}/lib"
)

配置、构建、运行:

cmake -S . -B build \
  -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DORT_ROOT="$INSTALL/onnxruntime"

cmake --build build --parallel

export
 LD_LIBRARY_PATH="$INSTALL/onnxruntime/lib:${LD_LIBRARY_PATH:-}"
./build/ort_cpp_smoke

预期输出:

output:
2 3 4 5 6 7
C++ ONNX Runtime CPU smoke test OK

13. 最终检查清单

13.1 Python 检查

python - <<'PY'
import onnx
import onnxruntime as ort

print
("onnx =", onnx.__version__)
print
("onnxruntime =", ort.__version__)
print
("providers =", ort.get_available_providers())
PY

必须看到:

CPUExecutionProvider

13.2 ONNX C++ 检查

"$ROOT/smoke_onnx_cpp/build/onnx_cpp_smoke"

必须输出:

ONNX C++ smoke test OK

13.3 ONNX Runtime C++ 检查

"$ROOT/smoke_ort_cpp/build/ort_cpp_smoke"

必须输出:

C++ ONNX Runtime CPU smoke test OK

14. 常见失败与修复

问题 1:误用了系统 Python,而不是 venv Python

检查:

which python
python -c "import sys; print(sys.executable)"

正确路径应类似:

/home/your_name/onnx_lab/venv/bin/python

修复:

source "$VENV/bin/activate"

问题 2:protoc 不是手工构建的版本

检查:

which protoc
protoc --version

正确路径应类似:

/home/your_name/onnx_lab/install/protobuf/bin/protoc

修复:

export PATH="$INSTALL/protobuf/bin:$PATH"
export
 CMAKE_PREFIX_PATH="$INSTALL/protobuf:${CMAKE_PREFIX_PATH:-}"

问题 3:ONNX 构建时找不到 Protobuf

检查:

echo "$CMAKE_PREFIX_PATH"
echo
 "$ONNX_PROTOC_EXECUTABLE"
echo
 "$CMAKE_ARGS"

修复:

export ONNX_PROTOC_EXECUTABLE="$INSTALL/protobuf/bin/protoc"
export
 CMAKE_PREFIX_PATH="$INSTALL/protobuf:${CMAKE_PREFIX_PATH:-}"

export
 CMAKE_ARGS="-DONNX_USE_LITE_PROTO=ON \
-DONNX_USE_PROTOBUF_SHARED_LIBS=OFF \
-DONNX_CUSTOM_PROTOC_EXECUTABLE=$INSTALL/protobuf/bin/protoc \
-DCMAKE_PREFIX_PATH=$INSTALL/protobuf"

然后重新构建 ONNX wheel:

cd "$SRC/onnx"
rm
 -rf build dist *.egg-info
python -m build --wheel --no-isolation
python -m pip install --force-reinstall dist/onnx-*.whl

问题 4:libonnxruntime.so: cannot open shared object file

现象:

error while loading shared libraries: libonnxruntime.so: cannot open shared object file

修复:

export LD_LIBRARY_PATH="$INSTALL/onnxruntime/lib:${LD_LIBRARY_PATH:-}"

或者在 CMake 中设置 rpath:

set_target_properties(your_target PROPERTIES
  BUILD_RPATH "/home/your_name/onnx_lab/install/onnxruntime/lib"
  INSTALL_RPATH "/home/your_name/onnx_lab/install/onnxruntime/lib"
)

问题 5:ORT 构建中途下载依赖失败

原因通常是:

  • • 网络不稳定;
  • • GitHub 连接慢;
  • • 子模块未初始化完整;
  • • 依赖缓存损坏。

修复:

cd "$SRC/onnxruntime"
git submodule sync --recursive
git submodule update --init --recursive

然后重试:

./build.sh \
  --config Release \
  --update \
  --build \
  --parallel \
  --build_shared_lib \
  --build_wheel \
  --compile_no_warning_as_error

如果仍失败,尝试:

./build.sh \
  --config Release \
  --update \
  --build \
  --parallel \
  --build_shared_lib \
  --build_wheel \
  --compile_no_warning_as_error \
  --use_vcpkg

问题 6:内存不足,编译被 killed

现象:

c++: fatal error: Killed signal terminated program cc1plus

原因:

并行编译过多导致内存不足。

修复:

降低并行度:

export MAX_JOBS=2

或者:

./build.sh \
  --config Release \
  --update \
  --build \
  --build_shared_lib \
  --build_wheel \
  --compile_no_warning_as_error \
  --parallel 1

如果 --parallel 2 不被当前脚本接受,就去掉 --parallel,让构建系统使用较保守并行度。


问题 7:模型 ONNX checker 通过,但 ORT 加载失败

这不是矛盾。

onnx.checker.check_model(model) 只说明:

这个模型在 ONNX 格式层面是合法的。

它不说明:

当前 ONNX Runtime 版本一定实现了这些 operator。
当前 CPUExecutionProvider 一定支持这些 dtype。
当前 ORT 一定支持这个 IR version。

所以课堂最小模型里故意使用:

model.ir_version = 10
opset_imports=[helper.make_operatorsetid("", 13)]

这是为了降低“模型格式比 runtime 新”的风险。