← reports index
重新训练 · 2026-09-08

重训右臂:用 state dropout 真正断开捷径

state6 那一版实机 0/2。今天把它拆开量了一遍,结论是两件互相独立的事:一个推理端的 bug(已修),和一个训练问题(必须重训)。这一页是重训的完整操作手册 —— 每一步该跑什么、看到什么算通过、什么情况下要停下来别继续烧租金。

最重要的一条:state6 的截断技巧没有生效。 它的设计意图是「不传 --policy.input_features=null,让 smolvla_base 自带的 state[6] 盖住数据集的 17 维,截断只留前 6 维(左臂,右臂任务里恒 0)」,以此断开 state 捷径。 实测:模型在推理时确实在读右臂 state(固定图像、只换 state,输出 pan 摆动 11.08°),而且把 state 全喂 0 并不比现在好。捷径从来没断过。

一、今天量到了什么

1. 推理端:state 通道被放大 1e8 倍(已修)

数据集 xlerobot-right-pick-cup-20260826-0042 的 16487 帧里左臂 6 维全是 0,于是 checkpoint 的归一化统计量是 mean=0, std=0。而 lerobot 的归一化没有对 std==0 做任何保护:

# lerobot/processor/normalize_processor.py:359
denom = std + self.eps          # eps = 1e-8
return (tensor - mean) / denom

训练时 (0-0)/1e-8 = 0,干净。真机上左臂是连着的,喂进去的是真实角度 —— 那 6 维被放大 1e8 倍。实测同一张图、同一右臂状态下:

左臂喂什么右臂输出(pan/lift/elbow/wflex/wroll/grip)与基准差
0(训练时的样子)-27.26   44.88   -21.23   -31.96   -0.19   16.93—
±0.1°-2.68   -2.45   42.53   -57.49   0.40   15.97maxΔ=63.76
±5°0.21   2.68   41.63   -61.43   1.12   16.34maxΔ=62.86
真机典型(数十度)-0.45   1.80   35.61   -55.24   1.05   13.15maxΔ=56.84

左臂偏 0.1 度,右臂输出变 64 度;而 0.1° / 5° / 几十度结果几乎一样 —— token 已经饱和,模型退化成输出一个跟本体姿态无关的固定姿势。

已修(run_policy_trials.py:1015 起,install_state_guard()):从 checkpoint 自己的统计量推,任何 std==0 的维度在喂给策略前钉回训练均值。不硬编码「左臂」,换模型自动重算;--dry / preview / 实跑同步循环 / RTC 四处全覆盖;只改喂给策略的那一份,安全层继续看真实 17 维(它要算 FK 和钳位)。对没有恒定维的老 checkpoint 自动 no-op。

修完用同一个 checkpoint、同样参数重跑 2 次:

修复前 T1修复前 T2修复后 T1修复后 T2
帧数 / 60 s9424195253
夹爪开合次数1018510
每帧开合率0.1060.0750.0530.040
CLIPPED010 (4.1%)03 (1.2%)
下探被安全层削0%9%0%2%
闭爪帧9 / 94(约 6 s)—30 / 95(约 19 s)
判定失败失败失败失败

抖动两次都对半砍,闭爪时机从「一伸手就夹」推到「先够一段再夹」—— 顺序对了。但仍 0/2。

2. 训练侧:视觉增益只有需要量的 9%

控制变量(所有集共用同一个 state,只换图像),沿够取过程测三个点:

phase杯位跨度输出 pan 跨度敏感度pan rpan 斜率
0.00(起始位)45.5°4.4°9.7%-0.310-0.041
0.3545.5°2.5°5.6%+0.449+0.029
0.70(快够到)45.5°2.8°6.2%+0.614+0.042

方向感随靠近变好(r 从 -0.31 升到 +0.61),但幅度始终是需要量的约 4%(示范里斜率应接近 +1)。

再换四种 state 喂法,看能不能救回来:

state 喂法pan 跨度敏感度r斜率
全零(17 维都是 0)8.37°18.4%+0.188+0.043
只钉左臂(当前修复)15.00°33.0%+0.255+0.086
固定真实 state5.50°12.1%+0.467+0.052
每集真实 state(修复前)11.98°26.3%-0.010-0.003
三条结论:
① 没有任何喂法能把斜率救到 0.3 以上 —— 不是喂错,是模型里就没有这个能力,必须重训;
② 全零并不优于只钉左臂 → 「训练时被截断成 6 维」不成立,训练吃的就是 17 维;
③ 修复前斜率 -0.003(视觉完全失效),修复后 +0.086 是四种里最好的 → 今天的推理端修复方向正确。

还量了 state 和图像谁说了算(固定其一、只变另一个):

A 只换图像 (state 固定)   输出 pan 跨度   6.19°   与杯位 r = -0.405
B 只换 state (图像固定)   输出 pan 跨度  11.08°   与杯位 r = +0.090
                          state 摆动 / 图像摆动 = 1.8x

state 对输出的影响比图像大 1.8 倍,而且跟杯位不相关(r=0.09,是噪声)。这就是 causal confusion 的标准长相:模型在复读示范的平均轨迹,起始姿态抖一点复读轨迹就跟着抖,而杯子在哪它基本没看。

曾经怀疑、已排除:起始位姿泄漏杯位。怀疑过录制时人先把胳膊朝杯子摆了一点。实测不成立:开录第 0 帧(和第 10 帧)的右臂 6 维与杯位 pan 相关系数全部 |r| ≤ 0.34。

二、为什么用 state dropout,不用 input_features 那个技巧

state6 的做法state dropout
手段忘记传 --policy.input_features=null,靠截断副作用断捷径在策略 prepare_state 里按概率把 state token 置零
看得出来吗看不出来 —— config 里只留下 state:[6],分不清是故意还是漏了载入时打印启用状态和分段进度
推理端一致吗不一致 —— 推理照喂 17 维,没人拦只在 self.training 下生效,推理原样
实测效果没生效(模型仍在读右臂 state)待验

机制

示范轨迹光滑,「这一帧的关节角」几乎能直接推出「下一帧该转到哪」。于是模型走捷径:读上一帧关节角往同方向再挪一点,比看图找杯子算该往哪转省事得多。梯度下降当然选前者 —— 结果是训练 loss 很低、真机上抓不到东西。数据越少越严重(我们只有 40 集)。

做法:训练时以概率 p 把 state 那一路输入整条置零(归一化之后置零 = 喂训练均值 = 中性值),逐样本,同一批里有的看得到有的看不到:

阶段区间p作用
A 纯视觉0 ~ 30%1.0完全读不到关节,强迫建立视觉通路
B 退火30% ~ 70%1.0 → 0.2慢慢放开,学会把关节信息用起来
C 永久下限70% ~ 100%0.2捷径永远走不通
为什么要留永久下限(关键)。如果第二阶段完全放开,state → action 还是损失下降最快的方向,捷径会很快被重新学回来,第一阶段建立的视觉依赖被冲掉,最后还是一个不看图的模型。留 20% 之后任何时候都有五分之一的样本没有 state,模型不可能只靠 state 把 loss 压下去。这就是「混合 + 退火」,不是「两段式冻结」。
前提要盯着。不给 state 却要预测绝对关节角,理论上欠定;这里能成立是因为两路腕部相机隐含编码了臂的姿态。如果阶段 A 的 loss 明显不降,说明这个假设不成立,那一阶段要改成预测 delta action。用 SD_DEBUG=1 看分段 loss 再决定,别盲目加长阶段 A。

三、六步操作手册

这次要传 --policy.input_features=null —— 和 train-right-b16.html 那一页正好相反。那次靠它缺席来断捷径(已证明没生效);这次断捷径由 state dropout 显式负责,state 必须是完整的 17 维,否则两个机制会互相干扰,跑出来没法归因。脚本里已经写好了,别手改。
1
本机:确认数据集在 Hub 上(Jetson 上跑)
hf auth login          # 粘贴 write token
cat ~/.cache/huggingface/lerobot/xlerobot-team/xlerobot-right-pick-cup-20260826-0042/meta/upload.json
已经在 Hub 上就跳过。没有就传:hf upload-large-folder --repo-type=dataset xlerobot-team/xlerobot-right-pick-cup-20260826-0042 <本地目录>。数据集不会在录制后自动补传。
2
租卡 + 装依赖
项选择为什么
平台RunPod之前几次都在这上面跑,流程趟通
模板PyTorch 2.8 / CUDA 12.8torchcodec 要求 cu128
显卡RTX 4090够用。batch 16 的显存占用从没实测过 —— 用第 4 步的 40 步基准去量
存储≥ 50 GB,挂 /workspacecheckpoint 每 2000 步存一次 + 数据集缓存
pip install -U "lerobot[dataset,training,smolvla]" huggingface_hub
hf auth login && hf auth whoami     # 确认能读数据集 + 能写模型仓库
tmux new -s train                    # ★ 断线不杀训练 ★
tmux 不是可选项:家里网一断,没 tmux 训练就死了,而 Pod 照样计费。
3
把三个文件传上去(仓库里已经写好,直接 scp / git clone)
05-training/state_dropout.py                 # 机制 + 退火 + 自检
05-training/train_state_dropout.py           # 装补丁再交棒给 lerobot-train
05-training/train_smolvla_state_dropout.sh   # 配方,对齐 state6
先跑自检,不需要 GPU、不需要数据集:
python state_dropout.py → 应看到退火各分段点 ok + 逐样本置零比例 p=0.5 -> 实测 0.4991 ok + 自检通过
4
开付费之前:验解码后端 + 40 步烟测
python -c "import torch,torchcodec;print(torch.__version__, torchcodec.__version__)"
ffmpeg -decoders | grep -Ei "cuvid|nvdec"     # 要看到 av1_cuvid
BENCH=1 SD_DEBUG=1 ./train_smolvla_state_dropout.sh
数据集是 AV1。torchcodec 装不上会静默退回 pyav,训练照跑但慢几倍 —— 白烧租金。
烟测要看到:== state dropout【已启用】 三行阶段说明,以及每 200 次前向一行 [state-dropout] step .../... p=1.000 置零 16/16。p 不动就是补丁没挂上,停下来查。顺便 nvidia-smi 记下显存峰值。
5
正式训练
./train_smolvla_state_dropout.sh
# 想调档:  SD_P_END=0.3 SD_VISION_FRAC=0.4 ./train_smolvla_state_dropout.sh
参数默认含义
SD_VISION_FRAC0.3阶段 A(纯视觉)占总步数比例
SD_ANNEAL_FRAC0.4阶段 B(退火)占比
SD_P_END0.2永久下限
SD_ENABLE10 = 对照组,其余完全一样
配方对齐 state6:B_eff=16 / lr=1e-4 / 20000 更新 / warmup 1000 / 增强开。唯一变量就是 state dropout。
仓库名自动带档位(-p0.2- / -nodropout-),两组不会互相覆盖。4090 上一小时出头。
不用单独跑对照组:state6 的训练侧一直是干净的,它本身就是合格 baseline。
6
拉回来,先离线测斜率 —— 这是硬门禁
# Jetson 上
python 03-software/scripts/vision_sweep_phase.py \
  05-training/<新模型>/checkpoints/020000/pretrained_model \
  ~/.cache/huggingface/lerobot/xlerobot-team/xlerobot-right-pick-cup-20260826-0042 \
  "Pick up the cup with the right arm." right 12
拿 pan 斜率跟 state6 的 +0.086 比。
斜率 < 0.3 → 不要上机器人,再跑两次实机也只是再确认一遍抓不到,白费两小时和一次杯子摆位。
斜率 ≥ 0.3 → 上机,跑 2 次,重点看夹爪开合次数能不能降到个位数以内(示范是 1 次)。

四、顺带加的开关

--exec-horizon(run_policy_trials.py:1015):覆盖 n_action_steps,控制执行几步就重新看画面。这个 checkpoint 是 50 步 @30 Hz = 1.67 秒开环,今天两次实机的重规划间隔都是这个数。

别把它当主线。网上说的「执行 1–5 步优于 20 步」,前提是模型本身有足够视觉增益、只是被开环时长浪费了。我们这个增益压根不在(斜率 0.04–0.09)—— 每 0.3 秒重看一次,它每次也只修正需要量的 4%。等斜率上去了这个开关才有意义。
注意它和面板上的「动作块」不是一回事:那个是 chunk_k,管一个控制周期消费几步(让计划按真实时间播放),不改变重规划频率。

结果文件记 exec_horizon + exec_horizon_default,不同视野单独分组统计(trials_io.py:330)—— 那是两个不同的闭环系统,成功率不能混在一起算。老记录不会被拆组。

数据来源:05-training/STATE-CHANNEL-BUG-2026-09-08.md · 实机日志 trials-20260908-063932.json / trials-20260908-07*.json · 探针 vision_sweep_phase.py / state_vs_vision.py / state_regime.py。
所有数字都是这台 Jetson 上跑出来的,没有引用外部结论。