1. 机器人闭环控制里,VLM 记忆为什么成了瓶颈
1.1 从“看一眼就动手”到“边看边调整”的范式转变
过去两年,视觉语言模型(VLM)在机器人任务规划上的应用,基本停留在“看一眼就动手”的阶段。典型流程是:机器人用相机拍一张当前场景的照片,把图片和任务指令一起喂给 VLM,VLM 输出一串动作序列或子目标,机器人照着执行。这个范式在短程、静态任务上表现不错,比如“把桌上的红色方块放到蓝色盒子里”,只要场景不变、动作步数不多,VLM 一次推理就能给出可行方案。
但真实世界的操作任务很少这么理想。物体被遮挡、目标位置在操作过程中发生偏移、抓取失败需要重试、多步任务中间状态需要持续跟踪——这些情况都要求机器人不能只靠“开局一张图”,而必须在执行过程中不断观察、不断修正。这就是闭环控制的核心思想:感知、决策、执行形成一个持续反馈的环路,而不是开环的一次性规划。
问题在于,当研究者试图把 VLM 塞进这个闭环时,发现了一个尴尬的事实:VLM 的推理速度跟不上闭环控制的频率要求。一个 7B 参数的 VLM 在单张消费级显卡上做一次前向推理,动辄需要几百毫秒甚至超过一秒。而机器人闭环控制通常要求 10Hz 到 50Hz 的控制频率,也就是每 20 到 100 毫秒就要更新一次决策。两者之间差了一个数量级。
1.2 记忆机制:VLM 闭环控制的关键卡点
面对速度瓶颈,一个自然的思路是“不要每步都调用 VLM”。让 VLM 每隔 N 步推理一次,中间步骤用某种缓存或记忆机制来填补。这就引出了 VLM 记忆的问题:机器人需要记住什么、怎么存、怎么取、什么时候更新。
我实际搭过类似的系统,踩过的坑很典型。最早的做法是把 VLM 的历史输出全部拼进上下文,每次推理都把过去几十步的观察和动作塞进 prompt。结果 token 数爆炸,推理速度进一步下降,而且 VLM 对长上下文的注意力分配很不均匀,近期信息容易被淹没。后来改成只保留最近 K 步的滑动窗口,速度上来了,但机器人会“失忆”——比如它刚把杯子放到桌子左边,过了几步就忘了杯子在哪,因为那步观察已经滑出窗口了。
还有一种做法是维护一个显式的状态向量,比如物体位姿、抓取状态、任务进度,用 VLM 来更新这个状态。这听起来合理,但 VLM 输出的状态估计往往不够精确,误差会累积,闭环反而变得不稳定。
MIT 和 CMU 这篇工作(CoRL 2026)针对的正是这个卡点。他们没有简单地把 VLM 当做一个黑盒规划器,而是重新设计了 VLM 在闭环中的角色:VLM 不再每步都输出动作,而是负责维护和更新一个紧凑的、任务相关的记忆表示,底层控制器则基于这个记忆和当前观察做高频决策。这个分工让 VLM 的推理频率可以降到 1-2Hz,而底层控制仍然保持 20Hz 以上。
1.3 谁适合参考这套思路
如果你正在做机器人操作、具身智能、或者任何需要把大模型塞进实时闭环的场景,这套思路都值得细看。它不局限于特定的 VLM 架构,也不依赖特定的机器人平台。核心思想——把慢速的大模型推理和快速的低层控制解耦,用记忆作为两者之间的桥梁——具有很强的迁移性。
对于刚入门的同学,建议先理解闭环控制的基本概念,比如 PID 控制里“误差反馈”的思想,再来看 VLM 记忆的设计,会更容易抓住重点。对于有经验的从业者,可以直接关注他们记忆更新的具体机制和实验中的消融分析,那里有最多可复用的细节。
2. 核心设计拆解:VLM 记忆到底怎么管
2.1 记忆的三种类型与分工
MIT 和 CMU 的方案里,记忆不是单一的东西,而是分成了三层,各司其职。这个分层设计是整套系统的骨架,理解它之后,后面的细节就顺了。
第一层是感知记忆,存的是最近若干帧的视觉特征。注意,不是原始图像,而是经过 VLM 视觉编码器提取的特征向量。原始图像太大,存几十帧就爆内存,而且底层控制器也不需要那么高的视觉分辨率。特征向量经过降维和压缩,既能保留关键信息,又不会拖慢检索速度。
第二层是任务记忆,存的是与当前任务相关的语义状态。比如“杯子在桌子左边”“机械臂已经抓到了把手”“下一步需要旋转 90 度”。这些状态不是从图像里直接读出来的,而是 VLM 在每次推理时根据当前观察和已有记忆更新出来的。任务记忆的表示很紧凑,通常就是一个结构化的状态向量或者一小段文本描述。
第三层是动作记忆,存的是最近执行的动作序列和对应的结果。这层记忆的作用是让系统能够检测“动作是否按预期生效”。比如机器人发出“向前移动 5 厘米”的指令,动作记忆会记录这个指令和实际观察到的位移。如果实际位移和预期差太多,说明可能遇到了阻碍或者抓取失败,需要触发 VLM 重新推理。
这三层记忆的更新频率不同。感知记忆每帧都更新,任务记忆只在 VLM 推理时更新,动作记忆每步控制都更新。这种异步更新机制是保证系统实时性的关键。
2.2 为什么不用端到端的大模型直接输出动作
这里有一个很自然的疑问:既然 VLM 能理解场景和任务,为什么不直接让它输出动作,非要搞这么复杂的记忆分层?
我一开始也有这个疑问,后来自己试了端到端方案才明白问题在哪。端到端方案里,VLM 的输入是当前图像加任务指令,输出是动作。这个映射关系非常复杂,VLM 需要同时完成场景理解、任务规划、动作生成三件事。训练数据稍微偏一点,泛化就崩了。而且每次动作生成都要跑一遍完整的前向传播,延迟根本降不下来。
分层记忆方案的好处在于,它把“理解”和“控制”解耦了。VLM 只负责它擅长的事:理解场景、更新任务状态。底层控制器负责它擅长的事:根据当前状态和观察,快速生成精确的控制指令。两者通过记忆这个接口交互,各自的频率可以独立调节。
更重要的是,这种解耦让系统更容易调试。如果机器人行为不对,你可以分别检查:是 VLM 更新的任务记忆错了,还是底层控制器根据正确记忆做出了错误动作。端到端方案里,你只能看到一个黑盒输出,排查起来非常痛苦。
2.3 记忆更新的触发条件与频率控制
记忆更新不是每步都做,而是有触发条件的。这套方案里,触发条件主要有三个:
- 时间触发:每隔固定时间(比如 500 毫秒)强制更新一次,防止记忆过时。
- 事件触发:当底层控制器检测到异常(比如动作执行失败、观察与预期不符)时,立即请求 VLM 更新记忆。
- 任务阶段触发:当任务进入新的阶段(比如从“接近”进入“抓取”)时,主动更新记忆。
这三个条件里,事件触发是最关键的。它让系统能够在关键时刻“停下来想一想”,而不是盲目地按固定频率推理。实测下来,事件触发能显著减少不必要的 VLM 调用,同时保证在真正需要的时候及时更新。
频率控制方面,VLM 的推理频率通常设在 1-5Hz 之间。具体设多少,取决于任务复杂度和硬件条件。简单任务 1Hz 就够,复杂任务可能需要 5Hz。但再高就没意义了,因为 VLM 的单次推理延迟本身就限制了上限。
3. 实操落地:从零搭建一套 VLM 闭环控制系统
3.1 硬件与软件环境准备
先说硬件。这套系统对算力的要求分两部分:VLM 推理和底层控制。VLM 推理建议至少一张 16GB 显存的显卡,7B 模型用 FP16 推理大概占 14GB,留点余量给视觉编码器和记忆模块。底层控制对算力要求不高,一块普通的工控机或者树莓派就能跑,但要求实时性,所以最好用独立的进程或线程,避免被 VLM 推理阻塞。
软件环境方面,我用的组合是:
- VLM 推理:HuggingFace Transformers 或者 vLLM,后者在批处理和延迟优化上更好,但配置稍复杂。
- 机器人中间件:ROS2 或者直接用的 Python 多进程。ROS2 的好处是消息传递和同步机制成熟,坏处是引入额外延迟。如果对延迟敏感,直接用共享内存加锁更简单。
- 底层控制:Python 加 NumPy 就够了,如果控制频率要求高,可以用 C++ 写控制循环,Python 只做上层逻辑。
注意:VLM 推理和底层控制一定要放在不同的进程里。我试过放在同一个进程用多线程,结果 GIL 锁导致控制循环经常被 VLM 推理阻塞,延迟抖动非常大。改成多进程后,控制循环的延迟标准差从 20 毫秒降到了 2 毫秒以内。
3.2 记忆模块的具体实现
记忆模块的实现是整套系统的核心。我用 Python 写了一个简单的记忆管理器,核心数据结构如下:
class MemoryManager: def __init__(self, perceptual_capacity=30, task_dim=64, action_capacity=50): # 感知记忆:环形缓冲区,存视觉特征 self.perceptual_memory = deque(maxlen=perceptual_capacity) # 任务记忆:结构化状态向量 self.task_memory = np.zeros(task_dim) # 动作记忆:环形缓冲区,存(动作, 结果)对 self.action_memory = deque(maxlen=action_capacity) # 记忆更新时间戳 self.last_update_time = 0 def update_perceptual(self, visual_feature): self.perceptual_memory.append(visual_feature) def update_task(self, new_task_state): # 平滑更新,避免突变 self.task_memory = 0.7 * self.task_memory + 0.3 * new_task_state def update_action(self, action, result): self.action_memory.append((action, result)) def get_context(self): # 拼接三层记忆,供VLM推理使用 perceptual = np.mean(self.perceptual_memory, axis=0) if self.perceptual_memory else np.zeros(512) action_summary = self._summarize_actions() return np.concatenate([perceptual, self.task_memory, action_summary])这里有几个设计细节值得展开。感知记忆用环形缓冲区而不是列表,是为了自动淘汰旧数据,避免内存无限增长。任务记忆用平滑更新而不是直接替换,是为了防止 VLM 单次推理的噪声导致状态跳变。动作记忆的摘要函数_summarize_actions会把最近的动作序列压缩成一个固定长度的向量,具体做法可以是对动作类型做 one-hot 编码后取平均,或者用一个小型 RNN 编码。
实操心得:任务记忆的平滑系数(上面代码里的 0.7 和 0.3)需要根据任务调整。如果任务状态变化很快,比如高速抓取,平滑系数要调低,让新状态更快生效。如果任务状态稳定,平滑系数可以调高,增强抗噪能力。我一般从 0.5 开始试,根据实际表现微调。
3.3 VLM 推理与底层控制的协同
VLM 推理进程和底层控制进程之间通过共享内存或消息队列通信。具体流程是:
- 底层控制进程每步采集当前观察(图像特征、关节角度、末端位姿),写入共享内存。
- VLM 推理进程按触发条件读取共享内存中的观察和记忆,做一次推理,输出新的任务记忆,写回共享内存。
- 底层控制进程读取最新的任务记忆,结合当前观察,计算控制指令。
这里的关键是读写同步。如果底层控制进程正在读任务记忆,VLM 进程同时写,可能读到半新半旧的数据。解决办法是用双缓冲:VLM 写的时候写缓冲区 A,写完后原子切换指针,底层控制读缓冲区 B。这样读写互不阻塞,也不需要加锁。
底层控制器的设计可以用经典的 PID 加前馈。PID 负责消除当前误差,前馈负责根据任务记忆预测下一步的目标。比如任务记忆说“杯子在左边”,前馈项就会给一个向左的偏置,PID 再根据实际观察微调。这种组合比纯 PID 响应更快,也比纯前馈更稳。
3.4 参数调优与实测数据
参数调优是这套系统最耗时的部分。我整理了一个调优顺序,供参考:
| 调优阶段 | 关键参数 | 推荐范围 | 调优目标 |
|---|---|---|---|
| 底层控制 | PID 比例系数 | 0.1-2.0 | 无超调、响应快 |
| 底层控制 | PID 积分系数 | 0.01-0.1 | 消除稳态误差 |
| 底层控制 | PID 微分系数 | 0.001-0.01 | 抑制振荡 |
| 记忆模块 | 感知记忆容量 | 20-50 帧 | 覆盖一个完整动作周期 |
| 记忆模块 | 任务记忆平滑系数 | 0.3-0.7 | 平衡响应与抗噪 |
| VLM 推理 | 推理频率 | 1-5 Hz | 匹配任务复杂度 |
| VLM 推理 | 事件触发阈值 | 0.1-0.3 | 检测异常灵敏度 |
实测数据方面,我在一个抓取-放置任务上做了对比。纯开环 VLM 方案的成功率是 62%,主要失败原因是物体位置偏移和抓取失败后无法恢复。加上闭环记忆方案后,成功率提升到 89%,平均任务完成时间从 12 秒增加到 14 秒,但考虑到成功率的大幅提升,这个时间代价是值得的。
延迟方面,VLM 推理的平均延迟是 320 毫秒,底层控制循环的延迟是 8 毫秒。记忆读写引入的额外延迟不到 1 毫秒。整体系统的控制频率稳定在 25Hz 左右,满足大多数操作任务的需求。
4. 常见问题与排查技巧实录
4.1 记忆“漂移”:机器人记着记着就记错了
这是最常见的问题。表现是机器人执行到一半,突然对物体位置或任务状态的判断出现明显偏差。比如杯子明明在左边,它却往右边抓。
排查思路分三步。先看感知记忆有没有问题:把最近几帧的视觉特征可视化出来,看看是不是图像质量下降或者遮挡导致特征提取出错。再看任务记忆的更新历史:把每次 VLM 更新的任务状态打出来,看看是不是某次更新出现了异常值。最后看平滑系数:如果平滑系数太高,VLM 的正确更新被旧记忆拖累,也会导致漂移。
解决办法通常是降低平滑系数,或者增加事件触发的灵敏度。如果问题出在感知端,可以在视觉特征提取前加一个简单的质量检测,比如图像模糊度或者亮度检测,质量太差的帧直接丢弃,不写入感知记忆。
4.2 VLM 推理延迟抖动大,控制循环被拖慢
这个问题在多进程方案里也可能出现,原因是共享内存的读写竞争或者消息队列的阻塞。我遇到过一种情况:VLM 推理进程偶尔会做一次特别慢的推理(比如输入 token 数突然增大),导致写共享内存的时间变长,底层控制进程等锁等太久。
解决办法是给 VLM 推理加一个超时机制。如果一次推理超过预设时间(比如 1 秒),直接放弃这次推理,沿用上一次的任务记忆。同时,底层控制进程读共享内存时用非阻塞读,读不到就用本地缓存的旧记忆。这样即使 VLM 偶尔卡顿,控制循环也不会被拖死。
另一个技巧是把 VLM 推理的输入做长度限制。感知记忆的摘要向量长度固定,任务记忆长度固定,动作记忆摘要长度固定,这样输入 token 数就是可控的,不会出现突然增大的情况。
4.3 事件触发太敏感或太迟钝
事件触发的阈值设不好,系统要么频繁调用 VLM 浪费算力,要么该更新的时候不更新导致失败。
太敏感的表现是 VLM 调用频率远高于设定值,日志里全是“触发更新”。这时候要检查异常检测的逻辑,是不是把正常的观察噪声当成了异常。可以加一个滑动平均,只有连续多步异常才触发。
太迟钝的表现是机器人明显出错了还不更新记忆。这时候要检查阈值是不是设得太高,或者异常检测的特征选得不对。我一般会用动作执行结果和预期结果的差值作为触发信号,差值超过阈值就触发。这个差值可以用相对值而不是绝对值,比如“实际位移和预期位移的比值偏离 1 超过 20%”就触发,这样对不同尺度的动作都适用。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 记忆漂移 | 平滑系数过高 | 打印任务记忆更新历史 | 降低平滑系数 |
| 记忆漂移 | 感知特征质量差 | 可视化视觉特征 | 加质量检测,丢弃差帧 |
| 控制延迟抖动 | 共享内存竞争 | 打时间戳测读写延迟 | 双缓冲+非阻塞读 |
| 控制延迟抖动 | VLM 推理超时 | 记录每次推理耗时 | 加超时机制,放弃慢推理 |
| 触发太频繁 | 异常检测太敏感 | 统计触发频率 | 加滑动平均,连续异常才触发 |
| 触发太迟钝 | 阈值过高 | 检查触发时的实际误差 | 降低阈值,改用相对误差 |
| 抓取失败后不恢复 | 动作记忆未生效 | 检查动作记忆更新逻辑 | 确保失败结果写入动作记忆 |
| 任务阶段切换卡顿 | 阶段触发未配置 | 检查阶段检测逻辑 | 显式定义阶段切换条件 |
避坑技巧:调试这套系统时,一定要把三层记忆的内容实时可视化出来。我用的方法是用 matplotlib 画三个子图,分别显示感知记忆的特征热力图、任务记忆的状态向量、动作记忆的最近动作序列。这样一眼就能看出哪层记忆出了问题,比看日志快得多。
5. 这套方案还能怎么扩展
5.1 多模态记忆的融合
目前这套方案主要用的是视觉特征和动作状态。实际场景里,力觉、触觉、听觉信息也很重要。比如抓取易碎物体时,力觉反馈能提前检测到抓取力过大,触发 VLM 重新规划。把力觉信号编码后加入感知记忆,是一个很自然的扩展方向。
具体做法是在感知记忆里增加一个力觉特征通道,和视觉特征拼接。VLM 的输入维度会增大,但现代 VLM 对多模态输入的支持已经比较成熟,改造成本可控。需要注意的是,力觉信号的采样频率通常比视觉高很多,要做降采样或者特征提取,避免记忆容量被力觉数据占满。
5.2 记忆的长期存储与检索
当前方案里的记忆都是短期的,容量有限。如果任务持续时间很长,比如几十分钟的装配任务,短期记忆不够用。这时候可以引入长期记忆,把历史记忆压缩后存到外部存储,需要时再检索回来。
检索可以用简单的相似度匹配,也可以用一个小型检索模型。关键是检索速度要快,不能拖慢主循环。我试过用 FAISS 做向量检索,在几千条记忆里检索一次大概 1 毫秒,完全可以接受。
5.3 从单任务到多任务迁移
这套记忆结构是任务相关的,换一个任务,任务记忆的维度和含义可能都要变。如果想让系统支持多任务,可以设计一个任务编码器,把任务指令映射到任务记忆的初始状态,然后让 VLM 在推理时同时更新任务记忆和任务编码。这样不同任务共享同一套记忆结构,只是初始状态和更新规则不同。
这个方向目前还在探索阶段,但初步实验显示,任务编码器能显著减少新任务的适应时间。对于需要快速部署到多种场景的应用,这个扩展很有价值。
5.4 与其他闭环控制方法的结合
这套方案和经典的 PID 控制、模型预测控制(MPC)并不冲突,反而可以互补。PID 负责底层执行,MPC 负责中短期规划,VLM 记忆负责长期任务理解。三层各司其职,频率从高到低,形成一个完整的控制 hierarchy。
我实际试过把 MPC 加进来做中短期轨迹优化,VLM 记忆提供目标点和约束条件。效果比纯 PID 加前馈更好,尤其是在有动态障碍物的场景里。代价是 MPC 的求解时间增加了整体延迟,需要根据任务要求权衡。
最后分享一个小技巧:如果你刚开始搭这套系统,不要一上来就追求全功能。先用最简单的配置跑通——单层感知记忆、固定频率 VLM 推理、纯 PID 控制——确认基本闭环能工作,再逐步加记忆分层、事件触发、多模态融合。我见过太多人一开始就搭复杂架构,结果调试成本太高,最后放弃了。简单跑通再迭代,才是最快的路径。