SFT on Tetris visual-analogy puzzles with intermediate representations (latent_size=8).
Table 3 in the paper was trained and evaluated with grayscale intermediate rotation
strips, as described in the paper/code. Afterwards, we ran additional experiments with
colorized intermediate images and found little impact on task accuracy, but more
variation in the oracle latent representations. We standardized on the colorized setup
afterwards, and this checkpoint comes from that run — not the grayscale one Table 3
reports.
LantErn extends
Qwen2.5-VL-3B-Instruct with
Latent Visual Reasoning (LVR) tokens. Instead of always verbalising what it sees, the model can emit
compressed visual embeddings (
<|lvr_start|>…<|lvr_end|>) during its chain-of-thought, enabling
non-verbalized visual reasoning interleaved with text.
1git clone https://github.com/GuilhermeViveiros/LantErn.git
2cd LantErn
3pip install -r requirements.txt
4pip install -e .
1import torch
2from PIL import Image
3from qwen_vl_utils import process_vision_info
4
5from src.lantern_generate.generate import generate as lantern_generate
6from src.models import load_model
7
8# ── 1. Load model + processor ─────────────────────────────────────────────────
9device = "cuda" if torch.cuda.is_available() else "cpu"
10model, processor = load_model("AGViveiros/LanteRn-3B-Tetris", compute_dtype=torch.bfloat16, use_cache=True)
11model.eval().to(device)
12processor.tokenizer.padding_side = "left"
13
14# ── 2. Build inputs ───────────────────────────────────────────────────────────
15image = Image.open("path/to/image.jpg").convert("RGB")
16question = "Your question here"
17messages = [{
18 "role": "user",
19 "content": [
20 {"type": "image", "image": image},
21 {"type": "text", "text": question},
22 ],
23}]
24text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
25image_inputs, _ = process_vision_info(messages)
26inputs = processor(text=[text], images=image_inputs, return_tensors="pt").to(device)
27prompt_len = inputs["input_ids"].shape[1]
28
29# ── 3. Generate with latent visual reasoning ──────────────────────────────────
30output = model.generate(
31 **inputs,
32 max_new_tokens=512,
33 do_sample=False,
34 custom_generate=lantern_generate,
35 use_cache=True,
36 return_dict_in_generate=True,
37)
38
39generated = output.sequences[0][prompt_len:]
40print(processor.decode(generated, skip_special_tokens=False))
1@article{viveiros2026holding,
2 title={What's Holding Back Latent Visual Reasoning?},
3 author={Viveiros, Andr{\'e} G and Gon{\c{c}}alves, Nuno and Martins, Andr{\'e} FT and Lindemann, Matthias},
4 journal={arXiv preprint arXiv:2605.18445},
5 year={2026}
6}