Manipulation 入门
- 数据采集: U-Arm 以及 fork 仓库 遥操作 Agile PiPER 机械臂
- 模型:微调复现 openpi,复现 DP3
- 任务:从易到难(Pick & Place)从仿真到实机
- 训练平台: RTX 4090D 48G,使用 Docker 做环境隔离
数据采集环境准备
1. 完全复刻 U-Arm 中的硬件完成校准

2. 在 SAPIEN 中搭建仿真环境
使用 U-Arm 模拟器教程 中的指令来安装环境并启动模拟器
Pitfall 1: 环境配置,需要注意最后 a) 需要手动安装 pip install setuptools==81.0; b) 需要下载 ManiSkill 的场景数据集 python -m mani_skill.utils.download_asset "ReplicaCAD"。 此外如果在 conda 环境中需要运行 setup_ros_venv.sh 来安装 ros 依赖。
Pitfall 2: 容器渲染,在 debian13 + 自制 Docker 的环境下会出现一些问题:
- 在容器调教方面,发现只有宿主机使用 nvidia-container-toolkit==1.17.6-1 能够比较轻松的启动 SAPIEN - 同时如果使用默认的 rt-fast 渲染会因为宿主机缺少 optix 库而无法 denoise 输出的图像会有大量噪声 - 在宿主机安装了 optix 的库后,SAPINE 调用 optix 会导致 internal error。有两种解决方案 a) 使用 oidn,CPU 完成 denoise 会带来轻微的延迟 b) 使用以下命令来尝试将宿主机的库手动复制到容器内: ``
bash C=pi # 检查容器内缺少的库 docker exec -it "\(C" bash -lc ' echo "== Vulkan ICD ==" ls -l /etc/vulkan/icd.d/nvidia_icd.json 2>/dev/null || true ls -l /usr/share/vulkan/icd.d/nvidia_icd.json 2>/dev/null || true echo "== Vulkan layer ==" ls -l /etc/vulkan/implicit_layer.d/nvidia_layers.json 2>/dev/null || true ls -l /usr/share/vulkan/implicit_layer.d/nvidia_layers.json 2>/dev/null || true echo "== EGL vendor ==" ls -l /usr/share/glvnd/egl_vendor.d/10_nvidia.json 2>/dev/null || true echo "== OptiX bin ==" ls -l /usr/share/nvidia/nvoptix.bin 2>/dev/null || true echo "== NVIDIA libraries ==" ldconfig -p | grep -E "libGLX_nvidia|libnvoptix|libnvidia-rtcore|libnvidia-glvkspirv|libnvidia-gpucomp" || true ' # 复制缺少的 layers.json docker exec -u root "\)C" mkdir -p \ /etc/vulkan/implicit_layer.d \ /usr/share/nvidia docker cp /usr/share/vulkan/implicit_layer.d/nvidia_layers.json \ "\(C":/etc/vulkan/implicit_layer.d/nvidia_layers.json cp -L /usr/share/nvidia/nvoptix.bin /tmp/nvoptix.bin # 复制 optix 需要的模型参数 docker cp /tmp/nvoptix.bin \ "\)C":/usr/share/nvidia/nvoptix.bin rm /tmp/nvoptix.bin``
optix denoise 效果: 
oidn denoise 效果: 
无 denoise 效果: 
3.机械臂以及相机调整
- 原始的 U-Arm 代码中存在关节顺序/行程映射错误的问题,在 fork 仓库 中进行了修复(调整 3、4 号关节顺序关节方向,调整夹爪行程映射)。
- 原本的代码中只有第三人称的 "human" 视角,在 fork 仓库 添加了一个俯视固定相机和腕部相机。
OpenPi 训练
训练流程
- 采集数据:定义简单任务,固定红色物块以及蓝色盘子,固定任务将红色物块放到蓝色盘子上。其余颜色盘子随机生成在桌面的其他位置。 收集 20 episode 给 openpi 做 lora finetune。
定义 state 为 6 个关节角度 + 1 夹爪状态,action 为关节角度变化量,观测为桌面上方的俯视 (640 480) 和腕部相机 (320 320)

- 修改代码,添加
piper_sim_policy.py用于桥接采集到的数据和模型输出的维度。 在config.py中添加基于 pi0 的微调训练 config调整了 openpi 导入顺序,先导入 checkpoint 后导入 train 否则会提前退出 调整了 norm stat 的保存位置
uv run scripts/compute_norm_stats.py --config-name pi0_piper_sim_low_mem_finetune
XLA_PYTHON_CLIENT_MEM_FRACTION=0.9 uv run scripts/train.py pi0_piper_sim_low_mem_finetune --exp-name=my_experiment --overwrite
Pi0 测试
在录制数据集的相同环境下进行推理测试,大概架构为 policy server + simulation。
- 加载训练好的 ckpt,本地启动一个 policy server
- 启动一个仿真环境,与 policy server 建立连接,准备好后将机器人的状态以及相机渲染画面通过 websocket 传给 policy server。
- 从 policy server 接受控制指令,并传送给机器人。
但是今天在初步测试的过程中,机械臂会靠近红色的目标物块,但是接近后不能对准,在接近后会闭合夹爪,但是无法抓取方块。 在很多情况下会出现直接乱摆的情况,猜测是训练数据的长度基本 20s 以内就结束,所以超过 20s 算是 OOD?
下一步启动 pi0.5 微调测试
Pi0.5 训练测试
在 lgd 的提示下,使用了 Pi0.5 作为基础模型进行训练,loss 下降相较 Pi0 更快,在 10000 时做了一次 rollout。

能够稳定靠近物块且对准(相较 Pi0 是提升),同时能够在对准之后合并夹爪。 但是无法合并到足够小的值,会出现:在相机内夹爪和物块之间看起来没有空隙后就会抬起夹爪,于是无法夹持物体。 后续也能够移动到蓝色盘子上方,并放开夹爪。 如下所示:
在 rollout 过程中因仿真器默认渲染一个可以移动的 pov 相机,导致渲染卡顿,后续修改为只渲染相机视角。
类似的,在采集数据的时候也出现了仿真器渲染速度小于录制的 fps,目前怀疑是该问题导致了上文提到的无法抓握的问题,已经重新采集修复后的 31 episode 的训练集重新训练。
再次训练后与前一次的结果近似,在代码中添加了对 gripper 的距离以及接触情况的可视化。
可以看出来在抓取过程中当夹爪两端接触到物体后,action 基本为 0,这与数采得到的结果相同,因为当前的数采每个关节以及 gripper 的 action 定义是 \(a_t = s_{t+1} - s_t\)。 在夹持物体的时候确实是 0,LeRobot 文档 提到了关于不同 action 表征方式的使用。 其中提到 gripper 最好以 binary 的形式出现。
因此修改代码,录制原始的关节以及遥操作的指令,并转换为 absolute angle 给 OpenPi,由 OpenPi 在训练时转换为 relative action。
uv run scripts/compute_norm_stats.py --config-name pi05_piper_sim_low_mem_finetune
XLA_PYTHON_CLIENT_MEM_FRACTION=0.9 uv run scripts/train.py pi05_piper_sim_low_mem_finetune --exp-name=pi05_relative_action --overwrite
uv run scripts/serve_policy.py policy:checkpoint --policy.config=pi05_piper_sim_low_mem_finetune --policy.dir=checkpoints/pi05_piper_sim_low_mem_finetune/pi05_relative_action/10000
rollout 对于固定位置抓物体的效果很好,几乎和遥操作没区别,但是当物块移动 5cm 后,抓取的效果就立马变得很差很差。
这次训练将会随机放置红方块和盘子,随机区域由桌面和机械臂工作空间的交集确定,呈圆环状。 采集了 100 条 episode 包含了 3 条抓起后打滑重新抓取,3-5条在边缘勉强抓起在舒适的地方主动放下微调,2 条放置不到位重新放置。
但是当前只采集了遥操臂的指令以及关节实际状态,在几乎采集完之后添加了物体盘子的初始位置记录,以及末端 6 自由度的录制,后续可以使用。
rollout 结果非常好,不仅能够完成示例的将红色方块放到蓝色盘子,修改指令后有概率完成其他颜色方块放入其他颜色盘子的任务。
以上我认为训练的数据通路已经搭建完毕,接下来将分析当前模型的能力。
模型分析
random task
训练的时候只包括红色方块和三色盘子,发现在添加额外方块后其他的方块会以以下的方式干扰模型:
- 模型会 pick 错误的方块,尤其当 target 不是蓝色,会更容易发生。
- 模型在完成指定的任务后,不会返回 home 而会尝试将其他方块也放到目标位置
因此我添加了 20 条有额外的方块的数据,并再次进行微调。 测试结果如下。
本报告分析 LeRobot-Anything-U-Arm/eval_results/random_task/ 下的 OpenPI rollout 评测结果,并结合论文 Robust Skills, Brittle Grounding: Diagnosing Restricted Generalization in Vision-Language Action Policies via Multi-Object Picking 的诊断框架进行解释。
论文指出,视觉-语言-动作策略在 manipulation benchmark 中的高成功率,不一定意味着模型真正学到了稳健的语言到对象 grounding。模型可能已经掌握较稳定的抓取、移动等操作原语,但当评测打破训练中的对象、颜色、位置或目标关联时,指令条件成功率会明显下降。论文将这种现象概括为:技能相对稳健,grounding 更脆弱。
本项目中的关键数据设定如下:
random_task对应的训练数据包含 100 条“红色物体到蓝色目标”的 demo,以及 20 条“随机颜色到随机颜色”的 demo。- 对照目录
eval_results/openpi_rollout_eval_red_blue_30000/对应更窄的数据分布,只有 100 条“蓝色到红色”的 demo。 random_task评测每组包含 20 个 episode,主要比较原始颜色组合put red box to blue plate与颜色迁移组合put red box to green plate。
需要注意:eval_results/openpi_rollout_eval_red_blue_30000/original_30000_results.json 中的评测 prompt 字段仍显示为 put red box to blue plate。因此,本报告将该目录作为训练数据分布对照来讨论,而 random_task 的定量结论仅来自其自身 6 个 results.json。
2. random_task 结果汇总
2.1 原任务:red box -> blue plate
| Checkpoint | Episodes | Success | Returned Home | Success + Home | 平均首次成功步数 | 中位首次成功步数 | 失败 episode | | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | | origin_5000 | 20 | 18 / 20 (90%) | 14 / 20 (70%) | 13 / 20 (65%) | 220.2 | 208.0 | 2, 10 | | origin_10000 | 20 | 18 / 20 (90%) | 18 / 20 (90%) | 18 / 20 (90%) | 252.4 | 223.0 | 10, 11 | | origin_12000 | 20 | 20 / 20 (100%) | 17 / 20 (85%) | 17 / 20 (85%) | 204.3 | 201.5 | 无 |
原任务表现较强,三个 checkpoint 的任务成功率分别为 90%、90%、100%。这说明在训练主分布附近,策略能够可靠完成“红盒放到蓝盘”的目标。尤其是 origin_12000 达到 20/20 成功,说明模型并非缺少基本抓取、移动、放置能力。
不过,returned_home 与 success + home 指标低于单纯 success,说明部分 episode 虽然完成了接触/放置目标,但收尾动作并不总是稳定。例如 origin_5000 的 success 是 90%,但 success + home 只有 65%。因此,任务成功和轨迹稳定性应分开看。
2.2 颜色迁移任务:red box -> green plate
| Checkpoint | Episodes | Success | Returned Home | Success + Home | 平均首次成功步数 | 中位首次成功步数 | 失败 episode | | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | | green_plate_5000 | 20 | 13 / 20 (65%) | 10 / 20 (50%) | 8 / 20 (40%) | 225.8 | 203.0 | 1, 9, 10, 14, 15, 17, 19 | | green_plate_10000 | 20 | 14 / 20 (70%) | 14 / 20 (70%) | 10 / 20 (50%) | 230.9 | 219.0 | 4, 8, 10, 16, 18, 20 | | green_plate_12000 | 20 | 14 / 20 (70%) | 16 / 20 (80%) | 12 / 20 (60%) | 223.0 | 219.5 | 2, 7, 10, 18, 19, 20 |
将目标盘从 blue 改为 green 后,成功率下降到 65% 到 70%。这一下降不是训练步数不足导致的单调问题:5000、10000、12000 三个 checkpoint 都停留在相近区间,没有恢复到原任务的 90% 到 100%。这更像是颜色-目标 grounding 的泛化瓶颈,而不是单纯的运动控制不足。
从首次成功步数看,green plate 成功 episode 的平均首次成功步数约为 223 到 231,与 origin 任务并没有明显变慢。这说明一旦模型选对了目标并进入正确操作模式,执行速度并未显著恶化。主要问题更可能发生在“根据语言选择正确目标盘”这一阶段,而非完整操作技能完全崩溃。
个人结论
我认为模型理想情况下应该将红色到蓝色的位置泛化能力简单的迁移到红色到绿色。 在传统的规划控制视角下,你已经完成了从任意起点到任意目标,那么只是起点和目标位置改变只需要简单迁移。 但是在 VLA 下,这样的区别就会导致大量的问题。
我在想:
- RoboTwin 是否就是在尝试解决泛化数据的产生问题?
- VLA 为什么不具有这样的迁移能力,WAM 就能够有吗?
实机部署
类似的,在实验室中搭建真机部署流程类似:安装腕部和俯视摄像头,配置图像获取脚本,修改录制脚本接入真机数据。
- uarm 由于角度分辨率比价低,在缓慢小范围移动的时候会有跳变的动作。
- 在推理的时候一开始的脚本是:观测->推理 100ms -> 50 的 action chunk 执行前 10 个-> block ->观测->推理 100ms -> unblock。那么会导致每过 10 action 就会卡住等推理。 因此实现了 action ensamble 和异步推理,简单来说就例如当前部署:执行(消耗 action)=30hz,action chunk 大小=50,推理延迟=100ms。 那么我们推理开始的时候先停住,观测,等待第一个 chunk 返回,执行,当执行还剩 15 action 就执行完前 20 个的时候,进行一次推理。 那么当推理结束后我们应该还剩 5 action,此时丢弃新来的 chunk 中的前 3 个,剩余的和之前的剩下的做均值,执行。 这样的好处是推理的延迟被藏在执行过程当中了,整个执行都会流畅很多。
可以参考The importance of action chunking in imitation learning
为了微调,采集了 1 种胶带卷分布在桌面的不同位置,放置到目标位置的 40 条(15s)的数据。 训练后能够将另外两种外观不同,厚度不同的胶带卷放置到目标盒子中。
在测试过程中我发现一些现象:
- 俯瞰视角只对引导腕部相机指向目标有效:通过在不同阶段遮挡俯瞰镜头,可以看出来当腕部相机能够直接看到目标后,俯瞰视角几乎不起效。
- 对于光照比较敏感:通过关闭主灯,使用移动的闪光灯,发现抓取成功率下降,猜测是因为模型学到了影子和几何的关系。
- 当桌面上有两个胶带(训练数据集中不包含)那么它将会随机选一个,放入目标后认为任务完成,即我认为它学到了目标盒子中存在胶带代表任务完成。