在 NVIDIA DGX Spark 上让 RAGFlow DeepDoc 真正使用 GPU:ONNX Runtime aarch64/CUDA 13 源码编译实录

本文以技术爱好者的视角,记录一次在 NVIDIA DGX Spark 上运行 RAGFlow 0.26.4 源码,并让 DeepDoc OCR 真正使用 GPU 的完整实验。

文章目录

摘要

RAGFlow 的 DeepDoc 文档解析依赖 ONNX Runtime 执行 OCR 模型。在常见的 Linux x86_64 环境中,可以直接安装官方 onnxruntime-gpu wheel;但 NVIDIA DGX Spark 使用 ARM64(aarch64)架构,并搭载计算能力 12.1 的 GB10 GPU。在 RAGFlow 0.26.4 的原始依赖条件下,aarch64 会被解析为 CPU 版 onnxruntime,即使设置了 DEVICE=gpu,DeepDoc OCR 仍然只能在 CPU 上运行。

本次实验采用 ONNX Runtime 1.27.1、CUDA 13.0、cuDNN 9.13 和 Python 3.13,从源码构建 Linux aarch64 GPU wheel。构建过程中先后遇到依赖下载中断、CUDA 13 头文件警告被当作错误、wheel 能生成但 CUDA Provider 缺少 MoE 符号、关闭 contrib ops 后链接失败,以及 121-virtual 被错误归一化等问题。

最终通过组合使用:

CMAKE_CUDA_ARCHITECTURES=75;121;121-virtual

成功生成可运行的 aarch64 CUDA wheel。实际验证结果为:

ONNX Runtime: 1.27.1
Device: GPU
Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']

DeepDoc 的文本检测和文本识别模型均成功创建 CUDA Session,并完成真实 OCR 推理。

关键词: RAGFlow、DGX Spark、GB10、DeepDoc、ONNX Runtime、CUDA 13、aarch64、OCR、PyCharm


1. 为什么要做这次实验

我最初遇到的现象并不复杂:RAGFlow 前端、API Server 和 Task Executor 都能启动,文档也能进入解析队列,但解析速度并不理想。Task Executor 的环境变量已经设置为:

DEVICE=gpu

然而启动日志仍然显示 OCR 使用 CPU。

这很容易让人把问题归因于 RAGFlow 的 GPU 开关、PyCharm 环境变量,或者 CUDA 驱动。但进一步检查实际 Python 解释器后,我发现真正的问题是 ONNX Runtime 包本身:

ONNX Runtime: 1.23.2
Device: CPU
Providers: ['AzureExecutionProvider', 'CPUExecutionProvider']

也就是说,当前环境里安装的是 CPU 版 onnxruntimeDEVICE=gpu 只能表达“希望使用 GPU”,不能凭空给 CPU wheel 增加 CUDAExecutionProvider

这次实验的目标因此被明确为:

  1. 为 Linux aarch64、Python 3.13、CUDA 13 构建 ONNX Runtime GPU wheel。
  2. 让 RAGFlow DeepDoc 的 det.onnxrec.onnx 使用 CUDA Provider。
  3. 防止后续执行 uv sync 时又被 CPU ORT 覆盖。
  4. 让 API Server 和 Task Executor 能在 PyCharm 中稳定组合运行。
  5. 使用真实 OCR 推理验证,而不是只看安装命令是否成功。

2. 实验环境

本次实验环境如下:

项目 版本或规格
设备 NVIDIA DGX Spark
GPU NVIDIA GB10
CPU/GPU 架构 ARM64 / aarch64
GPU 计算能力 12.1,即 sm_121
操作系统 Ubuntu 24.04 LTS
NVIDIA Driver 580.159.03
CUDA 13.0
cuDNN 9.13
Python 3.13
RAGFlow 0.26.4 源码
ONNX Runtime 1.27.1 源码
IDE PyCharm aarch64 版本

开始前先检查机器基线:

uname -m
nvidia-smi
nvcc --version

预期至少确认:

uname -m -> aarch64
GPU      -> NVIDIA GB10
CUDA     -> 13.0

DGX Spark 采用 CPU/GPU 统一内存架构。部分版本的 nvidia-smi 不会像传统独立显卡那样显示完整的显存总量和进程显存统计,这不等于 GPU 不可用。


3. 先理解 RAGFlow 文档解析进程

源码运行 RAGFlow 时,至少要区分下面三部分:

组件 作用
api/ragflow_server.py 提供后端 API 和页面请求服务
rag/svr/task_executor.py 从队列领取并执行文档解析任务
web 提供前端页面,不执行 OCR

只启动 API Server 时,文件可以上传,也可以进入任务队列,但没有 Task Executor 消费任务,文档会长期停在等待状态。

DeepDoc OCR 真正运行在 Task Executor 进程里。因此,排查 GPU 时必须检查 Task Executor 使用的:

  • Python 解释器;
  • DEVICE 环境变量;
  • ONNX Runtime 包;
  • CUDA/cuDNN 动态库路径;
  • DeepDoc 模型文件。

不能只检查 API Server,也不能只看前端页面。


4. 为什么 RAGFlow 在 aarch64 上安装了 CPU ORT

RAGFlow 0.26.4 的原始依赖条件大致如下:

"onnxruntime==1.23.2; sys_platform == 'darwin' or platform_machine != 'x86_64'",
"onnxruntime-gpu==1.23.2; sys_platform != 'darwin' and platform_machine == 'x86_64'",

这两条 marker 的含义是:

  • macOS 或非 x86_64 平台安装 CPU ORT;
  • 非 macOS 的 x86_64 平台安装 GPU ORT。

DGX Spark 是 aarch64,因此会命中第一条规则:

platform_machine != 'x86_64'

最终安装:

onnxruntime==1.23.2

这不是 RAGFlow “识别不到显卡”,而是依赖解析阶段就选择了 CPU 包。

在继续之前,可以使用项目实际解释器检查:

python -c 'import sys, onnxruntime as ort; print(sys.executable); print(ort.__version__); print(ort.get_device()); print(ort.get_available_providers())'

如果输出中没有:

CUDAExecutionProvider

那么继续修改 DEVICE、任务并发数或显存参数都没有意义,首先必须解决 ORT GPU wheel。


5. 为什么单独建立编译环境

我没有直接在 RAGFlow 项目的 .venv 中编译 ONNX Runtime,而是另外创建了一个 Conda 环境。

这样做有三个好处:

  1. CUDA Toolkit、cuDNN、编译器和构建工具集中在一个环境中。
  2. ORT 编译过程不会污染 RAGFlow 的业务依赖。
  3. 编译完成后只需要把 wheel 安装到项目 .venv

需要注意,wheel 是否可用主要取决于:

  • 操作系统和 CPU 架构;
  • Python ABI;
  • CUDA/cuDNN 版本;
  • GPU 架构;
  • 编译时启用的 Execution Provider。

Conda 环境只是承载工具链,不是硬件能力的来源。


6. 创建 ONNX Runtime 编译环境

创建独立环境:

conda create -n ragflow-ort-gpu python=3.13 -y
conda activate ragflow-ort-gpu

安装 CUDA Toolkit 和 cuDNN:

conda install -y -c nvidia \
  cuda-toolkit=13.0.0 \
  cudnn=9.13.0.50

安装构建工具:

python -m pip install -U \
  cmake \
  ninja \
  numpy \
  packaging \
  protobuf \
  setuptools \
  wheel

本次成功环境中的关键版本为:

Python:       3.13.14
CMake:       4.4.0
Ninja:       1.13.0
CUDA nvcc:   13.0.48
cuDNN:       9.13.0.50
GCC/G++:     14.3.0
NumPy:       2.5.1

检查工具链:

python --version
cmake --version
ninja --version
nvcc --version
gcc --version
g++ --version

如果环境中没有可用的 C/C++ 编译器,应先通过 Conda 或系统包管理器补齐,再开始 ORT 构建。


7. 下载 ONNX Runtime 源码

下载官方 ONNX Runtime 1.27.1 源码,并初始化全部子模块:

git clone --recursive --branch v1.27.1 \
  https://github.com/microsoft/onnxruntime.git \
  onnxruntime-v1.27.1

cd onnxruntime-v1.27.1
git submodule update --init --recursive
git rev-parse HEAD

本次实验使用的官方提交为:

df2ba1cf8108aa63627cf4cdf8f807880b938616

建议把源码、构建日志和最终 wheel 分开管理:

  • 源码可以随时重新克隆;
  • 构建日志用于追踪参数和错误;
  • 成功 wheel 应复制到长期稳定的产物目录;
  • 产物目录中同时保存版本说明和 SHA-256。

文章不限定具体磁盘路径,只要这个位置不会被临时清理即可。


8. 六次构建尝试概览

这次编译并不是一次成功。完整过程大致如下:

次数 CUDA 架构或方案 结果 主要问题
1 121 CMake 配置失败 下载 Abseil 时 GitHub HTTP/2/TLS 中断
2 121 CUDA 编译失败 CUDA 13 头文件警告被 -Werror 当作错误
3 121 + 关闭警告即错误 wheel 生成,但运行失败 CUDA Provider 缺少 MoE 符号
4 121 + --disable_contrib_ops 链接失败 GetFusedActivationAttr 未定义
5 121-virtual CMake 配置失败 被归一化成非法架构字符串
6 75;121;121-virtual 成功 CUDA Provider 和真实 DeepDoc OCR 均通过

下面逐项分析。


9. 踩坑一:外部依赖下载失败

第一次配置时,CMake 需要下载 Abseil 等第三方依赖。日志中出现:

status_code: 56
Failure when receiving data from the peer
OpenSSL SSL_read ... unexpected eof while reading
Failed receiving HTTP2 data

这类错误发生在依赖下载阶段,与 CUDA 架构设置没有直接关系。

正确处理方式是:

  1. 保留已经下载的依赖和构建缓存;
  2. 检查 GitHub 网络连接或代理;
  3. 网络恢复后重新执行相同构建命令;
  4. 不要因为下载失败随意修改 CUDA 架构参数。

判断原则很简单:如果还没有进入 C/C++ 或 CUDA 编译,就不要先怀疑 sm_121


10. 踩坑二:CUDA 13 头文件警告被当作错误

第二次构建已经进入 CUDA 编译,但在 CUDA 13 自带头文件中失败:

cuda_fp4.hpp: error: unused parameter 'fp4_interpretation'
[-Werror=unused-parameter]

这里真正终止构建的不是语法错误,而是:

-Werror

它把第三方 CUDA 头文件中的警告升级成了错误。

解决方法是在 ORT 构建参数中增加:

--compile_no_warning_as_error

这个参数只是不再把警告当作错误,不会屏蔽真正的编译失败。


11. 踩坑三:wheel 生成成功,但 CUDA Provider 缺少符号

加入 --compile_no_warning_as_error 后,纯 sm_121 构建成功生成了 wheel。

这时最容易产生一个误判:

wheel 文件已经生成,所以 GPU 版本已经编译成功。

但实际安装并加载 CUDA Provider 时,出现了与下面符号相关的错误:

MoeGemmRunner ... getConfigs

也就是说:

  • wheel 打包成功;
  • Python 甚至可能能够 import onnxruntime
  • libonnxruntime_providers_cuda.so 内部仍缺少实现;
  • 创建 CUDA Session 时才真正暴露问题。

这一步让我确认,验证 ORT GPU 不能只看三个东西:

pip install 成功
import onnxruntime 成功
wheel 文件存在

必须继续创建 CUDA Provider Session,并运行真实模型。


12. 从 ORT CMake 源码定位根因

继续检查 ONNX Runtime 1.27.1 的构建逻辑,关键文件包括:

cmake/onnxruntime_cuda_source_filters.cmake
cmake/onnxruntime_providers_cuda.cmake
cmake/external/cuda_configuration.cmake

其中,ORT 对 contrib_ops/cuda/llm/ 下的 LLM/MoE CUDA 源码建立了独立对象库。源码注释明确说明:

The LLM directory contains kernels with minimum SM75 support.

同时使用了类似下面的过滤条件:

onnxruntime_filter_cuda_archs(
    _ort_llm_cuda_architectures
    MIN_SM 75
    EXCLUDE_SM120_REAL
)

过滤逻辑的关键部分是:

if(_FCA_EXCLUDE_SM120_REAL
   AND _arch_num GREATER_EQUAL 120
   AND _arch MATCHES "-real$")
  continue()
endif()

纯粹设置:

CMAKE_CUDA_ARCHITECTURES=121

会被 ORT 归一化成:

121-real

对于通用 LLM/MoE 对象库,121-real 又会被 EXCLUDE_SM120_REAL 排除。最终这个对象库没有可用架构,相关实现没有完整编入 CUDA Provider,而其他位置仍可能引用这些模板符号。

这就解释了为什么纯 sm_121 wheel 能生成,却在加载 Provider 时出现 MoE 未定义符号。


13. 踩坑四:关闭 contrib ops 不是正确方案

为了绕开 MoE/LLM 相关符号,我曾尝试:

--disable_contrib_ops

结果在链接 libonnxruntime.so 时出现:

undefined reference to
onnxruntime::GetFusedActivationAttr(...)

这说明简单关闭 contrib ops 会破坏当前构建目标的依赖闭合。即使通过进一步删减让它勉强链接,也会损失大量算子支持,不适合作为 RAGFlow 的通用 ORT 运行库。

因此,这条路线被放弃。


14. 踩坑五:只使用 121-virtual 仍然失败

既然 ORT 的注释说明 SM120+ 的 real 架构会被排除,而 virtual/PTX 会被保留,一个自然想法是只使用:

CMAKE_CUDA_ARCHITECTURES=121-virtual

但在本次 ORT 1.27.1 构建中,它又被归一化成了类似:

121-virtual-real;121-virtual

其中 121-virtual-real 不是合法的 CMake CUDA 架构字符串,因此配置阶段直接失败。

所以,单独使用 121-virtual 也没有解决问题。


15. 最终解法:75;121;121-virtual

最终使用的架构组合为:

CMAKE_CUDA_ARCHITECTURES=75;121;121-virtual

ORT 归一化后的结果是:

75-real;121-real;121-virtual

三个值分别承担不同角色:

15.1 75-real

使通用 LLM/MoE 对象库至少拥有一个满足 MIN_SM 75、且不会被 EXCLUDE_SM120_REAL 排除的 real 架构,从而把缺失实现链接进 Provider。

15.2 121-real

为 GB10 生成原生 sm_121 代码。

15.3 121-virtual

保留 SM121 的 PTX,使相关路径可以在 GB10 上通过驱动 JIT 编译。

这里的 75 并不意味着 GB10 只能按 SM75 运行。最终 wheel 同时包含 SM121 原生代码和 PTX。75-real 的主要作用,是满足 ORT 1.27.1 当前 LLM/MoE 对象库的架构过滤逻辑。

这是一种针对当前 ORT 源码实现的工程性解决方案。升级 ONNX Runtime 后,应重新检查相关 CMake 逻辑,而不是永久照搬这个组合。


16. 最终成功的完整编译命令

首先进入前面创建的 Conda 环境:

conda activate ragflow-ort-gpu

定义几个通用变量。请自行替换,不要直接保留尖括号:

export ORT_SRC="<ONNX Runtime 1.27.1 源码目录>"
export BUILD_LOG_DIR="<构建日志长期保存目录>"

配置 CUDA/cuDNN 环境:

export CUDA_HOME="$CONDA_PREFIX"
export CUDNN_HOME="$CONDA_PREFIX"
export CUDAToolkit_ROOT="$CONDA_PREFIX"
export PATH="$CONDA_PREFIX/bin:$PATH"
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$CONDA_PREFIX/targets/sbsa-linux/lib:${LD_LIBRARY_PATH:-}"

执行构建:

cd "$ORT_SRC"
mkdir -p "$BUILD_LOG_DIR"

./build.sh \
  --config Release \
  --update \
  --build \
  --build_wheel \
  --skip_tests \
  --parallel 5 \
  --cmake_generator Ninja \
  --compile_no_warning_as_error \
  --use_cuda \
  --cuda_home "$CONDA_PREFIX" \
  --cudnn_home "$CONDA_PREFIX" \
  --cuda_version=13.0 \
  --build_dir aarch64-conda-linux-gnu \
  --cmake_extra_defines \
    'CMAKE_CUDA_ARCHITECTURES=75;121;121-virtual' \
    "CUDAToolkit_ROOT=$CONDA_PREFIX" \
    "CUDA_CUDA_LIBRARY=$CONDA_PREFIX/lib/stubs/libcuda.so" \
  2>&1 | tee "$BUILD_LOG_DIR/ort-1.27.1-cuda13-sm75-sm121-vptx.log"

几个注意点:

  1. 架构列表必须整体加引号,否则 shell 会把分号当作命令分隔符。
  2. --parallel 5 是本次实验使用的并行度,可根据内存压力调整。
  3. --skip_tests 只跳过 ORT 完整单元测试,不能跳过后续 Provider 和真实模型验证。
  4. 构建阶段使用 CUDA Toolkit 中的 libcuda.so stub,运行阶段仍由 NVIDIA 驱动提供真实驱动库。
  5. 如果 Conda Toolkit 的目录结构不同,应先用 find 检查 libcuda.so 和 cuDNN 库的位置。

成功日志中应出现类似:

Original CMAKE_CUDA_ARCHITECTURES : 75;121;121-virtual
CMAKE_CUDA_ARCHITECTURES: 75-real;121-real;121-virtual
CUDA compiler identification is NVIDIA 13.0.48
Build complete

17. 长期保存 wheel

构建成功后,从构建输出目录找到:

onnxruntime_gpu-1.27.1-cp313-cp313-linux_aarch64.whl

不要让 RAGFlow 的依赖配置长期引用临时 build 目录。建议:

  1. 创建一个稳定的构建产物目录;
  2. ORT版本-CUDA版本-GPU架构-Python版本 分类;
  3. 将 wheel 复制到该目录;
  4. 保存 SHA-256;
  5. 保存对应的构建日志和 ORT Git 提交号。

示例:

export WHEEL_STORE="<长期保存 wheel 的目录>"
mkdir -p "$WHEEL_STORE"

find "$ORT_SRC" -path '*/Release/dist/onnxruntime_gpu-*.whl' -print
cp "<上一步找到的 wheel>" "$WHEEL_STORE/"

sha256sum "$WHEEL_STORE"/onnxruntime_gpu-*.whl

wheel 文件名中的含义:

cp313            -> CPython 3.13
linux_aarch64    -> Linux ARM64
onnxruntime_gpu  -> 含 GPU Execution Provider 的发行包

18. 安装到 RAGFlow 项目环境

编译环境与 RAGFlow 运行环境不是同一个环境:

编译环境:Conda ragflow-ort-gpu
运行环境:RAGFlow 项目 .venv

定义运行解释器和 wheel:

export RAGFLOW_HOME="<RAGFlow 源码根目录>"
export RAGFLOW_PYTHON="$RAGFLOW_HOME/.venv/bin/python"
export ORT_WHEEL="<长期保存目录中的 ORT GPU wheel>"

卸载 CPU/GPU ORT 旧包,再安装自编译 wheel:

uv pip uninstall --python "$RAGFLOW_PYTHON" \
  onnxruntime \
  onnxruntime-gpu

uv pip install --python "$RAGFLOW_PYTHON" \
  --reinstall \
  --no-deps \
  "$ORT_WHEEL"

立即验证:

export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$CONDA_PREFIX/targets/sbsa-linux/lib:${LD_LIBRARY_PATH:-}"

"$RAGFLOW_PYTHON" -c \
  'import onnxruntime as ort; print(ort.__version__); print(ort.get_device()); print(ort.get_available_providers())'

预期:

1.27.1
GPU
['CUDAExecutionProvider', 'CPUExecutionProvider']

19. 准备 DeepDoc 模型

DeepDoc OCR 需要下载以下模型或资源:

det.onnx
rec.onnx
layout.onnx
tsr.onnx
ocr.res
updown_concat_xgb.model

主要来源:

Hugging Face: InfiniFlow/deepdoc
Hugging Face: InfiniFlow/text_concat_xgb_v1.0

下载完成后,放入 RAGFlow 约定的项目相对目录:

rag/res/deepdoc

可以使用 huggingface_hub 下载:

cd "$RAGFLOW_HOME"

.venv/bin/python - <<'PY'
from huggingface_hub import snapshot_download

snapshot_download(
    repo_id="InfiniFlow/deepdoc",
    local_dir="rag/res/deepdoc",
)

snapshot_download(
    repo_id="InfiniFlow/text_concat_xgb_v1.0",
    local_dir="rag/res/deepdoc",
    allow_patterns=["updown_concat_xgb.model"],
)
PY

如果缺少 updown_concat_xgb.model,可能出现:

XGBoostError: No such file or directory
rag/res/deepdoc/updown_concat_xgb.model

这个模型用于版面上下文拼接,不是 ONNX OCR 模型,但缺失时仍会中断文档解析。


20. RAGFlow 源码适配:优先检测 ORT CUDA Provider

仅安装 GPU wheel 还不够。RAGFlow 0.26.4 的部分 GPU 检测逻辑依赖 PyTorch,可能在没有 PyTorch 时把 DeepDoc 判断为 CPU。

我的处理原则是:

  1. DEVICE=cpu 时明确使用 CPU;
  2. DEVICE=gpu 时优先检查 ORT 的 CUDAExecutionProvider
  3. ORT CUDA 不可用时,再保留 PyTorch 检测作为后备。

20.1 修改 deepdoc/vision/ocr.py

cuda_is_available() 中加入 ORT Provider 检测:

def cuda_is_available():
    if os.environ.get("DEVICE", "cpu") == "cpu":
        return False

    target_id = 0 if device_id is None else device_id
    try:
        if "CUDAExecutionProvider" in ort.get_available_providers():
            return True
    except Exception:
        pass

    try:
        pip_install_torch()
        import torch

        if torch.cuda.is_available() and torch.cuda.device_count() > target_id:
            return True
    except Exception:
        return False
    return False

这样,DeepDoc 可以直接基于 ORT CUDA 创建 Session,不必为了判断显卡而额外安装完整 PyTorch。

20.2 修改 common/settings.py

Task Executor 初始化设备数时,也优先检查 ORT:

def check_and_install_torch():
    global PARALLEL_DEVICES

    if os.environ.get("DEVICE", "cpu") == "cpu":
        PARALLEL_DEVICES = 0
        return

    try:
        import onnxruntime as ort

        if "CUDAExecutionProvider" in ort.get_available_providers():
            PARALLEL_DEVICES = 1
            logging.info("found 1 gpu via ONNX Runtime CUDA provider")
            return
    except Exception:
        pass

    # 保留项目原有的 PyTorch 检测逻辑作为后备

单 GPU 的 DGX Spark 设置为 1 即可。不要为了提高并发把 PARALLEL_DEVICES 伪造成 2,它表示设备数量,不是任务线程数。


21. 防止 uv sync 再次安装 CPU ORT

手工安装 wheel 后还有一个隐藏问题:再次运行 uv sync 时,uv 会根据 pyproject.tomluv.lock 恢复声明的依赖。

如果项目仍使用原始 marker,dry-run 可能显示:

- onnxruntime-gpu==1.27.1
+ onnxruntime==1.23.2

也就是说,刚刚安装的 GPU wheel 又会被 CPU ORT 覆盖。

21.1 调整平台 marker

可以将依赖拆分为:

"onnxruntime==1.23.2; sys_platform == 'darwin' or (platform_machine != 'x86_64' and (sys_platform != 'linux' or platform_machine != 'aarch64'))",
"onnxruntime-gpu==1.23.2; sys_platform != 'darwin' and platform_machine == 'x86_64'",
"onnxruntime-gpu==1.27.1; sys_platform == 'linux' and platform_machine == 'aarch64'",

含义是:

  • Linux aarch64 使用自编译的 1.27.1 GPU wheel;
  • Linux x86_64 继续使用项目原有 GPU 版本;
  • macOS 和其他非目标平台继续使用 CPU ORT。

21.2 为 uv 配置本地 wheel

[tool.uv.sources] 中为 Linux aarch64 指向长期保存的 wheel。

下面只是结构示例,YOUR_WHEEL_STORE 必须替换成真实、长期有效的位置:

[tool.uv.sources]
onnxruntime-gpu = [
    { path = "YOUR_WHEEL_STORE/onnxruntime_gpu-1.27.1-cp313-cp313-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64'" },
]

如果希望避免暴露个人目录,也可以在项目中约定一个不提交 Git 的稳定相对目录,例如:

[tool.uv.sources]
onnxruntime-gpu = [
    { path = "vendor/wheels/onnxruntime_gpu-1.27.1-cp313-cp313-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64'" },
]

此时要保证该文件在每次 uv sync 前已经存在。

21.3 更新锁文件并验证

cd "$RAGFLOW_HOME"

uv lock
uv lock --check --offline
uv sync --locked --offline --dry-run

确认 dry-run 不再出现:

卸载 onnxruntime-gpu 1.27.1
安装 onnxruntime 1.23.2

确认无误后再执行:

uv sync --locked --offline

然后重新检查 Provider。不要把“lock 解析成功”等同于“GPU Provider 仍然存在”。


22. PyCharm 中运行 RAGFlow 后端

为了让文档上传、排队和解析都完整工作,建议建立三个运行配置。

22.1 RAGFlow API Server

Name: RAGFlow API Server
Script: api/ragflow_server.py
Parameters: 留空
Working directory: RAGFlow 源码根目录
Interpreter: 项目 .venv/bin/python

环境变量:

PYTHONPATH=<RAGFlow 源码根目录>
DEVICE=gpu
NLTK_DATA=<NLTK 数据目录>
LITELLM_LOCAL_MODEL_COST_MAP=True
LD_LIBRARY_PATH=<Conda编译环境>/lib:<Conda编译环境>/targets/sbsa-linux/lib

不建议给脚本额外添加 --debug。需要断点时直接使用 PyCharm 的 Debug 按钮,避免 Flask reloader 再创建一套进程。

22.2 RAGFlow Task Executor

Name: RAGFlow Task Executor
Script: rag/svr/task_executor.py
Parameters: -i 0
Working directory: RAGFlow 源码根目录
Interpreter: 项目 .venv/bin/python

环境变量与 API Server 相同。

同一个 -i 0 不要启动两份。旧 worker 没有停止时又启动一个同编号 worker,会使日志和任务归属变得难以判断。

22.3 Compound 配置

创建 Compound:

Name: RAGFlow Backend All
包含: RAGFlow API Server
包含: RAGFlow Task Executor

以后先启动数据库、Redis、对象存储和搜索引擎,再运行 RAGFlow Backend All


23. 推荐启动顺序

23.1 启动基础服务

cd "$RAGFLOW_HOME"
docker compose -f docker/docker-compose-base.yml up -d
docker compose -f docker/docker-compose-base.yml ps

确认以下服务正常:

MySQL
Redis
MinIO
Elasticsearch 或项目选用的文档引擎

23.2 启动后端

在 PyCharm 中运行:

RAGFlow Backend All

23.3 启动前端

cd "$RAGFLOW_HOME/web"
npm run dev

24. 五层验证法

我把验证分成五层。任何一层失败,都不要直接进入下一层。

24.1 第一层:包和 Provider

"$RAGFLOW_PYTHON" -c \
  'import onnxruntime as ort; print(ort.__version__); print(ort.get_device()); print(ort.get_available_providers())'

目标:

1.27.1
GPU
['CUDAExecutionProvider', 'CPUExecutionProvider']

24.2 第二层:动态库

定位 ORT C API 目录:

ORT_CAPI_DIR=$("$RAGFLOW_PYTHON" -c \
  'import pathlib, onnxruntime as ort; print(pathlib.Path(ort.__file__).parent / "capi")')

ldd "$ORT_CAPI_DIR/libonnxruntime_providers_cuda.so" | grep 'not found'

正常情况下,最后一条命令没有输出。

如果出现缺失库,优先检查:

  • LD_LIBRARY_PATH
  • CUDA Toolkit 运行库;
  • cuDNN;
  • wheel 的 Python/架构 ABI;
  • NVIDIA 驱动。

24.3 第三层:DeepDoc Session

在 RAGFlow 根目录执行:

DEVICE=gpu "$RAGFLOW_PYTHON" - <<'PY'
from deepdoc.vision.ocr import load_model

model_dir = "rag/res/deepdoc"

for name in ("det", "rec"):
    session, _ = load_model(model_dir, name, 0)
    print(name, session.get_providers())
PY

期望:

det ['CUDAExecutionProvider', 'CPUExecutionProvider']
rec ['CUDAExecutionProvider', 'CPUExecutionProvider']

24.4 第四层:真实 OCR

准备一张带有清晰文字的测试图片,然后执行:

export OCR_TEST_IMAGE="<OCR 测试图片>"

DEVICE=gpu "$RAGFLOW_PYTHON" - <<'PY'
import os
import cv2

from common import settings
from deepdoc.vision.ocr import OCR

settings.PARALLEL_DEVICES = 1
image = cv2.imread(os.environ["OCR_TEST_IMAGE"])
if image is None:
    raise RuntimeError("无法读取 OCR_TEST_IMAGE")

ocr = OCR()
result = ocr(image)
print(result)
PY

本次实验使用包含 RAGFLOW GPU OCR 的图片,识别结果为:

RAGFLOW GPU OCR
confidence: 0.9708765745162964

真实推理能够验证:

  • 模型可以加载;
  • CUDA kernel 可以执行;
  • 文本检测和识别链路完整;
  • 不是只注册了一个不可用的 Provider 名称。

24.5 第五层:uv sync 后复测

执行一次真实或 dry-run 同步后,再次重复前四层检查。

尤其要确认:

onnxruntime-gpu 仍为 1.27.1
CUDAExecutionProvider 仍在
det/rec Session 仍以 CUDA 为第一 Provider

25. 哪些 CPU 日志不代表 OCR 回退

GPU OCR 正常时,日志中仍可能出现 CPU 字样。

25.1 updown_cnt_mdl initialized on CPU

这是 XGBoost 排版拼接模型,不是 det.onnxrec.onnx。它在 CPU 上运行是正常设计。

25.2 Some nodes were not assigned

ONNX Runtime 可能把 shape 等轻量算子分配到 CPU。这是 Provider 的正常图优化,不代表整个模型回退到 CPU。

25.3 /sys/class/drm/card0 警告

可能看到:

Failed to detect devices under "/sys/class/drm/card0"

如果 CUDA Provider 可以创建、Session Provider 顺序正确、真实 OCR 已成功,这条设备发现警告通常不影响推理。

25.4 libtinfo.so.6 提示

Conda 动态库路径可能让系统 shell 输出:

libtinfo.so.6: no version information available

如果编译和运行均正常,它通常只是 Conda 与系统库版本信息不一致的提示。

判断 GPU OCR 的依据应是:

Provider + Session + 真实推理

而不是日志中是否完全没有 CPU 字样。


26. Task Executor 的 libodbc.so.2 错误

Task Executor 启动时还可能出现:

ImportError: libodbc.so.2: cannot open shared object file

通常是 Python 环境中安装了 pyodbc,但操作系统或 Conda 环境缺少 unixODBC runtime。

它主要影响依赖 ODBC 的 SQL Server 或 Agent SQL 工具,并不等同于 DeepDoc GPU OCR 故障。但启动阶段长期保留 ImportError 并不理想,应补齐依赖。

可以选择系统包管理器:

sudo apt update
sudo apt install -y unixodbc libodbc2

也可以安装到 Conda 环境:

conda install -n ragflow-ort-gpu -c conda-forge unixodbc

如果采用 Conda 方案,PyCharm 中需要保留该环境的 LD_LIBRARY_PATH

验证:

"$RAGFLOW_PYTHON" -c 'import pyodbc; print(pyodbc.version)'

27. 为什么 GPU 利用率很高,文档仍然可能解析得慢

成功启用 ORT GPU 后,我又遇到了第二个容易误判的问题:

  • Task Executor 已占用一部分 GPU 统一内存;
  • GPU 利用率很高;
  • 继续提交文档,进度却仍然缓慢;
  • 增加任务数后,Task Executor 的显存占用没有线性增长。

对解析日志分段计时后发现,某个 13 页样例的 DeepDoc 阶段大致为:

OCR:             6.98 秒
Layout analysis: 3.15 秒
Table analysis:  0 秒
Text merge:      0.14 秒

真正慢的是后续 VLM 图片描述。RAGFlow 把文档图片提交给本地视觉语言模型,而该模型使用了很大的上下文,并且只有单并发 runner,多个图片描述请求因此排队。

这说明“文档解析”并不是一个单一算子,而是一条流水线:

PDF 读取
  -> OCR
  -> Layout
  -> Table
  -> 文本合并
  -> 图片/VLM 描述
  -> Embedding
  -> 索引写入

ORT GPU 主要解决 DeepDoc OCR 模型执行问题,不会自动加速外部 VLM 服务、Embedding 服务、数据库写入和网络请求。

27.1 为什么占用约 2GB 后不再增加

多个任务通常共享同一个 Task Executor 进程和已经加载的 ORT 模型。模型权重不会因为任务数量增加而重复加载,所以显存或统一内存占用不一定线性增加。

这不是“程序只被允许使用 2GB”,而是当前模型和 ORT arena 的实际需求。

RAGFlow 中还可能配置类似:

OCR_GPU_MEM_LIMIT_MB=2048

这个值控制 ORT CUDA Provider 的内存 arena 上限,不代表整台 DGX Spark 的 GPU 内存上限。盲目提高它通常不会让小型 OCR 模型变快。

27.2 更有效的优化方向

如果日志证明瓶颈在 VLM,可以考虑:

  1. 不需要图片描述时关闭默认 VLM;
  2. 使用更小的视觉语言模型;
  3. 将超大的上下文窗口降到 8192 或 16384;
  4. 降低单请求内存后,再测试 VLM 并发 2;
  5. 分别记录 OCR、Layout、VLM、Embedding 的耗时。

当 GPU 已接近满算力时,单纯增加显存占用并不会提高速度。


28. 常见故障快速判断

28.1 上传文件后完全不动

检查 Task Executor 是否启动:

pgrep -af task_executor.py

没有 worker 时,先启动 Task Executor。

28.2 ORT 只有 CPU Provider

"$RAGFLOW_PYTHON" -c \
  'import sys, onnxruntime as ort; print(sys.executable); print(ort.__version__); print(ort.get_available_providers())'

重点检查:

  • PyCharm 是否使用项目 .venv
  • 是否安装了 CPU onnxruntime
  • uv sync 是否覆盖了 GPU wheel;
  • wheel 是否与 CPython 3.13/aarch64 匹配。

28.3 Provider 在列表中,但 Session 创建失败

echo "$LD_LIBRARY_PATH"
ldd "$ORT_CAPI_DIR/libonnxruntime_providers_cuda.so" | grep 'not found'

重点检查 CUDA、cuDNN、驱动和 ABI。

28.4 updown 模型缺失

ls -lh rag/res/deepdoc/updown_concat_xgb.model

缺失时从 InfiniFlow/text_concat_xgb_v1.0 下载。

28.5 uv sync 想删除 GPU ORT

uv sync --locked --offline --dry-run

如果计划删除 onnxruntime-gpu 1.27.1、安装 CPU ORT,应先修复平台 marker、本地 source 和 lock,不要直接执行真实同步。

28.6 OCR 很快,但进度长时间停在中后段

按阶段搜索日志:

grep -E \
  'OCR finished|Layout analysis|Visual model|describe_with_prompt|Task done' \
  <Task Executor 日志> \
  | tail -n 100

如果 OCR finished 很快出现,而 Visual model 后长时间无进展,瓶颈在 VLM,不在 ORT OCR。


29. 日常运行清单

启动前

[ ] 基础服务全部健康
[ ] ORT wheel 的长期保存位置仍然有效
[ ] 项目 .venv 中是 onnxruntime-gpu 1.27.1
[ ] CUDAExecutionProvider 存在
[ ] DeepDoc 模型完整
[ ] PyCharm 的 DEVICE=gpu 和 LD_LIBRARY_PATH 正确

启动顺序

1. MySQL、Redis、MinIO、Elasticsearch
2. RAGFlow API Server
3. RAGFlow Task Executor
4. RAGFlow Web 前端

启动后

[ ] 只有一个 task_executor.py -i 0
[ ] 日志出现 ORT CUDA Provider
[ ] det.onnx 和 rec.onnx 使用 GPU
[ ] 上传一个小 PDF 做端到端验证

30. 什么时候需要重新编译

以下任一条件变化后,应重新编译或至少完整复测:

  • Python ABI 从 CPython 3.13 变为其他版本;
  • CUDA Toolkit 主版本变化;
  • cuDNN 主版本变化;
  • ONNX Runtime 版本变化;
  • GPU 架构变化;
  • 系统从 aarch64 迁移到 x86_64,或反向迁移;
  • NVIDIA 驱动对 PTX JIT 的支持发生变化;
  • ORT 修复或重构了 SM120+ LLM/MoE 架构过滤逻辑。

重新编译后,建议始终重复:

wheel 安装
  -> Provider 检查
  -> 动态库检查
  -> DeepDoc Session
  -> 真实 OCR
  -> uv sync 后复测

31. 本次实验得到的几个认识

31.1 环境变量不是运行时能力

DEVICE=gpu 只是配置意图。真正的 GPU 能力来自:

正确的 wheel + CUDA Provider + 动态库 + 驱动 + 可运行模型

31.2 构建成功不等于运行成功

生成 wheel 只是编译链的一个结果。动态链接错误往往要到 Provider 创建或模型推理时才出现。

31.3 平台 marker 是部署的一部分

手工安装一次 GPU wheel 不能解决长期维护问题。pyproject.tomltool.uv.sourcesuv.lock 必须表达同一个平台决策。

31.4 要从源码理解架构过滤

75;121;121-virtual 不是随机尝试出来的“神秘参数”,而是由 ORT 1.27.1 中 MIN_SM 75EXCLUDE_SM120_REAL 和 PTX 保留逻辑共同决定的。

31.5 性能问题要分阶段测量

OCR、Layout、VLM、Embedding 和索引写入属于不同组件。只看 UI 的总进度,无法判断真正的瓶颈。

31.6 统一内存不能套用传统显存直觉

DGX Spark 的统一内存模型与传统独立 GPU 不同。进程没有继续增加“显存占用”,不等于被限制,也不等于没有继续计算。


32. 结语

作为一个喜欢追根究底的技术爱好者,这次实验中最有价值的并不是得到了一条可以复制的编译命令,而是建立了一套排查顺序:

先确认包
再确认 Provider
再确认动态库
再确认 Session
最后做真实推理

如果只停留在 DEVICE=gpunvidia-smi 或 wheel 文件是否存在,很容易在错误方向上反复调整参数。

最终,Linux aarch64、CUDA 13 和 GB10 并不是不能运行 RAGFlow DeepDoc GPU OCR。真正的难点在于:官方依赖默认选择 CPU ORT,而 ORT 1.27.1 的 SM120+ LLM/MoE 构建过滤逻辑又让纯 sm_121 架构组合产生了运行时缺失符号。

通过源码定位后,使用:

75-real + 121-real + 121-virtual

同时满足了对象库链接、GB10 原生代码和 PTX JIT 三方面需求。再配合 RAGFlow 的 ORT Provider 检测、uv 平台依赖和 PyCharm 双进程配置,最终实现了可重复启动、可验证、不会被依赖同步轻易覆盖的 GPU OCR 环境。

对于类似的 ARM GPU 软件适配问题,我认为最可靠的方法仍然是:记录版本、保存日志、阅读构建源码、设计分层验证,并把一次成功变成能够重复的工程配置。


参考项目

版本提示:本文结论基于 RAGFlow 0.26.4、ONNX Runtime 1.27.1、CUDA 13.0、Python 3.13 和 NVIDIA GB10。升级任一组件后,应重新检查官方依赖、CMake 架构过滤逻辑和真实推理结果。

更多推荐