CVPR 2026 · Code WalkthroughCVPR 2026 · 代码走读

3DThinker

Think with 3D: Geometric Imagination Grounded Spatial Reasoning from Limited Views 用 3D 思考:基于几何想象力的有限视角空间推理

Apr 15, 2026 3d reasoning rl
Zhangquan Chen · Manyuan Zhang · Xinlei Yu · Xufang Luo · Mingze Sun · Zihao Pan · Xiang An · Yan Feng · Peng Pei · Xunliang Cai · Ruqi Huang

Overview概述

3DThinker is the first framework that enables VLMs to perform "3D mentaling" during reasoning — imagining 3D spatial structures from limited-view images without any 3D prior input. Training consists of two stages: Stage 1 aligns the VLM's 3D latent with a 3D foundation model (VGGT) via supervised training; Stage 2 refines the entire reasoning trajectory through GRPO reinforcement learning using outcome-based rewards.

3DThinker 是首个让 VLM 在推理过程中进行"3D 心象"的框架——无需任何 3D 先验输入,即可从有限视角图像中想象 3D 空间结构。训练分两阶段:阶段 1 通过监督训练将 VLM 的 3D 隐表示与 3D 基础模型(VGGT)对齐;阶段 2 通过 GRPO 强化学习,仅基于结果信号优化完整推理轨迹。

3DTHINKER TRAINING PIPELINE STAGE 01 Data Preparation VGGT features + CoT 10K supervised pairs extract STAGE 02 3D Alignment supervised MSE to VGGT Qwen-VL + 3D Projector refine STAGE 03 GRPO RL outcome-based rewards format · response · latent evaluate STAGE 04 Evaluation 8 spatial benchmarks MindCube · VSI · CV DATA SFT RLHF EVAL
Figure 0. Overall training pipeline. Raw MindCube scenes are processed through VGGT feature extraction and GPT-4.1 CoT generation to produce 10K supervised pairs. Stage 1 aligns Qwen's latent tokens with VGGT via a 3D projector. Stage 2 refines the full reasoning trajectory with GRPO and outcome-based rewards. 图 0。整体训练流程。原始 MindCube 场景经过 VGGT 特征提取和 GPT-4.1 CoT 生成,产生 10K 监督样本对。阶段 1 通过 3D 投影器将 Qwen 的 latent token 与 VGGT 对齐。阶段 2 通过 GRPO 和基于结果的奖励信号优化完整推理轨迹。

Environment Setup环境配置

Three conda environments are required — one per training stage plus an SFT env for LoRA weight merging. Select a tab to view the setup commands.

需要三套 conda 环境——每个训练阶段一套,加一套用于 LoRA 权重合并的 SFT 环境。点击 tab 查看对应安装命令。

bash
git clone https://github.com/zhangquanchen/3DThinker.git
cd 3DThinker

conda create -n 3DThinker-stage1 python=3.10 -y && conda activate 3DThinker-stage1
pip install -r envs/requirements_stage1.txt
conda create -n 3DThinker-stage2 python=3.10 -y && conda activate 3DThinker-stage2
bash 3dthinker/stage2/setup.sh

# Replace transformers with the stage1 custom version
# Replace trl with the local copy if version conflicts
cp -rf 3dthinker/stage2/package/trl \
  $(python -c "import site; print(site.getsitepackages()[0])")/trl
conda create -n SFT python=3.10 -y && conda activate SFT
cd SFT/env
pip install -e ".[torch,metrics]" --no-build-isolation

Data Preprocessing数据预处理

VGGT Feature ExtractionVGGT 特征提取

Download VGGT-1B weights into models/. The script processes each scene's images through VGGT and saves aggregated 3D tokens as .npz files with shape [N, 1374, 2048].

下载 VGGT-1B 权重到 models/。脚本将每个场景的图像通过 VGGT 处理,保存聚合的 3D token 为 .npz 文件,shape 为 [N, 1374, 2048]

preprocessing/feature/extract_vggt_feature.py
python
from vggt.models.vggt import VGGT

# Load VGGT-1B model
model = VGGT.from_pretrained("models/VGGT-1B")
model.eval().cuda()

# For each training sample: load N views, extract features
images = load_and_preprocess(image_paths)      # [N, 3, H, W]
with torch.no_grad():
    # VGGT outputs aggregated 3D tokens per view
    features = model.forward_features(images)  # [N, P_3D=1374, C=2048]

# Save features for downstream training
np.savez(output_path, feature=features.cpu().numpy())
# Output: data/feature_vggt/{idx}/vggt.npz
# Also saves resized images: data/resized_images/

CoT Data GenerationCoT 数据生成

Chain-of-thought reasoning traces are generated using GPT-4.1. Each trace starts with <output_3D> — the placeholder for the model's mental 3D scene — followed by structured thinking and answer blocks.

使用 GPT-4.1 生成思维链推理轨迹。每条轨迹以 <output_3D> 开头——作为模型"3D 心象"的占位符——随后是结构化的思考和回答块。

preprocessing/produce_cot.py — system prompt
python
SYSTEM_PROMPT = """You are a helpful assistant that can answer questions
about 3D spatial relationships from images.

The mental 3D scene you imagine will be represented by the special token
<output_3D>, which should be presented at the beginning of your response.

Your response should follow this format:
<output_3D><think> [step-by-step spatial reasoning] </think>
<answer> [final answer letter] </answer>"""

# Generate CoT with GPT-4.1 for each QA pair
response = client.chat.completions.create(
    model="gpt-4.1",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user",   "content": [
            {"type": "image_url", "image_url": {"url": img_url}},
            {"type": "text",      "text": question}
        ]}
    ]
)

Data Cleaning Pipeline数据清洗流程

Four scripts run sequentially to produce the final training JSONL:

四个脚本顺序执行,生成最终训练用的 JSONL 文件:

Data cleaning pipeline
bash
# 1. Generate chain-of-thought reasoning data
python preprocessing/produce_cot.py

# 2. Remove non-compliant entries (e.g., missing </output> tags)
python preprocessing/clean.py

# 3. Filter low-quality / useless data
python preprocessing/remove.py

# 4. Match VGGT feature indices to training samples
python preprocessing/jsonl_add_idx.py

# Output: data/data_output3d_begin_10k_resized.jsonl

Each entry in the final JSONL contains:

最终 JSONL 的每条数据包含:

data/example.jsonl — training sample format
json
{
  "text_input": "Question about spatial relationship...",
  "image_input": ["img1.jpg", "img2.jpg", "img3.jpg", "img4.jpg"],
  "text_output": "<output_3D><think>Looking at the images, I can see...</think>\n<answer>B</answer>",
  "idx": 42
}

Stage 1: Supervised 3D-Latent Alignment阶段 1:监督 3D 隐表示对齐

SUPERVISED 3D LATENT ALIGNMENT 4 Input Views v₁, v₂, v₃, v₄ multi-view RGB encode Qwen2.5-VL-3B ViT + LLM 12 latent tokens project 3D Projector MLP + cross-attn trainable align MSE Alignment Lalign = ‖Z₃ₑ − Fᵥₘ‖² λ = 5×10⁻⁴ TEACHER · FROZEN VGGT-1B 3D foundation model target raw views
Figure 1. Stage 1 architecture. Qwen2.5-VL-3B produces hidden states at the 12 latent token positions plus image patch embeddings for each of 4 input views. The trainable 3D Projector fuses them into Z3d features aligned with the frozen VGGT teacher’s 3D representation. Training minimizes MSE alignment + cross-entropy loss: L = 0.1·CE + 5×10⁻⁴·MSE. 图 1。阶段 1 架构。Qwen2.5-VL-3B 在 12 个 latent token 位置产生隐状态,并为 4 个输入视角提取图像 patch 嵌入。可训练的 3D 投影器将两者融合为 Z3d 特征,与冻结的 VGGT 教师 3D 表征对齐。训练最小化 MSE 对齐损失 + 交叉熵损失:L = 0.1·CE + 5×10⁻⁴·MSE

Special Token Design特殊 Token 设计

The tokenizer is extended with three new tokens: <|latent_start|>, <|latent_pad|> (repeated latent_size=12 times), and <|latent_end|>. During preprocessing, <output_3D> is replaced with these latent tokens in the assistant's response.

分词器新增三个特殊 token:<|latent_start|><|latent_pad|>(重复 latent_size=12 次)和 <|latent_end|>。预处理时,助手回复中的 <output_3D> 被替换为这些隐表示 token。

3dthinker/stage1/src/utils.py — token replacement
python
def replace_visual_spectial_tokens(texts):
    """Replace <output_3D> placeholder with latent token sequence."""
    update_texts = []
    for text in texts:
        parts = text.split("<|im_start|>assistant")
        prev, after = parts[0], parts[1]
        # Key replacement: vision tokens → latent tokens
        after = after.replace(
            "<|vision_start|><|image_pad|><|vision_end|>",
            "<|latent_start|><|image_pad|><|latent_end|>"
        )
        update_texts.append(prev + "<|im_start|>assistant" + after)
    return update_texts

3D Projector Architecture3D 投影器架构

The projector bridges the VLM's latent token hidden states and the VGGT 3D feature space. It takes [T=12, 2048] latent hidden states + [N=4, P=391, 2048] image patch embeddings, and produces [N, 1374, 2048] features aligned with VGGT.

投影器连接 VLM 的隐 token 隐状态和 VGGT 3D 特征空间。输入为 [T=12, 2048] 的隐状态 + [N=4, P=391, 2048] 的图像 patch 嵌入,输出与 VGGT 对齐的 [N, 1374, 2048] 特征。

3dthinker/stage1/src/multimodal_projector/mmprojector.py
python
class projector(nn.Module):
    def __init__(self, mlp_depth, mm_hidden_size, hidden_size,
                 latent_size, fusion_input, fusion_output):
        super().__init__()
        # Project latent hidden states: mm_hidden_size → hidden_size
        self.proj_3d_model = build_mlp(mlp_depth, mm_hidden_size, hidden_size)
        # Compress latent tokens: latent_size(12) → 64
        self.prompte_model = build_mlp(mlp_depth, latent_size, 64)
        # Fuse latent + image patches: (fusion_input + 64) → fusion_output(1374)
        self.fusion_model  = build_mlp(mlp_depth, fusion_input + 64, fusion_output)

    def forward(self, hidden_states_feature, image_embeddings):
        """
        hidden_states_feature: [1, T=12, C=2048]   — latent token hidden states
        image_embeddings:      [1, N=4, P=391, C=2048] — input image patches
        """
        # Step 1: Compress latent tokens → [C, 64]
        latent = self.prompte_model(
            hidden_states_feature.squeeze().permute(1, 0)  # [C, T=12] → [C, 64]
        )

        feature_proj = []
        for i in range(image_embeddings.shape[1]):  # loop over N views
            # Step 2: Concat latent (64) + image patches (391) → [C, 455]
            x = torch.cat((latent, image_embeddings[:, i, :, :]), dim=1)

            # Step 3: Fuse → [C, fusion_output=1374]
            x_unify = self.fusion_model(x.squeeze().permute(1, 0))

            # Step 4: Project → [1374, mm_hidden_size=2048]
            x_output = self.proj_3d_model(x_unify)
            feature_proj.append(x_output)

        # Output: [1, N=4, P_3D=1374, C=2048] — aligned with VGGT feature space
        return torch.stack(feature_proj).unsqueeze(0)

Training Loss训练损失

Stage 1 uses a combined loss: standard cross-entropy for language modeling + MSE similarity loss that aligns the projector's output with pre-extracted VGGT features.

阶段 1 使用组合损失:标准交叉熵(语言建模)+ MSE 相似度损失(将投影器输出与预提取的 VGGT 特征对齐)。

3dthinker/stage1/src/trainer_single.py — compute_loss
python
class CustomTrainerStage1(SFTTrainer):
    def compute_loss(self, model, inputs, **kwargs):
        # Forward pass → get logits and hidden states
        outputs = model(**inputs, output_hidden_states=True)
        ce_loss = outputs.loss

        # Extract hidden states at latent token positions
        hidden_states = outputs.hidden_states[-1]  # last layer
        shift_predict_embeddings = extract_latent_positions(
            hidden_states, inputs["input_ids"]
        )

        # Project latent embeddings through the 3D projector
        image_embeddings = get_image_embeddings(model, inputs)
        feature_proj = model.projector_model(
            shift_predict_embeddings, image_embeddings
        )

        # Load pre-computed VGGT features
        idx = inputs["idx"]
        data = np.load(f'../../data/feature_vggt/{idx[0]}/vggt.npz')
        feature_3d = torch.from_numpy(data['feature']).to(device)
        # feature_3d shape: [1, N=4, P_3D=1374, C=2048]

        # MSE similarity loss between projector output and VGGT
        feature_sim = ((feature_proj - feature_3d.detach()) ** 2).sum(dim=-1)
        sim_loss = feature_sim.mean() * 0.0005

        # Combined loss: 0.1 * CE + alignment
        loss = 0.1 * ce_loss + sim_loss

        return loss

Training Launch训练启动

3dthinker/stage1/train.sh
bash
# Stage 1: Supervised Alignment Training
conda activate 3DThinker-stage1

python src/main.py \
    --model Qwen2.5-VL-3B-Instruct \
    --epochs 10 \
    --task mindcube \
    --latent_size 12 \
    --per_device_train_batch_size 1 \
    --learning_rate 1e-4 \
    --warmup_steps 10 \
    --weight_decay 0.01 \
    --logging_steps 20 \
    --save_steps 2000 \
    --stage stage1 \
    --data_path ../../data/example.jsonl \
    --save_model_path ../../models/3DThinker-S1-Qwen2.5-VL-3B_mlp6_lr1e-4_latent12 \
    --wandb_name 3DThinker-S1-Qwen2.5-VL-3B_mlp6_lr1e-4_latent12

Stage 2: Reinforced Trajectory Refinement (GRPO)阶段 2:强化推理轨迹优化 (GRPO)

Stage 2 optimizes the full reasoning trajectory using Group Relative Policy Optimization (GRPO). The model generates multiple candidate responses, scores them with three reward signals, and updates the policy. No labeled 3D data is used — only outcome signals.

阶段 2 使用 Group Relative Policy Optimization (GRPO) 优化完整推理轨迹。模型生成多个候选回复,通过三个奖励信号评分,更新策略。不使用标注的 3D 数据——仅基于结果信号。

LORA GRADIENT UPDATE Policy Model Qwen2.5-VL + LoRA (trainable) sample 4 Completions per prompt score Reward Functions Format regex match Response answer correct Latent Sim cos(Z₃ₔ, VGGT) Group-Relative Advantage β = 0.04
Figure 2. Stage 2 GRPO architecture. The policy model (Qwen2.5-VL frozen + trainable LoRA adapters) samples 4 completions per question. Each completion is scored by three reward functions. Group-relative advantages are computed against the mean, and the LoRA weights are updated via clipped GRPO with KL penalty (β=0.04). 图 2。阶段 2 GRPO 架构。策略模型(Qwen2.5-VL 冻结 + 可训练 LoRA 适配器)对每个问题采样 4 条回复。每条回复由三个奖励函数评分。以组平均奖励为基线计算相对优势,并通过带 KL 惩罚 (β=0.04) 的截断 GRPO 更新 LoRA 权重。

Reward Functions奖励函数

Three reward functions score each generated trajectory. Each reward returns a scalar in [0, 1] per sample; GRPO then computes group-relative advantages over the num_generations=8 rollouts and updates the LoRA weights with a KL penalty (β=0.04) against the reference policy.

三个奖励函数为每条生成的轨迹评分。每个 reward 输出 [0, 1] 区间的标量;GRPO 随后在 num_generations=8 条采样上计算组内相对优势(以均值为基线),并带 KL 惩罚(β=0.04)更新 LoRA 权重。

  1. Format reward — a regex check on the full trajectory. The completion must contain the latent block followed by <think>…</think> and then <answer>…</answer>. Returns 1.0 if the structure matches, 0.0 otherwise. This keeps the model from collapsing to plain text and forces it to actually emit the 12 latent tokens that the projector will read.
    Example — same MindCube question, two rollouts:
    rollout #3  →  format = 1.0
    <|latent_start|>⟨z₁⟩⟨z₂⟩…⟨z₁₂⟩<|latent_end|>
    <think>The camera rotates 90° clockwise, so the
    chair that was on the left should now be in front…</think>
    <answer>B</answer>
    rollout #5  →  format = 0.0
    The chair that was on the left moves to the front
    after a 90° rotation, so the answer is B.
    // no latent block, no <think>/<answer> tags → regex misses → 0.0 even though the answer is correct.
  2. Response (answer) reward — the content inside <answer> is parsed for the option letter (A–E) via priority-based regex, then compared with the ground-truth letter. Exact match → 1.0, otherwise 0.0. This is the only outcome signal that ties the trajectory to the actual benchmark task.
    Example — ground-truth letter is B:
    extract_answer( … )extract_option(…)==r
    "B"B1.0
    "The answer is (B)."B1.0
    "D"D0.0
    "probably the chair"None0.0
    Note: the priority regex only fires on a clean letter — verbose answers without an extractable option still score 0.0.
  3. Latent-similarity reward — the 12 latent token hidden states from the rollout are fed through the (frozen) 3D Projector together with the question's image patch embeddings, producing a predicted [N, 1374, 2048] feature map. We compare it against the pre-computed VGGT ground-truth features for the same scene via mean cosine similarity, and remap from [-1, 1] to [0, 1]. This rewards trajectories whose internal 3D "mental image" stays geometrically faithful, even when the answer letter alone wouldn't reveal that.
    Example — same question (idx mc_00471), two rollouts that both answer B correctly:
    # load pre-computed teacher features for this scene
    vggt = np.load("../../data/feature_vggt/mc_00471/vggt.npz")['feature']  # shape [1, 1374, 2048]

    rollout #3:  projector( z₁…z₁₂, img_emb ) → pred  ⟶  mean_cos( pred, vggt ) =  +0.71  ⟶  (0.71 + 1)/2 =  0.855
    rollout #7:  projector( z₁…z₁₂, img_emb ) → pred  ⟶  mean_cos( pred, vggt ) =  −0.04  ⟶  (−0.04 + 1)/2 =  0.480
    Both rollouts win the response reward (answer = B), but only rollout #3 also wins here — its latent tokens encode the rotated layout faithfully, while rollout #7 happened to land on B by chance with a noisy 3D representation.
  1. Format reward(格式奖励) — 对整条轨迹做一次正则检查:输出必须包含 latent 段,随后是 <think>…</think>,再接 <answer>…</answer>。结构命中返回 1.0,否则 0.0。作用是防止模型倒退为纯文本、并强迫它真的吐出那 12 个供投影器读取的 latent token。
    例子 —— 同一道 MindCube 题,两次采样:
    采样 #3  →  format = 1.0
    <|latent_start|>⟨z₁⟩⟨z₂⟩…⟨z₁₂⟩<|latent_end|>
    <think>相机顺时针旋转 90°,原本在左边的椅子
    现在应该出现在正前方……</think>
    <answer>B</answer>
    采样 #5  →  format = 0.0
    原本在左边的椅子在 90° 旋转之后会到正前方,
    所以答案是 B。
    // 没有 latent 段,也没有 <think>/<answer> 标签 → 正则匹配失败 → 即使答案对了也只能拿 0.0。
  2. Response reward(答案奖励) — 对 <answer> 内部的内容用优先级正则抽出选项字母(A–E),与真值字母严格比对:完全一致 → 1.0,否则 0.0。这是唯一直接把轨迹绑定到下游任务的 outcome 信号。
    例子 —— 真值是 B
    extract_answer( … )extract_option(…)==r
    "B"B1.0
    "答案是 (B)。"B1.0
    "D"D0.0
    "大概是椅子吧"None0.0
    注意:优先级正则只认得「干净的字母」——含糊其辞、抽不出选项的回答即使语义上接近也只能记 0.0
  3. Latent-similarity reward(latent 相似度奖励) — 把采样轨迹中 12 个 latent token 的隐状态与该问题的图像 patch 嵌入一起送进冻结的《3D Projector》,得到预测的 [N, 1374, 2048] 特征,再与预计算好的 VGGT ground-truth 特征取平均余弦相似度,最后将 [-1, 1] 映射到 [0, 1]。它奖励那些内部「心象 3D」仍然几何上忠实的轨迹 — 这是单看选项字母看不出来的。
    例子 —— 同一道题(idx mc_00471),两条都答对 B 的采样:
    # 加载这道题预先算好的 VGGT 教师特征
    vggt = np.load("../../data/feature_vggt/mc_00471/vggt.npz")['feature']  # 形状 [1, 1374, 2048]

    采样 #3:  projector( z₁…z₁₂, img_emb ) → pred  ⟶  mean_cos( pred, vggt ) =  +0.71  ⟶  (0.71 + 1)/2 =  0.855
    采样 #7:  projector( z₁…z₁₂, img_emb ) → pred  ⟶  mean_cos( pred, vggt ) =  −0.04  ⟶  (−0.04 + 1)/2 =  0.480
    两条轨迹都拿到了 response reward(答案都是 B),但只有 #3 在这里也拿了高分——它的 latent token 真的把旋转后的布局编码对了;#7 只是「蒙对」字母,内部 3D 表示其实是噪声。

In the launch script, all three are switched on simultaneously via --reward_funcs format response latent_sim, and they are summed with equal weight per rollout (no per-reward coefficient). The format reward acts as a hard gate — a malformed trajectory loses 1 point regardless of accuracy — while the answer and latent rewards push the policy toward both correct and geometrically grounded reasoning.

启动脚本中三项同时打开(--reward_funcs format response latent_sim),等权相加(没有单独的 reward 系数)。Format 奖励充当硬门槛——格式出错不论答案对不对都会扣 1 分;Response 和 Latent 则共同推动策略同时走向「答案正确」与「几何上不脱靠」两个方向。

vlm_modules/qwen_module.py — format_reward
python
@staticmethod
def format_reward(completions, **kwargs):
    """Reward 1: Format compliance — full trajectory must match pattern."""
    # Regex: latent tokens → <think>...</think> → <answer>...</answer>
    pattern = r".*?<\|latent_start\|>.*?<think>.*?</think>\s*<answer>.*?</answer>"

    completion_contents = [c[0]["content"] for c in completions]
    matches = [
        re.search(pattern, content, re.DOTALL) is not None
        for content in completion_contents
    ]
    rewards = [1.0 if match else 0.0 for match in matches]
    return rewards
vlm_modules/qwen_module.py — response_text_reward
python
def extract_answer(xml_text):
    """Extract content between <answer> and </answer> tags."""
    match = re.search(r'<answer>\n?(.*?)\n?</answer>', xml_text, re.DOTALL)
    return match.group(1).strip() if match else ""

@staticmethod
def response_text_reward(completions, answer, prompts, **kwargs):
    """Reward 2: Answer accuracy — option letter matching."""
    contents = [c[0]["content"] for c in completions]
    rewards = []
    for content, gt_response in zip(contents, answer):
        pred_response = extract_answer(content)
        # extract_option: priority-based regex to find A-E letter
        if extract_option(pred_response) and \
           extract_option(gt_response) == extract_option(pred_response):
            rewards.append(1.0)
        else:
            rewards.append(0.0)
    return rewards
vlm_modules/qwen_module.py — sim_reward (latent similarity)
python
@staticmethod
def sim_reward(index, prompts, image_embeddings, extracted_emb, **kwargs):
    """Reward 3: 3D latent similarity — cosine sim with VGGT features."""
    rewards = []
    for idx, prompt in zip(index, prompts):
        target_dtype = torch.float32
        # Run the projector on generated latent embeddings
        decoder_feature = projector_model(
            extracted_emb.to(target_dtype).to("cuda:0"),
            image_embeddings.to(target_dtype).to("cuda:0")
        )
        decoder_feature_norm = decoder_feature / decoder_feature.norm(
            dim=-1, p=2, keepdim=True
        )

        # Load pre-computed VGGT ground truth features
        data = np.load(f'../../data/feature_vggt/{idx}/vggt.npz')
        feature_3d = torch.tensor(data['feature']).to(
            device=decoder_feature.device, dtype=decoder_feature.dtype
        ).squeeze()
        feature_3d_norm = feature_3d / feature_3d.norm(
            dim=-1, p=2, keepdim=True
        )

        # Cosine similarity → [0, 1] reward
        cos_sim = F.cosine_similarity(
            decoder_feature_norm, feature_3d_norm
        ).mean()
        reward = (cos_sim + 1) / 2  # map [-1,1] → [0,1]
        rewards.append(reward)
    return rewards

Training Launch & Weight Merging训练启动与权重合并

3dthinker/stage2/run_scripts/train.sh — GRPO config
bash
# Stage 2: GRPO Reinforcement Learning
conda activate 3DThinker-stage2

# Path to Stage 1 checkpoint
model_path="../../models/3DThinker-S1-Qwen2.5-VL-3B_mlp6_lr1e-4_latent12"
EXP_NAME="3DThinker-S1+S2-Qwen2.5-VL-3B_lr1e-5_gen8"

torchrun --nproc_per_node=8 \
    --nnodes=1 --node_rank=0 \
    --master_addr=127.0.0.1 --master_port=12346 \
  src/open_r1/grpo_jsonl.py \
    --use_vllm True \
    --model_name_or_path $model_path \
    --output_dir checkpoints/rl/${EXP_NAME} \
    --reward_funcs format response latent_sim \
    --beta 0.04 \
    --num_generations 8 \
    --max_completion_length 2048 \
    --use_peft true \
    --lora_r 64 \
    --lora_alpha 128 \
    --lora_dropout 0.05 \
    --lora_task_type CAUSAL_LM \
    --per_device_train_batch_size 1 \
    --gradient_accumulation_steps 2 \
    --learning_rate 1e-5 \
    --num_train_epochs 1 \
    --freeze_vision_modules true \
    --deepspeed local_scripts/zero3.json \
    --bf16
Weight merging — LoRA → full model
bash
# Merge LoRA adapters into the base model
conda activate SFT
llamafactory-cli export merge.yaml

# merge.yaml specifies:
#   model_name_or_path: checkpoints/stage1    (base)
#   adapter_name_or_path: checkpoints/stage2  (LoRA)
#   export_dir: checkpoints/merged            (output)

Evaluation评估

Evaluation follows the official MindCube benchmark protocol. Additional benchmarks include Ego3D-Bench, VSI-Bench, SPBench, CV-Bench, SPAR-Bench, ViewSpatial-Bench, and MMSI-Bench.

评估遵循 MindCube 基准的官方协议。其他基准包括 Ego3D-Bench、VSI-Bench、SPBench、CV-Bench、SPAR-Bench、ViewSpatial-Bench 和 MMSI-Bench。

MindCube-TinyMVmind-cube.github.io

Mental rotation and cross‑view object/ego pose tracking from a small set of related views.

INPUT
4 RGB images of one object (a black sneaker) from front / left / back / right, each camera aligned with room walls.
QUESTION
“From the viewpoint presented in image 2, what is to the right of the black sneaker?”
GT
C. Light purple sofa
FORMAT
MCQ · A / B / C / D
Ego3D-BenchMVvbdai/Ego3D-Bench

Ego‑centric 3D reasoning: distance estimation, motion direction, travel time, and relative position of nearby objects.

INPUT
6 surround‑view RGB images from an ego car (Front‑Left, Front, Front‑Right, Back‑Right, Back, Back‑Left).
QUESTION
“How much time does it take to move from the location of the ego car directly towards the black BMW hatchback stopped at the barrier in the front view, with a speed of 2 m/s?”
GT
A. Less than 5 seconds
FORMAT
MCQ · A / B / C / D · (a few sub‑tasks return numeric meters)
VSI-BenchVIDEOnyu-visionx/VSI-Bench

Visual‑spatial intelligence on real indoor videos: counting, size and distance, route planning, and appearance order.

INPUT
1 egocentric indoor video clip (~30–40 s, sampled to ~30 frames; ARKitScenes).
QUESTION
“If I am standing by the TV and facing the sofa, is the fireplace to my front‑left, front‑right, back‑left, or back‑right? Directions refer to quadrants of a Cartesian plane (origin at me, +y forward).”
GT
A. front‑left
FORMAT
MCQ · A / B / C / D · (8 task types incl. counting and numeric)
SPBenchSI · MVhongxingli/SPBench

Single‑ and multi‑image spatial perception: depth ordering, occupancy, layout, relative direction and distance.

INPUT
1 RGB indoor image (ScanNet frame) for the SI track; multi‑image variant for MV.
QUESTION
“From the camera’s perspective, is the backpack to the keyboard’s left‑front, left‑back, right‑front, or right‑back?”
GT
A. left‑front
FORMAT
MCQ · A / B / C / D

Classic computer‑vision reasoning from a single image: object count, depth ordering, distance, and spatial relations (Cambrian‑1).

INPUT
1 RGB image with two annotated bounding boxes (red → table, blue → bookcase; from Omni3D / Hypersim).
QUESTION
“Which object is closer to the camera taking this photo, the table (red box) or the bookcase (blue box)?”
GT
(A) table
FORMAT
MCQ · A / B (4 sub‑tasks: count, depth, distance, relation)
SPAR-BenchSI · MVjasonzhango/SPAR-Bench

Spatial reasoning at multiple difficulty levels with ground‑truth 3D supervision — from basic perception to compositional inference.

INPUT
2 RGB images (multi‑view: first perspective + second perspective; ScanNet).
QUESTION
“Describe the camera adjustment that turns the first image into the second. Output as move_<dir>:<m>,…,rotate_<dir>:<deg>,….”
GT
move_right:0, move_down:0, move_forward:0, rotate_down:5, rotate_right:20
FORMAT
Structured fill‑in (comma‑separated movement / rotation string)
ViewSpatial-BenchSI · MVlidingm/ViewSpatial-Bench

Spatial reasoning under changing viewpoints; tests whether a model maintains a consistent 3D world model as the camera moves.

INPUT
9 sequential RGB frames of one ScanNet scene (Scene‑Simulation track).
QUESTION
“If you stand at the television facing the counter, where is the sink?”
GT
A. front · (choices: A. front, B. back‑left, C. right, D. back‑right)
FORMAT
MCQ · A / B / C / D

Multi‑image spatial intelligence: cross‑image object grounding, attribute comparison, and scene‑level reasoning.

INPUT
2 RGB images (consecutive real‑world photos taken on a staircase).
QUESTION
“When taking consecutive photos on the stairs, where is the green plant on the table located relative to you when you take photo 2? — A: front‑left, B: back‑right, C: back‑left, D: front‑right.”
GT
C. back‑left
FORMAT
MCQ · A / B / C / D
MindCube-TinyMVmind-cube.github.io

心理旋转与跨视角的物体/自身位姿追踪,输入为一组相关视角的图像。

输入
同一双黑色运动鞋的 4 张 RGB 图像,分别从前/左/后/右拍摄,摄像头均与房间墙面对齐。
问题
“在图 2 的视角下,黑色运动鞋的右侧是什么?”
标准答案
C. 淺紫色沙发
输出
选择题 · A / B / C / D
Ego3D-BenchMVvbdai/Ego3D-Bench

以自我为中心的三维推理:距离估计、运动方向、行进时间、附近物体的相对位置。

输入
主车上 6 路环视 RGB 图像(Front‑Left、Front、Front‑Right、Back‑Right、Back、Back‑Left)。
问题
“以 2 m/s 的速度从主车直线驶向前方棚栏边停靠的黑色 BMW 掠背车,需要多长时间?”
标准答案
A. 少于 5 秒
输出
选择题 · A / B / C / D ·(部分子任务输出具体米数)
VSI-BenchVIDEOnyu-visionx/VSI-Bench

针对真实室内视频的空间智能:计数、尺寸与距离、路径规划、出现顺序。

输入
1 段第一人称室内视频片段(约 30–40 秒,以 ~30 帧采样;ARKitScenes)。
问题
“如果我站在电视机旁并面向沙发,那么壁炉在我的前左、前右、后左还是后右?(以我为原点、+y 为前方的笛卡尔坐标象限)”
标准答案
A. 前左
输出
选择题 · A / B / C / D ·(8 类任务含计数与数值题)
SPBenchSI · MVhongxingli/SPBench

单图与多图场景下的空间感知:深度排序、空间占用、场景布局、相对方向与距离。

输入
SI 赛道为 1 张 RGB 室内图像(ScanNet 帧);MV 赛道为多张图像。
问题
“从相机视角看,背包位于键盘的左前、左后、右前还是右后?”
标准答案
A. 左前
输出
选择题 · A / B / C / D

经典计算机视觉任务的推理评估:物体计数、深度排序、距离、空间关系(Cambrian‑1)。

输入
1 张带两个标注框的 RGB 图像(红框为桌子、蓝框为书架;来自 Omni3D / Hypersim)。
问题
“这张照片中,桌子(红框)还是书架(蓝框)离相机更近?”
标准答案
(A) 桌子
输出
选择题 · A / B(含计数、深度、距离、关系 4 个子任务)
SPAR-BenchSI · MVjasonzhango/SPAR-Bench

多个难度等级的空间推理任务,有真实三维标注 — 从基础感知到组合推理。

输入
2 张 RGB 图像(多视角:第一视角 + 第二视角;ScanNet)。
问题
“描述从第一张到第二张图像需要的相机调整,输出为 move_<方向>:<米>,…,rotate_<方向>:<度>,…。”
标准答案
move_right:0, move_down:0, move_forward:0, rotate_down:5, rotate_right:20
输出
结构化填空(逗号分隔的平移 / 旋转参数串)
ViewSpatial-BenchSI · MVlidingm/ViewSpatial-Bench

考察视角变化下的空间推理:相机移动时模型是否能保持一致的三维世界模型。

输入
同一 ScanNet 场景的 9 张连续 RGB 帧(Scene‑Simulation 赛道)。
问题
“如果你站在电视机那里,面向台面,水槽在哪个方向?”
标准答案
A. 前方 ·(选项:A. 前方、B. 后左、C. 右侧、D. 后右)
输出
选择题 · A / B / C / D

多图空间智能:跨图像的物体定位、属性比较以及场景级推理。

输入
2 张真实世界 RGB 图像(在楼梯上连拍的两张照片)。
问题
“在楼梯上连拍两张照片时,拍第二张照片的那一刻,桌上的绿植相对你的位置是?A: 前左、B: 后右、C: 后左、D: 前右。”
标准答案
C. 后左
输出
选择题 · A / B / C / D
Table 1
表 1

Accuracy comparison of generalist VLMs and 3DThinker

通用 VLM 与 3DThinker 的准确率对比

Accuracy on MindCube‑Tiny and Ego3D‑Bench, with training conducted on stage 1 (S1) and on both stages (S1 + S2). Best results within each VLM family are bolded; overall / average columns are in blue; the best results among all models are in red.

MindCube‑TinyEgo3D‑Bench 上的准确率,训练分别在第一阶段(S1)以及两阶段合训(S1 + S2)进行。同一 VLM 系列内的最佳结果以粗体标记;Overall / Avg. 列以蓝色标记;所有模型中的最佳结果以红色标记。

Overall / Avg.总体 / 平均 Best overall全局最佳 Best in family系列内最佳 Ours (S1 + S2)本文(S1 + S2)
Method MindCube‑Tiny Ego3D‑Bench
RotationAmongAround Overall EgoDist. Obj.Dist. Loc. EgoMot. Obj.Mot. TravelTime EgoRel. Obj.Rel. Avg.
Closed-source Models
gpt-4o-2024-11-2037.044.856.446.133.226.528.178.156.736.060.566.048.1
gpt-4.145.544.247.245.151.736.241.882.762.644.365.770.256.9
glm-4.5v28.043.033.237.849.939.648.488.873.440.457.181.959.9
gemini-2.5-pro84.039.756.852.258.550.261.492.975.543.572.878.666.7
claude-sonnet-449.542.212.836.648.936.551.681.955.133.653.969.553.9
doubao-seed-1.687.035.838.046.155.250.860.589.067.349.871.486.066.3
o3-2025-04-1686.542.766.056.671.359.365.693.480.153.577.783.173.0
Qwen2.5-VL Family [2]
Qwen2.5-VL-3B37.433.330.333.221.529.428.850.341.930.954.156.139.1
3DThinker-S1Qwen2.5-3B44.064.872.462.736.139.432.554.846.230.864.069.746.7
3DThinker-S1+S2Qwen2.5-3B55.581.875.275.241.646.033.154.753.330.870.176.950.8
Qwen2.5-VL-7B36.532.538.434.732.731.530.545.944.034.543.266.541.1
3DThinker-S1Qwen2.5-7B43.566.376.464.447.944.536.551.951.339.159.173.950.5
3DThinker-S1+S2Qwen2.5-7B55.083.076.076.054.052.336.552.756.638.266.083.154.9
Qwen2.5-VL-32B39.534.543.637.645.440.749.675.674.140.154.079.057.3
3DThinker-S1Qwen2.5-32B45.066.877.265.152.051.954.880.179.444.362.083.163.5
3DThinker-S1+S2Qwen2.5-32B56.583.277.276.762.261.954.580.286.643.769.986.068.1
Qwen2.5-VL-72B40.042.544.442.542.438.654.886.868.938.553.380.558.0
3DThinker-S1Qwen2.5-72B42.568.073.664.549.945.957.885.675.643.958.080.862.2
3DThinker-S1+S2Qwen2.5-72B57.083.777.677.161.159.959.793.184.943.769.887.870.0
InternVL3 Family [98]
InternVL3-8B37.040.363.245.125.828.729.854.154.836.149.965.243.1
3DThinker-S1InternVL3-8B43.066.879.265.243.844.432.960.661.246.964.172.153.3
3DThinker-S1+S2InternVL3-8B55.082.579.276.554.656.136.067.269.446.771.081.960.4
InternVL3-14B36.048.055.647.546.035.635.963.265.941.655.570.151.7
3DThinker-S1InternVL3-14B42.068.377.265.456.249.137.370.071.851.168.077.760.2
3DThinker-S1+S2InternVL3-14B54.584.377.677.063.559.941.378.380.250.075.184.066.5
InternVL3-38B32.548.556.047.235.431.039.466.664.938.061.077.351.7
3DThinker-S1InternVL3-38B39.068.076.864.644.847.043.673.168.648.571.279.159.5
3DThinker-S1+S2InternVL3-38B53.585.278.077.454.758.149.286.980.449.179.685.968.0
InternVL3-78B38.550.557.449.954.648.450.377.770.044.857.076.659.9
3DThinker-S1InternVL3-78B43.569.077.266.159.853.152.280.172.553.965.178.064.3
3DThinker-S1+S2InternVL3-78B57.086.278.878.969.961.061.091.988.654.875.383.973.3
Table 2
表 2

Evaluation of various baselines on spatial benchmarks

各空间基准上不同基线方法的评估

Accuracy across six spatial reasoning benchmarks: VSI‑Bench, SPBench, CV‑Bench, SPAR‑Bench, ViewSpatial‑Bench, and MMSI‑Bench. Modality tags: SI single image, MV multi‑view / video. Best within each backbone family is bolded; Avg. is in blue; our results are shaded.

六个空间推理基准上的准确率:VSI‑BenchSPBenchCV‑BenchSPAR‑BenchViewSpatial‑BenchMMSI‑Bench。模态标记:SI 单图,MV 多视角 / 视频。同一 backbone 系列内最佳以粗体标记;Avg. 列以蓝色标记;本文结果以底色高亮。

Avg.平均 Best in family系列内最佳 Ours (S1 + S2)本文(S1 + S2)
Method VSI‑BenchMV SPBenchSIMV CV‑BenchSI SPAR‑BenchSIMV ViewSpatialSIMV MMSI‑BenchMV Avg.
Qwen2.5‑VL‑3B Based Spatial Models
Qwen2.5‑VL‑3B29.438.570.624.635.626.537.5
Spatial‑MLLM‑4B47.348.473.835.143.631.546.6
SpatialLadder‑3B45.770.673.734.444.229.249.6
3DThinker‑S1Qwen2.5‑3B53.254.874.552.359.537.755.3
3DThinker‑S1+S2Qwen2.5‑3B59.160.278.458.264.741.960.4
Qwen2.5‑VL‑7B Based Spatial Models
Qwen2.5‑VL‑7B35.842.973.030.237.926.941.1
SpaceR‑7B44.554.075.337.145.528.847.5
VILASR‑7B45.453.977.137.846.130.248.4
Video‑R133.442.869.631.536.129.440.5
3DThinker‑S1Qwen2.5‑7B57.361.577.956.361.741.559.4
3DThinker‑S1+S2Qwen2.5‑7B63.768.381.163.368.643.364.7
eval/eval_mindcube.sh — inference
bash
cd eval

# Run inference on MindCube benchmark
python eval_qwen.py \
    --model_path ../checkpoints/merged \
    --data_path ../data/MindCube-Tiny \
    --output_path results/mindcube_predictions.jsonl \
    --max_new_tokens 1024

# Compute accuracy metrics
sh get_result.sh
eval/get_result.sh — scoring
bash
#!/bin/bash
# Extract answers and compute accuracy against ground truth

python compute_accuracy.py \
    --predictions results/mindcube_predictions.jsonl \
    --ground_truth ../data/MindCube-Tiny/test_answers.json

# Output:
# MindCube-Tiny Accuracy: XX.X%
# Per-category breakdown: spatial / counting / navigation / ...

Citation引用

citation.bib
bibtex
@inproceedings{chen2026think,
  title   = {Think with 3D: Geometric Imagination Grounded
             Spatial Reasoning from Limited Views},
  author  = {Chen, Zhangquan and Zhang, Manyuan and Yu, Xinlei
             and Luo, Xufang and Sun, Mingze and Pan, Zihao
             and An, Xiang and Feng, Yan and Pei, Peng
             and Cai, Xunliang and Huang, Ruqi},
  booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
  year    = {2026}
}