概述

Siglip2 测试套件的位置和规模:

项目说明
路径G:\Project\blog\content\posts\VLM\Siglip2\
文件数4 个 .py 文件
总行数~700 行
依赖transformers 核心库、torchPILnumpypytest
定位对 Siglip2 多模态模型的完整测试覆盖:tokenizer、image processor、vision model、text model、combined model
上游模型google/siglip2-base-patch16-naflex (灵活分辨率), google/siglip2-base-patch16-224 (固定分辨率)

四个文件的职责定位速览

文件行数抽象层次核心角色
__init__.py0出口层 (Barrel)空文件,标记目录为 Python 包
test_image_processing_siglip2.py~110预处理层 (Preprocessing)测试图像处理器:resize、rescale、normalize、patchify 全流程
test_modeling_siglip2.py~540模型层 (Model)测试 Vision/Text/Combined/Classification 四种模型的 forward、SDPA、Flash Attention
test_tokenization_siglip2.py~55分词层 (Tokenization)测试 tokenizer 的 lowercasing、padding、truncation、save/load 行为

分析框架说明

对每个文件,按以下五层解构:

+-------------------------------------------------------------+
| 1. 代码 (Code)                                               |
|    具体的函数签名、类结构、关键代码段                            |
+-------------------------------------------------------------+
| 2. 功能 (Function)                                           |
|    这段代码在测试运行时做什么                                   |
+-------------------------------------------------------------+
| 3. 抽象 (Abstraction)                                        |
|    代码在测试架构中的抽象层级/角色                               |
+-------------------------------------------------------------+
| 4. 实例 (Instance)                                           |
|    在哪里被引用/谁调用它                                       |
+-------------------------------------------------------------+
| 5. 设计目的 (Design Purpose)                                  |
|    为什么这么设计,验证了什么核心逻辑                           |
+-------------------------------------------------------------+

文件一:__init__.py – 出口层 (Barrel Layer)

项目
行数0
导出量0
依赖

解构分析

代码

1
# (空文件)

功能:标记 Siglip2/ 目录为 Python 包,使测试发现工具(如 pytest、unittest)能够扫描该目录。

抽象Python 包标记 – 在 Python 的隐式命名空间包(PEP 420)环境下,该文件确保目录被显式视为 package,使相对 import(如 from ...test_image_processing_common import ...)可正确解析。

被引用处

  • test_modeling_siglip2.pyfrom ...test_modeling_common import ModelTesterMixin
  • test_image_processing_siglip2.pyfrom ...test_image_processing_common import ImageProcessingTestMixin

设计目的:保持 HuggingFace Transformers 测试套件的标准目录结构。每个模型的测试文件统一放在 tests/models/{model_name}/ 下,__init__.py 是必需的包标记。


文件二:test_image_processing_siglip2.py – 预处理层 (Preprocessing Layer)

整体信息

项目
行数~110
类数2 (Tester + TestCase)
关键配置patch_size=16, max_num_patches=256, rescale_factor=1/255, mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5]
继承链ImageProcessingTestMixin, unittest.TestCase

解构分析


1. Siglip2ImageProcessingTester – 测试数据工厂

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Siglip2ImageProcessingTester:
    def __init__(
        self,
        parent,
        batch_size=7,
        num_channels=3,
        image_size=18,
        min_resolution=30,
        max_resolution=400,
        do_resize=True,
        size=None,
        do_rescale=True,
        rescale_factor=1 / 255,
        do_normalize=True,
        image_mean=[0.5, 0.5, 0.5],
        image_std=[0.5, 0.5, 0.5],
        resample=None,
        patch_size=16,
        max_num_patches=256,
    ):
        size = size if size is not None else {"height": 18, "width": 18}
        resample = resample if resample is not None else Image.Resampling.BILINEAR
        # ... 赋值给 self

功能:为图像处理测试提供标准化的配置参数和测试数据。核心方法:

  • prepare_image_processor_dict() – 返回图像处理器的完整参数字典
  • expected_output_image_shape(images) – 返回预期的输出 shape:(max_num_patches, patch_size * patch_size * num_channels) = (256, 768)
  • prepare_image_inputs(...) – 生成不同分辨率的批量图像输入

抽象测试夹具工厂 (Test Fixture Factory) – 将图像处理器的参数化配置与测试逻辑分离。测试用例不硬编码参数,而是从 Tester 获取。

被引用处

  • Siglip2ImageProcessingTest.setUp() 中创建 self.image_processor_tester = Siglip2ImageProcessingTester(self)
  • image_processor_dict 属性委托给 self.image_processor_tester.prepare_image_processor_dict()

设计目的

  • 参数集中管理,修改测试配置只需改 Tester 的默认值
  • expected_output_image_shape 编码了 Siglip2 的核心图像处理公式:图像被切分为最多 256 个 patch,每个 patch 是 16*16*3=768 维向量
  • rescale_factor=1/255 将像素值从 [0,255] 归一化到 [0,1]
  • image_mean=image_std=[0.5,0.5,0.5] 将 [0,1] 映射到 [-1,1]

2. Siglip2ImageProcessingTest – 图像处理器测试用例

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Copied from tests.models.clip.test_image_processing_clip.CLIPImageProcessingTest with CLIP->Siglip2
class Siglip2ImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
    def setUp(self):
        super().setUp()
        self.image_processor_tester = Siglip2ImageProcessingTester(self)

    @property
    def image_processor_dict(self):
        return self.image_processor_tester.prepare_image_processor_dict()

    def test_image_processor_properties(self):
        for image_processing_class in self.image_processing_classes.values():
            image_processing = image_processing_class(**self.image_processor_dict)
            self.assertTrue(hasattr(image_processing, "do_resize"))
            self.assertTrue(hasattr(image_processing, "patch_size"))
            self.assertTrue(hasattr(image_processing, "max_num_patches"))
            # ... 验证 8 个关键属性

    def test_image_processor_from_dict_with_kwargs(self):
        image_processor = image_processing_class.from_dict(self.image_processor_dict)
        self.assertEqual(image_processor.max_num_patches, 256)
        self.assertEqual(image_processor.patch_size, 16)
        image_processor = image_processing_class.from_dict(
            self.image_processor_dict, patch_size=32, max_num_patches=512
        )
        self.assertEqual(image_processor.patch_size, 32)
        self.assertEqual(image_processor.max_num_patches, 512)

功能:三个测试方法:

  1. test_image_processor_properties – 验证图像处理器拥有所有必需的 8 个属性
  2. test_image_processor_from_dict_with_kwargs – 验证 from_dict() 支持 kwargs 覆盖
  3. test_call_numpy_4_channels – 被跳过(不支持 4 通道输入)

抽象接口合规测试 (Contract Test) – 不测试具体输出值,而是验证:

  • 图像处理器实现了特定接口(拥有特定属性)
  • 序列化/反序列化 (from_dict) 保持参数一致性
  • kwargs 覆盖机制正常工作(patch_size 从 16 覆盖到 32)

被引用处

  • pytest/unittest 自动发现并执行
  • 代码标注 # Copied from tests.models.clip.test_image_processing_clip...,说明 Siglip2 的图像处理沿用了 CLIP 的测试框架

设计目的

  • # Copied from 注释表明 Siglip2 与 CLIP 共享图像处理测试代码,实际处理逻辑可能完全一致
  • 属性检查覆盖了完整的图像处理 pipeline:do_resize -> do_rescale -> do_normalize -> patchify
  • max_num_patches=256patch_size=16 是 Siglip2 的核心配置,定义了模型对图像的分割策略
  • 4 通道测试被跳过,因为 Siglip2 只处理 RGB 三通道图像

文件三:test_modeling_siglip2.py – 模型层 (Model Layer)

整体信息

项目
行数~540
类数10 (3 个 Mixin/Tester 基类 + 4 个 TestCase + 2 个 Tester + 1 个 IntegrationTest)
被测模型Siglip2VisionModel, Siglip2TextModel, Siglip2Model, Siglip2ForImageClassification
关键输入pixel_values, pixel_attention_mask, spatial_shapes
关键输出logits_per_image, logits_per_text, pooler_output

模型架构概览

+----------------------------+
|      Siglip2Model           |
|  (Dual Encoder - Contrastive)|
+----------------------------+
        |            |
        v            v
+--------------+  +--------------+
| VisionModel  |  |  TextModel   |
| (ViT-based)  |  | (Transformer)|
+--------------+  +--------------+
        |            |
        v            v
  pooler_output   pooler_output
        |            |
        +----+ +-----+
             | |
        logits_per_image
        logits_per_text
             |
    +--------+--------+
    |                  |
Siglip2ForImageClassification
    (Vision-only head)

解构分析


1. Siglip2ModelTesterMixin – SDPA/Flash Attention 测试混入

SDPA 是缩放点积注意力机制,Eager 是 PyTorch 默认的注意力实现,Flash Attention 是一种高效的注意力计算方法。Siglip2 支持三种注意力后端。

test_sdpa_can_dispatch_composite_models 创建、配置所有待测试的模型实例,保存到临时目录,同一个模型分别用 sdpa/eager 两种注意力实现进行前向传播。

功能:两个关键测试:

  1. SDPA dispatch – 验证复合模型(vision + text 子模型)能正确分派 SDPA 注意力实现,每个子模型的 _attn_implementation 独立配置
  2. Flash Attention 等价性 – 验证 flash_attention_2 与 eager 模式产生等价输出,容差 atol=4e-2, rtol=4e-2

test_flash_attn_2_inference_equivalence 测试 Flash Attention 与 Eager 在推理阶段的输出一致性。因为 Flash Attention 主要支持半精度计算,测试中使用 float16,并检验了有无 pixel_attention_mask 的两种情况。

抽象注意力实现兼容性测试 (Attention Backend Compatibility) – 确保模型在 eager、sdpa、flash_attention_2 三种注意力后端下行为一致。

被引用处

  • Siglip2VisionModelTest(Siglip2ModelTesterMixin, ...) – Vision-only 测试
  • Siglip2TextModelTest(Siglip2ModelTesterMixin, ...) – Text-only 测试
  • Siglip2ModelTest(Siglip2ModelTesterMixin, ...) – 组合模型测试
  • Siglip2ForImageClassificationModelTest(Siglip2ModelTesterMixin, ...) – 分类测试

设计目的

  • 复合模型分派:Siglip2 是双塔结构,每个塔独立控制注意力实现。MixIn 级测试确保配置保存/加载后分派正确
  • Flash Attention 等价性pixel_attention_mask 会导致 flash attention 与 eager 的 padding 区域不同,测试中通过 mask 相乘进行校正
  • atol=4e-2 是宽松容差,因为不同注意力实现的浮点运算顺序不同,但语义等价

2. Siglip2VisionModelTester – Vision 模型测试数据工厂

功能:生成 Vision 模型的测试输入。核心机制:

  1. pixel_values 构造:shape 为 (batch_size, seq_length, patch_dim),已经是 patchified 形式。patch_dim = num_channels * patch_size * patch_size = 12
  2. spatial_shapes 生成:枚举所有可能的 (height, width) 组合,使得 height * width <= seq_length。有效 shape 示例:(1,1)=(1), (1,2)=(2), (2,1)=(2), …, (4,6)=(24)
  3. pixel_attention_mask 构造:根据 spatial_shapes 中每个样本的实际 patch 数量设置 mask

抽象空间感知测试数据生成器 (Spatial-Aware Data Generator) – 模拟真实图像的不同尺寸/宽高比场景。

被引用处

  • Siglip2VisionModelTest.setUp() 创建 self.model_tester = Siglip2VisionModelTester(self)
  • Siglip2ModelTester 内部持有一个 self.vision_model_tester = Siglip2VisionModelTester(parent, **vision_kwargs)

设计目的

  • spatial_shapes 是 Siglip2 区别于 CLIP 的核心创新:传统 CLIP 固定输入尺寸(224x224 -> 14x14=196 patches),而 Siglip2 (naflex) 支持灵活分辨率。spatial_shapes 记录每个样本的 patch 网格 (height, width),使模型知道如何 reshape 1D patch 序列回 2D 结构用于位置编码
  • pixel_attention_mask 用于处理可变 patch 数量的 batch:同一 batch 中不同图像可能产生不同数量的 patch,padding 位置通过 mask 排除
  • 小尺寸测试配置(patch_size=2, hidden_size=64, num_hidden_layers=2)保证 CI 快速运行

3. Siglip2VisionModelTest – Vision 模型测试用例

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@require_torch
class Siglip2VisionModelTest(Siglip2ModelTesterMixin, unittest.TestCase):
    all_model_classes = (Siglip2VisionModel,)
    additional_model_inputs = ["pixel_attention_mask", "spatial_shapes"]

    test_resize_embeddings = False
    test_cpu_offload = False
    test_disk_offload_safetensors = False
    test_disk_offload_bin = False

    def test_forward_signature(self):
        # Verify forward() signature starts with ["pixel_values"]
        arg_names = [*signature.parameters.keys()]
        self.assertListEqual(arg_names[:1], ["pixel_values"])

    def test_model(self):
        config_and_inputs = self.model_tester.prepare_config_and_inputs()
        self.model_tester.create_and_check_model(*config_and_inputs)
        # Verifies: last_hidden_state.shape = (batch, seq_len, hidden_size)
        #           pooler_output.shape = (batch, hidden_size)

    @slow
    def test_model_from_pretrained(self):
        model_name = "google/siglip2-base-patch16-naflex"
        model = Siglip2VisionModel.from_pretrained(model_name)
        self.assertIsNotNone(model)

功能

  • 验证 Vision 模型的 forward() 签名(首参数为 pixel_values
  • 验证模型前向传播的输出 shape:last_hidden_state (batch, seq_len, hidden_size)pooler_output (batch, hidden_size)
  • 验证从 HuggingFace Hub 加载预训练模型

抽象模型接口合规测试 – 确保 Vision 模型:

  • 接受 pixel_values, pixel_attention_mask, spatial_shapes 三个输入
  • 输出标准格式(last_hidden_state + pooler_output)
  • 预训练检查点可正确加载

被引用处:由 unittest/pytest 自动发现执行

设计目的

  • additional_model_inputs 声明了 Siglip2 特有的两个输入,基类 ModelTesterMixin 会据此调整通用测试逻辑
  • 关闭设备卸载测试test_cpu_offload=False 等):因为 MultiheadAttentionPoolingHead 在卸载时存在问题,临时跳过
  • 关闭 embeddings resize 测试:Vision Transformer 不需要文本 tokenizer 的 embeddings resize 机制
  • test_model_from_pretrained 使用 naflex 变体(灵活分辨率),而非固定尺寸版本

4. Siglip2TextModelTester – Text 模型测试数据工厂

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Siglip2TextModelTester:
    def __init__(
        self,
        parent,
        batch_size=12,
        seq_length=7,
        vocab_size=99,
        hidden_size=64,
        num_hidden_layers=2,
        num_attention_heads=4,
        intermediate_size=37,
        max_position_embeddings=512,
        dropout=0.1,
    ):
        ...

    def prepare_config_and_inputs(self):
        input_ids = ids_tensor([self.batch_size, self.seq_length], self.vocab_size)
        input_mask = random_attention_mask([self.batch_size, self.seq_length])

        # Variable-length sequences: random start indices for truncation
        rnd_start_indices = np.random.randint(1, seq_length - 1, size=(batch_size,))
        for batch_idx, start_index in enumerate(rnd_start_indices):
            input_mask[batch_idx, :start_index] = 1
            input_mask[batch_idx, start_index:] = 0

        config = Siglip2TextConfig(vocab_size=99, hidden_size=64, ...)
        return config, input_ids, input_mask

功能:生成 Text 模型的测试输入:

  • input_ids:随机 token id 序列
  • input_mask:变长序列的 attention mask(每个样本随机截断长度)
  • Siglip2TextConfig:小尺寸测试配置

抽象变长序列测试数据生成器 – 模拟真实文本的变长特性。

被引用处

  • Siglip2TextModelTest.setUp() 创建 self.model_tester = Siglip2TextModelTester(self)
  • Siglip2ModelTester 内部持有 self.text_model_tester = Siglip2TextModelTester(parent, **text_kwargs)

设计目的

  • 变量长度序列(通过随机截断 input_mask 实现)模拟真实场景中同一 batch 内文本长度不同的情况
  • max_position_embeddings=512 定义了文本模型的最大序列长度
  • Text Model 比 Vision Model 更简单:只需要 input_idsattention_mask,不需要类似 spatial_shapes 的空间信息

5. Siglip2TextModelTest – Text 模型测试用例

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@require_torch
class Siglip2TextModelTest(Siglip2ModelTesterMixin, unittest.TestCase):
    all_model_classes = (Siglip2TextModel,)
    test_resize_embeddings = False

    def test_model(self):
        # Verifies: last_hidden_state.shape = (batch, seq_len, hidden_size)
        #           pooler_output.shape = (batch, hidden_size)

    @unittest.skip(reason="This module does not support standalone training")
    def test_training(self):
        pass

功能

  • 验证 Text 模型前向传播输出 shape
  • 跳过独立训练测试(Text 模型需要与 Vision 模型联合训练才有意义)

抽象文本塔接口测试 – 文本塔仅作为 Siglip2 联合模型的一部分,不支持独立训练。

设计目的

  • test_training 等 4 个训练相关测试全部跳过,因为 Siglip2 的 loss 是在 Siglip2Model 层面计算的(对比学习 loss),Siglip2TextModel 本身只是一个编码器
  • 与 Vision Model 类似的 pooler_output 输出,确保两个塔的输出对齐

6. Siglip2ModelTester – 组合模型测试数据工厂

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Siglip2ModelTester:
    def __init__(self, parent, text_kwargs=None, vision_kwargs=None, is_training=True):
        self.text_model_tester = Siglip2TextModelTester(parent, **text_kwargs)
        self.vision_model_tester = Siglip2VisionModelTester(parent, **vision_kwargs)

    def prepare_config_and_inputs(self):
        text_config, input_ids, attention_mask = self.text_model_tester.prepare_config_and_inputs()
        vision_config, pixel_values, pixel_attention_mask, spatial_shapes = \
            self.vision_model_tester.prepare_config_and_inputs()

        config = Siglip2Config(
            text_config=self.text_model_tester.get_config().to_dict(),
            vision_config=self.vision_model_tester.get_config().to_dict(),
        )
        return config, input_ids, attention_mask, pixel_values, pixel_attention_mask, spatial_shapes

    def create_and_check_model(self, config, input_ids, attention_mask,
                                pixel_values, pixel_attention_mask, spatial_shapes):
        model = Siglip2Model(config).eval()
        result = model(input_ids, pixel_values, pixel_attention_mask, spatial_shapes, attention_mask)

        # Vision batch=12, Text batch=12 → logits_per_image (12, 12), logits_per_text (12, 12)
        self.parent.assertEqual(result.logits_per_image.shape,
            (self.vision_model_tester.batch_size, self.text_model_tester.batch_size))
        self.parent.assertEqual(result.logits_per_text.shape,
            (self.text_model_tester.batch_size, self.vision_model_tester.batch_size))

功能

  • 聚合 Text + Vision 两个子模型的测试数据
  • 关键验证logits_per_imagelogits_per_text 的 shape 为 (vision_batch, text_batch)(text_batch, vision_batch),是标准的对比学习 logits 矩阵

抽象双塔模型测试编排器 (Dual-Encoder Test Orchestrator) – 协调两个子模型的测试数据生成,验证联合 forward 的输出格式。

被引用处

  • Siglip2ModelTest.setUp() 创建 self.model_tester = Siglip2ModelTester(self)
  • Siglip2ForImageClassificationModelTester(Siglip2ModelTester) 继承并覆盖

设计目的

  • 组合配置Siglip2ConfigSiglip2TextConfigSiglip2VisionConfig 组合而成,而非单一配置类
  • logits 矩阵:是 Siglip2 对比学习损耗的核心输出。传统 CLIP 使用 softmax cross-entropy loss,Siglip2 使用 sigmoid loss(在 logits 矩阵上逐元素计算)。测试验证 (batch, batch) 的方形 logits 矩阵 shape
  • text_kwargs / vision_kwargs 参数允许覆盖子模型的测试配置

7. Siglip2ModelTest – 组合模型测试用例

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@require_torch
class Siglip2ModelTest(Siglip2ModelTesterMixin, PipelineTesterMixin, unittest.TestCase):
    all_model_classes = (Siglip2Model,)
    pipeline_model_mapping = {"feature-extraction": Siglip2Model}
    additional_model_inputs = ["pixel_values", "pixel_attention_mask", "spatial_shapes"]
    _is_composite = True

    test_attention_outputs = False
    test_cpu_offload = False
    test_disk_offload_safetensors = False
    test_disk_offload_bin = False

    def test_load_vision_text_config(self):
        # Verify that Siglip2Config can be decomposed into
        # Siglip2VisionConfig and Siglip2TextConfig via from_pretrained
        config.save_pretrained(tmp_dir_name)
        vision_config = Siglip2VisionConfig.from_pretrained(tmp_dir_name)
        text_config = Siglip2TextConfig.from_pretrained(tmp_dir_name)
        self.assertDictEqual(config.vision_config.to_dict(), vision_config.to_dict())
        self.assertDictEqual(config.text_config.to_dict(), text_config.to_dict())

    @slow
    def test_model_from_pretrained(self):
        model_name = "google/siglip2-base-patch16-naflex"
        model = Siglip2Model.from_pretrained(model_name)
        self.assertIsNotNone(model)

功能

  • 验证组合模型 forward 输出
  • 验证 Siglip2Config 可分解为独立的 Siglip2VisionConfigSiglip2TextConfig
  • 跳过 output_attentionsoutput_hidden_statesinput_embeds 等不支持的测试

抽象组合模型完整性测试 – 验证双塔模型的两个子模块正确组合。

被引用处:由 unittest/pytest 自动发现执行

设计目的

  • _is_composite = True 声明这是复合模型,触发基类中针对复合模型的特殊测试逻辑
  • config 分解测试是 Siglip2 特有的:确保组合配置的序列化/反序列化后,子配置能独立加载
  • 大量跳过
    • output_attentions – Siglip2 当前不支持输出注意力权重
    • output_hidden_states – Siglip2 当前不支持输出隐藏状态(get_image_features / get_text_features 有局限)
    • 这些跳过表明 Siglip2 是一个功能尚不完整的模型实现,attention/hidden_state 输出功能需要在未来重构

8. Siglip2ForImageClassificationModelTester & Test – 图像分类测试

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Siglip2ForImageClassificationModelTester(Siglip2ModelTester):
    def prepare_config_and_inputs(self):
        # Only vision inputs — no text
        _, pixel_values, pixel_attention_mask, spatial_shapes = \
            self.vision_model_tester.prepare_config_and_inputs()
        ...

    @pytest.mark.xfail(reason="This architecture seems to not compute gradients for some layer.")
    def test_training_gradient_checkpointing(self):
        super().test_training_gradient_checkpointing()

功能

  • 测试 Siglip2ForImageClassification(Vision-only 分类头)
  • 输入只含 pixel_values, pixel_attention_mask, spatial_shapes,无文本输入
  • 梯度检查点测试标记为 xfail(已知失败)

抽象视觉下游任务头测试 (Vision Downstream Head Test) – 验证 Vision 编码器 + 分类头的组合。

设计目的

  • 复用 Siglip2ModelTester 但移除文本输入
  • 梯度检查点 xfail 表明:当 Vision 编码器使用 gradient checkpointing 时,某些层的梯度无法正确计算,这是已知的待修复问题

9. Siglip2ModelIntegrationTest – 端到端集成测试

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@require_vision
@require_torch
class Siglip2ModelIntegrationTest(unittest.TestCase):
    @slow
    def test_inference(self):
        model_name = "google/siglip2-base-patch16-naflex"
        model = Siglip2Model.from_pretrained(model_name).to(torch_device)
        processor = Siglip2Processor.from_pretrained(model_name)

        # Images: (224,224), (1024,1024), (224,1024) — different sizes/aspect ratios
        images = prepare_images()  # Red background + blue circle with green border
        text = [
            "circle", "ellipsoid",
            "blue circle on red background",
            "blue circle with green border on red background",
            "green circle on red background",  # negative
            "a dog",                            # negative
            "a blue dog with a green border on a red background",  # negative
        ]

        inputs = processor(text=text, images=images, return_tensors="pt")
        outputs = model(**inputs)

        # logits_per_image: (3 images, 7 texts) = (3, 7)
        # logits_per_text:  (7 texts, 3 images) = (7, 3)

        expected_logits_per_texts = Expectations({
            ("cuda", None): [
                [  1.0195,  -0.0280,  -1.4468],  # "circle" matches square best
                [ -4.5395,  -6.2269,  -1.5667],  # "ellipsoid" doesn't match circles well
                [  4.1757,   5.0358,   3.5159],  # "blue circle..." matches all images
                [  9.4264,  10.1879,   6.3353],  # "blue circle green border" = best match
                [  2.4409,   3.1058,   4.5491],  # "green circle" = weaker match
                [-12.3230, -13.7355, -13.4632],  # "a dog" = very low (correctly)
                [  1.1520,   1.1687,  -1.9647],  # "blue dog..." = low (correctly)
            ],
            ("rocm", (9, 5)): [ ... ],
            ("xpu", 3): [ ... ],
        })
        torch.testing.assert_close(outputs.logits_per_text, EXPECTED_LOGITS_PER_TEXT,
                                   rtol=1e-3, atol=1e-3)

功能:用真实预训练模型和真实图像执行完整推理:

  1. 加载 google/siglip2-base-patch16-naflex 模型和 processor
  2. 准备 3 张不同尺寸/比例的红底蓝圈图
  3. 准备 7 条不同语义贴近度的文本
  4. 验证 logits 值与预期精确匹配

抽象端到端回归测试 (End-to-End Regression Test) – 确保模型权重更新后推理结果无漂移。

设计目的

  • 多分辨率测试:3 张图片尺寸分别为 224, 1024, 224x1024,验证 naflex 的灵活分辨率特性
  • 语义梯度验证:logits 值形成清晰的语义梯度:
    • “blue circle with green border on red background” (9.4-10.2) > “blue circle on red background” (4.2-5.0) > “circle” (1.0)
    • 负样本 “a dog” (-12.3 到 -13.7) 和 “blue dog” (1.2 到 -2.0) logits 显著低于正样本
  • 多平台期望值:不同 CUDA/ROCm/XPU 平台的期望 logits 略有差异,但容差 rtol=1e-3, atol=1e-3 保证高度一致
  • prepare_images() 生成的测试图片是确定性图像(红底 + 蓝圆 + 绿边),确保可复现

文件四:test_tokenization_siglip2.py – 分词层 (Tokenization Layer)

整体信息

项目
行数~55
类数1 (TestCase)
被测模型google/siglip2-base-patch16-224
关键行为默认 lowercasing、padding/truncation、save/load roundtrip

解构分析


1. Siglip2TokenizerTest – Tokenizer 测试用例

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@require_tokenizers
class Siglip2TokenizerTest(unittest.TestCase):
    from_pretrained_id = "google/siglip2-base-patch16-224"

    def test_tokenizer(self):
        tokenizer = Siglip2Tokenizer.from_pretrained(self.from_pretrained_id)

        texts_uc = [
            "HELLO WORLD!",
            "Hello   World!!",
            "A Picture Of Zurich",
            "San Francisco",
            "MIXED-case: TeSt 123",
        ]
        texts_lc = [t.lower() for t in texts_uc]

        # Test 1: Default lowercasing — uppercase and lowercase inputs produce identical token ids
        for t_uc, t_lc in zip(texts_uc, texts_lc):
            enc_uc = tokenizer(t_uc, truncation=True)
            enc_lc = tokenizer(t_lc, truncation=True)
            self.assertListEqual(enc_uc["input_ids"], enc_lc["input_ids"])

        batch_uc = tokenizer(texts_uc, truncation=True)
        batch_lc = tokenizer(texts_lc, truncation=True)
        self.assertListEqual(batch_uc["input_ids"], batch_lc["input_ids"])

        # Test 2: Padding/truncation
        max_len = 64
        padded = tokenizer(texts_uc, padding="max_length", truncation=True, max_length=max_len)
        for seq in padded["input_ids"]:
            self.assertEqual(len(seq), max_len)

        # Test 3: Save/load roundtrip
        with tempfile.TemporaryDirectory() as tmpdir:
            tokenizer.save_pretrained(tmpdir)
            tokenizer_reloaded = Siglip2Tokenizer.from_pretrained(tmpdir)

            batch_uc_2 = tokenizer_reloaded(texts_uc, truncation=True)
            self.assertListEqual(batch_uc["input_ids"], batch_uc_2["input_ids"])

功能:三个核心测试:

  1. Lowercasing 行为:大写和小写输入产生完全相同的 token IDs(单条和批量均验证)
  2. Padding/Truncationmax_length=64 时所有序列被填充或截断到完全相同长度
  3. Save/Load 一致性:保存/重载后的 tokenizer 对相同输入产生完全一致的 token IDs

抽象Tokenizer 行为契约测试 (Tokenizer Contract Test) – 验证 tokenizer 在不同使用模式下的确定性行为。

被引用处:由 unittest 自动发现执行

设计目的

  • Lowercasing 是 Siglip2 tokenizer 的默认行为,这源于 Siglip2 的文本处理策略:与 CLIP 类似,Siglip2 在文本侧使用小写 tokenization 来减少词汇量、提高泛化能力
  • 测试覆盖三种模式:单条编码 vs 批量编码 vs padding/truncation,确保批量处理与单条处理一致
  • Save/Load roundtrip 确保序列化不丢失信息(vocab、special tokens、lowercasing 配置等)
  • 注意:tokenizer 测试使用 google/siglip2-base-patch16-224(固定 224x224 尺寸),而模型测试使用 google/siglip2-base-patch16-naflex(灵活分辨率)。两者 tokenizer 行为一致,差异仅在 vision encoder 的分辨率处理

跨文件架构总结

Siglip2 核心设计决策

决策体现位置理由
灵活分辨率 (Naflex)test_modeling_siglip2.pyspatial_shapes 参数传统 CLIP 固定 224x224 输入限制了对非方形/高分辨率图像的利用。Siglip2 通过 spatial_shapes + pixel_attention_mask 支持自适应 patch 数量
Dual Encoder 架构test_modeling_siglip2.pySiglip2Model = Vision + Text复用 CLIP 的双塔设计,但改用 Sigmoid Loss 替代 Softmax CE
Lowercasing Tokenizertest_tokenization_siglip2.py减少词汇量,提高文本编码的泛化性
Patch + Pooling 输出test_modeling_siglip2.pypooler_output shapeVision 和 Text 编码器都输出 pooler_output(而非仅用 last_hidden_state pooling),通过 MultiheadAttentionPoolingHead 聚合
注意力实现可替换Siglip2ModelTesterMixin — SDPA/Flash Attn 测试模型设计为注意力实现无关,eager/sdpa/flash_attention_2 均可运行
残缺点但可用大量 @unittest.skipoutput_attentionsoutput_hidden_states 等功能尚未实现,gradient checkpointing 在分类头有已知 bug

测试覆盖矩阵

被测组件测试文件关键验证
Tokenizertest_tokenization_siglip2.pyLowercasing, padding, save/load
Image Processortest_image_processing_siglip2.pyProperties, from_dict, patchify config
Vision Modeltest_modeling_siglip2.pyForward shape, SDPA/Flash Attn, pretrained loading
Text Modeltest_modeling_siglip2.pyForward shape, SDPA/Flash Attn, pretrained loading
Combined Modeltest_modeling_siglip2.pyLogits shape, config decomposition, SDPA/Flash Attn
Classification Headtest_modeling_siglip2.pyVision-only forward, xfail gradient checkpointing
End-to-Endtest_modeling_siglip2.pyReal model + real images, exact logit matching

测试参数配置速查

参数Vision (小测试)Text (小测试)真实模型 (base)
hidden_size6464768
num_hidden_layers2212
num_attention_heads4412
patch_size2N/A16
max_num_patchesN/A (seq_length=24)N/A256
max_position_embeddingsN/A51264
vocab_sizeN/A99 (mock)~32000

数据流全貌

[Image]                               [Text]
   |                                     |
   v                                     v
ImageProcessor                      Tokenizer
   |  (resize, rescale,                 |  (lowercase,
   |   normalize, patchify)             |   tokenize,
   |                                    |   pad/truncate)
   v                                    v
pixel_values                        input_ids
pixel_attention_mask                attention_mask
spatial_shapes
   |                                     |
   v                                    v
VisionModel                         TextModel
   |  (ViT Encoder +                    |  (Transformer +
   |   Attention Pooling)               |   Pooling)
   v                                    v
image_embeds                        text_embeds
   |  (batch, hidden_size)              |  (batch, hidden_size)
   |                                     |
   +------------+   +-------------------+
                |   |
                v   v
           logits = image @ text.T * temperature
                |
                v
        logits_per_image (I, T)
        logits_per_text  (T, I)
                |
                v
         Sigmoid Loss (per-element)

与 CLIP 的关键差异

维度CLIPSiglip2
固定分辨率是 (224x224 或 336x336)否 (naflex: 灵活分辨率)
空间信息隐式 (固定 patch grid)显式 (spatial_shapes tensor)
Patch maskpixel_attention_mask
LossSoftmax Cross-EntropySigmoid Loss
Attention Pooling Head标准线性投影MultiheadAttentionPoolingHead
Lowercasing
模型名openai/clip-vit-base-patch32google/siglip2-base-patch16-naflex

关键算法实现细节

1. 图像 Patch 化与输出 Shape

Siglip2 图像处理的核心公式:

给定图像 I ∈ R^{H x W x 3}
patch_size = 16
num_patches = floor(H / 16) * floor(W / 16)  (最多 256)
每个 patch flatten 为 16 * 16 * 3 = 768 维向量
输出 shape: (num_patches, 768)
若 num_patches < max_num_patches,padding 到 (256, 768)

测试中验证:expected_output_image_shape = (max_num_patches, patch_size * patch_size * num_channels) = (256, 768)

2. spatial_shapes 与 pixel_attention_mask 的配合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# spatial_shapes: (batch_size, 2)  — 每个样本的 (height, width)
# pixel_attention_mask: (batch_size, max_num_patches)

# 对于 height=4, width=6 的图像(共 24 个 patch):
spatial_shapes[i] = (4, 6)
pixel_attention_mask[i] = [1,1,1,..,1,1, 0,0,..,0]
                            +-- 24  --+  +- 232  -+

# 当 batch 内存在不同分辨率图像时
# model 内部使用 spatial_shapes 来 reshape 1D → 2D → compute position encodings
# pixel_attention_mask 确保 padding 区域不计入 attention

3. Sigmoid Loss (对比 Softmax CE)

Siglip2 的核心算法创新在于 Loss 函数。虽然测试文件不直接测试 loss 计算,但从 logits_per_image (I, T) 和 logits_per_text (T, I) 的输出 shape 可以推断:

# CLIP: Softmax CE Loss
#   loss_img = CE(softmax(logits_per_image, dim=1), targets)
#   loss_txt = CE(softmax(logits_per_text, dim=1), targets)

# Siglip2: Sigmoid Loss
#   对 logits 矩阵的每个 (i,j) 元素:
#     target = 1 if i == j else -1
#     loss = -log(sigmoid(target * logits[i,j]))
#   优点:不再受 batch size 约束,可利用大量负样本,训练更稳定

logits_per_imagelogits_per_text 互为转置,两者同时输出是为了对称性。

4. MultiheadAttentionPoolingHead 的卸载问题

Siglip2VisionModelTest 中:

1
2
3
test_cpu_offload = False
test_disk_offload_safetensors = False
# TODO: preload_module_classes = ["Siglip2MultiheadAttentionPoolingHead"]

这个 Head 使用多头注意力来聚合 patch 序列为单个向量表示(替代 CLIP 的简单 mean/CLS pooling)。由于内部使用了 nn.MultiheadAttention,在 device_map="auto" / cpu_offload 场景下可能出现模块卸载不正确的问题。解决方案是在 dispatch_model 时预加载该模块。

5. Gradient Checkpointing 兼容性

1
2
@pytest.mark.xfail(reason="This architecture seems to not compute gradients for some layer.")
def test_training_gradient_checkpointing(self):

Siglip2ForImageClassification 中启用 gradient checkpointing 时,某层(可能是 Vision Transformer 的注意力或 MLP)的梯度未正确回传。这可能是由于:

  • 自定义 attention 实现与 checkpoint 的 use_reentrant=False 不兼容
  • MultiheadAttentionPoolingHead 在中间激活值丢弃后无法正确重建

标记为 xfail 表示这是已知问题但暂不修复。


测试信息的业务价值

对模型用户

问题答案(从测试推断)
支持哪些分辨率?naflex 变体支持任意分辨率(max 256 patches);224 变体固定 224x224
Tokenizer 如何处理大小写?默认 lowercase,大小写输入等价
支持 Flash Attention 吗?支持,且与 eager 模式输出一致(atol=4e-2)
能用 gradient checkpointing 吗?Vision encoder 训练可以,分类头有 bug
能输出 attention 权重吗?当前不支持
图像预处理流程?resize(BILINEAR) → rescale(1/255) → normalize(mean=0.5, std=0.5) → patchify

对贡献者

  • 修改 vision/text model 时:必须通过 Siglip2ModelTesterMixin 中的 SDPA/Flash Attn 测试
  • 修改 image processor 时:max_num_patches=256patch_size=16 是硬约束
  • 修改 tokenizer 时:lowercasing 行为不能变更
  • 修改 config 序列化时:test_load_vision_text_config 必须通过

附录:SDPA 与 Flash Attention 算法详解

概述

SDPA 和 Flash Attention 都是 Transformer 中 Multi-Head Attention 的高效实现算法。它们计算相同的数学结果,但在内存使用和计算速度上有巨大差异。Siglip2 测试文件中的 Siglip2ModelTesterMixin 专门验证模型在 eager、sdpa、flash_attention_2 三种后端下行为一致。


SDPA (Scaled Dot-Product Attention)

本质:PyTorch 2.0 内置的融合注意力算子 torch.nn.functional.scaled_dot_product_attention

它不是新算法,而是工程优化——将原本分散的多个操作(QK^T、softmax、乘以 V)融合成一个 CUDA kernel,避免中间矩阵反复在 GPU 显存和计算单元之间搬运。

传统实现(多个 kernel 调用):
  scores = Q @ K.T           # kernel 1: 写回 HBM
  scores = scores / sqrt(d)   # kernel 2: 读 -> 算 -> 写
  attn   = softmax(scores)    # kernel 3: 读 -> 算 -> 写
  output = attn @ V           # kernel 4: 读 -> 算 -> 写

SDPA(融合 kernel):
  output = sdpa(Q, K, V)      # 一次 kernel 调用,中间结果不离开寄存器/SRAM

关键特性:PyTorch 的 SDPA 会根据输入自动选择最优后端:

优先级后端条件
1Flash Attention 2CUDA 可用,fp16/bf16,无自定义 mask,无 attn_mask dtype 不兼容
2Memory Efficient Attentionxformers 的 cutlass 实现,比 Flash Attn 慢但比朴素实现快
3朴素实现兜底,标准 math 实现

Flash Attention

本质:Stanford Tri Dao 提出的 I/O 感知精确注意力算法 (2022, FA1; 2023, FA2)。

核心洞察:Attention 的瓶颈不是计算量 (FLOPs),而是显存带宽。一个 (N, N) 的 attention 矩阵,在长序列时需要反复在 HBM (High Bandwidth Memory, 即 GPU 显存) 和计算单元之间搬运,严重拖慢速度。

HBM (显存): 大容量 (~80GB), 慢带宽 (~1.5 TB/s)
SRAM (片上): 小容量 (~20MB), 快带宽 (~19 TB/s)

传统做法:中间结果都在 HBM 上来回搬运 → 带宽是瓶颈
Flash Attention:中间结果全在 SRAM 上处理 → 省掉了大部分 HBM 读写

核心技巧一:平铺 (Tiling)

将 Q, K, V 分成小块 (tile),逐块加载到片上 SRAM 计算:

  +-----+-----+-----+
  | Q_1 | K_1 | V_1 |  Block 1: 只计算这一块
  +-----+-----+-----+      在 SRAM 内完成 QK^T -> softmax -> xV
  | Q_2 | K_2 | V_2 |  Block 2: 结果累加到输出
  +-----+-----+-----+

每一步:
  1. 从 HBM 加载 Q_block, K_block, V_block 到 SRAM
  2. 在 SRAM 内计算该块的 attention(不写回 HBM)
  3. 只把最终结果写回 HBM

关键效果:完整的 NxN attention 矩阵永远不会出现在 HBM 中

数值案例:假设 N=128K,N x N = 16B 元素,fp16 下约 32GB。传统实现需要这 32GB 矩阵完整存在于 HBM 中。Flash Attention 将其分割成小块后逐块处理,HBM 上只有最终输出 (N, d)

核心技巧二:Online Softmax(在线 Softmax)

分块计算 softmax 时,需要知道全局最大值来做数值稳定。传统做法是先扫一遍找最大值,再算 softmax(两次遍历)。Flash Attention 的在线 softmax 通过 running statistics 只需一次遍历:

传统 Stable Softmax:
  m = max(x_i)                       # 第一次遍历:找最大值
  s = sum(exp(x_i - m))              # 第二次遍历:算分母
  softmax(x_i) = exp(x_i - m) / s

Online Softmax (Flash Attention 用):
  对于每个新块:
    m_new = max(m_old, max(当前块))
    旧结果 *= exp(m_old - m_new)      # rescale:修正之前块的结果
    新贡献 = 当前块 * exp(当前块值 - m_new)
    结果 += 新贡献
  
  只需一次遍历,且空间复杂度 O(1)

Flash Attention 2 改进 (2023)

改进说明
减少非矩阵乘法操作FA1 中 non-matmul FLOPs 占比高,FA2 通过重排计算顺序降低
并行化策略调整分 sequence length 维度而非 batch/head 维度,提高 GPU 占用率
前向推理速度比 FA1 快约 2x

三种实现的对比

维度朴素 AttentionSDPA (auto)Flash Attention 2
显存复杂度O(N^2)O(N)O(N)
中间矩阵存放HBM (显存)寄存器 / 共享内存片上 SRAM
SRAM 利用率极低中等极高 (~19 TB/s)
N=128K 时的中间矩阵~32GB (可能 OOM)N/A (融合掉)N/A (从不落地)
PyTorch 调用手动实现F.scaled_dot_product_attention()SDPA 自动选或指定 attn_implementation="flash_attention_2"
精度fp32 全精度与后端一致fp16/bf16
自定义 mask 支持完全支持受限(取决于后端)有限

在 Siglip2 测试中的体现

test_sdpa_can_dispatch_composite_models

验证 Siglip2 的双塔结构(Vision + Text)能各自独立选择 SDPA 后端,save/load 后 _attn_implementation 属性正确恢复。关键校验逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 保存模型后重新加载(默认 SDPA)
model_sdpa = model_class.from_pretrained(tmpdirname)
# 强制加载为 eager
model_eager = model_class.from_pretrained(tmpdirname, attn_implementation="eager")

# 验证每个子模型的 config 中记录了正确的注意力实现
self.assertTrue(model_sdpa.vision_model.config._attn_implementation == "sdpa")
self.assertTrue(model_eager.vision_model.config._attn_implementation == "eager")

if hasattr(model_sdpa, "text_model"):
    self.assertTrue(model_sdpa.text_model.config._attn_implementation == "sdpa")
    self.assertTrue(model_eager.text_model.config._attn_implementation == "eager")

这确保了 Siglip2 的双塔各自可以灵活切换注意力后端而互不影响。

test_flash_attn_2_inference_equivalence

验证 Flash Attention 2 和 eager(朴素实现)产生数值等价的输出。核心代码:

1
2
3
4
5
6
7
# 分别用 eager 和 flash_attention_2 做前向推理
with torch.no_grad():
    outputs = model(**inputs_dict, output_hidden_states=True)
    outputs_fa = model_fa(**inputs_dict, output_hidden_states=True)

# 比较输出(logits 或 last_hidden_state)
torch.testing.assert_close(outputs[key], outputs_fa[key], atol=4e-2, rtol=4e-2)

容差为什么是 atol=4e-2

不同注意力实现的浮点累加顺序不同:

  • softmax 涉及 exp 求和,结果对累加顺序敏感
  • Flash Attention 中 tiling 改变了累加顺序
  • 这些微观差异在 fp16 下累积,但宏观语义等价

padding 区域的特殊处理

1
2
3
4
5
# pixel_attention_mask 位置在两种实现下数值不同
# (padding 区域 softmax 分母计算有差异)
if key == "last_hidden_state" and "pixel_attention_mask" in inputs_dict:
    output_tensor = output_tensor * inputs_dict["pixel_attention_mask"][..., None]
    output_tensor_fa = output_tensor_fa * inputs_dict["pixel_attention_mask"][..., None]

Flash Attention 对 mask 的处理与 eager 不同:padding 位置的 softmax 计算可能有微小差异。测试通过乘以 pixel_attention_mask 排除这些位置,确保只在有效 patch 上比较。

训练模式验证

1
2
3
# 训练模式 + dropout: 验证 flash attention 的 dropout 也能正常工作
model.train()
_ = model_fa(**inputs_dict, output_hidden_states=True)

这确保 Flash Attention 的 dropout 模式(random mask pattern)与 eager 的语义一致,不会在训练时产生 NaN 或异常。


一句话总结

SDPA 是 PyTorch 的"自动选最优"融合接口——它不是一个算法,而是一个智能调度器。Flash Attention 是具体的"把显存带宽省到极致"的算法——通过 tiling + online softmax,让完整的 NxN attention 矩阵永远不出现在显存中。Siglip2 测试确保两种(以及 eager 朴素实现)都能正常工作且语义等价。


附录二:四文件变量全量词典

按文件 -> 类 -> 变量的层级,逐一解释每个变量的含义、默认值选择原因、对应模型组件的角色。


文件一:test_image_processing_siglip2.py 变量

Siglip2ImageProcessingTester.__init__

变量默认值类型含义与用途
parent(必传)unittest.TestCase指向调用此 Tester 的测试用例实例,用于 self.parent.assertEqual(...) 等断言
batch_size7int每次测试生成的图像 batch 大小。值 7 是质数,避免对称 batch size 带来的虚假通过
num_channels3int图像通道数,RGB = 3。Siglip2 只接受 RGB 输入,4 通道测试被显式跳过
image_size18int测试用图像的基础尺寸(像素)。18 不是 16 的整数倍,用于验证 processor 对非对齐尺寸的处理
min_resolution30int随机生成测试图像的最小边长。30 < 2 * 16 = 32,用于验证极小图的处理
max_resolution400int随机生成测试图像的最大边长。400 > 16 * 16 = 256 (max patches 对应 4096x4096),覆盖边界场景
do_resizeTruebool是否启用 resize 步骤。控制 pipeline 中的第一步开关
size{"height": 18, "width": 18}dictresize 的目标尺寸。当 do_resize=True 时生效,将图像缩放至此尺寸
do_rescaleTruebool是否启用像素值重缩放。控制 pipeline 中的第二步开关
rescale_factor1 / 255float重缩放因子。将像素值从 [0, 255] 除以 255 映射到 [0, 1]。来自 CLIP 的标准预处理
do_normalizeTruebool是否启用归一化。控制 pipeline 中的第三步开关
image_mean[0.5, 0.5, 0.5]list[float]各通道的均值,用于 (x - mean) / std。将 [0, 1] 中心化到 [-1, 1]
image_std[0.5, 0.5, 0.5]list[float]各通道的标准差。与 mean=[0.5, 0.5, 0.5] 配合,将 [0,1] -> [-1,1]
resampleImage.Resampling.BILINEARPIL enumresize 时的插值算法。BILINEAR 是 CV 领域标准选择,平衡速度与质量
patch_size16intViT 的 patch 边长。将图像划分为 16x16 的小块。值为 16 是 ViT 论文的经典选择(1/14, 1/16 是两种主流)
max_num_patches256int每张图像最多保留的 patch 数量。这是 Siglip2 Naflex 的核心参数:一张图最多切 256 个 patch。256 个 16x16 的 patch 最多覆盖 4096 个像素维度(如 256x256 需 16x16=256 patch)

Siglip2ImageProcessingTester 方法参数

变量默认值含义与用途
equal_resolution (in prepare_image_inputs)False是否强制所有测试图像使用相同分辨率。False 模拟真实场景中不同尺寸/宽高比的图像
numpify (in prepare_image_inputs)False是否将输出转为 NumPy 数组。控制数据格式切换
torchify (in prepare_image_inputs)False是否将输出转为 PyTorch Tensor。与 numpify 互斥

Siglip2ImageProcessingTest 类级别变量

变量含义与用途
self.image_processor_testerSiglip2ImageProcessingTester(self)测试数据工厂实例,在 setUp() 中创建,每个测试方法前重新初始化
self.image_processor_dict(property)委托给 image_processor_tester.prepare_image_processor_dict() 的属性,返回完整的 image processor 配置字典

文件二:test_modeling_siglip2.py 变量

Siglip2ModelTesterMixin 内部变量

变量默认值出现位置含义与用途
dtypetorch.float16test_flash_attn_2_inference_equivalenceFlash Attention 限制必须使用 fp16/bf16,不可用 fp32
key动态选择test_flash_attn_2_inference_equivalence选择比较哪个输出 key。优先级:logits > logits_per_image > last_hidden_state。根据模型类型自动适配
atol4e-2test_flash_attn_2_inference_equivalence绝对容差。值 0.04 较宽松,因为 flash attn 与 eager 的浮点累加顺序不同,在 fp16 下误差累积
rtol4e-2test_flash_attn_2_inference_equivalence相对容差。与 atol 配合使用,确保语义等价而非逐位精确
tmpdirnametempfile.TemporaryDirectory()多处临时目录,用于 save/load 测试后自动清理

Siglip2VisionModelTester.__init__

变量默认值类型含义与用途
parent(必传)unittest.TestCase指向调用此 Tester 的测试用例实例
batch_size12intVision 模型测试的 batch 大小。12 是 3 和 4 的最小公倍数,方便验证 attention head 的整除性
num_patches16intconfig 中声明的 patch 数量占位。注意:实际序列长度由 image_num_patches 决定,此值用于 config 初始化而非输入生成
image_num_patches24int实际的序列长度(= seq_length)。Vision 模型接收的 patch 数量。24 方便构造多种 (height, width) 组合:1x24, 2x12, 3x8, 4x6 等
patch_size2int测试用 patch 边长。仅 2(而非真实模型的 16),因为 patch_dim = 3 * 2 * 2 = 12,极小的向量便于快速测试
num_channels3int图像通道数,固定为 RGB=3
is_trainingTruebool模型是否处于训练模式。影响 dropout 和 batch norm 行为
hidden_size64intTransformer 隐藏层维度。仅为真实模型 768 的 1/12,大幅降低测试的计算开销
num_hidden_layers2intTransformer 层数。仅为真实模型的 2/12,最小可验证深度堆叠行为的层数
num_attention_heads4int注意力头数。4 个头确保 hidden_size // num_heads = 64 // 4 = 16 可整除
intermediate_size37intFFN 中间层维度。37 是质数,确保不因特殊数值巧合通过测试
dropout0.1float全连接层 dropout 比率。标准 Transformer 配置
attention_dropout0.1float注意力权重 dropout 比率。标准 Transformer 配置
initializer_range0.02float权重初始化的标准差。BERT/ViT 的经典值,源自 Xavier 初始化
scopeNonestr or None变量作用域前缀(旧版 TF 遗留)。PyTorch 下无用,保持 API 兼容
seq_length=image_num_patchesint模型接收的序列长度。等于 image_num_patches,是它的语义化别名

Siglip2VisionModelTester.prepare_config_and_inputs 局部变量

变量shape / 含义用途
pixel_values(batch_size, seq_length, patch_dim)已 patchify 的图像输入。每行是一个 flatten 的 patch 向量
pixel_attention_mask(batch_size, seq_length)标记哪些 patch 有效 (1) 哪些是 padding (0)。由 spatial_shapes 决定
spatial_shapes(batch_size, 2)每张图的 patch 网格高度和宽度。例如 (4, 6) 表示 4 行 6 列共 24 个 patch

spatial_shapes 的生成逻辑:枚举所有 (h, w) 组合满足 h * w <= seq_length(24),每个 batch 样本随机分配一个组合。这确保测试覆盖不同宽高比(1x24, 2x12, 3x8, 4x6, 6x4, 8x3, 12x2, 24x1)。

Siglip2VisionModelTest 类级别变量

变量含义与用途
all_model_classes(Siglip2VisionModel,)基类 Mixin 据此知道要测试哪些模型类。这里仅测试 Vision 塔
additional_model_inputs["pixel_attention_mask", "spatial_shapes"]告诉基类:你的 forward 除了 pixel_values 还接受这两个额外参数。基类的通用测试会根据此列表构造 inputs_dict
test_resize_embeddingsFalse禁用 embeddings resize 测试。Vision Transformer 无 token embeddings,不需要 resize 功能
test_cpu_offloadFalse禁用 CPU 卸载测试。原因:MultiheadAttentionPoolingHeaddevice_map="auto" 时卸载异常,已知 bug 待修复
test_disk_offload_safetensorsFalse禁用磁盘卸载测试(safetensors 格式)。同上原因
test_disk_offload_binFalse禁用磁盘卸载测试(bin 格式)。同上原因

Siglip2TextModelTester.__init__

变量默认值类型含义与用途
parent(必传)unittest.TestCase指向调用此 Tester 的测试用例实例
batch_size12intText 模型测试的 batch 大小。与 Vision 保持一致
seq_length7int文本序列长度。7 是质数且足够短,快速测试 padding/truncation 行为
is_trainingTruebool模型是否处于训练模式
use_input_maskTruebool是否生成 attention_mask。控制 random_attention_mask 的创建
use_labelsTruebool是否生成标签(用于 loss 计算)。Text 模型本身无 loss,此值在联合模型中生效
vocab_size99int词汇表大小(测试用)。远小于真实 ~32000,降低 embedding 矩阵尺寸
hidden_size64intTransformer 隐藏层维度。与 Vision 保持一致
num_hidden_layers2intTransformer 层数。与 Vision 保持一致
num_attention_heads4int注意力头数。64 / 4 = 16 可整除
intermediate_size37intFFN 中间层维度。37 是质数,与 Vision 一致
dropout0.1float全连接层 dropout 比率
attention_dropout0.1float注意力权重 dropout 比率
max_position_embeddings512int最大位置编码数。定义文本塔的最长序列长度。512 是 CLIP 文本塔的标准值
initializer_range0.02float权重初始化标准差
scopeNonestr or None变量作用域前缀(旧版 TF 遗留)

Siglip2TextModelTester.prepare_config_and_inputs 局部变量

变量shape / 含义用途
input_ids(batch_size, seq_length)随机 token ID 序列。值域 [0, vocab_size)
input_mask(batch_size, seq_length)Attention mask。每个样本随机截断到不同长度。rnd_start_indices 决定每个样本的有效 token 数:input_mask[b, :start] = 1, input_mask[b, start:] = 0

rnd_start_indices[1, seq_length-1] 区间均匀随机,使得 batch 内序列长度各不相同,模拟真实 batching 场景的 padding 需求。

Siglip2TextModelTest 类级别变量

变量含义与用途
all_model_classes(Siglip2TextModel,)仅测试 Text 塔
test_resize_embeddingsFalse禁用 embeddings resize 测试。虽然文本模型有 embeddings,但 Siglip2 的联合训练模式下 resize 不适用
model_split_percents[0.5, 0.8, 0.9]模型分割点比例。用于 device_map 测试:将模型按 50%、80%、90% 的比例分配到不同设备。覆盖浅层/深层分割场景

Siglip2ModelTester.__init__

变量默认值类型含义与用途
parent(必传)unittest.TestCase指向调用此 Tester 的测试用例实例
text_kwargsNone (→ {})dict or None传递给 Siglip2TextModelTester 的参数覆盖。None 时使用全部默认值
vision_kwargsNone (→ {})dict or None传递给 Siglip2VisionModelTester 的参数覆盖。None 时使用全部默认值
is_trainingTruebool联合模型的训练模式标志

Siglip2ModelTest 类级别变量

变量含义与用途
all_model_classes(Siglip2Model,)测试联合模型(Vision + Text)
pipeline_model_mapping{"feature-extraction": Siglip2Model}告诉 Pipeline 测试框架:Siglip2Model 可被当作 feature-extraction pipeline 使用。图像/文本特征提取
additional_model_inputs["pixel_values", "pixel_attention_mask", "spatial_shapes"]联合模型的额外输入列表。注意不含 input_idsattention_mask(那些是文本塔标准输入,基类自动处理)
test_resize_embeddingsFalse联合模型不需要 resize
test_attention_outputsFalse关键:Siglip2 当前不支持 output_attentions=True。设为 False 跳过相关测试
test_cpu_offloadFalse与 Vision 塔相同原因:MultiheadAttentionPoolingHead 卸载 bug
test_disk_offload_safetensorsFalse同上
test_disk_offload_binFalse同上
_is_compositeTrue关键:声明这是复合模型(包含子模型)。触发基类 ModelTesterMixin 中的复合模型特殊测试逻辑。如:test_sdpa_can_dispatch_composite_models 会检查 vision 和 text 子模型的注意力实现

Siglip2ForImageClassificationModelTest 类级别变量

变量含义与用途
all_model_classes(Siglip2ForImageClassification,)测试图像分类头
pipeline_model_mapping{"image-classification": Siglip2ForImageClassification}可作为 image-classification pipeline 使用
additional_model_inputs["pixel_values", "pixel_attention_mask", "spatial_shapes"]仅含视觉输入(无文本)
test_resize_embeddingsFalse不需要 resize
test_attention_outputsFalse不支持 attention 输出
test_cpu_offloadFalse同 Vision 塔原因
test_disk_offload_safetensorsFalse同上
test_disk_offload_binFalse同上
_is_compositeTrue分类头包含 Vision 子模型,属于复合模型

prepare_images() 函数局部变量

变量含义与用途
shapes[(224, 224), (1024, 1024), (224, 1024)]三张测试图像的尺寸。覆盖正方形(1:1)、大正方形、长方形(高瘦型)。224=16x14 是 CLIP 标准尺寸,1024=16x64 是 Naflex 支持的大尺寸
height / width遍历 shapes当前图像的尺寸
imageImage.new("RGB", (width, height), color="red")创建纯红色背景的 PIL 图像
drawImageDraw.Draw(image)PIL 绘图上下文
center_x / center_ywidth // 2 / height // 2圆心坐标 = 图像中心
radiusmin(center_x, center_y) // 8 * 7圆半径 = 短边的 7/8。确保圆完全在图像内,不同尺寸下比例一致
fill / outline / width"blue" / "green" / image.width // 20椭圆填充色(蓝)、边框色(绿)、边框粗细。生成 红底 + 蓝圆 + 绿边 的标准测试图

Siglip2ModelIntegrationTest.test_inference 局部变量

变量值/类型含义与用途
model_name"google/siglip2-base-patch16-naflex"预训练模型的 HuggingFace Hub ID。使用 naflex 变体而非固定尺寸版本
modelSiglip2Model 实例加载的预训练模型,移至 torch_device
processorSiglip2Processor 实例同时处理图像和文本的联合 processor
imageslist[Image] (3 张)prepare_images() 生成的测试图
textlist[str] (7 条)测试文本。语义梯度设计:精准描述 > 部分描述 > 模糊描述 > 错误描述
inputsdict[str, Tensor]processor 输出的编码后输入。return_tensors="pt" 指定 PyTorch 格式
outputsSiglip2ModelOutput模型前向传播输出。含 logits_per_imagelogits_per_text
logits_per_image(3, 7) tensor每张图像与 7 条文本的相似度得分。每行是一个图像对所有文本的 logits
logits_per_text(7, 3) tensor每条文本与 3 张图像的相似度得分。每行是一个文本对所有图像的 logits
expected_logits_per_textsExpectations 对象平台相关的期望 logits 值。键 ("cuda", None) 匹配所有 CUDA 设备,("rocm", (9, 5)) 匹配 ROCm 特定版本,("xpu", 3) 匹配 Intel GPU
EXPECTED_LOGITS_PER_TEXTTensor (7, 3)Expectations 提取的当前平台期望值
rtol / atol1e-3 / 1e-3集成测试的容差。比 flash attn 测试(4e-2)严格 40 倍,因为 eager 模式的结果应高度可复现

期望 logits 矩阵的语义解读

EXPECTED_LOGITS_PER_TEXT shape 为 (7, 3),7 条文本对 3 张图像:

文本 (行) \ 图像 (列)img0 (224x224 方)img1 (1024x1024 大)img2 (224x1024 高)解读
"circle"1.02-0.03-1.45方形图匹配最高(简单描述偏好方形图)
"ellipsoid"-4.54-6.23-1.57全部低分,因为画的是正圆而非椭圆
"blue circle on red background"4.185.043.52精确描述,全部高分。大图略高(更多细节)
"blue circle with green border..."9.4310.196.34最精确描述,最高分。绿边是区分性特征
"green circle on red background"2.443.114.55颜色错误(绿而非蓝),分数下降
"a dog"-12.32-13.74-13.46完全无关,极低分
"a blue dog with a green border..."1.151.17-1.96颜色对但物体错,低分

文件三:test_tokenization_siglip2.py 变量

Siglip2TokenizerTest 类级别变量

变量含义与用途
from_pretrained_id"google/siglip2-base-patch16-224"加载此预训练模型的 tokenizer。注意:tokenizer 测试用 patch16-224(固定 224 尺寸),模型测试用 patch16-naflex(灵活尺寸)。两者 tokenizer 完全一致

Siglip2TokenizerTest.test_tokenizer 局部变量

变量值/类型含义与用途
tokenizerSiglip2Tokenizer 实例从 HuggingFace Hub 加载的预训练 tokenizer
texts_uclist[str] (5 条)大写 / 混合大小写测试文本。覆盖:全大写、多余空格、特殊字符(带变音符号的 u)、地名、混合大小写 + 数字
texts_lclist[str] (5 条)texts_uc.lower() 版本。与 texts_uc 一一对应
enc_uc / enc_lcBatchEncoding单条编码结果。验证 enc_uc["input_ids"] == enc_lc["input_ids"],即大小写无关
batch_uc / batch_lcBatchEncoding批量编码结果。验证批量路径与单条路径行为一致
max_len64padding 的目标长度。64 远大于 5 条测试文本的自然 token 数,确保所有序列都经历 padding 而非 truncation
paddedBatchEncodingpadding="max_length" + truncation=True + max_length=64 的编码结果。验证每条序列长度恰好为 64
tmpdirtempfile.TemporaryDirectory()保存 tokenizer 的临时目录
tokenizer_reloadedSiglip2Tokenizer 实例tmpdir 重新加载的 tokenizer。验证 roundtrip 不变性
batch_uc_2 / batch_lc_2BatchEncoding重载后 tokenizer 的编码结果。与原始结果逐元素比较
padded_2BatchEncoding重载后 tokenizer 的 padded 编码结果。验证 padding 行为不变

文件四:__init__.py 变量

无变量。空文件仅作为 Python 包标记。


变量间的依赖关系

Image Processor 变量:
  patch_size=16  ────>  expected_output_image_shape = (max_num_patches, 16*16*3)
  max_num_patches=256  ──┘                      = (256, 768)
  rescale_factor=1/255 ──> pixel_values in [0, 1]
  image_mean=[0.5,0.5,0.5] + image_std=[0.5,0.5,0.5] ──> pixel_values in [-1, 1]

Vision Model 变量:
  patch_size=2 (test) ──>  patch_dim = 3 * 2 * 2 = 12
  image_num_patches=24 ──>  seq_length = 24, spatial_shapes 可构造 8 种 (h,w) 组合
  hidden_size=64 ──>  pooler_output.shape = (batch, 64)
  num_attention_heads=4 ──>  head_dim = 64 / 4 = 16
  intermediate_size=37 ──>  FFN 维度 = 37 (质数, 无巧合通过)

Text Model 变量:
  vocab_size=99 ──>  embedding 表尺寸 (99, 64)
  seq_length=7 ──>  短序列, 快速测试
  max_position_embeddings=512 ──>  最大文本长度上限
  hidden_size=64 ──>  与 Vision 塔对齐 (pooler_output 维度一致)
  model_split_percents=[0.5, 0.8, 0.9] ──>  device_map 测试的三种分割策略

Combined Model 变量:
  _is_composite=True ──>  触发复合模型特殊测试
  test_attention_outputs=False ──>  跳过 attention 输出测试 (功能未实现)
  test_cpu_offload=False ──>  跳过卸载测试 (MultiheadAttentionPoolingHead bug)

Tokenizer 变量:
  from_pretrained_id="...patch16-224" ──>  使用固定尺寸模型的 tokenizer
  max_len=64 ──>  远超测试文本长度, 纯 padding 场景

Integration Test 变量:
  shapes = [(224,224), (1024,1024), (224,1024)] ──>  覆盖 1:1 和不同宽高比
  model_name="...naflex" ──>  使用灵活分辨率模型
  atol=1e-3 (严格) vs model test atol=4e-2 (宽松) ──>  集成测试是 eager 模式, 误差应更小