最近在啃VLA(Vision-Language-Action)模型相关的论文,读到一篇思路很直接、但又特别容易被忽略的工作,题目叫“HAMLET: SWITCH YOUR VISION-LANGUAGE-ACTION MODEL INTO A HISTORY-AWARE POLICY”。翻译成人话就是:把VLA模型从“只看当前这一帧就下决定”,升级成“看完前面几步再下决定”。
这个方向太贴近实际部署了。我在多个机器人操作实验里反复撞到同一个问题:VLA模型的推理是基于单帧图像和单条指令的,也就是说模型每次“看世界”都是失忆状态——上一秒发生了什么、手已经移动到了哪里、上一步动作是对是错,模型一概不知。这导致长程任务里动作链条越跑越歪,而且没有任何自纠错能力。而HAMLET这篇论文给的解法不是重新训练一个大模型,而是在现有VLA外面挂一个轻量的历史感知模块,把过去的帧、语言指令、动作序列打包成条件信息,引导VLA输出更合理的动作。
这篇文章我不会复述论文全文,那不是我的风格。我更想把自己读完之后的架构理解、训练细节、实验设计逻辑,以及我在自己的模拟项目里复现类似思路时踩过的坑,一起整理出来。无论你是想跟读这篇论文,还是打算把历史感知这个能力加到自己的VLA方案里,这篇文章应该都能让你少走几段弯路。
1. 论文在解决什么痛点
1.1 VLA模型的“单帧失忆”问题
先聊清楚VLA模型的原罪在哪里。现在主流的视觉-语言-动作模型,本质上就是一个“多模态条件生成模型”。你给它一张当前摄像头图像、一段自然语言指令,它输出一串离散的动作token。整个过程跟多轮对话最明显的区别在于:语言模型在对话时会保留前面几轮对话作为上下文,而大多数VLA模型在每一步推理时,只把当前帧和当前指令拼进模型,历史信息完全不进入条件空间。
表面上看这没什么问题,因为很多机器人任务被看作马尔可夫决策过程,理论上只需要当前观测和当前目标就可以决定动作。但现实世界不是这样的,原因有三个。
第一,单帧图像存在严重的遮挡和歧义。比如机械臂在抓取一个物体时,物体一开始在视野里,等你手伸过去,物体可能在镜头里被机械臂自己挡住。这时候只看当前帧,模型根本不知道目标物体在哪,更别说判断“自己是不是已经抓稳了”。
第二,动作连续性被撕裂。同一段任务里,如果你让模型看连续的几帧,你会发现每个中间动作都依赖前一个动作的结果。比如“先推开门,再拿杯子”,模型在第50帧时如果不知道第49帧自己已经推开门了,就会反复尝试执行“推门”动作。单帧推理在这种多阶段任务里几乎必然导致抖动和重复。
第三,错误不可恢复。开环执行时,只要某一步动作错了——比如抓取时手指差了1厘米——后续所有动作都会在错误的状态上继续推导。没有历史信息,模型无法发现自己已经偏离了预期轨迹,更不会主动纠正。
我在自己的仿真环境里做过一个很简单的实验:让一个开环VLA模型执行“拿起红色方块,放到蓝色托盘里”。模型在第一步定位红色方块时出了一个小错,手指碰到方块但没抓紧。结果后面所有步骤都按照“已经抓起来了”继续执行,最后视觉看起来像在完成一个完全不存在的过程。这个现象在真实机器人上更显著,因为真实世界还有物理扰动、物体滑落、外部干扰等不可控因素。HAMLET这篇论文的核心出发点,就是要把这些“被丢掉的历史”捡回来。
1.2 为什么HAMLET是“切换”而不是“重训”
这篇论文标题里用了“SWITCH”这个词,我觉得选得很准确。它的意思是:不需要为带历史感知的任务重新训练一个VLA模型,而是把现有的VLA模型当作一个“动作生成器”,在外面加一个轻量历史融合模块,就能把模型切换到历史感知模式。
为什么要这么做,直接原因是性价比。现在一个可用的VLA底座模型参数量动辄几十亿到上百亿,如果想让它支持时间序列输入,需要把模型架构改成能处理多帧输入的形式,意味着从头预训练或者全参微调。普通团队根本背不动这个成本,一个完整的预训练流程少说也要上千张卡跑几周。
间接原因是模态差异。VLA模型通常采用视觉tokenizer、语言tokenizer分别编码图像和指令,再把两种token拼在一起进Transformer。如果要在输入端加入历史信息,不是单纯把历史帧的像素拼接起来就行,还需要把历史动作序列、时间戳、任务进度等异构信息统一编码。这就涉及模型内部的跨模态对齐问题,直接在原模型上改会破坏原有知识。
HAMLET的做法是借鉴了条件生成模型里常见的“控制模块”思路,也就是把VLA当成冻结的主干网络,历史信息经过一个小型编码器变成一组“任务条件特征”,再通过加权融合的方式注入当前观测的embedding空间。这样VLA自带的视觉语义能力和动作生成能力全部保留,新增的历史感知能力相当于一个辅助的外挂模块。
这个设计在工业落地时特别友好。你可以今天先部署一个标准的VLA推理服务,明天在不变更主线模型的情况下加上历史模块,两者可以平滑切换。如果你把整个VLA模型当作一个成熟的专家,HAMLET做的事情就是给这位专家配上一个记事本。
2. HAMLET的架构设计与模块拆解
2.1 历史信息从哪几种渠道进来
论文里的历史感知模块并不是简单的“把前K帧图像拼成一排”,而是把三类历史信息同时编码,再融合。我自己整理下来,这三路分别是:
第一路是历史视觉帧序列。即过去若干时刻的RGB图像,通常是固定间隔采样,比如每秒取3帧,总计取最近6到8帧。这路信息的作用是让模型知道“环境前一刻长什么样”,用于捕捉物体移动、手臂位置变化、遮挡恢复等视觉上下文。
第二路是语言指令和任务描述。注意,这里说的不是当前这一步的指令,而是整条指令序列,包括中间阶段的目标描述。比如任务“把盘子里的苹果拿起来放到桌子左边”,在中间某个时刻,模型需要知道“已经完成了拿苹果这半段,接下来是放”。HAMLET把这类信息按时间顺序编码,给模型提供一种“任务进度感”。
第三路是历史动作序列。这里指的是模型自己过去输出过的动作token,或者实际采集到的电机控制信号。机器人动作空间通常是一个多维向量,比如六轴末端位姿、夹爪开合度等。把动作序列喂给模型,相当于模型能看到自己的“运动轨迹”和“行为惯性”,它才知道自己刚才的手势姿势和位置。
这三路信息有各自的编码方式,在进入融合之前不会直接拼接到一起。原因很简单:图像特征是1024维的视觉embedding,语言特征是512维的文本embedding,动作序列则是连续数值,三种数据分布差异极大,直接拼接会导致梯度震荡和表征失衡。论文采用的方式是各做一个轻量特征投影层,先把三类信息映射到统一维度,再进入下一步处理。
2.2 历史编码器与信息压缩策略
历史编码器在论文里接手的输入是一个“时间段”而不是“单帧”。实际操作中,它会把时间序列里的每一帧逐帧编码成特征向量,然后做一个加权平均或者用一个小型Transformer做时序聚合。我在复现时发现,直接用平均池化会丢失关键事件信息,比如“第5秒时物体被碰翻”这个瞬间,如果被平均掉就完全失效了。
更合理的做法是用一个可学习的注意力聚合层。具体来说,给序列中每一帧的特征计算一个重要性权重,然后按权重求和。比如某帧中机械臂刚碰到物体,说明抓取事件正在发生,这帧的重要性就会更高。这个注意力权重是通过一个小型MLP从当前观测里学出来的,推理时动态调整。
历史动作序列的处理相对麻烦一点。动作token是离散的,不同任务的token长度不一样。如果直接输入Transformer,GPU显存开销会随历史长度线性上涨。HAMLET的解决方案是把动作序列压缩成一个“行为摘要向量”,大概思路是:将整段动作序列当作一个序列编码任务,输出一个定长向量,代表“过去一段时间内动作风格的浓缩”。这么设计的好处是无论历史有多长,最终进入融合分支的向量维度都是固定的,不会造成上下文长度爆炸。
语言指令的处理在论文里不算复杂,直接用VLA模型自带的语言编码器对当前指令编码,再额外存一个“历史指令列表”编码。我在对照实验中发现,把历史指令拆开处理比整段拼接效果更好,因为机器人任务往往在一轮对话里包含多个子目标,拆开后模型能区分不同阶段的目标边界。
2.3 门控融合机制:给每个时刻的“记忆”加权
这是整篇论文里我个人认为最巧妙的部分。HAMLET不把所有历史信息无差别地注入当前决策,而是用一个门控机制动态决定“此刻应该参考多少历史”。门控的物理含义可以类比人的注意力:有些任务里当前帧信息已经足够,比如一个静态抓取任务,模型完全可以只看当前画面,过多历史反而引入噪声;但有些任务里当前帧是模糊的,比如物体被遮挡,就必须更多依赖之前的记忆。
门控融合层的输入是当前观测embedding和历史上下文embedding,输出是一个介于0到1的权重标量。这个标量会作用在历史embedding上,然后与当前观测embedding相加。具体公式写出来大致是:
( h_{fused} = \lambda \cdot h_{history} + (1 - \lambda) \cdot h_{current} )
其中 (\lambda) 就是门控权重。极端情况下,如果 (\lambda) 趋近于0,HAMLET就退化成标准的无历史VLA模型。这个设计在任务切换时特别好用——执行静态任务时模型会自动降低历史权重,执行动态任务时权重升高,相当于自动判断任务对记忆的依赖程度。
我在具体实现时选用了sigmoid输出代替硬编码阈值,让权重可以更平滑地变化。训练初期门控权重会偏向0,也就是模型先学会了基于当前帧做预测,训练后期再慢慢学习如何利用历史信息。这个课程式训练节奏让整个模块的收敛稳定了不少。
3. 训练范式与数据组织方式
3.1 数据格式改造:从单帧样本到时间序列样本
想要训练HAMLET,最根本的变化是数据格式。标准VLA的数据集一般存成“{图像, 指令, 动作}”三元组,每个样本相互独立。HAMLET需要的则是“{历史帧序列, 历史动作序列, 当前帧, 指令, 动作}”这样的时间块样本。
我在整理训练数据时用了一个标准的滑动窗口策略。假设任务总长是120步,窗口长度是8步,那么我会从第9步开始逐帧切窗,每个窗口内包含前8步的历史信息以及当前步。这相当于把一个长任务拆成很多个重叠的子序列,数据量会比原来放大好几倍。
这里有一个必须注意的问题:独立采样会造成严重的偏置。如果每个窗口都是从任务中间随机截出来的,模型会看到大量“任务进行到一半”的场景,反而缺少从零开始的经验。我后来采用了一个混合采样策略,前30%的窗口从任务开头切,让模型多学开局状态;后70%均匀分布在任务各阶段,保证中间状态覆盖。
数据增强也是这个阶段的重头戏。我在图像通道加了亮度扰动和随机遮挡,用来模拟真实环境中传感器噪声。动作通道加了小幅高斯噪声,避免模型对历史动作过度迷信。这一步虽然简单,但在后续实验里的收益非常直接,模型的鲁棒性提升了一个档位。
3.2 训练三阶段:预训练、微调与冻结策略
HAMLET的训练分成三个明确的阶段,阶段之间切换时要特别注意学习率和优化器状态的清扫。
第一阶段是“预热对齐”。这个阶段冻结VLA主干,只训练历史编码器和门控融合层。目的是让新增模块先学会把历史信息映射到和当前观测一致的embedding空间里。这个阶段损失函数可以直接用VLA动作预测的交叉熵,不需要额外改。学习率可以调大一点,我用的是3e-4,跑一万步左右就基本能看到历史分支的输出不再是一团乱噪声。
第二阶段是“联合微调”。把VLA主干里靠近输出端的最后几层Transformer解锁,和历史模块一起微调。这个阶段的意图是让动作生成头适应带历史条件的特征表示。注意,这里不是全参微调,只解锁最后三分之一的层,否则历史模块刚学到的表征会被主干的大规模梯度变化冲掉。学习率要降一个数量级,我用的是3e-5。
第三阶段是“任务适配”。如果要在特定真机任务上部署,可以把任务演示的轨迹数据混合进训练集,用1e-5的学习率跑几百步。这一步主要是让模型熟悉目标环境的物理特性和物体布局,不需要大动干戈。
我自己的习惯是在第二阶段结束后做一次快速的回放评估,拿旧任务样本重新评测一遍,确认VLA原有能力没有被破坏。这个检查特别重要,因为历史模块加入后,当前观测的embedding会被融合分支污染,如果门控权重分配不当,可能会出现“原始能力退化”的问题,表现为简单任务反而做不好了。
3.3 轻量训练路线的成本对比
这部分有很多人问过,到底值不值得为了历史感知专门训这样一个模块。我按自己的实际配置算一笔账:假设VLA底座是几十亿参数的开源模型,历史编码器只是一个几千万参数的小型Transformer。
如果走全量微调路线,需要的显存至少是80GB级别乘以8卡,训练周期在两周以上。而HAMLET的轻量路线在第一阶段只用训练历史模块参数,占全部参数的比例不到5%,冻结模式下对显存的需求大约只是全量微调的40%,普通双卡3090级别的配置勉强能跑起来。如果只做单任务部署,整个训练周期可以压缩到一到两天。
这个成本差异决定了HAMLET这种方案更适合谁:预算有限的人、需要在多场景快速迭代的人、以及希望把同一个VLA复用在多个不同任务上的工程团队。不需要为了一个历史感知能力就推倒重来,这是它最大的现实价值。
4. 实验设计逻辑与关键结论解读
4.1 长程任务效果:历史长度怎么影响成功率
论文里最核心的一组实验是在长程操作任务上对比不同历史窗口长度下线塔效果。这里的长程指的是超过30步的序列任务,比如先码放、再抓取、再转移、再堆叠这一套连续动作。
实验结论可以概括成一个短语“够用就好”。历史长度从0增加到8时,任务成功率显著提升,原因很简单,模型开始能感知自己的动作轨迹,规避了重复和振荡;但是继续增加到16或更高,成功率的提升就非常有限了,某些任务甚至出现轻微下降。这个现象的解释是:过长的历史里包含了大量无关阶段的信息,比如任务早期探索阶段的无效动作,这些噪声干扰了当前决策。
我在复现时发现一个官方实验里没有细讲的细节:历史窗口的采样间隔比窗口长度更重要。如果历史帧来自均匀的每秒3帧,那么在快速运动场景下,相邻帧之间差异太小,提供的信息量很低。更好的做法是根据动作幅度自适应采样,动作变化快的时间段多采样几帧,动作平缓的时间段少采样几帧。我在一个托盘操作项目里用这个自适应采样方法,成功率比固定采样高了接近一成。
4.2 语言条件恢复:历史模块与语言指令的协作
论文有一个让我印象深刻的实验,叫做“语言条件恢复”。场景大概是这样的:机器人执行一个任务,中途有一个物体被外力碰倒,然后操作员在语言通道里发出一个新指令“红色物体倒了,把它扶起来再继续”。作者展示了HAMLET能正确处理这个中途切换,而基线VLA模型会无视新指令,继续执行原来的计划。
这里起作用的是历史动作序列与语言指令的交叉信息。模型看到历史里夹爪已经离开红色物体、当前图像里物体是倒下的,再结合新指令中的“扶起来”,判断出当前任务状态和语言指令的目标不匹配,于是重新生成新的动作序列。而基线模型只看到“当前画面里物体倒下”加上“继续执行原计划”,完全无法把两段信息联想到一起。
我在自己的仿真实验里也尝试了类似场景,把“到达某位置”改成“如果有人推了物体就重试”这类条件性指令。HAMLET架构在语义理解上有一个天然优势:语言编码器的表征在融合过程中被历史信息增强了,模型对指令的理解更接近“上下文相关的意图推断”,而不是简单的关键词匹配。
4.3 自适应历史权重:门控机制的泛化能力
论文作者在实验里单独评估了门控权重在不同任务上的分布情况,结论是门控确实学出了一些“可解释的偏好”。比如在“移动物体到目标点”这类任务里,门控权重在中后期明显升高,因为此时判断是否到达目标需要结合历史轨迹;而在“原地旋转”这类任务里,门控权重始终很低,因为这类动作只依赖当前朝向。
这说明门控机制的成功不是偶然的,它实际上学了一个隐式的“任务状态分类器”。我试过把门控权重可视化输出,发现它在任务快要结束时有一个明显的峰值——因为此时模型需要回忆整个轨迹来判断“是否完成了全部步骤”。这个发现对调试特别有用,如果某个任务里门控权重一直在高位徘徊,说明任务本身设计得过于依赖记忆,可能需要重新设计部分动作序列。
5. 复现与实操经验总结
5.1 数据准备与训练环境配置清单
如果你打算自己复现HAMLET的思路,我把整个环境的配置清单整理成了一张表格,方便直接对照:
| 配置项 | 推荐选择 | 说明 |
|---|---|---|
| VLA底座模型 | 开源视觉-语言-动作模型 | 必须带语言编码器和动作解码器 |
| 训练框架 | PyTorch + DeepSpeed | 冻结合并参数时节省显存 |
| 历史编码器 | 小型Transformer(4层左右) | 参数量控制在千万级 |
| 门控融合层 | 单层MLP + sigmoid | 直接在embedding维度上计算 |
| 采样策略 | 动作自适应采样 | 动作幅度大时加密采样 |
| 训练数据 | 长任务轨迹 + 单步混合采样 | 前30%窗口从任务开头切 |
| 学习率策略 | 预热后阶梯下降 | 冻结阶段3e-4到3e-5 |
| 评估环境 | 仿真环境 + 真机各一组 | 仿真调参,真机验证 |
训练时我的显存配置是四张24GB显卡,冻结VLA主干后,历史模块加融合层的参数量在几千万,实际占用的显存大头其实是输入端的视觉特征缓存。如果想进一步压缩显存,可以把历史帧的图像分辨率降低到224x224,只在当前帧上保留较高分辨率。我测过之后发现,对最终成功率的影响很小。
5.2 部署推理时的窗口管理与状态维护
训练完成后,落地部署时有一个容易被忽略的问题:推理阶段怎么维护历史窗口。训练时每个样本是预先从完整轨迹里截好的,但推理时是实时生成,只能靠不断往滑动窗口里追加数据。
我的做法是维护一个环形缓存,每步推理完当前帧和动作,把它们写入缓存,下一轮再从缓存里取出最近K帧作为历史输入。这里有一个耗时风险点:如果用PyTorch的拼接操作把历史帧和当前帧拼在一起,每次都会触发一次新的内存分配,推理延迟会增加不少。我最后改用显存预分配策略,把窗口区域的张量在启动时一次性分配好,后续只用索引填充更新,延迟降了下来。
还需要注意历史窗口的更新频率策略。如果每一帧都更新并重新推理,机器人会频繁抖动;我设为每3帧滚动更新一次,相邻推理之间动作输出保持一致性更强。尤其是抓取类任务里,关节角度突变会导致夹爪抖动,降低成功率。
5.3 常见复现问题与排查技巧实录
我把复现期间遇到的高频问题直接整理成一个速查表,每个问题都附了排查思路:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 历史模块加进去后任务成功率反而下降 | 门控权重初始值太大,噪声注入过多 | 把sigmoid输出初始偏置设为-2,让初始权重接近0 |
| 训练时历史分支梯度消失 | 历史编码器输入范围不对,图像未归一化 | 检查历史图像通道的顺序和数值范围 |
| 长窗口推理时显存溢出 | 历史帧缓存未复用,每次推理都重新拼接 | 使用预分配环形缓冲,避免缓存Tensor反复创建 |
| 简单任务被复杂化,动作出现多余的摆动 | 门控未正确学到低权重 | 将简单任务样本的比例增加到30%以上 |
| 语言条件恢复指令无效 | 历史动作序列编码丢失了动作顺序 | 检查动作序列编码器是否带有位置信息 |
排查时有一个技巧我几乎每次都用:把门控权重打印出来可视化。如果任务执行中大部分时间门控权重接近1,说明历史信息太容易被接纳,模型可能产生了记忆依赖;正常情况应该是一段时间内权重在0.3到0.6之间波动,只在关键阶段升高。如果权重曲线完全没有起伏,多半是门控层的梯度消失,需要检查融合层后的激活函数是否饱和。
5.4 几个值得后续尝试的改进方向
论文本身的方案已经闭环,但在实际工程项目里还有一些可以继续扩展的点。
第一是引入更细粒度的多模态时间对齐。历史视觉序列和历史动作序列本来就是同步采集的,但HAMLET把它们分开编码后再融合,丢掉了“哪个时刻的视觉对应当前动作状态”这种细粒度对齐信息。后续可以尝试在历史编码器里加一个跨模态注意力层,让视觉特征和动作特征在时间维度上显式对齐,理论上能提升长程操作的连贯性。
第二是把门控权重做成任务级可学习的条件输入。目前门控权重完全由当前观测和历史表征决定,如果能在输入里显式加入“当前任务类型”的编码,模型可以更快收敛到与任务匹配的记忆策略。
第三是把历史模块扩展到多机器人协作场景。目前的历史信息都来自单个机器人自身的传感器,如果能加入其他机器人传递过来的交互状态,那么协作任务里的预测会更准确。这部分需要硬件支持和通信协议设计,但架构上HAMLET的融合框架可以自然扩展。
我在实际项目里最想尝试的是把HAMLET的历史感知能力用于在线失败检测。既然模型能感知历史动作序列和任务进度,那么自然可以从中挖掘出“动作偏离预期”的信号。如果把门控权重的异常变化当作一个检测特征,再配一个异常分类头,理论上可以实现部署阶段的实时失败预警。这个功能一旦成熟,对实际产线的价值会非常大。
写在最后的一个经验
读HAMLET过程中,我最大的体会是,想提升VLA模型的实际表现,不一定非得把精力花在扩大模型规模和堆积训练数据上。很多问题出在决策过程没有记忆、没有上下文,一个设计得当的历史感知模块就能带来可观的性能提升。这种“小而稳”的改动,往往比盲目堆算力更值得优先尝试。
如果你准备在自己的项目里应用这个思路,我的建议是:先把门控机制的收敛状况作为第一优先级检查项,不要急着调模型结构。只要门控能学出合理的权重分布,后续历史编码器、数据采样和部署优化都会顺很多。如果门控权重一直是平的或者始终偏向一端,多半是数据本身的历史信息与动作关联太弱,先回数据层面修正,再继续往后调。