EVO-RL 适配 Theseus S1 机械臂操作指南

项目地址:https://github.com/YHRG-Robotics/Evo-RL-theseus

在原有的 EVO-RL 框架基础上,完成了对 Theseus S1 机械臂的适配。

通过本次适配,Theseus S1 的用户(无论是单臂还是双臂操作)现在可以无缝接入 EVO-RL 的闭环强化学习工作流。本指南将带您完成从前期硬件配置、多视角相机挂载、专家数据采集,到价值函数(Value Function)训练、优势条件策略(ACP)微调,以及最终带有“人工介入(Human-in-the-loop)”机制的模型评估与推理发布全流程。

适用硬件: Theseus S1 机械臂当前使用 V2 版本的 USB 串口 进行通信。V1 / V2 差异: V1 版本主臂使用 CAN 接口,V2 版本使用 USB 串口,现在全面改为V2版本,不再使用V1版本。

硬件校准特别说明: Theseus S1 机械臂在底层 SDK 层面已默认完成校准。因此,在使用 EVO-RL 框架时,不需要在框架层进行二次校准(您会在后续的所有启动命令中看到 --robot.require_calibration=false 和 --teleop.require_calibration=false 参数均已被显式设置为 false)。

环境安装

  1. 克隆项目
GIT_LFS_SKIP_SMUDGE=1 git clone https://github.com/YHRG-Robotics/Evo-RL-theseus.git
cd Evo-RL-theseus
  1. 安装编译工具链 (Linux)
sudo apt update
sudo apt install cmake build-essential
  1. 创建虚拟环境
conda create -y -n evo-rl python=3.10
conda activate evo-rl
  1. 克隆并安装 SDK 可在任意目录下克隆并执行编译脚本(不一定要在本项目文件夹内):
git clone https://github.com/YHRG-Robotics/S1_SDK_V2.git
cd S1_SDK_V2
./install.sh
  1. 安装项目依赖 回到 Evo-RL-theseus 根据想要训练的模型安装对应的依赖:
# 例如,如果想训练 SmolVLA
pip install -e ".[smolvla]"

# 或者,如果想训练 pi05 模型
pip install -e ".[pi]"

更多模型支持请查看: For setup details and platform-specific dependencies, follow the official LeRobot configuration guide.

硬件设置

S1 Series (V1 / V2) 版本差异说明

[!] 关键版本差异说明:

  • V1 版本:使用 CAN 接口进行通信(历史版本,不再使用)。

  • V2 版本:使用 USB 串口进行通信(例如 /dev/ttyUSB0, /dev/ttyUSB1),本文下面的所有命令均以 V2 为准。

硬件类型与配置说明

Theseus S1 硬件主要分为以下三种类型:

  • leader:主控臂(主臂),用于人类操作员进行遥控。
  • follower:从动臂(从臂),执行任务的实体。
  • 复合式臂 (mix):主从一体式臂。

配合关系与夹爪类型 (end_effector) 设置:

  1. 主从模式 (主臂 + 从臂):

    • 使用 leader 作为主臂时,可以配合 follower 或 复合式臂 作为从臂。
    • 如果从臂是 follower,其 end_effector 必须设置为 gripper。
    • 如果从臂是 复合式臂,其 end_effector 必须设置为 mix。
  2. 复合式臂方案 (主从一体):

    • 仅使用复合式臂进行操作(即“一臂两用”,不再需要额外的 leader 臂)。
    • 在此方案下,robot(机器人侧)和 teleop(遥控侧)的 end_effector 均需设置为 mix。

[!NOTE] 机械臂接入顺序说明: 在四个机械臂全部拔下之后,请按照 左从臂、右从臂、左主臂、右主臂 的顺序依次插入各自的 USB 到电脑上。系统默认映射分别为 USB0、USB1、USB2、USB3,即左从臂 = /dev/ttyUSB0、右从臂 = /dev/ttyUSB1、左主臂 = /dev/ttyUSB2、右主臂 = /dev/ttyUSB3。

系统环境与设备检查

在运行任何命令之前,必须先赋予 USB/Video 端口相应的读写权限:

# 检查设备是否成功挂载
ls -l /dev/ttyUSB*
ls -l /dev/video*

# 赋予串口和摄像头权限
sudo chmod 666 /dev/ttyUSB*
sudo chmod 666 /dev/video*

参数说明:

  • <LEFT_FOLLOWER_USB_PORT> / <RIGHT_FOLLOWER_USB_PORT>: 对应的 USB 串口设备名(例如 /dev/ttyUSB0, /dev/ttyUSB1)。
  • 如需额外的 USB 主控设备,请同样使用 /dev/ttyUSB* 设备名。

摄像头配置与测试

在数据采集前,请务必确认摄像头的索引号。您可以使用以下命令来寻找并测试摄像头:

需要先安装intel realsense摄像头驱动:

sudo apt install librealsense2-dkms librealsense2-utils
pip install pyrealsense2

然后执行下面命令查看摄像头画面:

lerobot-find-cameras realsense --output-dir=./outputs/cam_test_rs

从输出控制台获取相机的 Id(即 12 位序列号,如 352122272634)。 为了确认 ID 对应的物理位置,请检查导出目录中的图片(如 ./outputs/cam_test_rs/realsense_XXXX.png),根据图片画面判断左臂、右臂和全局相机,并依次填入后续命令的占位符中。

参数说明:

  • --output-dir: 测试相机画面的保存输出目录。

摄像头分配策略(双臂模式):

  • left(左臂视角):挂载在 --robot.left_arm_config.cameras 下。
  • right(右臂视角)与 middle(全局视角):挂载在 --robot.right_arm_config.cameras 下。

摄像头分配策略(单臂模式):

  • wrist(单臂视角)与 middle(全局视角):挂载在 --robot.cameras 下。

主臂 + 从臂 场景

架构说明

双臂和单臂情景可以使用的硬件配置:

  • 双臂情境
    • 两个 follower + 两个 leader
    • 两个 mix + 两个 leader(此时 mix 暂时充当 follower)
  • 单臂情境
    • 一个 follower + 一个 leader
    • 一个 mix + 一个 leader(此时 mix 暂时充当 follower)

主从操控

双臂情景

两个 follower + 两个 leader

lerobot-teleoperate \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=gripper \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=gripper \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader \
  --teleop.require_calibration=false \
  --display_data=true

如果使用两个 mix + 两个 leader 的组合,请改用以下命令:

lerobot-teleoperate \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader_mix \
  --teleop.require_calibration=false \
  --display_data=true

参数说明:

  • --robot.left_arm_config.* / --robot.right_arm_config.*: 使用 left/right 前缀为双臂指定各自的配置(如通信口 dev、协议 version、末端 end_effector 等)。遥控端主臂同理(使用 --teleop.left_arm_config.* 等)。
  • --robot.left_arm_config.cameras / --robot.right_arm_config.cameras: JSON 格式的相机字典配置。type 为相机后端(如 intelrealsense),通过 serial_number_or_name 指定摄像头唯一序列号,还可配置分辨率、帧率及摄像头预热缓冲时间(warmup_s)。命令中的键名统一使用 left、middle、right。
  • --display_data: 是否即时在屏幕上渲染显示摄像头画面及遥操作进度数据面板。

单臂情景

如果您使用的是单臂环境,请运行以下命令验证主从操控是否正常:

一个 follower + 一个 leader

lerobot-teleoperate \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=gripper \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<TELEOP_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --display_data=true

如果使用 一个mix + 一个leader 的组合,请改用以下命令:

lerobot-teleoperate \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<TELEOP_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --display_data=true

参数说明:

  • --robot.type / --teleop.type: 指定机器人的整体配置类型(例如 s1_follower 代表单臂从臂,s1_leader 代表单臂主臂)。
  • --robot.dev / --teleop.dev: 对应硬件通信端口,均使用 V2 版本对应 USB 串口 <FOLLOWER_USB_PORT> / <TELEOP_USB_PORT>。
  • --robot.end_effector / --teleop.end_effector: 末端执行器类型(从臂可以使用 gripper 或 mix 夹爪,主臂使用 teach 示教器)。
  • --robot.version / --teleop.version: 底层硬件协议版本。
  • --robot.id / --teleop.id: 用户自定义的唯一设备标识符。
  • --robot.require_calibration / --teleop.require_calibration: 告知框架该设备是否需要校准。因 S1 硬件底层已支持绝对值标定,这里固定配置为 false。
  • --display_data: 是否即时在屏幕上渲染显示摄像头画面及遥操作进度数据面板。

1. 采数据

使用 lerobot-human-inloop-record收集第一轮强化学习数据

[!IMPORTANT]

  • 每一集数据录制结束时,必须按下快捷键s(代表成功)和 快捷键f(代表失败),如果不按任何按键,默认为任务失败,会导致后续训练失效
  • 第一轮数据,由于此时还没有模型被训练出来的模型,只能人类全程操控,所以理论上全部集数都应该标记为成功(每一集都按下s)

双臂情景

两个 follower + 两个 leader

lerobot-human-inloop-record \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=gripper \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=gripper \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.num_image_writer_processes=3 \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

如果使用两个 mix (即两个复合式臂)+ 两个 leader 的组合,请改用以下命令:

lerobot-human-inloop-record \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader_mix \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.num_image_writer_processes=3 \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

参数说明:

  • --dataset.repo_id: 数据集的存储名,包含 local/ 前缀(如 local/<DATASET_NAME>)代表将数据保存在本地。
  • --dataset.single_task: 本次采集任务的简短文本描述,如 "Pick up the block"。
  • --dataset.num_episodes: 计划采集的数据回合(Episode)总数。
  • --dataset.episode_time_s: 单个回合的最长录制时间(秒)。
  • --dataset.reset_time_s: 两个回合之间预留给机械臂复位或人工调整准备的时间(秒)。
  • --dataset.num_image_writer_processes: 图像写入磁盘的后台进程数,适当增加可提升保存速度。
  • --dataset.push_to_hub: 录制完成后是否将数据集自动推送到 Hugging Face Hub(本地采集可设为 false)。
  • --play_sounds: 录制各阶段切换(如开始、结束、失败)时是否播放提示音效。

快捷键说明:

按键功能
i切换介入模式(在"模型自动控制"与"人工接管控制"之间切换)
s标记为成功并结束当前回合(Episode)
f标记为失败并结束当前回合
→ (右方向键)提前结束当前的循环(保存并进入下一个回合)
← (左方向键)提前结束并重新录制当前回合(舍弃本次录制的数据)
Esc停止并退出整个录制会话

单臂情景

一个 follower + 一个 leader

lerobot-human-inloop-record \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=gripper \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true

如果是 mix + leader 的组合,请改用以下命令:

lerobot-human-inloop-record \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true

2. 检查数据

lerobot-dataset-report --dataset local/<DATASET_NAME>

参数说明:

  • --dataset: 待分析与检查的数据集本地路径或 Hub 标识。

3. 价值函数训练

在您采集完数据后,需要训练价值函数 (Value Function)。

lerobot-value-train \
    --dataset.repo_id=<DATASET_PATH> \
    --dataset.video_backend=pyav \
    --value.type=pistar06 \
    --value.dtype=bfloat16 \
    --batch_size=4 \
    --num_workers=0 \
    --output_dir=outputs/value_train/<RUN_NAME> \
    --job_name=<JOB_NAME> \
    --wandb.enable=false

参数说明:

  • --dataset.video_backend: 视频处理后端,推荐使用 pyav 以获取更好的性能。
  • --value.type: 指定使用的价值函数模型类型(框架目前支持 pistar06 等代号)。
  • --value.dtype: 训练与推理时使用的数据精度,配置 bfloat16 可以大幅降低显存占用。
  • --batch_size: 每步训练迭代的 batch size。
  • --num_workers: 数据加载的线程数。为避免多线程与硬件通信干扰,安全起见可设为 0 代表使用主线程同步加载。
  • --output_dir: 训练输出的日志、模型检查点(Checkpoints)等文件的最终保存目录。
  • --job_name: 为当前训练任务指定的一个易于识别的名称。
  • --wandb.enable: 是否开启 Weights & Biases (WandB) 在线实验数据追踪日志(不使用时可关停以节省资源开销)。

多卡训练 (Multi-GPU Training)

如果你有多个 GPU,可以使用 accelerate 来加速训练过程:

CUDA_VISIBLE_DEVICES=<GPU_ID_LIST> accelerate launch \
  --multi_gpu \
  --num_processes=<NUM_GPUS> \
  --mixed_precision=bf16 \
  $(which lerobot-value-train) \
  --batch_size=32/<NUM_GPUS> \
  <VALUE_TRAIN_ARGS>

多卡训练参数说明:

  • CUDA_VISIBLE_DEVICES: 指定可见的 GPU 列表(如 0,1,2,3)。
  • --num_processes: 指定并行的 GPU 数量。
  • --mixed_precision: 混合精度训练模式,推荐使用 bf16。
  • --batch_size: 这里指单卡的 batch size。为了保持总 batch size 为 32,建议设置为 32 / <NUM_GPUS>。

4. 价值函数推理

训练完成后,使用价值推断 (Value Inference) 为数据集打上 ACP 标签(Advantage-Conditioned Tags):

lerobot-value-infer \
    --dataset.repo_id=<DATASET_PATH> \
    --dataset.video_backend=pyav \
    --inference.checkpoint_path=outputs/value_train/<RUN_NAME>/checkpoints/last/pretrained_model \
    --runtime.device=cuda \
    --runtime.batch_size=4 \
    --acp.enable=true \
    --acp.n_step=10 \
    --acp.positive_ratio=0.1 \
    --acp.value_field=complementary_info.value \
    --acp.advantage_field=complementary_info.advantage \
    --acp.indicator_field=complementary_info.acp_indicator \
    --output_dir=outputs/value_infer/<RUN_NAME> \
    --job_name=<INFER_JOB_NAME>

参数说明:

  • --inference.checkpoint_path: 用于推理打标签的价值模型检查点(Checkpoint)路径。
  • --runtime.device: 运行推理任务使用的设备(如 cuda)。
  • --runtime.batch_size: 批量推断时的 batch size。
  • --acp.enable: 是否开启优势条件策略 (Advantage-Conditioned Policies, ACP) 计算与辅助打标签模块。
  • --acp.n_step: 计算 N 步累积收益(Return)的时间窗口长度。
  • --acp.positive_ratio: 判定数据为“优势条件”的比例阈值。例如设为 0.1,代表将排名前 10% 的专家数据打上优选的正向标签。
  • --acp.value_field / --acp.advantage_field / --acp.indicator_field: 规定推断得出的价值 (Value)、相对优势绝对差值 (Advantage) 以及最后判定的 ACP 明确条件标志位,在保存进数据集中所使用的字段列名。

检查标签是否成功打入:

您可以通过一段简短的 Python 脚本检查 .parquet 文件,验证 complementary_info 字段是否已生成:

python -c "
import pandas as pd; 
df = pd.read_parquet('<DATASET_PATH>/data/chunk-000/file-000.parquet'); 
print('数据集包含的字段有:'); 
print(df.columns.tolist())
"
# 期望看到新增字段: 'complementary_info.value', 'complementary_info.advantage', 'complementary_info.acp_indicator'

5. 模型训练

基于上述使用价值函数打好标签的数据集,训练一个act模型(也支持训练其他模型):

lerobot-train \
  --dataset.repo_id=<DATASET_PATH> \
  --dataset.video_backend=pyav \
  --policy.type=act \
  --policy.device=cuda \
  --acp.enable=true \
  --acp.indicator_field=complementary_info.acp_indicator \
  --acp.indicator_dropout_prob=0.3 \
  --batch_size=8 \
  --steps=5000 \
  --output_dir=outputs/train/<POLICY_RUN_NAME> \
  --job_name=<POLICY_JOB_NAME> \
  --wandb.enable=false \
  --policy.push_to_hub=false

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:训练时使用 --rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

参数说明:

  • --policy.type: 指定策略模型的架构类型(如 act 代表 Action Chunking with Transformers 等)。
  • --policy.device: 训练策略模型所使用的运算设备(如 cuda)。
  • --acp.indicator_dropout_prob: 在训练中随机丢弃专家 ACP 标志的概率(这里设为 0.3)。这有助于防止策略模型陷入过度依赖完美条件标签的泥潭,从而提高在真实环境中未获得最优标签时的模型鲁棒性与泛化恢复力。
  • --steps: 训练任务运行的总迭代步数上限。
  • --policy.push_to_hub: 训练完成后是否将最终验证模型上传至 Hugging Face Hub(本地训练时可设为 false)。

6. 强化学习闭环+下一轮数据采集 (Human-in-the-loop)

模型训练完成后,使用 lerobot-human-inloop-record命令进入下一轮模型+人工介入的强化学习的数据采集。

[!IMPORTANT]

  • 在模型执行任务出错时,按下i键进入人类干预模式,此时人类接手,纠正模型行为。
  • 在第二轮及之后的所有轮次,因为已经存在训练完成的模型,此时数据由模型动作+人工介入之后的动作组成。
  • 最后仍然需要按下 s键 或 f键,代表这一集数据任务是否成功完成,如果不按任何按键,默认为任务失败,会导致后续训练失效。

双臂情景

两个 follower + 两个 leader

lerobot-human-inloop-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=gripper \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=gripper \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果使用两个 mix + 两个 leader 的组合,请改用以下命令:

lerobot-human-inloop-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.left_arm_config.version=V2 \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.right_arm_config.version=V2 \
  --teleop.id=my_bi_s1_leader_mix \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

参数说明:

  • --policy.path: 用于闭环评估或推理定型的策略模型(Checkpoint)文件路径。
  • --acp_inference.enable: (可选) 是否开启优势条件推理(ACP)。默认为 false 时,得益于训练时的 Dropout 机制,模型能以无标签的基线水平正常运行;若设为 true,系统会在提示中自动追加“高分/优势”标签(如 Advantage: positive),引导模型按照数据集中最佳专家的表现输出高优动作。
  • --acp_inference.use_cfg: (可选) 是否启用无分类器引导(Classifier-Free Guidance, CFG)。该项依赖 --acp_inference.enable=true。启用后,底层将在每一步同时完成“有标签”与“无标签”的两次推理,并通过差值外推显著提升生成的动作精确度与成功率。注意: 因为步骤计算量翻倍,会增加推理延迟,适合对动作质量要求极高且算力充足的闭环部署场景。
  • --acp_inference.cfg_beta: (可选) CFG 增强策略的引导力度系数(缩放因子)。默认为 1.0(即标准偏差)。建议调节范围为 1.0 ~ 3.0,适度调高(如 2.0)能够放大高优动作信号、促使动作更加坚决,但设置过高可能导致轨迹突变或动作不自然。

快捷键说明:

按键功能
i切换介入模式(在"模型自动控制"与"人工接管控制"之间切换)
s标记为成功并结束当前回合(Episode)
f标记为失败并结束当前回合
→ (右方向键)提前结束当前的循环(保存并进入下一个回合)
← (左方向键)提前结束并重新录制当前回合(舍弃本次录制的数据)
Esc停止并退出整个录制会话

[!NOTE] 介入模式工作原理 (bi_s1_direct_teleop)

  • 策略控制 (默认):[set_manual_control(False)] — 策略生成的 action 通过 robot.send_action() 控制臂运动,同时通过 teleop.send_feedback() 让臂跟随策略
  • 人工介入 (按 i):[set_manual_control(True)] — teleop 调用 gravity() 重力补偿,人可以自由拖拽臂进行纠正,action 来自 teleop 读取的当前位姿

继续回到第2步进入下一轮强化学习训练

单臂情景

一个 follower + 一个 leader

lerobot-human-inloop-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=gripper \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --dataset.num_image_writer_processes=3 \
  --display_data=true

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果是 mix + leader 的组合,请改用以下命令:

lerobot-human-inloop-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.version=V2 \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --dataset.num_image_writer_processes=3 \
  --display_data=true

如果您想删除本次评估录制产生的本地缓存数据,请执行以下命令进行清理:

rm -rf ~/.cache/huggingface/lerobot/local/eval_<DATASET_NAME>

模型推理

对最终经过多轮强化学习训练好的模型进行模型推理

双臂情景

两个 follower + 两个 leader

lerobot-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=gripper \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=gripper \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.id=my_bi_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=1 \
  --dataset.episode_time_s=15 \
  --dataset.push_to_hub=false \
  --display_data=true

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果使用两个 mix + 两个 leader 的组合,请改用以下命令:

lerobot-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=bi_s1_follower \
  --robot.left_arm_config.dev=<LEFT_FOLLOWER_USB_PORT> \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=<RIGHT_FOLLOWER_USB_PORT> \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_leader \
  --teleop.left_arm_config.dev=<LEFT_LEADER_USB_PORT> \
  --teleop.left_arm_config.end_effector=teach \
  --teleop.right_arm_config.dev=<RIGHT_LEADER_USB_PORT> \
  --teleop.right_arm_config.end_effector=teach \
  --teleop.id=my_bi_s1_leader_mix \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=1 \
  --dataset.episode_time_s=15 \
  --dataset.push_to_hub=false \
  --display_data=true

参数说明:

  • --dataset.fps: 定义环境和记录期间采用的时间截取与动作推演采样录制帧率(这里恒定为 30 fps)。

提示:在评估过程中产生的不必要的本地缓存数据集,可使用 rm -rf ~/.cache/huggingface/lerobot/local/<DATASET_NAME> 进行清理。

单臂情景

一个 follower + 一个 leader

lerobot-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=gripper \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=1 \
  --dataset.episode_time_s=15 \
  --dataset.push_to_hub=false \
  --display_data=true

如果是使用 mix 臂,请改用以下命令:

lerobot-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_follower \
  --robot.dev=<FOLLOWER_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_follower_mix \
  --robot.require_calibration=false \
  --teleop.type=s1_leader \
  --teleop.dev=<LEADER_USB_PORT> \
  --teleop.end_effector=teach \
  --teleop.id=my_s1_leader \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=1 \
  --dataset.episode_time_s=15 \
  --dataset.push_to_hub=false \
  --display_data=true

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果您想删除推理过程中产生的不必要的本地缓存数据集,请执行:

rm -rf ~/.cache/huggingface/lerobot/local/eval_<DATASET_NAME>

设备安全断开

如果在运行过程中遇到阻塞、程序异常退出或其他需要重置硬件的情况,建议运行以下 Python 脚本,安全断开双臂所有节点的连接:

双臂情景

两个 follower + 两个 leader

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_follower.config_s1_follower import S1FollowerConfig
from lerobot.robots.s1_follower.s1_follower import S1Follower
from lerobot.teleoperators.s1_leader.config_s1_leader import S1LeaderConfig
from lerobot.teleoperators.s1_leader.s1_leader import S1Leader

# 处理左从臂 (V2, USB)
robot_left = S1Follower(S1FollowerConfig(dev="<LEFT_FOLLOWER_USB_PORT>", version="V2", end_effector="gripper"))
robot_left.connect()
robot_left.disconnect()

# 处理右从臂 (V2, USB)
robot_right = S1Follower(S1FollowerConfig(dev="<RIGHT_FOLLOWER_USB_PORT>", version="V2", end_effector="gripper"))
robot_right.connect()
robot_right.disconnect()

# 处理左主臂 (V2, USB)
teleop_left = S1Leader(S1LeaderConfig(dev="<LEFT_LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop_left.connect()
teleop_left.disconnect()

# 处理右主臂 (V2, USB)
teleop_right = S1Leader(S1LeaderConfig(dev="<RIGHT_LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop_right.connect()
teleop_right.disconnect()
print("All S1 devices disconnected successfully.")
'

如果使用两个 mix + 两个 leader 的组合,请改用以下脚本:

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_follower.config_s1_follower import S1FollowerConfig
from lerobot.robots.s1_follower.s1_follower import S1Follower
from lerobot.teleoperators.s1_leader.config_s1_leader import S1LeaderConfig
from lerobot.teleoperators.s1_leader.s1_leader import S1Leader

# 处理左从臂 (V2, USB, mix)
robot_left = S1Follower(S1FollowerConfig(dev="<LEFT_FOLLOWER_USB_PORT>", version="V2", end_effector="mix"))
robot_left.connect()
robot_left.disconnect()

# 处理右从臂 (V2, USB, mix)
robot_right = S1Follower(S1FollowerConfig(dev="<RIGHT_FOLLOWER_USB_PORT>", version="V2", end_effector="mix"))
robot_right.connect()
robot_right.disconnect()

# 处理左主臂 (V2, USB)
teleop_left = S1Leader(S1LeaderConfig(dev="<LEFT_LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop_left.connect()
teleop_left.disconnect()

# 处理右主臂 (V2, USB)
teleop_right = S1Leader(S1LeaderConfig(dev="<RIGHT_LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop_right.connect()
teleop_right.disconnect()
print("All S1 mix + leader devices disconnected successfully.")
'

单臂情景

一个 follower + 一个 leader

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_follower.config_s1_follower import S1FollowerConfig
from lerobot.robots.s1_follower.s1_follower import S1Follower
from lerobot.teleoperators.s1_leader.config_s1_leader import S1LeaderConfig
from lerobot.teleoperators.s1_leader.s1_leader import S1Leader

# 处理从臂
robot = S1Follower(S1FollowerConfig(dev="<FOLLOWER_USB_PORT>", version="V2", end_effector="gripper"))
robot.connect()
robot.disconnect()

# 处理主臂
teleop = S1Leader(S1LeaderConfig(dev="<LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop.connect()
teleop.disconnect()
print("S1 single arm devices disconnected successfully.")
'

如果是使用 mix 臂,请改用以下脚本:

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_follower.config_s1_follower import S1FollowerConfig
from lerobot.robots.s1_follower.s1_follower import S1Follower
from lerobot.teleoperators.s1_leader.config_s1_leader import S1LeaderConfig
from lerobot.teleoperators.s1_leader.s1_leader import S1Leader

# 处理从臂 (mix)
robot = S1Follower(S1FollowerConfig(dev="<FOLLOWER_USB_PORT>", version="V2", end_effector="mix"))
robot.connect()
robot.disconnect()

# 处理主臂
teleop = S1Leader(S1LeaderConfig(dev="<LEADER_USB_PORT>", version="V2", end_effector="teach"))
teleop.connect()
teleop.disconnect()
print("S1 single arm (mix) devices disconnected successfully.")
'

复合式臂 场景

架构说明

复合式臂方案下,双臂和单臂情景可以使用的硬件配置:

  • 仅两个 mix(复合式夹爪数据采集,复合式臂)
  • 仅一个 mix(复合式夹爪数据采集,复合式臂)

[!IMPORTANT] 复合式臂方案下,采数据 / 主从操控时不再需要额外主臂!直接拖拽两条复合式臂即可。

[!NOTE] 机械臂接入顺序说明: 在两个复合式机械臂全部拔下之后,请按照 左复合式臂、右复合式臂 的顺序依次插入各自的 USB 到电脑上。系统默认映射分别为 USB0、USB1,即左复合式臂 = USB0、右复合式臂 = USB1。

[!NOTE] 以下命令中的设备路径和摄像头序列号为示例占位值,请根据实际硬件接线替换:

  • 左复合式臂串口: /dev/ttyUSB0,右复合式臂串口: /dev/ttyUSB1
  • 摄像头序列号: left=352122271174, right=352122272634, middle=352122271611

摄像头分配说明:

  • left(左臂视角摄像头)挂在 left_arm_config 下
  • right(右臂视角摄像头)+ middle(全局场景摄像头)挂在 right_arm_config 下

主从操控

[!TIP] teleop 的 dev 路径必须与 robot 侧完全一致,teleop 不会打开新的串口连接,而是通过 dev 字符串查找已建立的共享 arm 实例。

双臂情景

两个 mix(复合式臂)

lerobot-teleoperate \
  --robot.type=bi_s1_direct \
  --robot.left_arm_config.dev=/dev/ttyUSB0 \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=/dev/ttyUSB1 \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_direct_teleop \
  --teleop.left_arm_config.dev=/dev/ttyUSB0 \
  --teleop.left_arm_config.end_effector=mix \
  --teleop.right_arm_config.dev=/dev/ttyUSB1 \
  --teleop.right_arm_config.end_effector=mix \
  --teleop.id=my_bi_s1_direct_teleop \
  --teleop.require_calibration=false \
  --display_data=true

单臂情景

一个 mix(复合式臂)

lerobot-teleoperate \
  --robot.type=s1_direct \
  --robot.dev=<DIRECT_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=s1_direct_teleop \
  --teleop.dev=<DIRECT_USB_PORT> \
  --teleop.end_effector=mix \
  --teleop.id=my_s1_direct_teleop \
  --teleop.require_calibration=false \
  --display_data=true

1. 采数据

[!IMPORTANT]

  • 每一集数据录制结束时,必须按下快捷键s(代表成功)和 快捷键f(代表失败),如果不按任何按键,默认为任务失败,会导致后续训练失效
  • 第一轮数据,由于此时还没有模型被训练出来的模型,只能人类全程操控,所以理论上全部集数都应该标记为成功(每一集都按下s)

双臂情景

两个 mix(复合式臂)

lerobot-human-inloop-record \
  --robot.type=bi_s1_direct \
  --robot.left_arm_config.dev=/dev/ttyUSB0 \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=/dev/ttyUSB1 \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_direct_teleop \
  --teleop.left_arm_config.dev=/dev/ttyUSB0 \
  --teleop.left_arm_config.end_effector=mix \
  --teleop.right_arm_config.dev=/dev/ttyUSB1 \
  --teleop.right_arm_config.end_effector=mix \
  --teleop.id=my_bi_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.num_image_writer_processes=3 \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

快捷键说明:

按键功能
i切换介入模式(在"模型自动控制"与"人工接管控制"之间切换)
s标记为成功并结束当前回合(Episode)
f标记为失败并结束当前回合
→ (右方向键)提前结束当前的循环(保存并进入下一个回合)
← (左方向键)提前结束并重新录制当前回合(舍弃本次录制的数据)
Esc停止并退出整个录制会话

如果您想删除本次录制产生的本地缓存数据,可以执行以下命令进行清理:

rm -rf /home/wyb/.cache/huggingface/lerobot/local/<DATASET_NAME>

单臂情景

一个 mix(复合式臂)

lerobot-human-inloop-record \
  --robot.type=s1_direct \
  --robot.dev=<DIRECT_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=s1_direct_teleop \
  --teleop.dev=<DIRECT_USB_PORT> \
  --teleop.end_effector=mix \
  --teleop.id=my_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

如果您想删除本次录制产生的本地缓存数据,可以执行以下命令进行清理:

rm -rf ~/.cache/huggingface/lerobot/local/<DATASET_NAME>

2. 检查数据

lerobot-dataset-report --dataset local/<DATASET_NAME>

3. 价值函数训练

lerobot-value-train \
    --dataset.repo_id=<DATASET_PATH> \
    --dataset.video_backend=pyav \
    --value.type=pistar06 \
    --value.dtype=bfloat16 \
    --batch_size=4 \
    --num_workers=0 \
    --output_dir=outputs/value_train/<RUN_NAME> \
    --job_name=bi_s1_direct_value \
    --wandb.enable=false

### 多卡训练 (Multi-GPU Training)

如果你有多个 GPU,可以使用 `accelerate` 来加速训练过程:

```bash
CUDA_VISIBLE_DEVICES=<GPU_ID_LIST> accelerate launch \
  --multi_gpu \
  --num_processes=<NUM_GPUS> \
  --mixed_precision=bf16 \
  $(which lerobot-value-train) \
  --batch_size=32/<NUM_GPUS> \
  <VALUE_TRAIN_ARGS>

多卡训练参数说明:

  • CUDA_VISIBLE_DEVICES: 指定可见的 GPU 列表(如 0,1,2,3)。
  • --num_processes: 指定并行的 GPU 数量(例如 4)。
  • --mixed_precision: 混合精度训练模式,推荐使用 bf16。
  • --batch_size: 这里指单卡的 batch size。例如总 batch size 目标为 32,4 卡训练则设置为 8。

4. 价值函数推理

lerobot-value-infer \
    --dataset.repo_id=<DATASET_PATH> \
    --dataset.video_backend=pyav \
    --inference.checkpoint_path=outputs/value_train/<RUN_NAME>/checkpoints/last/pretrained_model \
    --runtime.device=cuda \
    --runtime.batch_size=4 \
    --acp.enable=true \
    --acp.n_step=10 \
    --acp.positive_ratio=0.1 \
    --acp.value_field=complementary_info.value \
    --acp.advantage_field=complementary_info.advantage \
    --acp.indicator_field=complementary_info.acp_indicator \
    --output_dir=outputs/value_infer/<RUN_NAME> \
    --job_name=s1-direct-value.infer

检查标签是否成功打入:

python -c "
import pandas as pd; 
df = pd.read_parquet('<DATASET_PATH>/data/chunk-000/file-000.parquet'); 
print('数据集包含的字段有:'); 
print(df.columns.tolist())
"
# 期望出现: 'complementary_info.value', 'complementary_info.advantage', 'complementary_info.acp_indicator'

5. 模型训练

lerobot-train \
  --dataset.repo_id=<DATASET_PATH> \
  --dataset.video_backend=pyav \
  --policy.type=act \
  --policy.device=cuda \
  --acp.enable=true \
  --acp.indicator_field=complementary_info.acp_indicator \
  --acp.indicator_dropout_prob=0.3 \
  --batch_size=8 \
  --steps=5000 \
  --output_dir=outputs/train/<MODEL_NAME> \
  --job_name=<RUN_NAME> \
  --wandb.enable=false \
  --policy.push_to_hub=false

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:训练时使用 --rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

6. 强化学习闭环+下一轮数据采集

模型训练完成后,使用 lerobot-human-inloop-record命令进入下一轮模型+人工介入的强化学习的数据采集。

[!IMPORTANT]

  • 在模型执行任务出错时,按下i键进入人类干预模式,此时人类接手,纠正模型行为。
  • 在第二轮及之后的所有轮次,因为已经存在训练完成的模型,此时数据由模型动作+人工介入之后的动作组成。
  • 最后仍然需要按下 s键 或 f键,代表这一集数据任务是否成功完成,如果不按任何按键,默认为任务失败,会导致后续训练失效。

双臂情景

两个 mix(复合式臂)

lerobot-human-inloop-record \
  --policy.path=/home/wyb/workspace/pretrain_models/dual-s1/pretrained_model \
  --policy.device=cuda \
  --robot.type=bi_s1_direct \
  --robot.left_arm_config.dev=/dev/ttyUSB0 \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=/dev/ttyUSB1 \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_direct_teleop \
  --teleop.left_arm_config.dev=/dev/ttyUSB0 \
  --teleop.left_arm_config.end_effector=mix \
  --teleop.right_arm_config.dev=/dev/ttyUSB1 \
  --teleop.right_arm_config.end_effector=mix \
  --teleop.id=my_bi_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true \
  --play_sounds=false

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果您想删除本次录制产生的本地缓存数据,可以执行以下命令进行清理:

rm -rf /home/wyb/.cache/huggingface/lerobot/local/<DATASET_NAME>

快捷键说明:

按键功能
i切换介入模式(在"模型自动控制"与"人工接管控制"之间切换)
s标记为成功并结束当前回合(Episode)
f标记为失败并结束当前回合
→ (右方向键)提前结束当前的循环(保存并进入下一个回合)
← (左方向键)提前结束并重新录制当前回合(舍弃本次录制的数据)
Esc停止并退出整个录制会话

[!NOTE] 介入模式工作原理 (bi_s1_direct_teleop)

  • 策略控制 (默认):[set_manual_control(False)] — 策略生成的 action 通过 robot.send_action() 控制臂运动,同时通过 teleop.send_feedback() 让臂跟随策略
  • 人工介入 (按 i):[set_manual_control(True)] — teleop 调用 gravity() 重力补偿,人可以自由拖拽臂进行纠正,action 来自 teleop 读取的当前位姿

单臂情景

一个 mix(复合式臂)

lerobot-human-inloop-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_direct \
  --robot.dev=<DIRECT_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=s1_direct_teleop \
  --teleop.dev=<DIRECT_USB_PORT> \
  --teleop.end_effector=mix \
  --teleop.id=my_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.reset_time_s=<RESET_SECONDS> \
  --dataset.push_to_hub=false \
  --dataset.num_image_writer_processes=3 \
  --display_data=true \
  --play_sounds=false

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果您想删除本次评估产生的本地缓存数据,请执行:

rm -rf ~/.cache/huggingface/lerobot/local/eval_<DATASET_NAME>

模型推理

双臂情景

两个 mix(复合式臂)

lerobot-record \
  --policy.path=outputs/train/<MODEL_NAME>/checkpoints/last/pretrained_model \
  --policy.device=cuda \
  --robot.type=bi_s1_direct \
  --robot.left_arm_config.dev=/dev/ttyUSB0 \
  --robot.left_arm_config.end_effector=mix \
  --robot.left_arm_config.version=V2 \
  --robot.left_arm_config.cameras='{"left": {"type": "intelrealsense", "serial_number_or_name": "352122271174", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.right_arm_config.dev=/dev/ttyUSB1 \
  --robot.right_arm_config.end_effector=mix \
  --robot.right_arm_config.version=V2 \
  --robot.right_arm_config.cameras='{"right": {"type": "intelrealsense", "serial_number_or_name": "352122272634", "width": 640, "height": 480, "fps": 30}, "middle": {"type": "intelrealsense", "serial_number_or_name": "352122271611", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_bi_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=bi_s1_direct_teleop \
  --teleop.left_arm_config.dev=/dev/ttyUSB0 \
  --teleop.left_arm_config.end_effector=mix \
  --teleop.right_arm_config.dev=/dev/ttyUSB1 \
  --teleop.right_arm_config.end_effector=mix \
  --teleop.id=my_bi_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=<NUM_EPISODES> \
  --dataset.episode_time_s=<EPISODE_SECONDS> \
  --dataset.push_to_hub=false \
  --display_data=true

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果您想删除推理过程中产生的不必要的本地缓存数据集,请执行:

rm -rf /home/wyb/.cache/huggingface/lerobot/local/eval_<DATASET_NAME>

单臂情景

一个 mix(复合式臂)

lerobot-record \
  --policy.path=<POLICY_CHECKPOINT_PATH> \
  --policy.device=cuda \
  --robot.type=s1_direct \
  --robot.dev=<DIRECT_USB_PORT> \
  --robot.end_effector=mix \
  --robot.version=V2 \
  --robot.cameras='{"wrist": {"type": "intelrealsense", "serial_number_or_name": "<WRIST_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}, "middle": {"type": "intelrealsense", "serial_number_or_name": "<MIDDLE_CAM_SERIAL>", "width": 640, "height": 480, "fps": 30, "warmup_s": 3}}' \
  --robot.id=my_s1_direct \
  --robot.require_calibration=false \
  --teleop.type=s1_direct_teleop \
  --teleop.dev=<DIRECT_USB_PORT> \
  --teleop.end_effector=mix \
  --teleop.id=my_s1_direct_teleop \
  --teleop.require_calibration=false \
  --dataset.repo_id=local/eval_<DATASET_NAME> \
  --dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
  --dataset.fps=30 \
  --dataset.num_episodes=1 \
  --dataset.episode_time_s=15 \
  --dataset.push_to_hub=false \
  --display_data=true

[!NOTE] 如果训练的是 SmolVLA 模型,请额外添加相机重命名参数:验证/推理时使用 --dataset.rename_map='{"observation.images.left": "observation.images.camera1", "observation.images.middle": "observation.images.camera2", "observation.images.right": "observation.images.camera3"}'。

如果您想删除推理过程中产生的不必要的本地缓存数据集,请执行:

rm -rf ~/.cache/huggingface/lerobot/local/eval_<DATASET_NAME>

设备安全断开

双臂情景

两个 mix(复合式臂)

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_direct.config_s1_direct import S1DirectConfig
from lerobot.robots.s1_direct.s1_direct import S1Direct

# 处理左复合式臂 (direct)
print("Connecting Left S1Direct (mix)...")
robot_left = S1Direct(S1DirectConfig(dev="/dev/ttyUSB0", version="V2", end_effector="mix"))
robot_left.connect()
robot_left.disconnect()

# 处理右复合式臂 (direct)
print("Connecting Right S1Direct (mix)...")
robot_right = S1Direct(S1DirectConfig(dev="/dev/ttyUSB1", version="V2", end_effector="mix"))
robot_right.connect()
robot_right.disconnect()

print("成功断开两个复合式臂")
'

单臂情景

一个 mix(复合式臂)

export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python -c '
from lerobot.robots.s1_direct.config_s1_direct import S1DirectConfig
from lerobot.robots.s1_direct.s1_direct import S1Direct

# 处理单臂复合式臂 (direct)
print("Connecting S1Direct (mix)...")
robot = S1Direct(S1DirectConfig(dev="<DIRECT_USB_PORT>", version="V2", end_effector="mix"))
robot.connect()
robot.disconnect()

print("成功断开单臂复合式臂")
'

复合式臂与主臂+从臂方案的对比

特性主臂+从臂方案复合式臂方案
硬件数量4 条臂(2 leader + 2 follower)2 条臂(一臂两用)
teleop 类型bi_s1_leaderbi_s1_direct_teleop
robot 类型bi_s1_followerbi_s1_direct
示教方式拖拽主臂直接拖拽复合式臂(重力补偿)
介入模式主臂接管 → 从臂跟随直接拖拽复合式臂
数据格式完全一致完全一致
策略兼容性完全一致完全一致