从环境搭建、数据采集、smolVLA 训练到 SO101 真机推理的完整踩坑记录,重点复盘 RTC 推理策略下轨迹顿挫、抓取精度下降问题的排查过程。

1. 背景

SO101(SO-ARM101)是一款低成本的开源机械臂方案,采用飞特(Feetech)总线舵机,配合 Hugging Face 的 LeRobot 框架,可以快速搭建一套"数据采集 → 模仿学习训练 → 真机部署"的具身智能闭环。本文使用 LeRobot v0.5.x 代码(JoyandAI 维护的同步仓库 dev 分支),策略模型为 smolVLA——一个面向边缘设备实时推理设计的轻量级视觉-语言-动作(VLA)模型。

最终目标:在 SO101 上完成"抓取目标方块放入白框"这一任务的真机推理。

硬件安全红线:

  • 舵机切勿长时间大负载工作,特别是长时间堵转、持续用力夹持木块石头等硬物,容易温度过高甚至烧坏。
  • 出现以下情况第一时间断开电源适配器,避免不可逆损坏:
    1. 舵机闪红灯;
    2. 某个关节被物理位置卡死。

2. 环境配置

2.1 软件环境

推荐使用 Miniconda + Python 3.12:

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
sh Miniconda3-latest-Linux-x86_64.sh
source ~/.bashrc

conda create -n lerobot python=3.12
conda activate lerobot

Windows 用户建议使用 Anaconda Powershell Prompt 执行命令(cmd.exe 的命令解析规则不同,容易踩坑)。

2.2 代码仓库

LeRobot 官方仓库更新频繁,为保证教程与代码一致,使用同步维护的仓库:

# 第一次安装
git clone https://github.com/JoyandAI/lerobot.git
# 已有老版本则更新
cd lerobot && git pull

# 切换到 dev 分支(0.5 版本代码)
git checkout dev
git pull origin dev

注意:克隆路径不要包含中文字符,否则运行时可能找不到 Python 库路径。

安装依赖(需带上飞特舵机驱动):

cd lerobot
pip install -e ".[feetech]"

另外需要安装 ffmpeg(建议 7.1.1,支持 libsvtav1 编码),用于视频数据的编码存储。

2.3 国内网络注意事项

  • GitHub 连接不稳定:可尝试使用GitHub520解决;
  • pip 可切换国内镜像源:升级 pip 后配置 pip config set global.index-url <国内镜像>,网络差时可临时用镜像站升级 pip 本身。

2.4 硬件连接

  • SO101 通过串口与上位机通信(本例为 COM6),相机为两个 USB 摄像头(腕部 + 正面),均按 640×480@30fps 采集。
  • 首次使用前需完成舵机校准与相机内外参检查,确认各关节零点正确、相机画面无遮挡。

3. 数据采集与训练

3.1 数据采集

使用 LeRobot 自带的遥操作流程录制示范数据:

lerobot-record ^
    --robot.disable_torque_on_disconnect=true ^
    --robot.type=so101_follower ^
    --robot.port=COM6 ^
    --robot.id=LXFA0001 ^
    --robot.cameras="{'wrist': {'type':'opencv', 'index_or_path':0, 'width':640, 'height':480, 'fps':30}, 'front': {'type':'opencv', 'index_or_path':1, 'width':640, 'height':480, 'fps':30}}" ^
    --teleop.type=so101_leader ^
    --teleop.port=COM5 ^
    --teleop.id=LXLA0001 ^
    --display_data=true ^
	--dataset.push_to_hub=false ^
    --dataset.repo_id=my_dataset/so101_train_B ^
	--dataset.root=so101_train_B_20260907_173056 ^
    --dataset.num_episodes=20 ^
	--dataset.episode_time_s=20 ^
	--dataset.reset_time_s=5 ^
    --dataset.single_task="Grab the cube of E into the white box" ^
    --resume=true

这里"–resume"和"–dataset.root"用于往原有数据集新增录制数据,本例采集了共40个epoch (一般30~50个epoch可有效抓取)。

3.2 模型训练

lerobot提供了完整的训练api,我的命令如下(请忽略个人本地路径):

lerobot-train \
  --dataset.repo_id=local/svla_so101 \
  --dataset.root=hf_cache/lerobot/my_dataset/so101_train_B_20260907_173056 \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.max_num_transforms=3 \
  --dataset.image_transforms.random_order=true \
  --policy.type=smolvla \
  --policy.load_vlm_weights=false \
  --policy.vlm_model_name=/tmp/hf_models/SmolVLM2-500M-Video-Instruct \
  --policy.push_to_hub=false \
  --policy.use_peft=false \
  --output_dir=outputs/train/smolvla_so101_bed \
  --job_name=smolvla_so101_3060 \
  --policy.device=cuda \
  --wandb.enable=false \
  --steps=13000 \
  --batch_size=32 \
  --num_workers=4

4. 真机推理:sync 与 rtc 两种策略

LeRobot v0.5 为真机部署提供了两种推理策略(--inference.type):

  • sync:模型推理一次输出完整 action chunk(例如 50 步),全部执行完再重新推理。轨迹内部平滑,但 chunk 之间存在推理等待间隙,反应迟钝;
  • rtc:保持连续推理,模型在后台持续生成新的 action chunk,控制循环同时执行动作;新 chunk 推理完成后,会根据推理期间已经执行的步数(源代码是按fps计算而非实际步数,记住要考)更新动作队列。反应快、可闭环纠错,是实时部署的推荐模式。

我的部署命令如下:

lerobot-rollout \
  --strategy.type=base \
  --policy.path=outputs/train/smolvla_so101_bed/checkpoints/013000/pretrained_model \
  --policy.device=cuda \
  --robot.type=so101_follower \
  --robot.id=LXFA0001 \
  --robot.port=COM6 \
  --robot.cameras='{"wrist": {"type": "opencv", "index_or_path": 0, "width": 640, "height": 480, "fps": 30}, "front": {"type": "opencv", "index_or_path": 1, "width": 640, "height": 480, "fps": 30}}' \
  --task="Grab the cube of E into the white box" \
  --duration=60 \
  --inference.type=rtc \
  --inference.rtc.execution_horizon=10 \
  --inference.rtc.max_guidance_weight=10.0 \
  --display_data=true \
  --interpolation_multiplier=2

切换到 rtc 后,机械臂出现明显异常:运动轨迹不平滑,急加速急停、一段一段的,抓取精度大幅下降。下文完整复盘排查过程。

5. 问题排查:rtc 为什么让机械臂"发疯"

5.1 直接现象

  • 关节运动呈离散段落,启停冲击明显;
  • 末端到达目标位置时抖动、对不准,抓取成功率显著下降。

5.2 原因一:重规划导致规划不连续

rtc 模式每步都基于最新观测重新推理出一段完整动作序列,但实际只执行前k步就被新规划覆盖。问题在于:相邻两次推理即便输入几乎相同,输出的轨迹也会因观测噪声(相机帧差异)和模型随机性而产生偏差。执行路径等于"许多起点略有差异、方向略有差异的短轨迹"的拼接,可能会导致抖动。这只是理论上的,如果因为某些原因起点未对齐导致差异过大,段与段之间就会出现跳变,宏观上就是急加速急停。base 策略执行完整 chunk,轨迹内部平滑,因此没有此问题——rtc 本质上是用轨迹平滑性换反应速度。

5.3 原因二:理论延迟与实际延迟不一致

源码默认fps=30,意味着每个控制步时长为0.33 秒,在该前提下,从警告日志

WARNING on_queue.py:239 Indexes diff is not equal to real delay. indexes_diff=19, real_delay=27

可以发现:

  • indexes_diff=19:按帧计数推算的计划延迟(约 0.63s);
  • real_delay=27:按真实时间戳测得的实际延迟(约 0.9s)。

实际比计划多出 8 帧(约 0.27s),即每轮推理都比预算慢 0.27 秒以上。后果有两个:

  1. 动作序列在时间轴上不连续,表现为顿挫;
  2. 动作到达机械臂时对应的观测已"过期"——模型以为自己在状态 A 执行动作,实际臂已在状态 B,抓取精度自然下降。

5.4 原因三:execution_horizon设置不当

RTC 用 execution_horizon 设置新动作块中受旧动作引导的前缀范围。当 real_delay 大于 execution_horizon 时,当前权重逻辑会把引导范围限制在前 10 步;但推理完成后,队列会跳过约27步,实际开始使用的是新动作块更靠后的部分。于是,最终接上旧队列的动作,可能没有受到足够的旧动作约束。

5.5 放大器:SO101 的廉价舵机

同样的策略抖动,在工业臂上可能几乎不可感知,但 SO101 的总线舵机对速度跳变的容忍度低,会把规划层面的不连续忠实放大为机械层面的顿挫。

6. 解决方案

  1. 加大 execution_horizon(10 → 15):每次推理执行更长的段、降低重规划频率,平滑性改善最明显;代价是反应略迟钝。这是最有效的单一手段。
  2. 直接返回indexes_diff而非real_delay:调整后 indexes_diff 与 real_delay 趋于一致,机械臂能精度范围内稳定抓取。
  3. 降低fps:事实(5.3)证明我的机械臂在推理时无法达到30fps,可能是由于USB带宽不足导致拖帧,相机无法稳定输出30fps,且推理延迟过高(900ms左右),降低fps能允许更高的推理延迟,我最终使用了15fps。

经验:execution_horizon / fps 必须大于单轮推理的真实耗时。

7. 视频对比

以下是rtc策略下优化前后的真机运行对比视频:

  • 优化前(启停冲击明显,无法抓取)
  • 优化后(稳定抓取,速度较慢)

8. 总结

  • SO101 + LeRobot + smolVLA 可以低成本跑通完整的 VLA 真机闭环,但廉价硬件对策略输出的时间连续性和平滑性非常敏感;
  • rtc 模式的顿挫往往不是模型不行,而是重规划频率过高 + 推理延迟超标 + 频率不对齐三者叠加,把 VLA 长程预测不一致的问题直接传导到了关节上;

参考资料

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐