Ultralytics SAM3 几何编码器源码剖析:从几何 Prompt 到 Transformer 可读序列的完整实现解读
2026/9/8 18:37:26 网站建设 项目流程

Ultralytics SAM3 几何编码器源码剖析:从几何 Prompt 到 Transformer 可读序列的完整实现解读

【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics

文章导读

本文基于 Ultralytics 仓库 geometry_encoders.py 的公开接口与真实实现,深入剖析 SAM3(Segment Anything Model 3)中"几何提示(geometric prompt)"的表示与编码链路:Prompt数据结构如何承载 box/point 几何提示,is_right_paddedconcat_padded_sequences两个工具函数如何维护右填充(right-padded)的不定长批序列,以及SequenceGeometryEncoder如何将"归一化的 CxCyWH 框"投影、RoI 池化并融合位置编码后送入 Transformer。读完本文,你将能够准确理解 geometry_encoders.md 所列四个公开符号的输入输出约定、维度规则与参数语义,并能在二次开发(接入自定义几何提示、调整编码配置)时直接定位到相关源码与调用点。

说明:docs/en/reference/...下以::: 模块路径.符号形式呈现的是由 mkdocstrings 从源码 docstring 自动生成的 API 参考页,其技术细节的权威来源正是本文所引用的源码模块与上游调用点。


一、模块定位:SAM3 中"框提示"通往 Transformer 的必经之路

ultralytics/models/sam/sam3/geometry_encoders.py位于 SAM3 子包内,是标准 SAM3 语义模型(SAM3SemanticModel)中"文本提示 + 几何提示"双通道提示编码的关键一环。从 sam3_image.py 可以看到,模型在编码阶段先将几何提示交给self.geometry_encoder(即SequenceGeometryEncoder实例),得到geo_feats, geo_masks,再与视觉提示 embedding 拼接成完整的 prompt 序列,随后被 Transformer encoder 消费:

# ultralytics/models/sam/sam3/sam3_image.py geo_feats, geo_masks = self.geometry_encoder( geo_prompt=geometric_prompt, img_feats=img_feats, img_sizes=vis_feat_sizes, img_pos_embeds=img_pos_embeds, ) prompt = torch.cat([geo_feats, visual_prompt_embed], dim=0) prompt_mask = torch.cat([geo_masks, visual_prompt_mask], dim=1)

由此可见该模块在整个 SAM3 前向中的位置:图像特征由 backbone 产出,几何提示(框/角点)由该模块编码为与图像特征同维的 token 序列,供 Transformer 在 cross-attention 中作为 query/condition 使用。

在调用链另一侧,build_sam3.py 以"模块配置"的方式给出了标准 SAM3 语义模型的几何编码器组装实例(详见第四节);predict.py 在推理器(SAM3Predictor)中通过Prompt(box_embeddings=torch.zeros(0, num_prompts, 4), ...)构造"零框 dummy 几何提示",用于无框提示时的推理路径。这两处共同构成该模块在"模型构建"与"推理"两条链路上的真实用法证据。


二、Prompt:统一承载几何提示的数据容器

Prompt在 geometry_encoders.py 的类 docstring 中被定位为"操作几何提示的工具类"。它是介于上游"归一化坐标输入"与下游编码器之间的统一数据容器,其维度约定如下(源码注释原文要点):

张量形状含义允许取值
box_embeddingsN_boxes × B × C_box每个框的几何特征归一化框坐标或预计算 embedding
box_maskB × N_boxesattention mask(PyTorch 约定,1 表示被 mask/pad)None表示无 mask 项
box_labelsN_boxes × B正/负样本标签(long 型)None表示全部视为正样本

三个关键约定在类 docstring 中被强调:

  1. 序列维度在前:所有序列张量按 PyTorch 约定组织——序列长度(N)在前、batch 维度(B)在后;而 mask 张量则 batch-first。
  2. 盒坐标采用归一化 CxCyWH 格式:即(center_x, center_y, width, height),坐标值归一化到[0, 1],这一点由SequenceGeometryEncoder的类 docstring 声明并由构造函数断言box_embeddings.shape[-1] == 4来保证(见 geometry_encoders.py)。
  3. 标签默认全正、mask 默认全非 padbox_labels缺省时填充torch.ones(...)box_mask缺省时填充torch.zeros(...)(geometry_encoders.py)。

2.1 构造与形状校验

Prompt.__init__接受box_embeddings / box_mask / box_labels三个可选参数。当box_embeddings is None时构造出一个"空 prompt"(三字段全为None),可用于零框推理;否则依次补齐缺失的labelsmask,并对以下条件做断言校验:

  • box_embeddings前两维为[N_boxes, B],末维必须为 4(四个几何量);
  • box_mask形状恰为[B, N_boxes]
  • box_labels形状恰为[N_boxes, B]
  • 三者 device 一致。

源码中这些校验一方面保证数据自洽,另一方面与concat_padded_sequences的断言共同构成编码前最后一道"维度防火墙"。

2.2 append_boxes:动态追加框提示

Prompt.append_boxes(boxes, labels=None, mask=None)(geometry_encoders.py)支持两种场景:

  • 首框初始化:当self.box_embeddings is None时,直接以本次boxes初始化并补齐 labels/mask;
  • 追加既有:校验 batch 大小与 shapes 一致后,借助concat_padded_sequences分别对box_labels(先unsqueeze(-1)再拼接、再squeeze(-1))与box_embeddings完成右填充序列拼接。

该方法与第一节所述_get_dummy_prompt(零框)形成互补:推理中通过逐帧"追加框"即可在保持右填充不变式的前提下累积提示。从源码结构看,这类逐次追加的能力为视频/交互式场景下提示的增量维护提供了原语。


三、两个基础工具函数:右填充判定与不等长序列拼接

3.1 is_right_padded:判定 padding 是否在右侧

def is_right_padded(mask: torch.Tensor): return (mask.long() == torch.sort(mask.long(), dim=-1)[0]).all()

按 PyTorch 约定,padding mask 中 1 表示被 pad 的占位。若 padding 位于序列右侧,则 mask 中先出现一段 0、后出现一段 1,恰好是**非递减(升序)**序列。该函数通过比较 mask 与其升序排序结果是否逐元素相等,来判断"整批序列是否都是右填充"(geometry_encoders.py)。它被concat_padded_sequences内部以torch._assert调用,作为拼接操作的前置不变量检查。

3.2 concat_padded_sequences:两条右填充序列的无缝拼接

concat_padded_sequences(seq1, mask1, seq2, mask2, return_index=False)(geometry_encoders.py)是Prompt.append_boxesSequenceGeometryEncoder.forward(追加 CLS token 时)共同依赖的核心拼接原语。其输入输出约定如下:

参数形状说明
seq1(L1, B, H)序列优先、特征在末维
mask1(B, L1)1 表示 pad
seq2(L2, B, H)同上
mask2(B, L2)同上
return_indexbool是否额外返回 seq2 在拼接序列中的索引

实现要点(算法层面):

  1. 前置断言:核对 batch、hidden、序列长度两两匹配,并断言mask1/mask2均为右填充。
  2. 计算真实长度actual_seqN_lengths = (~maskN).sum(dim=-1)统计每样本非 pad 的真实 token 数;拼接后每样本真实长度相加为final_lengths,最大可能长度为max_length = L1 + L2
  3. 构造拼接 mask:利用广播比较torch.arange(max_length) >= final_lengths生成新的右填充 mask——凡超过该样本真实总长度的位置都置 1。
  4. 移位放置 seq2:先新建(max_length, B, H)的全零张量,把seq1直接放进前L1行;随后计算 seq2 各行应落入的目标行号index = arange(L2)[:,None] + actual_seq1_lengths[None](即"在 seq1 实际长度基础上偏移"),用scatter将 seq2 写入对应位置。
  5. 可选返回 indexreturn_index=True时额外返回index(形状(L2, B)),用于从拼接序列中精确取回 seq2 的元素。

正是"mask 右填充 + 每样本真实长度可推导"这一不变式,使得该函数无需逐样本循环即可高效完成变长序列的批式拼接,拼接结果天然仍是右填充序列,可直接馈给下游 Transformer 的key_padding_mask


四、SequenceGeometryEncoder:构造参数与三种框编码路径

SequenceGeometryEncoder的完整 docstring 与构造函数位于 geometry_encoders.py。它声明接受"归一化 CxCyWH"格式的框,框可被三种方式编码,三者互不排斥、可叠加求和

  • direct projection(线性投影):对 4 维坐标做线性投影到d_model
  • pooling(RoI align):从 backbone 特征图做 RoI align,汇聚框内区域特征;
  • pos encoder(位置编码):对框中心做正余弦位置编码(复用PositionEmbeddingSine)。

作为替代方案,框还可以被拆解为左上/右下两个角点来编码(encode_boxes_as_points=True)。

4.1 构造参数语义

参数类型语义与影响
encode_boxes_as_pointsbool是否把框拆为两个角点编码。True 时使用(左上, 右下)两组点
boxes_direct_projectbool线性投影路径,对应nn.Linear(4, d_model)
boxes_poolboolRoI 路径,对应nn.Conv2d(d_model, d_model, roi_size)
boxes_pos_encbool位置编码路径,对应nn.Linear(d_model + 2, d_model)
d_modelint模型宽度,所有编码输出的公共通道维度
pos_encnn.Module位置编码器(如PositionEmbeddingSine),用于框中心编码
num_layersint后续 Transformer 编码层数量;> 0时强制建议开启 CLS
layernn.Module单个 Transformer 编码层(由_get_clones深拷贝复制)
roi_sizeint=7RoI align 输出尺寸(高/宽)
add_clsbool=True是否在序列头部加入可学习的 CLS token
add_post_encode_projbool=True是否追加Linear + LayerNorm作为编码后精化
use_act_ckptbool=False是否在多层编码器上启用激活检查点(省显存)

构造函数中还蕴含两个与配置一致性相关的细节:

  • 标签 embedding 数量动态化:编码为框时每 token 只有正/负 2 类标签;编码为角点时每点可能出现"普通正负、左上正负、右下正负"共 6 类,故label_embed = nn.Embedding(num_labels, d_model)num_labels = 6 if encode_boxes_as_points else 2(geometry_encoders.py)。
  • 非角点模式至少需要一种框编码方式:若encode_boxes_as_points=False且三种框编码开关全为 False,则直接断言报错"Error: need at least one way to encode boxes"
  • RoI 相关模块附带输入归一化:当任一 pooling 路径启用时,img_pre_normnn.Identity()切换为nn.LayerNorm(d_model),在池化前对特征做逐层归一。

4.2 仓库中的真实组装示例

build_sam3.py 中build_semantic_sam3给出的标准配置可作为理解各参数的权威样例:

input_geometry_encoder = SequenceGeometryEncoder( pos_enc=PositionEmbeddingSine( num_pos_feats=256, normalize=True, scale=None, temperature=10000, ), encode_boxes_as_points=False, boxes_direct_project=True, boxes_pool=True, boxes_pos_enc=True, d_model=256, num_layers=3, layer=TransformerEncoderLayer( d_model=256, dim_feedforward=2048, dropout=0.1, pos_enc_at_attn=False, pre_norm=True, pos_enc_at_cross_attn_queries=False, pos_enc_at_cross_attn_keys=True, ), use_act_ckpt=True, add_cls=True, add_post_encode_proj=True, )

可以看到生产级语义模型默认三条框编码路径全部开启并求和d_model=256、3 层 Transformer 编码层(num_layers=3)、激活检查点开启(use_act_ckpt=True),且使用正弦位置编码PositionEmbeddingSine(num_pos_feats=256, temperature=10000)。其中_get_clones(layer, num_layers)负责把同一个layer深拷贝出多层堆叠(该工具函数定义在 nn/modules/utils.py,是 Ultralytics 内部通用的模块克隆助手)。


五、三种框编码方式的底层实现

5.1 直接线性投影(direct projection)

_encode_boxes中(geometry_encoders.py),若boxes_direct_project开启,则将归一化 4 维坐标直接送入nn.Linear(4, d_model)

proj = self.boxes_direct_project(boxes.to(img_feats.dtype))

这里boxes.to(img_feats.dtype)表明坐标会先被转换成与图像特征一致的精度(如混合精度下为 fp16)。

5.2 RoI align 特征汇聚(pooling)

boxes_pool开启,其流程(geometry_encoders.py)为:

  1. img_featsH, W
  2. xywh2xyxy把归一化 CxCyWH 框转为xyxy,再按[W, H, W, H]缩放反归一化到像素坐标;
  3. 调用torchvision.ops.roi_align(延迟导入以加快ultralytics包加载)在特征图上采样,得到(B*N, d_model, roi_size, roi_size)的 RoI 特征;
  4. nn.Conv2d(d_model, d_model, roi_size)将每个 RoI 汇聚成d_model维向量(roi_size=7时等价于 7×7 全局卷积池化);
  5. view(bs, n_boxes, d_model).transpose(0, 1)还原为(N, B, d_model)的序列优先格式。

注意xywh2xyxy来自 utils/ops.py,是该仓库全局复用的坐标转换工具,与 SAM/SAM2 其他模块保持一致。

5.3 框中心位置编码(pos encoder)

boxes_pos_enc开启(geometry_encoders.py),会把框解绑为cx, cy, w, h四个标量组,调用位置编码器的encode_boxes

enc = self.pos_enc.encode_boxes(cx.flatten(), cy.flatten(), w.flatten(), h.flatten()) proj = self.boxes_pos_enc_project(enc.to(img_feats.dtype))

boxes_pos_enc_projectnn.Linear(d_model + 2, d_model)——多出的 2 维来自encode_boxes末尾直接拼接的(h, w)原始宽度/高度。PositionEmbeddingSine.encode_boxes的实现在 nn/.../blocks.py(注:该文件真实路径为ultralytics/models/sam/modules/blocks.py),其做法是把中心点按y, x顺序拼接正弦编码后再接h, w

pos_x, pos_y = self._encode_xy(x, y) return torch.cat((pos_y, pos_x, h[:, None], w[:, None]), dim=1)

三路结果(直接投影 / RoI / 位置编码)在_encode_boxes中通过"先到先得、后到累加"的方式求和;最后统一加上type_embed = self.label_embed(boxes_labels.long())的标签 embedding,作为几何 token 的最终表示。这印证了类 docstring 中"三种编码互不排斥、多选即求和"的表述。


六、角点编码模式:encode_boxes_as_points=True 的分支

encode_boxes_as_points=True时,forward_encode_points分支(geometry_encoders.py),将每个框"升级"成一对角点 token:

  1. boxes_xyxy = xywh2xyxy(boxes)转为归一化 xyxy,再split(split_size=2, dim=-1)拆成top_left(前两维)与bottom_right(后两维);
  2. 对角点标签做偏移区分来源labels_tl = boxes_labels + 2labels_br = boxes_labels + 4,配合构造时预留的 6 类label_embed,使 Transformer 能区分"左上正/负"与"右下正/负";
  3. 两组点按序列维torch.cat拼接成(2*N, B, 2)的点序列,mask 相应横向拼接;
  4. 交由_encode_pointsnn.Linear(2, d_model)直接投影 2 维坐标并叠加 6 类标签 embedding。

这种模式下序列长度翻倍(每框两 token),换来的是模型对框两角位置更细粒度的关注,适合需要更强空间定位能力的设定。


七、forward 主流程:CLS、归一化精化与 Transformer 编码层

SequenceGeometryEncoder.forward(geo_prompt, img_feats, img_sizes, img_pos_embeds=None)(geometry_encoders.py)的完整流水线为:

  1. 取数:从geo_prompt解出boxes / boxes_mask / boxes_labels;同时取img_feats[-1]作为"序列优先"(H*W, B, C)的跨模态记忆,供后续 cross-attention 使用。
  2. 池化前的特征准备:若启用了任一条 pooling 路径,则用img_pre_norm(LayerNorm)对最后一层图像特征归一化,并由(H*W, B, C)重排为(N, C, H, W)图像格式以配合 RoI align。
  3. 按模式编码encode_boxes_as_points为 True 走角点路径,否则走框编码路径,得到final_embeds (L', B, d_model)final_mask (B, L')
  4. 追加 CLS:若add_cls=True,用可学习cls_embed生成 1 个全 batch 共享的 CLS token(mask 位为 0,永不被 pad),并通过concat_padded_sequences将其拼接在序列头部(geometry_encoders.py)。这也解释了构造器中"使用 Transformer 时强烈建议开启 CLS"的断言——CLS 是编码层输出汇聚的聚合位。
  5. 后编码精化:若final_proj存在,执行norm(final_proj(final_embeds))(Linear + LayerNorm)。
  6. 堆叠 Transformer 编码层:将num_layers个克隆的layer逐层作用——每层以图像特征为memory、几何序列为tgttgt_key_padding_mask传入右填充 mask、pos传入图像侧位置编码,最终经encode_norm(LayerNorm)输出。

返回的(final_embeds, final_mask)即是第二节sam3_image.pygeo_feats, geo_masks的来历:前者为几何 token 序列,后者为其对应的 padding mask,二者一起作为 prompt 参与后续 Transformer 的文本/几何联合编码。use_act_ckpt在构造时被保存但不在本模块内显式包装,从源码结构看它由外部的封装层结合torch.utils.checkpoint机制统一启用。


八、维度约定与使用要点速查

综合Promptconcat_padded_sequencesSequenceGeometryEncoder三者的 docstring 与断言,可提炼出以下必须遵守的约定(也是二次开发时最容易出错之处):

  1. 序列优先、批次第二:embedding/坐标类张量形状为(seq_len, batch, feat);mask 与绝大多数标签张量批次优先。
  2. mask 的 1 表示 pad,且必须是右填充:所有交给编码器 / 拼接函数的 mask 都需满足is_right_padded
  3. 框坐标使用归一化 CxCyWH:末维为 4;转 xyxy、反归一化等由编码器内部按H/W完成,外部只需保证归一化。
  4. d_model贯穿始终:图像特征、位置编码输出、标签 embedding、投影输出与图像侧记忆共享同一维度,改动时需保证 backbone 特征通道与d_model匹配。
  5. 框与角点编码二选一encode_boxes_as_points=True时内部忽略boxes_direct_project/boxes_pool/boxes_pos_enc三开关(此时点编码仅用线性投影),其标签字典扩大为 6 类。

推理侧的最小化示例可以参考_get_dummy_prompt(predict.py):构造零框 Prompt 时传入box_embeddings=torch.zeros(0, B, 4)box_mask=torch.zeros(B, 0, dtype=torch.bool)即可,编码器对空序列亦能正常前向(直接拼接 CLS 后进入 Transformer)。


九、小结

geometry_encoders.py是 SAM3 几何提示链路上封装最完整、契约最严格的模块之一:Prompt统一了输入数据的形状与语义,两个工具函数维护了变长序列批式运算的核心不变式,SequenceGeometryEncoder则以"多路编码求和 + 可选 CLS + 后置精化 + Transformer 堆叠"的模块化设计,把任意数量的归一化框提示转换为与图像/文本特征同构的 token 序列。本文所引的接口签名与行为均以 reference 文档、源码 及其在 build_sam3.py、sam3_image.py、predict.py 中的真实调用为准;对"为何如此设计"等推断性结论,已在文中以"从源码结构看/可以推断"等措辞明确标注。读者如需深入,可直接以上述文件为入口研读完整实现。

【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询