🤖 roboto_origin_03 Wiki
首页 / 推理子模块 / ONNX Runtime 推理引擎集成

本项目采用 ONNX Runtime 作为强化学习策略网络的底层推理引擎,负责将 PyTorch 训练得到的策略模型(.onnx)部署到机器人端进行实时推理。本页面向具备 C++ 与深度学习部署基础的开发者,系统讲解 ONNX Runtime 的集成架构、张量管理、多策略运行时支持以及性能优化手段,帮助你理解推理管线如何与观测组装、动作执行衔接。

Sources: inference_node.hpp, inference_node.cpp

架构定位与核心抽象

推理引擎在整个系统中的定位是「策略执行层」:接收由观测管理器组装好的浮点向量,调用 ONNX Runtime 执行前向传播,输出关节目标位置。为了保持多策略热切换能力,代码引入了两级核心抽象:ModelContext 封装纯 ONNX 运行时资源,PolicyRuntime 在其之上叠加策略配置、观测布局与帧堆叠状态。

classDiagram
    class ModelContext {
        +unique_ptr~Ort::Session~ session
        +unique_ptr~Ort::MemoryInfo~ memory_info
        +unique_ptr~Ort::Value~ input_tensor
        +unique_ptr~Ort::Value~ output_tensor
        +vector~string~ input_names
        +vector~string~ output_names
        +vector~float~ input_buffer
        +vector~float~ output_buffer
        +vector~int64_t~ input_shape
        +vector~int64_t~ output_shape
    }

    class PolicyRuntime {
        +string name
        +string model_path
        +vector~ObsSourceSpec~ obs_layout
        +vector~float~ obs
        +int frame_stack
        +ObsStackOrder stack_order
        +unique_ptr~ModelContext~ ctx
        +shared_ptr~MotionLoader~ motion_loader
    }

    class InferenceNode {
        +unique_ptr~Ort::Env~ env_
        +vector~PolicyRuntime~ policies_
        +int active_policy_idx_
        +setup_model(ctx, path, input_size)
        +inference()
    }

    InferenceNode "1" *-- "many" PolicyRuntime : manages
    PolicyRuntime "1" *-- "1" ModelContext : owns

ModelContext 完全隔离了 ONNX C++ API 的所有句柄,包括 Ort::SessionOrt::MemoryInfo 与张量对象,确保每个策略模型拥有独立的输入输出缓冲区,避免多策略切换时的资源竞争。PolicyRuntime 则保存了观测布局、frame_stack 配置以及动作参考加载器,将「模型怎么跑」与「策略需要什么观测」解耦。这种分层设计使得新增一个策略仅需在 YAML 中追加配置项,系统会在构造函数中自动为其创建独立的 ModelContext

Sources: inference_node.hpp, inference_node.cpp

构建系统集成与跨平台支持

项目没有依赖系统包管理器中的 ONNX Runtime,而是在 thirdparty/ 目录下预置了 x64 与 aarch64 两个平台的官方预编译库(版本 1.21.0),通过 CMake 根据目标架构自动选择链接路径。

目标架构 预编译库路径 说明
x86_64 thirdparty/onnxruntime-linux-x64-1.21.0 桌面仿真 / x86 工控机
aarch64 thirdparty/onnxruntime-linux-aarch64-1.21.0 Jetson / ARM 嵌入式平台

CMakeLists.txt 中,通过 CMAKE_SYSTEM_PROCESSOR 进行条件分支,将对应路径加入包含目录与链接库,并在安装阶段把 .so 文件复制到 lib 目录,确保部署时动态链接正确解析。推理节点可执行文件通过 -Wl,--no-as-needed 标志强制链接 libonnxruntime.so,避免符号延迟加载导致的运行时异常。

Sources: CMakeLists.txt, CMakeLists.txt

模型加载与生命周期管理

所有策略模型在 InferenceNode 构造阶段一次性加载完成。setup_model() 方法负责完成从 ONNX 文件到可运行会话的完整初始化,其核心流程可分为五步:

  1. 创建 SessionOptions:关闭每会话独立线程池、启用 CPU 内存池与内存模式、开启最高级别图优化(ORT_ENABLE_ALL)。
  2. 实例化 Session:通过 Ort::Session 加载 .onnx 文件。
  3. 输入校验:强制要求模型仅有 1 个输入,并校验 ONNX 输入张量尺寸是否与配置计算出的 obs_num * frame_stack + extra_obs_num 完全一致,尺寸不匹配会立即抛出异常终止启动。
  4. 预分配缓冲区:根据输入形状和关节数量分别分配 input_bufferoutput_buffer
  5. 绑定张量:利用 Ort::Value::CreateTensor 将预分配缓冲区包装为 ONNX Tensor,后续推理循环直接复用,无需每次重新分配。

这种「启动时加载、运行时复用」的策略消除了实时循环中的内存分配开销,是满足硬实时约束的关键设计。

Sources: inference_node.cpp

张量管理与零拷贝推理

推理循环对延迟极度敏感,因此项目采用零拷贝(Zero-Copy)张量绑定机制。input_tensoroutput_tensor 在初始化时通过 Ort::Value::CreateTensor 直接指向 ModelContextstd::vector<float> 的底层数据指针,生命周期内永不重新创建。

flowchart LR
    A[obs_manager<br/>组装观测] -->|std::copy| B[input_buffer]
    B -->|Ort::Value::CreateTensor<br/>零拷贝引用| C[ONNX Runtime<br/>Session::Run]
    C -->|写入| D[output_buffer]
    D -->|action_scale + default_angle| E[RobotInterface<br/>apply_action]

inference() 线程中,每次循环仅执行:

  1. 将最新观测通过 std::copy / std::move 写入 input_buffer
  2. 调用 session->Run(..., input_tensor.get(), ..., output_tensor.get(), ...)
  3. output_buffer 读取动作,经 action_scale_ 缩放与默认角度偏移后送入电机。

整个路径没有额外的 mallocmemcpy 产生,输入输出张量与缓冲区之间始终维持指针引用关系。

Sources: inference_node.cpp, inference_node.cpp

实时推理循环与线程配置

推理运行在独立的 inference 线程中,通过 SCHED_FIFO 设置为实时优先级 70,周期由 dt * decimation 决定(例如 dt=0.004decimation=5 时周期为 20 ms)。线程启动时通过 Ort::ThreadingOptions 创建全局 ONNX 环境,并允许通过 YAML 参数 intra_threads 控制算子内并行线程数,默认设为 1 以避免与实时调度产生竞争。

sequenceDiagram
    participant Inf as inference线程
    participant ONNX as ONNX Runtime
    participant Robot as RobotInterface

    loop 每 dt*decimation
        Inf->>Inf: update_obs_segments / flatten
        Inf->>Inf: update_stacked_obs (帧堆叠)
        Inf->>ONNX: session->Run(...)
        ONNX-->>Inf: output_buffer
        Inf->>Inf: clamp + action_scale + default_angle
        Inf->>Robot: publish_action (写入act_)
    end

若单帧推理耗时超过周期,系统会打印 Inference loop overran! 警告,帮助开发者识别模型复杂度或线程竞争问题。

Sources: inference_node.hpp, inference_node.cpp

多策略模型支持

本项目支持在内存中同时驻留多个策略模型,通过 active_policy_idx_ 切换当前活跃策略。每个策略在 load_config() 阶段被解析为独立的 PolicyRuntime,拥有自己的 ModelContextobs_layoutframe_stack。这意味着不同策略可以加载完全不同的 ONNX 文件、观测维度与帧堆叠深度,例如:

切换策略时,推理循环仅改变 active_policy() 的引用目标,不需要重新加载模型或重建会话。关于策略切换的触发机制与状态迁移,请参阅 多策略运行时与热切换机制

Sources: ros_interface.cpp, inference_node.hpp

性能优化配置

ONNX Runtime 的 SessionOptions 在 setup_model() 中进行了四项针对性优化:

优化项 配置值 作用
DisablePerSessionThreads 复用全局线程池,降低线程创建开销
EnableCpuMemArena 启用 CPU 内存池,减少页分配
EnableMemPattern 启用静态内存规划,提升缓存局部性
SetGraphOptimizationLevel ORT_ENABLE_ALL 开启全部图优化(常量折叠、算子融合等)

这些选项的组合确保了在 CPU -only 场景下获得最低的推理延迟与最稳定的内存占用。配合 Ort::Env 级别的 ThreadingOptions 将 intra-op 线程限制在 1 条,有效避免了多线程调度对 SCHED_FIFO 控制线程的干扰。

Sources: inference_node.cpp

观测帧堆叠与张量形状

对于时序策略模型,单次观测往往不足以表达运动状态。系统通过 update_stacked_obs() 支持两种帧堆叠顺序,将多帧历史观测拼接到 input_buffer 中:

堆叠顺序 说明 适用场景
frame_major 先按帧排列,每帧内部按观测字段排列 常规时序策略
obs_major 先按观测字段排列,每个字段内部按帧排列 需要字段级时序对齐的模型

堆叠后的总输入尺寸为 obs_num * frame_stack + extra_obs_num,其中 extra_obs 不参与堆叠(常用于外部感知特征)。setup_model() 在初始化时严格校验该尺寸与 ONNX 模型输入张量是否一致,防止运行时形状错误。

Sources: inference_node.cpp, inference_node.cpp

约束与故障排查

在使用 ONNX Runtime 推理引擎时,需要特别注意以下约束:

与其他模块的衔接

ONNX Runtime 推理引擎并非独立工作,其上下游关系如下:

如果你需要新增一种观测源并喂入 ONNX 模型,除了修改 YAML 配置外,还需在 观测布局配置与动态解析 中注册对应的 ObsSourceDefinition