读完约 5 分钟。结论在最前面,中间是为什么,最后一节是走过的错路。 每个数字都在这台 Orin 上量过;没有一个来自真机试跑。
毛病:手臂每 2.7 秒定住 1.4 秒,然后猛冲 32°,夹爪一次抓取乱开合 4 次(人示范恒为 1)。 根因是模型算一段计划要 1.4 秒,算的时候整个程序卡住。
修法:RTC(把计算搬到后台 + 要求新计划的开头必须接上已经发出去的动作)。 代码已接好,安全层一行没改,默认关,随时能退回去。
代价:手臂只能跑 0.43 倍速 —— 一次 13.7 秒的示范动作要跑 40 秒, 超过现在 25 秒的上限。
第一批就这么跑:去噪步数保持默认 10,把每次上限从 25 秒改成 60 秒。 提高时限不花任何精度;第一批要验的是「顺不顺」,不是「快不快」。 别在同一批里同时改两个东西。
另外三个方案(含一个已经试过并否掉的)见第 6 节。
正常走 ▓▓▓▓▓ 定住 1.4 秒 ░░░░░░░ 猛冲 32° █ 正常走 ▓▓▓▓▓ 定住…
25 秒里这样「停 → 冲」7 次。正常帧只走 5.6°。 停的时候手臂对世界是瞎的(在按 1.4 秒前的画面动);冲的时候容易撞倒杯子。
猛冲是因为新计划从1.4 秒前的位置出发,等它算完手臂已经不在那儿了, 硬发新计划的第一步就得一步跳过去。
RTC(Real-Time Chunking),lerobot 里已有现成实现,我们没造轮子。两件事:
| ① 搬到后台 | 后台线程一直在算下一段;主循环只从「已算好的队列」取一步发出去。停顿消失。 |
| ② 让新计划接得上 | 算新计划时把「这期间已发出去的那几步」当成题目的一部分,要求新计划的开头必须和它们一样。猛冲消失。 |
第 ② 件不是事后抹平,是让跳变一开始就不产生。 安全层原地不动:引擎只算出动作,发不发仍由我们的安全层说了算。
RTC 算一段要 2.0 秒(原来 1.4 秒),而且新计划落地时, 这 2 秒里已经走掉的步要被丢掉 —— 那正是「接得上」的代价。
| 人自己的示范快慢就差 1.82 倍 | 40 次示范,最快 9.6 秒、最慢 17.5 秒。模型是在这个分布上学的 |
| 模型看不到速度 | 输入只有「三张图 + 17 个关节位置」,没有速度、没有时间。同一个位置的画面,快到的和慢到的一模一样 |
| 它是闭环 | 每 0.9 秒看一张新画面重新规划 —— 慢反而意味着纠正次数更多 |
证据等级:推理 + 旁证,没在真机上对比过。前提是杯子不动。
模型算这 50 步是从一团随机噪声开始、反复修正 10 次,每次问一下「往哪个方向改」。 这 10 次叫去噪步数。
一次推理 1.4 秒 = VLM 看图 0.33 秒(只算一次)+ 每次琢磨 0.105 秒 × 10 次
↑ 占 76%
调小它就立刻变快,而且不用重新训练。代价是「琢磨得少 = 答案粗」:
| 琢磨几次 | 手臂速度 | 13.7 秒的动作要跑 | 答案偏差(换成夹爪位置) |
|---|---|---|---|
| 10(现在,也是业界默认) | 0.43× | 40 秒 | 3~11 mm |
| 6 | 0.65× | 26 秒 | 6~20 mm |
| 4 | 0.90× | 19 秒 | 9~29 mm |
参照:人示范时 40 次抓取的落点,离中位点的中位距离 25 毫米 (这个参照不严谨:那 40 次杯子摆的位置本来就不同,我分不开「杯子位置差异」和「抓取容错」)。
原来是四条,C 已经试过并且否掉了(下面有数据)。剩下三条可选,选哪条是你的决定:
场景0 固定 10 步 1.315s / 2.93° 自适应最好 1.312s / 3.27° 场景1 固定 10 步 1.339s / 2.89° 自适应最好 1.321s / 3.10° 所有真省了时间的档(3 步) 20.8~24.3° 而固定 3 步只有 10.3~10.8° —— 差 2 倍根因是数据自己给的:固定步数的误差曲线是 10 步 2.93° → 6 步 5.58° → 4 步 8.01° → 3 步 10.32°, 涨得又快又稳 —— 这说明我们这个模型的流轨迹一点也不接近直线。 而这个方法的整个前提就是「有些时刻轨迹近似线性、可以一步跨过去」。前提不成立。
--adaptive-steps,默认关,为的是让这个负结果可复现。
三级,从不碰机器人的开始。前两级过了才做第三级。
| 做什么 | 过的标准 |
|---|---|
| 面板 → 数据 → 🤖 跑测数据 | 顶上是一张「按配置分组的成绩」表;点任意一行,下面列表筛到那个配置 |
| 试跑卡上把「去噪步数」从 10 改成 4 | 下面两行说明跟着变:0.34× · 40s · 超上限 ✗ → 0.72× · 19s · 在上限内 ✓ |
python rtc_glue.py --selftest --hz 3 --seconds 45 | 末行 结论: ✅,且「稳态 0 拍」 |
把 --hz 3 换成 --hz 15 再跑 | 应该失败(空拍 20%+,❌)—— 说明自检真的在量东西 |
勾「异步推理 RTC」→ 点空跑。日志要有这三行:
== async inference: RTC … | 开关传下去了 |
measured loop X Hz -> chunk_k=… | 自动调参跑到了 |
结束有 OK / CLIPPED / HELD 计数,帧数 > 0 | 安全层还在路径上 |
跑完看「跑测数据」:这一批的配置列应写着 RTC 且单独一组,没和同步的合并。
rtc_dry_ticks 除开头几秒应为 0gripper open/close cycles:越接近 1 越好(示范恒为 1,老路径是 4)不想用了:把勾去掉就是今天的行为。老路径一个字节没改。
| ✅ 测过 | 链路跑得通、不用重训;主循环完全没被后台线程拖慢(每拍最长 1.8 毫秒,老路径卡 1440 毫秒);安全层仍在工作(245 帧里 53 帧钳位);各档去噪步数的耗时与偏差 |
| ⏳ 完全没测 | 真机上的一切。抓取会不会变准 —— 没有任何证据。 |
| ⚠️ 只是推理 | 「慢了不影响准确性」(第 4 节)。有旁证,没在真机对比过 |
| ⚠️ 不能引用 | 「夹爪开合 1 次」—— 来自假画面 |
不影响上面的结论,放这里是因为怎么发现的比结论有用,也免得下一个人再走一遍。
| 半精度推理 | ❌ 反而慢 9~22%。动作只差 0.17°,所以不是精度问题,纯粹是这个小模型上类型转换的开销大过收益 |
torch.compile | ❌ 编译直接报错:torch 2.8 + triton 3.7.1 的 inductor 后端不兼容
('KernelMetadata' object has no attribute 'cluster_dims')。要修得动版本,临近现场不建议碰。踩过的坑: sample_actions 在内层 VLAFlowMatching 上,不在 SmolVLAPolicy 上 |
加上第 5 节那个「拿精度换速度」的旋钮,三条加速路里两条是死路, 唯一有效的那条要付精度 —— 这就是为什么建议 A 是「提高时限」。
| 我说过 | 实际 |
|---|---|
| 「我们的回路是 3 Hz」 | 这个数没有意义 —— 它把「读相机的 55 毫秒」和「等推理的 1.4 秒」平均在了一起。正确的单位是每秒走完多少步计划 |
| 「RTC 下一拍取一步就是原速」 | 错的。一拍取一步 = 回路频率 步/秒,离 30 步/秒差得远 |
| 「琢磨 4 次只偏 3.29°,推荐用 4」 | 偏乐观一倍多。那是拿全黑图、单个随机种子、以 10 次为基准量的 —— 而 10 次自己也只是近似。换成真实画面 + 6 个种子 + 高步数参考解后是 8.26°,并推翻了这条推荐 |
最后一条的教训:量「A 比 B 差多少」时,B 凭什么当基准要先说清楚。 拿一个近似当尺子去量另一个近似,量出来的是「两个近似之间的差」,不是「离对的答案有多远」。
全部通过了语法检查和逻辑推演,症状都不是崩溃,是「手臂不动 / 莫名其妙地慢 / 界面一片空白」。
详见交接文档 HANDOFF-SESSION-2026-09-06.md §5。发现方法:用假机器人替换掉真机器人、
走完整的程序入口跑一遍,而不是单独调几个函数看返回值。
xlerobot-smolvla-right-pick-b16-3cam-u20k/020000,device=cuda,全程没有连接机器人。
技术细节见面板 /rtc,名词表见 /readdata#s9。引用的论文全部带链接在正文里。