Molmo & PixMo: 把「指」做成一等公民

PixMo 数据集与 Molmo 能力总览
Fig. 1 — 左边 PixMo 的 7 个数据集 (3 个人工标注:绿色 Captions / AskModelAnything / Pointing;4 个合成:蓝色 Clocks / Docs / Count 等),右边对应点亮 Molmo 的能力 (细粒度理解、用户交互、Pointing & Counting、视觉技能)。关键:整套数据没用任何 VLM 生成——captions 来自标注员 60–90 秒口述,pointing 来自人工点击。

1. 出发点 (Motivation)

2024 年的开源 VLM 有一个公开的秘密:它们大多是闭源模型的蒸馏品

像 LLaVA 这类早期工作权重 + 数据全开,但落后 SOTA 太多;而更强的开源权重模型 (如 ShareGPT4V 系) 的训练数据要么干脆不公开,要么大量依赖 GPT-4V 合成的 caption。结果是:社区缺失了「如何从零造一个强 VLM」的基础知识——你拿到的开源模型其实是把 GPT-4V 装进了另一个壳子里。

Molmo 的目标就是堵这个洞:全程不蒸馏任何 VLM,从预训练 caption 到 fine-tuning 指令数据全部自采。论文的论点是——卡脖子的不是模型架构 (架构他们刻意用最标准的),而是高质量、非蒸馏的多模态数据。这套数据叫 PixMo (Pixels for Molmo)

其中最有原创性、也是本文要重点讲的,是一个全新的数据 modality:pointing (用点指代)

为什么是「点」而不是框 (bounding box) 或掩码 (mask)?论文给的理由非常实际:

Using points enables us to collect grounding data much faster than would be possible using bounding boxes or segmentation masks since it is much easier to annotate. — §1

一个未受训的众包标注员,让他画一个紧贴物体的框很难 (要对齐四条边),画 mask 更是噩梦;但让他**「点一下这个物体」**几乎零门槛。Molmo 利用这个差异,收集了 230 万 条 grounding 标注。更重要的是,点这个 modality 解锁了三件别的格式做不到的事——指代、计数、视觉解释,这正是 §2 的核心。

而当我们站在 2026 年回看,会发现 Molmo 的 pointing 是一条技术路线的起点:后来 locate-anything-2026 把 grounding 升级成「bbox 作为不可分割的输出原子」,thinking-visual-primitives-2026 进一步把它升级成「primitives 作为思维原子」。这两篇都直接复用了 PixMo/Molmo 的数据。我们在 §5 会把这条谱系讲透。

2. 方法 (Method)

Molmo 的方法可以拆成两半:一个刻意保守的架构 + 一套激进创新的数据 (尤其是 pointing)。论文反复强调「成败 90% 在数据」,所以我们也把重心放在 pointing。

2.1 架构:刻意标准,但有两个干净的工程点

Molmo 架构图 + Point to Mt. Rainier 示例
Fig. 2 — Molmo 走最标准的 VLM 结构:图像 → 多尺度多裁剪 → ViT 逐 crop 编码 → connector 池化投影 → decoder-only LLM。右边的 "Point to Mt. Rainier" 例子展示 pointing 输出:模型直接吐出纯文本坐标 <point x="63.5" y="44.5" alt="Mt. Rainier">Mt. Rainier</point>——坐标和语言在**同一个 token 流**里,不需要额外的检测头。

整体是四件套:(1) 预处理器把图切成多尺度多裁剪,(2) ViT 逐 crop 编码,(3) connector 池化 + 投影到 LLM 嵌入空间,(4) decoder-only LLM。主力用 OpenAI CLIP ViT-L/14 336px + Qwen2 72B,但也放出基于全开的 MetaCLIP + OLMo 的 100% 可复现版本。

两个值得记住的工程点:

(a) Overlapping multi-crop (重叠裁剪)。 高分辨率图被切成多个方形 crop 平铺,每个 crop 独立过 ViT。问题:裁剪边界的 patch 丢失了相邻上下文。Molmo 让 crop 之间重叠,使边界 patch 至少能看到一些邻居;但重叠区的 patch 特征不传给 LLM (只传刚好平铺原图的那些),避免重复。

No Overlap vs Overlap 裁剪对比
Fig. 3 — 无重叠 (左) vs 重叠 (右)。粉色框是真正喂给 LLM 的区域。重叠保证中心 patch 的编码带上了邻居上下文 (比如自行车品牌字样不会被切断)。消融里 overlap 显著优于 no-overlap (Table 2d:62.8 → 75.7 → 76.9)。

(b) 2×2 mean-query attention pooling (connector)。 ViT 出来的 patch 特征太多,要压。Molmo 不简单 concat,而是把每个 2×2 patch 窗口用一个 multi-head attention 池化成一个向量,query 取这 4 个 patch 的均值:

\[q = \frac{1}{4}\sum_{i=1}^{4} h_i, \qquad z = \mathrm{MHA}\big(q,\; \{h_1,h_2,h_3,h_4\},\; \{h_1,h_2,h_3,h_4\}\big)\]

—— 翻译: 把 4 个相邻 patch 特征 $h_i$ 的平均当作"提问者" $q$,让它对这 4 个 patch 做一次注意力,得到一个融合向量 $z$ 代表这 2×2 区域。比直接把 4 个特征拼起来 (stacking) 好——消融里 76.1 → 76.9 (Table 2f)。

这两点配合 connector 还有一个反常识结论:不需要单独的「只训 connector」热身阶段。只要预训练在高质量 dense caption 上做,直接给 connector 一个更高学习率 + 更短 warmup 就够了,省掉了一整个训练 stage 和它依赖的噪声网络数据。

2.2 PixMo 数据:全部不靠 VLM

PixMo 有 7 个数据集 (Fig 1)。三个人工标注:

四个合成 (但仍不用 VLM):PixMo-CapQA、PixMo-Docs (用 LLM 写代码渲染图表再据代码生成 QA)、PixMo-Clocks (渲染表盘)、PixMo-Count (用非 VLM 检测器在网图上造计数 QA)。

Fine-tuning 数据混合比例饼图
Fig. 4 — Fine-tuning 数据混合 (按采样率)。绿=自采人工标注,蓝=自采合成,紫=已有学术集。注意:"Points" 是整个 annotated 区里最大的一块单一来源——因为论文发现「pointing 任务比 QA 任务学得慢」,所以显著上采样 pointing 数据。这张图直观说明 pointing 在 Molmo 训练里的分量。

2.3 PixMo-Points:pointing 数据怎么收、为什么收三种

这是 Molmo 最核心的贡献。论文明确说 pointing 服务三个目标:

(1) enable the model to point to items described by text, (2) enable the model to count by pointing, and (3) use pointing as a form of visual explanation when answering questions. — §3

收集方式:标注员被要求「点一个物体,描述它,然后逐个点出图中这个物体的每一个实例,确保穷尽覆盖」。还专门收了 "not present" (物体不在图里) 的数据,让模型学会说「图里没有」。最终:

最妙的设计——count by pointing (数数即指点):

Pointing data also naturally supports answering counting questions with a chain-of-thought formed by the sequence of points. — §3

这句话信息量极大。传统 VLM 数数靠「一眼扫过去报个数」,密集场景必然出错。Molmo 让模型逐个点出每一个实例,点的序列本身就是一条 chain-of-thought,最后点的数量就是答案:

\[\hat{n} \;=\; \big|\{p_1, p_2, \ldots, p_{\hat n}\}\big|,\qquad p_i = (x_i, y_i)\ \text{由 LLM 自回归逐个生成}\]

—— 翻译: 模型不是直接「猜」出数字 $\hat n$,而是先一个一个地把点 $p_i$ 吐出来 (像人用手指着一个个数),数完了取点的个数作为计数。把"数数"这个易错的整体判断,拆成"逐个定位"这个可分解、可自我核对的过程。

这个 count-by-pointing 就是后来 thinking-visual-primitives-2026 把 visual primitives 当「思维原子」的最早雏形——只不过 Molmo 只把它用在计数这一个窄任务上,2026 那篇把它推广到迷宫、路径追踪、空间推理。

2.4 Pointing 的输出格式 (代码实锤)

点怎么变成 token?Molmo 不用任何特殊检测头,直接把坐标当纯文本输出,归一化到 0–100。核心就是这个 points_to_text:

repo/olmo/data/data_formatter.py:L308-L323 — 把像素点转成模型要生成/读取的文本坐标

def points_to_text(self, points, scale, label_text, alt_text):
    if isinstance(scale, (tuple, list)):
        points /= np.array(scale)[None, :]
    else:
        points *= (100/scale)
    points = [[round(x, 1), round(y, 1)] for x, y in points]
    points.sort(key=lambda x: x[0]*10000 + x[1])
    if len(points) == 1:
        x_str, y_str = points[0]
        return f"<point x=\"{x_str:0.1f}\" y=\"{y_str:0.1f}\" alt=\"{alt_text}\">{label_text}</point>"
    point_text = []
    for ix, (x, y) in enumerate(points, start=1):
        point_text.append(f"x{ix}=\"{x:0.1f}\"")
        point_text.append(f"y{ix}=\"{y:0.1f}\"")
    point_text = " ".join(point_text)
    return f"<points {point_text} alt=\"{alt_text}\">{label_text}</points>"

三个细节直接对应论文正文:

  1. 归一化到 0–100、保留 1 位小数 (points *= (100/scale) + round(x, 1)):坐标与图像绝对分辨率解耦,1 位小数让有效分辨率约等于 0–1000。这对应论文「outputs points as plain-text coordinates normalized between 0 and 100」。
  2. 排序 x*10000 + y (即先按 x 再按 y,近似从左到右、从上到下):多个点有确定顺序,训练目标不会因为点的排列歧义而抖动。
  3. 单点用 <point>,多点用 <points x1=.. y1=.. x2=.. ..>:都带 alt (无障碍描述) 和包在标签里的 label。坐标活在普通 token 流里,所以同一个 LLM 既能说话又能指点。

而 count-by-pointing 的「点完求和」也在数据格式里写死——format_pointspoint_count 风格下直接把总数拼进监督文本:

repo/olmo/data/data_formatter.py:L334-L359 — 计数样本的监督文本 = 点列表 + 总数

def format_points(self, example):
    ...
    if "point_scale" in example:                       # 点已归一化
        point_txt = self.points_to_text(points, example["point_scale"], label, label)
    else:                                               # 点是像素坐标
        h, w = example["image"].shape[:2]
        point_txt = self.points_to_text(points, [w/100, h/100], label, label)

    if style == "point_count":
        return f"Counting the {point_txt} shows a total of {len(points)}."
    else:
        return point_txt

注意监督文本 Counting the <points ...> shows a total of {len(points)}——点序列在前,总数 len(points) 在后,逼模型先定位再计数,这就是 count-by-pointing 的 CoT 在数据层面的实现。len(points)==0 时返回 "There are none.",对应「not present」负样本。

至于「~100 个 question 模板」的说法,也直接对得上代码里的模板表:

repo/olmo/data/data_formatter.py:L113-L166 — pointing / point_count 的提问模板 (节选)

"pointing": [
    "Point to {label}\nPlease say 'This isn't in the image.' if it is not in the image.",
    "Point to all occurrences of \"{label}\"",
    "Show me where {label} is",
    "Generate a list of points showing where the {label} are.",
    "Can you point out all {label} in this image?",
    ...
],
"point_count": [
    "Tell me how many {label} there are and point to them.",
    "Generate list of points showing where the {label} are and output the total count.",
    "Can you point out all {label} in this image? How many are there?",
    ...
],

随机采样这些模板,让模型学会无论用户怎么问「在哪/有几个」都能输出点 + 计数。

2.5 训练:两阶段,pointing 要上采样

训练只有两阶段 (比常见的三阶段少一个 connector 热身):

视觉 connector 那边对应的池化实现:

repo/olmo/model.py:L1551-L1566 — 2×2 窗口 mean-query attention pooling

image_features = einops.rearrange(
    image_features,
    'b n (h dh) (w dw) c -> (b n h w) (dh dw) c',
    dh=cfg.image_pooling_h,            # 2
    dw=cfg.image_pooling_w,            # 2
)

if cfg.image_pooling_2d == ImagePooling2DType.attention_meanq:
    query = image_features.mean(-2, keepdim=True)   # 4 个 patch 的均值作 query
    image_features = self.image_pooling_2d(query, image_features)

rearrange 把每个 2×2 patch 窗口拢到一起,query = mean 正是 §2.1 公式里的 \(q=\frac14\sum h_i\),然后一层 attention 池化。代码与论文一字不差。

3. 结论 (Key Findings)

Molmo 在 11 个学术 benchmark + 一个 870 人参与的 325k 次成对人评 上评测。核心数字:

模型 类别 11 项均分 Elo 排名
GPT-4o-0513 闭源 API 78.5 1
Molmo-72B (Qwen2-72B) 全开 81.2 2
Gemini 1.5 Pro 闭源 API 78.3 3
Claude 3.5 Sonnet 闭源 API 76.7 4
Molmo-7B-D 全开 77.3 6
Qwen2-VL-72B 开权重/闭数据 79.4 12
GPT-4V 闭源 API 71.1 10

关键发现:

  1. Molmo-72B 学术均分全场第一 (81.2),人评 Elo 第二、仅次于 GPT-4o——而且完全不蒸馏任何 VLM。最小的 MolmoE-1B 就逼近 GPT-4V。
  2. Counting 屠榜:在 CountBenchQA 和更难的新基准 PixMo-Count 上,Molmo 领先所有模型 (包括所有闭源),论文直接归功于「new pointing data and chain-of-thought point-and-count abilities」。这是 pointing 价值最硬的证据。
  3. 强在哪、弱在哪很诚实:自然图问答 (RealWorldQA / VQA v2)、OCR 类、时钟读取强;但 MMMU / MathVista 这类高阶推理偏弱,因为训练 mix 缺推理数据。
  4. Pointing 还能驱动 action:Molmo-72B 在 AndroidControl 上 low-level 88.7% / high-level 69.0%,接近专门工作——暗示「点 UI 按钮 = 操作界面」这条 agent 路线。

消融 (Table 2/3) 也支撑了架构选择:overlap 裁剪 (62.8→76.9)、attention 池化 (>stacking)、文本-only dropout、length hint 都有正贡献,且 DINOv2 (纯图像、无文本监督) 居然和 CLIP 打平,说明 caption 质量比视觉编码器的语言对齐更关键。

4. 实现细节 (Implementation Notes)

代码 + 权重 + 数据全开 (github.com/allenai/molmo,HF 有 Molmo-7B-D/O、MolmoE-1B、Molmo-72B)。交叉阅读 repo 后的实现要点:

5. 批判性总结 (Critical Assessment)

5.1 优点

5.2 不足 / 疑点

5.3 谱系对比:Molmo → LocateAnything → Thinking-with-Visual-Primitives

这是本文最值得展开的部分,也是用户关心的核心。三篇工作其实是同一条「让 VLM 用坐标说话」主线上的三代,而 Molmo 是公认的祖先 (后两篇都直接拿 PixMo/Molmo 当数据)。

维度 Molmo (2024) locate-anything-2026 (LocateAnything) thinking-visual-primitives-2026 (Thinking-VP)
一句话定位 点是答案 bbox 是一等输出 point/bbox 是一等思维
原语 point (位置) bounding box (位置+几何) point + bbox
坐标编码 纯文本 0–100,<point x=..> 量化整体 token 0–1000,<box> 原子块 专用词表 <|box|>/<|point|> 0–999
解码方式 标准自回归逐 token 并行 (Parallel Box Decoding,一次出整框) 自回归,但穿插进 CoT
坐标出现位置 在最终回答里 在最终输出里 思考过程里 (interleave CoT)
核心诉求 grounding 数据可规模化 精度 + 吞吐 (12.7× 加速) 推理 (计数/拓扑/空间)
与 Molmo 的关系 起点 数据引擎直接用 Molmo 出点;训练含 PixmoPoints (2.2%) 把 PixMo/Molmo 列为 6 个 grounding 集之一

读这张表的三条主线:

  1. 输出单元在升级:点 (Molmo) → 框 (LocateAnything) → 点和框都当思维原子 (Thinking-VP)。Molmo 选点是为了标注便宜;LocateAnything 回到框是为了几何信息更全且能并行解码;Thinking-VP 两者都要,因为思考时既要精确指点 (point) 又要框住区域 (box)。

  2. 坐标在序列里的位置在「前移」:Molmo 和 LocateAnything 的坐标都出现在最终输出,区别只是「答案里」vs「结构化输出里」、串行 vs 并行;而 Thinking-VP 把坐标前移到思考过程中——模型一边推理一边「点」,坐标成了中间产物而非终点。这是最大的范式跳跃。

  3. Molmo 的 count-by-pointing 是 Thinking-VP 的种子。回看 §2.3:Molmo 早就让「点的序列构成一条 chain-of-thought」来数数。Thinking-VP 的 Reference Gap framing (语言无法精确指代密集空间,所以让模型「用手指点」着思考) 本质上是把 Molmo 这个只用于计数的窄 CoT,推广成了通用推理范式 (迷宫、路径追踪、多跳空间推理)。换句话说:Molmo 发明了「指着数」,Thinking-VP 把它变成「指着想」。

  4. 数据的血缘是实打实的,不是比喻。LocateAnything 的多目标 grounding 数据引擎里,有一步是「Qwen3-VL 出 query → Molmo 出候选点 → SAM 3 把点转框」,并且 LocateAnything-Data 直接含 PixmoPoints 子集;Thinking-VP 在专项 primitive 能力上补的 6 个公开集第一个就是 PixMo/Molmo。所以这不是「思想上的致敬」,是 Molmo 的标注数据物理地流进了 2026 这两个模型的训练集。

一句话总结这条谱系:Molmo 证明了「让 VLM 用坐标说话」可以靠便宜的人工点击规模化;LocateAnything 把「说」的单元和效率做到极致 (框 + 并行);Thinking-VP 把坐标从「说的内容」变成「想的工具」。三者不冲突,理论上可以是同一个模型的三种能力。

5.4 适用 vs 不适用

5.5 进一步阅读

讨论 / Comments

评论托管在本仓库的 GitHub Discussions, 需 GitHub 账号。