前言

截止到25年7月份,本博客内已经陆续解读了physical intelligence公司推出的一系列模型/算法,成了一个大系列,比如

  1. π0
    涉及对其原理的解读、lerobot pi0代码的解析、openpi官方代码的解析,以及我司对其的微调
  2. FAST
  3. Hi Robot
  4. π0.5
  5. π0.5的KI改进版

这家公司确实因为牛人很多,加之很多又是斯坦福、UC伯克利等高校的副教授/研究员,带了一堆聪明的博士生(要知道,现在具身科研的主力一半都是TOP高校的博士生),所以,使得他们产出很快、很多

这不,在25年6.9日,他们又推出了本文要介绍的实时动作分块技术,使得VLA也可以做点燃火柴、插入网线的高精度任务

由此,证明了,VLA也是可以做高精度任务的,不一定只有RL或RL参与的算法才能做高精度任务

顺带小小感慨一下

  • 本博客π0系列的文章 已经十多篇了,搞vla必读
  • 而我和我司『七月在线』组织的π0交流群,成员来自各大top985,以及各大科研院所、公司,​我们这帮人见证和推动了中国具身的飞跃
    且我司也会在中国具身科技发展史上,留下浓墨一笔,期待与更多同行、同仁、工厂多多合作

25年12月中旬更新

  1. 因为PI公司后续又推出了一个Training-Time RTC的工作,其《在训练时模拟推理延迟(承认既定事实专心预测后续动作):消除推理阶段的计算开销,让π0.6完成箱子装配与咖啡制作
    故又再次注意到了本文介绍的RTC
  2. 考虑到,原RTC论文只提供了伪代码实现 无法运行
    故,我于25年12.15上午在文末提供了一份RTC的Python实现,并把Python代码和论文中的公式做了一一对应,且详尽细致解读了下

第一部分 动作分块流策略的实时执行

1.1 引言、相关工作、预备知识与动机

1.1.1 引言

如原论文所说,与聊天机器人和图像生成器不同,信息物理系统始终在实时环境中运行。当机器人在“思考”时,其周围的世界仍然根据物理定律不断演化。因此,输入和输出之间的延迟会对性能产生切实的影响

对于一个语言模型来说,生成速度快或慢的区别,通常只是让用户感到满意或恼火;而对于一个机器人动作模型而言,这种差异则可能意味着机器人要么把一杯热咖啡稳稳递到你手中,要么把它洒在你的腿上

不幸的是,现代大规模机器学习的高效能不可避免地伴随着高延迟这一副作用。LLMs、VLMs,以及VLAs都拥有数十亿个参数 [8,29,5,4,57]

  1. 这些模型不仅运行缓慢,而且还难以部署到诸如移动机器人等边缘设备上的重型硬件,从而使远程推理带来更多额外开销

    虽然边缘硬件会随时间改进,但随着机器人数据集规模扩大,最优视觉语言动作模型(VLA)的复杂度也将同步提升[28]
  2. 因此,要在实时控制问题中有效应用大模型,就必须引入某种形式的异步机制:也就是说,模型必须在执行前一个动作的同时思考其未来动作

    动作分块(action chunking)[68,33,11],即模型在每次推理调用中输出并执行一个由多个动作组成的序列,提供了一个部分性的解决方案。尽管动作分块已经在灵巧操作领域取得了多项最新的最优结果[5,4,58],但它仍然饱受时延问题的困扰
    因为相邻分块可能会在学习到的动作分布中跳转到不同的模式(或“策略”)。此类异常对基于学习的系统尤为有害,因为它们会在动力学上造成分布偏移,而模型很可能并未具备应对这种偏移的能力

    朴素的平滑策略『例如将多个预测结果进行平均[68]』并不能保证产生有效动作,反而可能使问题更加严重(例如,见图2,如果没看明白,没事,下文会再详细解释该图)

    一个良好的实时系统必须产生一致且连续的控制信号,在融合最新观测的同时,既不扰乱环境的自然动力学过程,也不削弱模型产生正确动作的能力

在本研究中,作者提出了实时分块(RTC)方法

  • 其对应的paper为:Real-Time Execution of Action Chunking Flow PoliciesSubmitted on 9 Jun 2025
    其对应的github为:Physical-Intelligence/real-time-chunking-kinetix
  • 将异步动作分块建模为一个图像填补(inpainting)问题
    即算法在执行上一段动作块的同时生成下一段动作块冻结那些由于推理延迟必然会被执行动作,并对剩余部分进行“图像填补”操作
    Our algorithm generates the next action chunk while executing the previous one , freezing the actions that are guaranteed to be executed ( due to inference delay ) and “inpainting” the rest. 
    ——————
    该方法适用于任意基于扩散 [22] 或基于流 [36] 的 VLA,并且仅在推理阶段运行,无需对现有训练方案做任何修改

1.1.2 相关工作

第一,对于动作分块与VLA

动作分块技术部分受到人类运动控制的启发[32],其近年来已成为视觉运动控制模仿学习领域的事实标准[67,11]

  1. 从人类数据中学习生成动作分块需要具有表现力的统计模型,如变分推断[67,18]、扩散模型[11,12,68,67,45,58]、流匹配[5,6]、向量量化方法 [33,3,43],或字节对编码 [46]
  2. 最近,这些方法中的一些已扩展到数十亿参数,催生了VLA[7,13,29,5,70,10,9,69,23,46,36],即基于预训练视觉语言模型骨架构建的大型模型

    且由于随着适配不断增长的机器人数据集 [13,28,61,14,40,26],以及来自视觉-语言预训练的互联网知识,VLA 在通用机器人操作任务中取得了令人瞩目的成果

此外,应用于真实机器人时,动作分块策略通常与底层高频控制回路(如PID控制器)协同工作,后者PID控制器:将策略输出(如关节位置)转换为硬件特定的控制信号(如关节力矩)

  1. 在此类场景中,动作分块策略可视为级联控制的一种形式[14],其中学习策略作为最外层控制回路运作
  2. 然而也存在例外:例如作者的仿真实验直接采用输出力矩和力的学习策略。因此,关于级联控制理论与学习型动作分块策略交叉领域的研究,作者表示,将留待未来工作探讨

第二,对于降低推理延迟

  1. 提升模型实时性能的一个自然方法就是简单地加快其运行速度
    例如
    \rightarrow  consistency policy [48] 通过蒸馏扩散策略来省略昂贵的迭代去噪过程
    \rightarrow  Streaming diffusion policy [22] 提出了一种替代的训练方法,使每个控制器时间步仅需极少的去噪步骤
    \rightarrow  Kim 等人 [30] 通过并行解码增强了 OpenVLA [29],从而省略了昂贵的自回归解码
  2. 更广泛而言,针对扩散模型[51,37,55,16] 和大型 Transformer [31,24,34] 的推理速度优化,已有大量研究文献
    然而,这些方法都无法将推理成本降到一次前向传播以下。只要这一步前向传播所需时间超过控制器的采样周期,要实现实时执行仍需其他方法

第三,对于图像修复与引导技术

已有大量文献探讨了利用“预训练扩散模型和流模型”进行图像修复的方法 [47,54,39,41]

  1. 在本文介绍的工作中,作者将此类方法 [47] 融入了他们新颖的实时执行框架,并进行了必要的修改(即软掩码和引导权重裁剪),以适应他们的应用场景
  2. 在序列决策制定方面,Diffuser [25] 首次提出了基于扩散的图像修复方法,用于在长期规划中遵循状态和动作约束,尽管其修复方法并非基于引导机制

    此外,Diffuser 及其他相关工作 [63,1] 也通过价值函数对扩散模型进行引导,以解决强化学习问题
    作者宣称,他们的工作具有独特性,因为这是首次将图像修复或引导方法应用于机器人的实时控制『Our work is distinct in that it is the first to apply either inpainting or guidance to real-time control.』

第四,对于实时执行

早在VLA出现之前,实时控制就已被广泛研究

  1. 与动作分块类似,模型预测控制『MPC;[50]』在递进的时间范围内生成计划;与作者的方法一样,它将执行与计算并行化,并利用前一个分块为下一个规划提供热启动
    ————
    尽管近期将学习方法与MPC结合的研究已在狭窄领域中展现出实时控制能力[52,20],但这些方法仍依赖于显式手工构建的动力学模型及代价函数『Though recent works combining learning methods with MPC have demonstrated real-time control capabilities in narrow domains [ 53 , 21], they rely on explicit, hand-crafted dynamics models and cost function

    使其在非结构化环境下的应用变得困难
    ,而VLA正是在这些领域获得了广泛应用
  2. 另一方面,在强化学习领域,已有多项研究提出了时延决策方法[56,15,53,62,65,66]。然而,这些方法并不总能适用于模仿学习,且均未利用动作分块技术
    巧的是,在25年7月10日前后,RL用到动作分块了,详见下一篇博客的解读
  3. 最近出现的分层VLA设计[57,4-GR00T N1] 将模型拆分为System 2(高层规划)和System 1(低层动作生成)两个部分
    用于高层规划的System 2组件承载VLA的主要能力,但运行频率较低,而用于低层动作生成的System 1组件则轻量且快速

    这种方法与本文介绍的方法相互独立,并具有自身的权衡(例如,需要限制System 1组件的规模,并需要其专门的训练方法)

第五,对于双向解码

  1. 与本研究最为相关的前期工作是双向解码(BID;[38]),该方法通过拒绝采样,使预训练的动作分块策略能够实现完全闭环控制
    ————
    尽管Liu等人[38]未考虑推理延迟,但BID算法依然可以用于实现与本文基于引导的图像修复(inpainting)相同的效果
  2. 作者在模拟基准测试中将BID与RTC进行了对比,发现BID的性能不如RTC,同时计算资源消耗却显著更高

1.1.3 预备知识与动机

如原论文所述,作者介绍了本工作所涉及的预备知识与动机

  1. 比如从一个动作分块策略开始,记作
    \pi\left(\mathbf{A}_{t} \mid \mathbf{o}_{t}\right)

    其中
    \mathbf{A}_{t}=\left[\mathbf{a}_{t}, \mathbf{a}_{t+1}, \ldots, \mathbf{a}_{t+H-1}\right]是未来动作的一个分块,\mathbf{o}_{t} 是观测值,t 表示控制器的时间步,且称H 为预测时域
  2. 当动作分块策略被执行时,仅执行每个分块中的前s \leq H个动作,然后策略会生成一个新的分块
    作者称s 为执行视野;它通常比预测视野短,但仍远大于1,例如s \approx H / 2,此,可参见[11,5,23]

分块执行在牺牲反应性的前提下确保时间一致性

  • 较长的执行时域会降低策略对新信息的响应能力
  • 而较短的执行时域则会增加发生模式跳变的可能性,即由于不同块之间的不连续性而产生的生硬、抖动式行为

在本文中,作者考虑使用条件流匹配[36] 训练的策略『当然,该方法也可以通过在推理时将扩散策略转换为流策略,从而以兼容扩散策略[48, 18]

  1. 为了从一个流策略中生成一个动作块,首先从标准高斯中采样随机噪声\mathbf{A}_{t}^{0},然后利用更新规则将流的速度场\mathbf{v}_{\pi}一个已学习的神经网络)从\tau=0积分到1

    此过程,定义为公式1
    \mathbf{A}_{t}^{\tau+\frac{1}{n}}=\mathbf{A}_{t}^{\tau}+\frac{1}{n} \mathbf{v}_{\pi}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)
    其中\tau \in[0,1)表示流匹配的时间步,n表示去噪步骤的数量
  2. 现在,令\Delta t为控制器采样周期(即单步控制时长),\delta 表示策略生成一个动作块所需的时间
    如果称一个系统是实时系统,则意味着其能保证在固定时间约束\Delta t内,对一个事件(接收观测值o_t)产生响应(在本场景中:动作a_t
    \rightarrow  若\delta \leq \Delta t,那么满足实时约束是很容易的,因为在两个控制器时间步之间可以生成完整的一个块
    然而,对于现代VLAs,这几乎是不可能实现的。例如,在一块NVIDIA RTX 4090 GPU上,具有30亿参数的π0 VLA 仅在KV 缓存预填充上就花费了46 ms,这还不包括任何去噪步骤[5],而目标控制频率为50Hz (\Delta t=20 \mathrm{~ms})

    在移动操作的远程推理场景中,即使在使用有线连接且条件完美的情况下,π0 的网络时延也达到13 ms。而在更为现实的环境下,仅网络开销就很容易超过20ms
    Kim 等人[31] 专门针对推理速度对70 亿参数的OpenVLA 模型[30] 进行了优化,但即便在服务器级A100 GPU 上,其延迟也不过降至321 ms


    \rightarrow  至于朴素的同步推理『许多先前工作的默认设置[5, 29, 8, 23, 30, 58]』 则更不用说了,因为其仅在执行时限结束后才开始推理,并等待策略生成下一个动作组块
    即当\delta>\Delta t,使得在分块之间引入了明显的停顿,这不仅会减慢执行速度,还会改变机器人的动态特性,从而导致训练与评估之间出现分布偏移

如上,使得为实现实时策略,必须首先引入异步推理机制:提前启动推理过程,使其与动作执行并发进行

  1. 作者定义d:=\lfloor\delta / \Delta t\rfloor,并将该量称为推理延迟,对应于从接收到\mathbf{o}_{t}到获得动作块\mathbf{A}_{t}之间的时间间隔

    \mathbf{a}_{t^{\prime} \mid t}表示从观察到\mathbf{o}_{t}后生成的chunk At 的第\left(t^{\prime}-t\right)个动作
    如果当前正在执行\mathbf{A}_{0},并且希望在第 j 步切换chunk,那么异步算法必须在j-d时刻开始推理
    然而,由于策略在生成\mathbf{A}_{j-d}时无法知道在步骤j -dj之间会发生什么,因此\mathbf{a}_{j-1 \mid 0}\mathbf{a}_{j \mid j-d}之间的切换点可能是任意不连续的,并且超出了分布范围

    若动作\mathbf{A}_{0}正在执行中,且期望执行时限为s,则异步算法必须在s-d时刻启动推理
    只要满足d \leq H-s,该策略就会满足实时约束,并保证在需要时总有一个可用的动作
    即如果H=8,s=6,d=2,则始终有动作可以用
    但如果H=8,s=6,d=4,则在t = 5和t=6时,则没动作可以用了(因为s=6,故需要执行6个动作)


    然而,由于在生成A_{s-d}时,策略无法知道在步 s-d 与 s 之间会发生什么,因此在a_{s-1|0}a_{s|s-d}之间的切换点可能出现任意程度的不连续性并落在分布之外

    类似于执行时限过短的情形,这一策略会导致动作抖动,并且在延迟增大时这种现象会显著恶化
  2. 具体参见下图图2,这是连续两个块之间典型分叉的一个示例。推理在第 3 步和第 4 步之间开始。正在执行的原始块 {a_{t}}(黑色)计划从障碍物上方通过而新生成的块 {a_{t}^{\prime}}(红色)则计划从下方通过。然而,{a_{t}^{\prime}} 要到 7 步之后的 d 时刻才可用
    一个简单的异步算法可能会a_{10} 跳转到a_{11}^{\prime},从而导致非常高的、超出分布的加速度——即机械臂突然剧烈抖动到某个位置
    ————
    至于时间集成 [67],即在块之间进行插值,可以减少加速度,但会产生较差的动作

1.2 基于图像修复的实时分块技术

实时执行的关键挑战在于保持各分块之间的连续性

  1. 当新分块可用时,前一个分块往往已经执行完部分,因此新分块必须与前一个分块“兼容”。与此同时,新分块还应当融合新的观测信息,以保证策略不会丧失反应和纠正的能力
    ————
    作者的关键见解是将实时分块问题视为一次图像修复(inpainting)问题
  2. 为了使新分块“兼容”,必须利用与前一分块剩余动作重叠的时间步——例如下图的a_3a_{10}在新分块中,最开始的动作不能被使用,因为当新分块变为可用时,这些时间步已经过去
    因此,将这些动作“冻结”为已知会被执行的值是合理的;作者的目标是在保证该冻结前缀一致性的前提下,补全新分块的剩余部分(如上图的a11-a15,如下图图3的a4-a10),这类似于修复一幅被移除部分的图像
    总之,上图图3展示了在实时分块过程中,动作生成如何关注前一个动作块,更多下文会详解

看到上面这,我觉得可能还是会有些朋友懵,为一目了然,我再补充说明下,简单一句话:RTC 让机器人边做边想,避免因为等待模型而停下来


更具体地说,它把异步流水线与动作序列 inpainting 结合起来:  

  1. 先“锁住”推理期间必然会发生、因而无法改变的动作
    机器人执行当前动作块到一定程度后,后台开始生成下一个动作块。但算得没那么快,比如慢了 4 拍。等新的动作算出来时,机器人已经把前 4 拍的动作做完了
    RTC 就把这 4 拍“锁死”,保证新算出来的动作必须从这里接着往下走,不会前后脱节
  2. 再“续”出还没做的动作
    锁住前 d 步后,模型一边参考旧 chunk 的剩余动作,一边补全更远的未来
    它会它不仅参考刚冻结的几步,还会以逐渐减弱的强度参考旧动作块的剩余部分,把后续动作补得更连续,就像按原画的笔触补全缺失区域

举个日常例子

  1. 一辆开着自动驾驶模式的汽车(本质就是机器人)在高速上快速行驶
    此时,自驾算法看到前面有左转的指示牌,于是自驾算法开始推理,2s后 自驾算法给出“左转”的指令,可这2s的时间内,车子已经开过左转指示牌50米远了,然后听到指令后,不得不猛打方向盘,此时会导致车子剧烈晃动甚至翻车
  2. RTC给出的策略是,自驾算法看你正在直行,心里清楚:“我推理的这2秒内他肯定会继续直行,这部分是改变不了的
    于是自驾算法不再规划现在的路,而是直接规划2秒后的路。他想:“等我把话说完,车子应该已经到了‘大树’旁边。所以我的指令是——‘从大树那里开始’向左转。”

    因此,车子边开自驾算法边想。等自驾算法把指令说完时,车子刚好开到大树旁边,指令和车子的当前位置完美无缝衔接,过弯如丝般顺滑

1.2.1 推理阶段的流匹配补绘:确保新动作跟冻结部分吻合

图像修复是迭代去噪框架(如扩散和流匹配)已知的优势之一

  1. 作者基于Pokle 等人[47] 提出的免训练图像修复算法进行改进,该算法本身基于伪逆引导(IIGDM;[54])
    该算法通过在每一步去噪时向学习到的速度场\mathbf{v}中添加一个基于梯度的引导项,见上文提到的这个公式1
    The algorithm operates by adding a gradient-based guidance term to the learned velocity field v at each denoising step (Equation 1)
    \mathbf{A}_{t}^{\tau+\frac{1}{n}}=\mathbf{A}_{t}^{\tau}+\frac{1}{n} \mathbf{v}_{\pi}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)

    以促使最终生成结果匹配某个目标值\text { Y },其中Y 是预期结果的受损版本
    that encourages the final generation to match some target value, Y, which is a corrupted version of the desired result.
  2. 在图像修复的情况下,损坏操作表现为掩码处理,\text { Y } 是被掩码的图像,而预期结果是与Y 在非掩码区域 保持一致的完整图像
    针对作者设定的IIGDM 梯度修正为——分别定义为为公式2 3 4
    \begin{aligned} \mathbf{v}_{\Pi \mathrm{GDM}}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right) & =\mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)+\min \left(\beta, \frac{1-\tau}{\tau \cdot r_{\tau}^{2}}\right)\left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \widehat{\mathbf{A}_{t}^{\tau}}} \\ \text { where } \widehat{\mathbf{A}_{t}^{1}} & =\mathbf{A}_{t}^{\tau}+(1-\tau) \mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right) \\ r_{\tau}^{2} & =\frac{(1-\tau)^{2}}{\tau^{2}+(1-\tau)^{2}} \end{aligned}

    其中,\widehat{\mathbf{A}_{t}^{1}}是最终完全去噪动作片段的估计,W 是掩码
    作者在这里滥用了记号,将Y, \mathbf{A}_{t}\mathbf{W} 视为维度为H M 的向量,其中M是每个动作的维度
    因此,引导项是一个向量-雅可比积,可以通过反向传播计算。引导权重裁剪\beta 是作者新增的内容;作者发现如果没有它,在控制问题中常用的较少去噪步数下,算法会变得不稳定——
    比如避免因为去噪步数少(比如n=5)导致动作乱飞

1.2.2 软掩码以提升跨片段连续性

在实际操作中,仅仅利用前一个动作片段的前几个时间步进行原生图像修复,通常不足以确保新的动作分块能够采用一致的策略,尤其当d较小时(例如,参见图4)——说白了,有时新旧分块的策略差别太大,接起来还是不顺

ΠGDM校正并不完美,且较小的d会导致指导信号较弱,这可能会导致新的分块仍然可能切换策略并导致不连续性『比如,当前分块计划让火柴划盒子顶部,新分块却想划底部,直接切换会让机器人动作猛地一跳

作者的解决方案如图3 所示

是通过不仅仅考虑前d 个重叠动作,而是考虑所有H - s 个重叠动作(上面说过了,H是预测范围,s是执行范围),来赋予作者的策略更多跨分块的连续性

作者通过软掩码(Soft Masking)实现这一点,将W 设置为实值权重,而不是1 和0

  • 前d 个动作的权重为1,相当于是必然会被执行的
    如果推理在执行完a_{-1}之后开始,且推理延迟为d=4,则新生成的块要等到a_{3}被消耗后才能使用。因此,a_{0: 3}被“冻结”,并以完全的引导权重1进行关注
  • 新分块的最后s 个动作则不与前一个区块重叠的部分,因此权重为0
    最后的s=5个动作超出了前一个块的末尾,需要重新生成。执行视野s是一个超参数,满足d \leq s \leq H-d的约束
  • 中间的动作则赋予从1到0指数衰减的权重,以反映距离未来越远的动作应当被赋予更高的不确定性
    在中间区域a_{4: 10},前一个块中的动作可用,但可能会被更新,因为推理会在a_{4}被需要之前完成。该区域以指数递减的引导权重进行关注

由此得到的W的表达式为——定义为公式5

\mathbf{W}_{i}=\left\{\begin{array}{ll} 1 & \text { if } i<d \\ c_{i} \frac{e^{c_{i}-1}}{e-1} & \text { if } d \leq i<H-s \\ 0 & \text { if } i \geq H-s \end{array} \quad \text { where } c_{i}=\frac{H-s-i}{H-s-d+1}, i \in\{0, \ldots, H-1\}\right.

直观来看,W调节了对前一块中每个相应动作所给予的“关注度”

    简言之,软掩码就像在两段动作间加了个“缓冲带”,让动作接得更自然,举个例子:假设预测范围H=16,执行范围s=5,延迟d=4

    1. RTC会给前4个动作(即图中的a_0a_3,已执行)的权重1,完全冻结;
    2. 接下来的7个动作(a_4a_{10},可能执行)的权重从1逐渐降到0;
    3. 最后5个动作(a_{11}a_{15},全新生成)的权重是0
      这样,新分块在点火柴时会更“尊重”前一段的划火柴动作,慢慢过渡到点燃蜡烛,而不是突然换个策略

    还可以再想象你正在看一部连续剧,H=16 表示总计有 16 集

    1. 你已经看完了 4 集(d=4),这 4 集的内容板上钉钉,不能改
    2. 接下来 7 集 是“预告片”,可能拍也可能不拍,所以它们的“戏份”从 100% 慢慢降到 0%
    3. 最后 5 集还没写剧本,完全自由发挥

    软掩码就像剪辑师在两段剧情之间加了“淡入淡出”:老剧情音量先保持最大,再慢慢调低,新剧情音量从 0 逐渐拉高。于是观众不会觉得“咔”一下换台,而是感觉剧情顺滑地接下去了

    1.2.3 实时分块的完整算法流程(含论文中的伪代码和我写的Python代码)

    如上可知,RTC 的核心思想是异步推理 (Asynchronous Inference),通过将动作生成视为一个Inpainting (修补) 问题,利用上一块(Chunk)未执行完的动作来“引导”新块的生成,从而实现平滑过渡

    作者在算法1 中展示了完整的实时分块系统(参见图3)

    1. 控制器通过GETACTION 与RTC的算法进行交互,该函数每隔\Delta t调用一次,用于消耗一个动作\mathbf{a}_{t-1}并提供下一个观测\mathbf{o}_{t}
    2. INFERENCELOOP在后台线程中运行,以确保始终有可用的动作
      它通过保留过去延迟的缓冲区来预测下一个延迟d
      执行范围s 可以在不同分块之间变化;用户提供一个期望的最小范围s_{\min },对于给定分块的实际范围为\max \left(d, s_{\min }\right),其中d是在计算下一个分块时遇到的延迟
    3. 最后,算法在GUIDEDINFERENCE 中描述了带有软掩码的修复过程,该过程明确地定义了去噪函数——即上文提到的公式3
      \widehat{\mathbf{A}_{t}^{1}}=\mathbf{A}_{t}^{\tau}+(1-\tau) \mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)
      并计算向量-雅可比积,这可以通过反向模式自动微分实现[2]

    可以看到,Algorithm 1 (Lines 24-29)

    "for \tau=0 to 1 ... do" (循环开始)
            "Compute vector-Jacobian product ... via autodiff" (在循环内部计算导数/疯狂算数学)
            "Integration step ... + g" (把修正加进去)

    即模型每进行一步思考(Denoising Step),算法都会计算一次复杂的导数(向量-雅可比积),强行把模型生成的动作“头”按在“既定事实”上

    为了方便大家更好的理解,我还是把上述整套伪代码,改写成Python代码,并再给大家逐一分析下

    这个算法主要分为三个部分,分别对应代码中的三个方法:

    A. get_action: 与环境的实时交互

    这是前台线程,必须满足实时性要求(Real-time constraint)

    • 功能:接收最新的观测o_{cur},并从当前缓存的动作序列A_{cur}中“切”出下一步动作a_t发送给机器人

    • 关键点:它只负责读和取,不会被繁重的推理过程阻塞

    def get_action(self, o_next):
            """
            [Algorithm 1: GETACTION]
            由控制器以固定频率调用 (如 50Hz)
            """
            with self.mutex:
                # 1. 更新观测
                self.o_cur = o_next
                
                # 2. 通知后台线程观测已更新 (如果是新的一步)
                self.t += 1
                self.cond.notify()
                
                # 3. 返回当前时间步的动作
                # 注意:如果 t 超出了 H,通常需要处理异常,但 RTC 设计保证新块会及时替换
                if self.t > self.H:
                    current_action = self.A_cur[-1] # Fallback
                else:
                    current_action = self.A_cur[self.t - 1]
                    
            return current_action
    B. inference_loop: 后台异步推理

    这是后台线程,负责在机器人执行动作的同时,“预判”未来的动作。

    • 延迟估计 (d):代码维护一个队列 delay_queue,记录过去几次推理实际花费的时间步数。在开始新推理时,取最大值 max(Q) 作为保守估计

    • 快照 (A_{prev}):在开始推理的一瞬间,记录下当前正在执行的动作序列中剩余未执行的部分。新生成的动作序列必须在这个基础上“长”出来,不能发生突变

    def inference_loop(self):
            """
            [Algorithm 1: INFERENCELOOP]
            后台循环线程,负责生成新的动作块
            """
            while self.running:
                with self.cond:
                    # 1. 等待直到满足最小执行时间 s_min 
                    # 同时也确保有新的观测数据进来
                    while self.t < self.s_min:
                        self.cond.wait()
                    
                    # 2. 获取用于推理的快照数据
                    s = self.t  # 已执行的步数
                    # 提取上一块中剩余未执行的部分 A_prev (用于Inpainting)
                    # A_prev = A_cur[s : H]
                    if s < self.H:
                        A_prev = self.A_cur[s:].clone()
                    else:
                        A_prev = torch.empty(0) # 上一块已耗尽
                    
                    obs = self.o_cur
                    
                    # 3. 估计延迟 d 
                    d_est = max(self.delay_queue)
                
                # --- 释放锁进行繁重的推理计算 (With M released) ---
                
                start_time = time.time()
                
                # 4. 执行引导式推理 (核心算法),下文有单独实现
                A_new = self.guided_inference(obs, A_prev, d_est, s)
                
                inference_time = time.time() - start_time
                
                # --- 重新获取锁更新状态 ---
                with self.mutex:
                    # 5. 只要新块可用,立即交换 (Swap)
                    self.A_cur = A_new
                    
                    # 6. 重置 t,使其指向新块的正确位置
                    # 新块生成花费了实际延迟时间,因此我们需要跳过已经过去的时间步
                    # 实际延迟计算逻辑可能需要根据具体的 system clock 调整,这里简化为 s
                    # 论文伪代码写的是 t = t - s,意味着新块从索引 t-s 开始用 (通常是0附近,取决于对齐)
                    self.t = self.t - s 
                    
                    # 7. 记录实际延迟 (转换为时间步)
                    # 假设 control_dt 是控制周期,例如 0.02s
                    control_dt = 0.02 
                    actual_delay_steps = int(inference_time / control_dt)
                    self.delay_queue.append(actual_delay_steps)
    C. guided_inference: 引导式 Inpainting (数学核心)

    这是论文最核心的贡献,位于代码的 guided_inference 函数中。它利用 Flow Matching (或者 Diffusion) 的迭代去噪过程进行动作生成

    1. Soft Masking (软掩码W):

      • 为了解决拼接处的跳变(Jerky movements),算法基于公式5 定义了一个权重矩阵W
        \mathbf{W}_{i}=\left\{\begin{array}{ll} 1 & \text { if } i<d \\ c_{i} \frac{e^{c_{i}}-1}{e-1} & \text { if } d \leq i<H-s \\ 0 & \text { if } i \geq H-s \end{array} \quad \text { where } \quad c_{i}=\frac{H-s-i}{H-s-d+1}, \quad i \in\{0, \ldots, H-1\}\right.

      • i < d(推理延迟期):W=1
        这部分时间在推理结束时已经过去了,动作必须完全等于A_{prev}中已规划的值(因为它们将被实际执行)

      • d \le i < H-s(过渡期): W指数衰减
        这允许新生成的动作平滑地偏离旧计划,去响应新的观测 o_{cur}

      • i \ge H-s(新未来): W=0,完全由新观测决定

        def guided_inference(self, obs, A_prev, d, s):
                """
                [Algorithm 1: GUIDEDINFERENCE]
                基于流匹配 (Flow Matching) 和 Inpainting 的生成过程
                """
                H = self.H
                action_dim = self.A_cur.shape[1]
                
                # 1. 计算 Soft Masking 权重 W [Eq. 5] [cite: 162]
                # W 决定了我们多大程度上通过 A_prev 来约束新动作
                W = torch.zeros(H, action_dim)
                
                # 重叠区域长度
                overlap_len = A_prev.shape[0] 
                
                # 填充 A_prev 到长度 H (右侧补零,虽然补零部分权重为0不影响)
                A_prev_padded = torch.zeros(H, action_dim)
                if overlap_len > 0:
                    A_prev_padded[:overlap_len] = A_prev
        
                for i in range(H):
                    if i < d:
                        # 冻结区域:必须完全匹配上一块,因为这些时刻在推理完成前就会被执行
                        W[i] = 1.0
                    elif d <= i < H - s:
                        # 中间过渡区域:指数衰减权重,允许平滑修正
                        # c_1 是衰减系数
                        c_1 = (H - s - i) / (H - s - d + 1 + 1e-6) 
                        W[i] = c_1
                    else:
                        # 自由生成区域:新产生的未来动作,权重为 0
                        W[i] = 0.0
    2. Guidance Gradient (引导梯度 g ):先后基于公式3、2、1

      • 在去噪的每一步,计算当前(最新)生成的估计值\hat{A}^1_t——如上文提到过的,\widehat{\mathbf{A}_{t}^{1}}是最终完全去噪动作片段的估计,与上一块A_{prev}的差异
        背后本质是这两\hat{A}^1_tA_{prev}的重叠区域 得是非常像的

      • 利用 PyTorch 的 autograd 计算这个差异相对于输入噪声A^\tau的梯度。这告诉模型:“请在这个方向上修改噪声,使得生成的动作更像上一块的延续”
        ————
        接下来,我们具体逐一的实现
        首先,实现公式3
        \widehat{\mathbf{A}_{t}^{1}}=\mathbf{A}_{t}^{\tau}+(1-\tau) \mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)

                # 2. 初始化噪声 A^0 ~ N(0, I)
                A_tau = torch.randn(H, action_dim)
                
                # 3. 迭代去噪循环 (Euler Integration) 
                dt = 1.0 / self.n
                
                for k in range(self.n):
                    tau = k / self.n # 当前时间步 [0, 1]
                    t_tensor = torch.tensor(tau).float()
                    
                    # 开启梯度计算,因为我们需要对 input 求导来计算 Guidance
                    A_tau.requires_grad_(True)
                    
                    # 预测速度场 v
                    v_pred = self.model(A_tau, obs, t_tensor)
                    
                    # --- 计算 Guidance (Inpainting) ---
                    
                    # 估计最终生成的动作 A_hat^1 [Eq. 3]
                    # A_hat^1 = A_tau + (1 - tau) * v_pred
                    A_hat_1 = A_tau + (1 - tau) * v_pred

        其次,是围绕公式2的实现
        而从上可知,公式2如下所示——其背后本质就是要平滑去噪,咋做到呢?把神经网络原始的“意图” v_{pred} 和我们想强制的“平滑约束” g 加在一起
        \mathbf{v}_{\Pi \mathrm{GDM}}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)=\mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)+\min \left(\beta, \frac{1-\tau}{\tau \cdot r_{\tau}^{2}}\right)\left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \mathbf{A}_{t}^{\tau}}
        ————
        对此,我们把其中的下面这部分定义为引导项g——它将引导新块的生成 本质实则就是引导去噪了,然后将其一拆为二
        g = \left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \mathbf{A}_{t}^{\tau}}
        \rightarrow  第一步,先算加权误差项
        e=\left(Y-\hat{A}_{t}^{1}\right)^{\top} \operatorname{diag}(W)
        这里的Y是目标——即上一块的动作A_{\text {prev }}相当于计算当前(最新)生成的估计值\hat{A}^1_t——如上文提到过的,\widehat{\mathbf{A}_{t}^{1}}是最终完全去噪动作片段的估计,与上一块A_{prev}的差异

                    # 计算加权误差项 e
                    # e = (A_prev - A_hat_1) * W
                    # 注意:这里我们只关心重叠部分的误差
                    error = (A_prev_padded - A_hat_1) * W

        \rightarrow  第二步,再算Vector-Jacobian Product
        g=e \cdot \frac{\partial \hat{A}_{t}^{1}}{\partial A_{t}^{\tau}}

                    # 计算 Guidance 梯度 g [Eq. 2 & Algorithm 1 line 27]
                    # g = e * d(A_hat_1)/d(A_tau)
                    # 这本质上是 error 对 A_tau 的梯度
                    # 我们可以通过反向传播计算这个向量-雅可比积
                    
                    # 这里的 Loss 本质上是 || (A_prev - A_hat_1) * sqrt(W) ||^2 的导数相关项
                    # 简化的实现方式是直接对 error 求和后 backward
                    # 但论文公式 explicit 写的是 Vector-Jacobian Product
                    
                    # 技巧:由于我们需要 grad(A_hat_1, A_tau) * error
                    # 这等价于 torch.autograd.grad(A_hat_1, A_tau, grad_outputs=error)
                    g = torch.autograd.grad(outputs=A_hat_1, inputs=A_tau, 
                                            grad_outputs=error, retain_graph=False)[0]

        ————
        再补充解释下,在数学上,需要计算“最终估计值与旧动作的差距”相对于“当前输入噪声”的梯度。在 PyTorch 中,torch.autograd.grad 的 grad_outputs 参数正是用来实现 v^T J(向量-雅可比积) 的
        这里的 error 就是那个向量v ,而 A_hat_1 对 A_tau 的导数就是雅可比矩阵 J

    3. Integration (更新):最后把公式2代入进公式1中

      • 将模型预测的速度场v_\pi加上引导梯度 g(受\beta裁剪限制)
        ————
        接下来,再看具体的实现
        首先,在上面,我们已经实现了公式2
        \mathbf{v}_{\Pi \mathrm{GDM}}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)=\mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)+\min \left(\beta, \frac{1-\tau}{\tau \cdot r_{\tau}^{2}}\right)\left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \mathbf{A}_{t}^{\tau}}
        的后半部分——即引导项g
        g = \left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \mathbf{A}_{t}^{\tau}}

        其次,为了防止在\tau=0 附近梯度爆炸,论文引入了裁剪
        \text { weight }=\min \left(\beta, \frac{1-\tau}{\tau \cdot r_{\tau}^{2}}\right)
        而其中的r_{\tau}^{2}是怎么计算的呢,好办,根据公式4,可得
        r_{\tau}^{2}=\frac{(1-\tau)^{2}}{\tau^{2}+(1-\tau)^{2}}

                    # 计算 Guidance 权重限制 
                    # term = min(beta, (1-tau)/(tau * r_tau^2))
                    # r_tau^2 定义见 Eq. 4: (1-tau)^2 / (tau^2 + (1-tau)^2)
                    if tau == 0:
                        weight_clip = self.beta # 避免除以0,第一步直接用 beta
                    else:
                        r_tau_sq = ((1-tau)**2) / (tau**2 + (1-tau)**2)
                        val = (1 - tau) / (tau * r_tau_sq)
                        weight_clip = min(self.beta, val)
      •  动作更新步 (Integration Step) - Equation 1
        根据论文公式 (Eq. 1)
        \mathbf{A}_{t}^{\tau+\frac{1}{n}}=\mathbf{A}_{t}^{\tau}+\frac{1}{n} \mathbf{v}_{\pi}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)

        做标准的欧拉积分更新,但加入了引导项
        A_{t}^{\tau+\frac{1}{n}}=A_{t}^{\tau}+\frac{1}{n}\left(v_{\pi}(\ldots)+\text { weight } \cdot g\right)

                    # 4. 更新步 (Integration Step) [Eq. 1 & Algorithm 1 line 28]
                    # A^{tau+1/n} = A^tau + 1/n * (v + weight * g)
        
        
                    # dt 对应 1/n
                    dt = 1.0 / self.n            
        
                    with torch.no_grad():
                        updated_v = v_pred + weight_clip * g
                        A_tau = A_tau + dt * updated_v
                        A_tau = A_tau.detach() #以此作为下一步的输入,切断图
                
                return A_tau

        注意,如上述代码中的这一行:updated_v = v_pred + weight_clip * g,所述,先通过公式2
        \mathbf{v}_{\Pi \mathrm{GDM}}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)=\mathbf{v}\left(\mathbf{A}_{t}^{\tau}, \mathbf{o}_{t}, \tau\right)+\min \left(\beta, \frac{1-\tau}{\tau \cdot r_{\tau}^{2}}\right)\left(\mathbf{Y}-\widehat{\mathbf{A}_{t}^{1}}\right)^{\top} \operatorname{diag}(\mathbf{W}) \frac{\partial \widehat{\mathbf{A}_{t}^{1}}}{\partial \mathbf{A}_{t}^{\tau}}
        即先做了下面这个操作

        其中引导项 weight \times g 是一个“修正力”,用来把动作拉向上一块动作A_{prev}的轨迹,以保证平滑连接,更新后的速度 (updated_v):这就是公式2 的计算结果
        相当于,在这一步去噪中,不使用模型原始输出的速度场v,而是使用一个基于公式 2 修改后的合成速度场\Pi GDM来推动 A_\tau的更新。这确保了生成的动作既符合模型的分布v_{pred},又符合平滑过渡的约束 g

        再之后,执行公式1

    // 待更

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐