论文3 bug 修复记录
# Atlas-Loc10 批处理显存 OOM 与 DINOv3 Top-K 问题记录
## 1. 基本信息
- 记录日期:2026-08-20
- 项目:`Atlas-Loc10_Dinov3Mast3r_ablation`
- 主脚本:`v3_dinov3_colmap_mast3r_ba.py`
- GPU:NVIDIA GeForce RTX 3070 Laptop GPU,8 GiB
- PyTorch:2.6.0 + CUDA 11.8
- DINOv3 query 分辨率:512
- MASt3R 输入分辨率:512
## 2. 问题现象
使用以下关键参数从第 10 张图像开始批处理:
```bash
--query-start 10 \
--query-end -1 \
--vpr-top-k 5 \
--vpr-query-resize 512 \
--match-max-points 1024 \
--live-viz
```
实际结果:
```text
DJI_00009.jpg ok
DJI_00010.jpg ok
DJI_00011.jpg ok
DJI_00012.jpg CUDA out of memory
DJI_00013.jpg CUDA out of memory
DJI_00014.jpg CUDA out of memory
... 后续连续 OOM
```
关键错误:
```text
torch.OutOfMemoryError: CUDA out of memory.
Tried to allocate 646.00 MiB.
GPU total capacity: 7.76 GiB
PyTorch allocated: about 5.07 GiB
```
错误发生于:
```text
mast3r_map_matches_asymmetric()
sim = d_q @ d_k_map.T
map_best_q = torch.argmax(sim, dim=0)
```
## 3. 根本原因
### 3.1 MASt3R 一次性创建约 646 MiB 相似度矩阵
原实现使用:
```python
sim = d_q @ d_k_map.T
```
其形状为:
```text
(query 稠密像素数, COLMAP 有效地图点数)
```
在 512 输入下,完整 `float32` 矩阵约占 646 MiB。RTX 4090 显存充足时可以承受,但在 8 GiB RTX 3070 上会与以下内容争抢显存:
- 常驻的 DINOv3 模型。
- 常驻的 MASt3R 模型。
- MASt3R decoder 临时张量。
- 查询帧 encoder 特征。
- 跨帧参考图像 GPU 缓存。
`--match-max-points 1024` 仅限制最终保留的匹配点,不会减小这个在匹配筛选前已经生成的完整相似度矩阵。
### 3.2 MASt3R 参考帧 GPU 缓存无上限增长
`ref_frame_cache` 原来保留每个历史参考帧的:
- GPU 图像张量。
- MASt3R encoder feature。
- 位置编码。
批处理进入新地图区域时,唯一参考帧数量不断增长。前三帧尚能创建 646 MiB 矩阵,第四帧起已没有足够显存。
### 3.3 OOM 后没有恢复,导致连锁丢帧
批处理会捕获单帧异常并继续下一帧,但原代码没有清理:
- `ref_frame_cache`。
- 失败帧释放后仍被 CUDA allocator 保留的空闲块。
所以第一次 OOM 后,后续帧会在相同位置连续失败。
## 4. 为什么不是 DINOv3 异常退出
DINOv3 和 MASt3R 只在批处理开始时加载一次,之后驻留 GPU 并复用。
如果 DINOv3 真的因显存不足失败,当前帧会直接报 OOM。代码中不存在“静默卸载 DINOv3,再切换为纯时序预测”的逻辑。
本次完整 traceback 明确位于 MASt3R 的描述子相似度搜索,而不是 DINOv3 forward、VLAD 或 FAISS。
4090 上进程显存超过 10 GiB,3070 早期帧仍可以运行,并不矛盾:
1. 4090 允许 PyTorch caching allocator 保留更多显存块。
2. 3070 使用 `--vpr-query-resize 512`;如果服务器使用 1024,DINOv3 临时激活会大很多。
3. `nvidia-smi` 与 `torch.cuda.memory_allocated()` / `memory_reserved()` 统计口径不同。
## 5. 已实施的修复
### 5.1 将完整相似度矩阵改为分块归约
新实现每次处理 4096 个 query 行,逐块维护:
- 每个 query 像素的最佳 map point。
- 每个 map point 的最佳 query 像素。
- 对应最高相似度。
该实现与原完整矩阵的双向 `argmax` 和互近邻结果等价。已用随机张量验证两个方向的 `argmax` 完全一致。
修复后,临时相似度峰值从约 646 MiB 降到数十 MiB,不改变匹配结果。
### 5.2 限制 MASt3R 参考帧 GPU LRU 缓存
新增参数:
```text
--ref-cache-max-frames N
```
取值含义:
- 默认:`8`。
- 8 GiB GPU 建议:`4`。
- `0`:禁用跨帧参考特征缓存,最省显存,但会重复执行 encoder。
超过上限后删除最久未使用的 `PairFrame`。
### 5.3 OOM 后自动恢复
单帧捕获 CUDA OOM 后执行:
```python
shared["ref_frame_cache"].clear()
gc.collect()
torch.cuda.empty_cache()
```
这可防止一次暂时 OOM 导致所有后续帧连锁失败。
### 5.4 修复 DINOv3 query 缓存跨序列冲突
旧缓存名:
```text
DJI_00001_r512.npy
```
City2 下多个季节和光照序列都含有 `DJI_00001.jpg`,但旧缓存没有记录查询目录,会误用另一序列的 DINOv3 描述子。
新格式:
```text
DJI_00001_r512_<12位SHA1摘要>.npy
```
摘要输入:
```text
图像绝对路径 + 文件大小 + mtime_ns
```
不同序列、替换后的图像或修改后的图像会自动重算描述子,旧的无摘要缓存不再命中。
### 5.5 在每帧 JSON 中保存 VPR 诊断信息
`vpr_retrieval` 新增:
```json
{
"query_descriptor_source": "dinov3 | cache",
"query_descriptor_cache": "...",
"num_temporal_candidates": 20,
"num_faiss_candidates": 0
}
```
可以明确判断每帧是 DINOv3 重算、安全缓存命中,还是 FAISS 候选被时序候选去重。
## 6. 修复后验证结果
在同一 RTX 3070 8 GiB GPU 上,从第 10 张 query 开始连续处理第 10–14 张,覆盖原来必定 OOM 的第 4 个处理帧:
```text
总帧数: 5
成功: 5
失败: 0
CUDA OOM: 0
水平误差 RMSE: 0.349 m
水平误差 max: 0.466 m
```
第 4 和第 5 个处理帧的 DINOv3、MASt3R、PnP 和 BA 均正常完成。
## 7. DINOv3 Top-K 与时序候选的独立问题
修复后的 5 帧诊断结果:
```text
第 1 帧: descriptor=dinov3, temporal=0, FAISS=15, selected=faiss
第 2 帧: descriptor=dinov3, temporal=20, FAISS=0, selected=temporal
第 3 帧: descriptor=dinov3, temporal=20, FAISS=0, selected=temporal
第 4 帧: descriptor=dinov3, temporal=20, FAISS=0, selected=temporal
第 5 帧: descriptor=dinov3, temporal=20, FAISS=0, selected=temporal
```
`FAISS=0` 不表示 DINOv3 退出。DINOv3 描述子和 FAISS Top-15 已经计算,但这 15 张全部与 20 张时序候选重合,按图像名去重后没有额外 FAISS 候选。
当前最终排序仍按时序距离,而不是 DINOv3 相似度。因此必须分开:
1. **连续丢帧/OOM**:已通过分块相似度、有界 GPU 缓存和 OOM 恢复解决。
2. **DINOv3 是否主导 Top-K**:这是候选融合策略问题。纯 DINOv3 VPR 消融应使用 `FAISS-only` 或 `FAISS-first`,时序候选只作为后备。
## 8. RTX 3070 8 GiB 推荐命令参数
在原命令中增加:
```bash
--ref-cache-max-frames 4
```
继续使用:
```bash
--vpr-query-resize 512 \
--match-max-points 1024
```
如果仍需要最低显存模式,可使用:
```bash
--ref-cache-max-frames 0
```
代价是重复参考帧不再复用 encoder feature,速度会下降。
## 9. 结论
本次“前几帧成功,随后频繁丢帧”的直接原因是:
```text
MASt3R 646 MiB 完整相似度矩阵
+ 无上限参考帧 GPU 缓存
+ OOM 后未恢复
= 8 GiB GPU 从第 4 个处理帧起连锁 OOM
```
该问题已修复并通过连续帧 GPU 实测。DINOv3 没有异常退出;DINOv3 Top-K 在第二帧后不主导最终候选,是独立的时序优先候选策略所致。
浙公网安备 33010602011771号