跳转至

第 6 章 长程任务

第 1 章立下的那把判断尺,挪到这里正式碰上了最硬的一格:任务只要超过两分钟,端到端就开始崩。它崩的方式不是冒出噪声,而是整套动作结构散了架。这一章解释那个结构,也解释为什么再大的网络也无法把它“训没”。


2025 年下半年,圈内反复传过一段不到两分钟的 demo。一台双臂 VLA 机器人收拾餐桌:先把脏碗挨个放进水池,再折回桌面擦去水迹。头 60 秒走得极漂亮,每个碗的位姿都对得上,进水池时没有任何剐蹭。

到了第 90 秒,它做了一件让看的人安静下来的事:把一个已经放进水池的碗又拿了回来,重新摆在餐桌上,接着伸手去抓它,再放回水池。

来回三趟。没有报错,没有迟疑,电机声音平稳,姿态甚至挑不出毛病。它认认真真地走入了一个死循环,直到工程师把急停按下。

事后回放数据,每一帧单拿出来看都“合理”。模型看见桌上有个碗,桌上有碗就该去抓;抓在手里看到旁边有水池,就该放进去;放进去之后再看一眼桌子,居然还有个碗(那是它自己刚拿回来的),那就再来一遍。任务级的状态在网络里根本不存在,每一步不过是在算“现在这一帧最像演示数据里的哪一帧”的最近邻。前 60 秒看着像样,是因为前 60 秒的状态分布跟训练数据完全重合;第 90 秒分布开始往外飘,飘到一个训练集里谁也没收拾过的中间态,模型就退化成了一个对当前画面的反射。

这是 2025 年所有人都在反复看的一幕。把这段录像看上十遍,能学到的其实只有一件事:长程任务不是“训得不够长”,是压根没拆。


VLA 只要跑过两分钟就必定会崩,这事能拆出四个独立的原因。它们彼此互不重叠,每一个都得单独去解。

第一是训练数据的长度分布。teleop 收集的演示绝大多数落在 10-60 秒之间。背后的原因很实在:操作员注意力撑不住更久,标注成本随着时长线性往上走,剪辑长 demo 时人还会下意识把停顿剪掉。整个数据集的时长中位数大概只有 25 秒。π0 能在折衣服上跑近 5 分钟,已经是把训练 budget 全压在长 demo 上换来的,而且也只管那一类任务。让同一个模型去跑 5 分钟的厨房任务,立刻就会退化。

第二是 context window。眼下 VLA 的视觉 token 化效率相当差,30Hz 的双目摄像头一秒钟就能吃掉两三千个 token。哪怕底下是用着 1M context 的语言模型骨干,光是跑纯视觉,90 秒之后窗口也要见底,再往后就只能扔掉早期帧。早期帧一旦扔了,机器人就会忘掉“这一步之前我已经把碗放进水池了”。这不是写一个长 context 模型就能解决的事,它本质上是 token 经济学的问题。

第三是累积误差。动作每走一步,都会带出几毫米的位置误差、几度的角度误差,单步看微不足道。但在闭环控制里,下一步全凭上一步留下的状态来定,连续走上 180 步,误差就能一路堆到桌面边缘外面去。经典控制器能用 Kalman filter(一种持续融合传感器读数把状态估计拉回真值的迭代滤波器)在每一步把误差拉回来,端到端策略没有任何机制做这件事,因为它的状态估计是隐式的,藏在 transformer 的 hidden state 里,根本没有地方挂上一个 correction step。

第四是状态估计漂移(state estimation drift,机器人对自己当前位姿/任务进度的内部估计随时间偏离真值),更准确地说是任务进度漂移。机器人能估出自己的手在哪儿(本体感觉,关节编码器给的关节角度很准),也能估出桌上有什么物体(视觉够好),但它唯独估计不出“这个任务我已经做到第几步”。这第四件事是这一章最关键的,下面会单独再讲。

把这四件事合起来,就是那段死循环画面的全部解释。问题不在于模型不够聪明,而是它在用一套专为短时反射优化的架构,去做一件需要任务级记忆的事。


所以这一章要立下第一条立场:长程任务 ≠ “训更长”,而是“该分层”。

这话有人不爱听,特别是 2024-2025 年那一波相信“scale is all you need”的人。立场得点名说清楚:两分钟以内,scale 确实买到了真东西,π0 折衣服、Helix 抓取片段、GR00T 的 manipulation skill,这些全都是 scale 换来的。但长度这一维根本 scale 不进去,因为它需要的不是更多参数,而是一个能把任务拆成若干个两分钟以下子段的外部结构。这件事网络自己生不出来,因为生成它需要的不是 pattern matching,而是 planning。

planning 这件事,LLM 早就学会了。非让 VLA 回头再学一遍 planning,等于把两样原本分工分得极清楚的能力强行揉在了一起。分层不是为了仿生大脑的 system 1 / system 2,而是为了把两件完全不同的优化问题彻底分开。


LLM 拿来当 planner 这条线的简史,值得单独写上一段。

最早是 SayCan(Ahn 等,Brian Ichter 和 Karol Hausman 在 Google Robotics,2022 年 4 月)。SayCan 的想法很朴素:让 LLM 列出可能的下一步动作,每一步用一个学到的 affordance 函数打分(“机器人在当前状态下做这件事能不能成”),用 say × can 的乘积选出下一步。那时候 GPT-3 刚出来,让一个语言模型去给机器人列动作,还是个所有人都觉得“反正试试看”的实验。它跑通了,证明 LLM 知道“想喝可乐先去拿可乐再带回来”,这件事在 2022 年还是件新鲜事。

紧接着是 Code as Policies(Liang 等,Google,2022 年 9 月)。这一篇换了思路:不再让 LLM 直接挑选动作,而是让它生成一段调用 API 的 Python 代码,写出 grasp("red_block"); place("on_top_of", "blue_block") 这类语句。代码这种结构天生带着 if-else 分支、for 循环与变量绑定,表达能力比一个个 step token 强得多。这条路后来成了主流。

ProgPrompt(Singh 等,NVIDIA,2022 年)把这一思路放进家务任务,证明 LLM 在结构化代码下,能做相当复杂的厨房任务规划。Inner Monologue(Huang 等,Google,2022 年中)做了另一件事:让 LLM 在执行过程中听得到反馈。视觉模型说“我看到红色方块倒了”,LLM 就要据此调整后续步骤。这是第一次把 LLM 用成闭环的而不是开环的 planner。

2023 年这条路线开始分叉。VoxPoser(Wenlong Huang 在 Stanford,2023 年 7 月)走的是用 LLM 生成 3D value map,再让轨迹优化器在 map 上跑出动作。这是个聪明的折中方案:LLM 不直接指挥动作,而是标出“哪些区域吸引、哪些区域排斥”,把语义与几何拼在了一起。ViLA(Hu 等,2023 年)和 Tidybot(Wu 等)继续推 LLM + scene graph 的路线。同年 Google 的 PaLM-E(Driess 等)走向了另一个极端,把视觉直接拼进语言模型的 token 序列里,让 LLM 独自端到端做规划与执行。PaLM-E 在某种意义上就是 RT-2 的前身。

2024 年这套打法开始进工业界。每家具身智能公司私底下都备着某种形式的 LLM-as-planner,用法大同小异:把高层指令拆成 2-5 个子任务,每个子任务对应一个已知的 skill API,派发下去执行,再根据返回值决定下一步。到了 2026 年初,这已经成了行业的默认选项,大家争论的焦点早就不再是要不要做,而是 skill API 到底长什么样。

flowchart TB
  T[任务: 收拾厨房] --> L[LLM planner]
  L -->|pick(red_bowl)| S1[VLA skill 1<br/>30-90s]
  L -->|place(sink)| S2[VLA skill 2<br/>30-90s]
  L -->|wipe(table)| S3[VLA skill 3<br/>30-90s]
  S1 --> SF[安全层<br/>力矩限幅 / e-stop]
  S2 --> SF
  S3 --> SF
  SF --> M[电机]
  S1 -.->|success / fail / stuck| L
  S2 -.->|success / fail / stuck| L
  S3 -.->|success / fail / stuck| L

skill API 是这一层成败的全部。子任务接口划得好,整套系统就站得稳;划得不好,上层的 LLM 规划得再漂亮,底下的 VLA 也接不住。

坏的 skill 就像 clean_kitchen()。这个粒度太大,底下没有任何 VLA 能稳定执行 5 分钟连续动作,把接口写成这样,其实只是把死循环延后到了下一层。

好的 skill 则是另一套样子:pick(object_id, grasp_pose=None)、place(target_pose, support_surface)、wipe(area_polygon, tool_id)、open_drawer(handle_pose)。细看这组接口,里头有几个要紧特征。第一是参数化,能接住 LLM 传下来的具体物体 ID 和位姿。第二是单步时长在 10-60 秒之间,正好落在 VLA 在演示分布里能稳跑的窗口。第三是有明确的成功或失败返回值,好让上层 LLM 能据此重新规划。第四是接口语义足够窄,窄到可以专门为它收一两千条 demo,单独训练一个小 policy。

写得差的 skill 还有一种常见的毛病,就是把自然语言当 API。比如直接塞一句“Pick up the red bowl that's on the left side of the table”。这话听起来很迁就大语言模型,底层的 VLA 却未必分得清当前坐标系里的“左边”到底指哪边。LLM 本身是个看不见画面的 planner,写出来的模糊指代全丢给底层的 VLA 自己去消歧,一旦消歧失败,系统就会陷入死循环。稳妥的做法是在上层先接一个 vision-language grounding 模块,把“红碗”结结实实地换算成 object_id=37, bbox=[...],再交由具体的 skill 执行。中间多一道翻译,底下少一类 bug。

至于系统里究竟该放多少个 skill,2025 年工业界摸出来的经验是一个家庭机器人备上 30 到 80 个就够用了,再多管理成本就会压垮团队。每一个 skill 都需要单独 fine-tune、单独评估、单独做 versioning。这套思路跟 2010 年代的 motion primitive library 颇为接近,真正的区别,无非是当年写死的 primitive 如今换成了一个个神经网络。


把长任务拆解开来还不够,系统总得懂得怎么从失败里自己恢复过来。

围绕失败恢复,2024 到 2025 年间集中涌现出一批扎实的研究。REFLECT(Liu 等,2023 年底)做的事情是让机器人在动作失手后调用 LLM 自行分析原因,比如在心里念叨“我刚才没抓住,是因为我抓的位置太靠边”,继而调整下一次尝试。AHA(Duan 等,2024 年)思路相仿,只是额外补上了视觉证据。RACER(Dai 等,2024 年)专门盯着长程任务的 recovery,直接把失败片段与恢复片段统统收进训练集。几家团队各自推进的 Robotic Recovery 工作,核心都在讲同一个道理:老老实实承认系统总会失手,预先给系统定下一套失败之后该去干什么的协议。

最值得单独挑出来看的是 Yell At Your Robot(Shi 等,Lerrel Pinto 在 NYU,2024 年)。这篇论文的设定非常朴素:机器人正在执行任务,人在旁边喊上一句“不对,往左一点”,机器人立刻顺势调整。它的价值并不在动嘴喊话本身,而在于它承认了人和机器人在长程任务里必须保留一条低带宽的纠错通道。这类做法在工业部署里其实早就司空见惯,Figure 和 1X 都有遥操作员时刻盯着做实时纠偏,Yell At Your Robot 则是把它搬进学术论文的第一步。

把这几条线索放在一块看,长程任务里的 recovery 设计大致有三类。第一类是 retry,失手之后原地重试一次,专门对付抓滑这类偶发意外;第二类是 replan,让 LLM 重新推演规划,适合子任务卡壳、比如门拉不开的局面;第三类是 ask,直接请人过来接手,处理前两招都化解不了的疑难杂症。一套成熟的长程系统往往三者兼备,分级化解层层设防,从不指望单靠哪一种手段兜住所有局面。


记忆是另一根难啃的硬骨头。

纯语言模型那边的记忆机制,眼下大致分成了三层。短期记忆全靠 transformer 的 context window,当前对话内容全盘直接塞进去;中期记忆依赖 agent scratchpad,把关键事实与中间结论规整地写下来,留给下一步调用;长期记忆则是 RAG over experience,把过往会话存成 embedding 索引,需要时按图索骥检索召回。这套架构在纯语言 agent 的场景里已经磨合得相对成熟。

机器人的记忆之所以比聊天 agent 难得多,症结主要在三处。

第一是带宽。聊天 agent 一秒钟新增几百 token,机器人一秒钟却要吞吐几千 token 的视觉信号,外加几百 token 的本体感觉和几十 Hz 的力觉数据。若是把这些全塞进 context,跑不上两分钟窗口就得撑爆。所以机器人的中期记忆必须遵循先压缩再写入的规矩,至于具体怎么去压,目前依旧是个开放问题。

第二是连续性。聊天对话里两条消息之间的状态切换是离散分明的,而在机器人的世界里,一只碗落进水池、池水随之飞溅开来,整个物理过程完全是连续的。动手压缩时到底该保留多少帧画面、保留多细颗粒度的物理细节,没人能给出确切的答案。

第三是检索的信号。在文本 RAG 之中,提问里的关键词只要与某段过往记忆高度重合,就能顺理成章把那段记录 retrieve 出来。然而当机器人执行到第 47 步时,该从训练集里调取哪一段经验,究竟是该依据手头的物体、当前的姿态还是子任务的名字,目前还没有任何一种 embedding 能把这三样要素同时编码妥当。VLM 给出的 image embedding 往往太粗,CLIP 这一系模型又普遍对纹理敏感而对空间几何迟钝,桩桩件件都是摆在眼前的已知难题。

落到 2025 年的工程实战中,大家见到的折中方案相当骨感:短期记忆交由 transformer context 兜底,只存 30 秒内的视觉与动作;中期记忆全靠 LLM 维护一份纯文本的任务进度流水账,格式无非是“已完成步骤 1、2、3,当前步骤 4,下一步 5”;至于长期记忆,则几乎被束之高阁。最后一项被搁置并不是因为无关紧要,而是检索难题根本还没打通。于是眼下的家用机器人即便在你家跑上整整三个月,也不会因此变得更懂你家,它每天醒来,面对的依旧是同一套模型权重。这道难关,正是 2026 到 2028 这三年间最容易迎来突破的方向之一。


立场二,写在这一章中段最显眼的位置:state estimation 成了这一波浪潮里被遗忘的问题。

回看 2010 年代的机器人学教材,差不多有半本书都在反复讲 state estimation(状态估计:从带有噪声的传感器读数里,反推机器人当前位姿、速度与任务变量的真实数值)。整套 SLAM 体系(从 EKF-SLAM、graph SLAM、ORB-SLAM、LSD-SLAM 一路到 Cartographer)都在合力解决一个最底层的问题:自己当前究竟在什么位置、姿态又是如何。VIO(visual-inertial odometry,靠视觉与 IMU 融合来估算自己动得多快、往哪儿走)这一脉把 IMU 纳入版图。直到 2020 年,这一切始终都是机器人软件栈的核心。

等 VLA 兴起之后,这件事便被大家悄悄绕了过去。“反正网络看着图像直接输出 action,它内部肯定隐式知道自己在哪。”这话只对了一半,拿来应付 60 秒以内的短任务确实够用。可长程任务里需要估计的远不止空间位姿,还有整个任务的执行进度。

关于任务进度这个物理量,传统机器人学其实从未正经考量过。过去机器人的任务全由人工手写的状态机掌管,第几步执行到哪里本就是状态机自带的内部变量,压根不需要特意去“估计”。到了 VLA 时代,任务换成了自然语言指令与 LLM 生成的子任务序列,“我现在在第几步”突然成了一个必须从外部感知中反向推导的隐变量。

反推这个隐变量所需的信号其实来源甚杂。视觉画面里提示着“这个碗已经在水池里了”,本体感觉传递出“我刚才抓过一次东西然后放下了”,LLM 的 plan 清单上则写着“下一步该是擦桌子”。把这三股杂乱的线索融合成对任务进度的统一估计,本质上做的事情就是一个不折不扣的 state estimator,只是状态空间从 SE(3) 换成了 plan tree 上的一个滑动游标。

2025 年前后,学术界开始零星冒出一两篇摸索这个方向的 paper(标题大多挂着“task progress estimation”或“subtask completion classifier”),可惜还远没有形成体系。我敢断定在 2027 年之前,这里一定会走出某种标准解法,正如当年 Kalman filter 之于位姿估计一样,整个长程任务的稳定性也会随之迈上一个大台阶。而在那套规范成型之前,工程上唯一的兜底做法,就是让 LLM planner 在每一步执行结束后回头看上一眼,自己裁决“刚才那一步成了吗”。这在本质上是把 state estimator 该干的活硬生生 hack 给了 LLM。这个 hack 比想象中更顶用,但它每一步都要白白耗费一次 LLM 调用(平添几百毫秒耗时与几分钱开销),放到高频任务里根本跑不起来。


行文至此,“分层”这个词其实在心智上包含了两种截然不同的东西,必须分作两头来说。

第一种是 RT-H 与 RT-Trajectory(Belkhale 等,DeepMind,2024 年)所代表的动作 hierarchy。它的层级切分在动作空间内部:上层输出一段轨迹或一段语言动作描述(“move arm forward, then down”),下层负责将这层描述翻译成关节角。整套系统依旧是通过端到端方式训练出来的,之所以切出层级,是为了让网络内部更容易优化、让数据更易于标注、也让中间表示更方便人工干预。它并不负责解决长程任务,只是让短程任务训得更稳、更可控。

另一种则是 SayCan、Code as Policies 和本章从头到尾在讲的语义 hierarchy。它的层级立足于任务空间:上层是大语言模型、是符号化的子任务、是带变量的 API 调用;下层才轮到 VLA 或经典控制器去逐段落实执行。这一类架构专门为了长程任务而设计,层与层之间的交互接口全然是离散的、可读的、能够完整记下 log 的。

这两种 hierarchy 要解决的本就是完全不同的两桩问题,工业界与学术界却经常混着说。无论是在简历里、在白板会议上还是在 paper review 里,都得听清楚对方嘴里说的到底是哪一种。RT-H 风格的动作分层替代不了 LLM planner 风格的任务分层,反过来的替代也同样行不通。一台真正能跑通长程任务的家庭服务机器人,身上这两层缺一不可。


最后给出一套实操方案,直接对应第 1 章提到的那个工程信号:手里明明已经攒了一万条演示,VLA 却偏偏过不了最终验收。

第一步,把这一万条 demo 按照时长重新切开。只要翻看一下分布就会发现,绝大多数片段其实都集中在 20 到 40 秒之间,少数几条长 demo 也不过是把若干段拼凑出来的。把所有超过 90 秒的 demo 统统砍成 30 到 60 秒的小段,每一段独立打上一个子任务名称(要求不必苛刻,保持在“pick”、“place”、“wipe”、“open”这种粗细粒度就行)。

第二步,画出 plan。把最终需要执行的那个长任务(比如“清理餐桌”)人工写成适合大模型阅读的 LLM-friendly 伪代码:

for dish in dishes_on_table:
  pick(dish.id)
  place(sink_pose, "drop")
for spot in wet_spots:
  wipe(spot.area, towel.id)
done()

这么写下来,所需的 skill 集合一目了然:pick、place 与 wipe,外加 vision 模块给出的 dishes_on_table 和 wet_spots 检测。要是这一步写得磕磕绊绊,说明手头的任务还没拆透,那就退回 demo 数据里接着拆。

第三步,每个 skill 拿对应的那批数据单独 fine-tune 一个小 VLA。每个 skill 只要 2-3k 条 demo,远比直接拿一万条数据去硬训长任务管用得多。先把每个 skill 单独测透,跑稳了再拼到一起。

第四步,把 LLM planner 接上来。给 skill API 写的 docstring 得让大模型一眼读懂,每个 skill 执行后都要返回明确的 success/failure 信号。遇到失败先 retry 一次,再失败就让 planner 重新 replan,要是依然走不通,最后由人来接手 ask。

第五步,加上任务进度 tracker。最简单的做法,莫过于在每次调用 skill 的前后,都让 LLM 扫一眼当前画面,回答“上一步成功了吗、下一步该做什么”。光是这么一个 hack,就能挡掉 80% 的死循环。

整套流程走下来,原先拿一万条 demo 怎么也训不动的长任务,通常一到两周就能稳稳跑起来。这并不是因为你喂了更多数据,而是因为问题终于被拆对了。


练习

找一段你看过的最长的 VLA demo video(无论是 π0 折衣服、Helix 操作 demo,还是 1X NEO 的家庭片段)。拿秒表卡一下时间,看它从动作开始,到第一次出现明显走样、停顿卡死或是姿态变怪,总共撑了多久。这个实测出来的时长,跟本章前面提到的“两分钟以上崩”,到底能不能对得上?

重读 SayCan 那篇 paper。这一次跳过 affordance 函数那一节,只看它喂给 LLM 的 prompt 和模型输出的子任务序列。把里面的子任务粒度单独拎出来看看:它们究竟是 pick(object) 这样的参数化形式,还是 “go to the kitchen” 这种纯自然语言?做出这种粒度取舍,分别要付出怎样的代价,又换来了什么好处?

找一个你身边的人做一件 5 分钟以上的家务(叠衣服、煮一顿饭或者整理书桌),把它录下来,回放时试着拆成一张离散子任务的清单。你一共拆出了多少个子任务?这些子任务之间是否存在明确的顺序约束?有没有哪一段是你根本拆不开、只能用一个名字概括的(比如“调味”)?这种拆不动的子段,才是端到端 VLA 真正的用武之地。

给你正在做的任务写一份 skill API 文档。每个 skill 配上一段 docstring,把参数和返回值标得一清二楚。写完之后,把这份文档交给 Claude 或 GPT,让它生成完成你那个长任务的伪代码。大模型写出来的代码究竟能不能跑通?要是跑不通,到底是缺了哪个 skill,还是哪个参数没有暴露周全?花上两个小时做完这个练习,手头的接口到底有没有划对,立刻就能见分晓。

下一章:第 7 章 仿真到现实