Paper · 2025-01-21 · cs.CV
Continuous 3D Perception Model with Persistent State
CUT3R turns an image stream into 3D one frame at a time. A learned persistent state of 768 tokens is updated and read by each new image, giving a metric pointmap and camera pose per frame; raymap queries predict unseen views. Author-reported: best or near-best among online methods on video depth, pose and 3D reconstruction, at about 16.6 FPS on KITTI (512x144, A100).
Feed-forward reconstructionMulti-view reconstructionMonocular depth estimationMemory-augmented neural network3d-reconstructionpointmapvideo-depthcamera-poseonline-inferencerecurrent-statetransformer
arXivPDFCodeMarkdown14 min read
AI-compiled by claude-haiku-5-5 on 2026-11-06, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Most systems that rebuild a 3D scene from photos need all the photos up front, or they fit a 3D model to each scene over many minutes. CUT3R works the other way round. It reads photos one after another, the way a person walks into a room and gradually builds a mental map of it.
The model keeps a small, fixed-size memory of what it has seen so far. Each new photo changes that memory and is also explained using it. From one photo the model outputs a 3D point for every pixel and an estimate of where the camera was. Stacking those outputs gives a growing 3D reconstruction. The model can also be asked about a camera position it has never photographed, and it answers with a guess of what is there, based on what it learned from real scenes.
The paper reports that this single approach covers several 3D tasks, works on both video and loose photo collections, and copes with moving people and objects. It is trained on a large mix of datasets, and the authors note that it can drift over very long sequences.
TL;DR
CUT3R is a recurrent transformer. It keeps 768 learned state tokens, updates them with each image, and reads context back from them. Each frame yields a world-frame pointmap, a camera pose and confidence maps, all in metric scale §3.1. A virtual camera described by a raymap can probe the state to predict an unseen view §3.2. The paper reports competitive or best results among online methods on video depth, camera pose and 3D reconstruction; all numbers are author-reported §4. The official code is non-commercial (CC BY-NC-SA 4.0).
Problem
Offline reconstruction methods that align many pairwise predictions with global alignment assume a static scene and cannot run as frames arrive §4.1. Online methods such as Spann3R handle streams, but the paper states they assume a static scene §4.1. Several baselines predict relative (not metric) pointmaps §4 Baselines, and most calibration-free pose methods work through test-time optimisation per sequence §4.2. The paper targets one model that is online, works without camera calibration or poses, handles dynamic content, and can infer unobserved parts of a scene §1.
Method
Inputs and state. The model takes a stream of images with no camera information §3. Each image is encoded into patch tokens by a ViT encoder §3.1, eq. 1. The state is a set of tokens that starts as learnable tokens shared by all scenes, so nothing is known before the first image §3.1.
State-input interaction (state-update and state-readout). Two interconnected transformer decoders run on the image tokens and the state tokens together §3.1, eq. 2. A learnable pose token z is placed in front of the image tokens. After each decoder block the two sides cross-attend, so the state takes in the new image (update) and the image takes in the stored context (readout). The output is the enriched image tokens, the pose-token output, and the updated state.
Heads. Three heads read the outputs §3.1, eqs. 3–5:
- a pointmap in the image's own frame, with confidence, from the image tokens (head_self, a DPT or linear head);
- a pointmap in the world frame, with confidence, from the image tokens and the pose token (head_cross, same family, with pose modulation inside its layer norms app. B);
- a 6-DoF pose, the rigid transform from the current frame to the world frame, from the pose token (head_pose, an MLP) §3.1. The world frame is defined as the frame of the first image, and all outputs are in meters §3.1. The paper argues that predicting the same quantity twice simplifies training and lets partially labelled datasets supervise one head or the other §3.1.
Querying unseen views. A virtual camera is encoded as a raymap, a six-channel image with a ray origin and direction per pixel §3.2. A separate small transformer encodes it. The raymap then goes through the same decoder with shared weights, but the state is not written to, so the query only reads §3.2. The same heads predict a pointmap and a colour head predicts the RGB of each ray §3.2.
Training. Pointmaps use a confidence-weighted L2 regression loss that follows MASt3R, with a penalty on log confidence §3.3, eq. 7. Pose is penalised by L2 on quaternion and translation §3.3, eq. 8. When the query is a raymap, an MSE colour loss is added §3.3. Metric data use the true scale; data with unknown scale do not use raymap queries §3.3.
Curriculum. Four stages §3.4 app. B:
- 4-view sequences, mostly static datasets, 224x224 images, linear heads.
- Adds dynamic datasets and partially annotated data, same resolution and heads.
- Higher resolution with varied aspect ratios, longest side 512 px, DPT heads.
- Encoder frozen; only decoder and heads are trained on longer sequences of 4 to 64 views.
The overview diagram is fig. 3; the teaser shows online reconstruction fig. 1 and the raymap query idea fig. 2.
Data. The text says 32 datasets, synthetic and real, static and dynamic, indoor and outdoor, object-centric and scene-level §3.4 app. A; the table lists 33 tab. 6. Some datasets carry only camera parameters or single views; single views are stacked with state resets to the initial state app. A.
Pseudocode (own wording, following §3 and the official implementation's structure; differences are listed in Paper vs code):
# Online loop over a stream of images [§3.1] [eq. 1-5]
# Input: frames, a list of (3, H, W) images without camera information
# Output: per frame, a world-frame pointmap, a pointmap in the image frame,
# their confidences and a camera pose
def run_stream(frames):
# Learnable state tokens shared by all scenes [§3.1]
state = learned_state_tokens # (768, 768)
for i, image in enumerate(frames):
# Encode the image into patch tokens [§3.1] [eq. 1]
tokens = image_encoder(image) # (P, 1024)
# Pose token: learned for the first frame [§3.1]
pose = learned_pose_token # (1, 768)
# State and image tokens interact in both directions [§3.1] [eq. 2]
state, image_out, pose_out = decoders(
state, tokens, pose
)
# Heads read the decoder features [§3.1] [eq. 3-5]
pts_self, conf_self = head_self(image_out)
pts_world, conf_world = head_cross(image_out, pose_out)
camera_pose = head_pose(pose_out)
yield pts_self, conf_self, pts_world, conf_world, camera_pose# Probing the state with an unseen view [§3.2] [eq. 6]
# Input: state from a run, raymap (6, H, W) of the virtual camera
# Output: pointmap and RGB for the unseen view; state is NOT changed
def query_state(state, raymap):
# Encode the raymap with its own small transformer [§3.2] [eq. 6]
ray_tokens = raymap_encoder(raymap) # (P, 1024)
# Read the state through the shared decoders, no state write [§3.2]
_, query_out, pose_out = decoders(state, ray_tokens, None)
pts, conf = head_cross(query_out, pose_out)
rgb = head_color(query_out) # RGB per ray [§3.2]
return pts, conf, rgbHyperparameters
Values from the paper unless marked "code". The paper's supplementary material defers to the code's configs in several places app. B.
| Name | Value | Where | Notes |
|---|---|---|---|
| image encoder | ViT-Large, 16x16 patches | §3.4 | initialised from DUSt3R encoder weights §3.4 |
| decoder | ViT-Base, 12 blocks, width 768, 12 heads | §3.4 stage1.yaml | |
| state tokens | 768 tokens of dimension 768 | §3.4 stage1.yaml | code: 2D grid positional embedding with width 28 (because int(sqrt(768)) = 27 is odd) model.py |
| pose token dimension | 768 | app. B | pose head is a 2-layer MLP, hidden size 768 app. B |
| raymap encoder | lightweight, 2 blocks | §3.4 | |
| positional encoding | RoPE applied to queries and keys | app. B | code: RoPE100 stage1.yaml |
| optimiser | AdamW, initial LR 1e-4 | §3.4 | code: betas (0.9, 0.95) train.py |
| LR schedule | linear warmup, then half-cosine decay to a minimum | §3.4 | misc.py |
| weight decay | not stated (paper) | §3.4 | code: 0.05 stage1.yaml |
| gradient clipping | not stated (paper) | code: clip_grad 1.0 train.py | |
| stage 1 | 4 views, 224x224, linear heads | §3.4 app. B | code: 100 epochs, warmup 10, LR 1e-4, min LR 1e-6, batch 16 stage1.yaml |
| stage 2 | 4 views, 224x224, linear heads | §3.4 app. B | code: 35 epochs, warmup 1, LR 5e-6, min LR 1e-6 stage2.yaml |
| stage 3 | longest side 512, DPT heads | §3.4 app. B | code: 40 epochs, warmup 2, LR 1e-5, min LR 1e-6 stage3.yaml |
| stage 4 | encoder frozen, 4 to 64 views | §3.4 | code: 64-view variable-length config, 10 epochs, LR 1e-6, batch 4 x accumulation 4 dpt_512_vary_4_64.yaml dpt_512_vary_4_64.yaml |
| stage 4 (second config) | 32 views, fixed length | not in paper | stage4.yaml |
| confidence loss weight alpha | not stated in paper | §3.3, eq. 7 | code: 0.2 stage1.yaml |
| pointmap loss scaling | not stated in paper | code: mean over views times 2.0 losses.py | |
| raymap replacement | 20% of non-first metric frames | app. B | code: 15% raymap only, 5% both base_multiview_dataset.py |
| colour jitter | same jitter for all frames in a sequence | app. B | code: SeqColorJitter stage1.yaml |
| frame interval | randomly 1 to k per dataset; fixed or variable, about half each | app. B | code: fixed-interval probability 0.8 in one sampler base_multiview_dataset.py; 0.5 in another base_multiview_dataset.py |
| hardware | 8 x A100 80 GB | §3.4 | |
| epochs (paper) | not stated |
Results
Author-reported. Each table gives the setup from its caption and text. Bold and underline in the paper mark best and second-best per column; the values below are as printed. Baselines are the paper's own choices §4 Baselines.
Monocular depth, zero-shot, per-frame median scaling (Abs Rel lower is better; δ<1.25 higher is better) tab. 1 §4.1:
| Method | Sintel Abs Rel | Sintel δ | Bonn Abs Rel | Bonn δ | KITTI Abs Rel | KITTI δ | NYU-v2 Abs Rel | NYU-v2 δ |
|---|---|---|---|---|---|---|---|---|
| DUSt3R | 0.424 | 58.7 | 0.141 | 82.5 | 0.112 | 86.3 | 0.080 | 90.7 |
| MASt3R | 0.340 | 60.4 | 0.142 | 82.0 | 0.079 | 94.7 | 0.129 | 84.9 |
| MonST3R | 0.358 | 54.8 | 0.076 | 93.9 | 0.100 | 89.3 | 0.102 | 88.0 |
| Spann3R | 0.470 | 53.9 | 0.118 | 85.9 | 0.128 | 84.6 | 0.122 | 84.9 |
| This paper | 0.428 | 55.4 | 0.063 | 96.2 | 0.092 | 91.3 | 0.086 | 90.9 |
Video depth, per-sequence scale alignment (Abs Rel, δ<1.25, FPS on KITTI at 512x144 on A100) tab. 2 §4.1:
| Method | Online | Sintel Abs Rel | Sintel δ | Bonn Abs Rel | Bonn δ | KITTI Abs Rel | KITTI δ | FPS |
|---|---|---|---|---|---|---|---|---|
| DUSt3R-GA | no | 0.656 | 45.2 | 0.155 | 83.3 | 0.144 | 81.3 | 0.76 |
| MASt3R-GA | no | 0.641 | 43.9 | 0.252 | 70.1 | 0.183 | 74.5 | 0.31 |
| MonST3R-GA | no | 0.378 | 55.8 | 0.067 | 96.3 | 0.168 | 74.4 | 0.35 |
| Spann3R | yes | 0.622 | 42.6 | 0.144 | 81.3 | 0.198 | 73.7 | 13.55 |
| This paper | yes | 0.421 | 47.9 | 0.078 | 93.7 | 0.118 | 88.1 | 16.58 |
Video depth, metric scale (no alignment) tab. 2 §4.1: MASt3R-GA gives Sintel Abs Rel 1.022 (δ 14.3), Bonn 0.272 (70.6), KITTI 0.467 (15.2). This paper gives 1.029 (23.8), 0.103 (88.5), 0.122 (85.5).
Camera pose, Sim(3)-aligned ATE and RPE (lower is better) tab. 3 §4.2:
| Method | Online | Sintel ATE | Sintel RPE trans | Sintel RPE rot | TUM ATE | TUM RPE trans | TUM RPE rot | ScanNet ATE | ScanNet RPE trans | ScanNet RPE rot |
|---|---|---|---|---|---|---|---|---|---|---|
| MonST3R-GA | no | 0.111 | 0.044 | 0.869 | 0.098 | 0.019 | 0.935 | 0.077 | 0.018 | 0.529 |
| DUSt3R (no GA) | yes | 0.290 | 0.132 | 7.869 | 0.140 | 0.106 | 3.286 | 0.246 | 0.108 | 8.210 |
| Spann3R | yes | 0.329 | 0.110 | 4.471 | 0.056 | 0.021 | 0.591 | 0.096 | 0.023 | 0.661 |
| This paper | yes | 0.213 | 0.066 | 0.621 | 0.046 | 0.015 | 0.473 | 0.099 | 0.022 | 0.600 |
Among online methods the paper reports the best overall result, with Spann3R better on ScanNet ATE tab. 3 §4.2.
Sparse-view 3D reconstruction, 7-Scenes and NRGBD, means only (Acc and Comp in metres, lower is better; NC higher is better) tab. 4 §4.3:
| Method | Online | 7-Scenes Acc | 7-Scenes Comp | 7-Scenes NC | NRGBD Acc | NRGBD Comp | NRGBD NC | FPS |
|---|---|---|---|---|---|---|---|---|
| DUSt3R-GA | no | 0.146 | 0.181 | 0.736 | 0.144 | 0.154 | 0.870 | 0.68 |
| MASt3R-GA | no | 0.185 | 0.180 | 0.701 | 0.085 | 0.063 | 0.794 | 0.34 |
| MonST3R-GA | no | 0.248 | 0.266 | 0.672 | 0.272 | 0.287 | 0.758 | 0.39 |
| Spann3R | yes | 0.298 | 0.205 | 0.650 | 0.416 | 0.417 | 0.684 | 12.97 |
| This paper | yes | 0.126 | 0.154 | 0.727 | 0.099 | 0.076 | 0.837 | 17.00 |
Medians are in tab. 4. The paper also reports 25 times the speed of DUSt3R-GA §4.3.
Qualitative comparisons with Spann3R and MonST3R on Internet videos are in fig. 4; probing unseen regions is shown in fig. 6 (MapFree and ARKitScenes validation sets, not seen in training §4.4).
State update (revisiting), same setup as above tab. 5 fig. 5 §4.4: running the state over the whole set once and then re-running it on the same frames improves the means from 7-Scenes Acc 0.126 to 0.113 and NRGBD Acc 0.099 to 0.094. "Revisit" is not an online result, because it needs a second pass over the frames.
Dataset coverage for training tab. 6 app. A: the table has 33 rows, with flags for metric scale, real or synthetic, static or dynamic, camera-only and single-view. The text says 32 datasets §3.4.
Paper vs code
Repository: the official code (https://github.com/CUT3R/CUT3R) is linked from the authors' project page (https://cut3r.github.io/); it is the only repository we found and it is not a third-party copy. Its LICENSE is CC BY-NC-SA 4.0 (non-commercial), so this card cites the code but quotes none of it. Checkpoints are linked from the repository README on Google Drive README.md; their license was not checked. The paper text was checked against the configs in config/, src/train.py, src/dust3r/model.py, src/dust3r/losses.py and the dataset loader at code_commit.
- Pose token. The paper describes one learnable pose token prepended to every image §3.1, eq. 2. In the code it is used for the first frame only; later frames read their pose token from a learned memory through a read block, and the memory is written after each frame model.py model.py model.py. The paper does not describe this memory.
- Which decoder layers feed the heads. The paper does not say which decoder layers feed the heads. The code passes the input features and the outputs of decoder layers 6, 9 and 12 of 12; the pose token is removed from the three layer outputs model.py.
- Confidence loss normalisation. Paper eq. 7 sums over all pointmaps §3.3. The code averages the per-view confidence losses and multiplies by 2.0 losses.py losses.py.
- Pose loss. Paper eq. 8 sums the L2 terms over frames t §3.3. The code takes the mean over frames and batch, and adds the pose loss to the total without a weight; a commented-out clip and weight of 5 is present losses.py losses.py. In the pose function I did not see a division of the translation by the scale factor s from eq. 8; I did not trace the call site to confirm.
- Raymap sampling. The paper says each non-first metric frame is replaced by its raymap with probability 20% app. B. The code uses 80% image only, 15% raymap only and 5% image plus raymap base_multiview_dataset.py. The first frame is never replaced, as the paper says.
- Frame-interval sampling. The paper says fixed and variable intervals each make up about half of video samples app. B. The code has two samplers, one with a fixed-interval probability of 0.8 base_multiview_dataset.py and one with 0.5 base_multiview_dataset.py. I did not trace which one each dataset uses.
- Learning rate. The paper gives one initial rate, 1e-4 §3.4. The code uses 1e-4 only in stage 1; stage 2 uses 5e-6, stage 3 uses 1e-5 and stage 4 uses 1e-6 stage2.yaml stage3.yaml dpt_512_vary_4_64.yaml.
- Stage 4 configuration. The paper describes stage 4 as a frozen encoder with 4 to 64 views §3.4. The config
dpt_512_vary_4_64.yamlmatches this, and the README names its checkpoint as the final model README.md. The other stage-4 config,stage4.yaml, uses a fixed 32 views stage4.yaml and was not described in the paper. - Epochs, batch and effective batch size. The paper gives no epoch counts and no batch size. The configs use 100, 35, 40 and 10 epochs for the four stages stage1.yaml stage2.yaml stage3.yaml stage4.yaml. Stages 1 to 4 set batch size 16 with accumulation 1, but the 16-view and 64-view configs use 6 with accumulation 2 and 4 with accumulation 4 linear_224_fixed_16.yaml dpt_512_vary_4_64.yaml. The paper uses eight GPUs; how the config batch maps onto GPUs was not traced.
- Undocumented training details. Gradient clipping at 1.0 and AdamW betas of (0.9, 0.95) are in the code but not in the paper train.py train.py.
- Head activations. The configs use
conf_mode('exp', 1, inf) andpose_mode('exp', -inf, inf) stage1.yaml. The paper does not give output activations for confidence or pose. - Heads by stage. The paper says the linear heads of stages 1 and 2 switch to DPT in stages 3 and 4 app. B. The configs agree: stage 1 uses
linearand stage 3 usesdptstage1.yaml stage3.yaml. - Alpha. The paper's eq. 7 has a weight alpha with no numerical value. The configs set alpha to 0.2 stage1.yaml.
Pitfalls
- Non-commercial code. The official code is CC BY-NC-SA 4.0. Do not copy code from it into a commercial system. Implement from the paper and this card, and check the weights' license before use; we did not check it.
- Numbers that are not in the paper. Alpha in eq. 7, epochs, effective batch size, weight decay, gradient clipping and output activations must be taken from the configs or chosen by you; the paper does not state them.
- Memory is an unpublished detail. The learned memory and its read and write blocks model.py are needed to match the code's pose tokens for frames after the first. Following only the paper's single pose token gives a different model.
- Raymap queries need metric data. The raymap branch is enabled only for training data with metric 3D annotation; otherwise the scale of the state and the query can disagree §3.3 app. B.
- State resets. Single-view datasets are fed as sequences of independent views, and the state is reset to the initial state after each view app. A. Forgetting the reset makes single-view batches leak context between views.
- Drift and reset are part of the design. The authors state that long sequences can drift because there is no global alignment, and that the structure generation is deterministic, so extrapolations far from the input views can be blurry §5 Limitations.
- "Revisit" is not online. The better numbers in tab. 5 come from a second pass over the same frames §4.4. Do not compare them with online rows.
- Speed claims depend on setup. The tab. 2 FPS values are for 512x144 images on one A100, except Spann3R, which only supports 224x224 inputs tab. 2 caption, so its row is not like-for-like. The paper's "nearly 50 times" and "25 times" comparisons come from the FPS columns of tab. 2 and tab. 4 §4.1 §4.3. Memory use and training time are not reported; the authors say recurrent training is time-consuming §5.
- Baselines are a mix. Several baselines are optimised per sequence (global alignment), so their numbers are not online. Table 4's MonST3R row carries the citation key of MASt3R; the label is the reliable part.
- Some cells are not wins. Sintel Abs Rel in tab. 1 and tab. 2 favours MASt3R or MonST3R; the metric-scale Sintel row favours MASt3R; ScanNet ATE in tab. 3 favours Spann3R. The paper's wording "competitive or state-of-the-art" §1 does not mean first place in every column.
- Sampling probabilities. Two frame-interval samplers appear in the code with different defaults (0.8 and 0.5) base_multiview_dataset.py. Check which one your dataset uses before comparing sampling statistics.
Lineage
- Builds on: DUSt3R, the encoder weights for stage 1 and the decoder design, which the paper cites as a base and as its baseline §3.4 §4; CroCo, the decoder family the paper cites §3.1; MASt3R, the confidence-aware regression loss §3.3.
- Compared against: DUSt3R, MASt3R, MonST3R and Spann3R, as both offline and online baselines; the offline ones with global alignment §4.
- Superseded by: not known from this wiki.
Source and license
Adapted from “Continuous 3D Perception Model with Persistent State” by Qianqian Wang, Yifei Zhang, Aleksander Holynski, Alexei A. Efros, Angjoo Kanazawa (arXiv:2501.12387), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/CUT3R/CUT3R at commit 8bc15dc92a6d (CC BY-NC-SA 4.0 (non-commercial)). Any quoted code is shown under that license.