ZipMap:基于 Test-Time Training 的线性时间有状态 3D 重建
论文:ZipMap: Linear-Time Stateful 3D Reconstruction via Test-Time Training
会议:CVPR 2026(据参考材料)
arXiv / 项目主页 / 代码:参考材料中未提供,待补充
这篇论文解决什么问题
前面读过的 VGGT、π³ 都是同一路线:feed-forward 3D reconstruction。它们把多张图一次性塞进 Transformer,用 global attention 让所有 token 互相看,从而建立多视角几何一致性。效果很好,但有一个硬伤:
图片数量 N 增加时,global attention 的复杂度是 O((N×M)²)
其中 M×M 是每张图的 patch token 数
假设每张图有 M² 个 token,总 token 数 L = N×M²,attention 复杂度 O(L²)。10 张图还能跑,100 张很慢,700 张直接爆炸。ZipMap 要解决的就是这个可扩展性问题:
不让所有图片互相 attention,而是把整个场景压缩成一个可更新的隐藏状态(scene state),随帧线性更新。
一句话版本
ZipMap 把 VGGT / π³ 的“全局 Transformer 建图”改造成“带神经记忆的建图系统”:
images / video frames
-> image tokens(DINOv2 encoder)
-> TTT update:用当前帧的 K,V 把 fast weights 更新一步
-> fast weights W_hat = scene memory
-> 用 query 读 memory -> point / depth / camera / target-view RGB+depth
复杂度从 O(N²) 降到 O(N),并且支持 streaming:来一帧,更新一次。
和 VGGT / π³ 的定位差异
| VGGT / π³ | ZipMap | |
|---|---|---|
| 思想 | 全局理解(所有视角同时优化) | 持续记忆(state update) |
| 关键机制 | global attention | TTT fast-weight update |
| 复杂度 | O(N²) | O(N) |
| 适合 | 几十张图 | 长视频 / 几百上千帧 |
| 类比 | 一次性 bundle adjustment | SLAM + Transformer |
ZipMap 并不是“不用 attention”:它保留 local-window attention(初始化自 VGGT frame-wise attention),真正替换的是昂贵的 global all-token attention,换成 TTT memory。
网络结构
整体流程:
Images
-> DINOv2 / VGGT-style encoder
-> image tokens
-> [local window attention <-> TTT block] × 24 层
-> fast weights W_hat(scene state)
-> query heads -> point / depth / camera
论文原文是 24 layers of local window attention interleaved with large-chunk TTT blocks,不是 24 个纯 TTT。两种模块分工:
local window attention:局部 token 交互(同一图的局部结构、邻近 patch、局部纹理几何)
TTT block: 全局场景信息写入与读取(替代 global attention)
TTT 参数的一部分初始化自 VGGT global-attention 参数。所以 ZipMap 可以看作对 VGGT aggregator 的可扩展化重构:
VGGT: frame attention <-> global attention <-> frame attention ...
ZipMap: local attention <-> TTT <-> local attention ...
TTT 模块:核心公式与 fast-weight 使用过程
⭐ ZipMap 论文里 TTT(Test-Time Training)模块的核心公式
TTT 的核心公式:q/k/v 投影、虚拟目标 Eq.(2)、fast-weight 更新 Eq.(3)、SwiGLU 与线性 attention 推导
q / k / v 投影
输入 token (d 维)先经过三个可学习的投影:
虚拟目标(virtual objective,Eq. 2)
TTT 的 fast-weight 更新不是由 3D 重建 loss 直接驱动,而是用一个自监督的“虚拟目标”:
要点:
- 它只依赖输入图片自己产生的 、,不需要任何 GT,所以训练和测试时都能算。
- 它不是最终的 3D reconstruction loss(那是 Eq. 9 那组 + query loss),它只是 TTT 层内部做 fast-weight 更新用的目标。
Fast-weight 更新(Eq. 3)
先求梯度:
然后做一步(或少量几步)梯度更新:
每个 token 还有一个网络预测出来的写入强度(per-token learning rate),控制“这个 token 写进 memory 的力度”:
注意这里有两个不同的 learning rate,别混:
η_i(Eq. 3 附近):每个 token 写进 fast weights 的强度,由网络预测
1e-4 / 1e-5:外层 Adam 优化器学习模型参数的步长
读取(query)
更新后的 fast weights 用于读取:
对输入视角, 是 image token 的 query;对 target view, 是 ray map 编码出的 ray query。
Fast-weight MLP:SwiGLU
fast-weight 函数不是简单的线性层,而是 SwiGLU:
为什么它像 attention(线性情形的直观推导)
先假设 ,loss 为 ,则 ,一步梯度下降得到:
用 query 读取:
后半部分 就是没有 softmax 的 linear attention:匹配度 加权 。所以“K,V 通过梯度写进 W,Q 通过 W 读出”不是模糊的比喻,线性情形下几乎可以直接推导成 attention。SwiGLU 版本只是把容量变大。
⭐ ZipMap 的 TTT fast weight 更新和使用过程
TTT fast weight 的 write/read 流程、batch 与 streaming 更新、测试时执行
一个 TTT block 的完整 write / read 流程:
x
├── W_Q ────► q ──────────────────────────────┐
│ │
├── W_K ──► k │
│ │ │
│ ├── L_TTT(f_W(k), v) ──► ∂L/∂W │
├── W_V ──► v │ │
│ ▼ │
│ W ──► W_hat │
│ ▼
└────────────────────────────────────► f_W_hat(q) ──► output
用文字说:
- 当前 fast-weight 状态是 (可学习的初始化)。
- 当前输入产生 、、。
- 用虚拟目标 求梯度。
- 做一步更新 ,等于把当前视角的信息“写”进 memory。
- 后续网络使用 读取,再进 point / depth / camera heads。
“写”和“读”合起来构成一个完整的 neural scene memory:
Write: (K, V) ──► W_hat
Read: q_ray ──► f_W_hat(q_ray) ──► RGB / depth
流式更新(streaming)
普通 batch 模式是一次性用全部图片更新一次:
流式模式下,第 帧只用自己的 个 token 更新:
是当前帧的 token 数(例如 224×224、patch 14 时约 16×16 = 256),不是图片数量。每帧只处理当前帧 + memory,所以每帧 O(1)、总计 O(N)。
测试时照样执行
测试时没有 GT,但 仍然能算(、 都来自输入图片自己),所以:
image ─► x ─► K,V ─► 虚拟 loss ─► 一步梯度更新 ─► W_hat ─► Q query ─► 3D 输出
这就是 TTT 的精髓:outer loss( + query loss)只在训练时有;inner virtual loss(Eq. 2)训练、测试都有,因为它是 self-supervised 的,作用是构建 in-context associative memory。
训练目标(3.5 节)
总损失:
其中 。前三个是普通重建监督(点、深度、相机),后两个是 query loss(target ray 查 memory 恢复 RGB 和 depth)。
Point loss(尺度不变)
point map 在 local camera coordinates:
其中 是全局共用的尺度:
为什么除以 :L1 含 X / Y / Z 三个维度(所以有 3),除以 让误差变成相对误差——1 m 处错 10 cm 和 100 m 处错 10 cm 意义完全不同。
为什么 全局共用:所有 view 只能有一个整体尺度模糊,不能 view 1 scale = 1.2、view 2 scale = 0.7,这是多视图一致性的体现。
Depth loss(带不确定性)
- 第一项:置信度高时误差惩罚大。
- 第二项 :防止模型作弊把 全设成 0( 会让该项发散)。
- 等价于 Laplace negative log-likelihood 形式。
point 和 depth 共用同一个 ,隐式约束两个 head 的尺度一致。
Camera loss 与 reference view
第一阶段用第一帧作 reference view:
的 translation 也乘 ,因为相机基线与场景点必须同尺度。
之后论文去掉 reference view,改成 affine-invariant camera loss(受 π³ 启发):固定第一帧对 5–10 张图没问题,但 100、500、1000 帧时越往后越依赖第一帧,形成 reference-frame bias;去掉锚点对 long sequence 更有利。
Query loss(ZipMap 特有的部分)
训练时把一部分 view 当 input views 写 memory,另一部分当 target views:只给 target 的相机 ray,让模型从 scene state 预测 target RGB 和 depth。
query loss 只在 finetuning 阶段开启:先练好 reconstruct,再练“任意 target ray 读 memory”——类似 curriculum。
没有 query loss 时, 可能只是 aggregator 的中间状态;加上 target-ray RGB/depth 监督后, 才被真正训练成“可以被查询的隐式 3D 场景表示”。
平滑损失
还加了 point-map normal loss 和 depth gradient loss,保证局部平滑(避免深度 2.00 / 5.31 / 1.20 / 7.80 这种跳变)。
Fast-weight state 大小与训练规模
- ,SwiGLU hidden 。
- 、 是 , 是 ,参数共 M / layer。
- state size 不随输入帧数增长:10 帧和 1000 帧都是 ,这才是可扩展性的本质。
- 24 层 TTT 的 fast-weight state 约 M 1.5 亿参数(若每层都是 )。
三阶段训练(共 64 张 H100):
| 阶段 | 数据 | 迭代 | 学习率 | 说明 |
|---|---|---|---|---|
| Stage 1 | static datasets | 80K | TTT 1e-4,其他 1e-5 | 约 5 天,第一帧作 reference view |
| Stage 2 | dynamic datasets | 40K | 统一 1e-5 | 约 2.5 天,处理人 / 车 / 运动物体 |
| Stage 3 | 移除 reference view | 60K | 1e-5 | 增强 long-sequence 与 affine-invariant 重建 |
另外,scene-state query 额外 fine-tune 100K iterations:每个场景随机采样 views,一半 input、一半 target。
两个关键问答(侧边栏加星号)
⭐ L(fW(ki), vi) = −fW(ki)ᵀvi 是在哪里训练的?需要先把 W 训练好再冻结吗?
Eq.(2) 的 TTT 虚拟目标到底在哪里训练?是不是“先训练 k→v 的 W,冻结后再训练这个 loss”?
不是“先训练 W → 冻结 → 再训练这个 loss”。准确的说法是:
- 外层训练(outer training):ZipMap 正常训练整个网络时,就把 TTT 的一步临时梯度更新嵌在 forward pass 内部。
- 内层更新(inner TTT update):每次 forward 内,用当前 和当前输入的 、 计算 ,求梯度,做一步更新 ,再用 继续前向。
也就是说有两层优化:
外层:训练几天,Adam,loss = L_point + L_depth + L_cam + query loss
学习 W_Q / W_K / W_V、W 的初始化、η_i 预测器、heads……
内层:每次 forward / inference,loss = Eq.(2)
只临时更新 fast weights W ──► W_hat
为什么不需要“先单独训练好 k→v 的 W”?因为不存在一个预先定义好的正确 value:
自己也是网络学出来的 latent,没有 GT 告诉模型“这个 patch 的 value 应该是 [0.31, 0.72, …]”。、、、 初始化、 是一起被外层 reconstruction objective 塑造出来的:训练最终学会的是一组 K/V/Q 表示,使得“做一次 K→V 拟合”这个动作本身成为一个有效的 scene-memory write operation。这也正是它叫 Test-Time Training 的原因。
⭐ ray query token 与 image query 形式不同,为什么能输入同一个 Fast-Weight MLP f_W_hat?
ray token 和 image token 完全不同,凭什么共用同一个 f_W_hat?
分两层看。
第一层:进入 TTT block 之前,两者已经被编码到同一个 d=1024 维 token 空间。
image: RGB ─► DINOv2 ─► x_img ∈ R^1024
ray map: [r_o, r_d, r_o × r_d](每像素 9D)─► patchify ─► Linear ─► x_ray ∈ R^1024
论文明确写:ray map 每像素 9D,patchify 后经线性层投影到和 image token 相同的 embedding dimension,之后 image 和 ray-map tokens 进入同一套 backbone。
第二层(更关键):进 的不是裸 token,而是经过 query projection + 前面 local processing 后的 query representation。
训练让它们落在同一个 query space。用 cross-attention 类比:
来自 ray、 来自 RGB,来源不同也能点乘,因为网络学会了把它们映射到 compatible latent space。ZipMap 只是把 换成 ,而 已经通过 把 image context 写进去了。
这个“ray 如何读取 visual memory”的接口是学出来的,不是天然成立的:额外的 state-query finetuning(100K iterations,一半 input 一半 target views)用 target RGB/depth loss 专门训练 ,让它落在能正确读取 的位置。
你读这篇论文时最该盯住的点
- TTT 的 inner / outer 两层优化怎么分工。
- fast weights 的 write(Eq. 2 / 3)与 read(query)机制。
- query loss 为什么把 从“中间状态”变成“可查询的 scene state”。
- local attention + TTT memory 与 VGGT global attention 的对应关系。
- streaming 更新为什么保持线性复杂度。
与前面笔记的衔接
- DUSt3R / MASt3R:pairwise pointmap。
- VGGT / π³:全局前馈几何推理。
- DA3:depth + ray 表示。
- AMB3R:backend 组织全局场景。
- ZipMap:把全局 attention 换成可更新的 fast-weight memory,支持流式、线性复杂度。
官方资料
参考材料中没有给出 ZipMap 的 arXiv 编号、项目主页或代码链接,待补充。