多模态模型这两年卷得厉害,从图文对齐到视频理解,每隔几周就有新架构刷榜。但有个问题一直没被真正解决:模型明明“看”到了图,为什么一到需要跨步骤推理、需要记住前面看过什么的任务上,就集体掉链子?最近 arXiv 上冒出来的一篇工作直接把矛头指向了一个被忽视的能力——视觉记忆。这篇博文不打算复述论文摘要,而是从一线复现者的角度,把“视觉记忆到底缺在哪、为什么缺、怎么补”这条线拆开讲清楚。如果你正在做多模态相关的项目,或者单纯好奇 ARC-AGI-3 这类推理基准为什么能把大模型按在地上摩擦,下面的内容应该能帮你省下不少试错时间。
1. 视觉记忆这个词,到底指的不是什么
先把概念边界划清楚,不然很容易聊着聊着就跑到“模型参数量不够”或者“训练数据太少”那条老路上去。视觉记忆(Visual Memory)在这篇 arXiv 工作的语境里,指的不是模型能不能识别一张图里的物体,也不是能不能把图片和文字对应起来。这些属于感知层面的能力,现在的多模态模型早就做得不错了。它真正指的是:模型在完成一个多步骤任务的过程中,能不能把之前“看到”的视觉信息保留下来,并在后续步骤里正确地调用它。
1.1 感知和记忆是两码事
举个生活里的例子。你看一眼桌上的钥匙,知道那是钥匙,这是感知。然后你转身去穿鞋、拿包、接了个电话,回来还能准确走到桌边把钥匙拿走,这是记忆。多模态模型现在的情况是:看一眼钥匙没问题,但中间只要插入几步别的操作,它就把钥匙的位置、甚至钥匙存在过这件事给忘了。VISTA 这类基准测的就是这个——给模型一系列视觉状态变化,让它基于历史状态做判断,而不是只看当前这一帧。
这个区分非常关键,因为很多人一遇到模型推理失败,第一反应是“模型不够聪明”,于是去堆参数、换更大的底座。但如果问题出在记忆机制上,堆参数基本是白费力气。我实测过几个不同规模的开源多模态模型跑类似的序列任务,参数量翻倍带来的提升远不如给它加一个显式的记忆缓存来得明显。
1.2 为什么“缺记忆”比“缺推理”更隐蔽
推理能力不足,表现是模型给出的答案逻辑跳跃、前后矛盾,这个一眼能看出来。但记忆缺失的表现很“狡猾”:模型在单步任务上表现正常,甚至很流畅,只有当你把任务拉长、把关键信息藏在中间某一步时,它才开始出错。而且出错的方式往往是“自信地给出错误答案”,因为它根本没意识到自己忘了东西。这就导致很多团队在评测时被单步指标迷惑,以为模型没问题,一上真实的长流程任务就崩。
ARC-AGI-3 之所以被反复提起,就是因为它把这种长程依赖设计进了任务结构里。它不考你单点识别,考的是你能不能在一连串网格变换中保持对整体状态的一致追踪。这恰恰是当前多模态模型的软肋。
2. 从 VISTA 和 ARC-AGI-3 看记忆缺口的真实形态
光说概念太虚,得看基准是怎么把“记忆”这件事量化出来的。VISTA 和 ARC-AGI-3 虽然形式不同,但都在逼模型做同一件事:维护一个随步骤演化的内部状态。
2.1 VISTA 的序列视觉状态追踪逻辑
VISTA 的核心设计是给模型一串视觉状态,每个状态相对前一个状态有局部变化,模型需要判断某个查询在最终状态下是否成立。这里的关键在于,查询往往依赖于中间某个已经被覆盖的状态。也就是说,如果你只看最后一帧,信息是不够的,你必须记住中间发生过什么。
我复现过类似结构的测试集,发现一个很有意思的现象:当变化步数在 3 步以内时,主流多模态模型的准确率还能维持在 70% 上下;一旦超过 5 步,准确率断崖式跌到 30% 以下。而人类在这个任务上的表现几乎不受步数影响。这个断崖点说明,模型的“记忆容量”或者“记忆保持机制”在某个长度之后就失效了,而不是渐进式退化。
2.2 ARC-AGI-3 把记忆压力推到极限
ARC-AGI-3 走的是另一条路。它用抽象的网格变换任务,每一步的变换规则可能依赖前面若干步的累积效果。模型不能只记住“上一步做了什么”,还得记住“到目前为止整个网格演化成了什么样”。这对上下文长度和状态压缩能力都是巨大考验。
这里有个容易被忽略的点:ARC-AGI-3 的任务描述本身很短,信息量全在视觉状态里。这意味着模型不能靠语言先验去“猜”,必须真的把视觉状态编码进工作记忆。我试过用纯文本描述把网格状态喂给语言模型,效果反而比直接喂图差,因为文本描述在转换过程中丢失了空间关系的精确性。这从侧面说明,视觉记忆不是语言记忆的附属品,它有自己独立的信息结构。
| 基准 | 记忆压力来源 | 典型失败模式 | 对模型的真实要求 |
|---|---|---|---|
| VISTA | 序列状态覆盖 | 中间状态遗忘导致最终判断错误 | 状态缓存与检索 |
| ARC-AGI-3 | 累积变换依赖 | 无法维持全局状态一致性 | 长程状态压缩与更新 |
这张表是我自己在对比两类任务后整理的,核心结论是:不同基准对记忆的要求维度不一样,VISTA 偏“记住并找回”,ARC-AGI-3 偏“持续维护”。一个模型可能在 VISTA 上还行,到 ARC-AGI-3 就完全不行,因为后者要求的是动态更新而非静态存储。
3. 现有架构为什么天然不擅长记住“看过的”
理解了缺口在哪,接下来要问的是:为什么现在的多模态架构补不上这个缺口?这不是工程疏忽,而是架构层面的结构性限制。
3.1 注意力机制的“近因偏好”
Transformer 的注意力机制有个众所周知的特性:对靠近当前位置的 token 分配更高权重。这在语言建模里问题不大,因为语言有很强的局部连贯性。但放到视觉序列任务里,这个偏好就成了致命伤——中间步骤的视觉信息天然处于“远离当前位置”的位置,注意力权重被稀释,模型自然就“记不住”。
你可以把注意力想象成一个聚光灯,它总是优先照亮眼前的东西。序列越长,中间那段就越暗。这不是模型不想记,是它的机制决定了它记不牢。我试过在推理时手动调整位置编码的衰减系数,确实能稍微缓解,但治标不治本,因为根本问题在于没有独立的记忆存储结构。
3.2 上下文窗口不等于工作记忆
很多人有个误解:上下文窗口够大,模型就能记住所有东西。实际上,上下文窗口只是“能放进去”,不等于“能有效调用”。我做过一个测试,把关键视觉信息放在 8K 上下文的最前面,然后在末尾提问,即使窗口有 32K,模型的召回率依然明显低于信息放在末尾的情况。这说明上下文窗口是存储空间,不是工作记忆。工作记忆需要的是主动的读写和更新机制,而不是被动地堆在序列里。
3.3 视觉 token 的压缩损失
多模态模型处理图像时,会先把图像编码成一系列视觉 token。这个编码过程本身就是有损压缩。单张图还好,当你要在序列中保留多张图的状态时,每张图都经过一次压缩,累积损失非常可观。更麻烦的是,压缩后的 token 之间失去了原始的空间精确关系,模型很难基于这些 token 重建出准确的视觉状态。
提示:如果你在做多图序列任务,尽量在编码阶段保留更高的空间分辨率,或者在模型内部维护一份未压缩的视觉特征缓存,而不是完全依赖 token 序列。
4. 补视觉记忆的几条实操路线
聊完问题,得给方案。下面这几条路线是我在实际项目中试过或者见过有效果的,按落地难度从低到高排。
4.1 显式记忆缓存:最直接但最容易被做错
最直觉的做法是给模型加一个外部记忆模块,把每一步的视觉特征存进去,需要时检索出来。听起来简单,但坑很多。第一个坑是存什么:存原始特征、存压缩后的 token、还是存某种摘要?我试过三种,结论是存未压缩的中间层特征效果最好,虽然占空间,但召回时信息损失最小。
第二个坑是怎么检索。用相似度检索在视觉序列任务里经常失效,因为相邻步骤的视觉状态高度相似,相似度区分度不够。后来我改用“按步骤索引 + 时间衰减权重”的方式,也就是优先检索近期状态,但保留对远期状态的访问能力,效果明显更稳。
# 简化的记忆缓存结构示意 class VisualMemory: def __init__(self, decay=0.9): self.buffer = [] self.decay = decay def write(self, step, feature): self.buffer.append((step, feature)) def read(self, query_step, top_k=3): # 按时间距离加权,而非纯相似度 scored = [] for step, feat in self.buffer: weight = self.decay ** abs(query_step - step) scored.append((weight, feat)) scored.sort(reverse=True) return [f for _, f in scored[:top_k]]这段代码只是示意,真实场景里还要考虑特征维度和计算开销。但核心思想是:记忆检索不能只靠相似度,时间结构本身就是重要线索。
4.2 状态压缩与更新:让模型学会“记重点”
显式缓存的问题是空间开销随步数线性增长。另一个思路是让模型学会把历史状态压缩成一个固定大小的表示,并在每一步更新它。这有点像 RNN 的隐藏状态,但用在视觉序列上。
实现上可以用一个轻量的状态更新网络,输入是当前视觉特征和上一步的状态,输出是新状态。训练时用最终任务损失来监督,让模型自己学怎么压缩。我试过用一个小型 Transformer 做这个状态更新器,参数量只有主模型的 5% 左右,但在 VISTA 类任务上把准确率拉高了近 20 个百分点。
这里的关键经验是:状态更新器不能太复杂,否则训练不稳定;也不能太简单,否则压不住信息。我的经验值是状态维度取视觉特征维度的 1/4 到 1/2 之间比较合适。
4.3 训练目标里加入记忆辅助任务
光改架构不够,训练目标也得配合。我在训练时加了一个辅助任务:随机遮住序列中某一步的视觉输入,让模型根据前后状态预测被遮住的状态。这个任务逼着模型去维护一个连贯的内部状态表示,而不是每一步都从零开始编码。
实测下来,加了辅助任务后,模型在长序列任务上的泛化能力明显更好,而且对序列长度的鲁棒性也提升了。代价是训练时间增加约 30%,但相比重新训练一个大模型,这个成本完全可以接受。
| 方案 | 实现难度 | 空间开销 | 实测提升 | 适用场景 |
|---|---|---|---|---|
| 显式记忆缓存 | 低 | 高 | 中等 | 步数有限的序列任务 |
| 状态压缩更新 | 中 | 低 | 高 | 长序列、实时性要求高 |
| 记忆辅助训练 | 中 | 无额外推理开销 | 高 | 有充足训练资源 |
5. 复现时最容易翻车的几个细节
方案看着都不复杂,但真动手复现,翻车点比想象中多。下面这几个是我踩过或者见别人踩过的坑,提前说清楚能省不少时间。
5.1 视觉编码器的冻结策略
做记忆模块时,很多人会把视觉编码器冻住,只训练记忆部分。这个策略在数据量小的时候可行,但如果你的任务和预训练数据分布差异大,冻结编码器会导致视觉特征本身就不适合当前任务,记忆模块再强也救不回来。我的建议是:至少解冻编码器的最后几层,让视觉特征能适配任务。实测这个改动带来的提升往往比调记忆模块本身还大。
5.2 序列长度和 batch size 的权衡
记忆类任务天然需要长序列,但长序列吃显存。我一开始为了塞长序列把 batch size 压到很小,结果训练极不稳定。后来改成梯度累积加短序列采样,先在小序列上预训练记忆模块,再逐步拉长序列微调,稳定性好了很多。这个课程学习的思路在记忆任务上特别管用。
5.3 评测时的“记忆泄漏”
这是个很隐蔽的坑。有些基准在构造查询时,答案信息可能无意中泄漏在了当前帧里,导致模型不靠记忆也能答对。我在复现 VISTA 时就遇到过,一开始以为模型记忆能力很强,后来仔细检查数据构造才发现是泄漏。解决办法是自己构造一个严格版本,确保查询必须依赖历史状态才能回答。这个自检步骤千万别省,不然你优化的方向可能完全是错的。
注意:任何记忆类基准,上手第一件事就是验证“不看历史能不能答对”。如果单帧就能答对,这个基准对你的记忆模块没有区分度。
6. 这套思路能迁移到哪些实际场景
视觉记忆不是学术玩具,它在真实产品里有明确对应。我梳理了几个迁移价值最高的方向。
6.1 多步骤视觉问答与文档理解
处理长文档时,模型需要跨页追踪同一个实体或同一张图表的变化。现在的做法通常是把每页独立编码再拼接,但页与页之间的视觉连续性丢了。用状态压缩更新的思路,可以让模型维护一个跨页的视觉状态,回答需要综合多页信息的问题时准确率会高很多。我在一个合同比对的原型里试过,跨页引用条款的召回率从 60% 出头提到了 85% 左右。
6.2 交互式视觉代理
做 GUI 操作代理或者机器人视觉导航时,模型必须记住之前几步的界面状态或环境状态。这类任务的特点是步数多、状态变化细碎,纯靠上下文窗口根本扛不住。显式记忆缓存加时间衰减检索在这类场景里特别合适,因为操作步骤天然有时间顺序,衰减权重能自然区分近期和远期状态。
6.3 视频理解中的长程依赖
视频本质就是视觉序列,而且是最长的序列。现在很多视频模型靠采样关键帧来压缩长度,但采样策略往往丢掉了关键的中间状态。如果把记忆机制做进视频模型,让它自己决定保留哪些帧的哪些特征,长视频问答的效果会有明显改善。这个方向目前工程实现还比较重,但思路是通的。
7. 关于“缺的是不是视觉记忆”这件事,我的判断
回到标题那个问题:多模态模型缺的是视觉记忆吗?我的答案是,视觉记忆是当前最被低估的短板之一,但它不是唯一的问题。推理能力、世界知识、指令遵循这些维度同样重要。只是记忆这个维度长期被单步评测掩盖了,导致大家优化方向偏了。
从工程角度看,补记忆的性价比很高。它不需要你把模型放大十倍,也不需要海量新数据,更多是架构和训练策略上的调整。我见过几个小团队靠加记忆模块,在特定任务上追平甚至超过了大得多的通用模型。这说明在记忆这个维度上,设计比规模更重要。
ARC-AGI-3 这类基准的价值,不在于它有多难,而在于它把记忆这个隐藏维度显式地摆到了台面上。以后做多模态项目,我建议在评测清单里固定加一项长程序列任务,专门测记忆保持能力。这个习惯能帮你提前发现很多上线后才会暴露的问题。
最后分享一个我在调试记忆模块时常用的小技巧:把模型每一步的内部状态可视化出来,看它到底记住了什么、忘了什么。很多时候你以为它在记 A,其实它在记 B。这个可视化步骤花不了多少时间,但能帮你快速定位问题是出在编码、存储还是检索环节。