两部分。前半是下一步要做的事——把实机试验记录改造成经得起检验的形式, 依据是三篇评测规程论文 + 肖宇老师那篇 MRISA,每条都带出处。 后半是当天从踩坑里沉淀下来的经验条目,其中最重要的一条是关于我自己的: 说结论必须标明证据等级。
仓库内同一份文件:GOAL-TRIAL-DATA-MANAGEMENT.md(新会话的唯一输入)
操作者的原话(2026-09-03):
诊断下来是同一个病的两面:
第 1 条已经写进记忆 claims-must-state-evidence-level。
这份 GOAL 要解决第 2 条。
数据在 05-training/trials/trials-*.json(37 个文件)+ traces/*.npz(83 个)。
已有(别推倒重来):
| 有的东西 | 位置 | 说明 |
|---|---|---|
| 逐帧动作轨迹 | traces/*.npz | raw(模型原始输出) / safe(过完安全层) / t / dims,17 维,质量很好 |
| 安全事件计数 | trials-*.json 的 counts | OK / CLIPPED / HELD |
| 中止原因(一句话) | aborted | 只有安全层中止时有 |
| 结果标签 | outcome | success / failure / void / unknown |
| 每次耗时、tick 数 | seconds / ticks |
缺的东西(这份 GOAL 的任务):
| 缺的 | 后果 |
|---|---|
| 成功判据(事先写死) | 每次凭感觉判,不同人/不同天不可比 |
| 初始条件:杯子在哪 | ★ 最要命 ★ 说不出"是没看见还是抓歪了" |
| 子目标评分 | 只知道"失败",不知道失败在接近/对准/闭爪/提起哪一步 |
| 失败模式分类 | 75 次 failure 是一个黑箱 |
| 关键帧图像 | 事后无法复看,没有证据 |
| 事先固定次数 + 排除规则 | 边跑边决定 = 可以无意识地挑数据 |
| 区间估计 | 面板只显示百分比 |
Robot Learning as an Empirical Science: Best Practices for Policy Evaluation —— 最系统的一篇,核心要求:
Trossen 的工程版清单 —— 每次试验该记:
policy version、task variation、environment parameters、timestamps、sensor data、
action trace、outcome label、completion time、safety events、failure explanation。
用 Clopper-Pearson 精确区间。即使 70 次 rollout、90% 成功率,95% 区间仍有 15.4 个百分点宽。
MRISA: A Visual Analytics Approach of Locomotion Policies Comparison for Robotics Training(Graphics Interface 2025)
方法论值得学:访谈 5 位机器人从业者 + 分析 20 篇论文里的 58 张图,从真实实践提炼需求再设计工具。
结论:大家实际在用的是带轨迹的关键帧 + 数学量可视化。
系统做成五面板:控制项 / 仿真器 / 仿真画面 / 全局时间轴(带关键帧快照) / 局部数据+轨迹。
对我们的直接含义:试验记录必须同时留下轨迹(已有 ✅)和关键帧(没有 ❌),
时间轴才画得出来,"这次和上次差在哪"才看得见。
smolvla_20260820-1502/last 4/43 → 9.3% 95%CI [ 2.6%, 22.1%]
smolvla_20260820-1502/016000 2/11 → 18.2% 95%CI [ 2.3%, 51.8%]
a100-6000/006000 0/23 → 0.0% 95%CI [ 0.0%, 14.8%]
...b8-u20k/020000 0/4 → 0.0% 95%CI [ 0.0%, 60.2%] ← 什么都没证明
区间宽度 vs 次数(Clopper-Pearson):
| 观测 | 真实成功率 95% 区间 | 宽度 |
|---|---|---|
| 1/5 | [0.5%, 71.6%] | 71 个百分点 |
| 1/10 | [0.3%, 44.5%] | 44 |
| 1/30 | [0.1%, 17.2%] | 17 |
| 1/100 | [0.0%, 5.4%] | 5 |
跑 5 次基本等于没跑。文献常规是每任务 10–20 次。
改 03-software/scripts/run_policy_trials.py 的输出。保留现有字段(向后兼容,
老记录还要能读),新增:
{
// ── 预注册:跑之前写好,跑完不许改 ──
"preregistered": {
"success_criterion": "杯子被提起离桌面 >3cm 并保持 >1s", // 事先写死的判据
"planned_trials": 10, // 事先定的次数
"void_rule": "摆放错误/人为干预/硬件故障 → void,不进分母",
"initial_conditions": "杯子在 120-175mm 中段,每次换一个杯位,顺序固定",
"written_by": "谁写的判据", "written_at": "ISO 时间"
},
// ── 每次试验 ──
"trials": [{
"trial": 1,
"outcome": "failure",
// ★ 新增:子目标,逐项可判定 ★
"subgoals": {"approached": true, "aligned": false, "closed": false,
"lifted": false, "placed": false},
// ★ 新增:失败模式(枚举,不是自由文本) ★
"failure_mode": "aligned_miss", // 见下方枚举
// ★ 新增:初始条件 ★
"initial": {"cup_xyz_mm": [x, y, z], // 深度算的,没有就 null
"cup_source": "depth|manual|none",
"keyframe": "keyframes/t01-init.jpg"},
// ★ 新增:关键帧(MRISA 那条) ★
"keyframes": {"init": "...", "closure": "...", "end": "..."},
// 已有的保留
"aborted": null, "counts": {...}, "seconds": 12.3, "ticks": 361,
"trace": "traces/trace-....npz"
}],
// ── 汇总:报绝对数 + 区间 ──
"summary": {"counted": 9, "success": 1, "void": 1,
"rate": 0.111, "ci95": [0.003, 0.482], "ci_method": "clopper-pearson"}
}
failure_mode 枚举(先定这几个,跑一轮再按实际情况增补):
| 值 | 含义 |
|---|---|
no_approach | 手臂根本没往杯子方向去 |
aligned_miss | 去了但没对准,抓空 |
closed_early / closed_late | 闭爪时机不对 |
slipped | 抓住了又掉了 |
lifted_not_placed | 提起来了但没放下 |
safety_held | 被安全层拒帧中止 |
timeout | 超时 |
hardware | 总线/相机故障 → 应判 void |
试跑时在三个时刻各存一张头相机图:开跑前(初始条件)/ 闭爪瞬间 / 结束时。
存到 05-training/trials/keyframes/。这是 MRISA 那篇里"关键帧+轨迹"的关键帧那一半,
轨迹我们已经有了。
robot_server_dashboard.html 的试验记录卡:
k/n + 95% 区间,不再只显示百分比试跑启动前要求填/确认成功判据和计划次数(面板上一个小表单,或命令行参数),
写进结果文件的 preregistered。没有预注册的试验,报告里标成"探索性",不参与结论。
trials-*.json 保持原样,v2 只作用于新试验。 读取端要同时兼容两种(老的没有 preregistered/subgoals)。
run_policy_trials.py 是会驱动真手臂的 。改它之前确认没人在跑 (pgrep -f run_policy_trials),改完先用 --dry 跑一次。
z_floor(pitch) / y_floor(pitch) 接好,回归 40 集拒帧率 96.6% → 0.49%,别在这份工作里顺手改它。
claims-must-state-evidence-level。这是这份 GOAL 存在的原因,别在做它的过程中又犯。
| 事项 | 状态 |
|---|---|
| 原生深度录制 | ✅ 已打通(orbbec_depth_camera.py,实测 lerobot 认 is_depth_map),下次重启 8781 生效。之前 9-02/9-03 录的深度是旁路格式,用不了 |
| A′(RGB/几何找杯子) | ⚠️ 未验证。去掉颜色假设后改用「减桌面找凸起 + 剔除接触画面边界的连通域」,检出 97.6%、跳动 6.4mm,但肉眼看图仍无法确认锁的是杯子(Z 在 216–792mm 之间飘)。别当成可用方案 |
| 相机内参 + 手眼标定 | ❌ 没做,A/A′ 的硬门槛 |
| 深度↔RGB 时间对齐 | ❌ 从没验证过 |
头部预置位 trial | ❌ 没存,标定和试跑都依赖它 |
--dry 验证)k/n + 区间 + 失败模式分布)第 1–4 步全部可以远程做完,不需要现场,也不碰手臂。
这些是写进项目长期记忆的条目,按重要性排。前两条是关于工作方法的,其余是关于这台机器的事实。
说结论必须标证据等级:跑测过 / 没跑过 / 跑了但被挡住;没跑过的不许说\"唯一可行\"\"从没成功\
用户 2026-09-03 的原话:「没有跑测的说抓不起来,跑测过的也没有记录清楚是定位有效还是别的什么
只看测试结果,甚至没有跑测过的说唯一可行。这个问题已经出现了很多次了。」
三个具体犯错实例(同一天内):
trials-*.json 里 114 次试验有 8 次 success(6 次是 SmolVLA 真跑)。
见 no-checkpoint-has-picked-up-cup。
「模型可能从来没被允许动第一帧」。实际试验记录里 106/114 次跑完了,
只有 8 次被安全层中止。离线过滤率 ≠ 实跑拒帧率,中间那一步我没验。
结果两个定位算法都锁在机械臂上、根本没找到杯子。推荐在前、验证在后。
How to apply — 每个结论必须自带证据等级,三选一:
这不等于方案失败,只能说「这次没跑到底」。
说「从没 / 一直 / 唯一 / 必然」之前先去数据里数一遍。
本项目的事实来源:05-training/trials/*.json(试验)、meta/upload.json(上传)、
meta/health.json(体检)、journalctl --user-unit=robot-status(作业日志)。
相关:ground-explanations-in-sources、evaluate-whole-system-not-patches
自研任何\"lerobot 好像不支持\"的东西之前,先 git log -S 查上游;深度旁路就是白写的
2026-08-31 给深度相机写了一整套自研旁路(depth_recorder.py + 数据集旁 depth/ 目录 +
墙钟事后对齐),理由写在代码注释里:「深度进 LeRobot 数据集会被视频编码压毁」。
两处都错了:
feat(depth maps))2026-06-27 就合入了,本机 checkout 停在 2026-08-07,写旁路时它已经躺在本地代码里三周多。
datasets/utils.py:143 DEPTH_FILE_PATTERN),根本不走视频编码。
代价:一套没人消费的旁路 + 两个自带的缺陷(孤儿线程写满磁盘、fd 泄漏),
见 depth-sidecar-orphan-fills-disk。
Why:用户当场就问出来了 —— 「也就是说你没有用原生支持深度自己编了一个嘛」。
自研的成本不是写代码的那半天,是从此要自己维护它的全部失败模式,
而上游那套已经被很多人跑过了。
How to apply:动手写任何「上游好像不支持」的东西之前,先做这两步,一分钟:
git -C ~/lerobot log -S '<关键词>' --oneline # 上游有没有做过
grep -rn '<关键词>' ~/lerobot/src/lerobot # 本地这份 checkout 里有没有
写进注释的技术理由必须当场验证过,否则整套设计会建立在一个没人复核的前提上。
和 ground-explanations-in-sources、evaluate-whole-system-not-patches 是同一条原则
在「造轮子」这个方向上的具体形式。
【已更正】旧模型抓成功过 6 次(114 次试验里),不是"从没成功过";换爪后的新模型缺乏可比记录
2026-09-03 更正:这条原来写的「至今没抓起过一次杯子」是错的。
查 05-training/trials/trials-*.json(37 个文件、114 次试验):
smolvla_20260820-1502,08-23 四次单跑+ 08-24 两批 1/10 和 1/5),另 2 次是示范重放(不算模型的功劳)。
所以正确说法:**旧夹爪 + 08-20 那个模型抓成功过,成功率约 1/10–1/5。
换夹爪(08-24)之后的新模型没有可比的成功记录** —— 但那是"没有记录",不是"试过且失败"。
这条错误结论造成的连带伤害:它被当成既成事实反复用于推理
("40 集都不够所以 30 集更不够"、"先加深度只会更难分辨"),而前提本身没核过。
教训 :任何"从没成功过 / 一直失败"这类全称否定,写进记忆之前必须先去数
trials-*.json。见 claims-must-state-evidence-level。
08-24 换夹爪后右臂安全下限 0.065 高了 8 cm,示范数据 96.6% 被拒、试跑第 1 帧即中止;09-03 改 −0.11;换爪后的\"抓不到\"不能算模型失败
2026-09-03 实测:teleop_safety.yaml 右臂 z_min = 0.065(08-24 "realgenius gripper" 标定)
把训练集 xlerobot-right-pick-cup-20260826-0042 全部 40 集 16,487 帧示范拒掉 96.6%
(35/40 集 ≥90%)。试跑走到第 1 帧就 HELD 15/15 → 中止,手臂一条指令都没收到。
08-25 的试验记录里 2/4 次是同一句 right wrist height +0.04 outside [0.065, 0.35]。
0.065 为什么错:它的含义是"爪子竖直时爪尖碰桌的腕高" = 桌面 + 爪长。
同一文件里实测新爪长 gripper_len_m = 0.1179,反推桌面 = −0.053;
而桌面实测是 −0.137/−0.142(08-16 摇臂法)。差 8 cm。新爪比旧爪(0.159)短,下限却抬了 5 cm,方向都反了。
示范最低腕高 −0.102 = 桌面上方 3.5 cm,物理合理,人当时真抓到了。
已改:右臂 z_min → −0.11(示范最低点再留 1 cm)。离线验收:第 0 集 399 OK + 1 CLIPPED,
40 集 0 帧被拒。只动这一个数;VR 遥操作的 PoseFilter 读同一个 yaml,所以 VR 的下限也跟着降了。
推论:08-24 换夹爪之后所有"模型抓不到杯子"的实机结论都不能算数 —— 模型很可能从来没被允许动。
no-checkpoint-has-picked-up-cup 里那些 0/N 要重新审视。
待办(按价值排):
teleop_config.z_floor(pitch) 俯仰补偿已经写好、gripper_len_m 也已实测,但 policy_safety._fk_check 和 vr_teleop 用的都还是标量 z[0],没有任何调用方接它。接上就是正解(算爪尖不算腕)。
policy_safety.py 文档里"19,453 帧验证只 4 帧出界"是换爪前的数;换爪/改下限后必须重跑那个验证 (~/floor_acceptance.py 就是可重跑的版本)。
教训:安全层的数值改了,必须拿真实示范数据回归一遍;文档里的"验证过"有日期,过期了等于没验。
① 「试跑第 1 帧即中止」「换爪后的抓不到不能算模型失败」—— 过度推断。
96.6% 是拿【示范数据】离线过滤算的,不是实跑拒帧率。查 trials-*.json:
114 次试验里只有 8 次被安全层中止,106 次跑完了。 离线过滤率 ≠ 实跑表现,
中间那一步没验证就拿来解释实跑现象了。
② z_min 0.065 不是「标错了」。
照片核过:爪竖直、爪尖抵桌,两次标定给 0.200/0.206,自洽。真正的 bug 是
安全层拿「爪子竖直」的地板去卡实际接近水平的爪子(示范 pitch 中位 −2.8°),
白锁掉一个爪长。前伸下限 y≥0.05 是同一个错误的水平版(爪尖比手腕前 20cm)。
正解已接:z_floor(pitch) + 新增 y_floor(pitch),爪长 0.1999、
min_wrist_clearance 0.03、新增 min_wrist_reach_m 0.02。
回归 40 集:拒帧率 96.6% → 0.49%,且爪子竖直时两条地板都抬回原位、仍拦得住。
LF 面板 Start 按两次会孤儿掉深度录制线程,它一直写帧到磁盘满;征兆是 LF 进程常驻 ~30% CPU
leader_follower_server.py 的 _cmd_start 原来直接 self._depth = DepthRecorder(...),
覆盖前不停旧的。操作者按两次 Start(这个习惯已经在 1637 那次把 mp4 录脏过一回),
上一个 DepthRecorder 就没人再持有引用了:它的 daemon 线程只认自己的 _stop 事件,
而 _cmd_stop 只停 self._depth 指向的那一个 —— 旧的一直写到进程死或磁盘满。
实测 xlerobot-left-pick-cup-sep2-20260902-1819 的 ep20-20260902-191409:
19:14:09 起跑了 13.7 小时到次日 08:53,220,412 帧 / 27G,把根分区写满。
同数据集其余 47 个目录都是 44–153 帧 / 8–18M。
诊断要点:
depth/<ep>/meta.json 缺失 ⇒ stop() 从没被调用过(meta 只在 stop 里写)。valid 均值 0.882,和正常集的 0.85 同量级。「深度开着但没正常工作」是错的判断 —— 相机正常,是生命周期管理漏了。
leader_follower_server.py 进程长期占 ~30% CPU 并一直 hold 住深度节点 /dev/video0。这是最好认的征兆。
2026-09-03 已修(_cmd_start 里覆盖前先 stop,和 _cmd_stop/shutdown 同一套收尾)。
改完要重启 8781 服务才生效,重启同时也才能杀掉还活着的孤儿线程。
相关:disk-is-now-a-hard-constraint、leader-follower-recording
自动上传只在录制结束当场触发一次;之后录的数据集不会自己补传,要走面板/api/dataset/publish
robot_server.py 的自动上传只在一次录制结束的当场触发(_publish_dataset 线程)。
错过那一下就再也不会自己传 —— 2026-09-02 的
xlerobot-left-pick-cup-sep2-20260902-1819(34 集)和 -1928(17 集)就是这样,
录完时间晚于当晚最后一次上传,在 Hub 上根本不存在(dataset_info 返回 404,不是 private)。
怎么判断一个数据集到底传没传(别只看面板):
meta/upload.json 存在 ⇒ 至少尝试过,里面 ok 是真结果,跨服务重启仍在。文件不存在 ⇒ 一次都没尝试过。
--user-unit=robot-status 里 grep uploaded / upload FAILED。HfApi().dataset_info(repo_id)。账号是 Suyang99。补传入口:POST /api/dataset/publish(8770,带 token),body {"repo_id": ...},
内部走和录制结束时同一道 verify_dataset.py 结构门,绿了才推。
面板上有对应按钮。
坑:upload_folder 的 ignore_patterns 只排除了 images/** 和 tmp*/**,
depth/ 会被整个推上去。正常一集深度约 10MB 没问题,但撞上
depth-sidecar-orphan-fills-disk 那种 22 万文件 / 27G 就会卡死在本地哈希阶段
(Hub 上只建了空仓库,一个真文件都推不出去),得重启 8770 服务才能停。
相关:disk-is-now-a-hard-constraint、publish-to-hf-space
2026-09-03 数据集迁到 HF 组织 xlerobot-team(显示名 Aalto-xlerobot-team);模型仍在 Suyang99;owner 用 bartender_tasks.HF_OWNER
组织名有两个,别搞混:URL / repo_id 用短名 xlerobot-team;
Aalto-xlerobot-team 只是显示名(fullname),直接拿它查是 404。
2026-09-03 迁移结果:
xlerobot-team/…(32 public + 1 private)。trash-* 并设 private,留在 Suyang99/ 个人名下(组织里保持干净)。 没删——trash-soak-20260816-220415 / -220918 是 xlerobot_vr.py 注释里的实测证据,
trash-xlerobot-TEST-… 是 VR-AXIS-POLLUTION 文档的证据。
Suyang99/。robot_server.HUB_AUTHOR = "Suyang99" 只管模型,别改。owner 现在是配置项,换账号只改一处:
bartender_tasks.HF_OWNER(默认 xlerobot-team,环境变量 XLEROBOT_HF_OWNER 可覆盖)。
跟着改过的:recordings.on_hub()、hub_audit.py、prune_uploaded_datasets.py、
dashboard / VR 页 / LF 页 / traincalc 的预填名。
本地缓存目录名 = repo_id,所以迁移后本地也要跟着搬:
~/.cache/huggingface/lerobot/<owner>/<name>。不搬的话补传会把数据集重新创建回个人账号。
9-03 只搬了 8 个(组织里已有对应的),剩 50 个还在 Suyang99/ 下没搬——
其中大部分是从没传成功的或垃圾。搬之前必须确认 LF 不在录制(目录被占会打断会话)。
踩过的坑:早上 prune_uploaded_datasets.py 删掉了本地
xlerobot-cup-grasp-20260820-0230(Hub 上有完整副本,删得对),但
demo_coverage.py / grasp_sight_check.py / trace_vs_demo.py 硬编码了它的本地路径,
现在会失败。要用先从 Hub 拉回来。教训:删本地副本前先 grep 有没有脚本硬编码它。
相关:dataset-upload-is-not-automatic-after-the-fact、disk-is-now-a-hard-constraint
Jetson 根分区 116G 已用九成;数据集缓存 38G + 仓库 data/ 44G;满盘会让 Claude Code 的 Bash 整个失效
Jetson 只有一块 116G 的根分区(/dev/mmcblk0p1),/tmp 也在上面 —— 不是 tmpfs。
2026-09-03 它被写到 100%(剩 28K),后果不只是录制失败:
! 命令 —— harness 每次执行前要在 /tmp/claude-<uid>/<project>/<session>/tasks/ 建输出文件,ENOSPC 就直接报错,命令根本没跑。
这种情况下只能到会话外的 shell(SSH / VNC)里腾空间。
/tmp 满了」,但 df -h /tmp 显示的是根分区 —— 别只清 /tmp。当前占用大头(2026-09-03):~/.cache/huggingface/lerobot 38G(92 个数据集)、
~/Robotic_challenge/data 44G(独立目录,不是软链)、/swapfile 17G(别动)、
~/miniconda3 16G(别动)。
安全的一次性回收约 7G:~/miniconda.sh、~/robot-restore-*、~/.cache/huggingface/hub
(模型缓存,用时自动重下)、/tmp/claude-*。
删本地数据集前必须逐个核对它是否真在 Hub 上 —— 见 dataset-upload-is-not-automatic-after-the-fact。
深度旁路(30fps 16-bit PNG)每集约 10MB,失控时是 GB/小时级,见
depth-sidecar-orphan-fills-disk。