← reports index
合并数据重训 · 2026-09-10

右臂重训手册:0826 + sep8 补录

sep8 补录已经和 0826 合并好、传上 Hub。这一页回答三件事:新数据集能不能直接拿去训、配方要不要改(不要)、以及这批补录按项目自己的录制验收标准补上了哪几项(第 1 项部分、第 2 项完全没有)。

2026-09-10 更正:这一页最初把 sep8 补录写成 23 集,不对。 Sep 8 当晚实际录了 6 个会话 / 33 集(1938 · 2008 · 2016 · 2039 · 2059 · 2117)。 当时只有 2039/2059/2117 三个做过去深度处理,所以只有这 23 集进了合并集 —— 这是「谁被处理过」,不是「谁合格」,两者被我当成一回事了。

另外两条也一并更正:2008(3 集)和 2016(1 集)复核后达标; 1938(6 集)我一度判为不合格,该判断已撤回 —— 依据是「前伸行程」这个相对量, 它把「起手就已经伸在外面」和「根本没伸手」混成同一个数。按绝对坐标复测,1938 够到 16–21 cm、 闭爪位置 12.5–18.9 cm,都在正常区间。

范围已定:合全部 6 个会话,73 集(操作者 2026-09-10 决定)。1938/2008/2016 的 head_depth 由本次摘除, 做法与 2039/2059/2117 那三个相同、并用它们反查确认过。新数据集 xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 取代原来 63 集那版。 下面第三节的三张图是只对已处理的 23 集测的,保留作为当时的记录;第四节之后按 73 集更新。
结论先行。 能不能用 —— 能。xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910,73 集 / 32,216 帧(0826 40 集 + Sep 8 全部 6 个会话 33 集),已在 Hub,结构、视角、任务文本全核过。
用在哪 —— 右臂 pick-cup 重训。配方沿用 右臂 state dropout 那一套,一个参数都不用改,唯一变量是数据。
cost —— 4090 一小时出头(20,000 更新,与前两次同档)。
注意点 —— 按录制指南的四项验收:横向覆盖改善但未达标(仍有四档不足 12 集),节奏散度完全没达标(7.8%,指南要求 ≥15%,<10% 视为「仍是单一节奏」)。节奏这一项加多少集都不会变 —— 它要的是录制时故意快慢混合、中途停 1–2 秒,现有数据里没有。别指望这批补录单独把 0/14 翻过来。
现在就跑(在租的 GPU 上,tmux 里)
cd ~/Robotic_challenge/05-training

# ① 开付费之前先烟测 40 步
BENCH=1 SD_DEBUG=1 ./train_smolvla_state_dropout.sh

# ② 正式训练
./train_smolvla_state_dropout.sh
不用传 REPO_ID / TAG —— 2026-09-11 起脚本默认就是这个合并集。DRY=1 实跑确认: dataset = xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910(73 集 / 32,216 帧), model repo = Suyang99/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000(不会覆盖前两次), 配方 B_eff 16 / lr 1e-4 / 20000 更新 / warmup 1000 / AUG on / eval_split 0.0 —— 与前两次逐项相同,唯一变量是数据。

烟测必须看到:== state dropout【已启用】 三行阶段说明,以及每 200 次前向一行 [state-dropout] step .../... p=1.000 置零 16/16。p 不动就是补丁没挂上,停下来查,别开正式训练。
装完依赖立刻验版本:python -c "import torch,torchcodec;print(torch.__version__,torchcodec.__version__)" 要看到 2.8.0+cu128 / 0.7.0。默认会装出 torchcodec 0.11.1,和 torch 2.8 不兼容, lerobot 会静默退回 pyav、慢好几倍还不报错。
tmux 不是可选项:网一断没 tmux 训练就死了,而 Pod 照样计费。
4090 上一小时出头,存档在 2000 / 4000 / … / 20000。拉回来时一定带上 training_state/,否则续不了。

一、三份配方的对照:其实只有数据在变

把左臂那次和右臂 0826 那次的 train_config.json 逐字段拉平比对,150 个字段里只有 5 个不同,其中 4 个是名字和路径:

字段左臂(left-sd)右臂 0826(right-sd)
dataset.repo_idxlerobot-left-pick-cup-sep3-clean-20260905
54 集 / 13,693 帧
xlerobot-right-pick-cup-20260826-0042
40 集 / 16,487 帧
policy.repo_idSuyang99/xlerobot-smolvla-left-sd-p0.2-b16-u20000Suyang99/xlerobot-smolvla-right-sd-p0.2-b16-u20000
job_name / output_dir只是把 left-sd / right-sd 拼进路径
eval.batch_size4450
eval.batch_size 那一行不是配方差异。eval_split=0.0,两次都没有验证集,这个字段是 lerobot 按数据集大小自动填的残留值,训练里从没被用到。所以实质差异只有一个:数据集。

这和左臂那页说的「唯一变量不成立」不冲突。左臂重训手册那条更正针对的是开跑之前的计划 —— 当时左臂上一轮的图像增强是关着的、右臂是开着的,照着「只换数据」去理解会漏掉这个变量,所以那页要求显式写 AUG=1。这里比的是两次都已经跑完之后的 train_config.json:增强字段逐项相同(enable=true、max_num_transforms=5、6 个 transform 的 weight 和 kwargs 全等),说明那条要求当时确实被执行了。事后实测口径比事前计划口径更强,两者说的是同一件事的两个阶段。

共享的那份配方(两次完全相同,本次继续不动):

组参数
优化B_eff=16(micro 16 / accum 1)· lr=1e-4 · 20000 更新 · warmup=1000 · seed=1000 · AdamW betas=[0.9,0.95] · wd=1e-10 · grad_clip=10.0
策略smolvla · chunk_size=50 · n_action_steps=50 · freeze_vision_encoder=true · train_expert_only=true · train_state_proj=true
输入input_features 完整 17 维 state + 三路相机(head / left_arm_wrist / right_arm_wrist)· 不传 rename_map
增强开,定义了 6 种、每次最多取 5 种:brightness / contrast / saturation / hue / sharpness / affine(±5°,平移 5%)
state dropout阶段 A 纯视觉 0–30% p=1.0 → 阶段 B 退火 30–70% 1.0→0.2 → 阶段 C 下限 p=0.2
存档save_freq=2000(10 个 checkpoint)

二、前两次跑出来的结果

按配置分组,不合并统计。两次试跑的执行参数不同(chunk 档不一样),合成一个总成功率会把不可比的东西混在一起。下表每一行都是一个配置。无效(void)不计入分母,但总次数照实列出。
模型ckpt执行配置总次数成功 / 有效无效
right-sd(0826,40 集)020000speed 1.0 · chunk auto/4140 / 140
right-sd(0826,40 集)012000speed 1.0 · chunk auto/420 / 20
left-sd(sep3-clean,54 集)020000speed 1.0 · chunk auto/422 / 20
left-sd(sep3-clean,54 集)020000speed 1.0 · chunk 1/12615 / 206

左臂合计 28 次试验(2 + 26),来自 7 个结果文件 trials-20260909-171157 … -185714。右臂 0/14 的那一批见 right-arm-trials-2026-09-08。

同一套配方,左臂能到 15/20,右臂 0/14。差别在数据,这也正是这次只换数据、别的一律不动的理由。

三、这批补录改变了什么(量出来的,不是推的)

09-08 的诊断给右臂列了两个数据缺陷:①杯位覆盖失衡(最疏的那一档几乎没录过,而它正是操作者标的 hard zone),②节奏太规律(模型数拍子就能压低 loss,不用看杯子)。逐集量一遍,看这批补录各治到什么程度。

① 横向覆盖:补上了

0826(40 集) sep8 已合并的 23 集
每档杯位录了几集(闭爪时刻的 shoulder_pan,10° 一档) 0 7 14 21 杯位 -40°~-30°:0826 6 集 + sep8 6 集 = 12 集 12 杯位 -30°~-20°:0826 20 集 + sep8 1 集 = 21 集(数据最密的一档) 21 杯位 -20°~-10°:0826 8 集 + sep8 0 集 = 8 集 8 杯位 -10°~0°:0826 4 集 + sep8 8 集 = 12 集(补录主力) 12 杯位 0°~+10°:0826 1 集 + sep8 8 集 = 9 集(原来最疏的一档) 9 杯位 +10°~+20°:0826 1 集 + sep8 0 集 = 1 集(仍然是空白) 1 -40~-30-30~-20-20~-10 -10~00~+10+10~+20 闭爪时的 shoulder_pan(度)
补录把 0826 最疏的两档 [-10,0) 和 [0,+10) 从 4 集和 1 集抬到 12 集和 9 集 —— 正是诊断点名的那一带。五个主档(-40~+10)的集数变异系数 CV 0.84 → 0.37。[+10,+20) 仍然只有 1 集,是这次没补到的角落。

② 闭爪时机:本来就没问题,补录也没弄坏

09-08 的报告说「示范里爪子在伸出行程的 99.8% 处闭合,实机只到 52%」。那是模型缺陷,不是数据缺陷 —— 重新量一遍,两批数据的示范都在伸出行程的 97% 附近闭爪:

数据集数闭爪 @ 伸出行程 中位四分位区间异常集
08264096.7%92.3% – 98.4%3 集
sep8 已合并的 23 集2397.9%93.9% – 98.7%0 集
合并后6397.3%92.8% – 98.7%3 集
0826 里那 3 集是真的怪。它们的「闭爪@行程」算出来是 −3.11、−0.15、+0.28 —— 也就是爪子在手臂几乎没往前伸的时候就合上了。sep8 那 23 集最小值是 0.778,一集都没有这种。这三集把 0826 的均值从中位 96.7% 拉到 81.2%、标准差拉到 66.9%,所以这一列只能看中位数和四分位,别看均值。

③ 节奏散度:只补了一半

0826 sep8 补录 合并后
闭爪发生在整集时长的第几成(越散越好) 50%57.5% 65%72.5% 80% 闭爪时刻 ÷ 整集时长 0826:p10 54.4% · 四分位 57.5%–62.8% · 中位 60.5% · p90 67.2%(IQR 宽 5.3 个百分点) 中位 60.5% sep8 补录:p10 58.5% · 四分位 61.8%–70.8% · 中位 67.1% · p90 75.0%(IQR 宽 9.0 个百分点) 67.1% 合并后:p10 55.2% · 四分位 58.6%–67.0% · 中位 62.3% · p90 71.1%(IQR 宽 8.4 个百分点) 62.3% 0826sep8合并后
粗竖线是中位数,方块是四分位区间,细横线是 p10–p90。合并后四分位区间从 0826 的 5.3 个百分点宽到 8.4 个百分点(约 1.6 倍)。方向对,但这个散度主要来自 sep8 整体偏晚(中位 67.1% vs 60.5%)而不是它内部真的乱,属于部分改善。
这一项别当成已解决。诊断建议的是「打散节奏」,让模型没法靠数拍子压 loss。现在 73 集的闭爪时刻仍然集中在整集的 58%–67% 之间(散度 7.8%,指南要求 ≥15%)。如果这一轮训完横向跟得上了、闭爪时机还是不对,那就是节奏这条没治够,下一批补录要专门在节奏上做文章(同一杯位、故意快慢不一)。

④ 按录制指南的四项 DONE WHEN 打分

上面三项是我自己挑的角度。项目里其实有写好的验收标准 —— recording-guide-2026-09-08 的四项,每项都带一个可判定的 DONE WHEN。按它重算,三种合并范围的得分:

杯位档(闭爪 pan,9° 一档)A 当前 63 集B +2008/2016
67 集
C 再 +1938
73 集 ★已采用
-40 ~ -31°111111
-31 ~ -22°151515
-22 ~ -13°141414
-13 ~ -4°111111
-4 ~ +5°101010
+5 ~ +14°(最疏)2610
指南第几项DONE WHENA 63 集B 67 集C 73 集 ★已采用
① 补齐稀疏杯位每档 ≥ 12 集未达标
四档不足
未达标
四档不足
未达标
四档不足
② 故意打散节奏闭爪@集长的散度 ≥ 15%
<10% = 仍是单一节奏
7.8%7.8%7.8%
③ 先偏了再修正存在 5–8 集待逐集判定 —— 1938 的 ep1(3.6→18.4→10.7→13.6 cm)就是这个形状
④ 杯位写进每集元数据每集带位置字段未做 —— 现在只能从手臂姿态反推,是循环论证
第 ② 项是加多少集都动不了的。三种范围的节奏散度都是 7.8%,而指南把 <10% 定义为「仍是单一节奏」。原因很直接:它要的是录制时故意快慢混合、中途停 1–2 秒,这在已有的任何一批里都没做过。所以下一批补录的重点不是再换杯位,是换节奏。
1938 那 6 集全部落在最疏的一档(闭爪 pan 11.9–16.1°),正是指南第 ① 项点名要补的位置:那一档从 2 集变 10 集。它的问题是另一件事 —— 4/6 集集间没归位、起手时手臂已经伸在 16–21 cm 处。这是已知的录制伪影,sep2-trim 那批用「切掉每集开头的归位跳变」处理过,不是丢弃的理由。

四、合并集本身的核对

合并不是把目录拼起来就完事,四项都实际跑过:

核对项怎么查的结果
结构可加载verify_dataset.py + LeRobotDataset 真加载,跨源边界抽 8 帧73 集 / 32,216 帧,三路视频形状正确
跨 7 个源边界抽 10 帧
schema 一致四个源的 info.json 逐字段比action/state 都 17 维,fps 30,robot_type 相同
任务文本读 meta/tasks.parquet四个源都是单一 Pick up the cup with the right arm.
头部视角中值背景相位相关同一视角,有约 18px 固定竖直偏移

73 集的构成,以及摘深度到底改了什么

源集帧head_depth闭爪 pan 落在
right-pick-cup-20260826-00424016,487本来就没有-36.7 ~ +10.2°
sep8-1938-nodepth62,579本次摘除+11.9 ~ +16.1°(最疏那一档)
sep8-2008-nodepth31,455本次摘除+11.4 ~ +12.6°
sep8-2016-nodepth1557本次摘除+5.0°
sep8-2039-nodepth83,68409-09 已摘+0.0 ~ +2.8°
sep8-2059-nodepth73,48009-09 已摘-7.0 ~ -4.0°
sep8-2117-nodepth83,97409-09 已摘-34.7 ~ -20.9°
合计7332,216

摘深度改的是这四处(仓库里原先没有脚本,做法是拿 09-09 已处理好的 2039 和它的源逐项 diff 反查出来的):

1. 删目录  videos/observation.images.head_depth/
2. info.json   features 去掉 observation.images.head_depth
3. stats.json  去掉同名 key
4. meta/episodes/*.parquet  删 14 列
      stats/observation.images.head_depth/{count,max,mean,min,q01,q10,q50,q90,q99,std}
      videos/observation.images.head_depth/{chunk_index,file_index,from_timestamp,to_timestamp}

data/*.parquet 一个字节都不动 —— 深度只存在视频流里,动作和状态不受影响。
1938 留了一个已知问题没处理。它 6 集里有 4 集集间没归位,起手时手臂已经伸在 16–21 cm 处(正常起手是 3.6 cm)。这不影响抓取本身 —— 闭爪绝对位置 12.5–18.9 cm 都在正常区间 —— 但它给模型的起始状态分布里混进了真机上不会出现的姿态(真机每次从 home 起步)。sep2-trim 那批的处理方式是切掉每集开头的归位跳变,这一步没做,是独立的一次操作。

视角那一项值得展开

选数据集必须先核视角 —— sep2 那七个数据集就是栽在这里(整批作废)。这次用中值背景做相位相关,量出来:

比较平移 dx / dy相关峰值读法
sep8 三个之间约 1 px0.77同一视角
sep8 vs 0826+4 px / +18 px0.13同一视角,有固定偏移
sep2 vs 0826(已知作废的反例)−45 px / −74 px0.06整机换了视角

18 px 是画面高的 7.5%,比 sep2 那种小一个量级,而且训练本来就开着 affine 增强(平移 ±5%)。判定:可以合。但这是没跑测过的推断 —— 18 px 偏移对成功率的影响没有做过对照实验,只是量级上远小于已知会出事的那一档。

四·七、恒定维保护漏掉了头部:判据从 std==0 改成 min==max(2026-09-11)

结论先行:头部两维在数据里是精确常数,但保护一直没钉住它们,放大约 107 倍。 2026-09-10 头掉到 customer 视角那次,head_motor_2 喂进策略的归一化值是 z = −240 —— 早就饱和了。已修,判据改成 min == max,钉住的维从 6 个变成 8 个。

为什么需要钉恒定维

lerobot 的归一化是 (x − mean) / (std + 1e-8)。某一维在训练集里如果是常数,它的 std 就趋近 0,于是真机上任何一点偏离都会被除以一个极小的数,放大成巨大的值,state token 直接饱和 —— 模型退化成输出一个跟本体姿态无关的固定姿势。所以要在喂给策略之前,把这些维钉回训练时的那个常数(安全层仍然看真实的 17 维,只有喂给策略的那一份被钉)。

旧判据 std == 0 错在哪

它只在常数恰好等于 0 时才成立。原因是 lerobot 在 float32 上用 E[x²] − E[x]² 算方差:

三条互相独立的证据说明就是这个机制:

① 假 std 随常数的绝对值线性增长 —— 相对误差的标志:
|c₂|/|c₁| = 49.846 / 3.692 = 13.50  s₂/s₁ = 0.1284 / 0.0104 = 12.34 (吻合)
两维的相对量都是 s/|c| ≈ 2.6~2.8 × 10⁻³。

② 常数恰好为 0 时不会发生 —— E[x²] = 0,没有大数相减,std 精确为 0。左臂那六维的常数正好是 0.0,所以一直被正确钉住;头部的常数是 −3.69 / 49.85,就漏了。这是旧判据漏掉头部的全部原因。

③ 直接在 float32 上复现(441 帧,等于一集的典型长度):
常数   0.000000  ->  E[x²]=0.000000     E[x]²=0.000000     var=+0.0e+00   std=0.000000
常数   3.692308  ->  E[x²]=13.633134    E[x]²=13.633135    var=-9.5e-07   std=0.000977
和逐集实测的假 std(0.0034~0.0048)同一量级。

新判据 min == max

min 和 max 是比较出来的,不是算出来的 —— 不累加、不相减,因此不受量级影响、也不累积误差。逐集实测:head_motor_1 的 min == max == −3.69230771 在 73/73 集上精确成立,而同一批数据的 mean 却在 −3.69231582~−3.69230533 之间飘、std 算出 0.0034~0.0048。

-  idx = np.flatnonzero(std == 0.0)      # 旧:只有常数恰好为 0 时才成立
+  idx = np.flatnonzero(smin == smax)    # 新:精确,与量级无关

checkpoint 的归一化器里本来就存了 observation.state.min 和 .max(连同 q01/q10/q50/q90/q99),所以推理时不需要去读数据集,改动是自足的。读不到 min/max 时退回旧判据并打印警告。

维minmaxstd旧判据新判据
left_arm ×60.0000000.0000000.000000钉钉
head_motor_1−3.692308−3.6923080.009319漏钉
head_motor_249.84615349.8461530.124309漏钉
right_arm ×6min ≠ max(真的在变)2.15~32.2——
x/y/theta.velmin ≠ max(真抖动)0.0007~0.23——

验证

检查结果
钉住的维数6 → 8(新增 head_motor_1/2)
训练数据受影响吗合并集 32,216 帧过 pin_state,改动 0 帧(这些维在数据里本来就是常数)
头掉到别处喂 20.0 → 策略看到 49.8459(钉回训练值)
正常手臂维喂 −36.0 → 原样 −36.0
同步 / RTC 两条路输出逐位一致(pin_raw 文档本来的不变量)
它不覆盖底盘三维,那是另一回事。x/y/theta.vel 的 min ≠ max —— 抖动是真的(stats 和数据实算逐位吻合),不是浮点假象。它们的 std 是 0.0007~0.23,放大 1400×/1533×,而且合并让它更严重(sep8 六个会话的底盘精确静止,拉低了整体 std,0826 单独是 1001×)。

但它不阻塞这次训练:底盘在抓取任务里不被驱动,策略在这三维上的输出实测恒为 0.000(7 条 trace,raw 和 safe 都是),训练集 action 侧也只有一个唯一值 0。风险只在底盘真被开起来时才兑现。

该做的清理(可以放到下一版数据集):把训练数据里这三维置零。它们在训练时被喂进模型的是 z ∈ ±6~8 的纯噪声,而 action 侧恒为 0 —— 模型要花容量学会忽略它。置零后 min == max == 0,新旧判据都会自动钉住,推理侧一并解决,不需要任何新机制。
一度加过的「±10σ 钳位」已撤销。操作者判断「除了安全层又多一道会静默改数的闸,完全没必要 —— 训练和输出时不动那几路就行」,这是对的:真正的 bug 是判据错了,不是缺一道闸。修好判据之后,头部由既有机制覆盖,底盘由数据清理覆盖,都不需要新增运行时变换。

五、完整流程:从 Jetson 到 Pod 再回到机器人,逐条命令

这一节 2026-09-11 重写成自足版:每一步都给出完整命令、预期输出和「看到什么要停」,不再要求读者去翻 09-08 右臂手册或 b16 页。所有命令都对应仓库里现存的脚本,路径和参数逐个核过(脚本文件头、add_argument、head_presets.json、上一轮的 shell 历史)。

先说一个会让人踩空的事实:脚本的「新默认数据集」还没推到 GitHub。 本机 05-training/train_smolvla_state_dropout.sh 在 2026-09-11 把默认 REPO_ID 改成了合并集,但这个改动还在工作区、没提交;origin/china-console 上那份默认仍是 0826 单独 40 集(right-sd),origin/main 上根本没有这个文件。 所以下面所有训练命令都显式写出 REPO_ID 和 TAG —— 不管 Pod 上拿到的是哪个版本,结果都一样。分支版和本机版的其余内容已经一致:--accelerator 那行只剩注释,SD_DEBUG_EVERY 两边都支持(09-08 左臂页记的那两个坑已经不存在)。

第 0 步 · 开跑前(Jetson 上)

0
确认这台机器上只有一个人在用模型,磁盘还有余量。
ps -C claude ; pgrep -af run_policy_trials      # 各只应有一行(你自己)
df -h /                                         # 根分区 116 GB,满盘会让整个会话失效
2026-09-11 上午两个会话同时加载 SmolVLA,可用内存剩 260 MB、试跑 134 秒只发出 1 帧。多于一个进程先问清谁在用,不要并行起。

第 1 步 · 数据集在 Hub 上(Jetson 上,已完成,留档)

1
cat ~/.cache/huggingface/lerobot/xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910/meta/upload.json
# 或者直接问 Hub:
~/miniconda3/envs/lerobot/bin/python -c "from huggingface_hub import HfApi;print(HfApi().dataset_info('xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910').lastModified)"
应看到 ok: true(2026-09-09T22:44:51Z),Hub 上 11 个文件 / 412.6 MB,main 加 v3.0 tag。没有就传:hf upload-large-folder --repo-type=dataset xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 ~/.cache/huggingface/lerobot/xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910。数据集不会在录制后自动补传,右臂 09-08 那次踩过。

第 2 步 · 租卡、装依赖、验版本、开 tmux(Pod 上)

2
项选择为什么
平台RunPod前三次(b16 / right-sd / left-sd)都在这上面跑通
模板PyTorch 2.8 / CUDA 12.8torchcodec 0.7.0 要 cu128
显卡RTX 4090同配方 20000 更新一小时出头(右臂 right-sd 实跑)
存储≥ 50 GB 挂 /workspace10 个存档 × 约 600 MB + 数据集缓存 413 MB + 日志
pip install -U "lerobot[dataset,training,smolvla]" huggingface_hub
pip uninstall -y torchcodec && pip install "torchcodec==0.7.0"   # 默认装出 0.11.1,和 torch 2.8 不兼容
python -c "import torch,torchcodec;print(torch.__version__, torchcodec.__version__)"
ffmpeg -decoders | grep -Ei "cuvid|nvdec"
hf auth login && hf auth whoami          # 粘贴 write token;要能读 xlerobot-team 数据集、能写 Suyang99 模型仓库
tmux new -s train-right                    # ★ 不是可选项 ★
要看到 2.8.0+cu128 / 0.7.0 和 av1_cuvid。torchcodec 装错时 import 报 undefined symbol: torch_dtype_float4_e2m1fn_x2,而 lerobot 会静默退回 pyav:训练照跑、慢几倍、不报错 —— 白烧租金。tmux:家里网一断,没 tmux 训练进程就被 SIGHUP 杀掉,而 Pod 照样计费(4090 约 $1.6/h)。挂起 Ctrl+B D,重连 tmux attach -t train-right。

第 3 步 · 三个文件上 Pod,先跑不需要 GPU 的自检

3
两条路任选一条。A · 从 GitHub 拉(Pod 上跑):
cd /workspace
git clone -b china-console https://<用户名>:<PAT>@github.com/ginagina19992023/Robotic_challenge.git
cd Robotic_challenge/05-training
B · 从 Jetson 直接推(Jetson 上跑,指向 Pod 的 SSH 端口;拿到的就是本机最新版):
scp -P <pod-ssh-port> \
  ~/Robotic_challenge/05-training/state_dropout.py \
  ~/Robotic_challenge/05-training/train_state_dropout.py \
  ~/Robotic_challenge/05-training/train_smolvla_state_dropout.sh \
  root@<pod-ip>:/workspace/
然后自检:
cd /workspace/Robotic_challenge/05-training   # 或 cd /workspace(B 路线)
chmod +x train_smolvla_state_dropout.sh
python state_dropout.py
应看到一张 step / frac / p / 期望 表,每行 ok,然后 逐样本置零比例 p=0.5 -> 实测 0.49xx ok,最后一行 自检通过。任何一行 FAIL 就停。-b china-console 不能省,漏了会拉 main,上面没有这三个文件。PAT 用 classic token(ghp_ 开头、勾 repo),别把尖括号一起抄进去。

第 4 步 · 开付费之前:DRY 看解析结果 + 40 步烟测

4
export REPO_ID=xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910
export TAG=right-0826sep8

DRY=1 ./train_smolvla_state_dropout.sh
BENCH=1 SD_DEBUG=1 SD_DEBUG_EVERY=5 ./train_smolvla_state_dropout.sh
DRY 要逐行核对:dataset : xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 · model repo : Suyang99/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000(名字里带 0826sep8 和 p0.2,不会覆盖 right-sd / left-sd 两个旧仓库)· B_eff / micro / accum : 16 / 16 / 1 · lr / updates / warmup : 1e-4 / 20000 / 1000 · augmentation : on · eval_split : 0.0 · --steps / --save_freq : 20000 / 2000 · rename_map : (不传)。任何一项不同就是配方变了,先停。
烟测要看到三行 == state dropout【已启用】total_steps=40 阶段说明,随后每 5 次前向一行 [state-dropout] step k/40 p=1.000 置零 16/16,到 30% 以后 p 开始下降。p 一直 1.000 不动、或者根本没有 [state-dropout] 行 = 补丁没挂上,停下来查,别开正式训练。烟测不存档(--save_checkpoint=false),另开一个 tmux 窗口 nvidia-smi 记显存峰值。SD_DEBUG_EVERY=5 必须给:默认每 200 次打一行,40 步烟测一行都看不到,很容易误判成没生效。

第 5 步 · 正式训练

5
REPO_ID=xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 \
TAG=right-0826sep8 \
./train_smolvla_state_dropout.sh
脚本展开后真正交给 train_state_dropout.py(再交棒给 lerobot-train)的参数是下面这一整串,列在这里是为了让人能核对,不是让人手敲:
python train_state_dropout.py \
  --dataset.repo_id=xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 \
  --dataset.video_backend=torchcodec \
  --dataset.eval_split=0.0 \
  --dataset.image_transforms.enable=true --dataset.image_transforms.max_num_transforms=5 \
  --policy.path=lerobot/smolvla_base \
  --policy.repo_id=Suyang99/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000 \
  --policy.push_to_hub=false \
  --policy.device=cuda \
  --policy.input_features=null --policy.output_features=null \
  --policy.optimizer_lr=1e-4 \
  --policy.scheduler_decay_steps=20000 --policy.scheduler_warmup_steps=1000 \
  --batch_size=16 --num_workers=8 --prefetch_factor=2 \
  --steps=20000 --save_freq=2000 --log_freq=100 --seed=1000 \
  --wandb.enable=false \
  --output_dir=/workspace/outputs/train/smolvla_right-0826sep8_b16_u20000 \
  --job_name=smolvla_right-0826sep8_b16_u20000
# 环境变量:SD_ENABLE=1 SD_VISION_FRAC=0.3 SD_ANNEAL_FRAC=0.4 SD_P_END=0.2
# 日志 tee 到 /workspace/smolvla_right-0826sep8_b16_u20000.log
可调项默认含义 / 什么时候动
SD_VISION_FRAC0.3阶段 A 纯视觉(p=1.0)占总步数比例
SD_ANNEAL_FRAC0.4阶段 B 退火 1.0→下限 的占比
SD_P_END0.2阶段 C 永久下限;改了仓库名跟着变(-p0.3-)
SD_ENABLE10 = 对照组,仓库名变 -nodropout-;这轮不需要
REPO_ID / TAG见上回旧数据集对照:REPO_ID=xlerobot-team/xlerobot-right-pick-cup-20260826-0042 TAG=right-sd
这一轮一个档位都不要动,唯一变量是数据。跑起来后 Ctrl+B D 挂起;盯进度用 tail -f /workspace/smolvla_right-0826sep8_b16_u20000.log 和 nvidia-smi(有负载 = 还在训)。存档在 checkpoints/002000 … 020000,每档含 pretrained_model/ 和 training_state/。进程死了不用从头来:tmux attach 后在同一命令末尾加 --resume=true,从 checkpoints/last 续。4090 上一小时出头。

第 6 步 · 推回 Hub:VERIFY → UPLOAD → VERIFY → STOP

6
JOB=smolvla_right-0826sep8_b16_u20000
REPO=Suyang99/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000

# VERIFY:看到 020000 且 last -> 020000 才算完成,目录名不算数
ls -lah /workspace/outputs/train/$JOB/checkpoints
ls /workspace/outputs/train/$JOB/checkpoints/020000        # 要有 pretrained_model 和 training_state
cat /workspace/outputs/train/$JOB/checkpoints/020000/training_state/training_step.json

# UPLOAD:整个 020000 连 training_state/ 一起传,外加日志(面板 loss 曲线要用)
hf repo create $REPO --repo-type model --private --exist-ok
hf upload $REPO /workspace/outputs/train/$JOB/checkpoints/020000 checkpoints/020000 --repo-type model
hf upload $REPO /workspace/$JOB.log $JOB.log --repo-type model
# 多留一两档(012000 / 016000),实机表现不单调,上一轮 012000 也上过机器
hf upload $REPO /workspace/outputs/train/$JOB/checkpoints/012000 checkpoints/012000 --repo-type model

# VERIFY:在 Hub 上数文件
python -c "from huggingface_hub import HfApi;print(len(HfApi().list_repo_files('$REPO')))"
Hub 上文件数对得上本地之后才去 RunPod 控制台 Stop。Stop ≠ Terminate:Stop 停计算计费但保留 /workspace;Terminate 删 Pod。全部传完再 Terminate。忘关会一直计费。模型在个人账号 Suyang99 下,数据集在组织 xlerobot-team 下,两者分开。

第 7 步 · 拉回 Jetson

7
df -h /                        # 一档存档约 600 MB(pretrained 约 200 + training_state 约 400)
cd ~/Robotic_challenge
hf download Suyang99/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000 \
  --include "checkpoints/020000/*" \
  --local-dir 05-training/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000
ls 05-training/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000/checkpoints/020000
目录布局和现有的 05-training/xlerobot-smolvla-right-sd-p0.2-b16-u20000/checkpoints/020000/ 一致,里面要同时有 pretrained_model/ 和 training_state/。上一轮 012000 就是用同样的 --include 写法拉的。只拉 pretrained_model 能推理但续不了训。Jetson 上 hf 找不到就 pip install -U huggingface_hub。

第 8 步 · 上机之前的三项离线检查

8
cd ~/Robotic_challenge/03-software/scripts
CK=~/Robotic_challenge/05-training/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000/checkpoints/020000/pretrained_model
DS=~/.cache/huggingface/lerobot/xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910

# ① 模型能不能加载、跑在 GPU 上、恒定维有没有被钉住 —— 不连机器人
~/miniconda3/envs/train310/bin/python run_policy_trials.py --dry --model $CK

# ② 头部预设和数据集录制时的头部姿态是不是同一个 —— 不连机器人
~/miniconda3/envs/lerobot/bin/python ../brain/check_head_pose.py

# ③ 横向增益参考值(不是门禁):从够取过程 70% 处起手,整条计划的末端 pan 对杯位回归
~/miniconda3/envs/train310/bin/python chunk_sweep.py $CK $DS "Pick up the cup with the right arm." right 12 50 0.70
① 日志必须有 == device: cuda (Orin),看到 device: cpu -- no usable GPU found 就停(那是用错了 python)。同时要看到 state guard 钉住 8 个恒定维(左臂 6 + 头部 2,见第四·七节)。② 只是核对 cup-grasp-v0(pan 2003 / tilt 2619)和数据集头部 state 的差异量,操作者定的视角为准。③ 对照基准(同一测法、同一 phase 0.70):右臂最好的旧模型 +1.02,左臂现役 +0.949。不要用 vision_sweep*.py —— 它只取动作块第一步、只在起手位测,任何模型都接近 0。这个数不做门禁(见第六节),只用来和下一轮比。

第 9 步 · 上机跑测

9
# a. 再确认没有第二份模型在跑
pgrep -af run_policy_trials

# b. 对头到标准视角,然后【看图】:浏览器开 http://<jetson>:8770/cams
python3 head_aim.py --preset cup-grasp-v0        # 自动切到 lerobot 环境;等价于面板「预设 → cup-grasp-v0」
python3 head_aim.py --read                       # 应回 pan≈2003 / tilt≈2617(2619 被限位钳到 2617)

# c. 跑
cd ~/Robotic_challenge/03-software/scripts
~/miniconda3/envs/train310/bin/python run_policy_trials.py \
  --panel --send --trials 8 --seconds 60 \
  --chunk-rate 1 --keep-torque --speed 1.0 --trace \
  --model ~/Robotic_challenge/05-training/xlerobot-smolvla-right-0826sep8-p0.2-b16-u20000/checkpoints/020000/pretrained_model

# d. 从终端起的进程,面板按钮会回 409;直接写控制文件,进程照读
printf '{"cmd":"start","ts":%d}\n' $(date +%s) > ~/Robotic_challenge/05-training/trials/.control.json.tmp \
  && mv ~/Robotic_challenge/05-training/trials/.control.json.tmp ~/Robotic_challenge/05-training/trials/.control.json
# cmd 取值:confirm | start | s(成功)| f(失败)| abort

# e. 跑完算闭爪时机(主判据)
~/miniconda3/envs/train310/bin/python trace_timing.py            # 最近 6 个 trace,在 05-training/trials/traces/
开跑前日志再看一次 == device: cuda (Orin)。--seconds 60 是每次上限,--trials 8 是最少样本量(8 次以下说明不了问题)。每一次的杯位当场手记到 05-training/trials/cup-positions.json,格式照现有条目:"runs" → "trials-YYYYMMDD-HHMMSS.json" → {"chunk":"1/1","model":"right-0826sep8-p0.2-b16-u20000@020000","trials":{"1":{"band":"P4","outcome":"failure"}, …}},档位用文件顶部 bands 里的 P1…P5。trials JSON 本身没有杯位字段,不记就复现不了「密档 3/3、疏档 0/4」那张表。k/n 按配置分组(模型、存档步、chunk、speed、视角、杯位档),任一不同就是另一组,void 不进分母。
跑测指令相对 09-08 那轮改了四处(2026-09-10 实测)。 ① 用 train310 的 python —— lerobot 环境的 torch 对不上驱动,会静默落到 CPU,每次推理 2.4 s,抓取根本完不成。 ② --chunk-rate 1 —— 实测把推理停顿从 46% 降到 22%、横向增益从 0.6 提到 0.85、k/n 从 0/14 到 3/7。 ③ --keep-torque —— 不加的话 connect 之后头部从 tilt 2613 掉回 1901,相机对着墙,整轮作废。 ④ 开跑前把头设到 cup-grasp-v0 并看图确认 —— 只比光照数字会漏,本轮就漏过一次。 ⑤ 每次把杯位记进 05-training/trials/cup-positions.json —— trials JSON 没有这个字段。

第 10 步 · 每次试验之间:判定 → 松爪 → 归位(顺序不能换)

10

这一步以前只写了「按 s / f」,2026-09-11 实跑时发现漏了两件事,这里补全。

① 判定必须先发,否则什么都做不了。脚本停在「How did this trial go?」时一直握着两条串口总线, open_gripper.py、arm_home.py 全都连不上(bus_guard 会直接拒绝)。控制命令只有六个: confirm(急停确认门)· start(开始这一次)· s 成功 / f 失败 / x 作废(摆放或操作失误,不进分母)· abort(提前终止并保存)。 没有「松爪」这一条,所以要松爪就必须先让这一轮走完或 abort。

# 面板认得这个进程时用 API(推荐:有 ok / 错误回执)
T=$(cat ~/.robot_server_password)
curl -s -X POST "http://127.0.0.1:8770/api/trials/control?token=$T" \
     -H 'Content-Type: application/json' -d '{"cmd":"s"}'       # s=成功 f=失败

# 面板回「没有在跑的试跑进程」时(终端起的进程会被判成 orphan):直接写控制文件,进程照读
cd ~/Robotic_challenge/05-training/trials
printf '{"cmd":"s","ts":%d}' $(date +%s) > .control.json.tmp && mv .control.json.tmp .control.json

② 爪里还夹着杯子时,不能直接归位。归位目标里的 gripper 取自示范起点 (configs/trial_start_pose.json:左臂 2.12、右臂 11.45,都是接近闭合的值), 所以「先归位再松爪」会带着杯子跑完全程。正确顺序是先松爪再归位。松爪脚本自己是四段动作: 后缩脱离接触 → 张开 → 抬起 → 再后缩,中途任一段不到位它会停下并拒绝继续。

cd ~/Robotic_challenge/03-software/scripts

# a. 松爪(四段;跑完力矩会卸、手臂会垂 —— 先把杯子拿走)
~/miniconda3/envs/train310/bin/python open_gripper.py --arm left --go

# b. 归位(--arm 换成这一轮在跑的那条臂)
~/miniconda3/envs/train310/bin/python arm_home.py --arm left --go --rate 15 --temp-stop 55 --hold 45
a 要看到 ✅ 已脱离、张开、抬起、后缩;出现 ! ③ 抬起 没到位 就是那一路欠力(多半是热), 别重试,先量温度。b 要看到 [home] 到位,最大偏差 x.x°,空载正常值 ≤ 3°; 2026-09-11 实测 6~9° 的偏差全部发生在 shoulder_lift 48~52°C 时,凉到 32°C 后回到 2.3°。 --hold 到点就卸力矩,手臂会垂 —— 要它停在归位点等下一轮,见下一步「保住力矩」那段。

第 10·5 步 · 换个起手位跑(偏左 / 偏前)

10

试跑和归位都能把起点沿该臂自己的基座系平移,单位厘米。IK 解不出来(残差超 25 mm)会直接抛错,而不是悄悄退回一个近似姿态 —— 那样等于把手臂送到一个你没要求的地方,而你以为自己在测偏移后的起点。

# 试跑:每一集都归到偏移后的起点(trial 1 之前一次,之后每次按 start 再一次)
--home-left N   --home-right N   --home-forward N   --home-back N   --home-up N   --home-down N

# 只归位、不跑模型
~/miniconda3/envs/train310/bin/python arm_home.py --arm left --left 10 --go --rate 15
★ 2026-09-11 之前这组方向别名的符号是反的 ★ 基座系 +x = 机器人的右 = 头相机画面的右(两臂同号:dx/dpan 左 +0.00278、右 +0.00341 m/deg)。 代码里却写成「+x = 左」,于是 --home-left 5 实际把手臂推到画面右。 怎么发现的:拍照,不是推公式 —— demo 起点 pan=+0.53 时爪子在中线;给「左 5cm」后 pan=+15.1, 头相机实拍爪子跑到画面右、贴着蓝杯子;给 −5cm 才移到画面左。 已修(arm_home.offset_vector 与 run_policy_trials 的 _hoff 各取反一次; --offset DX 保持基座系原样不取反,要「左 10cm」就写 [-10,0,0])。 2026-09-10 傍晚「起始偏左」那一轮方向是反的,结论要按这个重读。

保住力矩、让手臂停在偏移起点等下一轮:arm_home.py --hold 到点就卸力矩, 要一直停住得用 keep_torque=True 连接(这样 disconnect() 不掉电)。

cd ~/Robotic_challenge/03-software/scripts
~/miniconda3/envs/train310/bin/python - <<'EOF'
import sys; sys.path.insert(0, ".")
import run_policy_trials as rpt, arm_home as A, policy_safety, thermal_guard, bus_guard
from pathlib import Path
bus_guard.require_free_buses(sorted(str(p) for p in Path("/dev").glob("ttyACM*")), who="home-hold")
rpt.CAMERAS = {}                                   # 不开相机,免得和 8770 抢
pose = A.shift_pose("left", A.load_start_poses()["left"], (-0.10, 0.0, 0.0))   # 左 10cm
pose["gripper"] = 55.0                             # 保持张开,不让归位把爪子闭上
robot = rpt.connect_robot(cameras=False, keep_torque=True)
g = thermal_guard.XLeGuard(robot, ["left"])
r = A.home(robot, {"left": pose}, list(policy_safety.DIMS), send=True,
           hold_s=2.0, rate_deg_s=15.0, abort_if=lambda: bool(g.poll()))
print("worst_deg =", r.get("worst_deg"), g.report())
robot.disconnect()                                 # 力矩【保持】
EOF
日志里 == 归位点偏移 [-10.0, 0.0, 0.0] cm (基座系 +x右 +y前 +z上) 的符号要和你要的方向一致: 想往画面左就必须是负数。再核一眼 [offset] … 指尖 … → … 那行的残差,正常 ≤ 1 mm。 归位是在按 start 之后发生的(trial 1 之前还会多归一次),所以「按了 start 它才动」是对的,不是没归位。

第 11 步 · 跑完:卸力矩、记结果、回填

11
# a. 确认没有进程还占着总线(--keep-torque 跑完力矩还在)
pgrep -af run_policy_trials ; fuser /dev/ttyACM0 /dev/ttyACM1

# b. 卸力矩(不卸的话舵机长时间通电,正是这次过热的来源之一)
cd ~/Robotic_challenge/03-software/scripts
~/miniconda3/envs/train310/bin/python - <<'EOF'
import sys; sys.path.insert(0, ".")
import run_policy_trials as R
robot = R.connect_robot(cameras=False, keep_torque=False)
robot.disconnect()          # 打印「两条总线力矩已卸」
EOF

# c. 结果文件(脚本自动写)+ 闭爪时机(主判据)
ls -t ~/Robotic_challenge/05-training/trials/trials-*.json | head -1
~/miniconda3/envs/train310/bin/python trace_timing.py

# d. 手记杯位与颜色 —— trials JSON 里没有这两个字段
#    05-training/trials/cup-positions.json
#    05-training/trials/cup-colour-experiment.json
预注册参数不给,这一轮就不算数。 不传下面这组时,结果文件里 preregistered 是 null,收尾会打印 This run counts as EXPLORATORY only and must not be used for conclusions。 2026-09-11 上午那一轮就是这么作废的。正式跑测四个都要给:
--success-criterion "杯子被提起离桌面>3cm 并保持>1s" \
--planned-trials 3 \
--void-rule "摆放错误/人为干预/硬件故障/过热停机 → void" \
--initial-conditions "起手位=示范起点向机器人左移10cm;桌面杯位每次记录" \
--prereg-by "操作者+Claude,跑前写定"
看到 0/0 counted trials (no data)  (1 void, not counted) 这种行要当回事: void 不进分母,同时也说明这一轮没产出数据。k/n 按配置分组记, chunk 档、起手位、模型、时长任一不同就是另一组,不许合并成一个分数。

第 12 步 · 过热保护(2026-09-11 接上,跑测默认生效)

12
起因:2026-09-11 左臂 shoulder_lift 一路升到 69°C 才被发现。 征兆是动作越来越短:空载归位偏差 3.9° → 8.59° → 11.4°,最后整颗舵机从总线上消失 (Missing motor IDs: 2,那是 Feetech 的自保在动作)。 当时 thermal_guard.py 已经存在,但只有 arm_home --temp-stop 和 vr_teleop 用了, 而那天跑的是 run_policy_trials —— 全程没有任何东西在看温度。
入口现在怎么保护阈值
run_policy_trials.py每拍 XLeGuard.poll()(一拍只读一颗),到线停这一集并停掉整轮警告 50 / 停机 60
arm_home.home()
库函数内置
默认开,每 6 拍一次;place_cup / open_gripper / pour_cup / 试跑的归位全部自动继承,它们一行都不用改50 / 60
leader_follower_server.py
(录制)
3 Hz 巡检;到线收掉这一集(正常排空写盘并暂存)并松开主动臂力矩50 / 60
arm_move.py / arm_goto.py裸总线版 RawBusGuard:停在当前位置(不是继续走到 goal)并卸力矩50 / 60
arm_keys.py(手动按键)同上,走和按 x 一样的退出路径50 / 60
safe_replay.py本来就有(早于这次改动)警告 45 / 停机 55
vr_teleop.py · official_vr_teleop.py · trace_teleop.py每拍巡检;到线退出循环走 disconnect()(力矩随之卸)。trace_teleop 只看正在被驱动的那条臂 —— 另一条臂这里一个动作都不发,不该因为它热就打断这一场50 / 60
calibrate_leader_mirror.py每拍巡检;到线中止,已写入的关节仍然有效。(标定要把六个关节各来回推 20 拍 ×2,是连续负载排第二的用法)50 / 60
latency_move3.py每拍巡检;到线抛出,这次测量作废50 / 60
run_act_trial.py · verify_pan_sign.py发送前的温度门(一次性动作,不需要巡检):到停机线拒绝发送。verify_pan_sign 尤其要拦 —— 它靠行程判方向,热舵机走不满行程会把「没走够」误读成「方向反了」50 / 60
grasp_*.py · cup_reach · face_reach · visual_servo · teach_grasp 等 8 个实验脚本全部经 arm_move.smooth_move —— 继承裸总线守卫,一行都不用改50 / 60
brain/ 管线自己不发动作,spawn arm_home.py 和 run_policy_trials.py —— 继承上面两条50 / 60
collect_grip_traces.py不需要 —— 这个脚本只读电流不驱动电机(motion 被刻意排除在外,好让它能和面板同时跑)不适用

覆盖判据(2026-09-11 全仓审计):把所有直接调 send_action 的文件和两个裸总线驱动(arm_move / arm_keys)逐个过一遍, 现在全部有保护。唯一「没有」的是 policy_safety.py —— 那一行在文档字符串里,不是真代码。

为什么录制那条用 3 Hz 而不是每拍:录制回路的第一要求是永不停顿(_read_arms_fast 的整个设计前提), 30 Hz 下每拍读一颗就是每秒 30 次额外寄存器读;降到 3 Hz 后 12 颗轮一圈 4 秒,而舵机热时间常数是分钟级,够用。 为什么过热要停掉整集而不是降速:过热之后动作先变短再掉线,这段录进去是坏示范,留着会进训练集,比丢掉更糟。

# 随时查温度(只读寄存器,不驱动手臂)
cd ~/Robotic_challenge/03-software/scripts
~/miniconda3/envs/train310/bin/python - <<'EOF'
import sys; sys.path.insert(0, ".")
import run_policy_trials as R, thermal_guard
robot = R.connect_robot(cameras=False, keep_torque=True)
g = thermal_guard.XLeGuard(robot, ["left", "right"])
for _ in range(len(g.targets) * 2): g.poll()
print(g.report(top=12))
robot.disconnect()
EOF
空载 30~35°C 正常。到 50°C 日志打一行 !! 温度警告 … —— 这时归位偏差已经会从 2° 涨到 6~9°,数据开始不可比;60°C 停机。 ★ 停机后不要 power-cycle ★ 断电只清掉舵机的保护状态,温度还在,重新上电会接着热。 2026-09-11 实测从 69°C 自然冷却到 32°C 后,同一条归位从偏差 11.4° 回到 2.3°。

六、验收标准

不要用「视觉斜率 ≥ 0.3」当门禁。那条门禁在 左臂那一页已经撤销了:它建立在「只取动作块第一步、且只在起手位测」的指标上,而手臂停在 home 位时第一步本来就只挪一点点,任何模型测出来都接近 0。照着它走会把好模型拦在门外。
看什么怎么测及格线
闭爪 @ 伸出行程trace_timing.py,每次跑测自动输出示范是 97%。现役 52%。这一列是主判据 —— 参考值方差近乎为零,区分度高,物理含义直接。
横向增益杯位档 → 闭爪时 pan 的回归斜率示范隐含 1.0,现役 0.59–0.63。要看它有没有往 1.0 走。
实机 k/n按配置分组,void 不进分母右臂现在的基线是 0/14。样本要够 —— 8 次以下说明不了问题。
怎么算这一轮成功。这次唯一变量是数据(多了 23 集、横向覆盖补齐)。如果横向增益明显往 1.0 走,说明覆盖确实是瓶颈之一;如果横向好了但闭爪时机还是一半,那就锁定「节奏没打散」是剩下的那个缺陷,下一批补录直接冲它去。两种结果都有信息量。

七、证据等级

结论等级出处
合并集 73 集 / 32,216 帧,可加载跑测过verify_dataset.py + LeRobotDataset 实加载,2026-09-10
两次配方 150 字段只差 5 个跑测过两个 checkpoint 的 train_config.json 逐字段比对
右臂 0/14、左臂 28 次(15/20 + 2/2)跑测过05-training/trials/trials-*.json 逐文件计数
横向覆盖 CV 0.84 → 0.37跑测过逐集算闭爪 pan,闭爪判定与 demo_coverage.py 同规则
节奏 IQR 5.3pp → 8.4pp跑测过同上,闭爪帧 ÷ 集长
18px 视角偏移不影响训练没跑过只做了量级比较(远小于 sep2 的 −45/−74),没有对照实验
补录能把 0/14 翻过来没跑过这一轮训练就是为了回答它
数据集 xlerobot-team/xlerobot-right-pick-cup-0826sep8-full-20260910 · 合并来源记在它的 meta/merge_sources.json
前两次流程:右臂 state dropout 手册 · 左臂重训手册 · 诊断:右臂一天 16 次全败
本页数字实测于 2026-09-10 本机 Jetson · 调色板取自 dataviz 参考实例 1–3 号槽位,用 validate_palette.py 亮暗两档校验通过(浅色下 aqua 对比度 2.74:1 触发 WARN,三条序列均已配直接数值标签)