本文档总结当前的 ACT 模型部署流程。当前目标是把 PyTorch 训练得到的 ACT 模型导出为 ONNX,然后使用 ONNX Runtime在 StarryOS/QEMU/RISC-V 环境中完成推理。
整体流程:
PyTorch ACT 模型
-> 导出 ONNX
-> 准备 float32 输入数据
-> 交叉编译 ONNX Runtime RISC-V 动态库
-> 编写 C 语言 ONNX Runtime 推理程序
-> 交叉编译 C 程序为 RISC-V 可执行文件
-> 给 StarryOS 增加 /proc 内存统计补丁
-> 写入 StarryOS rootfs
-> 在 QEMU/StarryOS 中执行 ACT 推理并统计时间、内存、可执行文件大小
文档结构:
一、当前目录结构
二、模型导出为 ONNX
三、输入数据处理
四、交叉编译 ONNX Runtime
五、C 语言推理程序
六、交叉编译 C 推理程序
七、写入 StarryOS 镜像
八、在 StarryOS/QEMU 中运行
九、StarryOS /proc 内存统计补丁
十、StarryOS/QEMU 实验结果
当前 StarryOS 中跑通的输出形式:
ACT ONNX C inference start
model: /root/act/act.onnx
ORT version: 1.26.0
ONNX C output shape: [1, 8, 3]
first step: left_vel=+0.006228 | right_vel=-0.000566 | gripper_target=-0.000000
当前的 GitHub 提交仓库目录为:
/home/czw1/clean-repos/proj57-complete-clean
├── README.md
├── docs/
│ └── images/
│ └── left_turn_inference_result.png
└── qemu_act_experiment/
├── QEMU_ACT_FULL_FLOW.md
├── build_ort_riscv64_musl.sh
├── install_build_helpers.sh
├── scripts/
│ └── export_onnx.py
├── models/
│ └── .gitignore
├── data/
│ ├── frame_000001.jpg
│ ├── frame_000227.jpg
│ └── stats.json
├── c_onnx/
│ ├── act_onnx_c.c
│ ├── prepare_inputs.py
│ ├── prepare_left_turn_inputs.py
│ ├── input_bins/
│ │ ├── image_1x1x3x224x224.bin
│ │ ├── state_1x2.bin
│ │ ├── action_q01_3.bin
│ │ └── action_q99_3.bin
│ └── input_bins_left_turn/
│ ├── image_1x1x3x224x224.bin
│ ├── state_1x2.bin
│ ├── action_q01_3.bin
│ └── action_q99_3.bin
├── runtime_libs/
│ └── riscv64/
│ ├── libonnxruntime.so.1
│ ├── libatomic.so.1
│ ├── libstdc++.so.6
│ └── libgcc_s.so.1
└── starryos_memstat_patch/
├── README.md
└── kernel/src/
├── task/memstat.rs
├── task/mod.rs
└── pseudofs/proc.rs
StarryOS 中的部署目录:
/root/act
├── act.onnx
├── act_onnx_c_riscv64
└── input_bins/
├── image_1x1x3x224x224.bin
├── state_1x2.bin
├── action_q01_3.bin
└── action_q99_3.bin
StarryOS 中的运行时动态库目录:
/lib/libonnxruntime.so.1
/lib/libatomic.so.1
/lib/libstdc++.so.6
/lib/libgcc_s.so.1
原始模型是 PyTorch 的 model.pt。StarryOS 上不部署 PyTorch,而是部署 ONNX Runtime,所以需要先把 PyTorch 模型导出成 ONNX。
导出脚本:
/home/czw1/proj57-work/proj57/qemu_act_experiment/scripts/export_onnx.py
核心导出逻辑:
torch.onnx.export(
wrapper,
(dummy_image, dummy_state),
ONNX_PATH,
input_names=["image", "state"],
output_names=["action"],
opset_version=17,
do_constant_folding=True,
dynamo=False,
)导出结果:
/home/czw1/proj57-work/proj57/qemu_act_experiment/models/act.onnx
当前 C 语言推理程序不直接处理 JPEG 图片、JSON 文件和归一化逻辑,而是先用 Python 把输入转换成 float32 二进制文件。
输入处理脚本:
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/prepare_inputs.py
生成文件:
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/input_bins/image_1x1x3x224x224.bin
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/input_bins/state_1x2.bin
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/input_bins/action_q01_3.bin
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/input_bins/action_q99_3.bin
各文件含义:
image_1x1x3x224x224.bin
预处理后的图片输入。
类型是 float32。
shape 是 [1, 1, 3, 224, 224]。
含义是 [batch, camera, channel, height, width]。
state_1x2.bin
归一化后的状态输入。
类型是 float32。
shape 是 [1, 2]。
action_q01_3.bin
action 的 1% 分位数,用于反归一化。
action_q99_3.bin
action 的 99% 分位数,用于反归一化。
反归一化公式:
real = (normalized + 1) / 2 * (q99 - q01) + q01
模型输出的 action 是归一化值,不是直接控制量。C 程序会用 action_q01/action_q99 把第一步动作还原为真实动作值。
StarryOS/QEMU 的目标平台是 RISC-V,因此不能使用 x86_64 Linux 的 ONNX Runtime 动态库,需要交叉编译 RISC-V 版本。
ONNX Runtime 源码目录:
/home/czw1/ort-work/onnxruntime
交叉编译工具链:
/opt/riscv64-linux-musl-cross/bin/riscv64-linux-musl-gcc
/opt/riscv64-linux-musl-cross/bin/riscv64-linux-musl-g++
交叉编译脚本:(这里非常非常慢 给我的wsl2跑崩了一次 我的5070的电脑跑了半个多小时)
/home/czw1/proj57-work/proj57/qemu_act_experiment/build_ort_riscv64_musl.sh
编译出的 ONNX Runtime 主库:
/home/czw1/ort-work/onnxruntime/build/Linux/MinSizeRel/libonnxruntime.so.1.26.0
当前部署包中保存的 RISC-V 运行库:
/home/czw1/proj57-work/proj57/qemu_act_experiment/runtime_libs/riscv64/libonnxruntime.so.1
/home/czw1/proj57-work/proj57/qemu_act_experiment/runtime_libs/riscv64/libatomic.so.1
/home/czw1/proj57-work/proj57/qemu_act_experiment/runtime_libs/riscv64/libstdc++.so.6
/home/czw1/proj57-work/proj57/qemu_act_experiment/runtime_libs/riscv64/libgcc_s.so.1
这些动态库的作用:
libonnxruntime.so.1
ONNX Runtime 主库,负责加载 ONNX 模型并执行推理。
libatomic.so.1
原子操作运行库。RISC-V 上部分 atomic 操作需要它。
libstdc++.so.6
C++ 标准库。虽然推理程序是 C 写的,但 ONNX Runtime 内部是 C++ 实现。
libgcc_s.so.1
GCC 运行时库,提供底层运行时支持。
当前正式使用的推理源码:
/home/czw1/proj57-work/proj57/qemu_act_experiment/c_onnx/act_onnx_c.c
该程序直接使用 ONNX Runtime C API:
#include <onnxruntime_c_api.h>推理程序核心流程:
1. 拼出模型和输入文件路径。
2. 读取 image/state/action_q01/action_q99 四个 float32 bin 文件。
3. 通过 OrtGetApiBase()->GetApi(ORT_API_VERSION) 获取 ONNX Runtime C API 函数表。
4. 创建 OrtEnv。
5. 创建 OrtSessionOptions。
6. 加载 /root/act/act.onnx,创建 OrtSession。
7. 创建 image/state 输入 Tensor。
8. 调用 g_ort->Run(...) 执行推理。
9. 从 output_tensor 中取出 action。
10. 对第一步 action 做反归一化。
11. 打印 left_vel/right_vel/gripper_target。
12. 释放 ONNX Runtime 对象和 malloc 内存。
输出 shape:
[1, 8, 3]
含义:
1 = batch size
8 = 一次预测未来 8 个动作步
3 = 每步 3 个动作值
动作维度:
left_vel
right_vel
gripper_target
第一步动作对应:
action[0] = 第 1 步 left_vel
action[1] = 第 1 步 right_vel
action[2] = 第 1 步 gripper_target
进入 C 推理目录:
cd ~/proj57-work/proj57/qemu_act_experiment/c_onnx交叉编译命令:
/opt/riscv64-linux-musl-cross/bin/riscv64-linux-musl-gcc \
-O2 \
-no-pie \
act_onnx_c.c \
-I/home/czw1/ort-work/onnxruntime/include/onnxruntime/core/session \
-L/home/czw1/ort-work/onnxruntime/build/Linux/MinSizeRel \
-lonnxruntime \
-Wl,-rpath,/lib \
-o act_onnx_c_riscv64检查编译结果:
file act_onnx_c_riscv64
readelf -d act_onnx_c_riscv64 | grep NEEDED正常应看到:
ELF 64-bit LSB executable, UCB RISC-V
NEEDED Shared library: [libonnxruntime.so.1]
NEEDED Shared library: [libc.so]
StarryOS 镜像路径:
/home/czw1/starryos-work/StarryOS/make/disk.img
需要写入 StarryOS 的文件:
/root/act/act.onnx
/root/act/act_onnx_c_riscv64
/root/act/input_bins/image_1x1x3x224x224.bin
/root/act/input_bins/state_1x2.bin
/root/act/input_bins/action_q01_3.bin
/root/act/input_bins/action_q99_3.bin
/lib/libonnxruntime.so.1
/lib/libatomic.so.1
/lib/libstdc++.so.6
/lib/libgcc_s.so.1
写入方式是在 WSL2 中使用 debugfs 直接修改 StarryOS 的 ext4 镜像。
基本流程:
cd ~/starryos-work/StarryOS
e2fsck -f -y make/disk.img || true
debugfs -w -f install_commands.debugfs make/disk.img
e2fsck -f -y make/disk.img || true命令含义:
e2fsck -f -y make/disk.img
检查并修复 ext4 镜像。
debugfs -w -f install_commands.debugfs make/disk.img
以写模式打开镜像,并根据命令文件写入部署文件。
再次 e2fsck
写入完成后再次检查文件系统一致性。
启动 StarryOS:
cd ~/starryos-work/StarryOS
make ARCH=riscv64 run进入 StarryOS shell 后执行:
cd /root/act
./act_onnx_c_riscv64成功输出示例:
ACT ONNX C inference start
model: /root/act/act.onnx
ORT version: 1.26.0
ONNX C output shape: [1, 8, 3]
first step: left_vel=+0.006228 | right_vel=-0.000566 | gripper_target=-0.000000
为了满足比赛文档中“运行时内存占用情况”的要求,本项目对 StarryOS 做了一个最小内核补丁,让 /proc/self/status 支持 Linux 风格的进程内存字段:
VmSize 进程虚拟内存映射总量
VmHWM 进程历史峰值常驻内存
VmRSS 进程当前常驻内存
补丁代码保存在:
qemu_act_experiment/starryos_memstat_patch/
├── README.md
└── kernel/src/
├── task/memstat.rs
├── task/mod.rs
└── pseudofs/proc.rs
核心实现逻辑:memstat.rs 遍历进程地址空间中的 memory area,并逐页调用页表查询;能查到物理页的虚拟页计入 RSS。ProcessData 中新增 rss_high_water 用于记录峰值 RSS,proc.rs 在生成 /proc/[pid]/status 时输出 VmSize / VmHWM / VmRSS。
构建验证命令:
cd ~/starryos-work/StarryOS
export PATH=/opt/riscv64-linux-musl-cross/bin:/home/czw1/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export RUSTFLAGS=-Awarnings
make ARCH=riscv64 build
make ARCH=riscv64 runStarryOS 中验证:
cat /proc/self/status已验证输出包含:
VmSize: 1996 kB
VmHWM: 376 kB
VmRSS: 376 kB
因此该补丁只增强 /proc 内存观测能力,不改变 ACT 模型推理逻辑。
本节记录在 StarryOS/QEMU 环境中使用同一个 ACT ONNX C 推理程序,对两张输入图片分别运行后的结果。两组实验只替换 input_bins 输入数据,模型文件、ONNX Runtime 动态库、C 推理程序保持一致。
待验证图片 frame_000227.jpg:
待验证图片 frame_000001.jpg:
实验环境:
OS: StarryOS on QEMU riscv64
Runtime: ONNX Runtime 1.26.0, CPUExecutionProvider
Program: /root/act/act_onnx_c_riscv64
Model: /root/act/act.onnx
Input shape: image [1, 1, 3, 224, 224], state [1, 2]
Output shape: action [1, 8, 3]
指标含义:
executable_size_bytes RISC-V C 推理可执行文件大小
inference_time_ms 单次 OrtRun 推理耗时,不包含 StarryOS 启动时间
VmSize 推理结束后进程虚拟地址空间大小
VmHWM 推理过程中进程峰值常驻内存,作为内存占用主要指标
VmRSS 推理结束时进程当前常驻内存
两组实验结果:
| 输入图片 | 输入目录 | 可执行文件大小 | 推理耗时 | VmSize | VmHWM 峰值内存 | 第一帧动作输出 | 结果说明 |
|---|---|---|---|---|---|---|---|
frame_000227.jpg |
qemu_act_experiment/c_onnx/input_bins |
13056 bytes / 12.75 KB | 16285.831 ms | 229112 KB | 218108 KB | left_vel=+0.006228, right_vel=-0.000566, gripper_target=-0.000000 |
左轮速度大于右轮速度 |
frame_000001.jpg |
qemu_act_experiment/c_onnx/input_bins_left_turn |
13056 bytes / 12.75 KB | 17919.316 ms | 229112 KB | 218108 KB | left_vel=-0.001547, right_vel=+0.007225, gripper_target=-0.000000 |
右轮速度大于左轮速度,符合左转趋势 |
StarryOS 终端输出示例:
ACT ONNX C inference start
model: /root/act/act.onnx
executable_size_bytes=13056
executable_size_kb=12.75
mem_start_kb: VmSize=16100 | VmHWM=2880 | VmRSS=2880
mem_after_read_inputs_kb: VmSize=16692 | VmHWM=3480 | VmRSS=3480
ORT version: 1.26.0
mem_after_create_session_kb: VmSize=220644 | VmHWM=212212 | VmRSS=212212
mem_before_run_kb: VmSize=220644 | VmHWM=212212 | VmRSS=212212
inference_time_ms=17919.316
mem_after_run_kb: VmSize=229112 | VmHWM=218108 | VmRSS=218108
ONNX C output shape: [1, 8, 3]
first step: left_vel=-0.001547 | right_vel=+0.007225 | gripper_target=-0.000000
本项目在文档撰写和问题排查方面使用 AI 编辑器辅助,但核心代码编写、流程设计、交叉编译、StarryOS/QEMU 部署和实验验证均由本人独立完成。