이 레포가 무엇을 하는 물건인가.
스마트폰 카메라로 방 안을 한 바퀴 걷기만 해도, LingBot-Map이 실시간으로 공간을 3D 포인트 클라우드로 재구성한다. 기존 방식처럼 "먼저 영상을 다 찍고, 수 분 뒤에 결과를 본다"가 아니라 프레임이 들어오는 순간 바로 예측이 나온다.
핵심 아이디어는 Geometric Context Transformer(GCT)라는 트랜스포머 아키텍처다. 앵커 컨텍스트(anchor context)·포즈 기준 윈도우(pose-reference window)·궤적 메모리(trajectory memory) 세 가지를 하나의 스트리밍 프레임워크 안에 녹여, 장거리 누적 오류(드리프트)를 스스로 잡는다.
트렌딩 이유 · 기존 방법 대비 장점 · 경쟁 모델 비교.
3D 재구성 분야는 수십 년간 SfM(Structure from Motion)과 SLAM(Simultaneous Localization and Mapping)이 주도했다. 그런데 이 방식들은 공통적으로 수백~수천 번의 반복 최적화가 필요하고, 영상이 길어질수록 누적 오류(드리프트)가 심해진다는 한계가 있었다. 최근 VGGT·DUSt3R·MASt3R 같은 "피드포워드 3D 파운데이션 모델" 계보가 등장하면서 패러다임이 바뀌고 있다. LingBot-Map은 이 계보에서 스트리밍(온라인·실시간) 추론에 특화된 첫 번째 모델 중 하나다.
| 비교 항목 | 기존 방식 (COLMAP/SLAM) | LingBot-Map |
|---|---|---|
| 추론 방식 | 반복 최적화 (수백~수천 번) | 단일 피드포워드 패스 |
| 처리 속도 | 수 분~수십 분 | ~20 FPS 실시간 |
| 장시간 영상 | 메모리·시간 급증, 드리프트 누적 | 페이지드 KV 캐시로 10,000+ 프레임 안정 |
| 스트리밍 가능 | 불가 (전체 영상 수집 후 처리) | 프레임 단위 스트리밍 지원 |
| 출력 표현 | 희소 3D 점 + 카메라 행렬 | 밀집 깊이맵 + 포즈 + 포인트맵 |
COLMAP 같은 SfM 파이프라인은 특징점 추출→매칭→번들 조정(bundle adjustment)을 수백 번 반복해야 한다. 100장 이미지에 수 분, 10,000장이면 수 시간이다. 로봇·드론·AR 같은 실시간 시스템에 쓰기 어렵다.
비디오 SLAM도 오프라인 루프 클로저(loop closure) 단계가 필수라 진짜 스트리밍이 어렵고, 환경 변화나 빠른 움직임에 취약하다.
Geometric Context Transformer는 앵커 프레임과 궤적 메모리를 KV 캐시에 유지해 드리프트를 억제한다. FlashInfer 기반 페이지드 어텐션 덕분에 KV 캐시가 고정 크기로 유지되어 영상이 아무리 길어도 메모리가 선형적으로 늘지 않는다. 결과적으로 25,000프레임 실내 워크스루를 13분 만에 처리했다.
경쟁 모델인 VGGT(Facebook Research)는 양방향(bidirectional) 어텐션으로 정확도가 높지만 전체 시퀀스를 한꺼번에 봐야 해 스트리밍이 불가능하다. LingBot-Map은 VGGT의 스테이지-1 체크포인트를 재사용하면서 인과적(causal) 어텐션으로 전환해 온라인 추론을 가능하게 했다.
백엔드·ML·시각화 스택 한눈에.
| 레이어 | 기술 / 버전 | 역할 |
|---|---|---|
| 딥러닝 프레임워크 | PyTorch 2.8.0 + CUDA 12.8 | 모델 학습·추론 엔진. torch.compile로 추가 가속. |
| 비전 백본 | DINOv2 ViT-L/14 | 이미지 패치를 고차원 특징 벡터로 인코딩. 패치 크기 14×14, 임베딩 1024차원. |
| 어텐션 가속 | FlashInfer (paged KV cache) | 긴 시퀀스용 페이지드 어텐션. CUDA 커널 JIT 컴파일로 메모리 효율 극대화. |
| 대안 어텐션 | PyTorch SDPA | FlashInfer 없을 때 폴백. --use_sdpa 플래그로 전환. |
| 위치 인코딩 | RoPE (3D) | 시간축·공간축을 아우르는 회전 위치 임베딩. 320 뷰 기준 학습. |
| 모델 허브 | HuggingFace Hub | 가중치 다운로드·버전 관리. PyTorchModelHubMixin 상속. |
| 3D 시각화 | viser ≥0.2.23 | 브라우저 기반 실시간 3D 뷰어 (localhost:8080). |
| 렌더링 파이프라인 | Open3D 0.19 + NVIDIA Kaolin + ffmpeg | 오프라인 렌더링. 포인트 클라우드→MP4 변환. |
| 하늘 분할 | ONNX Runtime (skyseg.onnx) | 야외 영상에서 하늘 픽셀 마스킹. 포인트 클라우드 품질 향상. |
| CUDA 확장 | Custom .cu 커널 | Voxel Morton 인코딩, Frustum Culling — 렌더링 파이프라인 전용. |
| 기타 의존성 | einops · scipy · Pillow · tqdm · opencv-python | 텐서 연산 편의 · 기하 계산 · 이미지 로딩 · 진행 표시 |
이미지 스트림이 들어와서 3D 출력이 나오기까지 한 줄기를 끝까지 따라가 보자.
GCT(Geometric Context Transformer)는 크게 세 덩어리로 나뉜다: ① 패치 인코더(DINOv2 백본)로 각 프레임의 시각 특징을 뽑고, ② 스트리밍 어그리게이터(AggregatorStream)가 KV 캐시를 유지하며 인과적 어텐션으로 시공간 컨텍스트를 누적하고, ③ 예측 헤드들이 밀집 깊이맵·포인트맵·카메라 포즈를 동시에 뱉는다.
① 입력 이미지(518×378)가 패치 임베딩 레이어로 들어간다. 14×14 픽셀 블록 하나가 1024차원 벡터 하나가 된다(→ 37×27 = 999개 토큰).
② 토큰들이 AggregatorStream의 인과적 어텐션을 통과한다. 이 레이어는 현재 프레임 토큰과 KV 캐시에 저장된 과거 프레임 정보를 합쳐서 "지금 이 장면이 지금까지 본 장면과 어떻게 연결되는지"를 계산한다. 핵심은 여기서 RoPE가 시간 순서를 인코딩한다는 것.
③ 집약된 토큰이 세 갈래로 나뉜다.
# gct_base.py — GCTBase._build_depth_head()
self.depth_head = DPTHead(
dim_in=2 * self.embed_dim, # 2048 = 두 레이어 토큰 concat
output_dim=2, # depth + confidence
activation="exp", # 깊이는 항상 양수여야 한다
conf_activation="expp1"
)
④ CameraHead는 카메라 포즈를 예측하되, 4번 반복 정제(iterative refinement)를 한다. 처음 예측이 틀리면 자신의 출력을 다시 입력으로 넣어 4번까지 교정한다. --camera_num_iterations 1로 1회로 줄이면 속도가 빨라진다.
⑤ 최종 출력: 카메라 포즈(translation 3 + quaternion 4 + FoV 2 = 9차원), 밀집 깊이맵(픽셀마다 깊이+신뢰도), 포인트맵(픽셀마다 XYZ 좌표+신뢰도). 이 셋을 합치면 포인트 클라우드가 완성된다.
KV 캐시는 "인덱스 카드 서랍"이다. 과거에 본 모든 내용을 통째로 기억하는 대신, 중요한 장면(키프레임)의 핵심만 카드에 적어 서랍에 꽂아둔다. 새 프레임이 들어오면 서랍에서 관련 카드를 꺼내 맥락을 이해하고, 중요하면 새 카드를 추가하고 오래된 카드는 버린다. 서랍 크기는 항상 일정하다.
어떤 폴더가 무슨 일을 하나.
| 파일/폴더 | 역할 |
|---|---|
lingbot_map/models/gct_stream.py | 가장 중요한 파일. 스트리밍 추론 루프·KV 캐시 초기화·슬라이딩 윈도우 로직이 모두 여기 있다. |
lingbot_map/aggregator/stream.py | 인과적 어텐션 집약기. FlashInfer 또는 SDPA 백엔드를 선택해 KV 캐시 어텐션을 수행한다. |
lingbot_map/heads/dpt_head.py | DPT(Dense Prediction Transformer) 헤드. 멀티스케일 특징을 융합해 픽셀별 깊이+신뢰도를 출력한다. |
lingbot_map/heads/camera_head.py | 카메라 포즈 헤드. AdaLN(적응형 레이어 정규화) + 반복 정제로 9차원 포즈 벡터를 뱉는다. |
demo.py | viser 뷰어 실행 진입점. 이미지 폴더 또는 영상 파일을 받아 스트리밍 추론을 실행한다. |
demo_render/render_cuda_ext/ | 커스텀 CUDA 커널 (.cu 파일). 빌드하지 않으면 오프라인 렌더링 파이프라인이 동작하지 않는다. |
gct_profile.py | 추론 속도와 GPU 메모리 사용량을 측정하는 프로파일러. --compile --dtype bf16으로 최대 성능 확인. |
이 레포에서 배울 만한 것 + 어디를 보면 되는지.
카메라 포즈는 "카메라가 공간 어디에 있고 어느 방향을 보는지"를 행렬로 표현한 것이다. LingBot-Map은 이를 9차원 벡터(translation 3 + quaternion 4 + FoV 2)로 압축해 예측한다. 변환 코드는 lingbot_map/utils/pose_enc.py에 있다. 특히 SE3(특수 유클리드 군) 역행렬 계산인 closed_form_inverse_se3()는 3D 변환의 기초 중 기초다.
# pose_enc.py — 포즈 벡터→카메라 외/내부 파라미터 변환
def pose_encoding_to_extri_intri(pose_encoding, ...):
# T(translation) + R(quaternion) + FoV → 4×4 변환 행렬
...
LLM과 동일한 KV 캐시 개념이 비전에 적용된 실제 코드를 볼 수 있다. lingbot_map/layers/attention.py에 FlashInfer 페이지드 어텐션과 PyTorch SDPA 두 가지 백엔드가 모두 구현되어 있다. 두 코드를 비교하면 "왜 페이지드 어텐션이 필요한가"가 명확하게 보인다.
lingbot_map/layers/flashinfer_cache.py는 KV 캐시 슬롯 할당·해제·스케줄링을 담당한다. LLM 추론 최적화 공부의 훌륭한 실제 사례다.
DPT(Dense Prediction Transformer)는 ViT의 여러 레이어에서 중간 토큰을 뽑아 멀티스케일로 융합한 뒤 픽셀 해상도로 업샘플링한다. Depth Anything V2와 같은 설계다. lingbot_map/heads/dpt_head.py는 이 패턴의 깔끔한 PyTorch 구현체이므로 "ViT로 밀집 예측을 어떻게 하나"를 배우기 최적이다.
# dpt_head.py — 4개 중간 레이어 특징을 fusion
intermediate_layer_idx: List[int] = [0, 1, 2, 3]
# output_dim=2 → depth + confidence
# output_dim=4 → XYZ + confidence (포인트맵)
320프레임을 초과하는 시퀀스는 RoPE 학습 범위를 벗어나 정확도가 떨어진다. LingBot-Map은 슬라이딩 윈도우로 이를 해결한다. 인접 윈도우가 8개 키프레임을 공유(overlap)해서 포즈 정렬이 연속되도록 한다. 이 설계 패턴은 긴 문맥을 다루는 어떤 ML 시스템에도 응용 가능하다. 코드는 lingbot_map/models/gct_stream_window.py에 있다.
GCTBase가 PyTorchModelHubMixin을 상속하기 때문에 GCTStream.from_pretrained("robbyant/lingbot-map") 한 줄로 가중치를 내려받을 수 있다. 자신의 PyTorch 모델을 HF Hub에 올리고 싶을 때 참고할 만한 패턴이다.
돌리려면 무엇이 필요한가 — README·코드·벤치마크 구성에서 추출, 없는 항목은 합리적 추정임을 명시.
| 항목 | 요구사항 |
|---|---|
| GPU (필수) | NVIDIA GPU (CUDA 12.8 지원). 최소 8 GB VRAM 권장 (RTX 4060 8 GB 커뮤니티 검증, --offload_to_cpu 병용 시). 빠른 추론은 16 GB+ (A100/RTX 3090급) |
| VRAM 절약 옵션 | --offload_to_cpu: 프레임 예측을 CPU로 오프로드 (기본 활성화). --num_scale_frames 2: 스케일 추정 프레임 수 축소 (기본 8). --keyframe_interval N: 캐시 키프레임 간격 확대 |
| CUDA 버전 | 12.8 권장 (Kaolin 사전 빌드 휠 기준). 다른 버전은 Kaolin 소스 빌드 필요 |
| Python | 3.10 이상 (pyproject.toml requires-python ≥3.10) |
| PyTorch | 2.8.0 권장 (Kaolin 호환). 신버전은 Kaolin 소스 빌드 필요 |
| OS | Linux 권장 (CUDA 확장 빌드·ffmpeg 설치 편의). macOS는 FlashInfer 미지원, SDPA 폴백 사용 |
| RAM | 16 GB+ 권장 (CPU 오프로드 사용 시 더 필요) |
| 저장공간 | 모델 가중치 ~수 GB (HuggingFace 자동 다운로드). 오프라인 렌더링 시 출력 MP4·NPZ 추가 |
| 추론 속도 | ~20 FPS @ 518×378, RTX 4090급. --compile+--dtype bf16+FlashInfer로 최대화 |
--compile 플래그를 쓰면 CUDA 메모리 할당자 설정(expandable_segments:True)이 자동으로 비활성화된다. 두 가지가 동시에 켜지면 cudagraph_trees 충돌로 RuntimeError가 발생한다. demo.py가 이를 자동으로 감지해서 처리하므로 코드 상 걱정할 필요는 없지만, 커스텀 스크립트 작성 시 주의.
난이도별로 손에 익히는 단계.
레포에 포함된 example/courthouse/ 이미지 시퀀스로 viser 뷰어를 띄워본다. 브라우저에서 http://localhost:8080을 열면 3D 포인트 클라우드가 실시간으로 생성되는 것을 확인할 수 있다.
conda create -n lingbot-map python=3.10 -y
conda activate lingbot-map
pip install torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128
pip install -e ".[vis]"
pip install flashinfer-python
# 모델 가중치를 HuggingFace에서 다운로드 후:
python demo.py --model_path lingbot-map-long.pt \
--image_folder example/courthouse --mask_sky
스마트폰으로 방 안을 천천히 찍은 영상 파일을 만들어 입력으로 넣어본다. --fps로 샘플링 비율을 조정하고, --conf_threshold를 바꿔가며 포인트 클라우드 밀도 변화를 관찰한다.
python demo.py --model_path lingbot-map-long.pt \
--video_path my_room.mp4 --fps 5 \
--conf_threshold 2.0 \
--camera_num_iterations 1 # 속도 우선
다음으로 --keyframe_interval 3을 추가해서 KV 캐시 사용량이 줄어드는지 LINGBOT_DEBUG_KV=1 환경변수로 직접 확인해본다.
다양한 백엔드·정밀도 조합의 추론 속도와 메모리를 비교해본다. FlashInfer vs SDPA, bf16 vs fp32, compile 유무를 체계적으로 실험해 보면 딥러닝 추론 최적화 감각이 생긴다.
# FlashInfer + BF16 + 컴파일 최대 성능
python gct_profile.py --backend flashinfer --dtype bf16 --compile
# SDPA 폴백 비교
python gct_profile.py --backend sdpa --dtype fp32
3000프레임 이상의 영상에서 윈도우 모드를 실험한다. --window_size·--overlap_keyframes·--keyframe_interval 세 파라미터를 조합하며 "KV 캐시 슬롯 수 × keyframe_interval = 실제 커버 프레임 수" 공식을 직접 검증해본다.
python demo.py --model_path lingbot-map-long.pt \
--video_path long_walk.mp4 --fps 10 \
--mode windowed \
--window_size 128 --overlap_keyframes 8 --keyframe_interval 2
# 커버 범위: 8 + (128-8)×2 = 248프레임/윈도우
CUDA 확장을 직접 빌드하고, 배치 렌더링 파이프라인으로 포인트 클라우드 플라이스루 영상을 만들어본다. YAML 카메라 경로를 편집해서 다양한 시점(follow → birdeye → follow)을 연출해 보자.
# CUDA 확장 빌드
cd demo_render/render_cuda_ext && python setup.py build_ext --inplace
# 오프라인 렌더링
python demo_render/batch_demo.py \
--video_path my_video.mp4 \
--output_folder outputs/ \
--model_path lingbot-map.pt \
--config demo_render/config/indoor.yaml \
--mode windowed --window_size 64 --keyframe_interval 5
한 주씩 따라가는 학습 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | 카메라 모델·핀홀 카메라·내외부 파라미터 기초 | OpenCV 공식 카메라 캘리브레이션 튜토리얼, pose_enc.py |
| 2주차 | Vision Transformer(ViT) 구조 이해 — 패치 임베딩·멀티헤드 어텐션 | Dosovitskiy et al. "An Image is Worth 16×16 Words" 논문, lingbot_map/layers/block.py |
| 3주차 | DPT(Dense Prediction Transformer) — ViT로 픽셀 수준 예측하기 | Ranftl et al. DPT 논문, lingbot_map/heads/dpt_head.py |
| 4주차 | RoPE 위치 인코딩 — 회전 행렬로 위치를 인코딩하는 수학 | Su et al. RoFormer 논문, lingbot_map/layers/rope.py |
| 5주차 | KV 캐시·FlashAttention·페이지드 어텐션 — LLM 추론 최적화 기법 | FlashInfer 공식 문서, vLLM 블로그, lingbot_map/layers/flashinfer_cache.py |
| 6주차 | SfM·SLAM 기초 이해 — 전통 방식과 피드포워드의 차이 파악 | COLMAP 논문, OpenVSLAM 튜토리얼 |
| 7주차 | VGGT·DUSt3R·MASt3R 계보 비교 — 피드포워드 3D 파운데이션 모델 흐름 | 각 논문 arXiv + LingBot-Map 논문(arXiv:2604.14141) |
| 8주차 | 자체 데이터셋으로 벤치마크 실행 — 평가 지표(AUC·ATE·RPE) 이해 | benchmark/ 디렉토리, TUM-D·KITTI 데이터셋 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| GCT (Geometric Context Transformer) | LingBot-Map의 핵심 모델. 앵커 컨텍스트·포즈 기준 윈도우·궤적 메모리를 하나의 스트리밍 트랜스포머로 통합한 아키텍처. |
| 피드포워드 (Feed-forward) | 한 번의 순전파로 결과를 뽑는 추론 방식. 반복 최적화 없이 "사진 보자마자 즉답". |
| 포인트 클라우드 (Point Cloud) | 3D 공간에 흩뿌려진 XYZ 좌표 점들의 집합. LingBot-Map의 주요 출력 표현. |
| 카메라 포즈 (Camera Pose) | 카메라의 3D 위치(translation)와 방향(rotation)을 나타내는 변환 행렬. 여기서는 9차원 벡터로 압축. |
| KV 캐시 (Key-Value Cache) | 과거 프레임의 Key·Value 벡터를 저장해 현재 어텐션 계산에 재사용하는 메커니즘. LLM에서 비전으로 확장. |
| 페이지드 KV 캐시 (Paged KV Cache) | KV 캐시를 고정 크기 "페이지" 단위로 관리해 긴 시퀀스에서도 메모리를 일정하게 유지하는 기법. |
| RoPE (Rotary Position Embedding) | Q·K 벡터에 회전 행렬로 위치 정보를 주입하는 방식. LingBot-Map은 이를 3D(공간+시간)로 확장. |
| DPT (Dense Prediction Transformer) | ViT의 중간 레이어 특징을 멀티스케일로 융합해 픽셀 수준 밀집 예측(깊이·포인트)을 생성하는 헤드 구조. |
| 슬라이딩 윈도우 (Sliding Window) | 긴 시퀀스를 겹치는 작은 윈도우로 나눠 순차 처리하는 방식. RoPE 학습 범위(320프레임)를 초과하는 영상 처리에 사용. |
| FlashInfer | 페이지드 KV 캐시 어텐션을 CUDA에서 효율적으로 구현한 라이브러리. JIT 컴파일로 다양한 CUDA/PyTorch 버전 지원. |
| SfM (Structure from Motion) | 여러 이미지로부터 카메라 포즈와 3D 구조를 동시에 복원하는 전통 컴퓨터 비전 기법. 반복 최적화 기반이라 느리다. |
| 드리프트 (Drift) | 긴 시퀀스 처리 시 오류가 누적되어 나중 예측이 점점 틀어지는 현상. GCT는 앵커 컨텍스트로 이를 억제. |
| 키프레임 (Keyframe) | 전체 프레임 중 KV 캐시에 저장할 대표 프레임. --keyframe_interval N으로 N프레임마다 1개를 선택. |
| 쿼터니언 (Quaternion) | 4개 숫자(w, x, y, z)로 3D 회전을 표현하는 수학적 도구. 짐벌락이 없고 보간이 부드러워 로봇·3D 그래픽에 널리 쓰인다. |
| VGGT (Visual Geometry Grounded Transformer) | Facebook Research의 피드포워드 3D 파운데이션 모델. 양방향 어텐션이라 스트리밍 불가. LingBot-Map의 스테이지-1 가중치 출처. |
| viser | Python 기반 브라우저 내장 3D 뷰어 라이브러리. LingBot-Map이 실시간 포인트 클라우드 시각화에 사용. |