这几年 Agentic Robotics 的话题热度一直很高,但真正落到真机上的时候,大家会发现最卡人的往往不是某个模型能力不够,而是“决策节奏”不对。ChunkAct 是我一直在跟进和调优的一个动作生成框架,它的核心就是把 Action Chunking 这套思想用到自主机器人任务上:网络一次前向推理,输出未来 N 步的动作序列,而不是只输出当前一步的关节变化。所谓 agentic,我理解是机器人能在非结构化环境里自发完成多步操作,比如理货、摆盘、拧瓶盖、倒液体;这些任务的共同点是决策频率远低于控制频率。人不会在端起水杯的每一毫米都重新想一次怎么端,机器人的高层策略也不应该这么做。这篇文章面向正在做机械臂操作、模仿学习或者具身智能的同学,我会把 ChunkAct 的原理、模型结构选型、工程部署细节和我在实验里看到的真实差距一次说明白。
1. 为什么“一步一推理”的策略在真实任务里越来越难用
先说个反直觉的现象:很多端到端策略在仿真里跑得还行,仿真环境里传感器干净、动力学稳定、渲染和真机时钟接近,模型每帧输入图像、每帧输出一个动作,看起来逻辑闭环没有问题。可一旦把同一套模型部署到真实机械臂上,机器人经常表现出三种让人头疼的症状:动作抖得像帕金森、任务中途越跑越偏、偶尔还会在同一个位置来回试探很久。
这不是调参运气不好,而是“一步一推理”这种策略范式本身带来的结构性问题。
1.1 延迟不只是慢,还会改变决策性质
机器人的底层控制回路通常跑在 1kHz 左右,而基于视觉的端到端策略哪怕优化得很好,推理频率也就在 20 到 60Hz 之间。如果模型只在每个控制周期里输出一步动作,那推理延迟就变成了控制回路里的固定相位滞后。你在仿真里把延迟设成 0 测出来的稳定性,到了真机上会被 30 甚至 50ms 的延迟完全改写。
我们在模拟项目X里做过一个简单的目标跟踪实验:同一个策略,推理延迟从 10ms 增加到 40ms,末端抖动幅度大概翻了两倍多。原因不难理解——策略看到的画面已经是过去的状态,输出的动作却要作用于当前乃至未来的状态,一步推理的模式下,这个滞后没有任何缓冲机制,只能靠控制器硬扛。而动作分块相当于给这个滞后加了一层“惯性”,模型在预测未来 H 步动作时,天然把自身延迟作为轨迹设计的一部分考虑进去了,后文的 ChunkAct 设计里我会详细展开这一点。
1.2 误差累积:一个微小偏差如何被逐帧放大
另一个麻烦在于闭环反馈变成了闭环“追错”。单步策略每帧吃进包含当前执行误差的观测,然后输出一个动作修正,理论上这没问题——有误差反馈回来不是好事吗?
问题在于,单步策略的预测误差是逐帧叠加的。假设模型每一步平均有 2% 的预测噪声,单看一步没什么感觉,但执行 100 步以后,这些误差不再服从独立均匀分布,而是会被策略自身的闭环放大。尤其当任务包含长期规划成分时,比如先绕过障碍再抓取,模型每一步只看到局部,没有对整段运动的约束,最后轨迹会漂移到完全偏离任务目标的位置。
这个现象在训练里还有一个隐藏推手:模仿学习的训练通常用 teacher forcing,也就是每一步都拿真实状态作为输入来监督当前动作,模型在训练阶段根本没见过“自己执行错了之后的状态”。一步一推理的范式把这种训练-推理不一致放大到了每一个控制周期。动作分块至少把预测单位从 1 步变成 H 步,让模型被迫在一个更长的视界上保持轨迹的一致性,而不是把问题全丢给下一帧的自己。
1.3 人类抓东西并不是想好每一步才动手的
拿一个最简单的动作来说:伸手去拿桌上的杯子。人的运动皮层并不会在手臂移动的每一毫米都重新规划一次完全新的轨迹。实际发生的是,大脑先决定“我要把杯子拿过来”,然后生成一段连续的运动命令,执行过程中只在必要的时候做小幅修正。
机器人操作领域很久以前就有类似区分:高层任务规划负责“做什么、按什么顺序做”,低层伺服控制负责“怎么执行得稳”。而一步一推理的端到端策略,其实是被夹在中间的尴尬角色——它既没有像任务规划器那样理解长程目标,又没有像低层控制器那样利用高频反馈。ChunkAct 想填的正是这个空档:用动作分块模拟人脑中“打包好的运动意图”,让策略模型以动作块为单位思考,而不是以单个关节增量为单位思考。
我一开始也不完全相信这个思路能带来多大差别,直到我把同一个网络结构分别改成单步输出和 16 步分块输出,在真机上做重复实验,成功率从勉强能跑提升到了接近稳定可用。那之后我才开始认真抠动作分块背后的原理和工程细节。
2. ChunkAct 的核心语义:把“动作块”当作生成单元
很多第一次接触 ChunkAct 的人会以为,这不过就是把模型输出维度从 7 维关节角度改成 7×H 维矩阵。表面上确实是这样,但训练方式和推理方式必须联动改变,否则只是“一次性输出 H 个无关动作”而已,拿不到分块带来的收益。
2.1 动作分块的定义与三个基本参数
动作分块的正式说法很朴素:给定当前观测 o_t,模型输出的不再是一个动作 a_t,而是一段长度为 H 的未来动作序列 A_t = [a_t, a_{t+1}, ..., a_{t+H-1}]。这里 H 叫预测长度,是第一个要定的参数。
第二个参数是重规划间隔 K。意思是,模型输出这一段动作之后,是老老实实把这 H 步全部执行完,还是只执行 K 步就重新推理一次。我们最常用的配置是 K 取 H 的四分之一到二分之一,比如 H=32、K=8。这不是拍脑袋定的,后面我会说怎么调。
第三个参数是动作的表征粒度。分块里的每一步可以表示关节速度增量、关节绝对位置、末端位姿增量、夹爪开合目标等不同形式。我自己的经验是,增量形式在分块模型里表现最稳定。原因也很直觉:预测 H 步的加速度增量,比直接预测 H 步的绝对目标位置要容易得多,因为增量序列只需要保证渐变,不需要每一步都猜准全局坐标系下的绝对数值。
2.2 训练与推理方式的联动改变
如果训练数据还是按单帧随机采样,那模型学习到的仍然是“看到这个状态,输出下一个动作”的函数。分块训练要求从演示轨迹里切出完整片段,每段输入一段观测上下文,输出对应的整段动作序列作为监督。损失函数也要对整块动作做回归。
一个值得注意的细节是,观测上下文不一定只取当前一帧,我们通常把过去 T 帧的观测也拼进去。这个设计对分块执行非常重要:模型要生成未来 H 步动作,至少需要知道当前这段时间里物体是怎么运动的,否则它无法判断是继续执行还是已经偏离预期。
推理阶段的变化更加关键。单步模型每推一次就执行一步,相当于人每走一厘米就重新看一次路;ChunkAct 则是一次生成整段动作,然后按 K 的节奏决定要不要重新看路。这里有一个很容易踩的误区:重规划间隔 K 越小,策略越接近单步推理,动作分块的优势就越是体现不出来;K 越大,模型越要依赖自己对环境的预测能力,如果环境里有意外扰动,容错率会快速下降。所以 ChunkAct 不是一个“把 H 调大就完事”的方法,它是在预测能力和反馈敏捷度之间找一个平衡点。
2.3 分块为什么能缓解复合误差:一个简单的推导直觉
有人会问:既然动作分块也是让模型一次性生成 H 步,那这跟让单步模型连续跑 H 次有什么本质区别?
区别在误差传播结构上。单步模型跑 H 次,每一步都用上一步的预测误差作为输入,误差是一步叠一步传下去的,可以近似看作线性甚至更大的累积。动作分块生成 H 步,模型是从同一个观测出发、一次前向生成整块轨迹,它在生成时并没有把每一步的预测结果再次当作输入去重新推理。也就是说,块内部的误差是并行产生的,而不是串行累积的。
从控制频率的角度理解更清楚:把决策频率从每步一次降低到每块一次,相当于把误差累积窗口从整条轨迹缩小到单个块的长度。每执行完一块,重新推理一次,误差就有一次被“重置”的机会。我之前用一个很粗糙的估算验证过这个直觉:假设每一步的预测噪声是 ε,单步策略执行 T 步的轨迹偏差大约正比于 εT;而分块策略如果每块 H 步、总共执行 T/H 块,那么每块内部的偏差大约正比于 εH,整体偏差大约正比于 εH×(T/H) 还要加上块间修正带来的收敛项。仿真结果虽然不像这个估算那么干净,但量级方向是对的。
当然,这个直觉不能推导到极端——如果你把 H 调到几百步,模型大概率会在执行到一半时就发现环境跟预测对不上了。分块的价值是压缩决策频率,不是取消反馈。
3. 落地 ChunkAct 时,模型结构上最值得较真的几个选择
动作分块的思想本身跟具体网络结构没有绑定关系。你可以用 MLP、Transformer、扩散模型甚至循环网络来实现分块输出。但在真实项目里,结构选择直接影响分块效果的上限,而且每个选择背后都有必要的理由。
3.1 多模态观测的时间对齐:环境状态和本体状态不能一概而论
Agentic 机器人的输入通常包含三路:视觉观测、本体状态(关节角度、末端位姿、夹爪状态)、外加语言指令。很多实现把这三路特征简单拼接之后丢给动作头,分块模型跑一段时间就会出现动作漂移,排查到最后发现是时间对齐出了问题。
视觉信息来自相机,采样频率通常 30Hz,而本体状态可以从控制总线里高频读取,比如 200Hz。两者到达模型的时间戳天然不同步。一步一推理模式下,这个错位还不致命;但分块模型要依据这些观测去预测未来 H 步动作,对“当前时刻状态是谁”这件事极其敏感。我们的做法是给每个模态的特征序列打上相对时间编码,在做序列对齐时统一以“当前控制周期”为时间零点,视觉特征用其在过去时间窗内的帧序列表示,而不是用落后于控制周期的单帧。
语言指令的处理也容易走弯路。我见过有的实现把语言编码拼在每一帧的视觉特征后面,造成模型在生成动作块时反复“读到”同一个指令但不知道它是全局条件。更合理的做法是让语言指令作为全局条件,通过 cross-attention 注入骨干网络的每一层,这样动作头在生成块内不同时间步的动作时,都能稳定地参考任务目标。
3.2 动作头的选择:MLP、扩散头还是 Transformer 解码器
这是我在设计 ChunkAct 时纠结最久的地方。直接输出一个 H×动作维度矩阵,最简单的方式是 MLP 回归头;但动作分布往往不是单峰的,同一个开局可能对应多种成功轨迹,纯回归会把多峰分布平均成一条“平庸”路径。
下面这份对比表是我整理给自己团队看的,可以当作选型参考:
| 动作头类型 | 多峰建模 | 训练稳定性 | 推理延迟 | 分块长度上限 | 适合场景 |
|---|---|---|---|---|---|
| 回归 MLP 头 | 弱,趋向均值 | 高,收敛快 | 极低 | 中等,直接输出矩阵 | 确定性强的拧螺丝、固定轨迹装配 |
| 扩散头 | 强,能建模多峰 | 中,需要较多调参 | 偏高,需多次迭代去噪 | 高,整块生成友好 | 桌面整理、抓取姿态多样的任务 |
| Transformer 解码器 | 中,可做离散化处理 | 低至中,依赖掩码与训练技巧 | 中,自回归逐步解码 | 受步长限制,易二次误差累积 | 带子目标结构的复杂长程任务 |
我自己的结论是:如果你的任务中物体初始摆放是固定的,回归头完全够用;一旦任务里存在多种成功路径,比如抓取时可以左探也可以右探,建议直接用扩散头。ChunkAct 跟扩散模型是天然搭的,因为扩散模型的生成单位本身就可以是一整段序列,不需要像自回归解码器那样两步、三步地慢慢吐,正好避开了从“单步”结构里翻出来的老问题。
Transformer 解码器不是不能做动作分块,但你得小心一件事:如果解码器内部采用 step-by-step 的 token 生成方式,那它本质上又回到了自回归误差传播老路上,只是把“块”套在了外面。由于这个原因,我们没有把它作为默认动作头。
3.3 分块长度怎么定:固定窗口还是自适应窗口
固定 H 的做法简单,但真实机器人任务对分块长度的需求是不均匀的。一个插销插入任务,前半程要把销钉快速挪到孔附近,后半程需要毫米级精调。这时候统一的 H 会让模型很分裂:H 太小,前面路径规划走一步看一步;H 太大,后半程精调时又显得反应迟钝。
一个比较好用的折中是把“分块长度”设计成执行进度的函数。我们在 ChunkAct 里加了一个轻量的进度估计器,输入当前观测特征,输出一个 0 到 1 之间的任务完成度估计。任务刚开始时模型自动生成较大的块,比如 48 步;当进度估计器认为已经进入末段,就自动把块长缩到 12 到 16 步,同时提高重规划频率。
这个设计的直觉和人类操作非常像:粗活儿大刀阔斧,细活儿小心谨慎。从实现上看一点都不花哨,进度估计器只是一个几层 MLP 的小头,跟主策略联合训练就行。如果你不想额外加复杂度,至少可以把块长做成一个随阶段变化的配置,真机实验阶段用起来会灵活得多。
3.4 块与块的衔接:最容易翻车的边界设计
动作分块最隐蔽的坑不在块内部,而在块与块的边界上。假设模型在 t 时刻生成块 [a_t, ..., a_{t+31}],执行 8 步后在 t+8 重新推理,得到新块 [b_{t+8}, ..., b_{t+39}]。如果新块的第一步 b_{t+8} 和刚才已经执行到的动作 a_{t+8} 差距很大,机器人会瞬间出一个冲击性的修正,轻则抖动,重则碰倒物体。
解决边界突变有三种常用做法。第一种是重叠执行:重新推理时,新块的前若干步必须沿用旧块对应位置的动作,我们在训练数据里把这种重叠约束也加了进去,让网络知道“新块的前几步和旧块尾部要尽量一致”。第二种是边界插值:对新旧块重叠区做一个线性插值过渡,但这招对夹爪开合等离散动作不友好。第三种是损失函数加权:在训练时专门对每个块的头部几步加额外的平滑损失,让模型天然倾向于生成连续的块。
踩过这个坑以后我有个很深的体会:动作分块的收益很大一部分来自“整块生成的一致性”,而损失则很大一部分来自“块边界的突变”。所以工程调优时,边界衔接的优先级要排在损失函数之前。
4. 真实机器人上训练和部署 ChunkAct 的工程细节
模型结构和分块思路确定后,真正花时间的其实是数据、重规划节奏、损失函数和安全兜底这些工程细节。这些内容论文里通常只有一段话,但实操起来每个都能单独写一篇踩坑记录。
4.1 数据预处理:关节、末端位姿和夹爪放在一起时的归一化陷阱
动作分块模型的输入输出往往包含不同类型的动作维度:关节角度、末端位姿增量、夹爪宽度。把它们一起放进模型之前,归一化不是一个简单缩放到 [-1,1] 就完事的操作。
关节角度有周期性,比如 -π 和 π 在角度意义上是同一个值,但直接做数值回归会把它们当成最远的距离,模型会在边界上产生奇怪的大角度跳跃。处理方法是把角度拆成 sin 和 cos 两个分量,或者使用循环距离设计损失。末端位姿建议一律用相对当前时刻的增量表示,而不是绝对坐标,因为增量在数值范围上更稳定,模型也更容易学到“相对现在再动一点”的模式。夹爪动作如果是开关两类,可以直接输出连续值配合阈值判断;如果是连续夹持力,一定要单独归一化,不要跟关节角度共用一套统计量。
我们还犯过一个低级错误:把某条轨迹里末端速度的极值当成归一化上下限。这导致低速度区域被压缩到很小范围,模型输出分辨率不足,末端会一直小幅度抖动。后来改成用百分位数而不是极值来确定归一化范围,问题才解决。
4.2 重规划节奏:该开环执行多久,何时该让模型再看一眼
重规划间隔 K 是动作分块里最“玄学”的超参数。K 太小,分块退化成单步推理,前面说的误差重置优势就没了;K 太大,模型要纯靠开环执行很长一段,碰到意外只能认栽。
我们在模拟项目X上系统扫过这个参数。固定 H=32,逐次尝试 K=4、8、16、32,成功率呈现出明显的单峰形态,峰值在 K=8 附近。K=4 时成功率反而下降,因为每次重规划都会引入一点观测噪声和模型预测噪声,太频繁的重规划让策略变得“多疑”,物体稍微动一下它就改主意,轨迹来回摆动。K=32 时由于完全开环,遇到演示里没见过的干扰就彻底失控。
我后来总结出一个经验波形:K 取 H/4 到 H/2 之间是安全区。如果你的底层控制器质量特别好,可以适当加大 K;如果视觉延迟较大,则偏向更小的 K。还有一个小技巧是让 K 随置信度动态变化——模型对自己预测的信心低时,提前重规划;信心高时,多执行一会儿。这个逻辑和人的行为很接近:路况好就放心往前走,感觉不对就多看一眼。
4.3 损失函数的权重设计与过平滑问题
分块模型的损失函数最基础的形式是对块内每一步的预测动作求 L2 距离。但这个简单形式有两个问题。
第一个问题是过程中每步权重相等,但任务末端的精度要求其实更高。拧瓶盖任务里,前 20 步的轨迹偏差只要不是太离谱都能接受,最后 5 步如果偏了 2 毫米瓶盖就滑掉了。我们用的方案是对块内动作损失按时间位置加权,末段权重可以给到前段的 1.5 到 2 倍。这个权重在自适应分块模式下可以跟进度估计器联动——离目标越近,末段权重越高。
第二个问题是过平滑。回归头遇到多峰动作分布时,会把多个成功轨迹平均起来,机械臂可能在两条路径中间走一条谁都不像的“妥协轨迹”,表面看 MSE 不高,但实际任务就是完不成。如果你遇到模型在同一个任务上反复出现“骑墙”行为,第一反应应该检查动作分布是不是多峰的,然后决定是换扩散头,还是给动作标签做聚类后再分组建模。
4.4 安全兜底:低置信度、分布漂移与人工接管的触发逻辑
Agnet 机器人强调自主,但自主不等于把安全全交给端到端模型。动作分块模型给出的是一个长序列,如果它对自己预测的内容没有置信度估计,执行到一半时出错的代价会非常大。我们的部署里加了几条很朴素的安全逻辑。
第一条是集合置信度。在配置允许的情况下,动作头同时输出多份预测样本,通过样本方差估算置信度;置信度低于阈值时强制触发重规划。第二条是分布漂移检测。用一个轻量的密度估计器监控当前观测是否落在训练分布附近,一旦检测到新场景,比如有演示中没出现过的物体出现在工作台上,立即降低执行速度并提示人工确认。第三条是物理量超限保护。分块模型预测的轨迹再平滑,也要在控制层做限位和力矩保护,力传感器触发超阈值时无条件停机。
我把它们统称为“决策层只管怎么动,安全层只管动不动”。动作分块解决的是长序列规划的连贯性问题,但它不能解决所有现实世界的不确定性,安全边界必须独立存在。
5. 我们在桌面操作任务里看到的效果与适用边界
说了这么多原理和工程细节,最后用我们团队在模拟项目X上的一组对比实验来收尾。这组实验不追求发论文,目标很实在:搞清楚动作分块到底在哪些任务上带来真金白银的提升,在哪些任务上只是给系统增加了复杂度。
5.1 实验配置与评价指标
我们选了 6 个桌面操作任务:抓取置物、杯塔堆叠、倒水、旋盖、插销、目标搬运。每个任务采集了 500 条人工演示,统一用相同骨干网络训练三种策略:单步行为克隆、固定 16 步分块、ChunkAct 自适应分块。真机测试每任务跑 20 次,统计成功率、平均完成时间、重规划次数以及末端轨迹平滑度。
这里解释一下为什么要把“固定 16 步分块”单独列出来:它是为了把 ChunkAct 中“自适应分块”和“进度估计器”这两个附加设计的增量贡献拆开看,避免把所有功劳都笼统归到动作分块头上。
5.2 数据结果:成功率、完成时间和轨迹平滑度怎么变化
下面是我们记录到的成功率数据(模拟项目X,20 次重复实验):
| 任务 | 单步 BC | 固定 16 步分块 | ChunkAct 自适应分块 |
|---|---|---|---|
| 抓取置物 | 60% | 75% | 80% |
| 杯塔堆叠 | 35% | 55% | 70% |
| 倒水 | 40% | 60% | 75% |
| 旋盖 | 25% | 45% | 60% |
| 插销 | 20% | 40% | 55% |
| 目标搬运 | 70% | 80% | 85% |
从结果看,固定分块在大多数任务上已经把单步策略拉开了明显差距,这说明动作分块的收益主要来自“把动作序列当整体生成”这个范式本身,而不是自适应机制的功劳。ChunkAct 自适应的额外提升则集中在堆叠、倒水、插销这类“先粗后细”的任务上,粗调阶段用大块让动作连贯,细调阶段用小块保持敏感。
比较意外的是完成时间。ChunkAct 策略的平均完成时间并不比单步策略短,个别任务甚至还略长。但仔细观察轨迹会发现,单步策略时间短的时候往往是因为“边走边错”侥幸成功,而 ChunkAct 是在用更平稳的轨迹笨拙地绕过障碍。如果同时把重规划次数列入指标,ChunkAct 平均要比单步策略少一半以上——这个差值带来的控制负担下降,在长任务里比时间指标更重要。
5.3 失败案例分析:什么时候不要用动作分块
把失败的 20 次记录逐条看下来,有一个共同点很明显:凡是任务中需要快速响应外部状态突变的,动作分块都表现不佳。
最典型的是倒水。如果杯子里的水位变化是均匀的,分块模型可以处理得很好;但有一次实验中有人在执行中途轻轻碰了一下杯子,模型按照原来的块继续倒水,没有及时调整倾角,导致水洒了一部分。另一个失败集中在旋盖任务,旋盖拧到底之后需要依靠力矩反馈判断“已经拧紧”,分块模型倾向于按原轨迹继续输出动作,直到安全层的力传感器触发停机。
这说明了一个边界:动作分块适合的是“环境有轻微扰动,但大体可控”的任务,不适合强交互、需要连续接触反馈的任务。如果你遇到的是拧螺丝这样对接触力极其敏感的操作,我会建议在接触阶段切换成短分块,或者直接退化到高频单步策略,不要让整个任务都套用同一种分块配置。
5.4 经验备忘:把分块长度做成可配置项
最后分享一个没什么技术含量但非常实用的经验:所有与动作分块相关的参数,在真机部署时一定要做成运行时配置,动态读取,不要编译进模型甚至写死在代码里。
我们维护的配置缓存大概长这样:
chunk_policy = { "chunk_len": 32, # 最大分块长度 "replan_interval": 8, # 每执行多少步重新推理 "boundary_overlap": 4, # 新块与旧块的重叠步数 "terminal_weight": 2.0, # 块末段损失权重 "confidence_threshold": 0.75, }你可能会觉得这没什么好说的,但我自己吃了不止一次亏:代码里硬编码 H=32,跑完一组实验想试 16,只能重新部署、重新启动一轮流程,一个下午就没了。把这些参数做成可动态修改的配置之后,调参速度至少快了一倍。另外,参数名要写清楚单位是控制步还是秒,我之前把“8 步”理解成“8 秒”,导致重规划间隔设得过大,机器人闷头执行了好半天不理外界,排查了很久才发现是单位问题。
我在实际使用中的另一个体会是,动作分块的调参顺序比想象中固定:先把边界衔接调稳,再去动分块长度和重规划间隔;先确认归一化和数据没问题,再去纠结换哪种动作头。顺序反过来,你很容易在模型结构上花了一周,最后发现性能差的根源只是数据里某个关节角度没有做周期归一化。
如果这篇内容能让你少走一点弯路,那它最大的用处不是告诉你 ChunkAct 多厉害,而是让你明白:动作分块本质上是在调整机器人策略的时间尺度,决策慢一点,执行稳一点,而那些让人头疼的抖动、漂移和犹豫,很多都源于我们让机器人用错了决策节奏。