SAM 速览:为什么图像只编码一次

在动物数据集项目里,分割掩码是用 SAM 系列模型(Segment Anything)生成的,我的工作之一是检查这些掩码。要判断掩码为什么会错,就得先知道模型是怎么工作的。这篇按我的追问顺序整理。

结构上的细节主要依据 SAM 第一代论文。SAM 2 和 SAM 3 沿用了「图像编码器 + 提示编码器 + 掩码解码器」这套骨架,各自做了扩展,文中会单独说明。

bbox 和 keypoint 是什么?

二者是视觉任务里最基础的两种标注。

  • bbox(bounding box,边界框):用一个矩形框住物体。COCO 格式写成 [x, y, w, h],即左上角坐标加宽和高。
  • keypoint(关键点):物体上有明确语义的点,比如眼睛、鼻尖、关节、尾根。COCO 格式里每个点是 (x, y, v),v 表示可见性。

简单说,bbox 回答「物体在哪个范围」,keypoint 回答「身体上哪些位置有意义」。对 SAM 来说,二者都可以作为提示(prompt),告诉模型要分割哪个物体。格式细节在 IoU → OKS → AP 那篇里讲过。

提示框画大了一圈,掩码会怎样?

假设给 SAM 的框比目标动物大了一圈,旁边另一头水牛的角也被框了进来。

我当时的猜测是:框里的杂物会被一起包进掩码,特别是和目标颜色、纹理相近的东西。这个猜测对了一半。

需要修正的地方是:框是一个提示,不是硬裁剪。它告诉解码器「目标大致在这个范围里」,并不意味着框里的所有东西都会被包进来。框内背景如果和动物差别明显,模型通常能把它分开。真正危险的是紧贴目标、外观又很像的邻近物体,比如另一头水牛的角。模型找不到把两者切开的边界证据,就容易把它们一起包进来,于是出现「异物混入」型的错误。

所以检查掩码时,如果看到「框没画错,掩码却多了一截」,很多时候问题出在提示本身不够精确,或者目标和邻近物体太像。

SAM 的整体结构是怎样的?

SAM 是一种可提示(promptable)的分割模型:输入一张图和一个提示(点、框、粗掩码),输出提示所指物体的像素级掩码。它由三部分组成:

图像 ──→ Image Encoder ──→ 图像嵌入 ──┐
                                       ├──→ Mask Decoder ──→ 若干候选掩码 + 质量分数
提示 ──→ Prompt Encoder ──→ 提示嵌入 ──┘

图像编码器:把整张图变成一张特征图。SAM 第一代使用在 MAE 上预训练的 ViT:

  • 输入缩放到 1024×1024,切成 16×16 的 patch,得到 64×64 = 4096 个 token;
  • 经过几十层 Transformer 后,输出 64×64 的图像嵌入。

每个位置的向量都融合了「这块区域长什么样」和「它和全图其他部分的关系」。SAM 2 换成了分层的 Hiera 编码器。

提示编码器:把提示翻译成向量。

  • 点和框是稀疏提示,用位置编码加上可学习的类型嵌入表示(比如「前景点」「框的左上角」);
  • 粗掩码是稠密提示,用卷积下采样后,直接加到图像嵌入上。

掩码解码器:一个很轻的模块。

  • 几个可学习的输出 token 和提示 token 一起,与图像嵌入做双向的交叉注意力,一共只有两层;
  • 然后把图像嵌入上采样,与每个输出 token 生成的向量做点积,得到每个像素属于目标的打分。

提示有歧义时(比如点在衣服上,指的可能是衣服也可能是整个人),解码器会一次输出多个候选掩码,并分别给出预测的质量分数。

SAM 3 在此基础上扩展了「概念提示」:可以用名词短语或示例图像作为提示,分割出图中所有符合该概念的实例。它还增加了一个判断「这个概念是否出现在图中」的 presence 分支。

这套结构里有 encoder 也有 decoder,于是我想到了熟悉的 GPT。

这里的 Transformer 和 GPT 的 decoder 有什么区别?

Transformer 是一个家族。2017 年的原始结构是完整的 encoder-decoder,后来分化出几条路线:

路线 代表 擅长
Encoder-only BERT、ViT 理解:分类、检索、作为特征提取器
Decoder-only GPT 系列 自回归生成
Encoder-decoder 原始 Transformer、T5 输入 → 结构化输出

SAM 整体上更接近「重编码器 + 轻解码器」。它和 GPT 的区别主要有两点。

一是注意力的视野。

  • GPT 使用因果注意力:每个 token 只能看到它前面的 token。这是自回归训练成立的前提。如果训练时能看到后文,「预测下一个词」就变成了照抄答案。
  • SAM 的图像编码器使用双向注意力:每个 patch 都能看到其他 patch。图像没有天然的先后顺序,一块区域属于什么,要靠全局参照来判断。比如角要和身体、尾巴互相确认,才能断定它们属于同一只动物。

二是两个 decoder 根本不是一回事。

  • GPT 的 decoder 是一大摞 Transformer 层,一次生成一个 token,循环直到结束。
  • SAM 的掩码解码器只有两层,用少量 query 去图像特征里检索,一次前向就输出整张掩码。

二者只是在架构图里占了相同的位置,名字一样,而内部机制、规模和任务性质都不同。

能不能像 KV cache 一样,图像只编码一次?

可以,而且 SAM 就是这么设计的。

用户交互时图像不变,只有提示在变。所以图像嵌入只需计算一次并缓存下来,之后每加一个点、改一下框,只需重跑提示编码器和掩码解码器。SAM 论文里给出的数字是:在网页浏览器中,提示编码器加掩码解码器大约 50 ms 就能跑完,这样交互标注才能做到实时响应。

这和 LLM 的 KV cache 是同一类思想,都是记忆化:把不随新输入改变的中间结果存起来,反复使用。

  GPT 的 KV cache SAM 的图像嵌入缓存
缓存什么 历史 token 每一层的 K/V 图像编码器的输出
缓存在哪 注意力层内部 两个模块之间
谁来反复查询 新生成的 token 新的提示
缓存会变吗 随生成不断变长 对同一张图固定不变

同类的优化还有 LLM 服务中的 prefix caching:把相同系统提示词的计算结果缓存起来,新请求直接复用。

另一类常见优化是量化、蒸馏和剪枝,它们的目标是让单次计算更便宜,而不是避免重复计算,思路上不属于同一类。

为什么编码贵、解码便宜?

我当时的理解是:编码让每个孤立的像素块获得整张图的上下文,而解码是「结合图片和提示做 next token prediction」。前半句是对的,后半句需要修正。

❌ 常见误解:掩码解码就是 next token prediction

SAM 的掩码解码器没有「下一个」的概念。它不是循环生成,而是一次前向就对所有像素同时打分,本质上是带着提示的逐像素分类。

这也是分割比文本生成快的原因之一:它不需要排队生成。

「编码器层数更多」是原因之一,但更根本的原因有两个。

原因一:参与计算的 token 数差了几个数量级。

自注意力的计算量随 token 数 \(N\) 的平方增长。图像编码器处理全部 4096 个 patch:如果每层都做全局注意力,每层要计算约 \(4096^2 \approx 1.7 \times 10^7\) 对关系,而且有几十层。SAM 第一代为了省算力,大多数层用的是窗口注意力,只有少数几层是全局的,但计算量依然很大,何况每层还有大维度的 MLP。

解码器只有几个到十几个 token,交叉注意力的规模大约是 \(\text{query 数} \times 4096\),只有两层,通道数也小得多。两边差的不是一两倍,而是好几个数量级。

原因二:编码必须无差别地理解全图,解码只做定向检索。

编码器事先不知道之后会收到什么提示,所以必须把全图的信息都准备好。也正因为如此,提示才可以指向图上任意位置。解码器知道提示在哪,只需要沿着提示去特征里取需要的信息。

可以类比图书馆:编目录是一次性的大工程,之后每位读者查书只需要翻目录。能不能反过来,把编码做轻、把解码做重?不行,因为解码器能抠出干净的轮廓,前提是编码器已经提供了足够的全局信息。重活一次干完,轻活按需反复做,这是交互式分割的基本分工。

向 AI 学习时,为什么要追问出处?

这一节和 SAM 本身无关,但它是我这次学习中印象最深的一课。

我问 AI:数据集里的 bbox 是人工标的,还是自动生成的?AI 先跑了一段数据分析,发现 bbox 普遍比关键点的外接矩形向外扩了几十像素,据此推断「bbox 是标注员对照整只动物独立画的」,说得很有条理。

我追问了一句:「你引用论文原文了吗?」它承认没有。随后它去论文原文里查找,结论被推翻了。

问题出在:这个现象其实有两种解释,而数据本身区分不了它们。

  1. 人工画框时,把关键点没覆盖到的角、耳朵、尾巴也框了进去;
  2. 框是从分割掩码的外接矩形算出来的。掩码本来就比关键点覆盖的范围大,外接矩形自然也更大。

两种解释产生的观测完全一样,AI 却只挑了其中一种当作结论。

这件事让我形成了一个习惯:把证据按强度排个序。

数据推断 < 二手转述(他人笔记、博客) < 一手来源(论文原文、官方文档、源码)
  • 「观测到某种模式」不等于「某种机制成立」。涉及「它是怎么产生的」这类机制问题,要去一手来源里找依据。
  • AI 讲解时最常见的问题,是把推断说得和事实一样笃定。问一句「出处在哪」,代价很小,收益很大。

实战观察:模型为什么对被改动过的像素那么敏感?

这一节接着 SIFT + RANSAC 对齐 那篇。经过生成式模型处理的图在几何上已经对齐得很好,提示坐标也没有偏,但 SAM 在这些图上的分割效果明显变差。

结合上面的结构就能理解:

  • 提示只是一个坐标,告诉解码器「往这里看」;
  • 真正决定掩码的是图像嵌入,也就是编码器从像素里提取出的特征。

生成式处理会让全图像素都有轻微改动,被模型生成的区域,纹理统计特征也和自然图像不同。人眼觉得很自然,编码器提取出的特征却已经偏移了,解码器拿到的「目录」也就跟着变了。

所以坐标没偏,不代表模型看到的内容没变。

自测题

  1. 既然双向注意力对理解更好,GPT 为什么不改用双向注意力?
参考答案

GPT 的训练目标是根据前文预测下一个词。如果用双向注意力,训练时每个位置都能直接看到答案本身,损失可以轻易降到很低,模型却什么也学不到。因果掩码不是性能上的妥协,而是自回归训练能够成立的前提。

  1. 如果编码器每层都用全局注意力:1024×1024 的图、16×16 的 patch,每层要算多少对关系?4 个 query 对这些 patch 做一次交叉注意力呢?
参考答案

patch 数为 \(64 \times 64 = 4096\),全局自注意力每层约 \(4096^2 \approx 1.7 \times 10^7\) 对;4 个 query 的交叉注意力约 \(4 \times 4096 \approx 1.6 \times 10^4\) 对。单层就差了约 1000 倍,还没算编码器更多的层数和更宽的通道。

  1. 检查掩码时,看到同一只动物有好几种差别不大的轮廓版本,可能是什么原因?
参考答案

SAM 对有歧义的提示会一次输出多个候选掩码,并附带质量分数。生成管线可能按分数挑了其中一个,换一种提示方式或换一个版本,挑出来的就可能不同。可以去看生成脚本里是如何从候选中选择的。

参考

  • A. Kirillov et al. Segment Anything. ICCV, 2023.
  • N. Ravi et al. SAM 2: Segment Anything in Images and Videos. 2024.
  • Meta AI. SAM 3: Segment Anything with Concepts. 2025.
  • A. Vaswani et al. Attention Is All You Need. NeurIPS, 2017.



    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • World Model 与 VLA:具身智能的两条路线,以及从 3D 视觉出发的视角
  • COCO → IoU → OKS → AP:读懂姿态估计论文里的那些数字
  • 用 SIFT + RANSAC 做尺寸校准,以及怎么判断「对得准不准」
  • SIFT 自学笔记:尺度不变到底保证了什么