sep8 补录已经和 0826 合并好、传上 Hub。这一页回答三件事:新数据集能不能直接拿去训、配方要不要改(不要)、以及这批补录按项目自己的录制验收标准补上了哪几项(第 1 项部分、第 2 项完全没有)。
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,结构、视角、任务文本全核过。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、慢好几倍还不报错。training_state/,否则续不了。
把左臂那次和右臂 0826 那次的 train_config.json 逐字段拉平比对,150 个字段里只有 5 个不同,其中 4 个是名字和路径:
| 字段 | 左臂(left-sd) | 右臂 0826(right-sd) |
|---|---|---|
dataset.repo_id | xlerobot-left-pick-cup-sep3-clean-20260905 54 集 / 13,693 帧 | xlerobot-right-pick-cup-20260826-0042 40 集 / 16,487 帧 |
policy.repo_id | Suyang99/xlerobot-smolvla-left-sd-p0.2-b16-u20000 | Suyang99/xlerobot-smolvla-right-sd-p0.2-b16-u20000 |
job_name / output_dir | 只是把 left-sd / right-sd 拼进路径 | |
eval.batch_size | 44 | 50 |
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 集) | 020000 | speed 1.0 · chunk auto/4 | 14 | 0 / 14 | 0 |
| right-sd(0826,40 集) | 012000 | speed 1.0 · chunk auto/4 | 2 | 0 / 2 | 0 |
| left-sd(sep3-clean,54 集) | 020000 | speed 1.0 · chunk auto/4 | 2 | 2 / 2 | 0 |
| left-sd(sep3-clean,54 集) | 020000 | speed 1.0 · chunk 1/1 | 26 | 15 / 20 | 6 |
左臂合计 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,不用看杯子)。逐集量一遍,看这批补录各治到什么程度。
[-10,0) 和 [0,+10) 从 4 集和 1 集抬到 12 集和 9 集 —— 正是诊断点名的那一带。五个主档(-40~+10)的集数变异系数 CV 0.84 → 0.37。[+10,+20) 仍然只有 1 集,是这次没补到的角落。09-08 的报告说「示范里爪子在伸出行程的 99.8% 处闭合,实机只到 52%」。那是模型缺陷,不是数据缺陷 —— 重新量一遍,两批数据的示范都在伸出行程的 97% 附近闭爪:
| 数据 | 集数 | 闭爪 @ 伸出行程 中位 | 四分位区间 | 异常集 |
|---|---|---|---|---|
| 0826 | 40 | 96.7% | 92.3% – 98.4% | 3 集 |
| sep8 已合并的 23 集 | 23 | 97.9% | 93.9% – 98.7% | 0 集 |
| 合并后 | 63 | 97.3% | 92.8% – 98.7% | 3 集 |
上面三项是我自己挑的角度。项目里其实有写好的验收标准 —— recording-guide-2026-09-08 的四项,每项都带一个可判定的 DONE WHEN。按它重算,三种合并范围的得分:
| 杯位档(闭爪 pan,9° 一档) | A 当前 63 集 | B +2008/2016 67 集 | C 再 +1938 73 集 ★已采用 |
|---|---|---|---|
-40 ~ -31° | 11 | 11 | 11 |
-31 ~ -22° | 15 | 15 | 15 |
-22 ~ -13° | 14 | 14 | 14 |
-13 ~ -4° | 11 | 11 | 11 |
-4 ~ +5° | 10 | 10 | 10 |
+5 ~ +14°(最疏) | 2 | 6 | 10 |
| 指南第几项 | DONE WHEN | A 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)就是这个形状 | ||
| ④ 杯位写进每集元数据 | 每集带位置字段 | 未做 —— 现在只能从手臂姿态反推,是循环论证 | ||
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 固定竖直偏移 |
| 源 | 集 | 帧 | head_depth | 闭爪 pan 落在 |
|---|---|---|---|---|
right-pick-cup-20260826-0042 | 40 | 16,487 | 本来就没有 | -36.7 ~ +10.2° |
sep8-1938-nodepth | 6 | 2,579 | 本次摘除 | +11.9 ~ +16.1°(最疏那一档) |
sep8-2008-nodepth | 3 | 1,455 | 本次摘除 | +11.4 ~ +12.6° |
sep8-2016-nodepth | 1 | 557 | 本次摘除 | +5.0° |
sep8-2039-nodepth | 8 | 3,684 | 09-09 已摘 | +0.0 ~ +2.8° |
sep8-2059-nodepth | 7 | 3,480 | 09-09 已摘 | -7.0 ~ -4.0° |
sep8-2117-nodepth | 8 | 3,974 | 09-09 已摘 | -34.7 ~ -20.9° |
| 合计 | 73 | 32,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 一个字节都不动 —— 深度只存在视频流里,动作和状态不受影响。
sep2-trim 那批的处理方式是切掉每集开头的归位跳变,这一步没做,是独立的一次操作。选数据集必须先核视角 —— sep2 那七个数据集就是栽在这里(整批作废)。这次用中值背景做相位相关,量出来:
| 比较 | 平移 dx / dy | 相关峰值 | 读法 |
|---|---|---|---|
| sep8 三个之间 | 约 1 px | 0.77 | 同一视角 |
| sep8 vs 0826 | +4 px / +18 px | 0.13 | 同一视角,有固定偏移 |
| sep2 vs 0826(已知作废的反例) | −45 px / −74 px | 0.06 | 整机换了视角 |
18 px 是画面高的 7.5%,比 sep2 那种小一个量级,而且训练本来就开着 affine 增强(平移 ±5%)。判定:可以合。但这是没跑测过的推断 —— 18 px 偏移对成功率的影响没有做过对照实验,只是量级上远小于已知会出事的那一档。
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]² 算方差:
c,理论上 E[x²] = c²、E[x]² = c²,相减应得 0;c² 累加再除以 N,结果不会精确回到 c²;|c₂|/|c₁| = 49.846 / 3.692 = 13.50 s₂/s₁ = 0.1284 / 0.0104 = 12.34 (吻合)s/|c| ≈ 2.6~2.8 × 10⁻³。E[x²] = 0,没有大数相减,std 精确为 0。左臂那六维的常数正好是 0.0,所以一直被正确钉住;头部的常数是 −3.69 / 49.85,就漏了。这是旧判据漏掉头部的全部原因。常数 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 == maxmin 和 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 时退回旧判据并打印警告。
| 维 | min | max | std | 旧判据 | 新判据 |
|---|---|---|---|---|---|
| left_arm ×6 | 0.000000 | 0.000000 | 0.000000 | 钉 | 钉 |
head_motor_1 | −3.692308 | −3.692308 | 0.009319 | 漏 | 钉 |
head_motor_2 | 49.846153 | 49.846153 | 0.124309 | 漏 | 钉 |
| right_arm ×6 | min ≠ max(真的在变) | 2.15~32.2 | — | — | |
x/y/theta.vel | min ≠ 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,新旧判据都会自动钉住,推理侧一并解决,不需要任何新机制。这一节 2026-09-11 重写成自足版:每一步都给出完整命令、预期输出和「看到什么要停」,不再要求读者去翻 09-08 右臂手册或 b16 页。所有命令都对应仓库里现存的脚本,路径和参数逐个核过(脚本文件头、add_argument、head_presets.json、上一轮的 shell 历史)。
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 左臂页记的那两个坑已经不存在)。ps -C claude ; pgrep -af run_policy_trials # 各只应有一行(你自己)
df -h / # 根分区 116 GB,满盘会让整个会话失效
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)"
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 那次踩过。| 项 | 选择 | 为什么 |
|---|---|---|
| 平台 | RunPod | 前三次(b16 / right-sd / left-sd)都在这上面跑通 |
| 模板 | PyTorch 2.8 / CUDA 12.8 | torchcodec 0.7.0 要 cu128 |
| 显卡 | RTX 4090 | 同配方 20000 更新一小时出头(右臂 right-sd 实跑) |
| 存储 | ≥ 50 GB 挂 /workspace | 10 个存档 × 约 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 # ★ 不是可选项 ★
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。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),别把尖括号一起抄进去。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
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 步烟测一行都看不到,很容易误判成没生效。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_FRAC | 0.3 | 阶段 A 纯视觉(p=1.0)占总步数比例 |
SD_ANNEAL_FRAC | 0.4 | 阶段 B 退火 1.0→下限 的占比 |
SD_P_END | 0.2 | 阶段 C 永久下限;改了仓库名跟着变(-p0.3-) |
SD_ENABLE | 1 | 0 = 对照组,仓库名变 -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 上一小时出头。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')))"
/workspace;Terminate 删 Pod。全部传完再 Terminate。忘关会一直计费。模型在个人账号 Suyang99 下,数据集在组织 xlerobot-team 下,两者分开。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。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。这个数不做门禁(见第六节),只用来和下一轮比。# 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 不进分母。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 没有这个字段。这一步以前只写了「按 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
✅ 已脱离、张开、抬起、后缩;出现 ! ③ 抬起 没到位 就是那一路欠力(多半是热),
别重试,先量温度。b 要看到 [home] 到位,最大偏差 x.x°,空载正常值 ≤ 3°;
2026-09-11 实测 6~9° 的偏差全部发生在 shoulder_lift 48~52°C 时,凉到 32°C 后回到 2.3°。
--hold 到点就卸力矩,手臂会垂 —— 要它停在归位点等下一轮,见下一步「保住力矩」那段。试跑和归位都能把起点沿该臂自己的基座系平移,单位厘米。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
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 它才动」是对的,不是没归位。# 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 档、起手位、模型、时长任一不同就是另一组,不许合并成一个分数。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
!! 温度警告 … ——
这时归位偏差已经会从 2° 涨到 6~9°,数据开始不可比;60°C 停机。
★ 停机后不要 power-cycle ★ 断电只清掉舵机的保护状态,温度还在,重新上电会接着热。
2026-09-11 实测从 69°C 自然冷却到 32°C 后,同一条归位从偏差 11.4° 回到 2.3°。| 看什么 | 怎么测 | 及格线 |
|---|---|---|
| 闭爪 @ 伸出行程 | trace_timing.py,每次跑测自动输出 | 示范是 97%。现役 52%。这一列是主判据 —— 参考值方差近乎为零,区分度高,物理含义直接。 |
| 横向增益 | 杯位档 → 闭爪时 pan 的回归斜率 | 示范隐含 1.0,现役 0.59–0.63。要看它有没有往 1.0 走。 |
| 实机 k/n | 按配置分组,void 不进分母 | 右臂现在的基线是 0/14。样本要够 —— 8 次以下说明不了问题。 |
| 结论 | 等级 | 出处 |
|---|---|---|
| 合并集 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 翻过来 | 没跑过 | 这一轮训练就是为了回答它 |
meta/merge_sources.jsonvalidate_palette.py 亮暗两档校验通过(浅色下 aqua 对比度 2.74:1 触发 WARN,三条序列均已配直接数值标签)