★ 这一页的核心主张已被同一晚的实验推翻,保留以便追溯 ★ 本页写于 state 泄漏结论之前,把 state 被截成 6 维当成「白训了」,要求验到 [17] 才算 OK。 方向是反的:右臂任务里截断保留的前 6 维是左臂,而左臂全程不动 —— 捷径正是这样被断掉的。 四个模型的对照里,state 含正在动的那条臂的两个模型视觉斜率只有 +0.004 / +0.006,不含的两个是 +0.210 / +0.214,差 50 倍。
本页提的 b16 / b64 对照也随之作废:E 从 1.64 到 93.5 差 57 倍而斜率不变,E 已被证明不是决定因素。
→ 现在该看 假设清单 和 右臂 batch 16 训练页。
注意:左臂数据集用不了这个截断方案 —— 截断保留的正是它们在动的那条臂。

左臂重训 · 两批对照

2026-09-08 · 数据集 xlerobot-team/xlerobot-left-pick-cup-sep3-clean-20260905(54 集 / 13,693 帧)
★ 这次跟上次最关键的区别:多了一行 --policy.input_features=null 上一批(09-07 那个 batch 64)少了这一行,导致 observation.state 从 17 维被静默砍成 6 维, 模型拿着残缺状态训了 20000 步。它不报错、不警告,只有事后翻 config.json 才看得出来。

① batch 16

lerobot-train \
  --policy.path=lerobot/smolvla_base \
  --policy.input_features=null --policy.output_features=null \
  --dataset.repo_id=xlerobot-team/xlerobot-left-pick-cup-sep3-clean-20260905 \
  --batch_size=16 \
  --num_workers=8 \
  --steps=20000 \
  --save_freq=2000 \
  --policy.scheduler_decay_steps=20000 \
  --dataset.eval_split=0.0 \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.max_num_transforms=5 \
  --dataset.video_backend=torchcodec \
  --seed=1000 \
  --policy.device=cuda --policy.push_to_hub=false \
  --output_dir=/workspace/outputs/train/left_b16 --job_name=left_b16 \
  2>&1 | tee /workspace/left_b16.log
E = 20000 × 16 ÷ 13693 = 23.4 遍

② batch 64

lerobot-train \
  --policy.path=lerobot/smolvla_base \
  --policy.input_features=null --policy.output_features=null \
  --dataset.repo_id=xlerobot-team/xlerobot-left-pick-cup-sep3-clean-20260905 \
  --batch_size=64 \
  --num_workers=8 \
  --steps=20000 \
  --save_freq=2000 \
  --policy.scheduler_decay_steps=20000 \
  --dataset.eval_split=0.0 \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.max_num_transforms=5 \
  --dataset.video_backend=torchcodec \
  --seed=1000 \
  --policy.device=cuda --policy.push_to_hub=false \
  --output_dir=/workspace/outputs/train/left_b64 --job_name=left_b64 \
  2>&1 | tee /workspace/left_b64.log
E = 20000 × 64 ÷ 13693 = 93.5 遍(跟上次废掉那批同一个 E,但这次 state 是对的)

两批只差 batch,其余逐字相同

这样跑完能直接分开两件事:上次训废到底是 state 被砍成 6 维造成的,还是 E 太高造成的。

结果说明
两批都变好root cause 就是 state[6],E 不是问题 —— 以后 batch 随便挑,只要别漏那一行
b16 好、b64 差E 确实有影响,往低 E 走;同时 state 那条也必须一直带着
两批都还差问题不在训练配方,回到数据本身(示范阶段性、杯位覆盖)

开跑前 40 步基准

正式开跑前,把 --steps=20000 临时改成 --steps=40 --save_checkpoint=false 先跑一遍:

看什么正常值
step/s约 4–5
mem_gb约 4.6(batch 16)
loss在下降
batch 64 显存需求高得多,24 G 的 4090 大概率装不下,要 48 G 以上那一档(A100 / A6000 / H100)。

★ 训完第一件事:验 state 维度 ★

别急着上机。先确认这次的 state 是不是 17 维——如果还是 6,那这批又白跑了,上机测多少次都没意义。

python -c "
import json
c=json.load(open('outputs/train/left_b16/checkpoints/020000/pretrained_model/config.json'))
s=c['input_features']['observation.state']['shape']
print('state shape:', s, '->', 'OK' if s[0]==17 else '★ 又错了,不要上机 ★')
print('images:', [k for k in c['input_features'] if 'image' in k])
"
期望输出:state shape: [17] -> OK,images 是 head / left_arm_wrist / right_arm_wrist。

为什么上一批会废 —— 完整因果链

文档模板缺 --policy.input_features=null
  ↓
smolvla_base 自带的 input_features 没被清空(它是单臂 SO-100 的配置:state [6] + camera1/2/3)
  ↓
policies/factory.py:312 的 if not cfg.input_features: 不成立 → 数据集的 features 用不上
  ↓
state 从 17 维 → 6 维,静默,不报错
  ↓
模型拿着残缺状态训 20000 步 → 输出坍缩成一条平均轨迹
  ↓
离线实测视觉敏感度 0.8%(杯子位置差 58.7°,输出只差 0.49°)→ 实机 0/9

检查器结果

两条命令都过了 03-software/scripts/check_train_cmd.py:

检查项batch 16batch 64
没有 rename_map✓✓
input_features=null✓✓
decay_steps == steps✓✓
相机键名跟推理侧一致✓✓
E(数据重复遍数)23.4 偏高93.5 太高
image_transforms开着开着
那两项警告是故意留的,不是疏漏 E=93.5 报 ❌ —— 这次就是要拿它跟 b16 对照,看修好 state 之后高 E 还差不差。
image_transforms 开着 —— 0826 那批 b16 模型开着增强,而它是目前唯一被证实会看图的模型,所以跟着它对齐。

任务句子(上机试跑时逐字用,别手输)

Pick up the cup with the left arm.