← 报告首页 / reports index
2026-09-06 · 手臂为什么一卡一卡的,怎么修

让手臂别再一卡一卡的

读完约 5 分钟。结论在最前面,中间是为什么,最后一节是走过的错路。 每个数字都在这台 Orin 上量过;没有一个来自真机试跑。

结论

毛病:手臂每 2.7 秒定住 1.4 秒,然后猛冲 32°,夹爪一次抓取乱开合 4 次(人示范恒为 1)。 根因是模型算一段计划要 1.4 秒,算的时候整个程序卡住。

修法:RTC(把计算搬到后台 + 要求新计划的开头必须接上已经发出去的动作)。 代码已接好,安全层一行没改,默认关,随时能退回去。

代价:手臂只能跑 0.43 倍速 —— 一次 13.7 秒的示范动作要跑 40 秒, 超过现在 25 秒的上限。

第一批就这么跑:去噪步数保持默认 10,把每次上限从 25 秒改成 60 秒。 提高时限不花任何精度;第一批要验的是「顺不顺」,不是「快不快」。 别在同一批里同时改两个东西。

另外三个方案(含一个已经试过并否掉的)见第 6 节。

1 · 毛病

正常走 ▓▓▓▓▓  定住 1.4 秒 ░░░░░░░  猛冲 32° █  正常走 ▓▓▓▓▓  定住…

25 秒里这样「停 → 冲」7 次。正常帧只走 5.6°。 停的时候手臂对世界是瞎的(在按 1.4 秒前的画面动);冲的时候容易撞倒杯子。

2 · 为什么

模型不是「看一眼动一下」,而是看一眼,一口气规划未来 50 步;而算这 50 步要 1.4 秒。 老代码是排队做的:读相机 → 算(卡住 1.4 秒) → 发指令 → 回到开头。 卡住时电机收不到新指令,保持上一条 —— 手臂就定住了。

猛冲是因为新计划从1.4 秒前的位置出发,等它算完手臂已经不在那儿了, 硬发新计划的第一步就得一步跳过去。

比方:你开车,副驾看地图。他每次看要 1.4 秒,这期间一句话不说,你只能沿上一句开。 等他抬头说「左转」,你已经开过路口了 —— 于是猛打方向盘。

3 · 修法:让他一边看地图一边说话

RTC(Real-Time Chunking),lerobot 里已有现成实现,我们没造轮子。两件事:

① 搬到后台后台线程一直在算下一段;主循环只从「已算好的队列」取一步发出去。停顿消失。
② 让新计划接得上算新计划时把「这期间已发出去的那几步」当成题目的一部分,要求新计划的开头必须和它们一样。猛冲消失。

第 ② 件不是事后抹平,是让跳变一开始就不产生。 安全层原地不动:引擎只算出动作,发不发仍由我们的安全层说了算。

4 · 代价:手臂只能跑 0.43 倍速

RTC 算一段要 2.0 秒(原来 1.4 秒),而且新计划落地时, 这 2 秒里已经走掉的步要被丢掉 —— 那正是「接得上」的代价。

算下来 RTC 每秒最多喂出 12.8 步,而按示范原速需要 30 步/秒。 = 0.43 倍速。13.7 秒的动作要跑 40 秒,25 秒上限跑不完。

慢了会不会不准?对这个任务,不会

人自己的示范快慢就差 1.82 倍40 次示范,最快 9.6 秒、最慢 17.5 秒。模型是在这个分布上学的
模型看不到速度输入只有「三张图 + 17 个关节位置」,没有速度、没有时间。同一个位置的画面,快到的和慢到的一模一样
它是闭环每 0.9 秒看一张新画面重新规划 —— 慢反而意味着纠正次数更多

证据等级:推理 + 旁证,没在真机上对比过。前提是杯子不动。

5 · 那个旋钮:模型「反复琢磨几次」

模型算这 50 步是从一团随机噪声开始、反复修正 10 次,每次问一下「往哪个方向改」。 这 10 次叫去噪步数。

一次推理 1.4 秒 = VLM 看图 0.33 秒(只算一次)+ 每次琢磨 0.105 秒 × 10 次
                                                    ↑ 占 76%

调小它就立刻变快,而且不用重新训练。代价是「琢磨得少 = 答案粗」:

琢磨几次手臂速度13.7 秒的动作要跑答案偏差(换成夹爪位置)
10(现在,也是业界默认)0.43×40 秒3~11 mm
60.65×26 秒6~20 mm
40.90×19 秒9~29 mm

参照:人示范时 40 次抓取的落点,离中位点的中位距离 25 毫米 (这个参照不严谨:那 40 次杯子摆的位置本来就不同,我分不开「杯子位置差异」和「抓取容错」)。

这种「拿精度换速度」是业界常规做法

DDIM(Song, Meng & Ermon 2020)就是为此发明的: 早期扩散模型训练用多少步、推理就得用多少步,动辄上千步。DDIM 把两者拆开, 论文自己的说法是 “trading off computation for sample quality”(用计算量换采样质量), 给出 10×~50× 提速。
Diffusion Policy(Chi et al. 2023)在机器人上就这么用: 训练 100 步、推理只用 10 步,3080 上 0.1 秒延迟。 综述里转引的量化结果:采样快 10 倍、成功率掉 5.6% (★ 我只在综述里读到,没读到原始论文,请当二手引用 ★)。
而 10 步正是 flow-matching 这类模型的默认值 —— VLA-Perf 写着 π0 和 SmolVLA 用的都是 10, 且「这 10 次 action expert 前向主导端到端延迟」,和我实测的 76% 吻合。
所以我们已经站在业界默认值上了。Diffusion Policy 从 100 降到 10 是白捡的; 我们从 10 再往下,是往默认值以下走。
文献里真做到 1~2 步的(Consistency Policy、 SnapFlow、 One-Step Flow Policy) 走的都是蒸馏 —— 额外训一个学生模型,不是把旋钮调小。

6 · ★ 还可以讨论的方案 ★

原来是四条,C 已经试过并且否掉了(下面有数据)。剩下三条可选,选哪条是你的决定:

A · 保持 10 步 + 上限 60 秒 ← 我建议第一批用这个
代价:零精度损失,只是每次试验最长 60 秒。 第一批要验的是 RTC 的卖点 —— 不卡顿、不猛冲、夹爪不乱开合。这三件事和速度无关。 先把它证明了,再谈提速。
B · 干脆不用 RTC,只把去噪步数降到 4
同步路径 + 4 步:原速播放、13.7 秒的动作 13.7 秒跑完、25 秒上限够用。 手臂定住的时间从 84% 降到 45%,单条指令从 53° 降到 16°。
代价:还是会卡(只是短一半),而且动作偏差 3.1° → 8.3°。
值得讨论的点:这条完全不需要 RTC,改动最小。如果现场时间只够跑一批, 它可能比 A 更划算 —— 但它不能证明 RTC 有没有用。
C · 自适应步数(AdaVLA)—— 试过了,在我们这个模型上没用
思路是「简单的时刻少琢磨几次、难的时刻多琢磨几次」,免训练,本来是这几条里唯一 可能不用取舍的。2026-09-06 实现并实测了,结论是负的。

2 个场景 × 8 个随机种子 × 2 种判据读法 × 3 个阈值 = 12 个配置,全部输给固定步数 (都存在一个固定步数设置,不比它慢、也不比它差):
场景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°, 涨得又快又稳 —— 这说明我们这个模型的流轨迹一点也不接近直线。 而这个方法的整个前提就是「有些时刻轨迹近似线性、可以一步跨过去」。前提不成立。
再往下一层:它要省时间,唯一途径是最后来一个大跨步;而大跨步的误差正比于步长的平方乘曲率。 结构上就赢不了,不是调参没调对。
论文能赢的三个可能,我没法从这里证伪:(1) 他们同时做了 MLP 剪枝,我只实现了曲率判据这一半; (2) 他们没写清两个探测时刻怎么取,我试了两种自然读法都输; (3) 他们测的是 π0.5 和 X-VLA 不是 SmolVLA —— 他们的阈值在不同模型间差两个数量级, 本身就说明这个判据对模型很敏感。
开关保留在 --adaptive-steps,默认关,为的是让这个负结果可复现。
D · 蒸馏一个学生模型(Consistency Policy / SnapFlow)
这是文献里压到 1~2 步的标准做法,能在几乎不掉精度的前提下快 5~10 倍。
代价:要在远程 4090 上额外训一次,而且它是另一个模型 —— 成绩要单独一组,不能和现在的合并。
值得讨论的点:现场之前来不及;但如果这条路线要长期走下去,它是终点。

7 · 怎么验收

三级,从不碰机器人的开始。前两级过了才做第三级。

A 级 · 不碰机器人(2 分钟,现在就能做)

做什么过的标准
面板 → 数据 → 🤖 跑测数据顶上是一张「按配置分组的成绩」表;点任意一行,下面列表筛到那个配置
试跑卡上把「去噪步数」从 10 改成 4下面两行说明跟着变:0.34× · 40s · 超上限 ✗ → 0.72× · 19s · 在上限内 ✓
python rtc_glue.py --selftest --hz 3 --seconds 45末行 结论: ✅,且「稳态 0 拍」
把 --hz 3 换成 --hz 15 再跑应该失败(空拍 20%+,❌)—— 说明自检真的在量东西

B 级 · 空跑(开相机开总线,一帧不发电机)

勾「异步推理 RTC」→ 点空跑。日志要有这三行:

== async inference: RTC …开关传下去了
measured loop X Hz -> chunk_k=…自动调参跑到了
结束有 OK / CLIPPED / HELD 计数,帧数 > 0安全层还在路径上

跑完看「跑测数据」:这一批的配置列应写着 RTC 且单独一组,没和同步的合并。

C 级 · 实跑(手臂会动,急停必须在手边)

  1. 按第 6 节选定的方案设参数(建议 A:10 步 + 60 秒)
  2. 先跑 1 次,不是 10 次
  3. 打开热力图和轨迹,看三件事:没有 1.4 秒的水平段 · 没有孤立的 30° 跳变 · rtc_dry_ticks 除开头几秒应为 0
  4. 看 gripper open/close cycles:越接近 1 越好(示范恒为 1,老路径是 4)
  5. 都对了再跑 10 次一批。两批成绩不会合并 —— 推理方式是「配置」的一维,面板上是两行

不想用了:把勾去掉就是今天的行为。老路径一个字节没改。

8 · 哪些测过,哪些没有

✅ 测过链路跑得通、不用重训;主循环完全没被后台线程拖慢(每拍最长 1.8 毫秒,老路径卡 1440 毫秒);安全层仍在工作(245 帧里 53 帧钳位);各档去噪步数的耗时与偏差
⏳ 完全没测真机上的一切。抓取会不会变准 —— 没有任何证据。
⚠️ 只是推理「慢了不影响准确性」(第 4 节)。有旁证,没在真机对比过
⚠️ 不能引用「夹爪开合 1 次」—— 来自假画面

9 · 走过的错路

不影响上面的结论,放这里是因为怎么发现的比结论有用,也免得下一个人再走一遍。

试过但不行的两个加速办法

半精度推理❌ 反而慢 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 凭什么当基准要先说清楚。 拿一个近似当尺子去量另一个近似,量出来的是「两个近似之间的差」,不是「离对的答案有多远」。

代码里只有真跑才暴露的 5 个 bug

全部通过了语法检查和逻辑推演,症状都不是崩溃,是「手臂不动 / 莫名其妙地慢 / 界面一片空白」。 详见交接文档 HANDOFF-SESSION-2026-09-06.md §5。发现方法:用假机器人替换掉真机器人、 走完整的程序入口跑一遍,而不是单独调几个函数看返回值。

2026-09-06 · 全部数字来自合成观测和假机器人端到端测试,checkpoint xlerobot-smolvla-right-pick-b16-3cam-u20k/020000,device=cuda,全程没有连接机器人。 技术细节见面板 /rtc,名词表见 /readdata#s9。引用的论文全部带链接在正文里。