SimpleMemVLA 深度解读:把原生视频上下文变成机器人的记忆

0. 简介

SimpleMemVLA 面向长时程机器人操作,处理的是一个很具体的部分可观测问题:决定下一步动作所需的证据,可能在几十秒甚至几分钟前出现,而当前相机画面已经看不到它。检索库、视觉压缩器和循环状态都要在写入历史时提前决定保留什么,未来任务一旦需要被删掉的细节,策略就无法补救。SimpleMemVLA 直接把采样后的历史画面保持为带时间戳的视频,送入预训练过视频建模的 Qwen3.5-4B 主干,再用生成子任务的隐藏状态连接标准流匹配动作头;连续决策之间共享的历史前缀则在机器人执行动作时提前计算。项目报告在 RMBench、RoboMME、MIKASA-Robo 和 RoboMemArena 四项记忆基准上取得最高结果,同时在 LIBERO 和 LIBERO-Plus 上保持通用控制能力。下面结合机制、代码、消融、延迟和真机任务,说明这条路线为什么成立、它依赖哪些工程条件,以及它还没有解决什么。OpenBMB/SimpleMemVLA

1. 研究问题与现有方法的缺口

1.1 任务不是看一帧,而是追踪已经消失的证据

长时程操作中的困难经常被误写成"视觉理解不够强",但 SimpleMemVLA 的任务设计把问题限定得更清楚 。机器人可能先看到三个颜色不同的积木,随后被相同的黑盖完全遮住,最后必须按红、绿、蓝的顺序揭盖;也可能把一个方块从四个外观相同的垫子之一拿到中央,按下按钮后再放回原来的垫子。行动发生时,当前画面只提供盖子、按钮和空垫子,真正决定动作的颜色、初始位置和已经完成次数,都埋在历史视频里。核心问题在于,策略需要把"过去发生了什么"与"现在该做什么"放进同一次决策,而不是把记忆当成任务之外的附加模块。

机器人控制还受到时间尺度的约束。主摄像头按低帧率保留过去,腕部相机保留当前图像,本体状态以归一化向量输入动作头;每次动作不是一步,而是预测一小段动作块,然后执行前几步再重新决策。这样一来,历史表示既要保留分钟级线索,也要在亚秒级临界路径上完成语言生成和动作采样。记忆质量与控制延迟是同一个系统问题的两面:把历史压得太狠会丢证据,完整重算又会让机器人停等模型。

1.2 三类记忆路线都存在写入时承诺

现有 VLA 记忆机制大致可以分成三类。第一类是检索库,在每次决策前按相似度挑选若干历史片段,优点是上下文短,缺点是检索器必须预判未来问题,遮挡、计数和顺序任务中的关键帧可能没有足够的文字或视觉相似度。第二类是学习式压缩器,把连续视频映射为少量 token 或摘要,计算成本下降,但细粒度颜色、空间关系和动作先后顺序会在压缩时被固定丢弃。第三类是循环状态或记忆 token,每一步更新一个固定维度的隐状态,推理便宜,却容易覆盖早期事件,也很难在任务改变后重新读取原始画面。它们共同依赖一个假设:分钟级历史太长,不能直接交给视觉语言主干。

Qwen3.5-4B 这类现代 VLM 已经提供 262k token 的上下文窗口,项目估算 60 秒历史只使用约 5.6k token,足以在不做专用压缩的情况下保存分钟级视频。进一步看,SimpleMemVLA 的路线判断不是"任何任务都应该把全部历史塞进去",而是当上下文预算允许时,延迟证据选择比提前写入更稳妥。 模型在决策时可以同时看到当前子任务、视频时间顺序、本体状态和历史画面,让注意力机制自己判断哪一个时间片段与当前动作有关。

直觉理解:把历史写入记忆像整理一间实验室。检索和压缩要求你在整理当天就决定哪些纸张会有用,原生视频则先把带时间的实验记录完整放进柜子,真正做实验时再按问题翻阅。柜子足够大时,延后筛选通常比过早丢弃更安全。

2. 整体框架:视频主干、子任务通道与动作专家

2.1 输入输出接口与时间采样

SimpleMemVLA 的一次决策输入由四部分组成:主摄像头的历史视频、只表示当前时刻的腕部或辅助相机图像、描述总体任务和机器人形态的文本提示、可选的当前本体状态。历史视频按 history_video_sec 和 history_video_fps 从原生控制流中抽样,帧数被调整为偶数,因为 Qwen3.5 的视频时间 patch 以两帧为一个单元;每个视频 patch 带有明文时间戳,沿用主干预训练时的格式 。输出先是最多 64 个 token 的当前子任务,再由动作头生成一个长度为 action_horizon 的动作块,推理时执行动作块前部并重新观察。这个接口把"理解历史"和"生成动作"分成可检查的两步,同时保留端到端梯度。

V t = Sample ⁡  ⁣ ( O t − H : t ,    f h ,    f n ) , I t = O t wrist , s t = Normalize ⁡ ( q t ) V_t = \operatorname{Sample}\!\left(O_{t-H:t},\; f_h,\; f_n\right), \qquad I_t = O_t^{\text{wrist}}, \qquad s_t = \operatorname{Normalize}(q_t) Vt=Sample(Ot−H:t,fh,fn),It=Otwrist,st=Normalize(qt)

这里的 H H H 是历史时间窗口, f h f_h fh 是历史采样帧率, f n f_n fn 是原生控制帧率; V t V_t Vt 和 I t I_t It 分别进入视频通道和图像通道, s t s_t st 是与动作关节布局一致的本体状态。模型先在多模态序列上产生隐藏状态 H t H_t Ht,再从子任务跨度中收集条件,动作头依据噪声时间 τ \tau τ 预测速度场 u θ u_\theta uθ。这意味着,历史并没有被转成一个额外的 memory vector,而是保留在主干的空间和时间位置编码里,后续注意力仍可按时间位置访问它。

z t = Backbone ⁡ ( V t , I t , p t ) , c t = Gather ⁡  ⁣ ( z t ,    S t ) , a ^ t = DiT ⁡ ( c t , s t , τ ) z_t = \operatorname{Backbone}(V_t, I_t, p_t), \qquad c_t = \operatorname{Gather}\!\left(z_t,\; \mathcal{S}_t\right), \qquad \hat{a}_t = \operatorname{DiT}(c_t, s_t, \tau) zt=Backbone(Vt,It,pt),ct=Gather(zt,St),a^t=DiT(ct,st,τ)

其中 p t p_t pt 是包含机器人类型、控制频率和总体任务的提示, S t \mathcal{S}_t St 是子任务答案跨度。Gather 同时取出该跨度的隐藏状态和词元嵌入,并保存它们的多维位置索引;动作头接收的是这组连续表示,而不是一条孤立的子任务字符串。信息流的关键不对称是:历史以视频形式进入语言视觉主干,动作专家只看到子任务跨度和本体状态。

2.2 训练与推理的角色分工

训练时,数据样本已经包含专家动作块和当前子任务文本,模型可以用教师答案标出子任务 token,并同时计算语言监督与流匹配动作损失;推理时则先根据历史生成子任务,再把实际生成的 token 隐状态送进动作头。这里的训练和部署并非两套接口:代码用同一个 subtask_span_mask 规定哪些 token 属于条件跨度,避免训练时取错位置、部署时取另一段文本。推理阶段还要补上结束标记和换行 token,以保持动作头看到的条件形状与训练一致。

这里的关键是,子任务并不是一个供人阅读的解释性输出,而是历史证据进入动作专家的窄通道。消融实验显示,只替换子任务文字并不能复现完整收益,生成子任务时的隐藏状态才携带了较丰富的视觉上下文;如果只给当前画面,或者打乱历史帧顺序,遮挡和计数任务会一起失败。换句话说,语言跨度承担了接口责任,隐藏状态承担了信息传输责任,二者不能简单等同。

难点提示:这像一位调度员给机械师下达"处理三号阀门"的工单。工单文字让机械师知道当前子任务,但调度员刚才查看过的时间表、报警记录和现场照片,才解释为什么是三号阀门。SimpleMemVLA 把这些信息留在调度员的工作记忆里,再用生成工单时的内部状态传给机械师。

3. 核心机制一:原生视频上下文延后选择证据

3.1 时间戳视频怎样成为可读记忆

SimpleMemVLA 不把历史帧拼成一串无序图片,而是保持视频通道的时空结构。build_simplememvla_messages 为主摄像头放入 video 占位符,为不属于历史集合的腕部相机放入当前 image 占位符,再用文本明确说明每一帧带有时间戳。处理器生成视频 patch 时,主干拿到的是与预训练视频任务相同的时间组织方式;历史画面距当前越远,仍然能凭时间位置被注意力访问。这样设计的工程价值是,记忆机制没有新增一个需要单独训练、单独调参和单独导出的模块,模型可以复用已有的视觉时间编码。

python 复制代码
# simplememvla/data/messages.py
def derive_video_sampling(
    history_video_sec: float, history_video_fps: float, native_fps: float
) -> tuple[int, int]:
    n_frames = max(2, round(history_video_sec * history_video_fps))
    if n_frames % 2:
        n_frames += 1
    stride = max(1, round(native_fps / history_video_fps))
    return n_frames, stride

def variable_history_frames(num_real_frames: int) -> int:
    num = int(num_real_frames)
    return max(2, num + (num % 2))

这段代码完成了两个容易被忽略的约束。第一,stride 把原生控制频率换算成历史采样间隔,保证不同 benchmark 的控制频率差异不会改变时间窗口含义;第二,历史帧数被补成偶数,直接匹配视频时间 patch 的大小。variable_history_frames 还允许短 episode 只提供真实存在的帧,不用用大量复制帧假装已经经历了完整窗口。同一套采样规则被训练数据集、普通闭环策略和流式策略共用,避免训练与部署看到不同的历史布局。

3.2 消融实验检验历史是否真的被读取

如果模型只是在当前画面上猜测,增加历史窗口不应该稳定改善任务,也不应该对特定历史片段的遮挡产生有方向的反应。SimpleMemVLA 的消融给出相反证据:在 RMBench 的盖积木任务中,30 秒窗口仍保留约 25 秒前的遮盖过程,机器人可以完成;窗口缩短到 15 秒、关键遮盖帧被移出后,任务失败。按键计数任务也随窗口缩短逐步退化,帧顺序打乱或只保留当前画面时,两个任务都无法完成。直观理解是,模型必须真的翻回那一页记录,这组结果把"有历史"与"读到了正确历史"区分开来。

python 复制代码
# simplememvla/data/messages.py
def build_simplememvla_messages(
    camera_tags, instruction, robot_tag, control_frequency_hz,
    action_horizon, camera_labels, subtask=None,
    history_camera_tags=None,
):
    if history_camera_tags is None:
        history_camera_tags = list(camera_tags)
    history = set(history_camera_tags)
    content = []
    for tag in camera_tags:
        content.append({"type": "text", "text": f"{camera_labels[tag]}:"})
        content.append({"type": "video" if tag in history else "image"})
    content.append({"type": "text", "text": build_embodiment_prompt(
        instruction, robot_tag, control_frequency_hz, action_horizon,
        num_cameras=len(history_camera_tags),
        num_current_cameras=len(camera_tags) - len(history_camera_tags),
    )})
    messages = [{"role": "user", "content": content}]
    if subtask:
        messages.append({"role": "assistant", "content": subtask})
    return messages

这段消息构造代码说明了历史与当前输入的结构性区分。主摄像头的每个样本都以 video 进入处理器,腕部相机若没有加入历史集合,就只放入当前 image;提示词还明确告诉模型哪些画面带时间戳、控制频率是多少以及要预测多少个动作。模型不是把几路相机简单串在一起,而是借由输入类型把"过去"与"现在"编码成不同角色。 这也是为什么在只改变无关历史时,输出应当保持稳定;真正有关的片段改变,才会沿注意力路径影响动作。

4. 核心机制二:子任务隐藏状态连接语言与动作

4.1 为什么不把整段主干状态直接交给动作头

把整段视频语言序列直接接到动作头,会同时带来计算量、位置对齐和训练目标冲突。语言视觉主干需要处理大量视频 patch、系统提示和多模态占位符,动作专家真正需要的是与当前子任务有关的条件。如果动作头对所有 token 都做跨注意力,历史 patch 数量一变,动作条件的长度和统计分布就会变化;如果只把生成的子任务文字重新编码,历史证据已经被压成了有限的词面,颜色、空间位置和事件顺序可能丢失。SimpleMemVLA 取一个窄而稳定的折中:子任务跨度的 token embedding 提供离散语义,主干对这些 token 产生的 hidden states 提供上下文证据,再拼接一个归一化本体状态。

C t =   E ( y t , 1 ) , ... , E ( y t , m )   ;    H t , 1 , ... , H t , m   ;    P t , 1 : m   , a t ∼ p θ ( ⋅ ∣ C t , s t ) C_t = \left\\,E(y_{t,1}),\\ldots,E(y_{t,m})\\,;\\;H_{t,1},\\ldots,H_{t,m}\\,;\\;P_{t,1:m}\\,\\right, \qquad a_t \sim p_\theta(\cdot\mid C_t, s_t) Ct=E(yt,1),...,E(yt,m);Ht,1,...,Ht,m;Pt,1:m,at∼pθ(⋅∣Ct,st)

其中 y t , 1 : m y_{t,1:m} yt,1:m 是子任务 token, E E E 是输入嵌入, H t , 1 : m H_{t,1:m} Ht,1:m 是主干隐藏状态, P t , 1 : m P_{t,1:m} Pt,1:m 是对应的位置编码。代码通过 span 逐样本收集三类张量并补齐到批次最大长度,动作头的条件注意力只在有效 token 上工作。历史信息经过主干的自注意力汇聚,再通过隐藏状态进入动作专家;子任务文字负责对齐接口,不能替代隐藏状态。

4.2 子任务监督与答案跨度掩码

训练数据里的子任务是普通 assistant answer,模型用 token-level cross entropy 学会根据历史描述当前正在进行的子目标。</think> 后的答案跨度被标成监督区域,其他系统提示、用户问题和视觉占位符都设为 -100,因此语言损失不会要求模型复述整个输入 。推理时,策略生成同样的答案跨度,再用完全一致的掩码把生成 token 的隐藏状态交给动作头。这个接口设计看似只是一个 mask,却解决了多模态序列中最容易出错的索引问题:视频 token 数量、图片 token 数量和文本 token 数量会随处理器分辨率与历史窗口改变,硬编码起止位置会很快失效。

...详情请参照古月居

相关推荐
杜子不疼.3 小时前
没有真机,我给启元 Q1 造了一台“假机器人“:官方 SDK 示例跑通实录
人工智能·机器人
todoitbo3 小时前
启元机器人 Q1 SDK 接口实操:构建 aimdk_msgs 并用 ROS 2 本地 Mock 验证 Service 与 Topic
ai·机器人·二次开发·sdk·启元
lisw054 小时前
智能化机器人系统的模块化设计自动化!
机器学习·机器人·自动化
敢敢のwings7 小时前
TACDYN-WAM 深度解读:机器人如何预测接触变化,并据此生成动作
人工智能·机器学习·机器人
敢敢のwings8 小时前
Super Odometry 2.0:相机与激光雷达退化后,机器人如何继续估计运动
人工智能·数码相机·机器人
ck0135799 小时前
视频片段提取与视频修剪的区别:多时间段选择及导出流程
人工智能·音视频·视频
沐飞工具箱9 小时前
游戏、漫画、视频里的外语怎么实时翻译?从 OCR + AI 原理聊聊 VoriTrans 屏幕翻译
人工智能·游戏·实时互动·音视频·自动翻译
QY_Research_14 小时前
2026房地产NFT平台创业机遇与落地指南分析报告
大数据·人工智能·物联网·机器人·制造
小豆庄20 小时前
监控视频脱敏的追踪漂移与ID撞号陷阱
目标跟踪·ffmpeg·音视频·视频·敏捷流程