coredot.today
1만 프레임을 잊지 않는 법 — LingBot-Map과 기하 컨텍스트 어텐션
블로그로 돌아가기
LingBot-Map3D 재구성SLAMVGGT스트리밍어텐션KV 캐시공간 지능임바디드 AIRobbyant

1만 프레임을 잊지 않는 법 — LingBot-Map과 기하 컨텍스트 어텐션

2026년 4월, 앤트그룹의 로봇 자회사가 카메라 영상만으로 1만 프레임 이상을 20 FPS로 3차원 복원하는 모델을 오픈소스로 공개했다. 핵심은 '무엇을 기억하고 무엇을 버릴지'를 어텐션 마스크로 학습시킨 것. 40년 된 SLAM의 지혜가 트랜스포머 안으로 들어온 순간을, 논문과 코드를 끝까지 따라가며 해부한다.

코어닷투데이2026-08-13118

복도를 지나간 로봇 뒤로 점군이 쌓인다크게 보기

당신이 지금 앉아 있는 방을 눈을 감고 떠올려 보세요.

문은 어디에 있고, 창문은 어느 쪽이고, 뒤로 두 걸음 물러나면 무엇에 부딪히는지 — 당신은 답할 수 있습니다. 그런데 당신이 기억하고 있는 건 이 방의 사진 수만 장이 아닙니다. 오늘 이 방에서 눈이 받아들인 프레임은 수십만 장이지만, 머릿속에 남은 것은 놀랍도록 적습니다. 문 하나, 창문 하나, 대략적인 거리감 몇 개.

이건 결함이 아니라 설계입니다. 인간의 공간 인지는 모든 관측을 보관하지 않습니다. 꼭 필요한 단서만 골라 남기고, 나머지는 버립니다. 그래서 우리는 30년을 살아온 동네를 머릿속에 넣고 다닐 수 있습니다.

사람의 공간 기억 vs 기계의 전체 기록크게 보기

기계는 이걸 못 했습니다. 2026년 초까지 카메라 영상으로 3차원 공간을 실시간 복원하는 모델들은 두 갈래로 갈라져 있었습니다. 하나는 다 버리는 쪽 — 최근 몇 프레임만 보고 나머지는 잊어서, 5분쯤 걸어가면 자기가 어디서 출발했는지 모르게 됩니다. 다른 하나는 다 들고 가는 쪽 — 프레임 하나마다 1,000개씩 토큰을 캐시에 쌓아서, 3,000 프레임쯤 되면 GPU 메모리가 터집니다.

2026년 4월 15일, arXiv에 올라온 한 논문이 이 이분법을 깼습니다. 제목은 Geometric Context Transformer for Streaming 3D Reconstruction, 모델 이름은 LingBot-Map. 앤트그룹(Ant Group)의 임바디드 AI 자회사 Robbyant가 아파치 2.0 라이선스로 코드와 가중치를 모두 공개했습니다.

이 모델이 낸 숫자 하나만 먼저 보겠습니다.

Oxford Spires 벤치마크320 프레임3,840 프레임악화폭
CUT3R18.16 m32.47 m+14.31
Wint3R21.10 m32.90 m+11.80
TTT3R19.35 m25.05 m+5.70
LingBot-Map6.42 m7.11 m+0.69

표에 적힌 숫자는 절대 궤적 오차(ATE) — 모델이 추정한 카메라 경로가 실제 경로에서 평균 몇 미터 벗어났는지입니다. 시퀀스가 12배 길어졌을 때 경쟁 모델들은 오차가 1.3~1.8배로 뛰었고, LingBot-Map은 0.69미터 늘었습니다.

그리고 이 논문의 저자들이 공개한 데모 중 가장 인상적인 것은 이것입니다 — 25,000 프레임, 13분간의 실내 워크스루를 단일 좌표계 점군 하나로 복원한 결과.

25,000 프레임 실내 워크스루 복원 결과크게 보기

여러 방과 복도를 13분간 걸어다닌 영상 한 편이 하나의 평면도가 되었습니다. 파란 선이 추정된 카메라 궤적입니다. 출처: Robbyant/lingbot-map (Apache-2.0)

이 글은 그 안을 끝까지 들여다봅니다. 왜 이 문제가 어려운지, 40년 동안 어떤 시도들이 있었는지, 아키텍처가 정확히 어떻게 생겼는지 — 논문뿐 아니라 1만 3천 줄의 소스 코드까지 읽고 정리했습니다. 그리고 2026년의 지금, 이 기술이 어디에 앉아 있는지까지.


1부. 이 문제가 왜 어려운가

문제를 한 줄로 정의하면

카메라가 영상을 찍으며 어딘가로 움직입니다. 프레임 하나가 도착할 때마다 두 가지를 답해야 합니다.

입력
프레임 ItI_t 한 장
그리고 과거 I1...It1I_1 ... I_{t-1}
출력 ①
카메라 포즈 P^t\hat{P}_t
지금 어디에서 어디를 보는지
출력 ②
깊이 맵 D^t\hat{D}_t
픽셀마다 거리가 얼마인지

미래 프레임은 절대 볼 수 없습니다. 이 한 가지 제약이 문제 전체의 성격을 바꿉니다. 그리고 이 제약 아래에서 세 가지 함정이 순서대로 나타납니다.

함정 ① 사진 한 장으로는 크기를 알 수 없다

카메라 한 대로 찍은 사진에는 근본적인 정보 결손이 있습니다. 스케일 모호성(scale ambiguity) 입니다.

사진 한 장으로는 크기를 알 수 없다크게 보기

체육관 크기의 거실과 인형의 집 크기의 거실은, 카메라를 적당히 놓으면 완전히 똑같은 사진을 만듭니다. 사진에서 3차원을 복원할 때 "2미터"인지 "2센티미터"인지는 원리적으로 결정되지 않습니다. 실수 배율 하나가 자유롭게 남습니다.

오프라인 모델들(DUSt3R, VGGT)은 이걸 편법으로 해결했습니다. 전체 프레임을 다 받은 다음 복원된 점군 전체의 평균 거리를 1로 맞춰버립니다. 하지만 스트리밍에서는 "전체"가 없습니다. 첫 프레임을 처리하는 순간에는 앞으로 어떤 장면이 올지 전혀 모릅니다.

그래서 스케일을 어딘가에 못 박아야 합니다. 이게 뒤에 나올 앵커 컨텍스트의 존재 이유입니다.

함정 ② 오차는 반드시 누적된다

프레임마다 카메라의 움직임을 추정합니다. 각 추정은 조금씩 틀립니다 — 0.1도, 1센티미터. 문제는 이 오차들이 곱해지며 이어진다는 것입니다. 회전 오차 0.1도는 10미터 앞에서 1.7센티미터, 100미터 앞에서 17센티미터의 위치 오차가 됩니다.

이 현상을 표류(drift) 라고 부릅니다. 그리고 표류의 시각적 증상은 아주 특징적입니다.

정확한 지도 vs 표류크게 보기

같은 건물을 두 번 지나갔는데, 두 번째로 본 건물이 첫 번째와 다른 위치에 그려집니다. 벽이 두 겹으로 겹치고, 방 하나가 두 개가 되고, 결국 점군 전체가 형체를 잃습니다. 논문의 정성 비교 그림에서 TTT3R과 Wint3R이 복잡한 다중 건물 장면에서 "공간적 일관성을 완전히 상실했다"고 표현한 것이 바로 이 상태입니다.

함정 ③ 기억을 다 들고 가면 계산이 터진다

이건 언어 모델을 다뤄본 사람에게 익숙한 문제입니다. 트랜스포머는 과거를 KV 캐시에 저장해 재계산을 피합니다. 그런데 3D 재구성에서 프레임 하나가 만드는 토큰 수는 언어와 비교가 안 됩니다.

518×378 해상도, 패치 크기 14픽셀이면 프레임 하나가 37×27 = 999개의 이미지 토큰을 만듭니다. 문장 하나가 아니라, 프레임 하나가 그렇습니다.

3,840 프레임을 인과 어텐션으로 처리하면
프레임당 토큰 = 999 (이미지) + 6 (컨텍스트) = 1,005 전체 캐시 토큰 = 3,840 × 1,005 = 3,859,200 토큰당 KV 바이트 = 24블록 × 2(K,V) × 16헤드 × 64차원 × 2바이트(bf16) = 96 KiB ──────────────────────────────────────────────── KV 캐시 총량 = 약 353 GB ← H100 80GB × 4.4대

그래서 스트리밍 3D 재구성의 진짜 문제는 정확도가 아니라 선택적 컨텍스트 관리(selective context management) 입니다. 장기 일관성을 위한 풍부한 기하 정보와, 효율적 추론을 위한 압축된 상태 — 이 둘을 동시에 만족시켜야 합니다.

논문의 서론이 이 딜레마를 한 문장으로 정리합니다.

"스트리밍 상태는 단순히 얼마나 많이 가 아니라 무엇이 가장 중요한지 를 선택적으로 보존해야 한다. 그리고 그 선택은 기하학적 사전 지식에 근거하면서도, 데이터로부터 종단간 학습되어야 한다."


2부. 40년의 계보 — 이 아이디어는 어디서 왔나

LingBot-Map은 갑자기 나타난 것이 아닙니다. 이 논문의 설계 원리는 1980년대 사진측량과 2000년대 로보틱스에서 그대로 가져온 것입니다. 계보를 훑고 가야 GCA가 왜 저렇게 생겼는지 이해됩니다.

결정적인 유산: SLAM의 삼분할

위 타임라인에서 반드시 짚어야 할 노드는 SLAM입니다. Simultaneous Localization and Mapping — 로봇이 움직이면서 동시에 자기 위치를 알아내고 지도를 만드는 문제.

수십 년에 걸쳐 다듬어진 SLAM 시스템(ORB-SLAM3 같은)은 예외 없이 세 종류의 공간 컨텍스트를 따로따로 관리합니다.

① 기준 프레임
reference frame
좌표 원점과 스케일을 못 박는다. 여기가 흔들리면 전체가 흔들린다.
② 지역 창
local window
최근 관측을 모아 촘촘한 지역 기하를 추정한다. 새 프레임을 붙이는 접착면.
③ 전역 지도
global map
누적된 표류를 되잡는다. 루프 클로저와 포즈 그래프 최적화가 여기서 돈다.

이 삼분할은 우연이 아닙니다. 세 가지가 서로 다른 문제를 풀기 때문에 합칠 수 없습니다. 기준 프레임은 좌표계를, 지역 창은 정합 정확도를, 전역 지도는 장기 일관성을 담당합니다.

그런데 고전 SLAM에는 결정적 한계가 있었습니다. "어떤 프레임을 남길지"를 사람이 손으로 정한 임계값으로 결정한다는 것. 특징점이 몇 개 이하면 버리고, 이동량이 얼마 이상이면 키프레임으로 삼고 — 이런 규칙들은 새로운 장면에서 무너집니다. 어두운 계단, 텍스처 없는 흰 벽, 반복 패턴의 타일 바닥.

LingBot-Map이 한 일은 정확히 이것입니다.

SLAM의 삼분할 구조는 그대로 가져오고, 그 안의 손으로 만든 규칙을 어텐션으로 갈아끼운다.

논문은 이 어텐션을 GCA(Geometric Context Attention, 기하 컨텍스트 어텐션) 라고 부릅니다. 그리고 GCA를 몸통으로 삼은 트랜스포머 전체를 GCT(Geometric Context Transformer) 라고 부릅니다.

직계 부모: VGGT

계보에서 하나 더 짚을 노드는 VGGT입니다. 2025년 CVPR 최우수 논문. LingBot-Map의 아키텍처적 부모입니다.

VGGT가 증명한 것은 충격적으로 단순한 명제였습니다 — 기하 문제를 회귀 문제로 바꿔도 된다. 카메라 파라미터를 몰라도, 특징점 매칭을 안 해도, 번들 조정을 안 돌려도, 사진 수백 장을 트랜스포머에 밀어 넣으면 포즈와 깊이와 점 지도가 몇 초 안에 나옵니다.

그 핵심 구조가 교대 어텐션(alternating attention) 입니다. 프레임 내부만 보는 층과 프레임 사이를 잇는 층을 24번 번갈아 쌓습니다. 모든 토큰이 모든 토큰을 보는 전역 어텐션을 쓰면 계산량이 폭발하기 때문에, 축을 나눠 번갈아 처리하는 것입니다.

LingBot-Map은 이 골격을 거의 그대로 물려받았습니다 — DINOv2 백본, 카메라 토큰, 레지스터 토큰, DPT 깊이 헤드, 9차원 포즈 인코딩, 24블록 교대 구조. 바꾼 것은 딱 하나, "프레임 사이를 잇는 층"의 어텐션 마스크입니다. 그 한 곳을 바꾸기 위해 이 논문 전체가 존재합니다.


3부. 기존 스트리밍 방법들이 실패한 세 가지 방식

VGGT를 스트리밍으로 옮기려는 시도는 2025년에 여러 갈래로 터져 나왔습니다. 각각 다르게 실패했고, 그 실패의 지도가 GCA의 설계도가 되었습니다.

접근대표 모델전략무너지는 지점
순환 상태CUT3R, TTT3R고정 크기 상태 하나를 프레임마다 갱신 (RNN 방식)압축이 공격적 → 상태 망각. 오래된 기하가 지워진다
인과 어텐션 + 캐시StreamVGGT, Stream3R과거 K/V를 거의 다 캐시 (LLM 방식)선택 기준이 없음 → 캐시 선형 폭발 + 먼 과거 토큰의 잡음
학습 모델 + SLAM 백엔드VGGT-SLAM, MASt3R-SLAM피드포워드 모델 앞에 고전 SLAM을 붙인다키프레임 선택이 수작업 규칙 + 반복 최적화로 실시간성 상실
청크 + 테스트 타임 학습LoGeR, Scal3R덩어리로 잘라 처리 + 추론 중 파라미터 갱신오프라인 설정 + 추론 중 학습 오버헤드

세 번째 줄이 특히 중요합니다. SLAM 백엔드를 붙인 하이브리드는 구조는 맞았습니다. 키프레임을 고르고 포즈 그래프를 관리하는 것은 정확히 옳은 방향입니다. 문제는 그 선택이 학습된 것이 아니라는 점이었습니다.

문제
구조는 알지만 규칙은 못 만든다
SLAM은 "무엇을 기억해야 하는지"의 구조를 40년간 정제했다. 하지만 그 구조를 채우는 판단(이 프레임이 중요한가?)은 손으로 만든 임계값에 맡겨져 있었다.
해결
구조는 마스크로, 판단은 어텐션으로
세 종류의 컨텍스트를 어텐션 마스크의 모양으로 하드코딩하고, 그 안에서 무엇에 얼마나 주의를 줄지는 어텐션 가중치가 데이터로부터 배우게 한다.
결과
프레임당 비용은 거의 상수, 정확도는 오프라인 모델 초과
Oxford Spires에서 스트리밍 모델은 물론 오프라인 모델(DA3)과 최적화 기반 모델(ViPE)까지 앞질렀다. 20 FPS를 유지한 채로.

4부. 아키텍처 — GCA를 뼈까지 발라내기

여기가 이 글의 본체입니다. 천천히 갑니다.

4-1. 세 개의 기억

GCA는 하나의 어텐션 층 안에서 세 종류의 컨텍스트를 동시에 유지합니다. SLAM의 삼분할과 일대일로 대응합니다.

앵커 컨텍스트 · 궤적 메모리 · 포즈 참조 윈도우크게 보기

GCA의 세 컨텍스트대응하는 SLAM 개념해결하는 문제보관하는 것
앵커 컨텍스트
anchor context
기준 프레임좌표계·스케일 확정이미지 토큰 전부 (영구)
포즈 참조 윈도우
local pose-reference window
지역 창정확한 상대 포즈 추정이미지 토큰 전부 (최근 k개)
궤적 메모리
trajectory memory
전역 지도장기 표류 보정컨텍스트 토큰 6개만

하나씩 보겠습니다.

4-2. 앵커 컨텍스트 — 좌표계를 못 박는 대못

시퀀스가 시작될 때 맨 앞의 nn개 프레임(nNn \ll N)을 앵커 프레임으로 지정합니다. 이 프레임들에게는 특별 대우를 합니다.

1
앵커끼리 완전 어텐션. 앵커 프레임 nn개는 서로를 자유롭게 봅니다(양방향). 인과 제약이 없습니다. 이 구간은 사실상 미니 오프라인 재구성입니다.
2
학습 가능한 앵커 토큰을 붙인다. 앵커 프레임에는 스트리밍 프레임과 다른 값의 특수 토큰이 붙습니다. 네트워크가 "이건 기준 프레임이다"를 구별할 수 있게 됩니다.
3
이후 모든 프레임이 앵커를 본다. 앵커의 이미지 토큰은 캐시에서 절대 쫓겨나지 않습니다. 1만 번째 프레임도 1번째 프레임의 픽셀 특징을 직접 참조합니다.
4
학습 시 정답도 앵커 기준으로 정규화. 앵커 프레임들의 정답 점군에서 원점까지의 평균 거리 ss를 계산해, 모든 정답 깊이와 카메라 이동량을 ss로 나눕니다.

4번이 조용하지만 결정적입니다. 수식으로는 이렇습니다.

s=1XˉanchorxXˉanchorx2s = \frac{1}{|\bar{X}^{\text{anchor}}|} \sum_{x \in \bar{X}^{\text{anchor}}} \| x \|_2

이게 왜 중요할까요. 모델에게 "절대 스케일"을 예측하라고 요구하면 불가능한 것을 요구하는 셈입니다(함정 ①). 그런데 "앵커 프레임 기준의 상대 스케일" 을 예측하라고 하면, 이건 결정 가능한 문제가 됩니다. 앵커가 자를 들고 서 있으니까요.

코드에서 앵커 프레임의 개수는 num_scale_frames(기본 8개)이고, 추론 시 1단계에서 이들을 한 덩어리로 묶어 한 번에 처리합니다.

gct_stream.py — inference_streaming()
# Phase 1: 앵커(scale) 프레임을 한꺼번에 — 서로 양방향 어텐션 scale_output = self.forward( images[:, :scale_frames], num_frame_for_scale=scale_frames, num_frame_per_block=scale_frames, # 전부 한 블록으로 causal_inference=True, )

# Phase 2: 나머지는 한 장씩 — 캐시를 읽으며 스트리밍 for i in range(scale_frames, S): frame_output = self.forward(images[:, i:i+1], num_frame_per_block=1, ...)

어블레이션에서 앵커 초기화만 추가했을 때 AUC@3이 9.80 → 13.63으로 올랐고, ATE는 8.59 → 7.88로 떨어졌습니다. 논문의 해석이 흥미롭습니다 — AUC@3(가장 엄격한 국소 포즈 정확도)이 오른 것은 앵커가 전역 일관성만 개선한 게 아니라 프레임 하나하나의 등록 정확도까지 끌어올렸다는 뜻입니다. 기준점이 명확하니 새 프레임을 붙이는 일 자체가 쉬워집니다.

4-3. 포즈 참조 윈도우 — 새 프레임을 붙이는 접착면

앵커만으로는 부족합니다. 100미터 걸어온 뒤에 첫 프레임과 지금 프레임은 겹치는 픽셀이 하나도 없습니다. 새 프레임을 정확히 등록하려면 시각적으로 겹치는 최근 관측이 필요합니다.

그래서 최근 kk개 프레임의 이미지 토큰을 전부 유지합니다. 이게 포즈 참조 윈도우입니다. 코드 기본값은 kv_cache_sliding_window = 64, 논문 실험 기본값도 k=64k = 64입니다.

학습할 때 재미있는 장치를 씁니다. kk를 16에서 64 사이에서 매번 무작위로 뽑습니다. 다양한 수용 영역을 경험시켜서, 추론 시 창 크기를 바꿔도 무너지지 않게 만드는 것입니다. 실무적으로 이게 중요한 이유는 뒤에서 나옵니다.

그리고 이 창 안의 프레임들끼리는 상대 포즈 손실로 직접 감독합니다(4-10에서 자세히).

4-4. 궤적 메모리 — 이 논문의 심장

앵커(과거의 시작점)와 윈도우(최근)가 있습니다. 그럼 그 사이의 수천 프레임은 어떻게 하죠?

버리면 표류합니다. 다 들고 가면 터집니다. GCA의 답은 이렇습니다.

이미지 토큰만 버리고, 프레임당 컨텍스트 토큰 6개는 끝까지 들고 간다.

이 6개가 정확히 무엇인지가 핵심입니다. 코드를 보면 명확합니다.

aggregator/stream.py — _setup_special_tokens()
self.camera_token = nn.Parameter(torch.randn(1, 2, 1, embed_dim)) # 카메라 1개 self.register_token = nn.Parameter(torch.randn(1, 2, 4, embed_dim)) # 레지스터 4개 self.scale_token = nn.Parameter(torch.ones (1, 2, 1, embed_dim)) # 앵커 1개

self.patch_start_idx = 1 + 4 + 1 # = 6 → 이미지 토큰은 인덱스 6부터

토큰개수역할
카메라 토큰1이 프레임의 포즈가 최종적으로 응축되는 자리. 카메라 헤드가 읽는 유일한 입력.
레지스터 토큰4ViT가 엉뚱한 배경 패치에 전역 정보를 쌓아두는 병리를 막는 완충 슬롯 (Darcet et al. 2024).
앵커/스케일 토큰1이 프레임이 앵커인지 스트리밍 프레임인지 알려주는 표식. 코드에서는 scale_token.

여기서 감탄할 지점은 이 6개가 원래부터 그 자리에 있었다는 것입니다. VGGT도 카메라 토큰과 레지스터 토큰을 씁니다. 이 논문은 새 압축 모듈을 발명하지 않았습니다. 트랜스포머가 이미 프레임 요약을 모아두던 자리를 그대로 "기억 슬롯"으로 재해석했을 뿐입니다.

그래서 학습 단계 전환이 매끄러웠습니다. GCA의 Q·K·V 투영은 전역 어텐션과 파라미터 형태가 완전히 같아서, 1단계 오프라인 모델의 가중치가 그대로 옮겨집니다.

그리고 하나 더 — 남겨진 6개 토큰에는 비디오 RoPE로 시간 좌표를 심습니다. 이게 없으면 궤적 메모리는 "어떤 기하가 있었다"는 것만 알고 "그게 언제였는지"를 모릅니다. 어블레이션에서 이 하나가 ATE를 7.46 → 5.98로 떨어뜨렸습니다. 단일 요소 중 가장 큰 개선폭이었습니다.

4-5. 네 가지 어텐션 마스크

지금까지의 이야기를 하나의 그림으로 보면 전부 이해됩니다. 논문 Figure 3을 인터랙티브로 재현했습니다. 버튼을 눌러 네 가지를 비교해 보세요.

GCA 마스크를 골라 슬라이더를 오른쪽 끝까지 밀어보면 구조가 선명해집니다. 왼쪽 두 열(앵커)은 항상 통짜로 켜져 있고, 대각선 근처(윈도우)도 통짜로 켜져 있고, 그 사이는 얇은 띠만 남습니다. 그 얇은 띠들이 궤적 메모리입니다.

4-6. 복잡도 — 80배라는 숫자의 정체

이제 계산해 봅시다. TT 프레임 시퀀스에서 현재 프레임이 참조하는 총 토큰 수는:

n(M+6)앵커+k(M+6)윈도우+(Tnk)6궤적 메모리  =  (n+k)M+6T\underbrace{n \cdot (M + 6)}_{\text{앵커}} + \underbrace{k \cdot (M + 6)}_{\text{윈도우}} + \underbrace{(T - n - k) \cdot 6}_{\text{궤적 메모리}} \;=\; (n+k) \cdot M + 6T

nnkk는 고정 상수입니다. 그래서 첫 항은 상수고, 둘째 항만 프레임당 6씩 늘어납니다.

인과 어텐션은 T(M+6)=MT+6TT \cdot (M+6) = MT + 6T입니다. 뒤쪽 항은 GCA와 똑같고, 거기에 MTMT가 더 붙습니다. 프레임 하나가 추가될 때 늘어나는 양을 비교하면:

인과 어텐션
+1,005 토큰
GCA
+6 토큰

논문은 M500M \approx 500을 기준으로 "약 80배 감소"라고 썼습니다. 실제 518×378 설정(M=999M = 999)에서는 167배입니다. 직접 만져보세요.

슬라이더를 10,000 프레임에 놓으면 논문의 주장이 눈에 보입니다. 인과 어텐션은 약 1,000만 토큰, GCA는 약 13만 토큰. 논문 표현대로 "거의 상수에 가까운 프레임당 메모리와 계산량" 입니다.

4-7. 전체 파이프라인

이제 프레임 하나가 도착해서 포즈와 깊이가 나올 때까지의 경로 전체를 조립해 보겠습니다. 블록을 눌러보세요.

숫자로 정리하면 이렇습니다.

구성 요소사양비고
백본DINOv2 ViT-L, 패치 14px임베딩 차원 1024, 헤드 16개(헤드 차원 64)
입력 해상도518 × 37837 × 27 = 999 이미지 토큰
블록 수24 (Frame ↔ GCA 교대)프레임 블록 24개 + 전역 블록 24개
특수 토큰6 (카메라 1 + 레지스터 4 + 앵커 1)patch_start_idx = 6
중간 출력 추출5·12·18·24번째 블록selected_idx=[4, 11, 17, 23] — DPT 헤드용
카메라 헤드트렁크 4층, 반복 정제 4회출력 9차원 (이동 3 + 쿼터니언 4 + FoV 2)
깊이 헤드DPT, 출력 채널 2깊이 + 불확실성
추론 정밀도bfloat16헤드는 autocast(enabled=False)로 fp32 유지

마지막 줄에 주의할 만한 디테일이 있습니다. 몸통은 bf16으로 돌리지만 카메라·깊이 헤드는 fp32로 강제합니다. 포즈는 작은 수치 오차가 궤적 끝에서 미터 단위로 증폭되는 양이라, 여기서 정밀도를 아끼면 손해가 큽니다.

4-8. 9개 숫자로 카메라를 표현하는 법 — 그리고 방향의 문제

카메라 헤드는 프레임당 9개 숫자를 뱉습니다. 코드에서 pose_encoding_type = "absT_quaR_FoV"입니다.

pose_enc[9] 의 구성
[0:3] 이동 벡터 T 카메라 중심의 3차원 좌표 [3:7] 회전 쿼터니언 q 3차원 회전을 4개 숫자로 (짐벌락 없음, 보간 매끄러움) [7:9] 시야각 FoV 가로·세로 화각 → 초점거리 fx, fy로 환산

내장 파라미터(초점거리)까지 예측한다는 점을 주목하세요. 카메라 캘리브레이션 정보가 없어도 됩니다. 아무 휴대폰으로 찍은 영상, 심지어 생성 모델이 만들어낸 영상도 그냥 넣으면 됩니다.

그런데 여기서 LingBot-Map이 VGGT와 의식적으로 갈라진 지점이 있습니다. 논문 4.1절의 한 문장입니다.

"VGGT와 달리, 우리는 world-to-camera 대신 camera-to-world 변환으로 네트워크를 감독한다."

왜 이게 중요할까요. world-to-camera(w2c) 표현에서 이동 벡터는 t=Rct = -R \cdot c 형태입니다. 회전 RR과 카메라 중심 cc가 곱셈으로 얽혀 있습니다. 회전을 0.5도 틀리면 이동 벡터가 그 회전에 곱해져서 위치 오차가 증폭됩니다. 그리고 긴 시퀀스에서는 이 증폭이 프레임마다 쌓입니다.

camera-to-world(c2w)로 바꾸면 이동 성분이 곧 카메라 중심 좌표 그 자체입니다. 회전과 위치가 분리됩니다. 이 논문이 초장문 시퀀스를 다루려면 반드시 건드려야 했던 부분이고, 실제로 코드 전반에서 closed_form_inverse_se3()로 c2w ↔ w2c를 명시적으로 왕복하는 것을 볼 수 있습니다.

그리고 카메라 헤드는 한 번에 답을 맞추지 않습니다. 반복 정제(iterative refinement) 구조입니다.

1회
학습된 "빈 포즈" 토큰에서 시작 → 포즈 9숫자 예측
2회
방금 예측한 포즈를 다시 입력으로 임베딩 → adaLN 변조로 카메라 토큰을 조절 → 보정량(delta) 예측
3~4회
같은 과정 반복. 매번 이전 예측을 detach()해서 역전파가 시간축으로 흐르지 않게 막는다

--camera_num_iterations 1로 줄이면 정제 3번을 건너뛰어 속도가 오르고, 이 헤드의 KV 캐시가 4분의 1로 줄어듭니다. 정확도를 조금 내주고 지연을 사는 노브입니다.

4-9. 비디오 RoPE — 궤적 메모리에 시계를 달아주기

궤적 메모리 토큰은 "이런 기하가 있었다"는 정보를 담고 있습니다. 그런데 언제 봤는지를 모르면 반쪽입니다. 10초 전에 지나간 벽인지 10분 전에 지나간 벽인지, 카메라가 그 방향으로 가고 있는지 오는 중인지 — 이걸 알아야 표류를 되잡을 수 있습니다.

그래서 3차원 RoPE를 씁니다. 어텐션 헤드 차원 64를 시간·높이·너비 세 축으로 쪼개 각각 독립적인 회전 위치 인코딩을 넣습니다. 코드에서는 fhw_dim = [20, 22, 22]로 나눕니다 — 시간축에 20차원, 높이·너비에 각각 22차원.

layers/rope.py — WanRotaryPosEmbed
# 스트리밍이므로 "전역 프레임 인덱스"를 넘겨야 한다. # 현재 배치의 0번이 아니라, 시퀀스 시작부터 몇 번째 프레임인지. pos3d = self.rope3d( ppf=num_frames, pph=H//14, ppw=W//14, patch_start_idx=self.num_special_tokens, f_start=self.total_frames_processed, # ← 여기가 핵심 f_end=self.total_frames_processed + S_global, )

total_frames_processed를 모델이 계속 세고 있다는 점이 포인트입니다. 프레임 한 장만 넣어도 그 프레임이 전체 궤적에서 몇 번째인지를 위치 인코딩이 알고 있습니다.

이 하나가 ATE를 1.48미터 줄였습니다. 궤적 메모리 자체가 준 개선(0.42미터)보다 3배 이상 큽니다. 논문의 해석대로, 시간 순서는 궤적 메모리가 제 성능을 내기 위한 마지막 조각이었습니다.

4-10. 손실 함수 — 회전이 특히 예민하다

학습 목표는 세 항의 합입니다.

L=λdepthLdepth+λabsLabs-pose+λrelLrel-pose\mathcal{L} = \lambda_{\text{depth}} \mathcal{L}_{\text{depth}} + \lambda_{\text{abs}} \mathcal{L}_{\text{abs-pose}} + \lambda_{\text{rel}} \mathcal{L}_{\text{rel-pose}}

앞의 두 항은 VGGT를 따릅니다. 깊이 손실은 예측한 불확실성 Σ\Sigma로 가중된 L1 + 그래디언트 매칭이고, 절대 포즈 손실은 정답 포즈와의 거리입니다.

세 번째 항이 이 논문이 추가한 것입니다. 포즈 참조 윈도우 안의 모든 프레임 쌍에 대해 상대 포즈를 감독합니다.

Lrel-pose=1k(k1)ij(Lrot(i,j)+λtransLtrans(i,j))\mathcal{L}_{\text{rel-pose}} = \frac{1}{k(k-1)} \sum_{i \neq j} \left( \mathcal{L}_{\text{rot}}(i,j) + \lambda_{\text{trans}} \mathcal{L}_{\text{trans}}(i,j) \right)

Lrot\mathcal{L}_{\text{rot}}은 측지 회전 오차(두 회전 사이의 최단 각), Ltrans\mathcal{L}_{\text{trans}}는 L1 이동 오차입니다. 창 안의 프레임은 모두 이미 관측된 프레임이므로 이 손실은 인과성을 깨지 않습니다.

절대 손실과 상대 손실은 서로 보완적입니다.

절대 포즈 손실
각 프레임을 전역 좌표계에 고정한다
"너는 여기 있어야 해"
상대 포즈 손실
창 안의 프레임 간 상대 운동을 직접 구속한다
"너와 옆 프레임의 차이는 이만큼이어야 해"

효과가 얼마나 클까요. 어블레이션에서 이 손실을 빼면 회전 오차(RPE-rot)가 2.26 → 5.35로 2.4배 악화됩니다. 반면 이동 오차는 1.48 → 1.67로 소폭만 나빠집니다.

회전 오차 · 상대 손실 있음
2.26
회전 오차 · 상대 손실 없음
5.35

이 비대칭이 시사하는 것: 회전 추정은 국소 쌍별 감독의 결핍에 특히 취약합니다. 그리고 4-8절에서 봤듯이 회전 오차는 이동 오차로 증폭됩니다. c2w 감독과 상대 포즈 손실은 같은 병을 두 방향에서 치료하는 처방인 셈입니다.


5부. 학습 — GPU 3만 7천 시간

아키텍처가 좋아도 학습이 안 되면 끝입니다. 그리고 이 모델의 학습은 정말 어려웠습니다.

왜 어려운가: 긴 시퀀스를 바로 학습시키면 초기 프레임의 포즈 오차가 궤적 전체로 전파되어 손실 지형이 불안정해집니다. 수백 뷰짜리 샘플로 처음부터 최적화하는 건 사실상 불가능합니다.

그래서 2단계 커리큘럼을 씁니다.

1단계: 기반 모델2단계: 스트리밍 모델
목표일반적인 기하 사전 지식 확보스트리밍 설정으로 전이 + 장기 일관성
어텐션표준 전역 어텐션 (GCA 아님)GCA
뷰 개수2 ~ 24 (무작위)24 → 320 (선형 증가)
학습률2×10⁻⁴5×10⁻⁴
반복160K160K
병렬화FSDP + 그래디언트 체크포인팅+ Ulysses 컨텍스트 병렬 (차원 16)
GPU 시간약 21,500시간약 15,360시간
프레임 샘플링공간 근접 샘플러 (순서 무관)폴드백 비디오 샘플러

1단계에서 일부러 GCA를 쓰지 않는 것이 영리합니다. 1단계 데이터에는 순서 없는 다중뷰 컬렉션(CO3D, Objaverse, MegaDepth)과 순서 있는 비디오가 섞여 있습니다. 전역 어텐션은 시간 구조를 강제하지 않으므로 양쪽 데이터를 다 활용할 수 있습니다.

폴드백 비디오 샘플러

2단계의 데이터 샘플링 방식에 재미있는 트릭이 있습니다.

시작
긴 비디오의 임의 프레임에서 출발합니다.
전진
무작위 스트라이드로 앞으로 나아갑니다. 스트라이드가 다르면 프레임 레이트가 다른 것처럼 보입니다 — 느린 도보와 빠른 주행을 같은 데이터로 만듭니다.
반사
비디오 끝에 닿으면 방향을 뒤집습니다. 그리고 이전과 다른 새 스트라이드를 뽑습니다.
효과
같은 두 프레임을 왕복하는 퇴화(degenerate oscillation)를 피하면서, 시간 방향 편향 없는 다양한 부분 시퀀스를 얻습니다.

"미래로만 흐르는 영상"만 학습하면 모델이 시간 방향에 관한 지름길을 배웁니다. 되돌아 걷는 영상, 뒤로 후진하는 차 — 실제 로봇은 그런 걸 합니다.

데이터

29개 데이터셋. 실내·실외·객체 중심·합성·실촬을 모두 덮습니다. 2단계에서는 긴 궤적 데이터셋의 샘플링 비중을 확 올립니다.

TartanAirV2 (2단계 비중)
10.8%
TartanGround
10.8%
내부 게임 데이터셋
10.8%
TartanAir
7.6%
MatrixCity (도시 규모)
7.6%
DL3DV
5.7%
MegaDepth (순서 없는 다중뷰)
1단계만

게임 엔진 데이터가 30% 이상이라는 점을 눈여겨볼 만합니다. TartanAir·TartanGround·MatrixCity·내부 게임 데이터셋 — 모두 합성입니다. 이유는 명확합니다. 실제 세계에서 1만 프레임짜리 궤적의 픽셀 단위 정답 깊이와 밀리미터급 정답 포즈를 얻을 방법이 사실상 없습니다. 라이다를 매달고 캠퍼스를 돌아도 Oxford Spires 규모가 한계입니다. 긴 궤적 학습은 시뮬레이션 없이는 불가능합니다.

그리고 이건 뒤에서 다룰 월드 모델과의 순환 관계로 이어집니다.

데이터 증강의 미묘한 설계

색상 증강에 흥미로운 장치가 하나 있습니다.

  • 확률 0.9로 색상 지터(밝기·대비·채도 ±0.5, 색조 ±0.1)
  • 확률 0.3으로 공동 지터(co-jittering) — 한 장면의 모든 프레임에 동일한 색 변환
  • 나머지 0.7은 프레임마다 독립적인 변환

왜 둘을 섞을까요. 공동 지터는 "프레임들이 비슷한 색감을 공유할 때 모델이 색 단서로 지름길을 타는 것"을 막습니다 — 기하로 풀라고 강제하는 것입니다. 독립 지터는 프레임 간 노출·화이트밸런스가 튀는 실제 촬영 조건에 대한 강건성을 만듭니다. 정반대 목적의 증강을 확률로 섞어 둘 다 얻습니다.


6부. 추론 시스템 — 코드를 읽어야 보이는 것

논문의 3.4절과 4.4절은 짧습니다. 하지만 이 부분이 20 FPS를 만든 곳이고, 실제로 모델을 돌려볼 사람에게는 가장 실용적인 부분입니다. 코드를 열어봤습니다.

6-1. 왜 페이지로 쪼개는가

GCA가 프레임당 컨텍스트를 작게 유지하는 건 맞습니다. 그런데 슬라이딩 윈도우와 궤적 축출 로직이 캐시를 계속 갱신해야 합니다 — 새 항목을 붙이고 오래된 걸 버립니다.

캐시를 하나의 연속된 텐서로 두면 이게 재앙입니다. 프레임을 버릴 때마다 torch.cat으로 전체를 다시 복사해야 합니다. 프레임당 7만 토큰 × 96KiB를 매번 복사하면, 어텐션 계산보다 메모리 복사가 더 오래 걸립니다.

해법은 LLM 서빙에서 이미 검증된 것이었습니다 — PagedAttention (Kwon et al., vLLM, 2023). 캐시를 고정 크기 페이지로 쪼개고, 페이지 테이블(어떤 페이지가 어떤 순서로 보이는지)만 갈아끼웁니다. 복사가 사라집니다.

앵커는 자물쇠, 윈도우는 재활용, 컨텍스트는 누적크게 보기

6-2. 두 개의 스트림

LingBot-Map의 구현은 여기서 한 걸음 더 갑니다. 캐시를 성격이 다른 두 스트림으로 나눕니다. 코드의 주석이 이걸 명료하게 적어놨습니다.

이미지 토큰 스트림컨텍스트 토큰 스트림
페이지 크기프레임당 이미지 토큰 수와 정확히 일치 (999)같은 물리 페이지 크기를 6개 단위로 빽빽하게 채움
페이지 수고정 88장 (앵커 8 + 윈도우 64 + 예비 16)프레임 수에 따라 증가 (999÷6 = 166프레임/페이지)
생애재활용 — 윈도우를 벗어나면 free list로추가 전용 — 절대 회수 안 함
쓰기 대상모든 프레임모든 프레임 (앵커·윈도우 프레임도 포함)

page_size를 999 그대로 쓰는 이유도 코드에 적혀 있습니다. FlashInfer의 FA2 커널은 2의 거듭제곱이 아닌 페이지 크기를 지원합니다. 999를 1024로 올려버리면 페이지마다 25개 슬롯이 0으로 낭비됩니다. 정확히 999로 맞춰 낭비를 없앴습니다. (H100의 FA3 커널을 쓰려면 2의 거듭제곱이 필수라, fa3=True일 때만 1024로 올립니다.)

그리고 페이지 테이블을 만들 때의 순서에도 계산이 있습니다.

flashinfer_cache.py — build_visible_page_table()
return ( list(self.scale_patch_pages[block_idx]) # ① 앵커 + list(self.live_window_patch_pages[block_idx]) # ② 윈도우 + list(self.all_special_pages[block_idx]) # ③ 컨텍스트 ← 반드시 마지막 )

# 컨텍스트 페이지를 맨 뒤에 두면, 부분적으로만 채워진 페이지가 # "마지막 하나"밖에 없다. → paged_kv_last_page_len 하나로 설명 끝. # 커스텀 어텐션 마스크가 필요 없어진다.

이게 정말 깔끔한 트릭입니다. 컨텍스트 토큰은 6개씩 쌓이니 페이지가 딱 맞게 채워지는 일이 드뭅니다. 만약 컨텍스트 페이지가 중간에 있으면 "여기는 절반만 유효함"을 알려주는 커스텀 마스크가 필요해집니다. 맨 뒤로 밀어버리면 그 문제가 사라집니다.

직접 프레임을 흘려보내며 페이지 풀이 어떻게 움직이는지 보세요.

6-3. plan은 한 번, run은 24번

FlashInfer의 어텐션 호출은 두 단계입니다. plan()으로 페이지 구조를 알려주고, run()으로 실제 커널을 돌립니다. plan()은 CPU에서 인덱스 배열을 만드는 일이라 공짜가 아닙니다.

여기서 구현이 영리한 관찰을 씁니다 — 24개 블록의 페이지 구조는 완전히 동일합니다. 같은 프레임 스텝에서는 어떤 블록이든 같은 페이지 ID가 같은 위치에 있습니다. 그러니:

블록 0
plan() 호출 1회
+ run()
블록 1~23
plan() 생략
run()만 23회

프레임 하나당 plan()이 24회에서 1회로 줄어듭니다. 여기에 torch.compilenorm1 + qkv 선형 + q/k 정규화 + RoPE + 포맷 변환을 하나의 CUDA 그래프로 묶고(attn_pre), FFN 잔차 분기도 통째로 묶습니다(ffn_residual).

결과: 동일 조건의 PyTorch 연속 캐시 구현이 10.5 FPS일 때, FlashInfer 페이지 구현은 20 FPS.

PyTorch 연속 KV 캐시
10.5 FPS
FlashInfer 페이지 KV 캐시
20.3 FPS

FlashInfer가 없는 환경에서는 --use_sdpa로 PyTorch 기본 어텐션 경로로 폴백합니다. 참고로 저장소 뉴스에 2026년 6월 28일 SDPA KV 캐시 버그 수정이 올라와 있습니다 — 긴 시퀀스에서 SDPA 백엔드 성능이 개선되었지만, 저자들은 여전히 FlashInfer를 권합니다.

6-4. 키프레임 선택 — 프레임을 버리는 기술

이 모델은 320뷰까지만 학습되었습니다. 비디오 RoPE도 그 범위에서 학습됐습니다. 그러면 3,000 프레임 영상은 어떻게 할까요?

키프레임만 캐시에 남깁니다. 여기서 중요한 것: 키프레임이 아닌 프레임도 예측은 정상적으로 나옵니다. 캐시에만 쌓이지 않습니다.

논문이 제시한 방식은 광학 흐름 기반입니다.

1
새 프레임의 깊이와 포즈를 먼저 예측합니다.
2
그 깊이와 두 포즈를 써서 현재 프레임의 픽셀을 마지막 키프레임의 카메라로 재투영합니다.
3
픽셀 변위(광학 흐름)의 평균 크기를 계산합니다. 카메라가 별로 안 움직였으면 작고, 크게 움직였으면 큽니다.
4
임계값을 넘으면 새 키프레임으로 지정해 캐시에 넣고, 안 넘으면 버립니다. 움직인 만큼만 기억합니다.

코드에서 이 함수는 _compute_flow_magnitude()입니다. 실제 광학 흐름 네트워크를 돌리는 게 아니라 자기가 예측한 깊이와 포즈로 흐름을 계산한다는 점이 요령입니다. 추가 모델이 필요 없습니다.

그리고 여기서 구현 난제가 하나 생깁니다. 비키프레임은 캐시를 읽어서 어텐션은 해야 하는데, 자기 K/V는 남기면 안 됩니다. 문제는 FlashInfer의 페이지 어텐션이 캐시에서만 읽을 수 있다는 것입니다. 현재 프레임의 K/V를 캐시 밖에 두고 참조할 방법이 없습니다.

해결책이 아주 실용적입니다.

① 축출 유예
_defer_eviction = True — 잠시 축출 로직을 껐다
② 일단 넣고 어텐션
append_frame()compute_attention()
③ 되돌리기
rollback_last_frame() — 페이지 반납 + 컨텍스트 토큰 카운터 되돌림 + 프레임 카운트 감소

"쓴 다음 지운다." 데이터베이스의 트랜잭션 롤백과 같은 패턴입니다. rollback_last_frame()append_frame()의 세 가지 부작용(페이지 할당, 컨텍스트 토큰 쓰기, 카운터 증가)을 정확히 역순으로 되돌립니다.

참고로 저장소 뉴스에 2026년 4월 24일 FlashInfer KV 캐시 버그 수정이 있습니다 — keyframe_interval > 1일 때 비키프레임이 조용히 캐시에 남던 버그였고, 수정 후 320프레임 이상에서 포즈·재구성 품질이 개선됐다고 적혀 있습니다. 이 롤백 로직이 얼마나 미묘한지 보여주는 흔적입니다.

실무에서는 demo.py가 간격을 자동으로 잡아줍니다.

demo.py — 키프레임 간격 자동 선택
if args.keyframe_interval is None: if args.mode == "streaming" and num_frames > 320: args.keyframe_interval = (num_frames + 319) // 320 # ceil(N / 320) else: args.keyframe_interval = 1

캐시에 남는 프레임 수를 학습 상한인 320 이하로 유지하는 것, 그게 전부입니다.

6-5. 두 가지 추론 모드

Direct 모드VO 모드 (windowed)
동작세 컨텍스트를 리셋 없이 계속 누적겹치는 창으로 분할, 창마다 상태 리셋
적정 범위~3,000 프레임 (학습 길이의 약 10배)수만 프레임
오차원모델의 프레임별 추론 오차만+ 창 경계마다 Sim(3) 정렬 오차 누적
정확도더 정확 — 외부 정렬 단계가 없다범위 초과 시 유일한 선택
논문 기본값이것 (k=64, m=1, bf16)도시 규모·초장문 데모

VO 모드의 창 이어붙이기가 어떻게 작동하는지 봅시다.

창 사이를 Sim(3) 정렬로 이어붙인다크게 보기

각 창은 자기만의 좌표계와 스케일을 가집니다(창 시작마다 앵커 프레임으로 새로 못 박으니까요). 그래서 이어붙이려면 닮음 변환(similarity transform) 을 구해야 합니다 — 스케일 ss, 회전 RR, 이동 tt의 7자유도. 이걸 Sim(3) 군이라고 부릅니다.

코드의 _pairwise_alignment()가 하는 일:

앵커 고르기
겹침 구간에서 양쪽 창 모두에서 키프레임인 프레임 쌍을 찾습니다. 그중 시간상 가장 마지막 것을 정렬 기준으로 씁니다. (키프레임 쌍이 없으면 겹침 첫 프레임으로 폴백)
회전
Rab=RaRbR_{ab} = R_a R_b^{\top} — 두 창이 같은 프레임에 대해 추정한 회전의 차이.
스케일
짝지어진 모든 키프레임의 깊이 맵을 모아 비율의 중앙값을 씁니다. 평균이 아니라 중앙값이라는 점이 중요합니다 — 깊이 예측의 이상치에 흔들리지 않습니다.
이동
tab=casabRabcbt_{ab} = c_a - s_{ab} R_{ab} c_b — 회전과 스케일을 적용한 뒤 남는 평행이동.

그런 다음 이 변환을 창 전체의 예측에 적용합니다. 포즈의 회전과 중심, 깊이(스케일만), 월드 점(전부) — 각각 다른 방식으로 변환됩니다.

당신의 영상은 어떤 모드로 돌려야 할까요. 직접 계산해 보세요.

플래너에서 window_size 128을 넣었을 때 창 하나가 128프레임이 아니라 훨씬 많은 프레임을 덮는 것을 보셨을 겁니다. README가 이걸 명시적으로 경고합니다.

window_size는 실제 프레임 수가 아니라 KV 캐시 슬롯 수를 셉니다. 앞의 8개 슬롯은 앵커 프레임이고, 남은 120개 슬롯이 키프레임을 담습니다. keyframe_interval = 13이면 창 하나가 8 + 120 × 13 = 1,568 실제 프레임을 덮습니다.

이런 종류의 함정은 논문에는 절대 안 적히고 README에만 있습니다.


7부. 결과 읽기 — 무엇이 진짜 인상적인가

7-1. Oxford Spires — 가장 잔인한 시험

Oxford Spires는 옥스퍼드 캠퍼스를 라이다-관성 SLAM으로 정답 궤적을 만든 데이터셋입니다. 왜 잔인한가:

  • 실외 안뜰에서 어두운 계단으로 급전환
  • 오랜 시간이 지난 뒤 같은 장소 재방문
  • 하나의 궤적 안에서 스케일이 크게 변함

즉 국소 포즈 정확도와 장거리 전역 일관성을 동시에 요구합니다. GCA가 겨냥한 바로 그 지점입니다.

320 프레임 (희소 설정) — 이 길이는 모든 방법이 돌아가므로 공정 비교가 됩니다.

방법 (논문 표2의 17개 중 발췌)유형AUC@15 ↑AUC@30 ↑ATE ↓RPE-rot ↓
Fast3R오프라인1.202.9934.8059.51
VGGT오프라인23.8435.0924.7822.79
Pi3오프라인38.6448.6514.0310.33
DA3오프라인49.8456.6812.8716.17
DROID-SLAM최적화8.5821.4121.846.90
MegaSAM최적화15.9128.0313.807.24
ViPE최적화45.3551.8810.525.98
CUT3R스트리밍5.9814.9518.167.18
TTT3R스트리밍13.9225.9019.3513.30
Wint3R스트리밍11.6123.4221.106.27
LingBot-Map스트리밍61.6475.166.423.70

이 표에서 놓치면 안 되는 세 가지를 짚겠습니다.

① 스트리밍 모델이 오프라인 모델을 이겼습니다. 이건 원래 일어나면 안 되는 일입니다. 오프라인 모델은 미래를 볼 수 있고, 스트리밍 모델은 못 봅니다. 정보량 자체가 다릅니다. 그런데 AUC@15에서 61.64 대 49.84(DA3)입니다.

왜 이런 일이 벌어졌을까요. 논문의 진단이 예리합니다.

"기존 오프라인 방법은 연속 프레임이 서로 가깝고 대체로 같은 국소 영역을 보는, 뷰포인트 수가 적은 데이터셋으로 학습되었다. Oxford Spires의 복잡한 장면 전환과 큰 시점 변화를 만나면 학습된 사전 지식이 전이되지 않는다."

즉 오프라인 모델은 원리적 우위를 가졌지만 그 우위를 쓸 수 있는 훈련을 받지 않았습니다. 반면 LingBot-Map은 24→320뷰 커리큘럼으로 긴 궤적을 정면으로 학습했습니다. 정보 접근권보다 훈련 분포가 결정적이었다는 이야기입니다.

② 최적화 기반 방법도 이겼습니다. ViPE는 반복 번들 조정으로 재투영 오차를 명시적으로 최소화합니다. 즉 목적 함수를 직접 푸는 방법입니다. 그런데 AUC@15 61.64 대 45.35. 순전파 한 번으로 그렇습니다.

단, 정직하게 짚어야 할 것이 있습니다. RPE-trans(국소 이동 오차)에서는 ViPE가 0.43으로 LingBot-Map의 1.01보다 좋습니다. DROID-SLAM(1.02)과 MegaSAM(0.76)도 이 지표에서는 경쟁력이 있습니다. 최적화 방법의 강점은 여전히 프레임 대 프레임의 미세 정확도입니다. LingBot-Map이 압도하는 것은 전역 궤적 일관성(ATE)과 회전(RPE-rot 3.70)입니다.

③ 스트리밍 모델들 간 격차는 자릿수 수준입니다. 최고 경쟁 스트리밍 모델 CUT3R의 AUC@15는 5.98입니다. 61.64 대 5.98 — 10배가 넘습니다.

Oxford Spires 궤적 비교 — 회색 점선이 정답, 파란 선이 예측크게 보기

저장소의 벤치마크 파이프라인이 자동 생성하는 궤적 비교 도표. 회색 점선(ref)과 파란 실선(est)이 거의 겹칩니다. 출처: Robbyant/lingbot-map (Apache-2.0)

7-2. 3,840 프레임 — 길이가 진실을 드러낸다

글 맨 앞에서 봤던 표가 여기입니다. 12배 긴 시퀀스에서 무엇이 남는지.

CUT3R · 320 → 3,840 프레임
18.16 → 32.47
Wint3R
21.10 → 32.90
TTT3R
19.35 → 25.05
LingBot-Map
6.42 → 7.11

여기서 흥미로운 것은 속도까지 함께 보는 것입니다.

방법ATE (3,840 프레임)FPS해석
CUT3R32.4729.21빠르지만 표류
TTT3R25.0528.97빠르지만 표류
Stream3R-w33.7313.66느리고 표류
InfiniteVGGT31.757.78느리고 표류
Wint3R32.903.88가장 느리고 표류
LingBot-Map7.1120.29정확하면서 실시간

CUT3R과 TTT3R이 더 빠릅니다 — 순환 상태 방식은 컨텍스트가 상수니까 당연합니다. 하지만 오차가 4~5배입니다. LingBot-Map은 20 FPS라는 실시간 문턱을 넘긴 채로 최고 정확도를 냈습니다. 이 조합이 요점입니다.

7-3. 일반화 — 방 하나부터 도시까지

Oxford Spires에서만 잘하는 특수병기가 아님을 보이기 위해 세 벤치마크를 더 돌렸습니다.

ATE ↓ETH3D
실내+실외, 레이저 스캔
7-Scenes
방 규모, 모션블러
Tanks & Temples
대형 실외 구조물
CUT3R1.430.291.79
TTT3R1.220.100.66
Wint3R0.860.120.88
Stream3R1.670.100.76
InfiniteVGGT1.460.121.00
LingBot-Map0.430.080.20

Tanks & Temples에서 AUC@30이 92.80(2위 Stream3R 81.33)이고 ATE는 3.8배 낮습니다. 7-Scenes는 궤적이 짧아서 대부분의 방법이 어느 정도는 되는 영역인데, 여기서도 1위입니다. 이 데이터셋의 난점은 길이가 아니라 텍스처 없는 벽, 반복 구조, 심한 모션블러입니다.

재구성 품질(F1)도 함께 보겠습니다. 포즈가 정확해야 점군이 겹치므로, 포즈 개선이 그대로 넘어옵니다.

F1 ↑ / Acc ↓ / Comp ↓ETH3D7-ScenesNRGBD
StreamVGGT58.1169.4445.08
CUT3R67.6358.9832.22
TTT3R68.4877.2553.55
Stream3R72.8778.7954.07
Wint3R77.2878.8156.96
LingBot-Map86.79 (0.16 / 0.08)80.39 (0.02 / 0.07)64.26 (0.07 / 0.03)

작은 참고: 논문 본문 6.3절은 ETH3D F1을 98.98이라고 서술하는데 표 5의 값은 86.79입니다. 표를 기준으로 읽었습니다. ETH3D ATE도 본문은 0.22, 표는 0.43입니다. 프리프린트 단계의 서술 오류로 보이며, 어느 쪽이든 1위 순위는 바뀌지 않습니다.

NRGBD에서 개선폭이 가장 큽니다(+7.30). 이 데이터셋은 세밀한 기하가 있는 어수선한 실내인데, 포즈가 표류하면 같은 표면이 다른 위치로 투영되어 표면이 두 겹으로 겹치거나 흐려집니다. 표류에 강한 것이 곧 재구성 충실도로 이어집니다.

7-4. 어블레이션 — 각 부품이 얼마나 기여했나

상대 손실앵커 초기화컨텍스트 토큰비디오 RoPEAUC@3 ↑ATE ↓RPE-rot ↓
9.808.592.57
13.637.882.90
13.918.255.35
15.757.462.26
16.395.981.93
기준선 (상대 손실만)
ATE 8.59
+ 앵커 초기화
7.88
+ 컨텍스트 토큰 (궤적 메모리)
7.46
+ 비디오 RoPE
5.98

세 번째 줄(상대 손실을 뺀 조합)이 반례 대조군입니다. RPE-rot이 2.26 → 5.35로 튀고 ATE도 뒷걸음질칩니다.

그리고 어블레이션 실험 하나당 약 3,840 GPU 시간이 들었다는 것도 적어둘 만합니다. 이 표 한 장이 약 2만 GPU 시간입니다.

7-5. 가장 반직관적인 결과

논문에서 가장 흥미로운 표는 마지막입니다. 포즈 참조 윈도우(64) vs 전체 인과 어텐션을 비교했습니다.

윈도우 크기ATE ↓RPE-trans ↓RPE-rot ↓FPS ↑메모리 ↓
645.981.331.9320.2913.28 GB
전체 (Full)6.601.501.7111.8736.06 GB

속도 1.7배, 메모리 2.7배 절감은 예상된 결과입니다. 그런데 정확도도 더 좋습니다. 과거의 이미지 토큰을 다 들고 있는 쪽이 더 나빴습니다.

왜일까요. 논문의 설명:

"모든 과거 이미지 토큰을 유지하면 멀고 덜 관련된 프레임에서 잡음이 유입되어 어텐션 계산을 혼란시킬 수 있다."

정보가 많은 것이 항상 좋은 게 아닙니다. 100미터 전에 지나간 벽의 픽셀 특징 999개는 지금 프레임을 등록하는 데 도움이 안 되면서, 소프트맥스의 확률 질량을 나눠 가집니다. GCA는 이미지 토큰은 버리고 기하 요약(컨텍스트 토큰)만 남겨서 필요한 신호만 통과시키는 필터 역할까지 합니다.

단 하나 예외가 있습니다. RPE-rot에서는 전체 어텐션이 1.71로 더 좋습니다. 밀도 높은 과거 토큰이 국소 회전 단서를 조금 더 풍부하게 준다는 뜻입니다. 저자들도 이걸 인정하고 "효율성 이득과 전역 일관성 개선에 비하면 미미하다"고 씁니다. 모든 지표에서 이기지는 않았다는 것을 명시한 정직한 표입니다.


8부. 2026년, 이 기술이 앉아 있는 자리

이건 논문 하나가 아니라 스택의 한 층이다

LingBot-Map은 독립 연구가 아닙니다. 이 모델의 이름을 다시 보세요 — LingBot-Map. 형제들이 있습니다.

Robbyant는 앤트그룹 산하 임바디드 AI 회사입니다. 노인 돌봄, 의료 보조, 가사 같은 영역에서 "사람의 일상을 이해하고 향상시키는 로봇 동반자"를 목표로 한다고 밝히고 있습니다. 그리고 2026년 상반기에 LingBot 시리즈를 연달아 오픈소스로 냈습니다.

LingBot 스택 — 깊이 인식 · 행동 정책 · 월드 모델 · 공간 지도크게 보기

모델역할공개
LingBot-Depth고정밀 공간 지각 — 단일 프레임 깊이LingBot-World보다 앞서
LingBot-VLA비전-언어-행동 모델 — 로봇의 "범용 두뇌"Depth 다음 · VA 2.0은 2026년 7월
LingBot-World월드 모델 — 밀리초급 상호작용, 2.0은 720p/60fps 1시간 연속 생성1.0 2026년 1월 · 2.0 7월
LingBot-Map스트리밍 3D 지도 — 지속적 공간 기억2026년 4월

이 배치를 보면 LingBot-Map의 위치가 선명해집니다. VLA가 "무엇을 할지"를 결정하려면 "여기가 어디인지"를 알아야 합니다. 그리고 그 "어디"는 한 프레임의 깊이가 아니라, 30분 전에 지나온 거실을 포함하는 지속적 지도여야 합니다.

논문 결론부가 이걸 직접 말합니다.

"정확한 실시간 밀집 3D 재구성을 가능하게 함으로써, LingBot-Map은 자율 항법, 증강현실, 그리고 무엇보다도 물리 세계와 상호작용하기 위해 지속적이고 즉각적인 공간 이해가 필요한 임바디드 AI 시스템으로 문을 연다."

왜 지금 이게 중요해졌나

2026년 AI 산업의 지형에서 세 흐름이 이 기술과 맞물립니다.

① 로봇의 상용화 압력
embodiment
VLA 모델이 실험실을 나오려면 "집 안 지도"가 필요하다. 라이다 없이 카메라만으로. 원가 때문이다.
② 월드 모델의 검증 문제
world models
생성된 영상이 "기하학적으로 일관되는지" 재구성으로 채점할 수 있다. 생성과 측정의 순환.
③ 공간 지능 담론
spatial intelligence
언어 다음이 공간이라는 합의가 굳어졌다. 그런데 공간의 "기억"은 아직 미해결이었다.

②가 특히 재미있습니다. 저장소의 워크드 예제 중 하나가 LingBot-World가 생성한 영상을 LingBot-Map으로 재구성하는 것입니다.

README — Worked Example: LingBot-World scenes
python demo_render/batch_demo.py \ --video_path /data/demo_videos/lingbo_world_frames.mp4 \ --config demo_render/config/outdoor_drive.yaml \ --mode windowed --window_size 128 ...

# "우리의 월드 모델이 생성한 영상을 재구성한다. # 같은 파이프라인이 생성된 영상에도 그대로 작동한다."

이게 왜 중요할까요. 논문의 학습 데이터가 게임 엔진 합성 데이터에 30% 이상 의존한다는 걸 5부에서 봤습니다. 재구성 모델은 합성 데이터로 학습되고, 생성 모델은 재구성 모델로 검증되고, 그렇게 검증된 생성 모델이 다시 학습 데이터를 만듭니다. 시뮬레이션과 측정이 서로를 부트스트랩하는 고리가 닫히고 있습니다.

논문 Figure 2에도 "월드 모델이 생성한 카툰 영상"을 재구성한 결과가 실려 있습니다. 만화풍 영상에서 기하가 복원된다는 것은, 이 모델이 사실적 텍스처에 의존하지 않고 구조를 보고 있다는 증거이기도 합니다.

실내 다중 방 순회와 실외 주행 — 두 장면 모두 단일 좌표계로 복원크게 보기

출처: Robbyant/lingbot-map (Apache-2.0)

정직하게, 아직 안 되는 것들

논문의 한계 절이 짧지만 정확합니다.

한계 ①
명시적 루프 클로저가 없다
같은 장소를 다시 방문했을 때 "여기 왔던 곳이다"를 인식해 궤적을 스냅시키는 메커니즘이 없습니다. 궤적 메모리가 암묵적으로 일부 역할을 하지만, 고전 SLAM의 포즈 그래프 최적화만큼 강하게 오차를 되잡지는 못합니다. 저자들은 이걸 어텐션 안으로 넣는 것을 향후 과제로 제시합니다.
한계 ②
궤적 메모리의 압축 손실
프레임당 6개 토큰은 아주 적습니다. 수만 프레임 규모에서는 세밀한 기하 디테일이 이 압축을 통과하지 못합니다. VO 모드로 창을 나눠야 하는 이유이기도 합니다.
한계 ③
동적 장면과 상태 리셋
움직이는 물체가 많은 장면은 아직 정면으로 다루지 않았습니다(데모에 Droid-W 동적 시퀀스가 있지만 별도 튜닝이 필요합니다). 그리고 README가 밝히듯, 학습 데이터에서 본 최장 거리를 넘어가면 상태 리셋이 필요해집니다 — 포즈가 붕괴하면 --mode windowed로 갈아타야 합니다.

한계 ③의 마지막 문장은 README에만 있는 실전 정보입니다.

"우리 방법은 기본적으로 상태 리셋을 수행하지 않으므로, 최대 추론 범위는 학습 시 데이터셋에서 본 최장 거리로 제한됩니다. 그 거리를 넘어가면 상태 리셋이 필요해집니다. 포즈 붕괴가 관찰되면 윈도우 모드로 전환하세요."

논문 본문의 "10배(약 3,000 프레임)까지 안정적"이라는 표현과 짝을 지어 읽어야 합니다. 무한히 되는 게 아닙니다.

그래서 어디에 쓸 수 있나

용도지금 바로 되는 것아직 조심할 것
실내 로봇 항법카메라 한 대로 집·창고 전체 평면도 + 궤적. 25,000프레임 데모가 이걸 보였다.사람·반려동물이 많이 움직이는 장면. 재방문 시 스냅이 없음.
공간 스캔 / 디지털 트윈휴대폰 영상 한 편 → 점군. 캘리브레이션 불필요. 오프라인 렌더 파이프라인까지 제공.측량급 절대 정밀도가 필요하면 여전히 COLMAP + 라이다.
AR / 공간 앵커20 FPS 실시간, 앵커 프레임 기준 좌표계가 안정적.GPU 필요. 모바일 온디바이스는 아직 아니다.
자율주행 데이터 처리주행 영상의 포즈·깊이 자동 라벨링. 야외 프리셋 제공.도시 규모는 VO 모드 → 창 경계 정렬 오차 누적.
생성 영상 평가생성된 영상의 기하 일관성을 재구성 품질로 채점.정량 지표 체계는 아직 정립 전.

9부. 직접 돌려보기

아파치 2.0입니다. 상업적 사용도 됩니다. 가중치는 Hugging Face와 ModelScope에 올라와 있습니다.

설치

hljs language-bash
conda create -n lingbot-map python=3.10 -y
conda activate lingbot-map

# PyTorch 2.8.0 + CUDA 12.8 권장 (Kaolin 프리빌드 휠이 이 조합에만 있다)
pip install torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128
pip install -e .

# FlashInfer — 페이지 KV 캐시. 순수 파이썬 패키지로 첫 사용 시 CUDA 커널을 JIT 컴파일한다
pip install --index-url https://pypi.org/simple flashinfer-python

# 시각화 (viser 뷰어)
pip install -e ".[vis]"

첫 실행

hljs language-bash
python demo.py --model_path /path/to/lingbot-map.pt \
    --image_folder example/courthouse --mask_sky

브라우저에서 http://localhost:8080을 열면 viser 기반 3D 뷰어가 뜹니다. 저장소에 예제 장면 3개가 들어 있습니다 — courthouse, university, 그리고 루프 클로저 궤적을 담은 loop.

체크포인트 세 종류

이름용도
lingbot-map논문·벤치마크·오프라인 데모에 쓴 균형 체크포인트. 여기서 시작하세요.
lingbot-map-long긴 시퀀스와 대규모 장면에 더 적합
lingbot-map-stage11단계(오프라인) 체크포인트. VGGT 모델에 로드해 양방향 추론 가능

자주 부딪히는 벽

증상원인대처
GPU 메모리 부족초기 앵커 단계의 활성값 피크--num_scale_frames 2 (기본 8 → 2). --offload_to_cpu는 기본 켜져 있음
너무 느림카메라 헤드 반복 정제 4회--camera_num_iterations 1 — 포즈 정확도를 조금 내주고 속도와 캐시를 얻는다
FlashInfer 설치 실패내부 미러 인덱스 / CUDA 조합--use_sdpa로 PyTorch 기본 어텐션 폴백
긴 영상에서 포즈 붕괴학습 범위 초과 · 상태 리셋 없음먼저 --keyframe_interval만 조정, 그래도 안 되면 --mode windowed
하늘이 점군에 섞임무한 거리 픽셀의 깊이 예측--mask_sky (ONNX 하늘 분할 모델 자동 다운로드, 마스크는 캐시됨)
점군이 흐릿함저신뢰도 점이 섞임--conf_threshold 상향 (기본 1.5)

렌더링 파이프라인

viser 뷰어가 못 버티는 길이는 오프라인 렌더러로 갑니다. demo_render/batch_demo.py가 영상 하나를 받아 추론 + 점군 플라이스루 MP4 생성까지 한 명령으로 처리합니다. 가상 카메라 경로는 YAML로 설계합니다.

hljs language-yaml
camera:
  fov: 60.0
  transition: 30          # 인접 세그먼트 간 블렌딩 프레임 수
  segments:
    - mode: follow        # 궤적을 뒤에서 따라가는 추적 카메라
      frames: [0, 1500]
      back_offset: 0.3    # 뒤로 얼마나 (장면 스케일의 비율)
      up_offset: 0.08
      look_offset: 0.4
      smooth_window: 30
    - mode: birdeye       # 위로 솟아 전체를 보여주는 리빌 샷
      frames: [1500, 1800]
      reveal_height_mult: 2.5
    - mode: follow        # 다시 추적 카메라로
      frames: [1800, -1]

follow / birdeye / static / pivot 네 가지 모드를 세그먼트로 이어 붙입니다. 다만 함정이 하나 있습니다 — --config로 YAML을 로드한 상태에서 세그먼트 관련 CLI 플래그를 하나라도 주면, YAML의 segments가 통째로 버려지고 플래그로 재구성됩니다. YAML로만 가려면 그 플래그들을 아예 건드리지 마세요.

벤치마크 재현

2026년 5월 25일 평가 벤치마크가 공개됐습니다. benchmark/ 아래에 9개 데이터셋(KITTI, Oxford Spires, VBR, Droid-W, TUM-D, 7-Scenes, ETH3D, Tanks and Temples, NRGBD)용 파이프라인이 들어 있고, 데이터셋 설정은 YAML, 결과는 HTML 리포트로 나옵니다. Oxford Spires는 preprocess/oxford.py로 먼저 전처리해야 합니다.


에필로그 — 무엇을 버릴지 아는 것

이 논문의 기술적 기여를 한 줄로 줄이면 "어텐션 마스크를 세 조각으로 나눴다"입니다. 새로운 층도, 새로운 손실도, 새로운 정규화도 아닙니다. 어떤 토큰이 어떤 토큰을 볼 수 있는지의 패턴을 바꾼 것이 전부입니다.

그런데 그 마스크를 그리기 위해 필요했던 것은 세 가지였습니다.

첫째
40년간 축적된 도메인 지식. 좌표 기준 · 지역 창 · 전역 지도라는 삼분할은 SLAM 연구자들이 수십 년에 걸쳐 발견한 구조입니다. 이걸 몰랐다면 "앵커 + 윈도우 + 궤적 메모리"라는 조합을 떠올릴 수 없었습니다.
둘째
이미 있던 것을 다시 보는 눈. 궤적 메모리로 쓰인 6개 토큰은 VGGT에도 있었습니다. 아무도 그걸 "기억 슬롯"으로 보지 않았을 뿐입니다.
셋째
시스템 엔지니어링. 마스크 아이디어만으로는 20 FPS가 안 나옵니다. LLM 서빙에서 페이지 어텐션을 가져오고, plan 호출을 24회에서 1회로 줄이고, 페이지 크기를 999로 정확히 맞추고, 롤백 트랜잭션을 구현해야 했습니다.

그리고 결국 남는 교훈은 7-5절의 그 반직관적인 표입니다. 모든 과거를 들고 있는 쪽이 더 나빴습니다. 버릴 것을 버렸을 때 빨라지기만 한 게 아니라, 정확해졌습니다.

글 맨 처음의 질문으로 돌아가겠습니다. 우리가 방을 기억하는 방식은 문 하나, 창문 하나, 거리감 몇 개였습니다. 그건 저장 용량의 한계가 만든 타협이 아니라, 무엇이 중요한지 아는 능력이었습니다.

기계가 이제 그걸 배우기 시작했습니다.


참고 자료

논문과 코드

직계 선행 연구

  • Wang et al. VGGT: Visual Geometry Grounded Transformer. CVPR 2025 최우수 논문.
  • Wang et al. DUSt3R: Geometric 3D Vision Made Easy. CVPR 2024.
  • Oquab et al. DINOv2: Learning Robust Visual Features without Supervision. 2023.
  • Ranftl et al. Vision Transformers for Dense Prediction (DPT). ICCV 2021.
  • Kwon et al. Efficient Memory Management for LLM Serving with PagedAttention (vLLM). SOSP 2023.
  • Darcet et al. Vision Transformers Need Registers. ICLR 2024.

비교 대상 스트리밍 모델

  • CUT3R (Wang et al.), TTT3R (Chen et al., ICLR 2026), Stream3R (Lan et al., ICLR 2026), Wint3R (Li et al., ICLR 2026), StreamVGGT, InfiniteVGGT, Spann3R, SLAM3R

Robbyant / LingBot 시리즈