PaddleOCR 3.7 在 Jetson AGX Orin 上编译 C++ 推理部署
记录在 Jetson AGX Orin 上编译 PaddleOCR 3.7 C++ 推理运行库,适配 CUDA/Jetson 环境,并完成 PP-OCRv6 GPU 部署的过程。
PaddleOCR 3.7 在 Jetson AGX Orin 上编译 C++ 推理部署
本文记录使用 PaddleOCR 3.7 源码,在 Jetson AGX Orin 上编译 Paddle Inference C++ 运行库和 PaddleOCR deploy/cpp_infer,并完成 PP-OCRv6 GPU 推理的过程。
本文省略源码、依赖和模型的下载过程,只记录源码准备完成后的编译、适配、排错和部署。
1. 最终结论
PaddleOCR 3.7 的 C++ 推理程序可以在 Jetson AGX Orin 上编译并使用 CUDA GPU 推理。
最终验证结果:
ppocr生成为 ARM64/aarch64 ELF 可执行文件;- Paddle Inference 动态库成功链接;
ldd检查无缺失动态库;- PP-OCRv6 medium 检测模型和识别模型均可加载;
- 在 Orin GPU 上完成检测+识别,返回码为 0;
- 目标机运行阶段不需要 Python 版 PaddleOCR。
需要注意的是,PP-OCRv6 的 safetensors 模型属于 Transformers 引擎格式,不能直接交给 C++ cpp_infer。C++ 部署应使用对应的 Paddle 静态推理模型,即包含 inference.json 和 inference.pdiparams 的模型目录。
2. 源码和环境
2.1 源码版本
使用用户提供的 PaddleOCR 3.7 源码:
/Users/guwei/Downloads/PaddleOCR-main.zip
源码 SHA-256:
309a8283049df77a38cb631ecfc75647788289c7ef6eb219fb7ec76cbceac193
PaddleOCR C++ demo 位于:
PaddleOCR-main/deploy/cpp_infer
PaddleOCR 源码本身不包含完整的 Paddle Inference C++ 运行库,因此还需要准备 Paddle 源码,用来生成 PADDLE_LIB。本次使用 Paddle develop 源码,提交版本为:
4887335c9679
整个过程没有使用 Paddle 3.2.0 或 PaddleOCR 3.2.0。
2.2 Orin 环境
本次实际编译环境如下:
| 项目 | 版本 |
|---|---|
| 设备 | Jetson AGX Orin Developer Kit 64GB |
| CPU 架构 | aarch64 |
| 系统 | Ubuntu 22.04.5 |
| JetPack | 6.2.1 |
| L4T | 36.4.7 |
| GCC/G++ | 11.4.0 |
| CMake | 3.22.1 |
| CUDA | 12.6.68 |
| cuDNN | 9.3 |
| TensorRT | 10.3 |
| OpenCV | 4.8.0 |
Orin 的 GPU 架构为 Ampere,CUDA 计算能力为 8.7。因此 Paddle runtime 编译时使用 CUDA_ARCH_NAME=Ampere,编译输出中可以看到 compute_87/sm_87。
3. 编译目录规划
在 Orin 上设置路径变量,避免源码、构建目录和最终部署目录混在一起:
export WORK_ROOT=/home/wooley/paddle_build
export PADDLE_SRC=$WORK_ROOT/Paddle-develop-git
export PADDLE_BUILD=$PADDLE_SRC/build-orin-cxx
export OCR_SRC=$WORK_ROOT/PaddleOCR-main
export OCR_BUILD=$OCR_SRC/deploy/cpp_infer/build-orin-gpu
export PADDLE_LIB=$PADDLE_BUILD/paddle_inference_install_dir
目录关系:
Paddle-develop-git/ Paddle Inference runtime 源码
PaddleOCR-main/ PaddleOCR 3.7 源码
build-orin-cxx/ Paddle runtime 构建目录
build-orin-gpu/ C++ demo 构建目录
paddleV6/ 最终运行部署目录
4. 编译 Paddle Inference C++ runtime
4.1 配置原则
Orin 上首次编译建议先使用 CUDA GPU、关闭 TensorRT,先把 Paddle 原生 GPU 推理链路跑通。TensorRT 可以在基础版本验证完成后再单独开启。
主要配置:
WITH_GPU=ON:启用 CUDA GPU;WITH_ARM=ON:启用 ARM/aarch64;WITH_NV_JETSON=ON:启用 Jetson 适配;WITH_TENSORRT=OFF:减少 TensorRT 版本耦合;WITH_PYTHON=OFF:只构建 C++ runtime;ON_INFER=ON:生成推理库;WITH_MKL=OFF:Orin 不使用 Intel MKL;WITH_TESTING=OFF、WITH_INFERENCE_API_TEST=OFF:减少编译目标;WITH_NCCL=OFF、WITH_DISTRIBUTE=OFF:单机单卡不需要分布式组件;WITH_FLASHATTN=OFF、WITH_CUTLASS=OFF:先降低 CUDA 适配复杂度。
4.2 CMake 配置
cmake -S "$PADDLE_SRC" \
-B "$PADDLE_BUILD" \
-G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_GPU=ON \
-DWITH_ARM=ON \
-DWITH_NV_JETSON=ON \
-DWITH_TENSORRT=OFF \
-DWITH_FLASHATTN=OFF \
-DWITH_CUTLASS=OFF \
-DWITH_PYTHON=OFF \
-DON_INFER=ON \
-DWITH_SETUP_INSTALL=ON \
-DWITH_TESTING=OFF \
-DWITH_INFERENCE_API_TEST=OFF \
-DWITH_MKL=OFF \
-DWITH_AVX=OFF \
-DWITH_XBYAK=OFF \
-DWITH_SHARED_PHI=OFF \
-DWITH_SYSTEM_BLAS=OFF \
-DWITH_ONEDNN=OFF \
-DWITH_OPENVINO=OFF \
-DWITH_ONNXRUNTIME=OFF \
-DWITH_DISTRIBUTE=OFF \
-DWITH_NCCL=OFF \
-DWITH_RCCL=OFF \
-DWITH_CINN=OFF \
-DWITH_CRYPTO=OFF \
-DWITH_POCKETFFT=OFF \
-DWITH_FLAGCX=OFF \
-DWITH_CUSPARSELT=OFF \
-DWITH_PROFILER=OFF \
-DCUDA_ARCH_NAME=Ampere
WITH_ARM=ON 和 WITH_NV_JETSON=ON 建议同时指定。只指定 Jetson 选项时,部分 CMake 逻辑会较晚才设置 ARM 选项,容易造成配置和编译阶段不一致。
4.3 AArch64 大型动态库链接问题
Paddle runtime GPU 组件较多,生成大型 libpaddle_inference.so 时,默认 GNU ld.bfd 可能出现:
R_AARCH64_CALL26 relocation truncated
这是 AArch64 分支跳转距离超出默认范围导致的链接错误,不是 CUDA kernel 编译错误。
给 ld.bfd 增加 -Wl,--long-plt 会出现:
ld.bfd: unrecognized option '--long-plt'
Orin 系统中存在支持该选项的 GNU gold,因此重新配置 Paddle runtime,显式指定 gold:
cmake -S "$PADDLE_SRC" \
-B "$PADDLE_BUILD" \
-DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=gold -Wl,--long-plt" \
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=gold -Wl,--long-plt"
然后重新构建:
cmake --build "$PADDLE_BUILD" -j 8 --target paddle_inference_shared
cmake --build "$PADDLE_BUILD" -j 8 --target inference_lib_dist
最终使用 inference_lib_dist 生成的目录作为 PADDLE_LIB:
$PADDLE_BUILD/paddle_inference_install_dir
该目录应包含:
paddle/include/
paddle/lib/libpaddle_inference.so
paddle/lib/libpaddle_inference.a
paddle/lib/libcommon.so
third_party/install/
5. Paddle runtime 的 CUDA/Jetson 兼容修复
5.1 ROIAlign 线程变量类型不匹配
编译 Jetson CUDA 路径时,paddle/phi/kernels/gpu/roi_align_kernel.cu 出现:
argument of type "uint32_t *" is incompatible with parameter of type "int *"
将 Jetson 分支中的:
uint32_t threads = kNumCUDAThreads;
改为:
int threads = kNumCUDAThreads;
原因是 Jetson 分支调用的 ChangeThreadNum 接口接收 int*,原代码使用了 uint32_t*。该修改只解决类型匹配问题,不改变 kernel 算法。
5.2 CUDA 12.6 下 Thrust 内部宏不存在
编译 paddle/phi/kernels/funcs/shuffle_batch.cu.h 时出现 __thrust_exec_check_disable__ 未声明或找不到的错误。
原代码:
__thrust_exec_check_disable__ __host__ __device__
改为:
__host__ __device__
CUDA 12.6 当前 Thrust 版本中,该内部宏已经不可用。删除它不会改变函数的 host/device 属性。
5.3 非阻塞编译警告
src/utils/ilogger.cc 可能出现 sprintf 格式溢出相关 warning。该 warning 不影响生成 ppocr,本次没有作为阻塞错误处理。若生产环境要求告警清零,可以再单独修改日志格式化代码。
6. 编译 PaddleOCR C++ demo
6.1 Jetson OpenCV CMake 路径差异
PaddleOCR C++ demo 的 CMake 配置会按照以下形式查找 OpenCV:
${OPENCV_DIR}/lib64/cmake/opencv4
但 Jetson 系统中的 OpenCV CMake 配置实际位于:
/usr/lib/cmake/opencv4
建立一个只用于 CMake 查找的前缀目录:
export OPENCV_PREFIX=$WORK_ROOT/opencv-prefix
mkdir -p "$OPENCV_PREFIX/lib64/cmake"
ln -s /usr/lib/cmake/opencv4 "$OPENCV_PREFIX/lib64/cmake/opencv4"
ln -s /usr/lib "$OPENCV_PREFIX/lib"
这不会修改系统 OpenCV,只是让 demo 的固定目录拼接逻辑找到 Jetson 的 OpenCV 配置和库文件。
6.2 CMake 配置
cmake -S "$OCR_SRC/deploy/cpp_infer" \
-B "$OCR_BUILD" \
-G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DPADDLE_LIB="$PADDLE_LIB" \
-DWITH_GPU=ON \
-DWITH_MKL=OFF \
-DWITH_STATIC_LIB=OFF \
-DUSE_FREETYPE=OFF \
-DOPENCV_DIR="$OPENCV_PREFIX" \
-DCUDA_LIB=/usr/local/cuda/lib64 \
-DCUDNN_LIB=/usr/lib/aarch64-linux-gnu \
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=gold -Wl,--long-plt"
这里使用 WITH_STATIC_LIB=OFF,让 ppocr 动态链接 libpaddle_inference.so,便于最终部署时单独管理 runtime。
USE_FREETYPE=OFF 是为了减少字体库和 OpenCV freetype 模块的额外依赖。关闭后,核心 OCR 推理不受影响;部分单模块可视化可能提示 OpenCV 没有 freetype 支持。
6.3 编译
cmake --build "$OCR_BUILD" -j 8
成功后可执行文件为:
$OCR_BUILD/ppocr
验证文件类型:
file "$OCR_BUILD/ppocr"
应看到类似:
ELF 64-bit LSB pie executable, ARM aarch64
7. PP-OCRv6 模型兼容处理
7.1 safetensors 不能直接用于 C++
PP-OCRv6_medium_rec_safetensors 是 Transformers 引擎模型,官方使用方式类似:
from paddleocr import TextRecognition
model = TextRecognition(
model_name="PP-OCRv6_medium_rec",
engine="transformers",
)
它的 model.safetensors 不能直接被 deploy/cpp_infer 的静态模型加载器读取。
C++ demo 要求模型目录至少包含:
inference.json
inference.pdiparams
inference.yml
因此本次部署使用官方静态模型:
PP-OCRv6_medium_det
PP-OCRv6_medium_rec
这两个模型已经包含 Paddle Inference 格式文件,不需要在 Orin 上安装 Python 做转换。自定义 safetensors 权重如果要进入 C++,应优先获取对应的 .pdparams checkpoint,再使用 PaddleOCR 的 tools/export_model.py 导出静态模型;只有 safetensors 时,需要自行完成模型结构和参数名映射,不能简单改文件后缀。
7.2 V6 检测模型导致 std::out_of_range
使用官方 V6 静态检测模型首次运行时,程序异常退出:
terminate called after throwing an instance of 'std::out_of_range'
what(): _Map_base::at
原因不是动态库,也不是模型权重损坏,而是 V6 的 inference.yml 中:
PreProcess:
transform_ops:
- DetResizeForTest: null
当前 C++ 检测模块却无条件读取:
pre_tfs.at("DetResizeForTest.resize_long")
涉及文件:
deploy/cpp_infer/src/modules/text_detection/predictor.cc
将无条件读取改为可选读取:
auto resize_long_it = pre_tfs.find("DetResizeForTest.resize_long");
if (resize_long_it != pre_tfs.end() && !resize_long_it->second.empty()) {
resize_param.resize_long = std::stoi(resize_long_it->second);
}
这样处理后:
- 老模型存在
resize_long时,保持原逻辑; - V6 模型为
null时,使用 pipeline 参数中的limit_side_len、limit_type和max_side_limit; - 不修改 V6 模型结构或推理计算。
重新编译 ppocr 后,V6 检测模块和识别模块均可正常创建。
8. 整理最终部署目录
最终部署目录建议与构建目录分离:
/home/wooley/paddleV6/
├── bin/
│ └── ppocr
├── runtime/
│ └── paddle_inference_install_dir/
│ ├── paddle/include/
│ ├── paddle/lib/
│ └── third_party/
├── models/
│ ├── PP-OCRv6_medium_det/
│ │ ├── inference.json
│ │ ├── inference.pdiparams
│ │ ├── inference.yml
│ │ └── configuration.json
│ └── PP-OCRv6_medium_rec/
│ ├── inference.json
│ ├── inference.pdiparams
│ └── inference.yml
├── examples/
│ └── general_ocr_002.png
├── env.sh
├── ocr_v6.sh
├── README.md
└── BUILD_INFO.md
8.1 运行环境变量
env.sh 的核心内容如下:
#!/usr/bin/env bash
set -euo pipefail
PADDLEV6_HOME="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
export PADDLEV6_HOME
export LD_LIBRARY_PATH="$PADDLEV6_HOME/runtime/paddle_inference_install_dir/paddle/lib:/usr/local/cuda/lib64:/usr/lib/aarch64-linux-gnu:/usr/lib/aarch64-linux-gnu/tegra:/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
Paddle runtime 和 demo 的动态库由部署目录提供;CUDA、cuDNN、OpenCV、系统 C++ runtime 仍使用 JetPack 系统环境。
8.2 运行脚本
cd /home/wooley/paddleV6
./ocr_v6.sh /path/to/input.png
指定输出目录:
./ocr_v6.sh /path/to/input.png /home/wooley/paddleV6/output
追加其他参数,例如使用 FP16:
./ocr_v6.sh /path/to/input.png /home/wooley/paddleV6/output --precision=fp16
脚本默认关闭文档方向分类、文档矫正和文本行方向分类,只运行文本检测和文本识别,适合先验证主链路。
9. 最终验证
9.1 动态库检查
cd /home/wooley/paddleV6
source ./env.sh
ldd ./bin/ppocr | grep "not found"
没有任何输出表示动态库依赖完整。
9.2 PP-OCRv6 GPU 推理
cd /home/wooley/paddleV6
./ocr_v6.sh ./examples/general_ocr_002.png ./output
运行日志中应出现:
Create model: PP-OCRv6_medium_det.
Create model: PP-OCRv6_medium_rec.
本次实际运行返回码为 0,并生成:
general_ocr_002_res.json
general_ocr_002_ocr_res_img.png
识别结果包含中文和英文,例如:
登机牌
BOARDING PASS
福州
FUZHOU
ZHANGQIWEI
10. 常见问题速查
| 现象 | 原因 | 处理方式 |
|---|---|---|
R_AARCH64_CALL26 relocation truncated |
大型 ARM64 动态库分支跳转超范围 | 使用 ld.gold 和 -Wl,--long-plt |
ld.bfd: unrecognized option '--long-plt' |
当前 linker 是 ld.bfd |
加 -fuse-ld=gold |
uint32_t* 与 int* 不兼容 |
Jetson ROIAlign 分支类型不匹配 | 将线程变量改为 int |
__thrust_exec_check_disable__ 未声明 |
CUDA 12.6 的 Thrust 已移除内部宏 | 删除该宏,保留 __host__ __device__ |
OpenCV_DIR 找不到 |
Jetson OpenCV 配置不在 demo 预期目录 | 建立 opencv-prefix/lib64/cmake/opencv4 软链接 |
std::out_of_range _Map_base::at |
V6 检测 inference.yml 中 DetResizeForTest 为 null |
改为 find() 可选读取 resize_long |
libpaddle_inference.so not found |
未设置运行时库路径 | source /home/wooley/paddleV6/env.sh |
C++ 无法加载 model.safetensors |
这是 Transformers 模型,不是 Paddle static 模型 | 使用 inference.json + inference.pdiparams |
| 单模块可运行但可视化图片没有字体 | 关闭了 freetype 或 OpenCV 未编译 freetype | 不影响 OCR 推理;需要可视化字体时单独启用 freetype |