RTC(Real-time chunking)与 Temporal Ensemble:从概念到本仓库实现
本文面向 完全不了解 RTC 的读者,用本仓库(sc)里的实现作为 唯一具体例子,说明:
- RTC / real-time chunking 在机器人策略里通常解决什么问题;
- temporal ensemble(时间集成) 是什么、数学上在做什么;
infer_next.py里代码如何对应上述概念;- 你若要 自己改算法或换一套 RTC,应动哪里、注意什么。
说明:业界对 RTC 没有唯一标准定义。本仓库实现 是推理期的 temporal ensemble(在绝对位姿空间对重叠段做凸组合 + 重链) ,与 π 团队论文中的 推理时 RTC(基于流匹配/扩散的 inpainting + 软掩码) 目标相近(消除 chunk 接缝、支持异步推理),但 数学机制不同------详见下文「与 Physical Intelligence 论文的对照」。
与 Physical Intelligence(π₀ 团队)的 Real-Time Chunking:论文、公式与本仓库差异
Physical Intelligence (常被写作 π、PI;策略族常称 π₀ )围绕 action chunking 流策略 提出了系统的 Real-Time Chunking(RTC) 框架。下面按 读论文 → 记公式 → 对照本仓库 的顺序写,便于零基础读者建立「工业界标准 RTC」与「本仓库 patch」的边界。
核心文献(建议按此顺序读)
| 文献 | arXiv / 说明 | 你在文里会学到什么 |
|---|---|---|
| π₀:VLA 流模型基座 | 2410.24164 --- π₀: A Vision-Language-Action Flow Model for General Robot Control(Black et al., 2024) | 动作为何用 flow matching 建策略;chunk 化输出的基本设定。 |
| 推理时 RTC(原始定义) | 2506.07339 --- Real-time execution of action chunking flow policies(Black, Galliker, Levine, 2025) | 异步 生成下一 chunk、冻结 已提交动作、对其余时间步 inpainting ;与同步基线对比。官方 PDF 亦见 physicalintelligence.company 下载页。 |
| 训练时替代推理 inpainting | 2512.05964 --- Training-Time Action Conditioning for Efficient Real-Time Chunking(Black, Ren, Equi, Levine, 2025) | 用 训练期 对 action prefix 的条件化,省去 推理期 pseudoinverse guidance 的额外算力与 VJP;与推理时 RTC 接口对齐、可作为高效替代。 |
后文把 [2506.07339] 记作 RTC-Inf ,[2512.05964] 记作 RTC-Train。
记号:action chunk、预测视界与执行视界
与 RTC-Train 论文 Preliminaries 一致,策略写为条件分布
p ( A t ∣ o t ) , p(\mathbf{A}_t \mid \mathbf{o}_t), p(At∣ot),
其中 观测 为 o t \mathbf{o}_t ot,动作块(chunk) 为
A t = [ a t , a t + 1 , ... , a t + H − 1 ] ∈ R H × d a . \mathbf{A}t = [\mathbf{a}t,\, \mathbf{a}{t+1},\, \ldots,\, \mathbf{a}{t+H-1}] \in \mathbb{R}^{H \times d_a}. At=[at,at+1,...,at+H−1]∈RH×da.
- H H H:预测视界(prediction horizon),一次网络前向规划的未来步数。
- 执行视界 s s s :每个 chunk 实际 只执行前 s s s 步再重算( s ≤ H s \le H s≤H)。若 s = H s=H s=H 则退化为「块内执行完再规划」的同步极端。
这种「一次预测多步、只执行前缀」的结构,就是 action chunking;π₀ 系 VLA 广泛采用。
推理延迟 d d d 与 action prefix(图上红色段)
设 模型推理延迟 为 d d d 个 控制周期 (单位与 a t \mathbf{a}_t at 的离散时间一致)。若在时刻 t t t 启动推理,则 直到 t + d t+d t+d 才能得到新 chunk。
在这 d d d 步里机器人不能「空白」,因此在 流水线式 控制中,会继续执行 上一轮 chunk 里已经算好的动作------这些与当前正在生成的 chunk 在时间上重叠的前 d d d 步,RTC 把它们称为 action prefix :即 上一轮已提交、本轮仍必须相容 的动作前缀。
RTC-Train 图 1 配文给出同一时间轴约束:
d ≤ H − s d \;\le\; H - s d≤H−s
(直觉:上一轮在重叠区至少要能覆盖 d d d 步 prefix,否则缓冲区不够。)
Flow matching:π₀ / RTC 家族的生成式训练目标(式 (1)(2))
RTC-Train 沿用 conditional flow matching 的标准构造。给定 干净动作块 A t \mathbf{A}_t At 与 高斯噪声 ϵ ∼ N ( 0 , I ) \boldsymbol{\epsilon} \sim \mathcal{N}(\mathbf{0}, \mathbf{I}) ϵ∼N(0,I),对插值系数 τ ∈ [ 0 , 1 ] \tau \in [0,1] τ∈[0,1]:
A t τ = τ A t + ( 1 − τ ) ϵ . (1) \mathbf{A}_t^{\tau} \;=\; \tau \,\mathbf{A}_t + (1-\tau)\,\boldsymbol{\epsilon}. \tag{1} Atτ=τAt+(1−τ)ϵ.(1)
网络 v θ \mathbf{v}_\theta vθ 在给定观测 o t \mathbf{o}_t ot 与 τ \tau τ 下预测速度场,极小化期望 L 2 L_2 L2 误差:
L ( θ ) = E ∥ v θ ( A t τ , o t , τ ) − ( ϵ − A t ) ∥ 2 . (2) \mathcal{L}(\theta) \;=\; \mathbb{E}\left\|\mathbf{v}_\theta(\mathbf{A}_t^{\tau}, \mathbf{o}_t, \tau) - (\boldsymbol{\epsilon} - \mathbf{A}_t)\right\|^2. \tag{2} L(θ)=E∥vθ(Atτ,ot,τ)−(ϵ−At)∥2.(2)
推理时从 τ = 0 \tau{=}0 τ=0 积分到 1 1 1 即得到 采样到的动作 chunk 。RTC 不改变式 (2) 的监督形式本身 ,而是在 采样 / 约束 上动手脚,使相邻 chunk 在时间上连续。
RTC-Inf:推理时怎样做「冻结 + inpainting」与 soft masking
直观说法:
- 异步 :在机器人执行当前 chunk 尚未结束时,已开始算下一 chunk(否则长推理会拖垮实时性)。
- 硬约束 :对 已确定要执行的 prefix ,生成过程 不改变 (论文称 frozen prefix)。
- Inpainting :对 chunk 里 剩余时间步 ,在扩散/流匹配的迭代中 只对未冻结位置更新 ,等价于有条件生成 postfix。技术上 RTC-Inf 使用基于 pseudoinverse guidance(参见 RTC-Inf 引用中的 Song et al.; Pokle et al.)的推理期引导。
- Soft masking(软掩码) :对 超出硬 prefix、但仍处于两 chunk 重叠区 的动作,再给模型 附加 一种随步数衰减的权重信息(示意图中的 黄色 区),减轻仅靠硬 prefix 时更长重叠窗上的不连续。这是 生成过程内部 的事,不是在笛卡尔末端空间里简单线性插值。
代价 :pseudoinverse guidance 往往涉及 向量--雅可比乘积(VJP) 或额外反传,增大单次去噪步的延迟------这也正是 RTC-Train 试图用「训练期 prefix 条件化」去掉的开销来源。
RTC-Train:把「prefix 已知」内化到训练分布里
RTC-Train 提出直接学习 后段条件分布
p ( A t + d : H ∣ o t , A t : t + d ) , p(\mathbf{A}_{t+d:H} \mid \mathbf{o}t,\, \mathbf{A}{t:t+d}), p(At+d:H∣ot,At:t+d),
即在训练时随机采样延迟 d d d,把 A t : t + d \mathbf{A}_{t:t+d} At:t+d(与推理时一致的 action prefix )以 洁净、非噪声 形式送入网络对应 token;对 prefix 位置的 flow time 设为 1 1 1 (已落到数据侧),仅对 postfix 计算 flow matching loss。采样接口与 RTC-Inf 一致 ,可当 更高效、少反传的 drop-in 替代(实现细节按其 Algorithm 1)。
对照表:PI 论文中的 RTC vs 本仓库 infer_next.py
| 维度 | PI:RTC-Inf / RTC-Train | 本仓库 (apply_temporal_ensemble) |
|---|---|---|
| 作用阶段 | 生成模型内部:约束流匹配/采样轨迹 | 生成之后 :已对 离散 chunk 算出增量,再在 末端 7 维(位置 + 旋转四元数 + 夹爪)绝对域 上融合 |
| 连续手段 | inpainting、pseudoinverse guidance、soft masking ;RTC-Train 用 前缀条件分布 | 重叠下标上对 两条绝对轨迹 mix_pose7(Lerp + 旋转 Spherical Linear Interpolation) ,再 transform 差分链重算 |
| 是否与训练耦合 | RTC-Train 需要改训练 ;RTC-Inf 无需重训但推理更重 | 无需重训;语义上同属「推理侧修补」,但不是 PI 的同一种数学对象 |
| 延迟语义 | 显式建模 d d d 、( H , s ) (H,s) (H,s) | MAX_LATENCY、ind+latency 等工程量;若在论文中对齐 RTC,可自行映射 d , H , s d,H,s d,H,s |
| 可读 PI 来补什么 | chunk + 异步 + prefix 的 正式问题建模 ;与 VLA flow 对齐的 式 (1)(2) | 没有改扩散内核时,如何用 轻量化几何后处理 缓冲突变 |
一句话 :PI 论文教的是 流策略 chunk 在时间轴上的条件采样该怎么做 ;本仓库教的是 若只能动输出后处理,如何把接缝在笛卡尔空间里揉软 。若将来把 PI 式 inpainting 接进采样环,应避免与本文的 mix_pose7 ensemble 叠在同一段重叠上做双重平滑。
1. 先建立直觉:没有 RTC 时会发生什么
1.1 在线策略的常见形态
机器人每隔一小段时间就 重新算一遍「未来一小段轨迹」 (一个 chunk ),然后从 chunk 里 一步一步执行。算得快时,队列里会同时存在:
- 还没执行完 的「旧计划」轨迹;
- 新一轮推理出来的「新计划」轨迹。
若实现上 直接丢弃旧计划、整段换成新计划 ,在 重规划边界 上很容易出现:
- 末端位姿 突然跳变(上一拍的预测下一步 vs 这一拍预测的第一步不一致);
- 速度/加速度 瞬时变大,看起来像抖动。
这不是模型「错」,而是 两次独立优化的解在时间上不连续------类似你用两条不同曲线去拼一条路径,接缝没对齐。
1.2 RTC / temporal ensemble 在干什么(一句话)
在 新旧两条轨迹都覆盖到的「重叠时间段」 里,不要只吃新的或只吃旧的,而是做一个 加权混合 ,让接缝 变软 ;对 没有重叠的后半段 ,再用 一致的微分规则 把轨迹 接长,避免「只改了前几步、后面又乱套」。
本仓库用的就是这一种思想,实现上叫 apply_temporal_ensemble。
2. 本仓库里的三个「必备名词」
2.1 Action chunk(动作块)
扩散头一次前向会得到 多步未来动作 ,长度与 INFER_CHUNK_SIZE 等有关(实现里常见为 INFER_CHUNK_SIZE + 1 步)。
每一步在内部先表示为 相对当前系的增量 ,再通过 transform 变成 绝对末端位姿(7 维:xyz + 欧拉 xyz + 夹爪)。
2.2 transform:把「模型的一行输出」变成「绝对位姿」
对每个控制周期,模型给出一条向量(至少 7 维有效),transform(当前绝对位姿, 增量) 用 齐次变换 做右乘,得到 下一步绝对位姿 。
多步时:第 i 步依赖第 i-1 步的绝对位姿,形成一条链------这是后面「重链」的基础。
2.3 action_queue:还没执行的绝对位姿序列
SYNC_MODEL 把每一步可执行的绝对位姿放进队列;重规划前会 截断队列 到「当前已决定保留的前缀」,被截掉但 尚未执行 的那一段尾巴,在本文里记为 old_suffix(旧后缀)。
3. 算法总流程(与代码一一对应)
3.1 一次重规划时,代码在做什么
用 infer_next.py 中 SYNC_MODEL.forward 的逻辑描述(概念步骤):
- 快照旧队列 :在截断前复制
old_q = list(action_queue)。 - 截断队列 :只保留当前决策需要的历史前缀
action_queue = action_queue[:ind+latency+1]。 - 模型推理 :
action_diff = super().forward(...),得到 多行 delta(每行对应一步)。 - 复制 delta :
deltas[i]= 第 i 步的模型输出(变换前)。 - 若不开 RTC :用
_plain_chain------完全信任新 plan,从curr_states[-1]起逐步transform,得到新绝对轨迹,写入队列。 - 若开 RTC 且存在可融合的旧尾巴:
old_suffix = old_q[ind+latency+1:]- 调用
apply_temporal_ensemble(curr, deltas, old_suffix, transform, ...)
- 把融合后的绝对轨迹 逐行 append 回
action_queue。
3.2 核心:apply_temporal_ensemble 分两段
段 A --- 先算一条「纯新 plan」的绝对链
对应函数 build_absolute_chain:
- 输入:当前末端状态
curr_state、每步模型增量deltas[0..N]、transform_fn。 - 输出:
chain[k]= 完全由 本轮模型 推出来的绝对位姿序列。
这是你 不融合 时本来就会得到的结果。
段 B --- 在重叠下标上做混合,再「重接」后面
设重叠长度为 ovl = min(len(old_suffix), len(chain), overlap_cap)。
-
对每个
k ∈ [0, ovl-1]:
out [ k ] = mix_pose7 ( old_suffix [ k ] , chain [ k ] , α k ) \text{out}[k] = \text{mix\_pose7}(\text{old\_suffix}[k],\ \text{chain}[k],\ \alpha_k) out[k]=mix_pose7(old_suffix[k], chain[k], αk)其中
α k = c l i p ( α ⋅ ( 1 − decay ) k , 0 , 1 ) \alpha_k = \mathrm{clip}\Big(\alpha \cdot (1-\text{decay})^k,\ 0,\ 1\Big) αk=clip(α⋅(1−decay)k, 0, 1)- α \alpha α:总体更信 新计划 还是 旧计划 (
RTC_ALPHA); decay:沿重叠索引 k k k 让 α k \alpha_k αk 变化(RTC_ALPHA_DECAY);overlap_cap:最多融合多少步(RTC_OVERLAP_CAP)。
- α \alpha α:总体更信 新计划 还是 旧计划 (
-
对
k ∈ [ovl, N]: 不能再用旧的绝对位姿 ,否则与模型增量不一致。做法是 用融合后的最后一步out[ovl-1]作为起点,仍用模型给的deltas[k]继续transform:
out [ k ] = transform ( out [ k − 1 ] , deltas [ k ] ) \text{out}[k] = \text{transform}(\text{out}[k-1],\ \text{deltas}[k]) out[k]=transform(out[k−1], deltas[k])这保证了:重叠区 是 old/new 的折中;非重叠新区 仍与 模型输出的相对增量 一致,整条链在几何上是 同一条微分动力学下 的延展。
4. mix_pose7:为什么在 7 维上要「分段」插值
末端位姿用 7 个数:平移 3、欧拉角 3、夹爪 1。
- 平移、夹爪 :用 线性插值(Lerp)即可,物理意义是「在空间上走直线 / 夹爪慢慢合拢」。
- 姿态(欧拉) :两个欧拉角三元组 不能直接分量线性插值(不保证旋转群的测地线,且有大角与万向锁问题)。
实现里把两端欧拉转成 scipy.spatial.transform.Rotation ,在 球面上 做 Slerp(球面线性插值) ,再转回欧拉,用于显示与后续 transform 接口一致。
函数 mix_pose7(a, b, alpha):
- t = α t=\alpha t=α 时,结果在 a(偏旧) 与 b(偏新) 之间;
alpha=1完全等于新 plan 该步;alpha=0完全等于旧 plan 该步。
5. 超参数怎么理解(面向调参与改算法)
| 符号 / 变量 | 代码中 | 含义 |
|---|---|---|
| α \alpha α | RTC_ALPHA |
越大越 跟本次新推理 ;越小越 黏在旧队列 上。 |
decay |
RTC_ALPHA_DECAY |
在重叠步索引 k k k 上改变 α k \alpha_k αk。>0 时通常让 越往后的重叠步 越偏向某一侧(取决于实现公式,本仓库是每步乘 (1-decay)^k)。 |
overlap_cap |
RTC_OVERLAP_CAP |
只融合前若干步重叠,防止融合段过长导致 反应迟钝。 |
| 总开关 | RTC_ENABLED |
为 False 时不进 ensemble,等价于永远 _plain_chain。 |
设计直觉:
- 场景 动态快 (要跟新手): α \alpha α 略大;
- 场景 噪声大、模型抖 : α \alpha α 略小,或加一个 低通滤波(本仓库未内建,可在控制层做);
- 若发现 远处轨迹仍跳 :有时是
decay与cap的配合问题,而不是单纯增大 α \alpha α。
6. 与「训练新模型」的关系
本仓库的 RTC 不修改网络权重 ,不写新 loss,纯属推理期对队列输出的处理。
若你希望从根上让 跨步一致性 更好,需要 数据、损失或架构 上的多步一致性约束------那是 另一条研发线 ;RTC 是 廉价、可调、可关 的推理期补丁。
7. 实现上常见坑(开发自己的 RTC 时必看)
7.1 只融合前几步,后面忘了用 transform 续链
若把重叠段的绝对位姿做了混合,但后续步仍按 未混合的绝对中间态 去算,会破坏 与模型 delta 的一致性 。
本仓库的做法 :重叠段用 mix_pose7,从第一项 超出重叠 的下标起,强制 transform(out[k-1], deltas[k])。
7.2 欧拉角插值
见第 4 节:分量 lerp 欧拉 不推荐 作为旋转主方案;至少用 Rotation + Slerp 或改用 四元数 / 轴角。
7.3 模型输出维度
若模型一行是 9 维 而 transform 只吃 7 维 ,必须先规范到 7 维(本仓库 transform 使用 reshape(-1)[:7])。否则欧拉切片会错位。
7.4 队列语义与 old_suffix 对齐
old_suffix 必须与 截断前 队列里「被扔掉的那一段」在 时间顺序上 一一对齐;infer_next.py 用 old_q[ind+latency+1:] 与 snap[latency:](V1)等写法是为对齐 同一语义时刻 。改队列规则时,这里最容易出现 off-by-one 或 新旧时间轴错位。
8. 若想「自己开发一套 RTC 变体」,建议路线
- 先做离线可视化 :同一组
(old_suffix, new_chain),画末端位置/姿态随步数曲线,对比 Lerp-only、Slerp-only、不同 α \alpha α。 - 先保持
build_absolute_chain不变 ,只替换mix_pose7(例如换成 SE(3) 上的一步插值、或对位置加限速)。 - 再考虑改
apply_temporal_ensemble的重链策略 (例如重叠区融合后对deltas做微调------难度更大,易与训练分布不一致)。 - 最后在真机或仿真闭环 看任务成功率,而不仅看「曲线更滑」。
9. 本仓库代码索引(便于对着读)
| 内容 | 位置(infer_next.py) |
|---|---|
| 末端增量 → 绝对位姿 | transform(...) |
| 单步位姿混合 | mix_pose7 |
| 新 plan 绝对链 | build_absolute_chain |
| 融合 + 重链 | apply_temporal_ensemble |
| 队列上启用 RTC | SYNC_MODEL.forward、SYNC_MODEL_V1.forward / forward_monitor |
| 开关与超参 | RTC_ENABLED、RTC_ALPHA、RTC_ALPHA_DECAY、RTC_OVERLAP_CAP |
与 运行参数、调参顺序 相关的简短说明另见:
deploy/RTC_TEMPO_ENSEMBLE_SOP.md。
10. 小结
- RTC(本文语义) = 在 实时、多段 chunk、带队列 的执行方式下,对 重叠时间范围 的轨迹做 融合 ,减轻 重规划接缝。
- 本仓库实现 = temporal ensemble (
mix_pose7)+ 差分一致性约束 (融合后对尾部transform重链)。 - 不需要会深度学习 也能改 RTC;需要清晰掌握 队列语义、7 维位姿含义、
transform链式规则。
若你只记一张图:
text
模型 → deltas → [可选] 与 old_suffix 在重叠步上 mix → 后续步仍以 deltas transform 续链 → action_queue
把这张图里的 [可选] 换成你自己的融合规则,就是在开发 你自己的 RTC 变体。