From 54f31c0217a0ef6421620dded1523b2141e7945d Mon Sep 17 00:00:00 2001 From: wangwl Date: Wed, 24 Dec 2025 01:45:43 +0000 Subject: [PATCH 1/4] add SigLIP2 --- .../SigLIP2/SigLIP2-PyTorch/LICENSE | 21 + .../SigLIP2/SigLIP2-PyTorch/README.md | 5 + .../SigLIP2/SigLIP2-PyTorch/requirements.txt | 3 + .../SigLIP2/SigLIP2-PyTorch/siglip2.py | 2033 ++++++++++++++++ .../SigLIP2/configuration_siglip2.py | 185 ++ .../Classification/SigLIP2/coverage.txt | 3 + PyTorch/build-in/Classification/SigLIP2/run | 1 + .../Classification/SigLIP2/siglip2.py | 325 +++ .../Classification/SigLIP2/siglip2SDAA.py | 315 +++ .../Classification/SigLIP2/siglip2_loss.jpg | Bin 0 -> 36902 bytes .../Classification/SigLIP2/siglip2_loss.txt | 29 + .../Classification/SigLIP2/siglip2_origin.py | 2164 +++++++++++++++++ .../Classification/SigLIP2/weloTrainStep.py | 693 ++++++ 13 files changed, 5777 insertions(+) create mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE create mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md create mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt create mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py create mode 100644 PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py create mode 100644 PyTorch/build-in/Classification/SigLIP2/coverage.txt create mode 100644 PyTorch/build-in/Classification/SigLIP2/run create mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2.py create mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py create mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg create mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt create mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py create mode 100644 PyTorch/build-in/Classification/SigLIP2/weloTrainStep.py diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE new file mode 100644 index 000000000..0ad01cf22 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Yuan-Man + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md new file mode 100644 index 000000000..349224070 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md @@ -0,0 +1,5 @@ +# SigLIP 2 PyTorch + +PyTorch implementation of SigLIP 2. + +[SigLIP 2](https://arxiv.org/abs/2502.14786): Multilingual Vision-Language Encoders with Improved Semantic Understanding, Localization, and Dense Features. diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt new file mode 100644 index 000000000..f4959db67 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt @@ -0,0 +1,3 @@ +numpy +torch +dataclasses diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py new file mode 100644 index 000000000..2b1b9ece9 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py @@ -0,0 +1,2033 @@ +import math +import warnings +from dataclasses import dataclass +from typing import Any, Optional, Tuple, Union + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss +from torch.nn.init import _calculate_fan_in_and_fan_out + +from configuration_siglip2 import Siglip2Config, Siglip2TextConfig, Siglip2VisionConfig + + +@dataclass +class Siglip2VisionOutput(): + """ + 视觉模型的输出基类,包含最后一层隐藏状态的池化结果得到的图像嵌入。 + + Args: + image_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): + 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的图像嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 图像嵌入,用于表示图像的特征。 + + last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): + 模型最后一层输出的隐藏状态序列。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, sequence_length, hidden_size)` + - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 + + hidden_states (tuple(torch.FloatTensor), 可选): + 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 + - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 + + attentions (tuple(torch.FloatTensor), 可选): + 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 + - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 + """ + + image_embeds: Optional[torch.FloatTensor] = None + last_hidden_state: torch.FloatTensor = None + hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None + attentions: Optional[Tuple[torch.FloatTensor, ...]] = None + + +@dataclass +class Siglip2TextOutput(): + """ + 文本模型的输出基类,包含最后一层隐藏状态的池化结果得到的文本嵌入。 + + Args: + text_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): + 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的文本嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 文本嵌入,用于表示文本的特征。 + + last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): + 模型最后一层输出的隐藏状态序列。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, sequence_length, hidden_size)` + - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 + + hidden_states (tuple(torch.FloatTensor), 可选): + 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 + - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 + + attentions (tuple(torch.FloatTensor), 可选): + 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 + - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 + """ + + text_embeds: Optional[torch.FloatTensor] = None + last_hidden_state: torch.FloatTensor = None + hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None + attentions: Optional[Tuple[torch.FloatTensor, ...]] = None + + +@dataclass +class Siglip2Output(): + """ + Siglip2 模型的输出,包含图像-文本对比损失、相似度分数、嵌入以及子模型的输出。 + + Args: + loss (torch.FloatTensor, 可选, 形状为 `(1,)`): + 当 `return_loss=True` 时返回,用于图像-文本相似度的对比损失。 + - **类型**: `torch.FloatTensor` + - **形状**: `(1,)` + - **说明**: 对比损失,用于衡量图像和文本之间的相似度。 + + logits_per_image (torch.FloatTensor, 必填, 形状为 `(image_batch_size, text_batch_size)`): + `image_embeds` 和 `text_embeds` 之间的缩放点积分数,表示图像-文本相似度分数。 + - **类型**: `torch.FloatTensor` + - **形状**: `(image_batch_size, text_batch_size)` + - **说明**: 图像-文本相似度分数,用于评估图像和文本之间的匹配程度。 + + logits_per_text (torch.FloatTensor, 必填, 形状为 `(text_batch_size, image_batch_size)`): + `text_embeds` 和 `image_embeds` 之间的缩放点积分数,表示文本-图像相似度分数。 + - **类型**: `torch.FloatTensor` + - **形状**: `(text_batch_size, image_batch_size)` + - **说明**: 文本-图像相似度分数,用于评估文本和图像之间的匹配程度。 + + text_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 文本嵌入,用于表示文本的特征。 + + image_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 图像嵌入,用于表示图像的特征。 + + text_model_output (BaseModelOutputWithPooling): + [`Siglip2TextModel`] 的输出。 + - **类型**: `BaseModelOutputWithPooling` + - **说明**: 包含文本模型的详细输出信息,如隐藏状态等。 + + vision_model_output (BaseModelOutputWithPooling): + [`Siglip2VisionModel`] 的输出。 + - **类型**: `BaseModelOutputWithPooling` + - **说明**: 包含视觉模型的详细输出信息,如隐藏状态等。 + """ + + loss: Optional[torch.FloatTensor] = None + logits_per_image: torch.FloatTensor = None + logits_per_text: torch.FloatTensor = None + text_embeds: torch.FloatTensor = None + image_embeds: torch.FloatTensor = None + text_model_output: BaseModelOutputWithPooling = None + vision_model_output: BaseModelOutputWithPooling = None + + def to_tuple(self) -> Tuple[Any]: + """ + 将 Siglip2Output 对象转换为元组。 + + Returns: + Tuple[Any]: 包含 Siglip2Output 对象的各个属性值的元组。 + """ + return tuple( + self[k] if k not in ["text_model_output", "vision_model_output"] else getattr(self, k).to_tuple() + for k in self.keys() + ) + + +class Siglip2VisionEmbeddings(nn.Module): + """ + Siglip2 视觉嵌入模块,用于将图像像素值转换为嵌入向量,并添加位置嵌入。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含模型的各种配置参数,如隐藏层大小、patch 大小等。 + """ + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # patch 大小,表示每个图像块的高度和宽度 + self.patch_size = config.patch_size + + # 定义一个线性层,用于将每个图像 patch(像素块)映射到嵌入向量 + self.patch_embedding = nn.Linear( + # 输入特征数:通道数 * patch大小平方 + in_features=config.num_channels * self.patch_size * self.patch_size, + # 输出特征数:嵌入维度 + out_features=self.embed_dim, + ) + + # 图像被分割成的总patch数量 + self.num_patches = config.num_patches + # 计算位置嵌入的网格大小(假设图像是正方形) + self.position_embedding_size = int(self.num_patches**0.5) + # 定义一个嵌入层,用于存储每个patch的位置嵌入 + self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) + + @staticmethod + def resize_positional_embeddings( + positional_embeddings: torch.Tensor, + spatial_shapes: torch.LongTensor, + max_length: int, + ) -> torch.Tensor: + """ + 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小。 + + Args: + positional_embeddings (`torch.Tensor`): + 位置嵌入张量,形状为 (高度, 宽度, 嵌入维度)。 + spatial_shapes (`torch.LongTensor`): + 空间形状张量,形状为 (batch_size, 2),用于调整位置嵌入的大小。 + 每个元素包含 [目标高度, 目标宽度]。 + max_length (`int`): + 填充后的最大长度,用于确保所有批次的位置嵌入具有相同的长度。 + + Returns: + `torch.Tensor`: + 调整大小并填充后的嵌入张量,形状为 (batch_size, max_length, 嵌入维度)。 + """ + # 获取批次大小 + batch_size = spatial_shapes.shape[0] + # 获取嵌入维度 + embed_dim = positional_embeddings.shape[-1] + # 记录原始数据类型 + source_dtype = positional_embeddings.dtype + + # 创建一个空的张量,用于存储调整后的位置嵌入 + resulted_positional_embeddings = torch.empty( + (batch_size, max_length, embed_dim), + device=positional_embeddings.device, + dtype=source_dtype, + ) + + # 将位置嵌入的维度顺序从 (高度, 宽度, 嵌入维度) 转换为 (嵌入维度, 高度, 宽度) 以便进行插值 + positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0) + + # 如果设备是 CPU,则将数据类型上转换为 float32,因为 CPU 不支持 bfloat16/float16 的 antialias + if positional_embeddings.device.type == "cpu": + positional_embeddings = positional_embeddings.to(torch.float32) + + for i in range(batch_size): + # 获取当前批次的目标高度和宽度 + # (1, dim, height, width) -> (1, dim, target_height, target_width) + height, width = spatial_shapes[i] + # 对位置嵌入进行双线性插值,调整到目标尺寸 + resized_embeddings = F.interpolate( + positional_embeddings, + size=(height, width), + mode="bilinear", + align_corners=False, + antialias=True, + ) + + # 将调整后的嵌入形状从 (1, 嵌入维度, 高度, 宽度) 转换为 (高度 * 宽度, 嵌入维度) + # (1, dim, target_height, target_width) -> (target_height * target_width, dim) + resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1) + + # 将数据类型转换回原始类型 + resized_embeddings = resized_embeddings.to(source_dtype) + + # 将调整后的嵌入填充到结果张量中 + resulted_positional_embeddings[i, : height * width] = resized_embeddings + # 对于不足的部分,用第一个位置的嵌入填充 + resulted_positional_embeddings[i, height * width :] = resized_embeddings[0] + + return resulted_positional_embeddings + + def forward(self, pixel_values: torch.FloatTensor, spatial_shapes: torch.LongTensor) -> torch.Tensor: + """ + 前向传播方法,用于生成图像嵌入。 + + Args: + pixel_values (`torch.FloatTensor`): + 像素值张量,形状为 (batch_size, 最大patch数量, 通道数 * patch大小平方)。 + spatial_shapes (`List[Tuple[int, int]]`): + 空间形状列表,形状为 (batch_size, 2),用于调整位置嵌入的大小。 + 每个元素包含 [高度, 宽度]。 + + Returns: + `torch.Tensor`: + 生成的图像嵌入张量,形状为 (batch_size, 最大patch数量, 嵌入维度)。 + """ + # 将像素值张量转换为与patch_embedding权重相同的类型 + target_dtype = self.patch_embedding.weight.dtype + patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype)) + + # 获取位置嵌入,并调整其形状为 (高度, 宽度, 嵌入维度) + positional_embeddings = self.position_embedding.weight.reshape( + self.position_embedding_size, self.position_embedding_size, -1 + ) + + # 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小 + resized_positional_embeddings = self.resize_positional_embeddings( + positional_embeddings, spatial_shapes, max_length=pixel_values.shape[1] + ) + + # 将位置嵌入添加到patch嵌入中,得到最终的图像嵌入 + embeddings = patch_embeds + resized_positional_embeddings + return embeddings + + +class Siglip2Attention(nn.Module): + """ + 多头注意力机制,源自论文 'Attention Is All You Need'。 + + Args: + config: + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小,也是注意力机制的嵌入维度。 + - num_attention_heads (int): 注意力头的数量。 + - attention_dropout (float): 注意力权重在 dropout 时的丢弃概率。 + """ + + def __init__(self, config): + super().__init__() + self.config = config + # 注意力机制的嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # 注意力头的数量 + self.num_heads = config.num_attention_heads + # 每个注意力头的维度 + self.head_dim = self.embed_dim // self.num_heads + + # 检查 embed_dim 是否能被 num_heads 整除 + if self.head_dim * self.num_heads != self.embed_dim: + raise ValueError( + f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:" + f" {self.num_heads})." + ) + + # 缩放因子,用于缩放注意力得分 + self.scale = self.head_dim**-0.5 + self.dropout = config.attention_dropout + + # 定义线性层,用于计算查询 (query)、键 (key) 和值 (value) 的投影 + self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) + + # 输出投影层 + self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + """ + 前向传播方法,计算多头注意力。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.Tensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`, 可选): + 是否返回注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + """ + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # 重塑查询、键和值张量,以适应多头注意力的计算 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + + # 获取键和值序列的长度 + k_v_seq_len = key_states.shape[-2] + # 计算原始的注意力得分 + attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale + + # 检查注意力权重的形状是否正确 + if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len): + raise ValueError( + f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is" + f" {attn_weights.size()}" + ) + + # 如果提供了注意力掩码,则将其添加到注意力得分中 + if attention_mask is not None: + if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len): + raise ValueError( + f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, but is {attention_mask.size()}" + ) + attn_weights = attn_weights + attention_mask + + # 将注意力得分转换为 float32 以进行 softmax 计算,然后转换回原始数据类型 + attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) + attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) + + # 计算最终的注意力输出 + attn_output = torch.matmul(attn_weights, value_states) + + # 检查注意力输出的形状是否正确 + if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim): + raise ValueError( + f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is" + f" {attn_output.size()}" + ) + + # 重塑注意力输出张量,以适应后续的处理 + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim) + + # 应用输出投影层 + attn_output = self.out_proj(attn_output) + + # 根据需要返回注意力权重 + return attn_output, attn_weights + + +class Siglip2FlashAttention2(Siglip2Attention): + """ + Siglip2Attention 的 Flash Attention 模块。该模块继承自 `Siglip2Attention`,因此模型的权重保持不变。 + 唯一需要修改的是前向传播方法,需要正确调用 Flash Attention 的公共 API,并处理输入中可能存在的填充 token。 + + Attributes: + is_causal (bool): + 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 + """ + + is_causal = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Adapted from transformers.models.llama.modeling_llama.LlamaFlashAttention2.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.LongTensor] = None, + output_attentions: bool = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + """ + 前向传播方法,计算 Flash Attention。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.LongTensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`): + 是否输出注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + - `attn_weights_tuple`: 其他注意力权重信息(可选)。 + """ + output_attentions = False + + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # Flash Attention 要求输入的形状为 (batch_size, seq_length, head_dim, hidden_dim) + # 因此我们保持原始形状不变 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim) + + dropout_rate = self.dropout if self.training else 0.0 + + # 在 PEFT 中,通常我们将层归一化层转换为 float32 以提高训练稳定性 + # 因此,输入的隐藏状态会被静默地转换为 float32。因此,我们需要将其转换回正确的类型,以确保一切按预期工作。 + # 这种转换可能会减慢训练和推理速度,因此建议不要将 LayerNorms 转换为 fp32。 + + input_dtype = query_states.dtype + if input_dtype == torch.float32: + if torch.is_autocast_enabled(): + target_dtype = torch.get_autocast_gpu_dtype() + # 处理模型量化的情形 + elif hasattr(self.config, "_pre_quantization_dtype"): + target_dtype = self.config._pre_quantization_dtype + else: + target_dtype = self.q_proj.weight.dtype + + logger.warning_once( + f"The input hidden states seems to be silently casted in float32, this might be related to" + f" the fact you have upcasted embedding or layer norm layers in float32. We will cast back the input in" + f" {target_dtype}." + ) + + query_states = query_states.to(target_dtype) + key_states = key_states.to(target_dtype) + value_states = value_states.to(target_dtype) + + # 调用 Flash Attention 的前向传播方法 + attn_output = _flash_attention_forward( + query_states, + key_states, + value_states, + attention_mask, + q_len, + dropout=dropout_rate, + is_causal=self.is_causal, + use_top_left_mask=self._flash_attn_uses_top_left_mask, + ) + + # 重塑注意力输出张量 + attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim).contiguous() + attn_output = self.out_proj(attn_output) + + if not output_attentions: + attn_weights = None + + # 返回注意力输出和可选的注意力权重 + return attn_output, attn_weights + + +class Siglip2SdpaAttention(Siglip2Attention): + """ + 使用 torch.nn.functional.scaled_dot_product_attention 的 Siglip2 注意力模块。该模块继承自 `Siglip2Attention`,因为模块的权重保持不变。 + 唯一的变化是在前向传播方法中,以适应 SDPA API。 + + Attributes: + is_causal (bool): + 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 + """ + + is_causal = False + + # Adapted from Siglip2Attention.forward and transformers.models.llama.modeling_llama.LlamaSdpaAttention.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + """ + 前向传播方法,计算使用 SDPA 的注意力。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.Tensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`, 可选): + 是否输出注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + """ + if output_attentions: + return super().forward( + hidden_states=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + ) + + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # 重塑张量以适应多头注意力的计算 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + + if query_states.device.type == "cuda" and attention_mask is not None: + query_states = query_states.contiguous() + key_states = key_states.contiguous() + value_states = value_states.contiguous() + + # 我们通过 `is_causal` 语句而不是 SDPA 中的内联条件分配来调度到 SDPA 的 Flash Attention 或 Efficient 内核, + # 以支持 torch.compile 的动态形状和完整图选项。内联条件会阻止动态形状的编译。 + is_causal = True if self.is_causal and q_len > 1 else False + + attn_output = torch.nn.functional.scaled_dot_product_attention( + query_states, + key_states, + value_states, + attn_mask=attention_mask, + dropout_p=self.dropout if self.training else 0.0, + is_causal=is_causal, + ) + + # 重塑注意力输出张量 + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.view(batch_size, q_len, self.embed_dim) + + # 应用输出投影层 + attn_output = self.out_proj(attn_output) + + # 返回注意力输出,不返回注意力权重 + return attn_output, None + + +class Siglip2MLP(nn.Module): + """ + Siglip2 的多层感知机(MLP)模块,用于在注意力机制之后进行非线性变换。 + + Args: + config: + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 + """ + def __init__(self, config): + super().__init__() + self.config = config + # 根据配置选择激活函数 + self.activation_fn = ACT2FN[config.hidden_act] + # 定义第一个全连接层,将隐藏层大小映射到中间层大小 + self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) + # 定义第二个全连接层,将中间层大小映射回隐藏层大小 + self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + """ + 前向传播方法,应用 MLP 变换。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 `(batch_size, seq_length, hidden_size)`。 + + Returns: + `torch.Tensor`: + 变换后的张量,形状为 `(batch_size, seq_length, hidden_size)`。 + """ + # 应用第一个全连接层 + hidden_states = self.fc1(hidden_states) + # 应用激活函数 + hidden_states = self.activation_fn(hidden_states) + # 应用第二个全连接层 + hidden_states = self.fc2(hidden_states) + # 返回变换后的张量 + return hidden_states + + +# 定义注意力机制的实现类映射 +SIGLIP2_ATTENTION_CLASSES = { + "eager": Siglip2Attention, + "flash_attention_2": Siglip2FlashAttention2, + "sdpa": Siglip2SdpaAttention, +} + + +class Siglip2EncoderLayer(nn.Module): + """ + Siglip2 的编码器层,包含自注意力机制和 MLP 模块。 + + Args: + config (Siglip2Config): + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 + """ + def __init__(self, config: Siglip2Config): + super().__init__() + + # 嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # 根据配置选择注意力机制的实现类,并实例化 + self.self_attn = SIGLIP2_ATTENTION_CLASSES[config._attn_implementation](config=config) + # 定义第一个层归一化层 + self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + # 实例化 MLP 模块 + self.mlp = Siglip2MLP(config) + # 定义第二个层归一化层 + self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.FloatTensor]: + """ + 前向传播方法,处理输入张量通过自注意力和 MLP。 + + Args: + hidden_states (`torch.FloatTensor`): + 输入张量,形状为 `(batch_size, seq_len, embed_dim)`。 + attention_mask (`torch.FloatTensor`): + 注意力掩码张量,形状为 `(batch_size, 1, q_len, k_v_seq_len)`,其中填充元素由非常大的负值表示。 + output_attentions (`bool`, 可选, 默认值为 `False`): + 是否返回所有注意力层的注意力权重。 + + Returns: + `Tuple[torch.FloatTensor]`: + - `hidden_states`: 变换后的隐藏状态,形状为 `(batch_size, seq_len, embed_dim)`。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 `(batch_size, num_heads, q_len, k_v_seq_len)`。 + """ + # 保存残差连接的输入 + residual = hidden_states + + # 应用第一个层归一化 + hidden_states = self.layer_norm1(hidden_states) + # 应用自注意力机制 + hidden_states, attn_weights = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + ) + # 残差连接 + hidden_states = residual + hidden_states + + # 保存残差连接的输入 + residual = hidden_states + + # 应用第二个层归一化 + hidden_states = self.layer_norm2(hidden_states) + # 应用 MLP + hidden_states = self.mlp(hidden_states) + # 残差连接 + hidden_states = residual + hidden_states + # 打包输出 + outputs = (hidden_states,) + + if output_attentions: + # 如果需要,添加注意力权重到输出 + outputs += (attn_weights,) + + return outputs + + +class Siglip2Encoder(nn.Module): + """ + Transformer 编码器,由 `config.num_hidden_layers` 个自注意力层组成。每个层都是 [`Siglip2EncoderLayer`] 的实例。 + + Args: + config (Siglip2Config): + 模型配置对象,包含以下属性: + - num_hidden_layers (int): 编码器的层数。 + - 其他配置参数,如 hidden_size, intermediate_size, hidden_act, layer_norm_eps, output_attentions, output_hidden_states, use_return_dict 等。 + """ + + def __init__(self, config: Siglip2Config): + super().__init__() + self.config = config + self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) + self.gradient_checkpointing = False + + # Ignore copy + def forward( + self, + inputs_embeds, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入嵌入通过多个自注意力层。 + + Args: + inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`): + 可选地,直接传递嵌入表示,而不是传递 `input_ids`。这在您希望对如何将 `input_ids` 索引转换为关联向量有更多控制时非常有用。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 掩码张量,用于避免在填充 token 索引上执行注意力计算。掩码值选择 `[0, 1]`: + + - `1` 表示 **未掩码** 的 token, + - `0` 表示 **掩码** 的 token。 + + [什么是注意力掩码?](../glossary#attention-mask) + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutput]`: + - 如果 `return_dict=True`,返回 `BaseModelOutput` 对象。 + - 否则,返回包含 `hidden_states`, `encoder_states`, `all_attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 初始化存储隐藏状态和注意力权重的容器 + encoder_states = () if output_hidden_states else None + all_attentions = () if output_attentions else None + + # 设置初始隐藏状态为输入嵌入 + hidden_states = inputs_embeds + + # 遍历所有编码器层 + for encoder_layer in self.layers: + if output_hidden_states: + # 存储当前隐藏状态 + encoder_states = encoder_states + (hidden_states,) + + # 如果启用梯度检查点,则使用检查点机制 + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + encoder_layer.__call__, + hidden_states, + attention_mask, + output_attentions, + ) + else: + # 否则,正常调用编码器层的前向传播方法 + layer_outputs = encoder_layer( + hidden_states, + attention_mask, + output_attentions=output_attentions, + ) + + # 更新隐藏状态 + hidden_states = layer_outputs[0] + + if output_attentions: + # 存储注意力权重 + all_attentions = all_attentions + (layer_outputs[1],) + + if output_hidden_states: + # 存储最终隐藏状态 + encoder_states = encoder_states + (hidden_states,) + + if not return_dict: + return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None) + return BaseModelOutput( + last_hidden_state=hidden_states, hidden_states=encoder_states, attentions=all_attentions + ) + + +# 视觉输入参数的中文文档字符串 +SIGLIP2_VISION_INPUTS_DOCSTRING = r""" +Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 像素值。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。详情见 [`CLIPImageProcessor.__call__`]。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 + interpolate_pos_encoding (`bool`, *optional*, defaults to `False`): + 是否插值预训练的位置编码。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 +""" + + +class Siglip2VisionTransformer(nn.Module): + """ + Siglip2 的视觉 Transformer 模型,包含图像嵌入、编码器、层归一化以及可选的头部模块。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - vision_use_head (bool): 是否使用头部模块。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + + # 实例化视觉嵌入模块 + self.embeddings = Siglip2VisionEmbeddings(config) + # 实例化编码器模块 + self.encoder = Siglip2Encoder(config) + # 实例化后置层归一化层 + self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) + # 判断是否使用头部模块 + self.use_head = True if not hasattr(config, "vision_use_head") else config.vision_use_head + if self.use_head: + self.head = Siglip2MultiheadAttentionPoolingHead(config) + + # 判断是否使用 Flash Attention 2 + self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" + + def forward( + self, + pixel_values: torch.FloatTensor, + attention_mask: torch.Tensor, + spatial_shapes: torch.LongTensor, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入图像像素值通过视觉 Transformer 模型。 + + Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 像素值张量。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): + 空间形状张量,用于调整位置嵌入的大小。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 应用视觉嵌入模块,将像素值转换为嵌入向量 + hidden_states = self.embeddings(pixel_values, spatial_shapes) + + # 处理注意力掩码 + if attention_mask is not None and not self._use_flash_attention_2: + # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] + encoder_attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) + else: + encoder_attention_mask = attention_mask + + # 应用编码器模块 + encoder_outputs = self.encoder( + inputs_embeds=hidden_states, + attention_mask=encoder_attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取最后一层的隐藏状态 + last_hidden_state = encoder_outputs[0] + # 应用后置层归一化 + last_hidden_state = self.post_layernorm(last_hidden_state) + + # 应用头部模块(如果使用) + pooler_output = self.head(last_hidden_state, attention_mask) if self.use_head else None + + # 根据 return_dict 参数返回不同的输出格式 + if not return_dict: + return (last_hidden_state, pooler_output) + encoder_outputs[1:] + + return BaseModelOutputWithPooling( + last_hidden_state=last_hidden_state, + pooler_output=pooler_output, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + ) + + +class Siglip2TextEmbeddings(nn.Module): + """ + Siglip2 的文本嵌入模块,用于将输入的 token ID 转换为嵌入向量,并添加位置嵌入。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - vocab_size (int): 词汇表大小。 + - hidden_size (int): 隐藏层大小。 + - max_position_embeddings (int): 最大位置嵌入长度。 + """ + def __init__(self, config: Siglip2TextConfig): + super().__init__() + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + + # 定义 token 嵌入层,将 token ID 转换为嵌入向量 + self.token_embedding = nn.Embedding(config.vocab_size, embed_dim) + # 定义位置嵌入层,为每个位置生成位置嵌入 + self.position_embedding = nn.Embedding(config.max_position_embeddings, embed_dim) + + # 注册一个缓冲区,存储位置 ID 张量,并在模型保存时不进行序列化 + self.register_buffer( + "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False + ) + + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + position_ids: Optional[torch.LongTensor] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + ) -> torch.Tensor: + """ + 前向传播方法,将输入的 token ID 或嵌入向量转换为包含位置嵌入的嵌入向量。 + + Args: + input_ids (`torch.LongTensor`, *optional*): + 输入的 token ID 张量,形状为 `(batch_size, sequence_length)`。 + position_ids (`torch.LongTensor`, *optional*): + 位置 ID 张量,形状为 `(batch_size, sequence_length)`。如果未提供,则根据 `input_ids` 自动生成。 + inputs_embeds (`torch.FloatTensor`, *optional*): + 预先计算的输入嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。如果提供,则忽略 `input_ids`。 + + Returns: + `torch.Tensor`: + 包含位置嵌入的嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。 + """ + # 获取序列长度 + seq_length = input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2] + # 获取最大位置嵌入长度 + max_position_embedding = self.position_embedding.weight.shape[0] + + # 检查序列长度是否超过最大位置嵌入长度 + if seq_length > max_position_embedding: + raise ValueError( + f"Sequence length must be less than max_position_embeddings (got `sequence length`: " + f"{seq_length} and max_position_embeddings: {max_position_embedding}" + ) + + # 如果未提供位置 ID,则自动生成 + if position_ids is None: + position_ids = self.position_ids[:, :seq_length] + + # 如果未提供输入嵌入向量,则使用 token 嵌入层进行嵌入 + if inputs_embeds is None: + inputs_embeds = self.token_embedding(input_ids) + + # 获取位置嵌入 + position_embeddings = self.position_embedding(position_ids) + + # 将 token 嵌入和位置嵌入相加,得到最终的嵌入向量 + embeddings = inputs_embeds + position_embeddings + + return embeddings + + +def _trunc_normal_(tensor, mean, std, a, b): + """ + 对输入张量进行截断正态分布初始化。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + mean (float): 正态分布的均值。 + std (float): 正态分布的标准差。 + a (float): 截断的下界。 + b (float): 截断的上界。 + """ + def norm_cdf(x): + """ + 计算标准正态分布的累积分布函数(CDF)。 + + Args: + x (float): 输入值。 + + Returns: + float: 标准正态分布的 CDF 值。 + """ + return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0 + + # 检查均值是否在截断范围内超过2个标准差 + if (mean < a - 2 * std) or (mean > b + 2 * std): + warnings.warn( + "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. " + "The distribution of values may be incorrect.", + stacklevel=2, + ) + + # 通过使用截断的均匀分布,然后使用正态分布的逆 CDF 来生成值。 + # 获取上下 CDF 值 + l = norm_cdf((a - mean) / std) + u = norm_cdf((b - mean) / std) + + # 将张量均匀地填充为 [2l-1, 2u-1] 范围内的值 + tensor.uniform_(2 * l - 1, 2 * u - 1) + + # 使用逆误差函数(erfinv)进行逆 CDF 变换,得到截断的标准正态分布 + tensor.erfinv_() + + # 将分布转换为指定的均值和标准差 + tensor.mul_(std * math.sqrt(2.0)) + tensor.add_(mean) + + # 截断以确保值在正确的范围内 + tensor.clamp_(min=a, max=b) + + +def trunc_normal_tf_( + tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0, a: float = -2.0, b: float = 2.0 +) -> torch.Tensor: + """ + 使用截断正态分布填充输入张量。值实际上是从正态分布 :math:`\\mathcal{N}(\\text{mean}, \\text{std}^2)` 中抽取的, + 超出 :math:`[a, b]` 的值将被重新抽取,直到它们在边界内。该方法在 :math:`a \\leq \\text{mean} \\leq b` 时效果最佳。 + + 注意:这个 'tf' 变体更接近于 TensorFlow / JAX 的实现,其中边界 [a, b] 在采样正态分布(均值为0,标准差为1.0)时应用, + 然后结果通过均值和标准差参数进行缩放和平移。 + + Args: + tensor (torch.Tensor): 一个 n 维的 `torch.Tensor`。 + mean (float): 正态分布的均值,默认为0.0。 + std (float): 正态分布的标准差,默认为1.0。 + a (float): 最小截断值,默认为-2.0。 + b (float): 最大截断值,默认为2.0。 + """ + with torch.no_grad(): + # 使用标准正态分布(均值为0,标准差为1.0)进行截断初始化 + _trunc_normal_(tensor, 0, 1.0, a, b) + # 将分布缩放和平移到指定的均值和标准差 + tensor.mul_(std).add_(mean) + + +def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"): + """ + 方差缩放初始化方法,用于根据指定的模式和分布初始化张量。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + scale (float, optional): 缩放因子,默认为1.0。 + mode (str, optional): 缩放模式,可以是 'fan_in', 'fan_out' 或 'fan_avg',默认为 'fan_in'。 + distribution (str, optional): 分布类型,可以是 'truncated_normal', 'normal' 或 'uniform',默认为 'normal'。 + """ + fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor) + if mode == "fan_in": + denom = fan_in + elif mode == "fan_out": + denom = fan_out + elif mode == "fan_avg": + denom = (fan_in + fan_out) / 2 + + variance = scale / denom + + if distribution == "truncated_normal": + # 标准正态分布截断到 (-2, 2) 的标准差常数 + trunc_normal_tf_(tensor, std=math.sqrt(variance) / 0.87962566103423978) + elif distribution == "normal": + with torch.no_grad(): + tensor.normal_(std=math.sqrt(variance)) + elif distribution == "uniform": + bound = math.sqrt(3 * variance) + with torch.no_grad(): + tensor.uniform_(-bound, bound) + else: + raise ValueError(f"invalid distribution {distribution}") + + +def lecun_normal_(tensor): + """ + LeCun 正态分布初始化方法。 + + 使用方差缩放初始化方法,模式为 'fan_in',分布为 'truncated_normal'。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + """ + variance_scaling_(tensor, mode="fan_in", distribution="truncated_normal") + + +def default_flax_embed_init(tensor): + """ + 默认的 Flax 嵌入初始化方法。 + + 使用方差缩放初始化方法,模式为 'fan_in',分布为 'normal'。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + """ + variance_scaling_(tensor, mode="fan_in", distribution="normal") + + +class Siglip2TextTransformer(nn.Module): + """ + Siglip2 的文本 Transformer 模型,包含文本嵌入、编码器、最终层归一化以及线性头。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - projection_size (int): 线性头的输出维度。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + def __init__(self, config: Siglip2TextConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + # 实例化文本嵌入模块 + self.embeddings = Siglip2TextEmbeddings(config) + # 实例化编码器模块 + self.encoder = Siglip2Encoder(config) + # 实例化最终层归一化层 + self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) + + # 定义线性头,将隐藏状态映射到指定的投影大小 + self.head = nn.Linear(embed_dim, config.projection_size) + # 判断是否使用 Flash Attention 2 + self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" + + def forward( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入文本通过文本 Transformer 模型。 + + Args: + input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if input_ids is None: + raise ValueError("You have to specify input_ids") + + # 获取输入形状 + input_shape = input_ids.size() + # 重塑张量 + input_ids = input_ids.view(-1, input_shape[-1]) + + # 应用文本嵌入模块,将 token ID 转换为嵌入向量 + hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids) + + # 注意:Siglip2 的文本模型不像原始的 CLIP 模型那样使用因果掩码。 + # 扩展注意力掩码 + if attention_mask is not None and not self._use_flash_attention_2: + # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] + attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) + + # 应用编码器模块 + encoder_outputs = self.encoder( + inputs_embeds=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取最后一层的隐藏状态 + last_hidden_state = encoder_outputs[0] + # 应用最终层归一化 + last_hidden_state = self.final_layer_norm(last_hidden_state) + + # 假设使用 "sticky" EOS tokenization,最后一个 token 始终是 EOS。 + # 取最后一个 token 的隐藏状态作为池化输出 + pooled_output = last_hidden_state[:, -1, :] + # 应用线性头 + pooled_output = self.head(pooled_output) + + if not return_dict: + # 返回元组形式的输出 + return (last_hidden_state, pooled_output) + encoder_outputs[1:] + + return BaseModelOutputWithPooling( + last_hidden_state=last_hidden_state, + pooler_output=pooled_output, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + ) + + +class Siglip2PreTrainedModel(PreTrainedModel): + """ + 一个抽象类,用于处理权重初始化以及提供一个简单的接口用于下载和加载预训练模型。 + + Attributes: + config_class (Siglip2Config): 配置类。 + base_model_prefix (str): 模型前缀,用于保存和加载模型时的命名。 + supports_gradient_checkpointing (bool): 是否支持梯度检查点。 + """ + + config_class = Siglip2Config + base_model_prefix = "siglip2" + supports_gradient_checkpointing = True + + # 不需要分割的模块列表 + _no_split_modules = [ + "Siglip2TextEmbeddings", + "Siglip2EncoderLayer", + "Siglip2VisionEmbeddings", + "Siglip2EncoderLayer", + "Siglip2MultiheadAttentionPoolingHead", + ] + # 是否支持 Flash Attention 2 + _supports_flash_attn_2 = True + # 是否支持 SDPA + _supports_sdpa = True + + def _init_weights(self, module): + """ + 初始化模型权重。 + + Args: + module (nn.Module): 需要初始化的模块。 + """ + if isinstance(module, Siglip2VisionEmbeddings): + width = ( + self.config.vision_config.hidden_size + if isinstance(self.config, Siglip2Config) + else self.config.hidden_size + ) + # 初始化位置嵌入权重 + nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width)) + elif isinstance(module, nn.Embedding): + default_flax_embed_init(module.weight) # 使用默认的 Flax 嵌入初始化方法 + elif isinstance(module, Siglip2Attention): + nn.init.xavier_uniform_(module.q_proj.weight) # 初始化查询投影权重 + nn.init.xavier_uniform_(module.k_proj.weight) # 初始化键投影权重 + nn.init.xavier_uniform_(module.v_proj.weight) # 初始化值投影权重 + nn.init.xavier_uniform_(module.out_proj.weight) # 初始化输出投影权重 + nn.init.zeros_(module.q_proj.bias) # 初始化查询投影偏置 + nn.init.zeros_(module.k_proj.bias) # 初始化键投影偏置 + nn.init.zeros_(module.v_proj.bias) # 初始化值投影偏置 + nn.init.zeros_(module.out_proj.bias) # 初始化输出投影偏置 + elif isinstance(module, Siglip2MLP): + nn.init.xavier_uniform_(module.fc1.weight) # 初始化第一个全连接层权重 + nn.init.xavier_uniform_(module.fc2.weight) # 初始化第二个全连接层权重 + nn.init.normal_(module.fc1.bias, std=1e-6) # 初始化第一个全连接层偏置 + nn.init.normal_(module.fc2.bias, std=1e-6) # 初始化第二个全连接层偏置 + elif isinstance(module, Siglip2MultiheadAttentionPoolingHead): + nn.init.xavier_uniform_(module.probe.data) # 初始化探测数据 + nn.init.xavier_uniform_(module.attention.in_proj_weight.data) # 初始化注意力输入投影权重 + nn.init.zeros_(module.attention.in_proj_bias.data) # 初始化注意力输入投影偏置 + elif isinstance(module, Siglip2Model): + logit_scale_init = torch.log(torch.tensor(1.0)) # 初始化 logit scale + module.logit_scale.data.fill_(logit_scale_init) + module.logit_bias.data.zero_() # 初始化 logit 偏置 + elif isinstance(module, Siglip2ForImageClassification): + # 初始化分类器权重 + nn.init.normal_( + module.classifier.weight, + std=self.config.vision_config.hidden_size**-0.5 * self.config.initializer_factor, + ) + elif isinstance(module, (nn.Linear, nn.Conv2d)): + # 使用 LeCun 正态分布初始化线性层或卷积层权重 + lecun_normal_(module.weight) + if module.bias is not None: + # 初始化偏置为零 + nn.init.zeros_(module.bias) + elif isinstance(module, nn.LayerNorm): + # 初始化层归一化偏置为零 + module.bias.data.zero_() + # 初始化层归一化权重为1.0 + module.weight.data.fill_(1.0) + + +class Siglip2TextModel(Siglip2PreTrainedModel): + """ + Siglip2 的文本模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - projection_size (int): 线性头的输出维度。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - use_return_dict (bool): 是否使用 `ModelOutput` 对象返回结果。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + config_class = Siglip2TextConfig + + def __init__(self, config: Siglip2TextConfig): + super().__init__(config) + self.text_model = Siglip2TextTransformer(config) + # 初始化权重并应用最终处理 + self.post_init() + + def get_input_embeddings(self) -> nn.Module: + """ + 获取输入嵌入层。 + + Returns: + nn.Module: 输入嵌入层,通常是 `token_embedding`。 + """ + # 返回文本嵌入模块中的 token 嵌入层 + return self.text_model.embeddings.token_embedding + + def set_input_embeddings(self, value): + """ + 设置输入嵌入层。 + + Args: + value (nn.Module): 要设置的输入嵌入层。 + """ + # 设置文本嵌入模块中的 token 嵌入层 + self.text_model.embeddings.token_embedding = value + + def forward( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入文本通过文本模型。 + + Args: + input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用文本 Transformer 模型的前向传播方法 + return self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + +class Siglip2MultiheadAttentionPoolingHead(nn.Module): + """ + 多头注意力池化头。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - num_attention_heads (int): 注意力头的数量。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - 其他配置参数,如 intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + + # 初始化探测参数,形状为 (1, 1, hidden_size) + self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size)) + # 实例化多头注意力层 + self.attention = torch.nn.MultiheadAttention(config.hidden_size, config.num_attention_heads, batch_first=True) + # 实例化层归一化层 + self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + # 实例化 MLP 模块 + self.mlp = Siglip2MLP(config) + # 注意力头的数量 + self.num_heads = config.num_attention_heads + + def forward(self, hidden_state: torch.Tensor, attention_mask: Optional[torch.Tensor] = None): + """ + 前向传播方法,应用多头注意力池化。 + + Args: + hidden_state (`torch.Tensor`): + 输入隐藏状态,形状为 `(batch_size, sequence_length, hidden_size)`。 + attention_mask (`torch.Tensor`, *optional*): + 注意力掩码张量,形状为 `(batch_size, sequence_length)`,用于屏蔽某些位置。 + + Returns: + `torch.Tensor`: + 池化后的隐藏状态,形状为 `(batch_size, hidden_size)`。 + """ + # 获取批次大小 + batch_size = hidden_state.shape[0] + # 重复探测参数以匹配批次大小 + probe = self.probe.repeat(batch_size, 1, 1) + + if attention_mask is not None: + # 获取目标长度和源长度 + target_len, source_len = probe.shape[1], hidden_state.shape[1] + # 准备 4D 注意力掩码 + attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_state.dtype, target_len) + # 重复掩码以匹配多头 + attention_mask = attention_mask.repeat(1, self.num_heads, target_len, 1) + # 重塑掩码形状 + attention_mask = attention_mask.reshape(-1, target_len, source_len) + + # 应用多头注意力层 + hidden_state = self.attention(probe, hidden_state, hidden_state, attn_mask=attention_mask)[0] + + # 保存残差连接的输入 + residual = hidden_state + # 应用层归一化 + hidden_state = self.layernorm(hidden_state) + # 应用 MLP 并进行残差连接 + hidden_state = residual + self.mlp(hidden_state) + + # 返回池化后的隐藏状态(第一个 token) + return hidden_state[:, 0] + + +class Siglip2VisionModel(Siglip2PreTrainedModel): + """ + Siglip2 的视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - num_attention_heads (int): 注意力头的数量。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - 其他配置参数,如 num_hidden_layers, attention_dropout, hidden_dropout_prob 等。 + """ + config_class = Siglip2VisionConfig + main_input_name = "pixel_values" + + def __init__(self, config: Siglip2VisionConfig): + super().__init__(config) + + # 实例化视觉 Transformer 模型 + self.vision_model = Siglip2VisionTransformer(config) + + # 初始化权重并应用最终处理 + self.post_init() + + def get_input_embeddings(self) -> nn.Module: + """ + 获取输入嵌入层。 + + Returns: + nn.Module: 输入嵌入层,通常是 `patch_embedding`。 + """ + # 返回视觉嵌入模块中的 patch 嵌入层 + return self.vision_model.embeddings.patch_embedding + + def forward( + self, + pixel_values: torch.FloatTensor, + pixel_attention_mask: torch.Tensor, + spatial_shapes: torch.LongTensor, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入像素值通过视觉模型。 + + Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 输入像素值张量。 + pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): + 空间形状张量,用于调整位置嵌入的大小。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉 Transformer 模型的前向传播方法 + return self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + +class Siglip2Model(Siglip2PreTrainedModel): + """ + Siglip2 模型,结合了文本和视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2Config): + 模型的配置对象,包含以下属性: + - text_config (Siglip2TextConfig): 文本模型的配置。 + - vision_config (Siglip2VisionConfig): 视觉模型的配置。 + - 其他配置参数,如 logit_scale, logit_bias 等。 + """ + config_class = Siglip2Config + + def __init__(self, config: Siglip2Config): + super().__init__(config) + + # 检查 text_config 是否为 Siglip2TextConfig 类型 + if not isinstance(config.text_config, Siglip2TextConfig): + raise TypeError( + "config.text_config is expected to be of type Siglip2TextConfig but is of type" + f" {type(config.text_config)}." + ) + + # 检查 vision_config 是否为 Siglip2VisionConfig 类型 + if not isinstance(config.vision_config, Siglip2VisionConfig): + raise TypeError( + "config.vision_config is expected to be of type Siglip2VisionConfig but is of type" + f" {type(config.vision_config)}." + ) + + # 获取文本配置 + text_config = config.text_config + # 获取视觉配置 + vision_config = config.vision_config + + # 首先,使用正确的注意力实现方式初始化文本和视觉模型 + text_model = Siglip2TextModel._from_config(text_config) + vision_model = Siglip2VisionModel._from_config(vision_config) + + # 其次,获取文本和视觉子模块(为了向后兼容) + # 获取文本模型的子模块 + self.text_model = text_model.text_model + # 获取视觉模型的子模块 + self.vision_model = vision_model.vision_model + + self.logit_scale = nn.Parameter(torch.randn(1)) + self.logit_bias = nn.Parameter(torch.randn(1)) + + # 初始化权重并应用最终处理 + self.post_init() + + def get_text_features( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> torch.FloatTensor: + """ + 获取文本特征。 + + Returns: + text_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 + + 示例: + + ```python + >>> from transformers import AutoTokenizer, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> tokenizer = AutoTokenizer.from_pretrained("google/siglip2-base-patch16-224") + + >>> # 重要:确保设置 padding="max_length",因为这是模型训练的方式 + >>> inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding="max_length", return_tensors="pt") + >>> with torch.no_grad(): + ... text_features = model.get_text_features(**inputs) + ``` + """ + # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用文本模型的前向传播方法 + text_outputs = self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取池化输出 + pooled_output = text_outputs[1] + + # 返回文本特征 + return pooled_output + + def get_image_features( + self, + pixel_values: Optional[torch.FloatTensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> torch.FloatTensor: + """ + 获取图像特征。 + + Returns: + image_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 + + 示例: + + ```python + >>> from PIL import Image + >>> import requests + >>> from transformers import AutoProcessor, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") + + >>> image = Image.open(requests.get(url, stream=True).raw) + + >>> inputs = processor(images=image, return_tensors="pt") + + >>> with torch.no_grad(): + ... image_features = model.get_image_features(**inputs) + ``` + """ + # 使用 Siglip2Model 的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + vision_outputs = self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取池化输出 + pooled_output = vision_outputs[1] + + # 返回图像特征 + return pooled_output + + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + pixel_values: Optional[torch.FloatTensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + return_loss: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, Siglip2Output]: + """ + 前向传播方法,处理输入文本和图像通过 Siglip2 模型。 + + Returns: + + 示例: + + ```python + >>> from PIL import Image + >>> import requests + >>> from transformers import AutoProcessor, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") + + >>> image = Image.open(requests.get(url, stream=True).raw) + + >>> texts = ["a photo of 2 cats", "a photo of 2 dogs"] + >>> # 重要:我们传递 `padding=max_length`,因为模型是使用这种方式训练的 + >>> inputs = processor(text=texts, images=image, padding="max_length", return_tensors="pt") + + >>> with torch.no_grad(): + ... outputs = model(**inputs) + + >>> logits_per_image = outputs.logits_per_image + >>> probs = torch.sigmoid(logits_per_image) # 这些是概率 + >>> print(f"{probs[0][0]:.1%} that image 0 is '{texts[0]}'") + 31.9% that image 0 is 'a photo of 2 cats' + ``` + """ + # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + vision_outputs = self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 调用文本模型的前向传播方法 + text_outputs = self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取图像嵌入 + image_embeds = vision_outputs[1] + # 获取文本嵌入 + text_embeds = text_outputs[1] + + # 归一化特征 + image_embeds = image_embeds / image_embeds.norm(p=2, dim=-1, keepdim=True) + text_embeds = text_embeds / text_embeds.norm(p=2, dim=-1, keepdim=True) + + # 计算余弦相似度作为 logits + logits_per_text = torch.matmul(text_embeds, image_embeds.t().to(text_embeds.device)) + + logit_scale, logit_bias = self.logit_scale.to(text_embeds.device), self.logit_bias.to(text_embeds.device) + logits_per_text = logits_per_text * logit_scale.exp() + logit_bias + + # 转置得到图像-文本的 logits + logits_per_image = logits_per_text.t() + + loss = None + if return_loss: + eye = torch.eye(logits_per_text.size(0), device=logits_per_text.device) + m1_diag1 = -torch.ones_like(logits_per_text) + 2 * eye + loglik = torch.nn.functional.logsigmoid(m1_diag1 * logits_per_text) + nll = -torch.sum(loglik, dim=-1) + loss = nll.mean() + + if not return_dict: + output = (logits_per_image, logits_per_text, text_embeds, image_embeds, text_outputs, vision_outputs) + return ((loss,) + output) if loss is not None else output + + # 返回 Siglip2Output 对象 + return Siglip2Output( + loss=loss, + logits_per_image=logits_per_image, + logits_per_text=logits_per_text, + text_embeds=text_embeds, + image_embeds=image_embeds, + text_model_output=text_outputs, + vision_model_output=vision_outputs, + ) + + +class Siglip2ForImageClassification(Siglip2PreTrainedModel): + """ + Siglip2 的图像分类模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2Config): + 模型的配置对象,包含以下属性: + - vision_config (Siglip2VisionConfig): 视觉模型的配置。 + - num_labels (int): 分类标签的数量。 + - 其他配置参数,如 problem_type 等。 + """ + main_input_name = "pixel_values" + + def __init__(self, config: Siglip2Config) -> None: + super().__init__(config) + + # 分类标签的数量 + self.num_labels = config.num_labels + + # 使用正确的注意力实现方式创建视觉模型,并仅获取视觉模型的子模块(为了向后兼容) + vision_model = Siglip2VisionModel._from_config(config.vision_config) + self.vision_model = vision_model.vision_model + + # 分类器头 + self.classifier = ( + # 如果 num_labels > 0,则使用线性层作为分类器,否则使用恒等映射 + nn.Linear(config.vision_config.hidden_size, config.num_labels) if config.num_labels > 0 else nn.Identity() + ) + + # 初始化权重并应用最终处理 + self.post_init() + + def forward( + self, + pixel_values: Optional[torch.Tensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + labels: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入图像通过图像分类模型。 + + Args: + pixel_values (`torch.Tensor` of shape `(batch_size, num_channels, height, width)`, *optional*): + 输入像素值张量。 + pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`, *optional*): + 空间形状张量,用于调整位置嵌入的大小。 + labels (`torch.LongTensor` of shape `(batch_size,)`, *optional*): + 标签,用于计算图像分类/回归损失。索引应在 `[0, ..., config.num_labels - 1]`。 + 如果 `config.num_labels == 1`,则计算回归损失(均方损失),如果 `config.num_labels > 1`,则计算分类损失(交叉熵)。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, ImageClassifierOutput]`: + - 如果 `return_dict=True`,返回 `ImageClassifierOutput` 对象。 + - 否则,返回包含 `logits`, `hidden_states`, `attentions` 的元组。 + """ + # 设置是否输出注意力 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + # 设置是否输出隐藏状态 + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + outputs = self.vision_model( + pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取序列输出 + sequence_output = outputs[0] + + # 对 patch tokens 进行平均池化 + if pixel_attention_mask is not None: + # 准备池化掩码 + pool_mask = pixel_attention_mask[..., None].to(sequence_output.device) + # 应用掩码进行池化 + sequence_output = torch.sum(sequence_output * pool_mask, dim=1) / torch.sum(pool_mask, dim=1) + else: + # 否则,直接进行平均池化 + sequence_output = torch.mean(sequence_output, dim=1) + + # 应用分类器 + logits = self.classifier(sequence_output) + + loss = None + if labels is not None: + # 将标签移动到正确的设备以启用模型并行 + labels = labels.to(logits.device) + if self.config.problem_type is None: + if self.num_labels == 1: + # 设置问题类型为回归 + self.config.problem_type = "regression" + elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): + # 设置问题类型为单标签分类 + self.config.problem_type = "single_label_classification" + else: + # 设置问题类型为多标签分类 + self.config.problem_type = "multi_label_classification" + + if self.config.problem_type == "regression": + # 使用均方损失 + loss_fct = MSELoss() + if self.num_labels == 1: + # 计算回归损失 + loss = loss_fct(logits.squeeze(), labels.squeeze()) + else: + loss = loss_fct(logits, labels) + elif self.config.problem_type == "single_label_classification": + # 使用交叉熵损失 + loss_fct = CrossEntropyLoss() + # 计算分类损失 + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + elif self.config.problem_type == "multi_label_classification": + # 使用二元交叉熵损失 + loss_fct = BCEWithLogitsLoss() + # 计算多标签分类损失 + loss = loss_fct(logits, labels) + + if not return_dict: + # 返回元组形式的输出 + output = (logits,) + outputs[2:] + return ((loss,) + output) if loss is not None else output + + # 返回封装后的模型输出 + return ImageClassifierOutput( + loss=loss, + logits=logits, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) diff --git a/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py b/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py new file mode 100644 index 000000000..7bbd89c39 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py @@ -0,0 +1,185 @@ +import os +from typing import Union +from transformers import PretrainedConfig +from transformers.utils import logging + +logger = logging.get_logger(__name__) + +class Siglip2VisionConfig(PretrainedConfig): + r""" + Siglip2VisionConfig 是用于存储 Siglip2VisionModel 配置的类。 + 它用于实例化一个 Siglip2 视觉编码器,根据指定的参数定义模型架构。 + + 默认配置对应于 SigLIP-2 ViT-Base 模型。 + """ + + model_type = "siglip2_vision" + + def __init__( + self, + hidden_size=768, + intermediate_size=3072, + num_hidden_layers=12, + num_attention_heads=12, + num_channels=3, + image_size=224, + patch_size=16, + hidden_act="gelu_pytorch_tanh", + layer_norm_eps=1e-6, + attention_dropout=0.0, + num_patches=None, # 如果不传,会自动计算 (image_size // patch_size) ** 2 + vision_use_head=True, + # 用于 Flash Attention 或 SDPA 的实现选择 + _attn_implementation="eager", + **kwargs, + ): + super().__init__(**kwargs) + + self.hidden_size = hidden_size + self.intermediate_size = intermediate_size + self.num_hidden_layers = num_hidden_layers + self.num_attention_heads = num_attention_heads + self.num_channels = num_channels + self.image_size = image_size + self.patch_size = patch_size + self.hidden_act = hidden_act + self.layer_norm_eps = layer_norm_eps + self.attention_dropout = attention_dropout + self.vision_use_head = vision_use_head + self._attn_implementation = _attn_implementation + + # 自动计算 num_patches,这对于位置编码的初始化至关重要 + if num_patches is None: + self.num_patches = (image_size // patch_size) ** 2 + else: + self.num_patches = num_patches + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig": + config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs) + + # 如果从 checkpoint 加载,确保 vision_config 字典被正确使用 + if "vision_config" in config_dict: + config_dict = config_dict["vision_config"] + + return cls.from_dict(config_dict, **kwargs) + + +class Siglip2TextConfig(PretrainedConfig): + r""" + Siglip2TextConfig 是用于存储 Siglip2TextModel 配置的类。 + """ + + model_type = "siglip2_text" + + def __init__( + self, + vocab_size=32000, + hidden_size=768, + intermediate_size=3072, + num_hidden_layers=12, + num_attention_heads=12, + max_position_embeddings=2048, + hidden_act="gelu_pytorch_tanh", + layer_norm_eps=1e-6, + attention_dropout=0.0, + # 投影层大小,即文本嵌入最后映射到的维度 + projection_size=768, + _attn_implementation="eager", + **kwargs, + ): + super().__init__(**kwargs) + + self.vocab_size = vocab_size + self.hidden_size = hidden_size + self.intermediate_size = intermediate_size + self.num_hidden_layers = num_hidden_layers + self.num_attention_heads = num_attention_heads + self.max_position_embeddings = max_position_embeddings + self.hidden_act = hidden_act + self.layer_norm_eps = layer_norm_eps + self.attention_dropout = attention_dropout + self.projection_size = projection_size + self._attn_implementation = _attn_implementation + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig": + config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs) + + # 如果从 checkpoint 加载,确保 text_config 字典被正确使用 + if "text_config" in config_dict: + config_dict = config_dict["text_config"] + + return cls.from_dict(config_dict, **kwargs) + + +class Siglip2Config(PretrainedConfig): + r""" + Siglip2Config 是用于存储 Siglip2Model 配置的类。 + 它包含实例化 Siglip2VisionConfig 和 Siglip2TextConfig 所需的所有参数。 + + Args: + vision_config (`dict`, *optional*): + 用于初始化 Siglip2VisionConfig 的字典。 + text_config (`dict`, *optional*): + 用于初始化 Siglip2TextConfig 的字典。 + kwargs (*optional*): + 传递给父类 PretrainedConfig 的关键字参数。 + """ + + model_type = "siglip2" + is_composition = True + + def __init__( + self, + vision_config=None, + text_config=None, + projection_dim=768, + logit_scale_init_value=2.6592, + logit_bias_init_value=-10.0, + **kwargs, + ): + # 1. 初始化 Vision Config + if vision_config is None: + self.vision_config = Siglip2VisionConfig() + logger.info("vision_config is None. Initializing the Siglip2VisionConfig with default values.") + elif isinstance(vision_config, dict): + self.vision_config = Siglip2VisionConfig(**vision_config) + elif isinstance(vision_config, Siglip2VisionConfig): + self.vision_config = vision_config + else: + raise TypeError(f"vision_config should be a dict or Siglip2VisionConfig, but got {type(vision_config)}") + + # 2. 初始化 Text Config + if text_config is None: + self.text_config = Siglip2TextConfig() + logger.info("text_config is None. Initializing the Siglip2TextConfig with default values.") + elif isinstance(text_config, dict): + self.text_config = Siglip2TextConfig(**text_config) + elif isinstance(text_config, Siglip2TextConfig): + self.text_config = text_config + else: + raise TypeError(f"text_config should be a dict or Siglip2TextConfig, but got {type(text_config)}") + + self.projection_dim = projection_dim + self.logit_scale_init_value = logit_scale_init_value + self.logit_bias_init_value = logit_bias_init_value + + # 确保初始化父类 + super().__init__(**kwargs) + + @classmethod + def from_vision_text_configs(cls, vision_config: Siglip2VisionConfig, text_config: Siglip2TextConfig, **kwargs): + r""" + 从 vision_config 和 text_config 实例化 Siglip2Config。 + """ + return cls(vision_config=vision_config, text_config=text_config, **kwargs) + + def to_dict(self): + """ + 将配置序列化为字典。 + """ + output = super().to_dict() + output["vision_config"] = self.vision_config.to_dict() + output["text_config"] = self.text_config.to_dict() + return output \ No newline at end of file diff --git a/PyTorch/build-in/Classification/SigLIP2/coverage.txt b/PyTorch/build-in/Classification/SigLIP2/coverage.txt new file mode 100644 index 000000000..254e65b6c --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/coverage.txt @@ -0,0 +1,3 @@ +all api: ['_amp_foreach_non_finite_check_and_unscale_', '_amp_update_scale_', '_copy_from', '_has_compatible_shallow_copy_type', '_local_scalar_dense', '_log_softmax', '_log_softmax_backward_data', '_pin_memory', '_reshape_alias', '_softmax', '_softmax_backward_data', 'add', 'add_', 'addmm', 'as_strided', 'bmm', 'bmm_backward', 'bmm_forward', 'contiguous', 'convolution', 'convolution_backward', 'copy_stride', 'div', 'eq', 'fill_', 'floor_divide', 'fused_sgd', 'gelu', 'gelu_backward', 'is_pinned', 'linear', 'matmul', 'mean', 'mm', 'mul', 'mul_', 'native_layer_norm', 'native_layer_norm_backward', 'nll_loss_backward', 'nll_loss_forward', 'reciprocal', 'resize_', 'scaled_dot_product_attention', 'set_', 'slice_backward', 'sum', 'topk_out', 'view', 'zero_'], total: 49 +fallback op: ['_upsample_bilinear2d_aa_backward_out', '_upsample_bilinear2d_aa_out'], total: 2 +coverage rate: 95.92% diff --git a/PyTorch/build-in/Classification/SigLIP2/run b/PyTorch/build-in/Classification/SigLIP2/run new file mode 100644 index 000000000..010c7412f --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/run @@ -0,0 +1 @@ +bash ../sdaaTest.sh siglip2 32 0 diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2.py b/PyTorch/build-in/Classification/SigLIP2/siglip2.py new file mode 100644 index 000000000..89995dacb --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/siglip2.py @@ -0,0 +1,325 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import Optional, Tuple, Union +from dataclasses import dataclass + +# ========================================== +# 1. 极简配置类 (Configuration) +# ========================================== +@dataclass +class Siglip2VisionConfig: + """ + SigLIP 2 视觉模型配置 (针对 ImageNet 分类精简版) + 默认参数对应 ViT-Base (86M params) + """ + hidden_size: int = 768 + intermediate_size: int = 3072 + num_hidden_layers: int = 12 + num_attention_heads: int = 12 + num_channels: int = 3 + image_size: int = 224 + patch_size: int = 16 + layer_norm_eps: float = 1e-6 + attention_dropout: float = 0.0 + num_labels: int = 1000 # ImageNet 类别数 + + def __post_init__(self): + # 自动计算 patch 数量,用于位置编码初始化 + self.num_patches = (self.image_size // self.patch_size) ** 2 + +# ========================================== +# 2. 核心模块 (Building Blocks) +# ========================================== + +class Siglip2VisionEmbeddings(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embed_dim = config.hidden_size + self.patch_size = config.patch_size + self.image_size = config.image_size + + self.patch_embedding = nn.Conv2d( + in_channels=config.num_channels, + out_channels=self.embed_dim, + kernel_size=self.patch_size, + stride=self.patch_size, + padding="valid", # SigLIP 不做 padding + ) + + self.num_patches = config.num_patches + self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) + + # 注册位置 ID + self.register_buffer( + "position_ids", + torch.arange(self.num_patches).expand((1, -1)), + persistent=False, + ) + + def resize_positional_embeddings(self, positional_embeddings, spatial_shapes, max_length): + """ + SigLIP 2 核心特性:动态插值位置编码 + """ + batch_size = spatial_shapes.shape[0] + embed_dim = positional_embeddings.shape[-1] + + # 将 flat 的位置编码还原回 2D 网格: (H_grid, W_grid, Dim) + grid_size = int(self.num_patches**0.5) + pos_embed_grid = positional_embeddings.view(grid_size, grid_size, embed_dim) + + # Permute to (1, Dim, H, W) for interpolate + pos_embed_grid = pos_embed_grid.permute(2, 0, 1).unsqueeze(0) + + result_embeddings = torch.zeros( + (batch_size, max_length, embed_dim), + device=positional_embeddings.device, + dtype=positional_embeddings.dtype + ) + + for i in range(batch_size): + h_pixels, w_pixels = spatial_shapes[i] + # 计算目标 grid 大小 + h_grid = h_pixels // self.patch_size + w_grid = w_pixels // self.patch_size + + # 双线性插值 + resized = F.interpolate( + pos_embed_grid, + size=(h_grid, w_grid), + mode="bilinear", + align_corners=False, + antialias=True + ) + + # Flatten back: (Dim, H*W) -> (H*W, Dim) + resized = resized.squeeze(0).flatten(1).transpose(0, 1) + + # 填入结果 + seq_len = resized.shape[0] + if seq_len > max_length: + seq_len = max_length # 截断保护 + result_embeddings[i, :seq_len, :] = resized[:seq_len, :] + + return result_embeddings + + def forward(self, pixel_values: torch.Tensor, spatial_shapes: torch.Tensor) -> torch.Tensor: + # pixel_values: [B, 3, H, W] + patch_embeds = self.patch_embedding(pixel_values) + # [B, C, H_grid, W_grid] -> [B, C, Seq_Len] -> [B, Seq_Len, C] + embeddings = patch_embeds.flatten(2).transpose(1, 2) + + # 获取位置编码权重 + pos_weights = self.position_embedding.weight # [Num_Patches, Dim] + + # 动态插值位置编码 + resized_pos_embeds = self.resize_positional_embeddings( + pos_weights, spatial_shapes, max_length=embeddings.shape[1] + ) + + return embeddings + resized_pos_embeds + +class Siglip2MLP(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) + self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) + self.act = nn.GELU() # 默认 GELU + + def forward(self, hidden_states): + hidden_states = self.fc1(hidden_states) + hidden_states = self.act(hidden_states) + hidden_states = self.fc2(hidden_states) + return hidden_states + +class Siglip2Attention(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embed_dim = config.hidden_size + self.num_heads = config.num_attention_heads + self.head_dim = self.embed_dim // self.num_heads + self.scale = self.head_dim**-0.5 + + self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) + + def forward(self, hidden_states): + batch_size, seq_len, _ = hidden_states.shape + + q = self.q_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + k = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + v = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + + # --- 修改部分开始 --- + # 原理:SDPA 内部公式是 Softmax( (Q @ K.T) / sqrt(d_k) ) @ V + # 我们的目标是:Softmax( (Q @ K.T) * self.scale ) @ V + # 因此需要:q_scaled = q * self.scale * sqrt(d_k) + # 这样进入 SDPA 后,内部除以 sqrt(d_k) 正好抵消,剩下 self.scale + + d_k = q.size(-1) + # 使用 math.sqrt(d_k) 也可以,或者 (d_k ** 0.5) + q_scaled = q * (self.scale * (d_k ** 0.5)) + + # 兼容旧版本 PyTorch,不再传 scale 参数 + attn_output = F.scaled_dot_product_attention(q_scaled, k, v) + # --- 修改部分结束 --- + + attn_output = attn_output.transpose(1, 2).contiguous().reshape(batch_size, seq_len, self.embed_dim) + return self.out_proj(attn_output) + +class Siglip2EncoderLayer(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.layer_norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + self.self_attn = Siglip2Attention(config) + self.layer_norm2 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + self.mlp = Siglip2MLP(config) + + def forward(self, hidden_states): + # Pre-Norm 结构 + residual = hidden_states + hidden_states = self.layer_norm1(hidden_states) + hidden_states = self.self_attn(hidden_states) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.layer_norm2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + +class Siglip2Encoder(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) + + def forward(self, hidden_states): + for layer in self.layers: + hidden_states = layer(hidden_states) + return hidden_states + +class Siglip2VisionTransformer(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embeddings = Siglip2VisionEmbeddings(config) + self.encoder = Siglip2Encoder(config) + self.post_layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + + def forward(self, pixel_values, spatial_shapes): + hidden_states = self.embeddings(pixel_values, spatial_shapes) + hidden_states = self.encoder(hidden_states) + hidden_states = self.post_layernorm(hidden_states) + return hidden_states + +# ========================================== +# 3. 最终分类模型 (Model Wrapper) +# ========================================== + +class Siglip2ForImageClassification(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + self.num_labels = config.num_labels + + # 骨干网络 + self.vision_model = Siglip2VisionTransformer(config) + + # 分类头 + self.classifier = nn.Linear(config.hidden_size, config.num_labels) + + # 初始化 + self.apply(self._init_weights) + + def _init_weights(self, module): + if isinstance(module, (nn.Linear, nn.Conv2d)): + nn.init.trunc_normal_(module.weight, std=0.02) + if module.bias is not None: + nn.init.zeros_(module.bias) + elif isinstance(module, nn.LayerNorm): + nn.init.zeros_(module.bias) + nn.init.ones_(module.weight) + elif isinstance(module, nn.Embedding): + nn.init.trunc_normal_(module.weight, std=0.02) + + def forward(self, pixel_values, labels=None): + """ + 标准的 ImageNet 训练接口 + Args: + pixel_values: [Batch, 3, Height, Width] + labels: [Batch] (0 ~ num_classes-1) + """ + b, c, h, w = pixel_values.shape + device = pixel_values.device + + # 1. 自动生成 spatial_shapes (SigLIP 2 必需) + # 假设 Batch 内所有图片尺寸一致 (ImageNet 默认行为) + spatial_shapes = torch.tensor([[h, w]], device=device).expand(b, 2) + + # 2. 骨干网络前向传播 + # Output: [Batch, Seq_Len, Hidden_Size] + sequence_output = self.vision_model(pixel_values, spatial_shapes) + + # 3. 全局平均池化 (Global Average Pooling) - 替代 CLS token + # [Batch, Seq_Len, Hidden_Size] -> [Batch, Hidden_Size] + pooled_output = sequence_output.mean(dim=1) + + # 4. 分类映射 + logits = self.classifier(pooled_output) + + loss = None + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + return loss, logits + + return logits + +# ========================================== +# 4. 工厂函数 & 测试代码 +# ========================================== + +def Model(num_classes=1000, model_size='base'): + if model_size == 'base': + config = Siglip2VisionConfig( + hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, num_labels=num_classes + ) + elif model_size == 'large': + config = Siglip2VisionConfig( + hidden_size=1024, num_hidden_layers=24, num_attention_heads=16, intermediate_size=4096, num_labels=num_classes + ) + elif model_size == 'so400m': + config = Siglip2VisionConfig( + hidden_size=1152, num_hidden_layers=27, num_attention_heads=16, intermediate_size=4304, num_labels=num_classes + ) + else: + raise ValueError("Unsupported model size") + + return Siglip2ForImageClassification(config) + +if __name__ == "__main__": + print("正在初始化 SigLIP 2 (ImageNet Classification)...") + + # 1. 创建模型 + model = Model(model_size='base', num_classes=1000) + + # 2. 模拟输入数据 (Batch=2, RGB, 224x224) + pixel_values = torch.randn(2, 3, 224, 224) + labels = torch.tensor([0, 999], dtype=torch.long) # 假设两个标签 + + # 3. 运行前向传播 + print("开始前向传播测试...") + loss, logits = model(pixel_values, labels=labels) + + print(f"Loss: {loss.item():.4f}") + print(f"Logits Shape: {logits.shape}") # 应该是 (2, 1000) + + # 4. 运行反向传播 (验证梯度) + print("开始反向传播测试...") + loss.backward() + + print("测试完成!所有梯度计算正常。") + print(f"FC层梯度范数: {model.classifier.weight.grad.norm().item():.4f}") + diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py b/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py new file mode 100644 index 000000000..dae6fc302 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py @@ -0,0 +1,315 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import Optional, Tuple, Union +from dataclasses import dataclass + +# ========================================== +# 1. 极简配置类 (Configuration) +# ========================================== +@dataclass +class Siglip2VisionConfig: + """ + SigLIP 2 视觉模型配置 (针对 ImageNet 分类精简版) + 默认参数对应 ViT-Base (86M params) + """ + hidden_size: int = 768 + intermediate_size: int = 3072 + num_hidden_layers: int = 12 + num_attention_heads: int = 12 + num_channels: int = 3 + image_size: int = 224 + patch_size: int = 16 + layer_norm_eps: float = 1e-6 + attention_dropout: float = 0.0 + num_labels: int = 1000 # ImageNet 类别数 + + def __post_init__(self): + # 自动计算 patch 数量,用于位置编码初始化 + self.num_patches = (self.image_size // self.patch_size) ** 2 + +# ========================================== +# 2. 核心模块 (Building Blocks) +# ========================================== + +class Siglip2VisionEmbeddings(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embed_dim = config.hidden_size + self.patch_size = config.patch_size + self.image_size = config.image_size + + self.patch_embedding = nn.Conv2d( + in_channels=config.num_channels, + out_channels=self.embed_dim, + kernel_size=self.patch_size, + stride=self.patch_size, + padding="valid", # SigLIP 不做 padding + ) + + self.num_patches = config.num_patches + self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) + + # 注册位置 ID + self.register_buffer( + "position_ids", + torch.arange(self.num_patches).expand((1, -1)), + persistent=False, + ) + + def resize_positional_embeddings(self, positional_embeddings, spatial_shapes, max_length): + """ + SigLIP 2 核心特性:动态插值位置编码 + """ + batch_size = spatial_shapes.shape[0] + embed_dim = positional_embeddings.shape[-1] + + # 将 flat 的位置编码还原回 2D 网格: (H_grid, W_grid, Dim) + grid_size = int(self.num_patches**0.5) + pos_embed_grid = positional_embeddings.view(grid_size, grid_size, embed_dim) + + # Permute to (1, Dim, H, W) for interpolate + pos_embed_grid = pos_embed_grid.permute(2, 0, 1).unsqueeze(0) + + result_embeddings = torch.zeros( + (batch_size, max_length, embed_dim), + device=positional_embeddings.device, + dtype=positional_embeddings.dtype + ) + + for i in range(batch_size): + h_pixels, w_pixels = spatial_shapes[i] + # 计算目标 grid 大小 + h_grid = h_pixels // self.patch_size + w_grid = w_pixels // self.patch_size + + # 双线性插值 + resized = F.interpolate( + pos_embed_grid, + size=(h_grid, w_grid), + mode="bilinear", + align_corners=False, + antialias=True + ) + + # Flatten back: (Dim, H*W) -> (H*W, Dim) + resized = resized.squeeze(0).flatten(1).transpose(0, 1) + + # 填入结果 + seq_len = resized.shape[0] + if seq_len > max_length: + seq_len = max_length # 截断保护 + result_embeddings[i, :seq_len, :] = resized[:seq_len, :] + + return result_embeddings + + def forward(self, pixel_values: torch.Tensor, spatial_shapes: torch.Tensor) -> torch.Tensor: + # pixel_values: [B, 3, H, W] + patch_embeds = self.patch_embedding(pixel_values) + # [B, C, H_grid, W_grid] -> [B, C, Seq_Len] -> [B, Seq_Len, C] + embeddings = patch_embeds.flatten(2).transpose(1, 2) + + # 获取位置编码权重 + pos_weights = self.position_embedding.weight # [Num_Patches, Dim] + + # 动态插值位置编码 + resized_pos_embeds = self.resize_positional_embeddings( + pos_weights, spatial_shapes, max_length=embeddings.shape[1] + ) + + return embeddings + resized_pos_embeds + +class Siglip2MLP(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) + self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) + self.act = nn.GELU() # 默认 GELU + + def forward(self, hidden_states): + hidden_states = self.fc1(hidden_states) + hidden_states = self.act(hidden_states) + hidden_states = self.fc2(hidden_states) + return hidden_states + +class Siglip2Attention(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embed_dim = config.hidden_size + self.num_heads = config.num_attention_heads + self.head_dim = self.embed_dim // self.num_heads + self.scale = self.head_dim**-0.5 + + self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) + + def forward(self, hidden_states): + batch_size, seq_len, _ = hidden_states.shape + + q = self.q_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + k = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + v = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) + + # 使用 PyTorch 原生 SDPA (自研卡对齐重点) + # 相当于 Softmax(Q @ K.T / scale) @ V + attn_output = F.scaled_dot_product_attention(q, k, v, scale=self.scale) + + attn_output = attn_output.transpose(1, 2).contiguous().reshape(batch_size, seq_len, self.embed_dim) + return self.out_proj(attn_output) + +class Siglip2EncoderLayer(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.layer_norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + self.self_attn = Siglip2Attention(config) + self.layer_norm2 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + self.mlp = Siglip2MLP(config) + + def forward(self, hidden_states): + # Pre-Norm 结构 + residual = hidden_states + hidden_states = self.layer_norm1(hidden_states) + hidden_states = self.self_attn(hidden_states) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.layer_norm2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + +class Siglip2Encoder(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) + + def forward(self, hidden_states): + for layer in self.layers: + hidden_states = layer(hidden_states) + return hidden_states + +class Siglip2VisionTransformer(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.embeddings = Siglip2VisionEmbeddings(config) + self.encoder = Siglip2Encoder(config) + self.post_layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + + def forward(self, pixel_values, spatial_shapes): + hidden_states = self.embeddings(pixel_values, spatial_shapes) + hidden_states = self.encoder(hidden_states) + hidden_states = self.post_layernorm(hidden_states) + return hidden_states + +# ========================================== +# 3. 最终分类模型 (Model Wrapper) +# ========================================== + +class Siglip2ForImageClassification(nn.Module): + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + self.num_labels = config.num_labels + + # 骨干网络 + self.vision_model = Siglip2VisionTransformer(config) + + # 分类头 + self.classifier = nn.Linear(config.hidden_size, config.num_labels) + + # 初始化 + self.apply(self._init_weights) + + def _init_weights(self, module): + if isinstance(module, (nn.Linear, nn.Conv2d)): + nn.init.trunc_normal_(module.weight, std=0.02) + if module.bias is not None: + nn.init.zeros_(module.bias) + elif isinstance(module, nn.LayerNorm): + nn.init.zeros_(module.bias) + nn.init.ones_(module.weight) + elif isinstance(module, nn.Embedding): + nn.init.trunc_normal_(module.weight, std=0.02) + + def forward(self, pixel_values, labels=None): + """ + 标准的 ImageNet 训练接口 + Args: + pixel_values: [Batch, 3, Height, Width] + labels: [Batch] (0 ~ num_classes-1) + """ + b, c, h, w = pixel_values.shape + device = pixel_values.device + + # 1. 自动生成 spatial_shapes (SigLIP 2 必需) + # 假设 Batch 内所有图片尺寸一致 (ImageNet 默认行为) + spatial_shapes = torch.tensor([[h, w]], device=device).expand(b, 2) + + # 2. 骨干网络前向传播 + # Output: [Batch, Seq_Len, Hidden_Size] + sequence_output = self.vision_model(pixel_values, spatial_shapes) + + # 3. 全局平均池化 (Global Average Pooling) - 替代 CLS token + # [Batch, Seq_Len, Hidden_Size] -> [Batch, Hidden_Size] + pooled_output = sequence_output.mean(dim=1) + + # 4. 分类映射 + logits = self.classifier(pooled_output) + + loss = None + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + return loss, logits + + return logits + +# ========================================== +# 4. 工厂函数 & 测试代码 +# ========================================== + +def Model(num_classes=1000, model_size='base'): + if model_size == 'base': + config = Siglip2VisionConfig( + hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, num_labels=num_classes + ) + elif model_size == 'large': + config = Siglip2VisionConfig( + hidden_size=1024, num_hidden_layers=24, num_attention_heads=16, intermediate_size=4096, num_labels=num_classes + ) + elif model_size == 'so400m': + config = Siglip2VisionConfig( + hidden_size=1152, num_hidden_layers=27, num_attention_heads=16, intermediate_size=4304, num_labels=num_classes + ) + else: + raise ValueError("Unsupported model size") + + return Siglip2ForImageClassification(config) + +if __name__ == "__main__": + print("正在初始化 SigLIP 2 (ImageNet Classification)...") + + # 1. 创建模型 + model = Model(model_size='base', num_classes=1000) + + # 2. 模拟输入数据 (Batch=2, RGB, 224x224) + pixel_values = torch.randn(2, 3, 224, 224) + labels = torch.tensor([0, 999], dtype=torch.long) # 假设两个标签 + + # 3. 运行前向传播 + print("开始前向传播测试...") + loss, logits = model(pixel_values, labels=labels) + + print(f"Loss: {loss.item():.4f}") + print(f"Logits Shape: {logits.shape}") # 应该是 (2, 1000) + + # 4. 运行反向传播 (验证梯度) + print("开始反向传播测试...") + loss.backward() + + print("测试完成!所有梯度计算正常。") + print(f"FC层梯度范数: {model.classifier.weight.grad.norm().item():.4f}") + diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg b/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e54a8c4cc204bcf6970366475edca0544b1f7d0c GIT binary patch literal 36902 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 N@cExO1`d21`!8KH2eSYG literal 0 HcmV?d00001 diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt b/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt new file mode 100644 index 000000000..b470d42db --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt @@ -0,0 +1,29 @@ +=== CUDA === +4.767600 5.222800 5.756000 5.191500 5.151500 5.159400 5.669600 5.087000 6.098400 6.147800 +5.758500 5.970300 5.500600 5.334700 6.209800 6.813000 6.486800 7.537900 6.307400 5.496100 +5.540500 5.648600 5.764700 5.439500 5.240700 5.696200 6.983000 6.788300 5.502300 5.642000 +5.446200 6.043000 6.056700 6.282900 6.528400 6.260600 5.938800 5.681700 6.247400 5.564100 +5.572400 7.095900 5.623700 5.794600 6.043000 5.577700 6.276600 5.304900 6.235300 5.737200 +5.268800 5.124500 4.965100 5.025600 5.006600 5.002600 5.181800 4.891100 4.650800 4.733300 +4.714200 4.554100 4.635800 4.662100 4.378500 4.484000 4.579700 4.646200 4.538900 4.847400 +4.579600 4.657000 4.368700 4.348400 4.691300 4.573400 4.487200 4.518500 4.565900 4.521200 +4.650500 4.267200 4.386800 4.284500 4.387000 4.452300 4.310300 4.388000 4.488500 4.249100 +4.505000 4.523500 4.330800 4.468500 4.534600 4.234700 4.550600 4.397000 4.467800 4.605600 + +=== SDAA === +4.732200 5.101900 5.626800 5.192600 5.163000 5.230600 5.745600 5.061500 6.252900 6.115400 +5.667200 5.806900 5.462300 5.088700 6.387700 7.127700 6.249800 7.723200 6.648400 5.588700 +5.387000 5.471700 5.711300 5.324100 5.167600 5.854000 6.896900 6.756300 5.429000 5.486700 +5.353800 5.956600 5.952600 6.345500 6.303800 5.499500 5.293800 5.298300 6.012800 5.092400 +5.114700 6.352700 4.922000 5.332700 5.151900 5.297900 5.128800 5.515700 5.725500 5.615100 +4.825800 4.598700 4.931500 5.068800 4.976100 5.066800 5.062600 5.077600 4.870700 4.723000 +4.938200 4.633300 4.860000 4.632900 4.075600 4.573700 4.596200 4.941700 4.772600 4.730500 +4.602500 4.741700 4.502600 4.457800 4.882900 4.477300 4.518200 4.582000 4.402200 4.679700 +4.429900 4.428600 4.467500 4.415800 4.431800 4.496300 4.469200 4.434700 4.498200 4.382200 +4.556100 4.600000 4.306000 4.488700 4.597600 4.219700 4.417100 4.352900 4.563400 4.631000 + +=== RESULT === +MeanRelativeError: -0.011135153214594594 +MeanAbsoluteError: -0.07194500000000004 +Rule,mean_absolute_error -0.07194500000000004 +pass mean_relative_error=-0.011135153214594594 <= 0.05 or mean_absolute_error=-0.07194500000000004 <= 0.0002 diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py b/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py new file mode 100644 index 000000000..4c03a72c6 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py @@ -0,0 +1,2164 @@ +import math +import warnings +from dataclasses import dataclass +from typing import Any, Optional, Tuple, Union + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss +from torch.nn.init import _calculate_fan_in_and_fan_out + +from configuration_siglip2 import Siglip2Config, Siglip2TextConfig, Siglip2VisionConfig + + +@dataclass +class Siglip2VisionOutput(): + """ + 视觉模型的输出基类,包含最后一层隐藏状态的池化结果得到的图像嵌入。 + + Args: + image_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): + 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的图像嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 图像嵌入,用于表示图像的特征。 + + last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): + 模型最后一层输出的隐藏状态序列。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, sequence_length, hidden_size)` + - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 + + hidden_states (tuple(torch.FloatTensor), 可选): + 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 + - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 + + attentions (tuple(torch.FloatTensor), 可选): + 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 + - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 + """ + + image_embeds: Optional[torch.FloatTensor] = None + last_hidden_state: torch.FloatTensor = None + hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None + attentions: Optional[Tuple[torch.FloatTensor, ...]] = None + + +@dataclass +class Siglip2TextOutput(): + """ + 文本模型的输出基类,包含最后一层隐藏状态的池化结果得到的文本嵌入。 + + Args: + text_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): + 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的文本嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 文本嵌入,用于表示文本的特征。 + + last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): + 模型最后一层输出的隐藏状态序列。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, sequence_length, hidden_size)` + - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 + + hidden_states (tuple(torch.FloatTensor), 可选): + 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 + - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 + + attentions (tuple(torch.FloatTensor), 可选): + 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 + - **类型**: `tuple(torch.FloatTensor)` + - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 + - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 + """ + + text_embeds: Optional[torch.FloatTensor] = None + last_hidden_state: torch.FloatTensor = None + hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None + attentions: Optional[Tuple[torch.FloatTensor, ...]] = None + + +@dataclass +class Siglip2Output(): + """ + Siglip2 模型的输出,包含图像-文本对比损失、相似度分数、嵌入以及子模型的输出。 + + Args: + loss (torch.FloatTensor, 可选, 形状为 `(1,)`): + 当 `return_loss=True` 时返回,用于图像-文本相似度的对比损失。 + - **类型**: `torch.FloatTensor` + - **形状**: `(1,)` + - **说明**: 对比损失,用于衡量图像和文本之间的相似度。 + + logits_per_image (torch.FloatTensor, 必填, 形状为 `(image_batch_size, text_batch_size)`): + `image_embeds` 和 `text_embeds` 之间的缩放点积分数,表示图像-文本相似度分数。 + - **类型**: `torch.FloatTensor` + - **形状**: `(image_batch_size, text_batch_size)` + - **说明**: 图像-文本相似度分数,用于评估图像和文本之间的匹配程度。 + + logits_per_text (torch.FloatTensor, 必填, 形状为 `(text_batch_size, image_batch_size)`): + `text_embeds` 和 `image_embeds` 之间的缩放点积分数,表示文本-图像相似度分数。 + - **类型**: `torch.FloatTensor` + - **形状**: `(text_batch_size, image_batch_size)` + - **说明**: 文本-图像相似度分数,用于评估文本和图像之间的匹配程度。 + + text_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 文本嵌入,用于表示文本的特征。 + + image_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 + - **类型**: `torch.FloatTensor` + - **形状**: `(batch_size, output_dim)` + - **说明**: 图像嵌入,用于表示图像的特征。 + + text_model_output (BaseModelOutputWithPooling): + [`Siglip2TextModel`] 的输出。 + - **类型**: `BaseModelOutputWithPooling` + - **说明**: 包含文本模型的详细输出信息,如隐藏状态等。 + + vision_model_output (BaseModelOutputWithPooling): + [`Siglip2VisionModel`] 的输出。 + - **类型**: `BaseModelOutputWithPooling` + - **说明**: 包含视觉模型的详细输出信息,如隐藏状态等。 + """ + + loss: Optional[torch.FloatTensor] = None + logits_per_image: torch.FloatTensor = None + logits_per_text: torch.FloatTensor = None + text_embeds: torch.FloatTensor = None + image_embeds: torch.FloatTensor = None + text_model_output: BaseModelOutputWithPooling = None + vision_model_output: BaseModelOutputWithPooling = None + + def to_tuple(self) -> Tuple[Any]: + """ + 将 Siglip2Output 对象转换为元组。 + + Returns: + Tuple[Any]: 包含 Siglip2Output 对象的各个属性值的元组。 + """ + return tuple( + self[k] if k not in ["text_model_output", "vision_model_output"] else getattr(self, k).to_tuple() + for k in self.keys() + ) + + +class Siglip2VisionEmbeddings(nn.Module): + """ + Siglip2 视觉嵌入模块,用于将图像像素值转换为嵌入向量,并添加位置嵌入。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含模型的各种配置参数,如隐藏层大小、patch 大小等。 + """ + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # patch 大小,表示每个图像块的高度和宽度 + self.patch_size = config.patch_size + + # 定义一个线性层,用于将每个图像 patch(像素块)映射到嵌入向量 + self.patch_embedding = nn.Linear( + # 输入特征数:通道数 * patch大小平方 + in_features=config.num_channels * self.patch_size * self.patch_size, + # 输出特征数:嵌入维度 + out_features=self.embed_dim, + ) + + # 图像被分割成的总patch数量 + self.num_patches = config.num_patches + # 计算位置嵌入的网格大小(假设图像是正方形) + self.position_embedding_size = int(self.num_patches**0.5) + # 定义一个嵌入层,用于存储每个patch的位置嵌入 + self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) + + @staticmethod + def resize_positional_embeddings( + positional_embeddings: torch.Tensor, + spatial_shapes: torch.LongTensor, + max_length: int, + ) -> torch.Tensor: + """ + 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小。 + + Args: + positional_embeddings (`torch.Tensor`): + 位置嵌入张量,形状为 (高度, 宽度, 嵌入维度)。 + spatial_shapes (`torch.LongTensor`): + 空间形状张量,形状为 (batch_size, 2),用于调整位置嵌入的大小。 + 每个元素包含 [目标高度, 目标宽度]。 + max_length (`int`): + 填充后的最大长度,用于确保所有批次的位置嵌入具有相同的长度。 + + Returns: + `torch.Tensor`: + 调整大小并填充后的嵌入张量,形状为 (batch_size, max_length, 嵌入维度)。 + """ + # 获取批次大小 + batch_size = spatial_shapes.shape[0] + # 获取嵌入维度 + embed_dim = positional_embeddings.shape[-1] + # 记录原始数据类型 + source_dtype = positional_embeddings.dtype + + # 创建一个空的张量,用于存储调整后的位置嵌入 + resulted_positional_embeddings = torch.empty( + (batch_size, max_length, embed_dim), + device=positional_embeddings.device, + dtype=source_dtype, + ) + + # 将位置嵌入的维度顺序从 (高度, 宽度, 嵌入维度) 转换为 (嵌入维度, 高度, 宽度) 以便进行插值 + positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0) + + # 如果设备是 CPU,则将数据类型上转换为 float32,因为 CPU 不支持 bfloat16/float16 的 antialias + if positional_embeddings.device.type == "cpu": + positional_embeddings = positional_embeddings.to(torch.float32) + + for i in range(batch_size): + # 获取当前批次的目标高度和宽度 + # (1, dim, height, width) -> (1, dim, target_height, target_width) + height, width = spatial_shapes[i] + # 对位置嵌入进行双线性插值,调整到目标尺寸 + resized_embeddings = F.interpolate( + positional_embeddings, + size=(height, width), + mode="bilinear", + align_corners=False, + antialias=True, + ) + + # 将调整后的嵌入形状从 (1, 嵌入维度, 高度, 宽度) 转换为 (高度 * 宽度, 嵌入维度) + # (1, dim, target_height, target_width) -> (target_height * target_width, dim) + resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1) + + # 将数据类型转换回原始类型 + resized_embeddings = resized_embeddings.to(source_dtype) + + # 将调整后的嵌入填充到结果张量中 + resulted_positional_embeddings[i, : height * width] = resized_embeddings + # 对于不足的部分,用第一个位置的嵌入填充 + resulted_positional_embeddings[i, height * width :] = resized_embeddings[0] + + return resulted_positional_embeddings + + def forward(self, pixel_values: torch.FloatTensor, spatial_shapes: torch.LongTensor) -> torch.Tensor: + """ + 前向传播方法,用于生成图像嵌入。 + + Args: + pixel_values (`torch.FloatTensor`): + 像素值张量,形状为 (batch_size, 最大patch数量, 通道数 * patch大小平方)。 + spatial_shapes (`List[Tuple[int, int]]`): + 空间形状列表,形状为 (batch_size, 2),用于调整位置嵌入的大小。 + 每个元素包含 [高度, 宽度]。 + + Returns: + `torch.Tensor`: + 生成的图像嵌入张量,形状为 (batch_size, 最大patch数量, 嵌入维度)。 + """ + # 将像素值张量转换为与patch_embedding权重相同的类型 + target_dtype = self.patch_embedding.weight.dtype + patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype)) + + # 获取位置嵌入,并调整其形状为 (高度, 宽度, 嵌入维度) + positional_embeddings = self.position_embedding.weight.reshape( + self.position_embedding_size, self.position_embedding_size, -1 + ) + + # 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小 + resized_positional_embeddings = self.resize_positional_embeddings( + positional_embeddings, spatial_shapes, max_length=pixel_values.shape[1] + ) + + # 将位置嵌入添加到patch嵌入中,得到最终的图像嵌入 + embeddings = patch_embeds + resized_positional_embeddings + return embeddings + + +class Siglip2Attention(nn.Module): + """ + 多头注意力机制,源自论文 'Attention Is All You Need'。 + + Args: + config: + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小,也是注意力机制的嵌入维度。 + - num_attention_heads (int): 注意力头的数量。 + - attention_dropout (float): 注意力权重在 dropout 时的丢弃概率。 + """ + + def __init__(self, config): + super().__init__() + self.config = config + # 注意力机制的嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # 注意力头的数量 + self.num_heads = config.num_attention_heads + # 每个注意力头的维度 + self.head_dim = self.embed_dim // self.num_heads + + # 检查 embed_dim 是否能被 num_heads 整除 + if self.head_dim * self.num_heads != self.embed_dim: + raise ValueError( + f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:" + f" {self.num_heads})." + ) + + # 缩放因子,用于缩放注意力得分 + self.scale = self.head_dim**-0.5 + self.dropout = config.attention_dropout + + # 定义线性层,用于计算查询 (query)、键 (key) 和值 (value) 的投影 + self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) + self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) + + # 输出投影层 + self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + """ + 前向传播方法,计算多头注意力。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.Tensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`, 可选): + 是否返回注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + """ + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # 重塑查询、键和值张量,以适应多头注意力的计算 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + + # 获取键和值序列的长度 + k_v_seq_len = key_states.shape[-2] + # 计算原始的注意力得分 + attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale + + # 检查注意力权重的形状是否正确 + if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len): + raise ValueError( + f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is" + f" {attn_weights.size()}" + ) + + # 如果提供了注意力掩码,则将其添加到注意力得分中 + if attention_mask is not None: + if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len): + raise ValueError( + f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, but is {attention_mask.size()}" + ) + attn_weights = attn_weights + attention_mask + + # 将注意力得分转换为 float32 以进行 softmax 计算,然后转换回原始数据类型 + attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) + attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) + + # 计算最终的注意力输出 + attn_output = torch.matmul(attn_weights, value_states) + + # 检查注意力输出的形状是否正确 + if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim): + raise ValueError( + f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is" + f" {attn_output.size()}" + ) + + # 重塑注意力输出张量,以适应后续的处理 + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim) + + # 应用输出投影层 + attn_output = self.out_proj(attn_output) + + # 根据需要返回注意力权重 + return attn_output, attn_weights + + +class Siglip2FlashAttention2(Siglip2Attention): + """ + Siglip2Attention 的 Flash Attention 模块。该模块继承自 `Siglip2Attention`,因此模型的权重保持不变。 + 唯一需要修改的是前向传播方法,需要正确调用 Flash Attention 的公共 API,并处理输入中可能存在的填充 token。 + + Attributes: + is_causal (bool): + 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 + """ + + is_causal = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Adapted from transformers.models.llama.modeling_llama.LlamaFlashAttention2.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.LongTensor] = None, + output_attentions: bool = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + """ + 前向传播方法,计算 Flash Attention。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.LongTensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`): + 是否输出注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + - `attn_weights_tuple`: 其他注意力权重信息(可选)。 + """ + output_attentions = False + + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # Flash Attention 要求输入的形状为 (batch_size, seq_length, head_dim, hidden_dim) + # 因此我们保持原始形状不变 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim) + + dropout_rate = self.dropout if self.training else 0.0 + + # 在 PEFT 中,通常我们将层归一化层转换为 float32 以提高训练稳定性 + # 因此,输入的隐藏状态会被静默地转换为 float32。因此,我们需要将其转换回正确的类型,以确保一切按预期工作。 + # 这种转换可能会减慢训练和推理速度,因此建议不要将 LayerNorms 转换为 fp32。 + + input_dtype = query_states.dtype + if input_dtype == torch.float32: + if torch.is_autocast_enabled(): + target_dtype = torch.get_autocast_gpu_dtype() + # 处理模型量化的情形 + elif hasattr(self.config, "_pre_quantization_dtype"): + target_dtype = self.config._pre_quantization_dtype + else: + target_dtype = self.q_proj.weight.dtype + + logger.warning_once( + f"The input hidden states seems to be silently casted in float32, this might be related to" + f" the fact you have upcasted embedding or layer norm layers in float32. We will cast back the input in" + f" {target_dtype}." + ) + + query_states = query_states.to(target_dtype) + key_states = key_states.to(target_dtype) + value_states = value_states.to(target_dtype) + + # 调用 Flash Attention 的前向传播方法 + attn_output = _flash_attention_forward( + query_states, + key_states, + value_states, + attention_mask, + q_len, + dropout=dropout_rate, + is_causal=self.is_causal, + use_top_left_mask=self._flash_attn_uses_top_left_mask, + ) + + # 重塑注意力输出张量 + attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim).contiguous() + attn_output = self.out_proj(attn_output) + + if not output_attentions: + attn_weights = None + + # 返回注意力输出和可选的注意力权重 + return attn_output, attn_weights + + +class Siglip2SdpaAttention(Siglip2Attention): + """ + 使用 torch.nn.functional.scaled_dot_product_attention 的 Siglip2 注意力模块。该模块继承自 `Siglip2Attention`,因为模块的权重保持不变。 + 唯一的变化是在前向传播方法中,以适应 SDPA API。 + + Attributes: + is_causal (bool): + 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 + """ + + is_causal = False + + # Adapted from Siglip2Attention.forward and transformers.models.llama.modeling_llama.LlamaSdpaAttention.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + """ + 前向传播方法,计算使用 SDPA 的注意力。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 + attention_mask (`torch.Tensor`, 可选): + 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 + output_attentions (`bool`, 可选): + 是否输出注意力权重。 + + Returns: + `Tuple[torch.Tensor, Optional[torch.Tensor]]`: + - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 + """ + if output_attentions: + return super().forward( + hidden_states=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + ) + + # 获取批次大小和时间步长度 + batch_size, q_len, _ = hidden_states.size() + + # 计算查询 (query)、键 (key) 和值 (value) 的投影 + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + # 重塑张量以适应多头注意力的计算 + query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) + + if query_states.device.type == "cuda" and attention_mask is not None: + query_states = query_states.contiguous() + key_states = key_states.contiguous() + value_states = value_states.contiguous() + + # 我们通过 `is_causal` 语句而不是 SDPA 中的内联条件分配来调度到 SDPA 的 Flash Attention 或 Efficient 内核, + # 以支持 torch.compile 的动态形状和完整图选项。内联条件会阻止动态形状的编译。 + is_causal = True if self.is_causal and q_len > 1 else False + + attn_output = torch.nn.functional.scaled_dot_product_attention( + query_states, + key_states, + value_states, + attn_mask=attention_mask, + dropout_p=self.dropout if self.training else 0.0, + is_causal=is_causal, + ) + + # 重塑注意力输出张量 + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.view(batch_size, q_len, self.embed_dim) + + # 应用输出投影层 + attn_output = self.out_proj(attn_output) + + # 返回注意力输出,不返回注意力权重 + return attn_output, None + + +class Siglip2MLP(nn.Module): + """ + Siglip2 的多层感知机(MLP)模块,用于在注意力机制之后进行非线性变换。 + + Args: + config: + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 + """ + def __init__(self, config): + super().__init__() + self.config = config + # 根据配置选择激活函数 + self.activation_fn = ACT2FN[config.hidden_act] + # 定义第一个全连接层,将隐藏层大小映射到中间层大小 + self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) + # 定义第二个全连接层,将中间层大小映射回隐藏层大小 + self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + """ + 前向传播方法,应用 MLP 变换。 + + Args: + hidden_states (`torch.Tensor`): + 输入张量,形状为 `(batch_size, seq_length, hidden_size)`。 + + Returns: + `torch.Tensor`: + 变换后的张量,形状为 `(batch_size, seq_length, hidden_size)`。 + """ + # 应用第一个全连接层 + hidden_states = self.fc1(hidden_states) + # 应用激活函数 + hidden_states = self.activation_fn(hidden_states) + # 应用第二个全连接层 + hidden_states = self.fc2(hidden_states) + # 返回变换后的张量 + return hidden_states + + +# 定义注意力机制的实现类映射 +SIGLIP2_ATTENTION_CLASSES = { + "eager": Siglip2Attention, + "flash_attention_2": Siglip2FlashAttention2, + "sdpa": Siglip2SdpaAttention, +} + + +class Siglip2EncoderLayer(nn.Module): + """ + Siglip2 的编码器层,包含自注意力机制和 MLP 模块。 + + Args: + config (Siglip2Config): + 模型配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 + """ + def __init__(self, config: Siglip2Config): + super().__init__() + + # 嵌入维度,通常与隐藏层大小相同 + self.embed_dim = config.hidden_size + # 根据配置选择注意力机制的实现类,并实例化 + self.self_attn = SIGLIP2_ATTENTION_CLASSES[config._attn_implementation](config=config) + # 定义第一个层归一化层 + self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + # 实例化 MLP 模块 + self.mlp = Siglip2MLP(config) + # 定义第二个层归一化层 + self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor, + output_attentions: Optional[bool] = False, + ) -> Tuple[torch.FloatTensor]: + """ + 前向传播方法,处理输入张量通过自注意力和 MLP。 + + Args: + hidden_states (`torch.FloatTensor`): + 输入张量,形状为 `(batch_size, seq_len, embed_dim)`。 + attention_mask (`torch.FloatTensor`): + 注意力掩码张量,形状为 `(batch_size, 1, q_len, k_v_seq_len)`,其中填充元素由非常大的负值表示。 + output_attentions (`bool`, 可选, 默认值为 `False`): + 是否返回所有注意力层的注意力权重。 + + Returns: + `Tuple[torch.FloatTensor]`: + - `hidden_states`: 变换后的隐藏状态,形状为 `(batch_size, seq_len, embed_dim)`。 + - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 `(batch_size, num_heads, q_len, k_v_seq_len)`。 + """ + # 保存残差连接的输入 + residual = hidden_states + + # 应用第一个层归一化 + hidden_states = self.layer_norm1(hidden_states) + # 应用自注意力机制 + hidden_states, attn_weights = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + ) + # 残差连接 + hidden_states = residual + hidden_states + + # 保存残差连接的输入 + residual = hidden_states + + # 应用第二个层归一化 + hidden_states = self.layer_norm2(hidden_states) + # 应用 MLP + hidden_states = self.mlp(hidden_states) + # 残差连接 + hidden_states = residual + hidden_states + # 打包输出 + outputs = (hidden_states,) + + if output_attentions: + # 如果需要,添加注意力权重到输出 + outputs += (attn_weights,) + + return outputs + + +class Siglip2Encoder(nn.Module): + """ + Transformer 编码器,由 `config.num_hidden_layers` 个自注意力层组成。每个层都是 [`Siglip2EncoderLayer`] 的实例。 + + Args: + config (Siglip2Config): + 模型配置对象,包含以下属性: + - num_hidden_layers (int): 编码器的层数。 + - 其他配置参数,如 hidden_size, intermediate_size, hidden_act, layer_norm_eps, output_attentions, output_hidden_states, use_return_dict 等。 + """ + + def __init__(self, config: Siglip2Config): + super().__init__() + self.config = config + self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) + self.gradient_checkpointing = False + + # Ignore copy + def forward( + self, + inputs_embeds, + attention_mask: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入嵌入通过多个自注意力层。 + + Args: + inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`): + 可选地,直接传递嵌入表示,而不是传递 `input_ids`。这在您希望对如何将 `input_ids` 索引转换为关联向量有更多控制时非常有用。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 掩码张量,用于避免在填充 token 索引上执行注意力计算。掩码值选择 `[0, 1]`: + + - `1` 表示 **未掩码** 的 token, + - `0` 表示 **掩码** 的 token。 + + [什么是注意力掩码?](../glossary#attention-mask) + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutput]`: + - 如果 `return_dict=True`,返回 `BaseModelOutput` 对象。 + - 否则,返回包含 `hidden_states`, `encoder_states`, `all_attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 初始化存储隐藏状态和注意力权重的容器 + encoder_states = () if output_hidden_states else None + all_attentions = () if output_attentions else None + + # 设置初始隐藏状态为输入嵌入 + hidden_states = inputs_embeds + + # 遍历所有编码器层 + for encoder_layer in self.layers: + if output_hidden_states: + # 存储当前隐藏状态 + encoder_states = encoder_states + (hidden_states,) + + # 如果启用梯度检查点,则使用检查点机制 + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + encoder_layer.__call__, + hidden_states, + attention_mask, + output_attentions, + ) + else: + # 否则,正常调用编码器层的前向传播方法 + layer_outputs = encoder_layer( + hidden_states, + attention_mask, + output_attentions=output_attentions, + ) + + # 更新隐藏状态 + hidden_states = layer_outputs[0] + + if output_attentions: + # 存储注意力权重 + all_attentions = all_attentions + (layer_outputs[1],) + + if output_hidden_states: + # 存储最终隐藏状态 + encoder_states = encoder_states + (hidden_states,) + + if not return_dict: + return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None) + return BaseModelOutput( + last_hidden_state=hidden_states, hidden_states=encoder_states, attentions=all_attentions + ) + + +# 视觉输入参数的中文文档字符串 +SIGLIP2_VISION_INPUTS_DOCSTRING = r""" +Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 像素值。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。详情见 [`CLIPImageProcessor.__call__`]。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 + interpolate_pos_encoding (`bool`, *optional*, defaults to `False`): + 是否插值预训练的位置编码。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 +""" + + +class Siglip2VisionTransformer(nn.Module): + """ + Siglip2 的视觉 Transformer 模型,包含图像嵌入、编码器、层归一化以及可选的头部模块。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - vision_use_head (bool): 是否使用头部模块。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + + # 实例化视觉嵌入模块 + self.embeddings = Siglip2VisionEmbeddings(config) + # 实例化编码器模块 + self.encoder = Siglip2Encoder(config) + # 实例化后置层归一化层 + self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) + # 判断是否使用头部模块 + self.use_head = True if not hasattr(config, "vision_use_head") else config.vision_use_head + if self.use_head: + self.head = Siglip2MultiheadAttentionPoolingHead(config) + + # 判断是否使用 Flash Attention 2 + self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" + + def forward( + self, + pixel_values: torch.FloatTensor, + attention_mask: torch.Tensor, + spatial_shapes: torch.LongTensor, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入图像像素值通过视觉 Transformer 模型。 + + Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 像素值张量。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): + 空间形状张量,用于调整位置嵌入的大小。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 应用视觉嵌入模块,将像素值转换为嵌入向量 + hidden_states = self.embeddings(pixel_values, spatial_shapes) + + # 处理注意力掩码 + if attention_mask is not None and not self._use_flash_attention_2: + # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] + encoder_attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) + else: + encoder_attention_mask = attention_mask + + # 应用编码器模块 + encoder_outputs = self.encoder( + inputs_embeds=hidden_states, + attention_mask=encoder_attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取最后一层的隐藏状态 + last_hidden_state = encoder_outputs[0] + # 应用后置层归一化 + last_hidden_state = self.post_layernorm(last_hidden_state) + + # 应用头部模块(如果使用) + pooler_output = self.head(last_hidden_state, attention_mask) if self.use_head else None + + # 根据 return_dict 参数返回不同的输出格式 + if not return_dict: + return (last_hidden_state, pooler_output) + encoder_outputs[1:] + + return BaseModelOutputWithPooling( + last_hidden_state=last_hidden_state, + pooler_output=pooler_output, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + ) + + +class Siglip2TextEmbeddings(nn.Module): + """ + Siglip2 的文本嵌入模块,用于将输入的 token ID 转换为嵌入向量,并添加位置嵌入。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - vocab_size (int): 词汇表大小。 + - hidden_size (int): 隐藏层大小。 + - max_position_embeddings (int): 最大位置嵌入长度。 + """ + def __init__(self, config: Siglip2TextConfig): + super().__init__() + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + + # 定义 token 嵌入层,将 token ID 转换为嵌入向量 + self.token_embedding = nn.Embedding(config.vocab_size, embed_dim) + # 定义位置嵌入层,为每个位置生成位置嵌入 + self.position_embedding = nn.Embedding(config.max_position_embeddings, embed_dim) + + # 注册一个缓冲区,存储位置 ID 张量,并在模型保存时不进行序列化 + self.register_buffer( + "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False + ) + + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + position_ids: Optional[torch.LongTensor] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + ) -> torch.Tensor: + """ + 前向传播方法,将输入的 token ID 或嵌入向量转换为包含位置嵌入的嵌入向量。 + + Args: + input_ids (`torch.LongTensor`, *optional*): + 输入的 token ID 张量,形状为 `(batch_size, sequence_length)`。 + position_ids (`torch.LongTensor`, *optional*): + 位置 ID 张量,形状为 `(batch_size, sequence_length)`。如果未提供,则根据 `input_ids` 自动生成。 + inputs_embeds (`torch.FloatTensor`, *optional*): + 预先计算的输入嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。如果提供,则忽略 `input_ids`。 + + Returns: + `torch.Tensor`: + 包含位置嵌入的嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。 + """ + # 获取序列长度 + seq_length = input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2] + # 获取最大位置嵌入长度 + max_position_embedding = self.position_embedding.weight.shape[0] + + # 检查序列长度是否超过最大位置嵌入长度 + if seq_length > max_position_embedding: + raise ValueError( + f"Sequence length must be less than max_position_embeddings (got `sequence length`: " + f"{seq_length} and max_position_embeddings: {max_position_embedding}" + ) + + # 如果未提供位置 ID,则自动生成 + if position_ids is None: + position_ids = self.position_ids[:, :seq_length] + + # 如果未提供输入嵌入向量,则使用 token 嵌入层进行嵌入 + if inputs_embeds is None: + inputs_embeds = self.token_embedding(input_ids) + + # 获取位置嵌入 + position_embeddings = self.position_embedding(position_ids) + + # 将 token 嵌入和位置嵌入相加,得到最终的嵌入向量 + embeddings = inputs_embeds + position_embeddings + + return embeddings + + +def _trunc_normal_(tensor, mean, std, a, b): + """ + 对输入张量进行截断正态分布初始化。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + mean (float): 正态分布的均值。 + std (float): 正态分布的标准差。 + a (float): 截断的下界。 + b (float): 截断的上界。 + """ + def norm_cdf(x): + """ + 计算标准正态分布的累积分布函数(CDF)。 + + Args: + x (float): 输入值。 + + Returns: + float: 标准正态分布的 CDF 值。 + """ + return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0 + + # 检查均值是否在截断范围内超过2个标准差 + if (mean < a - 2 * std) or (mean > b + 2 * std): + warnings.warn( + "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. " + "The distribution of values may be incorrect.", + stacklevel=2, + ) + + # 通过使用截断的均匀分布,然后使用正态分布的逆 CDF 来生成值。 + # 获取上下 CDF 值 + l = norm_cdf((a - mean) / std) + u = norm_cdf((b - mean) / std) + + # 将张量均匀地填充为 [2l-1, 2u-1] 范围内的值 + tensor.uniform_(2 * l - 1, 2 * u - 1) + + # 使用逆误差函数(erfinv)进行逆 CDF 变换,得到截断的标准正态分布 + tensor.erfinv_() + + # 将分布转换为指定的均值和标准差 + tensor.mul_(std * math.sqrt(2.0)) + tensor.add_(mean) + + # 截断以确保值在正确的范围内 + tensor.clamp_(min=a, max=b) + + +def trunc_normal_tf_( + tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0, a: float = -2.0, b: float = 2.0 +) -> torch.Tensor: + """ + 使用截断正态分布填充输入张量。值实际上是从正态分布 :math:`\\mathcal{N}(\\text{mean}, \\text{std}^2)` 中抽取的, + 超出 :math:`[a, b]` 的值将被重新抽取,直到它们在边界内。该方法在 :math:`a \\leq \\text{mean} \\leq b` 时效果最佳。 + + 注意:这个 'tf' 变体更接近于 TensorFlow / JAX 的实现,其中边界 [a, b] 在采样正态分布(均值为0,标准差为1.0)时应用, + 然后结果通过均值和标准差参数进行缩放和平移。 + + Args: + tensor (torch.Tensor): 一个 n 维的 `torch.Tensor`。 + mean (float): 正态分布的均值,默认为0.0。 + std (float): 正态分布的标准差,默认为1.0。 + a (float): 最小截断值,默认为-2.0。 + b (float): 最大截断值,默认为2.0。 + """ + with torch.no_grad(): + # 使用标准正态分布(均值为0,标准差为1.0)进行截断初始化 + _trunc_normal_(tensor, 0, 1.0, a, b) + # 将分布缩放和平移到指定的均值和标准差 + tensor.mul_(std).add_(mean) + + +def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"): + """ + 方差缩放初始化方法,用于根据指定的模式和分布初始化张量。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + scale (float, optional): 缩放因子,默认为1.0。 + mode (str, optional): 缩放模式,可以是 'fan_in', 'fan_out' 或 'fan_avg',默认为 'fan_in'。 + distribution (str, optional): 分布类型,可以是 'truncated_normal', 'normal' 或 'uniform',默认为 'normal'。 + """ + fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor) + if mode == "fan_in": + denom = fan_in + elif mode == "fan_out": + denom = fan_out + elif mode == "fan_avg": + denom = (fan_in + fan_out) / 2 + + variance = scale / denom + + if distribution == "truncated_normal": + # 标准正态分布截断到 (-2, 2) 的标准差常数 + trunc_normal_tf_(tensor, std=math.sqrt(variance) / 0.87962566103423978) + elif distribution == "normal": + with torch.no_grad(): + tensor.normal_(std=math.sqrt(variance)) + elif distribution == "uniform": + bound = math.sqrt(3 * variance) + with torch.no_grad(): + tensor.uniform_(-bound, bound) + else: + raise ValueError(f"invalid distribution {distribution}") + + +def lecun_normal_(tensor): + """ + LeCun 正态分布初始化方法。 + + 使用方差缩放初始化方法,模式为 'fan_in',分布为 'truncated_normal'。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + """ + variance_scaling_(tensor, mode="fan_in", distribution="truncated_normal") + + +def default_flax_embed_init(tensor): + """ + 默认的 Flax 嵌入初始化方法。 + + 使用方差缩放初始化方法,模式为 'fan_in',分布为 'normal'。 + + Args: + tensor (torch.Tensor): 需要初始化的张量。 + """ + variance_scaling_(tensor, mode="fan_in", distribution="normal") + + +class Siglip2TextTransformer(nn.Module): + """ + Siglip2 的文本 Transformer 模型,包含文本嵌入、编码器、最终层归一化以及线性头。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - projection_size (int): 线性头的输出维度。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + def __init__(self, config: Siglip2TextConfig): + super().__init__() + self.config = config + # 嵌入维度,通常与隐藏层大小相同 + embed_dim = config.hidden_size + # 实例化文本嵌入模块 + self.embeddings = Siglip2TextEmbeddings(config) + # 实例化编码器模块 + self.encoder = Siglip2Encoder(config) + # 实例化最终层归一化层 + self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) + + # 定义线性头,将隐藏状态映射到指定的投影大小 + self.head = nn.Linear(embed_dim, config.projection_size) + # 判断是否使用 Flash Attention 2 + self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" + + def forward( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入文本通过文本 Transformer 模型。 + + Args: + input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + # 根据配置或传入参数设置输出标志 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if input_ids is None: + raise ValueError("You have to specify input_ids") + + # 获取输入形状 + input_shape = input_ids.size() + # 重塑张量 + input_ids = input_ids.view(-1, input_shape[-1]) + + # 应用文本嵌入模块,将 token ID 转换为嵌入向量 + hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids) + + # 注意:Siglip2 的文本模型不像原始的 CLIP 模型那样使用因果掩码。 + # 扩展注意力掩码 + if attention_mask is not None and not self._use_flash_attention_2: + # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] + attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) + + # 应用编码器模块 + encoder_outputs = self.encoder( + inputs_embeds=hidden_states, + attention_mask=attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取最后一层的隐藏状态 + last_hidden_state = encoder_outputs[0] + # 应用最终层归一化 + last_hidden_state = self.final_layer_norm(last_hidden_state) + + # 假设使用 "sticky" EOS tokenization,最后一个 token 始终是 EOS。 + # 取最后一个 token 的隐藏状态作为池化输出 + pooled_output = last_hidden_state[:, -1, :] + # 应用线性头 + pooled_output = self.head(pooled_output) + + if not return_dict: + # 返回元组形式的输出 + return (last_hidden_state, pooled_output) + encoder_outputs[1:] + + return BaseModelOutputWithPooling( + last_hidden_state=last_hidden_state, + pooler_output=pooled_output, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + ) + + +class Siglip2PreTrainedModel(PreTrainedModel): + """ + 一个抽象类,用于处理权重初始化以及提供一个简单的接口用于下载和加载预训练模型。 + + Attributes: + config_class (Siglip2Config): 配置类。 + base_model_prefix (str): 模型前缀,用于保存和加载模型时的命名。 + supports_gradient_checkpointing (bool): 是否支持梯度检查点。 + """ + + config_class = Siglip2Config + base_model_prefix = "siglip2" + supports_gradient_checkpointing = True + + # 不需要分割的模块列表 + _no_split_modules = [ + "Siglip2TextEmbeddings", + "Siglip2EncoderLayer", + "Siglip2VisionEmbeddings", + "Siglip2EncoderLayer", + "Siglip2MultiheadAttentionPoolingHead", + ] + # 是否支持 Flash Attention 2 + _supports_flash_attn_2 = True + # 是否支持 SDPA + _supports_sdpa = True + + def _init_weights(self, module): + """ + 初始化模型权重。 + + Args: + module (nn.Module): 需要初始化的模块。 + """ + if isinstance(module, Siglip2VisionEmbeddings): + width = ( + self.config.vision_config.hidden_size + if isinstance(self.config, Siglip2Config) + else self.config.hidden_size + ) + # 初始化位置嵌入权重 + nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width)) + elif isinstance(module, nn.Embedding): + default_flax_embed_init(module.weight) # 使用默认的 Flax 嵌入初始化方法 + elif isinstance(module, Siglip2Attention): + nn.init.xavier_uniform_(module.q_proj.weight) # 初始化查询投影权重 + nn.init.xavier_uniform_(module.k_proj.weight) # 初始化键投影权重 + nn.init.xavier_uniform_(module.v_proj.weight) # 初始化值投影权重 + nn.init.xavier_uniform_(module.out_proj.weight) # 初始化输出投影权重 + nn.init.zeros_(module.q_proj.bias) # 初始化查询投影偏置 + nn.init.zeros_(module.k_proj.bias) # 初始化键投影偏置 + nn.init.zeros_(module.v_proj.bias) # 初始化值投影偏置 + nn.init.zeros_(module.out_proj.bias) # 初始化输出投影偏置 + elif isinstance(module, Siglip2MLP): + nn.init.xavier_uniform_(module.fc1.weight) # 初始化第一个全连接层权重 + nn.init.xavier_uniform_(module.fc2.weight) # 初始化第二个全连接层权重 + nn.init.normal_(module.fc1.bias, std=1e-6) # 初始化第一个全连接层偏置 + nn.init.normal_(module.fc2.bias, std=1e-6) # 初始化第二个全连接层偏置 + elif isinstance(module, Siglip2MultiheadAttentionPoolingHead): + nn.init.xavier_uniform_(module.probe.data) # 初始化探测数据 + nn.init.xavier_uniform_(module.attention.in_proj_weight.data) # 初始化注意力输入投影权重 + nn.init.zeros_(module.attention.in_proj_bias.data) # 初始化注意力输入投影偏置 + elif isinstance(module, Siglip2Model): + logit_scale_init = torch.log(torch.tensor(1.0)) # 初始化 logit scale + module.logit_scale.data.fill_(logit_scale_init) + module.logit_bias.data.zero_() # 初始化 logit 偏置 + elif isinstance(module, Siglip2ForImageClassification): + # 初始化分类器权重 + nn.init.normal_( + module.classifier.weight, + std=self.config.vision_config.hidden_size**-0.5 * self.config.initializer_factor, + ) + elif isinstance(module, (nn.Linear, nn.Conv2d)): + # 使用 LeCun 正态分布初始化线性层或卷积层权重 + lecun_normal_(module.weight) + if module.bias is not None: + # 初始化偏置为零 + nn.init.zeros_(module.bias) + elif isinstance(module, nn.LayerNorm): + # 初始化层归一化偏置为零 + module.bias.data.zero_() + # 初始化层归一化权重为1.0 + module.weight.data.fill_(1.0) + + +class Siglip2TextModel(Siglip2PreTrainedModel): + """ + Siglip2 的文本模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2TextConfig): + 文本模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - projection_size (int): 线性头的输出维度。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - use_return_dict (bool): 是否使用 `ModelOutput` 对象返回结果。 + - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + config_class = Siglip2TextConfig + + def __init__(self, config: Siglip2TextConfig): + super().__init__(config) + self.text_model = Siglip2TextTransformer(config) + # 初始化权重并应用最终处理 + self.post_init() + + def get_input_embeddings(self) -> nn.Module: + """ + 获取输入嵌入层。 + + Returns: + nn.Module: 输入嵌入层,通常是 `token_embedding`。 + """ + # 返回文本嵌入模块中的 token 嵌入层 + return self.text_model.embeddings.token_embedding + + def set_input_embeddings(self, value): + """ + 设置输入嵌入层。 + + Args: + value (nn.Module): 要设置的输入嵌入层。 + """ + # 设置文本嵌入模块中的 token 嵌入层 + self.text_model.embeddings.token_embedding = value + + def forward( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入文本通过文本模型。 + + Args: + input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用文本 Transformer 模型的前向传播方法 + return self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + +class Siglip2MultiheadAttentionPoolingHead(nn.Module): + """ + 多头注意力池化头。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - num_attention_heads (int): 注意力头的数量。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - 其他配置参数,如 intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 + """ + + def __init__(self, config: Siglip2VisionConfig): + super().__init__() + + # 初始化探测参数,形状为 (1, 1, hidden_size) + self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size)) + # 实例化多头注意力层 + self.attention = torch.nn.MultiheadAttention(config.hidden_size, config.num_attention_heads, batch_first=True) + # 实例化层归一化层 + self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) + # 实例化 MLP 模块 + self.mlp = Siglip2MLP(config) + # 注意力头的数量 + self.num_heads = config.num_attention_heads + + def forward(self, hidden_state: torch.Tensor, attention_mask: Optional[torch.Tensor] = None): + """ + 前向传播方法,应用多头注意力池化。 + + Args: + hidden_state (`torch.Tensor`): + 输入隐藏状态,形状为 `(batch_size, sequence_length, hidden_size)`。 + attention_mask (`torch.Tensor`, *optional*): + 注意力掩码张量,形状为 `(batch_size, sequence_length)`,用于屏蔽某些位置。 + + Returns: + `torch.Tensor`: + 池化后的隐藏状态,形状为 `(batch_size, hidden_size)`。 + """ + # 获取批次大小 + batch_size = hidden_state.shape[0] + # 重复探测参数以匹配批次大小 + probe = self.probe.repeat(batch_size, 1, 1) + + if attention_mask is not None: + # 获取目标长度和源长度 + target_len, source_len = probe.shape[1], hidden_state.shape[1] + # 准备 4D 注意力掩码 + attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_state.dtype, target_len) + # 重复掩码以匹配多头 + attention_mask = attention_mask.repeat(1, self.num_heads, target_len, 1) + # 重塑掩码形状 + attention_mask = attention_mask.reshape(-1, target_len, source_len) + + # 应用多头注意力层 + hidden_state = self.attention(probe, hidden_state, hidden_state, attn_mask=attention_mask)[0] + + # 保存残差连接的输入 + residual = hidden_state + # 应用层归一化 + hidden_state = self.layernorm(hidden_state) + # 应用 MLP 并进行残差连接 + hidden_state = residual + self.mlp(hidden_state) + + # 返回池化后的隐藏状态(第一个 token) + return hidden_state[:, 0] + + +class Siglip2VisionModel(Siglip2PreTrainedModel): + """ + Siglip2 的视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2VisionConfig): + 视觉模型的配置对象,包含以下属性: + - hidden_size (int): 隐藏层大小。 + - num_attention_heads (int): 注意力头的数量。 + - intermediate_size (int): MLP 中间层的维度。 + - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 + - layer_norm_eps (float): 层归一化中的 epsilon 值。 + - 其他配置参数,如 num_hidden_layers, attention_dropout, hidden_dropout_prob 等。 + """ + config_class = Siglip2VisionConfig + main_input_name = "pixel_values" + + def __init__(self, config: Siglip2VisionConfig): + super().__init__(config) + + # 实例化视觉 Transformer 模型 + self.vision_model = Siglip2VisionTransformer(config) + + # 初始化权重并应用最终处理 + self.post_init() + + def get_input_embeddings(self) -> nn.Module: + """ + 获取输入嵌入层。 + + Returns: + nn.Module: 输入嵌入层,通常是 `patch_embedding`。 + """ + # 返回视觉嵌入模块中的 patch 嵌入层 + return self.vision_model.embeddings.patch_embedding + + def forward( + self, + pixel_values: torch.FloatTensor, + pixel_attention_mask: torch.Tensor, + spatial_shapes: torch.LongTensor, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入像素值通过视觉模型。 + + Args: + pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + 输入像素值张量。 + pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): + 空间形状张量,用于调整位置嵌入的大小。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, BaseModelOutputWithPooling]`: + - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 + - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 + """ + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉 Transformer 模型的前向传播方法 + return self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + +class Siglip2Model(Siglip2PreTrainedModel): + """ + Siglip2 模型,结合了文本和视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2Config): + 模型的配置对象,包含以下属性: + - text_config (Siglip2TextConfig): 文本模型的配置。 + - vision_config (Siglip2VisionConfig): 视觉模型的配置。 + - 其他配置参数,如 logit_scale, logit_bias 等。 + """ + config_class = Siglip2Config + + def __init__(self, config: Siglip2Config): + super().__init__(config) + + # 检查 text_config 是否为 Siglip2TextConfig 类型 + if not isinstance(config.text_config, Siglip2TextConfig): + raise TypeError( + "config.text_config is expected to be of type Siglip2TextConfig but is of type" + f" {type(config.text_config)}." + ) + + # 检查 vision_config 是否为 Siglip2VisionConfig 类型 + if not isinstance(config.vision_config, Siglip2VisionConfig): + raise TypeError( + "config.vision_config is expected to be of type Siglip2VisionConfig but is of type" + f" {type(config.vision_config)}." + ) + + # 获取文本配置 + text_config = config.text_config + # 获取视觉配置 + vision_config = config.vision_config + + # 首先,使用正确的注意力实现方式初始化文本和视觉模型 + text_model = Siglip2TextModel._from_config(text_config) + vision_model = Siglip2VisionModel._from_config(vision_config) + + # 其次,获取文本和视觉子模块(为了向后兼容) + # 获取文本模型的子模块 + self.text_model = text_model.text_model + # 获取视觉模型的子模块 + self.vision_model = vision_model.vision_model + + self.logit_scale = nn.Parameter(torch.randn(1)) + self.logit_bias = nn.Parameter(torch.randn(1)) + + # 初始化权重并应用最终处理 + self.post_init() + + def get_text_features( + self, + input_ids: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> torch.FloatTensor: + """ + 获取文本特征。 + + Returns: + text_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 + + 示例: + + ```python + >>> from transformers import AutoTokenizer, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> tokenizer = AutoTokenizer.from_pretrained("google/siglip2-base-patch16-224") + + >>> # 重要:确保设置 padding="max_length",因为这是模型训练的方式 + >>> inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding="max_length", return_tensors="pt") + >>> with torch.no_grad(): + ... text_features = model.get_text_features(**inputs) + ``` + """ + # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用文本模型的前向传播方法 + text_outputs = self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取池化输出 + pooled_output = text_outputs[1] + + # 返回文本特征 + return pooled_output + + def get_image_features( + self, + pixel_values: Optional[torch.FloatTensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> torch.FloatTensor: + """ + 获取图像特征。 + + Returns: + image_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): + 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 + + 示例: + + ```python + >>> from PIL import Image + >>> import requests + >>> from transformers import AutoProcessor, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") + + >>> image = Image.open(requests.get(url, stream=True).raw) + + >>> inputs = processor(images=image, return_tensors="pt") + + >>> with torch.no_grad(): + ... image_features = model.get_image_features(**inputs) + ``` + """ + # 使用 Siglip2Model 的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + vision_outputs = self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取池化输出 + pooled_output = vision_outputs[1] + + # 返回图像特征 + return pooled_output + + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + pixel_values: Optional[torch.FloatTensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + return_loss: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, Siglip2Output]: + """ + 前向传播方法,处理输入文本和图像通过 Siglip2 模型。 + + Returns: + + 示例: + + ```python + >>> from PIL import Image + >>> import requests + >>> from transformers import AutoProcessor, AutoModel + >>> import torch + + >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") + >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") + + >>> image = Image.open(requests.get(url, stream=True).raw) + + >>> texts = ["a photo of 2 cats", "a photo of 2 dogs"] + >>> # 重要:我们传递 `padding=max_length`,因为模型是使用这种方式训练的 + >>> inputs = processor(text=texts, images=image, padding="max_length", return_tensors="pt") + + >>> with torch.no_grad(): + ... outputs = model(**inputs) + + >>> logits_per_image = outputs.logits_per_image + >>> probs = torch.sigmoid(logits_per_image) # 这些是概率 + >>> print(f"{probs[0][0]:.1%} that image 0 is '{texts[0]}'") + 31.9% that image 0 is 'a photo of 2 cats' + ``` + """ + # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + vision_outputs = self.vision_model( + pixel_values=pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 调用文本模型的前向传播方法 + text_outputs = self.text_model( + input_ids=input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取图像嵌入 + image_embeds = vision_outputs[1] + # 获取文本嵌入 + text_embeds = text_outputs[1] + + # 归一化特征 + image_embeds = image_embeds / image_embeds.norm(p=2, dim=-1, keepdim=True) + text_embeds = text_embeds / text_embeds.norm(p=2, dim=-1, keepdim=True) + + # 计算余弦相似度作为 logits + logits_per_text = torch.matmul(text_embeds, image_embeds.t().to(text_embeds.device)) + + logit_scale, logit_bias = self.logit_scale.to(text_embeds.device), self.logit_bias.to(text_embeds.device) + logits_per_text = logits_per_text * logit_scale.exp() + logit_bias + + # 转置得到图像-文本的 logits + logits_per_image = logits_per_text.t() + + loss = None + if return_loss: + eye = torch.eye(logits_per_text.size(0), device=logits_per_text.device) + m1_diag1 = -torch.ones_like(logits_per_text) + 2 * eye + loglik = torch.nn.functional.logsigmoid(m1_diag1 * logits_per_text) + nll = -torch.sum(loglik, dim=-1) + loss = nll.mean() + + if not return_dict: + output = (logits_per_image, logits_per_text, text_embeds, image_embeds, text_outputs, vision_outputs) + return ((loss,) + output) if loss is not None else output + + # 返回 Siglip2Output 对象 + return Siglip2Output( + loss=loss, + logits_per_image=logits_per_image, + logits_per_text=logits_per_text, + text_embeds=text_embeds, + image_embeds=image_embeds, + text_model_output=text_outputs, + vision_model_output=vision_outputs, + ) + + +class Siglip2ForImageClassification(Siglip2PreTrainedModel): + """ + Siglip2 的图像分类模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 + + Args: + config (Siglip2Config): + 模型的配置对象,包含以下属性: + - vision_config (Siglip2VisionConfig): 视觉模型的配置。 + - num_labels (int): 分类标签的数量。 + - 其他配置参数,如 problem_type 等。 + """ + main_input_name = "pixel_values" + + def __init__(self, config: Siglip2Config) -> None: + super().__init__(config) + + # 分类标签的数量 + self.num_labels = config.num_labels + + # 使用正确的注意力实现方式创建视觉模型,并仅获取视觉模型的子模块(为了向后兼容) + vision_model = Siglip2VisionModel._from_config(config.vision_config) + self.vision_model = vision_model.vision_model + + # 分类器头 + self.classifier = ( + # 如果 num_labels > 0,则使用线性层作为分类器,否则使用恒等映射 + nn.Linear(config.vision_config.hidden_size, config.num_labels) if config.num_labels > 0 else nn.Identity() + ) + + # 初始化权重并应用最终处理 + self.post_init() + + def forward( + self, + pixel_values: Optional[torch.Tensor] = None, + pixel_attention_mask: Optional[torch.Tensor] = None, + spatial_shapes: Optional[torch.LongTensor] = None, + labels: Optional[torch.Tensor] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ): + """ + 前向传播方法,处理输入图像通过图像分类模型。 + + Args: + pixel_values (`torch.Tensor` of shape `(batch_size, num_channels, height, width)`, *optional*): + 输入像素值张量。 + pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 + spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`, *optional*): + 空间形状张量,用于调整位置嵌入的大小。 + labels (`torch.LongTensor` of shape `(batch_size,)`, *optional*): + 标签,用于计算图像分类/回归损失。索引应在 `[0, ..., config.num_labels - 1]`。 + 如果 `config.num_labels == 1`,则计算回归损失(均方损失),如果 `config.num_labels > 1`,则计算分类损失(交叉熵)。 + output_attentions (`bool`, *optional*): + 是否返回所有注意力层的注意力权重。 + output_hidden_states (`bool`, *optional*): + 是否返回所有层的隐藏状态。 + return_dict (`bool`, *optional*): + 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 + + Returns: + `Union[Tuple, ImageClassifierOutput]`: + - 如果 `return_dict=True`,返回 `ImageClassifierOutput` 对象。 + - 否则,返回包含 `logits`, `hidden_states`, `attentions` 的元组。 + """ + # 设置是否输出注意力 + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + # 设置是否输出隐藏状态 + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # 调用视觉模型的前向传播方法 + outputs = self.vision_model( + pixel_values, + attention_mask=pixel_attention_mask, + spatial_shapes=spatial_shapes, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + # 获取序列输出 + sequence_output = outputs[0] + + # 对 patch tokens 进行平均池化 + if pixel_attention_mask is not None: + # 准备池化掩码 + pool_mask = pixel_attention_mask[..., None].to(sequence_output.device) + # 应用掩码进行池化 + sequence_output = torch.sum(sequence_output * pool_mask, dim=1) / torch.sum(pool_mask, dim=1) + else: + # 否则,直接进行平均池化 + sequence_output = torch.mean(sequence_output, dim=1) + + # 应用分类器 + logits = self.classifier(sequence_output) + + loss = None + if labels is not None: + # 将标签移动到正确的设备以启用模型并行 + labels = labels.to(logits.device) + if self.config.problem_type is None: + if self.num_labels == 1: + # 设置问题类型为回归 + self.config.problem_type = "regression" + elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): + # 设置问题类型为单标签分类 + self.config.problem_type = "single_label_classification" + else: + # 设置问题类型为多标签分类 + self.config.problem_type = "multi_label_classification" + + if self.config.problem_type == "regression": + # 使用均方损失 + loss_fct = MSELoss() + if self.num_labels == 1: + # 计算回归损失 + loss = loss_fct(logits.squeeze(), labels.squeeze()) + else: + loss = loss_fct(logits, labels) + elif self.config.problem_type == "single_label_classification": + # 使用交叉熵损失 + loss_fct = CrossEntropyLoss() + # 计算分类损失 + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + elif self.config.problem_type == "multi_label_classification": + # 使用二元交叉熵损失 + loss_fct = BCEWithLogitsLoss() + # 计算多标签分类损失 + loss = loss_fct(logits, labels) + + if not return_dict: + # 返回元组形式的输出 + output = (logits,) + outputs[2:] + return ((loss,) + output) if loss is not None else output + + # 返回封装后的模型输出 + return ImageClassifierOutput( + loss=loss, + logits=logits, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) + +import torch +import torch.nn as nn + +# 假设 configuration_siglip2 已经存在,或者你需要根据实际情况定义这两个配置类 +# 如果没有配置文件,下面提供了一个最小化的 Mock 类以便代码运行 +try: + from configuration_siglip2 import Siglip2Config, Siglip2VisionConfig +except ImportError: + class Siglip2VisionConfig: + def __init__(self, **kwargs): + for k, v in kwargs.items(): setattr(self, k, v) + + class Siglip2Config: + def __init__(self, vision_config=None, **kwargs): + self.vision_config = vision_config + for k, v in kwargs.items(): setattr(self, k, v) + +class Siglip2ImageNetWrapper(nn.Module): + """ + SigLIP 2 的包装器,用于适配标准的 ImageNet 训练循环。 + 主要作用是自动根据输入生成 `spatial_shapes` 参数。 + """ + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, x, labels=None): + """ + Args: + x: [Batch, Channels, Height, Width] + labels: [Batch] (可选) + """ + # 1. 自动计算 spatial_shapes + # SigLIP 2 需要知道每张图的 (Height, Width) 用于插值位置编码 + # 在 ImageNet 训练中,一个 Batch 内的图片通常大小一致 + b, c, h, w = x.shape + device = x.device + + # 构造形状为 (Batch, 2) 的 tensor,存储 [h, w] + spatial_shapes = torch.tensor([[h, w]], device=device).expand(b, 2) + + # 2. 调用原始模型 + # 注意:这里假设输入的 x 就是 pixel_values + if labels is not None: + return self.model(pixel_values=x, spatial_shapes=spatial_shapes, labels=labels) + else: + return self.model(pixel_values=x, spatial_shapes=spatial_shapes) + +def Model(num_classes=1000, model_size='base', image_size=224, patch_size=16, use_wrapper=True): + """ + 工厂函数:构建用于 ImageNet 训练的 SigLIP 2 模型。 + + Args: + num_classes (int): 分类数量 (ImageNet 默认为 1000) + model_size (str): 'base', 'large', 'so400m' + image_size (int): 输入图像分辨率 (用于计算 num_patches) + patch_size (int): Patch 大小 + use_wrapper (bool): 是否包裹一层 Wrapper 以自动处理 spatial_shapes。 + 如果你是直接替换 ResNet/ViT,建议设为 True。 + + Returns: + model: 配置好的 PyTorch 模型 + """ + + # 1. 定义不同规模的超参数 + # 参考 SigLIP 2 论文或常见 ViT 设置 + configs = { + 'base': { + 'hidden_size': 768, + 'intermediate_size': 3072, + 'num_hidden_layers': 12, + 'num_attention_heads': 12, + }, + 'large': { + 'hidden_size': 1024, + 'intermediate_size': 4096, + 'num_hidden_layers': 24, + 'num_attention_heads': 16, + }, + 'so400m': { # SigLIP 特有的 400M 参数量模型 + 'hidden_size': 1152, + 'intermediate_size': 4304, + 'num_hidden_layers': 27, + 'num_attention_heads': 16, + } + } + + if model_size not in configs: + raise ValueError(f"Unknown model_size: {model_size}. Choose from {list(configs.keys())}") + + param = configs[model_size] + + # 2. 计算 num_patches (SigLIP2VisionEmbeddings 需要这个参数) + # 注意:虽然 SigLIP 2 支持动态分辨率,但在初始化位置编码权重时, + # 依然需要一个基准的 max_num_patches 或者 base_resolution + num_patches = (image_size // patch_size) ** 2 + + # 3. 实例化 Vision Config + vision_config = Siglip2VisionConfig( + hidden_size=param['hidden_size'], + intermediate_size=param['intermediate_size'], + num_hidden_layers=param['num_hidden_layers'], + num_attention_heads=param['num_attention_heads'], + num_channels=3, + image_size=image_size, + patch_size=patch_size, + num_patches=num_patches, # 关键参数 + layer_norm_eps=1e-6, + attention_dropout=0.0, + hidden_act="gelu", # 或者 "gelu_pytorch_tanh" + vision_use_head=False, # 分类任务通常不需要原始的 PoolingHead,而是用 GAP + _attn_implementation="sdpa" # 推荐使用 torch 的 SDPA 加速 + ) + + # 4. 实例化 Global Config + config = Siglip2Config( + vision_config=vision_config, + num_labels=num_classes, + problem_type="single_label_classification" if num_classes > 1 else "regression" + ) + + # 5. 创建模型 + # Siglip2ForImageClassification 已经在你提供的代码中定义了 + model = Siglip2ForImageClassification(config) + + # 6. (可选) 包裹 Wrapper 以自动处理 spatial_shapes + if use_wrapper: + model = Siglip2ImageNetWrapper(model) + + return model \ No newline at end of file diff --git a/PyTorch/build-in/Classification/SigLIP2/weloTrainStep.py b/PyTorch/build-in/Classification/SigLIP2/weloTrainStep.py new file mode 100644 index 000000000..cb13246b2 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/weloTrainStep.py @@ -0,0 +1,693 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +import os +import random +import sys +import time +import json +import argparse +from collections import OrderedDict +from pathlib import Path +import numpy as np +import pandas as pd +from tqdm import tqdm +import importlib + +os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # 强烈推荐在 shell/最顶端设置 +os.environ["PYTHONHASHSEED"] = "12345" +os.environ["OMP_NUM_THREADS"] = "1" +os.environ["MKL_NUM_THREADS"] = "1" + +def ensure_cublas_workspace(config=":4096:8"): + """ + 尝试为 cuBLAS 设置可复现 workspace。强烈建议在主脚本入口处(import torch 之前) + 通过 export 设置该 env。此函数会在运行时设置,但如果 torch 已经被 import, + 则可能为时已晚——函数会打印提醒。 + """ + already = os.environ.get("CUBLAS_WORKSPACE_CONFIG") + if already: + print(f"[seed_utils] CUBLAS_WORKSPACE_CONFIG 已存在:{already}") + else: + os.environ["CUBLAS_WORKSPACE_CONFIG"] = config + print(f"[seed_utils] 已设置 CUBLAS_WORKSPACE_CONFIG={config} (注意:请在 import torch 前设置以保证生效)") + +def set_global_seed(seed: int = 42, set_threads: bool = True): + """ + 统一随机性设置。注意:若希望完全发挥效果,请在主脚本入口(import torch 之前) + 先调用 ensure_cublas_workspace(...) 或在 shell 中 export CUBLAS_WORKSPACE_CONFIG。 + """ + ensure_cublas_workspace() # 会设置 env 并提醒 + os.environ["PYTHONHASHSEED"] = str(seed) + + if set_threads: + os.environ["OMP_NUM_THREADS"] = "1" + os.environ["MKL_NUM_THREADS"] = "1" + + random.seed(seed) + np.random.seed(seed) + + # 现在导入 torch(晚导入以便前面 env 生效) + import torch + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + # 强制确定性(如果存在不确定性算子,PyTorch 会报错并提示) + try: + # torch.use_deterministic_algorithms(True) + torch.use_deterministic_algorithms(True, warn_only=True) + except Exception as e: + print("[seed_utils] 设置 deterministic 模式时出错:", e) + print("[seed_utils] 请确认 CUBLAS_WORKSPACE_CONFIG 已在 import torch 之前设置。") + + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + if set_threads: + torch.set_num_threads(1) + torch.set_num_interop_threads(1) + + print(f"[seed_utils] 全局 seed 已设置为 {seed}") + +set_global_seed(2025) + +""" +通用训练模版(优先从本地导入 Model -> 支持 DDP / 单卡,AMP,resume,日志,checkpoint) +保存为 train_template_localmodel.py +""" +import torch +import torch.nn as nn +import torch.optim as optim +import torch.backends.cudnn as cudnn +import torchvision.transforms as transforms +import torchvision.datasets as datasets +import torchvision.models as tv_models + +import torch.distributed as dist +from torch.nn.parallel import DistributedDataParallel as DDP +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler + +from torch.sdaa import amp +# from torch.cuda import amp + + +# ---------------------------- +# Helper utilities (self-contained) +# ---------------------------- +class AverageMeter(object): + def __init__(self, name='Meter', fmt=':.4f'): + self.name = name + self.fmt = fmt + self.reset() + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / max(1, self.count) + def __str__(self): + fmtstr = '{name} {val' + self.fmt + '} (avg {avg' + self.fmt + '})' + return fmtstr.format(name=self.name, val=self.val, avg=self.avg) + +def accuracy(output, target, topk=(1,)): + """Computes the precision@k for the specified values of k + 返回一个 list,每个元素是 tensor(百分比形式) + """ + with torch.no_grad(): + maxk = max(topk) + batch_size = target.size(0) + + # output: (N, C) -> pred: (maxk, N) + _, pred = output.topk(maxk, 1, True, True) + pred = pred.t() # (maxk, N) + correct = pred.eq(target.view(1, -1).expand_as(pred)) # (maxk, N) bool + + res = [] + for k in topk: + # 把前 k 行展平后求和(返回 0-dim tensor),随后换算为百分比 + correct_k = correct[:k].reshape(-1).float().sum() # 注意:不传 keepdim + # 乘以 100.0 / batch_size,保持返回 tensor(和之前代码兼容) + res.append(correct_k.mul_(100.0 / batch_size)) + return res + +def save_checkpoint(state, is_best, save_dir, filename='checkpoint.pth'): + save_path = os.path.join(save_dir, filename) + torch.save(state, save_path) + if is_best: + best_path = os.path.join(save_dir, 'model_best.pth') + torch.save(state, best_path) + +def set_seed(seed, deterministic=False): + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + if deterministic: + cudnn.deterministic = True + cudnn.benchmark = False + else: + cudnn.deterministic = False + cudnn.benchmark = True + +# ---------------------------- +# Argument parser +# ---------------------------- +def parse_args(): + parser = argparse.ArgumentParser(description='Generic PyTorch training template (DDP/AMP) with LocalModel priority') + parser.add_argument('--name', default='run', type=str, help='experiment name (log/checkpoints dir)') + parser.add_argument('--seed', default=42, type=int, help='random seed') + parser.add_argument('--arch', default='None', type=str, help='model name') + parser.add_argument('--deterministic', action='store_true', help='set cudnn deterministic (may be slower)') + parser.add_argument('--dataset', default='cifar10', choices=['cifar10','cifar100','imagenet','custom'], help='which dataset') + parser.add_argument('--datapath', default='./data', type=str, help='dataset root / imagenet root / custom root') + parser.add_argument('--imagenet_dir', default='./imagenet', type=str, help='if dataset=imagenet, path to imagenet root') + parser.add_argument('--custom_eval_dir', default=None, help='if dataset=custom, provide val dir') + parser.add_argument('--num_workers', default=4, type=int, help='dataloader workers per process') + parser.add_argument('--epochs', default=200, type=int) + parser.add_argument('--steps', default=0, type=int, help='max steps to run (if >0, training will stop when global_step reaches this).') + parser.add_argument('--batch_size', default=128, type=int) + parser.add_argument('--model_name', default='resnet18', help='torchvision model name or python path e.g. mypkg.mymodule.Model (used if no local Model)') + parser.add_argument('--num_classes', default=None, type=int, help='override num classes (auto-detect for common sets)') + parser.add_argument('--pretrained', action='store_true', help='use torchvision pretrained weights when available') + parser.add_argument('--optimizer', default='sgd', choices=['sgd','adam','adamw'], help='optimizer') + parser.add_argument('--lr', '--learning_rate', default=0.1, type=float) + parser.add_argument('--momentum', default=0.9, type=float) + parser.add_argument('--weight_decay', default=5e-4, type=float) + parser.add_argument('--nesterov', action='store_true') + parser.add_argument('--scheduler', default='multistep', choices=['multistep','step','cosine','none'], help='lr scheduler') + parser.add_argument('--milestones', default='100,150', type=str, help='milestones for multistep (comma sep)') + parser.add_argument('--step_size', default=30, type=int, help='step size for StepLR or cosine max epochs') + parser.add_argument('--gamma', default=0.1, type=float) + parser.add_argument('--scheduler_step_per_batch', action='store_true', help='call scheduler.step() per batch (for some schedulers)') + parser.add_argument('--resume', default='', type=str, help='path to checkpoint to resume from') + parser.add_argument('--start_epoch', default=0, type=int) + parser.add_argument('--print_freq', default=100, type=int) + parser.add_argument('--save_freq', default=10, type=int, help='save checkpoint every N epochs (rank0 only)') + parser.add_argument('--amp', action='store_true', default = True,help='use automatic mixed precision (AMP)') + parser.add_argument('--grad_accum_steps', default=1, type=int, help='gradient accumulation steps') + parser.add_argument('--local_rank', default=None, type=int, help='local rank passed by torchrun (if any). Use -1 or None for non-distributed') + parser.add_argument('--cutmix_prob', default=0.0, type=float) + parser.add_argument('--beta', default=1.0, type=float) + parser.add_argument('--seed_sampler', default=False, action='store_true', help='set sampler epoch seeds to make deterministic distributed shuffling') + args = parser.parse_args() + args.milestones = [int(x) for x in args.milestones.split(',')] if args.milestones else [] + return args + +# ---------------------------- +# build model (优先 LocalModel) +# ---------------------------- +def build_model_with_local_priority(args, device=None): + """ + 用参数 args.arch 作为模块名导入 Model() + 如果模块不存在或没有 Model 类,则报错停止。 + """ + try: + # 动态导入模块,比如 args.arch = "rexnet" + mod = importlib.import_module(args.arch) + Model = getattr(mod, "Model") # 从模块中获取 Model 类 + except Exception as e: + raise RuntimeError( + f"无法导入模型模块 '{args.arch}' 或未找到类 Model。" + f"\n错误信息:{e}" + ) + + # 解析数据集类别数 + if args.dataset == 'cifar10': + num_classes = 10 + elif args.dataset == 'cifar100': + num_classes = 100 + else: + print(f"[ERROR] 不支持的数据集类型:{args.dataset},无法确定类别数。程序终止。") + sys.exit(1) + + + # 实例化 + try: + model = Model(num_classes) + except Exception as e: + raise RuntimeError( + f"Model() 实例化失败,请检查模型构造函数。\n错误信息:{e}" + ) + + return model + +# ---------------------------- +# Data loader factory +# ---------------------------- +def build_dataloaders(args, rank, world_size): + if args.dataset == 'cifar10' or args.dataset == 'cifar100': + mean = (0.4914, 0.4822, 0.4465) + std = (0.2470, 0.2435, 0.2616) if args.dataset == 'cifar10' else (0.2023, 0.1994, 0.2010) + # train_transform = transforms.Compose([ + # transforms.RandomCrop(32, padding=4), + # transforms.RandomHorizontalFlip(), + # transforms.ToTensor(), + # transforms.Normalize(mean, std), + # ]) + # test_transform = transforms.Compose([ + # transforms.ToTensor(), + # transforms.Normalize(mean, std), + # ]) + + train_transform = transforms.Compose([ # 2025/12/3 从visformer模型开始 + transforms.Resize(256), # 先放大到 256 + transforms.RandomCrop(224), # 再随机裁剪为 224(更符合 ImageNet 风格增强) + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize(mean, std), + ]) + test_transform = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize(mean, std), + ]) + root = args.datapath + if args.dataset == 'cifar10': + train_set = datasets.CIFAR10(root=root, train=True, download=False, transform=train_transform) + val_set = datasets.CIFAR10(root=root, train=False, download=False, transform=test_transform) + num_classes = 10 + else: + train_set = datasets.CIFAR100(root=root, train=True, download=False, transform=train_transform) + val_set = datasets.CIFAR100(root=root, train=False, download=False, transform=test_transform) + num_classes = 100 + + elif args.dataset == 'imagenet': + train_dir = os.path.join(args.imagenet_dir, 'train') + val_dir = os.path.join(args.imagenet_dir, 'val') + train_transform = transforms.Compose([ + transforms.RandomResizedCrop(224), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize((0.485,0.456,0.406), (0.229,0.224,0.225)), + ]) + test_transform = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize((0.485,0.456,0.406), (0.229,0.224,0.225)), + ]) + train_set = datasets.ImageFolder(train_dir, train_transform) + val_set = datasets.ImageFolder(val_dir, test_transform) + num_classes = args.num_classes or 1000 + + elif args.dataset == 'custom': + train_dir = os.path.join(args.datapath, 'train') + val_dir = args.custom_eval_dir or os.path.join(args.datapath, 'val') + train_transform = transforms.Compose([ + transforms.RandomResizedCrop(224), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + ]) + test_transform = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + ]) + train_set = datasets.ImageFolder(train_dir, train_transform) + val_set = datasets.ImageFolder(val_dir, test_transform) + num_classes = len(train_set.classes) + else: + raise ValueError("Unknown dataset") + + if dist.is_initialized() and world_size > 1: + train_sampler = DistributedSampler(train_set, num_replicas=world_size, rank=rank, shuffle=True) + else: + train_sampler = None + + train_loader = DataLoader(train_set, + batch_size=args.batch_size, + shuffle=(train_sampler is None), + num_workers=args.num_workers, + pin_memory=True, + sampler=train_sampler, + drop_last=False) + val_loader = DataLoader(val_set, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=True) + + return train_loader, val_loader, num_classes, train_sampler + +# ---------------------------- +# Train & validate +# ---------------------------- +def train_one_epoch(args, epoch, model, criterion, optimizer, train_loader, device, scaler, scheduler=None, train_sampler=None, global_step_start=0, max_global_steps=None): + """ + 现在支持:若 max_global_steps 非 None,则当 global_step 达到该值时提前退出 + 返回: epoch_summary_dict, step_logs_list, global_step_end + step_logs_list: list of dicts with per-step info (for logging to CSV if需要) + """ + batch_time = AverageMeter('Time') + data_time = AverageMeter('Data') + losses = AverageMeter('Loss') + top1 = AverageMeter('Acc@1') + top5 = AverageMeter('Acc@5') + + model.train() + end = time.time() + optimizer.zero_grad() + + iters = len(train_loader) + step_logs = [] + global_step = global_step_start + + for i, (images, targets) in enumerate(train_loader): + # check global steps limit + if (max_global_steps is not None) and (global_step >= max_global_steps): + break + + data_time.update(time.time() - end) + images = images.to(device, non_blocking=True) + targets = targets.to(device, non_blocking=True) + + if args.amp: + with amp.autocast(): + outputs = model(images) + loss = criterion(outputs, targets) / args.grad_accum_steps + else: + outputs = model(images) + loss = criterion(outputs, targets) / args.grad_accum_steps + + if args.amp: + scaler.scale(loss).backward() + else: + loss.backward() + + # 每当累积步满足 grad_accum_steps 就 step + if (i + 1) % args.grad_accum_steps == 0: + if args.amp: + scaler.step(optimizer) + scaler.update() + else: + optimizer.step() + optimizer.zero_grad() + if scheduler is not None and args.scheduler_step_per_batch: + scheduler.step() + + with torch.no_grad(): + acc1, acc5 = accuracy(outputs, targets, topk=(1,5)) + losses.update(loss.item() * args.grad_accum_steps, images.size(0)) + top1.update(acc1.item(), images.size(0)) + top5.update(acc5.item(), images.size(0)) + + batch_time.update(time.time() - end) + end = time.time() + + # increment global step AFTER processing this batch + global_step += 1 + + # per-step print (controlled by print_freq) + if ((global_step % args.print_freq == 0) or (i == iters - 1)) and ((dist.get_rank() if dist.is_initialized() else 0) == 0): + lr = optimizer.param_groups[0]['lr'] + print(f"Epoch[{epoch}]:step[{i+1}/{iters}] step_train_loss {losses.val:.4f} acc1 {top1.val:.2f} acc5 {top5.val:.2f}") + + # collect per-step log + step_logs.append({ + 'epoch': epoch, + 'batch_idx': i, + 'global_step': global_step, + 'lr': optimizer.param_groups[0]['lr'], + 'loss': losses.val, + 'loss_avg': losses.avg, + 'acc1': top1.val, + 'acc1_avg': top1.avg, + 'acc5': top5.val, + 'acc5_avg': top5.avg, + 'time': batch_time.val + }) + + # if reached max_global_steps inside epoch, break (handled at loop start next iter) + if (max_global_steps is not None) and (global_step >= max_global_steps): + if (dist.get_rank() if dist.is_initialized() else 0) == 0: + print(f"[Info] 达到 max_global_steps={max_global_steps},将在 epoch 内提前停止。") + break + + # --- flush remaining grads if needed (handle gradient accumulation leftovers) --- + processed_batches = global_step - global_step_start # 实际处理的 batch 数 + if args.grad_accum_steps > 1 and (processed_batches % args.grad_accum_steps) != 0: + # only step if there are gradients + grads_present = any((p.grad is not None and p.requires_grad) for p in model.parameters()) + if grads_present: + if args.amp: + try: + scaler.step(optimizer) + scaler.update() + except Exception as e: + # 防御性:若 scaler.step 因某些原因失败,尝试普通 step(只在极端情况下) + print("[Warning] scaler.step 失败,尝试普通 optimizer.step():", e) + optimizer.step() + else: + optimizer.step() + optimizer.zero_grad() + if scheduler is not None and args.scheduler_step_per_batch: + scheduler.step() + if (dist.get_rank() if dist.is_initialized() else 0) == 0: + print(f"[Info] flushed remaining gradients after early stop (processed_batches={processed_batches}, grad_accum={args.grad_accum_steps}).") + + if scheduler is not None and not args.scheduler_step_per_batch: + scheduler.step() + + return OrderedDict([('loss', losses.avg), ('acc1', top1.avg), ('acc5', top5.avg)]), step_logs, global_step + +def validate(args, model, val_loader, criterion, device, max_batches=None): + """ + Validate on the val_loader. + If max_batches is not None, only process up to that many batches (useful for quick checks). + Returns an OrderedDict with loss/acc1/acc5 (averaged over processed samples). + """ + losses = AverageMeter('Loss') + top1 = AverageMeter('Acc@1') + top5 = AverageMeter('Acc@5') + + model.eval() + processed_batches = 0 + processed_samples = 0 + with torch.no_grad(): + for i, (images, targets) in enumerate(tqdm(val_loader)): + images = images.to(device, non_blocking=True) + targets = targets.to(device, non_blocking=True) + outputs = model(images) + loss = criterion(outputs, targets) + acc1, acc5 = accuracy(outputs, targets, topk=(1,5)) + batch_n = images.size(0) + losses.update(loss.item(), batch_n) + top1.update(acc1.item(), batch_n) + top5.update(acc5.item(), batch_n) + + processed_batches += 1 + processed_samples += batch_n + + if (max_batches is not None) and (processed_batches >= max_batches): + break + + # 如果没处理任何样本,避免除0(不太可能,但防御性) + if processed_samples == 0: + return OrderedDict([('loss', 0.0), ('acc1', 0.0), ('acc5', 0.0)]) + return OrderedDict([('loss', losses.avg), ('acc1', top1.avg), ('acc5', top5.avg)]) + +# ---------------------------- +# Main +# ---------------------------- +def main(): + args = parse_args() + + # handle local_rank from env if not provided + local_rank_env = os.environ.get('LOCAL_RANK', None) + if args.local_rank is None and local_rank_env is not None: + args.local_rank = int(local_rank_env) + + distributed = (args.local_rank is not None and args.local_rank != -1) + if distributed: + dist.init_process_group(backend='nccl', init_method='env://') + rank = dist.get_rank() + world_size = dist.get_world_size() + else: + rank = 0 + world_size = 1 + + if distributed: + torch.cuda.set_device(args.local_rank) + device = torch.device('cuda', args.local_rank) + else: + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + set_seed(args.seed + (rank if distributed else 0), deterministic=args.deterministic) + + save_dir = os.path.join('models', args.name) + if rank == 0: + os.makedirs(save_dir, exist_ok=True) + with open(os.path.join(save_dir, 'args.json'), 'w') as f: + json.dump(vars(args), f, indent=2) + if distributed: + dist.barrier() + + train_loader, val_loader, auto_num_classes, train_sampler = build_dataloaders(args, rank, world_size) + if args.num_classes is None: + args.num_classes = auto_num_classes + + # 使用本地 Model 优先(LocalModel 已在文件顶部尝试导入) + model = build_model_with_local_priority(args, device) + model.to(device) + + if distributed: + model = DDP(model, device_ids=[args.local_rank], output_device=args.local_rank, find_unused_parameters=True) + + criterion = nn.CrossEntropyLoss().to(device) + params = [p for p in model.parameters() if p.requires_grad] + if args.optimizer == 'sgd': + optimizer = optim.SGD(params, lr=args.lr, momentum=args.momentum, + weight_decay=args.weight_decay, nesterov=args.nesterov) + elif args.optimizer == 'adam': + optimizer = optim.Adam(params, lr=args.lr, weight_decay=args.weight_decay) + elif args.optimizer == 'adamw': + optimizer = optim.AdamW(params, lr=args.lr, weight_decay=args.weight_decay) + else: + raise ValueError('Unknown optimizer') + + scheduler = None + if args.scheduler == 'multistep': + scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=args.milestones, gamma=args.gamma) + elif args.scheduler == 'step': + scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=args.step_size, gamma=args.gamma) + elif args.scheduler == 'cosine': + scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs) + elif args.scheduler == 'none': + scheduler = None + + scaler = amp.GradScaler() if args.amp else None + + start_epoch = args.start_epoch + best_acc = 0.0 + if args.resume: + if os.path.isfile(args.resume): + ckpt = torch.load(args.resume, map_location='cpu') + model_state = ckpt.get('state_dict', ckpt) + if isinstance(model, DDP): + model.module.load_state_dict(model_state) + else: + model.load_state_dict(model_state) + if 'optimizer' in ckpt: + optimizer.load_state_dict(ckpt['optimizer']) + start_epoch = ckpt.get('epoch', start_epoch) + best_acc = ckpt.get('best_acc', best_acc) + print(f"=> resumed from {args.resume}, start_epoch={start_epoch}") + else: + print(f"=> resume path {args.resume} not found") + + log_columns = ['epoch', 'lr', 'loss', 'acc1', 'acc5', 'val_loss', 'val_acc1', 'val_acc5'] + log_df = pd.DataFrame(columns=log_columns) + # step-level log + step_log_columns = ['epoch', 'batch_idx', 'global_step', 'lr', 'loss', 'loss_avg', 'acc1', 'acc1_avg', 'acc5', 'acc5_avg', 'time'] + step_log_df = pd.DataFrame(columns=step_log_columns) + + total_epochs = args.epochs + # global_step计数器(训练过程中跨epoch持续) + global_step = 0 + + epoch = start_epoch + # loop until either epoch criteria or step criteria met + while True: + if train_sampler is not None: + if args.seed_sampler: + train_sampler.set_epoch(epoch + args.seed) + else: + train_sampler.set_epoch(epoch) + + if rank == 0: + print(f"==== Epoch {epoch}/{total_epochs - 1} ====") + + # 如果传入了 args.steps (>0),则把剩余允许的 step 数传给 train_one_epoch, + # 否则 max_global_steps=None(按整 epoch 执行完) + if args.steps and args.steps > 0: + max_global_steps = args.steps + else: + max_global_steps = None + + train_log, step_logs, global_step = train_one_epoch( + args, epoch, model, criterion, optimizer, train_loader, device, scaler, + scheduler, train_sampler, global_step_start=global_step, max_global_steps=max_global_steps + ) + + # 如果启用了按 steps 的模式且已经达到上限,标记需要在做一次验证后退出 + if max_global_steps is not None and global_step >= max_global_steps: + if rank == 0: + print(f"[Main] 达到 max_global_steps={max_global_steps}(global_step={global_step}),将在完成验证后退出训练。") + # 我们不 return 立刻退出;后面的 validate / 保存逻辑会执行一次,然后 main 返回/结束 + end_due_to_steps = True + else: + end_due_to_steps = False + + # 验证并记录 epoch 级别日志(如果在 step 模式下很可能在中间某个 epoch 提前结束,但我们仍做一次 validate) + val_log = validate(args, model, val_loader, criterion, device, args.batch_size) + current_lr = optimizer.param_groups[0]['lr'] + + if rank == 0: + # epoch summary print, 格式与示例对齐 + print(f"Epoch[{epoch}]: epoch_train_loss {train_log['loss']:.4f} acc1 {train_log['acc1']:.2f} acc5 {train_log['acc5']:.2f} | " + f"val_loss {val_log['loss']:.4f} acc1 {val_log['acc1']:.2f} acc5 {val_log['acc5']:.2f} lr {current_lr:.6f}") + row = { + 'epoch': epoch, + 'lr': current_lr, + 'loss': train_log['loss'], + 'acc1': train_log['acc1'], + 'acc5': train_log['acc5'], + 'val_loss': val_log['loss'], + 'val_acc1': val_log['acc1'], + 'val_acc5': val_log['acc5'], + } + new_row_df = pd.DataFrame([row]) + log_df = pd.concat([log_df, new_row_df], ignore_index=True) + log_df.to_csv(os.path.join(save_dir, 'log.csv'), index=False) + + is_best = val_log['acc1'] > best_acc + if is_best: + best_acc = val_log['acc1'] + if (epoch % args.save_freq == 0) or is_best or ( (max_global_steps is None) and (epoch == total_epochs - 1) ) : + state = { + 'epoch': epoch, + 'state_dict': model.module.state_dict() if isinstance(model, DDP) else model.state_dict(), + 'best_acc': best_acc, + 'optimizer': optimizer.state_dict(), + 'args': vars(args) + } + save_checkpoint(state, is_best, save_dir, filename=f'checkpoint_epoch_{epoch}.pth') + + # 如果是因为 steps 模式达到上限,则在完成 validation / 保存后退出训练 + if end_due_to_steps: + if rank == 0: + print(f"[Main] 已在 steps 模式下完成最后一次验证并保存,训练结束(global_step={global_step})。") + break + + # increment epoch + epoch += 1 + + # stopping conditions: + # 1) if steps mode enabled and reached steps -> stop + if args.steps and args.steps > 0: + if global_step >= args.steps: + if rank == 0: + print(f"[Main] 已达到指定 steps={args.steps}(global_step={global_step}),训练结束。") + break + + # 2) if steps not used, stop when epoch >= epochs + else: + if epoch >= total_epochs: + if rank == 0: + print(f"[Main] 已达到指定 epochs={total_epochs}(epoch={epoch}),训练结束。") + break + + if dist.is_initialized(): + dist.barrier() + if rank == 0: + print("Training finished. Best val acc1: {:.2f}".format(best_acc)) + +if __name__ == '__main__': + main() \ No newline at end of file From 679cfe27ab21e862f7c66c54f7b9d06d06181f55 Mon Sep 17 00:00:00 2001 From: wangwl Date: Wed, 7 Jan 2026 05:41:24 +0000 Subject: [PATCH 2/4] fix: cleanup code and update --- .../SigLIP2/SigLIP2-PyTorch/LICENSE | 21 - .../SigLIP2/SigLIP2-PyTorch/README.md | 5 - .../SigLIP2/SigLIP2-PyTorch/requirements.txt | 3 - .../SigLIP2/SigLIP2-PyTorch/siglip2.py | 2033 ---------------- .../SigLIP2/configuration_siglip2.py | 185 -- .../Classification/SigLIP2/coverage.txt | 3 - .../build-in/Classification/SigLIP2/readme | 65 + .../SigLIP2/requirements_exact.txt | 89 + PyTorch/build-in/Classification/SigLIP2/run | 1 - .../Classification/SigLIP2/siglip2SDAA.py | 315 --- .../Classification/SigLIP2/siglip2_loss.jpg | Bin 36902 -> 0 bytes .../Classification/SigLIP2/siglip2_loss.txt | 29 - .../Classification/SigLIP2/siglip2_origin.py | 2164 ----------------- 13 files changed, 154 insertions(+), 4759 deletions(-) delete mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE delete mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md delete mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt delete mode 100644 PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py delete mode 100644 PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py delete mode 100644 PyTorch/build-in/Classification/SigLIP2/coverage.txt create mode 100644 PyTorch/build-in/Classification/SigLIP2/readme create mode 100644 PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt delete mode 100644 PyTorch/build-in/Classification/SigLIP2/run delete mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py delete mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg delete mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt delete mode 100644 PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE deleted file mode 100644 index 0ad01cf22..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Yuan-Man - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md deleted file mode 100644 index 349224070..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# SigLIP 2 PyTorch - -PyTorch implementation of SigLIP 2. - -[SigLIP 2](https://arxiv.org/abs/2502.14786): Multilingual Vision-Language Encoders with Improved Semantic Understanding, Localization, and Dense Features. diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt deleted file mode 100644 index f4959db67..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -numpy -torch -dataclasses diff --git a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py b/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py deleted file mode 100644 index 2b1b9ece9..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/SigLIP2-PyTorch/siglip2.py +++ /dev/null @@ -1,2033 +0,0 @@ -import math -import warnings -from dataclasses import dataclass -from typing import Any, Optional, Tuple, Union - -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss -from torch.nn.init import _calculate_fan_in_and_fan_out - -from configuration_siglip2 import Siglip2Config, Siglip2TextConfig, Siglip2VisionConfig - - -@dataclass -class Siglip2VisionOutput(): - """ - 视觉模型的输出基类,包含最后一层隐藏状态的池化结果得到的图像嵌入。 - - Args: - image_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): - 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的图像嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 图像嵌入,用于表示图像的特征。 - - last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): - 模型最后一层输出的隐藏状态序列。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, sequence_length, hidden_size)` - - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 - - hidden_states (tuple(torch.FloatTensor), 可选): - 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 - - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 - - attentions (tuple(torch.FloatTensor), 可选): - 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 - - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 - """ - - image_embeds: Optional[torch.FloatTensor] = None - last_hidden_state: torch.FloatTensor = None - hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None - attentions: Optional[Tuple[torch.FloatTensor, ...]] = None - - -@dataclass -class Siglip2TextOutput(): - """ - 文本模型的输出基类,包含最后一层隐藏状态的池化结果得到的文本嵌入。 - - Args: - text_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): - 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的文本嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 文本嵌入,用于表示文本的特征。 - - last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): - 模型最后一层输出的隐藏状态序列。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, sequence_length, hidden_size)` - - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 - - hidden_states (tuple(torch.FloatTensor), 可选): - 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 - - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 - - attentions (tuple(torch.FloatTensor), 可选): - 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 - - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 - """ - - text_embeds: Optional[torch.FloatTensor] = None - last_hidden_state: torch.FloatTensor = None - hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None - attentions: Optional[Tuple[torch.FloatTensor, ...]] = None - - -@dataclass -class Siglip2Output(): - """ - Siglip2 模型的输出,包含图像-文本对比损失、相似度分数、嵌入以及子模型的输出。 - - Args: - loss (torch.FloatTensor, 可选, 形状为 `(1,)`): - 当 `return_loss=True` 时返回,用于图像-文本相似度的对比损失。 - - **类型**: `torch.FloatTensor` - - **形状**: `(1,)` - - **说明**: 对比损失,用于衡量图像和文本之间的相似度。 - - logits_per_image (torch.FloatTensor, 必填, 形状为 `(image_batch_size, text_batch_size)`): - `image_embeds` 和 `text_embeds` 之间的缩放点积分数,表示图像-文本相似度分数。 - - **类型**: `torch.FloatTensor` - - **形状**: `(image_batch_size, text_batch_size)` - - **说明**: 图像-文本相似度分数,用于评估图像和文本之间的匹配程度。 - - logits_per_text (torch.FloatTensor, 必填, 形状为 `(text_batch_size, image_batch_size)`): - `text_embeds` 和 `image_embeds` 之间的缩放点积分数,表示文本-图像相似度分数。 - - **类型**: `torch.FloatTensor` - - **形状**: `(text_batch_size, image_batch_size)` - - **说明**: 文本-图像相似度分数,用于评估文本和图像之间的匹配程度。 - - text_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 文本嵌入,用于表示文本的特征。 - - image_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 图像嵌入,用于表示图像的特征。 - - text_model_output (BaseModelOutputWithPooling): - [`Siglip2TextModel`] 的输出。 - - **类型**: `BaseModelOutputWithPooling` - - **说明**: 包含文本模型的详细输出信息,如隐藏状态等。 - - vision_model_output (BaseModelOutputWithPooling): - [`Siglip2VisionModel`] 的输出。 - - **类型**: `BaseModelOutputWithPooling` - - **说明**: 包含视觉模型的详细输出信息,如隐藏状态等。 - """ - - loss: Optional[torch.FloatTensor] = None - logits_per_image: torch.FloatTensor = None - logits_per_text: torch.FloatTensor = None - text_embeds: torch.FloatTensor = None - image_embeds: torch.FloatTensor = None - text_model_output: BaseModelOutputWithPooling = None - vision_model_output: BaseModelOutputWithPooling = None - - def to_tuple(self) -> Tuple[Any]: - """ - 将 Siglip2Output 对象转换为元组。 - - Returns: - Tuple[Any]: 包含 Siglip2Output 对象的各个属性值的元组。 - """ - return tuple( - self[k] if k not in ["text_model_output", "vision_model_output"] else getattr(self, k).to_tuple() - for k in self.keys() - ) - - -class Siglip2VisionEmbeddings(nn.Module): - """ - Siglip2 视觉嵌入模块,用于将图像像素值转换为嵌入向量,并添加位置嵌入。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含模型的各种配置参数,如隐藏层大小、patch 大小等。 - """ - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # patch 大小,表示每个图像块的高度和宽度 - self.patch_size = config.patch_size - - # 定义一个线性层,用于将每个图像 patch(像素块)映射到嵌入向量 - self.patch_embedding = nn.Linear( - # 输入特征数:通道数 * patch大小平方 - in_features=config.num_channels * self.patch_size * self.patch_size, - # 输出特征数:嵌入维度 - out_features=self.embed_dim, - ) - - # 图像被分割成的总patch数量 - self.num_patches = config.num_patches - # 计算位置嵌入的网格大小(假设图像是正方形) - self.position_embedding_size = int(self.num_patches**0.5) - # 定义一个嵌入层,用于存储每个patch的位置嵌入 - self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) - - @staticmethod - def resize_positional_embeddings( - positional_embeddings: torch.Tensor, - spatial_shapes: torch.LongTensor, - max_length: int, - ) -> torch.Tensor: - """ - 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小。 - - Args: - positional_embeddings (`torch.Tensor`): - 位置嵌入张量,形状为 (高度, 宽度, 嵌入维度)。 - spatial_shapes (`torch.LongTensor`): - 空间形状张量,形状为 (batch_size, 2),用于调整位置嵌入的大小。 - 每个元素包含 [目标高度, 目标宽度]。 - max_length (`int`): - 填充后的最大长度,用于确保所有批次的位置嵌入具有相同的长度。 - - Returns: - `torch.Tensor`: - 调整大小并填充后的嵌入张量,形状为 (batch_size, max_length, 嵌入维度)。 - """ - # 获取批次大小 - batch_size = spatial_shapes.shape[0] - # 获取嵌入维度 - embed_dim = positional_embeddings.shape[-1] - # 记录原始数据类型 - source_dtype = positional_embeddings.dtype - - # 创建一个空的张量,用于存储调整后的位置嵌入 - resulted_positional_embeddings = torch.empty( - (batch_size, max_length, embed_dim), - device=positional_embeddings.device, - dtype=source_dtype, - ) - - # 将位置嵌入的维度顺序从 (高度, 宽度, 嵌入维度) 转换为 (嵌入维度, 高度, 宽度) 以便进行插值 - positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0) - - # 如果设备是 CPU,则将数据类型上转换为 float32,因为 CPU 不支持 bfloat16/float16 的 antialias - if positional_embeddings.device.type == "cpu": - positional_embeddings = positional_embeddings.to(torch.float32) - - for i in range(batch_size): - # 获取当前批次的目标高度和宽度 - # (1, dim, height, width) -> (1, dim, target_height, target_width) - height, width = spatial_shapes[i] - # 对位置嵌入进行双线性插值,调整到目标尺寸 - resized_embeddings = F.interpolate( - positional_embeddings, - size=(height, width), - mode="bilinear", - align_corners=False, - antialias=True, - ) - - # 将调整后的嵌入形状从 (1, 嵌入维度, 高度, 宽度) 转换为 (高度 * 宽度, 嵌入维度) - # (1, dim, target_height, target_width) -> (target_height * target_width, dim) - resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1) - - # 将数据类型转换回原始类型 - resized_embeddings = resized_embeddings.to(source_dtype) - - # 将调整后的嵌入填充到结果张量中 - resulted_positional_embeddings[i, : height * width] = resized_embeddings - # 对于不足的部分,用第一个位置的嵌入填充 - resulted_positional_embeddings[i, height * width :] = resized_embeddings[0] - - return resulted_positional_embeddings - - def forward(self, pixel_values: torch.FloatTensor, spatial_shapes: torch.LongTensor) -> torch.Tensor: - """ - 前向传播方法,用于生成图像嵌入。 - - Args: - pixel_values (`torch.FloatTensor`): - 像素值张量,形状为 (batch_size, 最大patch数量, 通道数 * patch大小平方)。 - spatial_shapes (`List[Tuple[int, int]]`): - 空间形状列表,形状为 (batch_size, 2),用于调整位置嵌入的大小。 - 每个元素包含 [高度, 宽度]。 - - Returns: - `torch.Tensor`: - 生成的图像嵌入张量,形状为 (batch_size, 最大patch数量, 嵌入维度)。 - """ - # 将像素值张量转换为与patch_embedding权重相同的类型 - target_dtype = self.patch_embedding.weight.dtype - patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype)) - - # 获取位置嵌入,并调整其形状为 (高度, 宽度, 嵌入维度) - positional_embeddings = self.position_embedding.weight.reshape( - self.position_embedding_size, self.position_embedding_size, -1 - ) - - # 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小 - resized_positional_embeddings = self.resize_positional_embeddings( - positional_embeddings, spatial_shapes, max_length=pixel_values.shape[1] - ) - - # 将位置嵌入添加到patch嵌入中,得到最终的图像嵌入 - embeddings = patch_embeds + resized_positional_embeddings - return embeddings - - -class Siglip2Attention(nn.Module): - """ - 多头注意力机制,源自论文 'Attention Is All You Need'。 - - Args: - config: - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小,也是注意力机制的嵌入维度。 - - num_attention_heads (int): 注意力头的数量。 - - attention_dropout (float): 注意力权重在 dropout 时的丢弃概率。 - """ - - def __init__(self, config): - super().__init__() - self.config = config - # 注意力机制的嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # 注意力头的数量 - self.num_heads = config.num_attention_heads - # 每个注意力头的维度 - self.head_dim = self.embed_dim // self.num_heads - - # 检查 embed_dim 是否能被 num_heads 整除 - if self.head_dim * self.num_heads != self.embed_dim: - raise ValueError( - f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:" - f" {self.num_heads})." - ) - - # 缩放因子,用于缩放注意力得分 - self.scale = self.head_dim**-0.5 - self.dropout = config.attention_dropout - - # 定义线性层,用于计算查询 (query)、键 (key) 和值 (value) 的投影 - self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) - - # 输出投影层 - self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) - - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - 前向传播方法,计算多头注意力。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.Tensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`, 可选): - 是否返回注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - """ - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # 重塑查询、键和值张量,以适应多头注意力的计算 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - - # 获取键和值序列的长度 - k_v_seq_len = key_states.shape[-2] - # 计算原始的注意力得分 - attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale - - # 检查注意力权重的形状是否正确 - if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len): - raise ValueError( - f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is" - f" {attn_weights.size()}" - ) - - # 如果提供了注意力掩码,则将其添加到注意力得分中 - if attention_mask is not None: - if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len): - raise ValueError( - f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, but is {attention_mask.size()}" - ) - attn_weights = attn_weights + attention_mask - - # 将注意力得分转换为 float32 以进行 softmax 计算,然后转换回原始数据类型 - attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) - attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) - - # 计算最终的注意力输出 - attn_output = torch.matmul(attn_weights, value_states) - - # 检查注意力输出的形状是否正确 - if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim): - raise ValueError( - f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is" - f" {attn_output.size()}" - ) - - # 重塑注意力输出张量,以适应后续的处理 - attn_output = attn_output.transpose(1, 2).contiguous() - attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim) - - # 应用输出投影层 - attn_output = self.out_proj(attn_output) - - # 根据需要返回注意力权重 - return attn_output, attn_weights - - -class Siglip2FlashAttention2(Siglip2Attention): - """ - Siglip2Attention 的 Flash Attention 模块。该模块继承自 `Siglip2Attention`,因此模型的权重保持不变。 - 唯一需要修改的是前向传播方法,需要正确调用 Flash Attention 的公共 API,并处理输入中可能存在的填充 token。 - - Attributes: - is_causal (bool): - 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 - """ - - is_causal = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Adapted from transformers.models.llama.modeling_llama.LlamaFlashAttention2.forward - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.LongTensor] = None, - output_attentions: bool = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: - """ - 前向传播方法,计算 Flash Attention。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.LongTensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`): - 是否输出注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - - `attn_weights_tuple`: 其他注意力权重信息(可选)。 - """ - output_attentions = False - - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # Flash Attention 要求输入的形状为 (batch_size, seq_length, head_dim, hidden_dim) - # 因此我们保持原始形状不变 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim) - - dropout_rate = self.dropout if self.training else 0.0 - - # 在 PEFT 中,通常我们将层归一化层转换为 float32 以提高训练稳定性 - # 因此,输入的隐藏状态会被静默地转换为 float32。因此,我们需要将其转换回正确的类型,以确保一切按预期工作。 - # 这种转换可能会减慢训练和推理速度,因此建议不要将 LayerNorms 转换为 fp32。 - - input_dtype = query_states.dtype - if input_dtype == torch.float32: - if torch.is_autocast_enabled(): - target_dtype = torch.get_autocast_gpu_dtype() - # 处理模型量化的情形 - elif hasattr(self.config, "_pre_quantization_dtype"): - target_dtype = self.config._pre_quantization_dtype - else: - target_dtype = self.q_proj.weight.dtype - - logger.warning_once( - f"The input hidden states seems to be silently casted in float32, this might be related to" - f" the fact you have upcasted embedding or layer norm layers in float32. We will cast back the input in" - f" {target_dtype}." - ) - - query_states = query_states.to(target_dtype) - key_states = key_states.to(target_dtype) - value_states = value_states.to(target_dtype) - - # 调用 Flash Attention 的前向传播方法 - attn_output = _flash_attention_forward( - query_states, - key_states, - value_states, - attention_mask, - q_len, - dropout=dropout_rate, - is_causal=self.is_causal, - use_top_left_mask=self._flash_attn_uses_top_left_mask, - ) - - # 重塑注意力输出张量 - attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim).contiguous() - attn_output = self.out_proj(attn_output) - - if not output_attentions: - attn_weights = None - - # 返回注意力输出和可选的注意力权重 - return attn_output, attn_weights - - -class Siglip2SdpaAttention(Siglip2Attention): - """ - 使用 torch.nn.functional.scaled_dot_product_attention 的 Siglip2 注意力模块。该模块继承自 `Siglip2Attention`,因为模块的权重保持不变。 - 唯一的变化是在前向传播方法中,以适应 SDPA API。 - - Attributes: - is_causal (bool): - 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 - """ - - is_causal = False - - # Adapted from Siglip2Attention.forward and transformers.models.llama.modeling_llama.LlamaSdpaAttention.forward - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - 前向传播方法,计算使用 SDPA 的注意力。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.Tensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`, 可选): - 是否输出注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - """ - if output_attentions: - return super().forward( - hidden_states=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - ) - - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # 重塑张量以适应多头注意力的计算 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - - if query_states.device.type == "cuda" and attention_mask is not None: - query_states = query_states.contiguous() - key_states = key_states.contiguous() - value_states = value_states.contiguous() - - # 我们通过 `is_causal` 语句而不是 SDPA 中的内联条件分配来调度到 SDPA 的 Flash Attention 或 Efficient 内核, - # 以支持 torch.compile 的动态形状和完整图选项。内联条件会阻止动态形状的编译。 - is_causal = True if self.is_causal and q_len > 1 else False - - attn_output = torch.nn.functional.scaled_dot_product_attention( - query_states, - key_states, - value_states, - attn_mask=attention_mask, - dropout_p=self.dropout if self.training else 0.0, - is_causal=is_causal, - ) - - # 重塑注意力输出张量 - attn_output = attn_output.transpose(1, 2).contiguous() - attn_output = attn_output.view(batch_size, q_len, self.embed_dim) - - # 应用输出投影层 - attn_output = self.out_proj(attn_output) - - # 返回注意力输出,不返回注意力权重 - return attn_output, None - - -class Siglip2MLP(nn.Module): - """ - Siglip2 的多层感知机(MLP)模块,用于在注意力机制之后进行非线性变换。 - - Args: - config: - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 - """ - def __init__(self, config): - super().__init__() - self.config = config - # 根据配置选择激活函数 - self.activation_fn = ACT2FN[config.hidden_act] - # 定义第一个全连接层,将隐藏层大小映射到中间层大小 - self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) - # 定义第二个全连接层,将中间层大小映射回隐藏层大小 - self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) - - def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: - """ - 前向传播方法,应用 MLP 变换。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 `(batch_size, seq_length, hidden_size)`。 - - Returns: - `torch.Tensor`: - 变换后的张量,形状为 `(batch_size, seq_length, hidden_size)`。 - """ - # 应用第一个全连接层 - hidden_states = self.fc1(hidden_states) - # 应用激活函数 - hidden_states = self.activation_fn(hidden_states) - # 应用第二个全连接层 - hidden_states = self.fc2(hidden_states) - # 返回变换后的张量 - return hidden_states - - -# 定义注意力机制的实现类映射 -SIGLIP2_ATTENTION_CLASSES = { - "eager": Siglip2Attention, - "flash_attention_2": Siglip2FlashAttention2, - "sdpa": Siglip2SdpaAttention, -} - - -class Siglip2EncoderLayer(nn.Module): - """ - Siglip2 的编码器层,包含自注意力机制和 MLP 模块。 - - Args: - config (Siglip2Config): - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 - """ - def __init__(self, config: Siglip2Config): - super().__init__() - - # 嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # 根据配置选择注意力机制的实现类,并实例化 - self.self_attn = SIGLIP2_ATTENTION_CLASSES[config._attn_implementation](config=config) - # 定义第一个层归一化层 - self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) - # 实例化 MLP 模块 - self.mlp = Siglip2MLP(config) - # 定义第二个层归一化层 - self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) - - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: torch.Tensor, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.FloatTensor]: - """ - 前向传播方法,处理输入张量通过自注意力和 MLP。 - - Args: - hidden_states (`torch.FloatTensor`): - 输入张量,形状为 `(batch_size, seq_len, embed_dim)`。 - attention_mask (`torch.FloatTensor`): - 注意力掩码张量,形状为 `(batch_size, 1, q_len, k_v_seq_len)`,其中填充元素由非常大的负值表示。 - output_attentions (`bool`, 可选, 默认值为 `False`): - 是否返回所有注意力层的注意力权重。 - - Returns: - `Tuple[torch.FloatTensor]`: - - `hidden_states`: 变换后的隐藏状态,形状为 `(batch_size, seq_len, embed_dim)`。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 `(batch_size, num_heads, q_len, k_v_seq_len)`。 - """ - # 保存残差连接的输入 - residual = hidden_states - - # 应用第一个层归一化 - hidden_states = self.layer_norm1(hidden_states) - # 应用自注意力机制 - hidden_states, attn_weights = self.self_attn( - hidden_states=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - ) - # 残差连接 - hidden_states = residual + hidden_states - - # 保存残差连接的输入 - residual = hidden_states - - # 应用第二个层归一化 - hidden_states = self.layer_norm2(hidden_states) - # 应用 MLP - hidden_states = self.mlp(hidden_states) - # 残差连接 - hidden_states = residual + hidden_states - # 打包输出 - outputs = (hidden_states,) - - if output_attentions: - # 如果需要,添加注意力权重到输出 - outputs += (attn_weights,) - - return outputs - - -class Siglip2Encoder(nn.Module): - """ - Transformer 编码器,由 `config.num_hidden_layers` 个自注意力层组成。每个层都是 [`Siglip2EncoderLayer`] 的实例。 - - Args: - config (Siglip2Config): - 模型配置对象,包含以下属性: - - num_hidden_layers (int): 编码器的层数。 - - 其他配置参数,如 hidden_size, intermediate_size, hidden_act, layer_norm_eps, output_attentions, output_hidden_states, use_return_dict 等。 - """ - - def __init__(self, config: Siglip2Config): - super().__init__() - self.config = config - self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) - self.gradient_checkpointing = False - - # Ignore copy - def forward( - self, - inputs_embeds, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入嵌入通过多个自注意力层。 - - Args: - inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`): - 可选地,直接传递嵌入表示,而不是传递 `input_ids`。这在您希望对如何将 `input_ids` 索引转换为关联向量有更多控制时非常有用。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 掩码张量,用于避免在填充 token 索引上执行注意力计算。掩码值选择 `[0, 1]`: - - - `1` 表示 **未掩码** 的 token, - - `0` 表示 **掩码** 的 token。 - - [什么是注意力掩码?](../glossary#attention-mask) - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutput]`: - - 如果 `return_dict=True`,返回 `BaseModelOutput` 对象。 - - 否则,返回包含 `hidden_states`, `encoder_states`, `all_attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 初始化存储隐藏状态和注意力权重的容器 - encoder_states = () if output_hidden_states else None - all_attentions = () if output_attentions else None - - # 设置初始隐藏状态为输入嵌入 - hidden_states = inputs_embeds - - # 遍历所有编码器层 - for encoder_layer in self.layers: - if output_hidden_states: - # 存储当前隐藏状态 - encoder_states = encoder_states + (hidden_states,) - - # 如果启用梯度检查点,则使用检查点机制 - if self.gradient_checkpointing and self.training: - layer_outputs = self._gradient_checkpointing_func( - encoder_layer.__call__, - hidden_states, - attention_mask, - output_attentions, - ) - else: - # 否则,正常调用编码器层的前向传播方法 - layer_outputs = encoder_layer( - hidden_states, - attention_mask, - output_attentions=output_attentions, - ) - - # 更新隐藏状态 - hidden_states = layer_outputs[0] - - if output_attentions: - # 存储注意力权重 - all_attentions = all_attentions + (layer_outputs[1],) - - if output_hidden_states: - # 存储最终隐藏状态 - encoder_states = encoder_states + (hidden_states,) - - if not return_dict: - return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None) - return BaseModelOutput( - last_hidden_state=hidden_states, hidden_states=encoder_states, attentions=all_attentions - ) - - -# 视觉输入参数的中文文档字符串 -SIGLIP2_VISION_INPUTS_DOCSTRING = r""" -Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 像素值。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。详情见 [`CLIPImageProcessor.__call__`]。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 - interpolate_pos_encoding (`bool`, *optional*, defaults to `False`): - 是否插值预训练的位置编码。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 -""" - - -class Siglip2VisionTransformer(nn.Module): - """ - Siglip2 的视觉 Transformer 模型,包含图像嵌入、编码器、层归一化以及可选的头部模块。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - vision_use_head (bool): 是否使用头部模块。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - - # 实例化视觉嵌入模块 - self.embeddings = Siglip2VisionEmbeddings(config) - # 实例化编码器模块 - self.encoder = Siglip2Encoder(config) - # 实例化后置层归一化层 - self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) - # 判断是否使用头部模块 - self.use_head = True if not hasattr(config, "vision_use_head") else config.vision_use_head - if self.use_head: - self.head = Siglip2MultiheadAttentionPoolingHead(config) - - # 判断是否使用 Flash Attention 2 - self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" - - def forward( - self, - pixel_values: torch.FloatTensor, - attention_mask: torch.Tensor, - spatial_shapes: torch.LongTensor, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入图像像素值通过视觉 Transformer 模型。 - - Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 像素值张量。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): - 空间形状张量,用于调整位置嵌入的大小。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 应用视觉嵌入模块,将像素值转换为嵌入向量 - hidden_states = self.embeddings(pixel_values, spatial_shapes) - - # 处理注意力掩码 - if attention_mask is not None and not self._use_flash_attention_2: - # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] - encoder_attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) - else: - encoder_attention_mask = attention_mask - - # 应用编码器模块 - encoder_outputs = self.encoder( - inputs_embeds=hidden_states, - attention_mask=encoder_attention_mask, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取最后一层的隐藏状态 - last_hidden_state = encoder_outputs[0] - # 应用后置层归一化 - last_hidden_state = self.post_layernorm(last_hidden_state) - - # 应用头部模块(如果使用) - pooler_output = self.head(last_hidden_state, attention_mask) if self.use_head else None - - # 根据 return_dict 参数返回不同的输出格式 - if not return_dict: - return (last_hidden_state, pooler_output) + encoder_outputs[1:] - - return BaseModelOutputWithPooling( - last_hidden_state=last_hidden_state, - pooler_output=pooler_output, - hidden_states=encoder_outputs.hidden_states, - attentions=encoder_outputs.attentions, - ) - - -class Siglip2TextEmbeddings(nn.Module): - """ - Siglip2 的文本嵌入模块,用于将输入的 token ID 转换为嵌入向量,并添加位置嵌入。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - vocab_size (int): 词汇表大小。 - - hidden_size (int): 隐藏层大小。 - - max_position_embeddings (int): 最大位置嵌入长度。 - """ - def __init__(self, config: Siglip2TextConfig): - super().__init__() - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - - # 定义 token 嵌入层,将 token ID 转换为嵌入向量 - self.token_embedding = nn.Embedding(config.vocab_size, embed_dim) - # 定义位置嵌入层,为每个位置生成位置嵌入 - self.position_embedding = nn.Embedding(config.max_position_embeddings, embed_dim) - - # 注册一个缓冲区,存储位置 ID 张量,并在模型保存时不进行序列化 - self.register_buffer( - "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False - ) - - def forward( - self, - input_ids: Optional[torch.LongTensor] = None, - position_ids: Optional[torch.LongTensor] = None, - inputs_embeds: Optional[torch.FloatTensor] = None, - ) -> torch.Tensor: - """ - 前向传播方法,将输入的 token ID 或嵌入向量转换为包含位置嵌入的嵌入向量。 - - Args: - input_ids (`torch.LongTensor`, *optional*): - 输入的 token ID 张量,形状为 `(batch_size, sequence_length)`。 - position_ids (`torch.LongTensor`, *optional*): - 位置 ID 张量,形状为 `(batch_size, sequence_length)`。如果未提供,则根据 `input_ids` 自动生成。 - inputs_embeds (`torch.FloatTensor`, *optional*): - 预先计算的输入嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。如果提供,则忽略 `input_ids`。 - - Returns: - `torch.Tensor`: - 包含位置嵌入的嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。 - """ - # 获取序列长度 - seq_length = input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2] - # 获取最大位置嵌入长度 - max_position_embedding = self.position_embedding.weight.shape[0] - - # 检查序列长度是否超过最大位置嵌入长度 - if seq_length > max_position_embedding: - raise ValueError( - f"Sequence length must be less than max_position_embeddings (got `sequence length`: " - f"{seq_length} and max_position_embeddings: {max_position_embedding}" - ) - - # 如果未提供位置 ID,则自动生成 - if position_ids is None: - position_ids = self.position_ids[:, :seq_length] - - # 如果未提供输入嵌入向量,则使用 token 嵌入层进行嵌入 - if inputs_embeds is None: - inputs_embeds = self.token_embedding(input_ids) - - # 获取位置嵌入 - position_embeddings = self.position_embedding(position_ids) - - # 将 token 嵌入和位置嵌入相加,得到最终的嵌入向量 - embeddings = inputs_embeds + position_embeddings - - return embeddings - - -def _trunc_normal_(tensor, mean, std, a, b): - """ - 对输入张量进行截断正态分布初始化。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - mean (float): 正态分布的均值。 - std (float): 正态分布的标准差。 - a (float): 截断的下界。 - b (float): 截断的上界。 - """ - def norm_cdf(x): - """ - 计算标准正态分布的累积分布函数(CDF)。 - - Args: - x (float): 输入值。 - - Returns: - float: 标准正态分布的 CDF 值。 - """ - return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0 - - # 检查均值是否在截断范围内超过2个标准差 - if (mean < a - 2 * std) or (mean > b + 2 * std): - warnings.warn( - "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. " - "The distribution of values may be incorrect.", - stacklevel=2, - ) - - # 通过使用截断的均匀分布,然后使用正态分布的逆 CDF 来生成值。 - # 获取上下 CDF 值 - l = norm_cdf((a - mean) / std) - u = norm_cdf((b - mean) / std) - - # 将张量均匀地填充为 [2l-1, 2u-1] 范围内的值 - tensor.uniform_(2 * l - 1, 2 * u - 1) - - # 使用逆误差函数(erfinv)进行逆 CDF 变换,得到截断的标准正态分布 - tensor.erfinv_() - - # 将分布转换为指定的均值和标准差 - tensor.mul_(std * math.sqrt(2.0)) - tensor.add_(mean) - - # 截断以确保值在正确的范围内 - tensor.clamp_(min=a, max=b) - - -def trunc_normal_tf_( - tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0, a: float = -2.0, b: float = 2.0 -) -> torch.Tensor: - """ - 使用截断正态分布填充输入张量。值实际上是从正态分布 :math:`\\mathcal{N}(\\text{mean}, \\text{std}^2)` 中抽取的, - 超出 :math:`[a, b]` 的值将被重新抽取,直到它们在边界内。该方法在 :math:`a \\leq \\text{mean} \\leq b` 时效果最佳。 - - 注意:这个 'tf' 变体更接近于 TensorFlow / JAX 的实现,其中边界 [a, b] 在采样正态分布(均值为0,标准差为1.0)时应用, - 然后结果通过均值和标准差参数进行缩放和平移。 - - Args: - tensor (torch.Tensor): 一个 n 维的 `torch.Tensor`。 - mean (float): 正态分布的均值,默认为0.0。 - std (float): 正态分布的标准差,默认为1.0。 - a (float): 最小截断值,默认为-2.0。 - b (float): 最大截断值,默认为2.0。 - """ - with torch.no_grad(): - # 使用标准正态分布(均值为0,标准差为1.0)进行截断初始化 - _trunc_normal_(tensor, 0, 1.0, a, b) - # 将分布缩放和平移到指定的均值和标准差 - tensor.mul_(std).add_(mean) - - -def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"): - """ - 方差缩放初始化方法,用于根据指定的模式和分布初始化张量。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - scale (float, optional): 缩放因子,默认为1.0。 - mode (str, optional): 缩放模式,可以是 'fan_in', 'fan_out' 或 'fan_avg',默认为 'fan_in'。 - distribution (str, optional): 分布类型,可以是 'truncated_normal', 'normal' 或 'uniform',默认为 'normal'。 - """ - fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor) - if mode == "fan_in": - denom = fan_in - elif mode == "fan_out": - denom = fan_out - elif mode == "fan_avg": - denom = (fan_in + fan_out) / 2 - - variance = scale / denom - - if distribution == "truncated_normal": - # 标准正态分布截断到 (-2, 2) 的标准差常数 - trunc_normal_tf_(tensor, std=math.sqrt(variance) / 0.87962566103423978) - elif distribution == "normal": - with torch.no_grad(): - tensor.normal_(std=math.sqrt(variance)) - elif distribution == "uniform": - bound = math.sqrt(3 * variance) - with torch.no_grad(): - tensor.uniform_(-bound, bound) - else: - raise ValueError(f"invalid distribution {distribution}") - - -def lecun_normal_(tensor): - """ - LeCun 正态分布初始化方法。 - - 使用方差缩放初始化方法,模式为 'fan_in',分布为 'truncated_normal'。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - """ - variance_scaling_(tensor, mode="fan_in", distribution="truncated_normal") - - -def default_flax_embed_init(tensor): - """ - 默认的 Flax 嵌入初始化方法。 - - 使用方差缩放初始化方法,模式为 'fan_in',分布为 'normal'。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - """ - variance_scaling_(tensor, mode="fan_in", distribution="normal") - - -class Siglip2TextTransformer(nn.Module): - """ - Siglip2 的文本 Transformer 模型,包含文本嵌入、编码器、最终层归一化以及线性头。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - projection_size (int): 线性头的输出维度。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - def __init__(self, config: Siglip2TextConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - # 实例化文本嵌入模块 - self.embeddings = Siglip2TextEmbeddings(config) - # 实例化编码器模块 - self.encoder = Siglip2Encoder(config) - # 实例化最终层归一化层 - self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) - - # 定义线性头,将隐藏状态映射到指定的投影大小 - self.head = nn.Linear(embed_dim, config.projection_size) - # 判断是否使用 Flash Attention 2 - self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" - - def forward( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入文本通过文本 Transformer 模型。 - - Args: - input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - if input_ids is None: - raise ValueError("You have to specify input_ids") - - # 获取输入形状 - input_shape = input_ids.size() - # 重塑张量 - input_ids = input_ids.view(-1, input_shape[-1]) - - # 应用文本嵌入模块,将 token ID 转换为嵌入向量 - hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids) - - # 注意:Siglip2 的文本模型不像原始的 CLIP 模型那样使用因果掩码。 - # 扩展注意力掩码 - if attention_mask is not None and not self._use_flash_attention_2: - # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] - attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) - - # 应用编码器模块 - encoder_outputs = self.encoder( - inputs_embeds=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取最后一层的隐藏状态 - last_hidden_state = encoder_outputs[0] - # 应用最终层归一化 - last_hidden_state = self.final_layer_norm(last_hidden_state) - - # 假设使用 "sticky" EOS tokenization,最后一个 token 始终是 EOS。 - # 取最后一个 token 的隐藏状态作为池化输出 - pooled_output = last_hidden_state[:, -1, :] - # 应用线性头 - pooled_output = self.head(pooled_output) - - if not return_dict: - # 返回元组形式的输出 - return (last_hidden_state, pooled_output) + encoder_outputs[1:] - - return BaseModelOutputWithPooling( - last_hidden_state=last_hidden_state, - pooler_output=pooled_output, - hidden_states=encoder_outputs.hidden_states, - attentions=encoder_outputs.attentions, - ) - - -class Siglip2PreTrainedModel(PreTrainedModel): - """ - 一个抽象类,用于处理权重初始化以及提供一个简单的接口用于下载和加载预训练模型。 - - Attributes: - config_class (Siglip2Config): 配置类。 - base_model_prefix (str): 模型前缀,用于保存和加载模型时的命名。 - supports_gradient_checkpointing (bool): 是否支持梯度检查点。 - """ - - config_class = Siglip2Config - base_model_prefix = "siglip2" - supports_gradient_checkpointing = True - - # 不需要分割的模块列表 - _no_split_modules = [ - "Siglip2TextEmbeddings", - "Siglip2EncoderLayer", - "Siglip2VisionEmbeddings", - "Siglip2EncoderLayer", - "Siglip2MultiheadAttentionPoolingHead", - ] - # 是否支持 Flash Attention 2 - _supports_flash_attn_2 = True - # 是否支持 SDPA - _supports_sdpa = True - - def _init_weights(self, module): - """ - 初始化模型权重。 - - Args: - module (nn.Module): 需要初始化的模块。 - """ - if isinstance(module, Siglip2VisionEmbeddings): - width = ( - self.config.vision_config.hidden_size - if isinstance(self.config, Siglip2Config) - else self.config.hidden_size - ) - # 初始化位置嵌入权重 - nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width)) - elif isinstance(module, nn.Embedding): - default_flax_embed_init(module.weight) # 使用默认的 Flax 嵌入初始化方法 - elif isinstance(module, Siglip2Attention): - nn.init.xavier_uniform_(module.q_proj.weight) # 初始化查询投影权重 - nn.init.xavier_uniform_(module.k_proj.weight) # 初始化键投影权重 - nn.init.xavier_uniform_(module.v_proj.weight) # 初始化值投影权重 - nn.init.xavier_uniform_(module.out_proj.weight) # 初始化输出投影权重 - nn.init.zeros_(module.q_proj.bias) # 初始化查询投影偏置 - nn.init.zeros_(module.k_proj.bias) # 初始化键投影偏置 - nn.init.zeros_(module.v_proj.bias) # 初始化值投影偏置 - nn.init.zeros_(module.out_proj.bias) # 初始化输出投影偏置 - elif isinstance(module, Siglip2MLP): - nn.init.xavier_uniform_(module.fc1.weight) # 初始化第一个全连接层权重 - nn.init.xavier_uniform_(module.fc2.weight) # 初始化第二个全连接层权重 - nn.init.normal_(module.fc1.bias, std=1e-6) # 初始化第一个全连接层偏置 - nn.init.normal_(module.fc2.bias, std=1e-6) # 初始化第二个全连接层偏置 - elif isinstance(module, Siglip2MultiheadAttentionPoolingHead): - nn.init.xavier_uniform_(module.probe.data) # 初始化探测数据 - nn.init.xavier_uniform_(module.attention.in_proj_weight.data) # 初始化注意力输入投影权重 - nn.init.zeros_(module.attention.in_proj_bias.data) # 初始化注意力输入投影偏置 - elif isinstance(module, Siglip2Model): - logit_scale_init = torch.log(torch.tensor(1.0)) # 初始化 logit scale - module.logit_scale.data.fill_(logit_scale_init) - module.logit_bias.data.zero_() # 初始化 logit 偏置 - elif isinstance(module, Siglip2ForImageClassification): - # 初始化分类器权重 - nn.init.normal_( - module.classifier.weight, - std=self.config.vision_config.hidden_size**-0.5 * self.config.initializer_factor, - ) - elif isinstance(module, (nn.Linear, nn.Conv2d)): - # 使用 LeCun 正态分布初始化线性层或卷积层权重 - lecun_normal_(module.weight) - if module.bias is not None: - # 初始化偏置为零 - nn.init.zeros_(module.bias) - elif isinstance(module, nn.LayerNorm): - # 初始化层归一化偏置为零 - module.bias.data.zero_() - # 初始化层归一化权重为1.0 - module.weight.data.fill_(1.0) - - -class Siglip2TextModel(Siglip2PreTrainedModel): - """ - Siglip2 的文本模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - projection_size (int): 线性头的输出维度。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - use_return_dict (bool): 是否使用 `ModelOutput` 对象返回结果。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - config_class = Siglip2TextConfig - - def __init__(self, config: Siglip2TextConfig): - super().__init__(config) - self.text_model = Siglip2TextTransformer(config) - # 初始化权重并应用最终处理 - self.post_init() - - def get_input_embeddings(self) -> nn.Module: - """ - 获取输入嵌入层。 - - Returns: - nn.Module: 输入嵌入层,通常是 `token_embedding`。 - """ - # 返回文本嵌入模块中的 token 嵌入层 - return self.text_model.embeddings.token_embedding - - def set_input_embeddings(self, value): - """ - 设置输入嵌入层。 - - Args: - value (nn.Module): 要设置的输入嵌入层。 - """ - # 设置文本嵌入模块中的 token 嵌入层 - self.text_model.embeddings.token_embedding = value - - def forward( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入文本通过文本模型。 - - Args: - input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用文本 Transformer 模型的前向传播方法 - return self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - -class Siglip2MultiheadAttentionPoolingHead(nn.Module): - """ - 多头注意力池化头。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - num_attention_heads (int): 注意力头的数量。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - 其他配置参数,如 intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - - # 初始化探测参数,形状为 (1, 1, hidden_size) - self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size)) - # 实例化多头注意力层 - self.attention = torch.nn.MultiheadAttention(config.hidden_size, config.num_attention_heads, batch_first=True) - # 实例化层归一化层 - self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - # 实例化 MLP 模块 - self.mlp = Siglip2MLP(config) - # 注意力头的数量 - self.num_heads = config.num_attention_heads - - def forward(self, hidden_state: torch.Tensor, attention_mask: Optional[torch.Tensor] = None): - """ - 前向传播方法,应用多头注意力池化。 - - Args: - hidden_state (`torch.Tensor`): - 输入隐藏状态,形状为 `(batch_size, sequence_length, hidden_size)`。 - attention_mask (`torch.Tensor`, *optional*): - 注意力掩码张量,形状为 `(batch_size, sequence_length)`,用于屏蔽某些位置。 - - Returns: - `torch.Tensor`: - 池化后的隐藏状态,形状为 `(batch_size, hidden_size)`。 - """ - # 获取批次大小 - batch_size = hidden_state.shape[0] - # 重复探测参数以匹配批次大小 - probe = self.probe.repeat(batch_size, 1, 1) - - if attention_mask is not None: - # 获取目标长度和源长度 - target_len, source_len = probe.shape[1], hidden_state.shape[1] - # 准备 4D 注意力掩码 - attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_state.dtype, target_len) - # 重复掩码以匹配多头 - attention_mask = attention_mask.repeat(1, self.num_heads, target_len, 1) - # 重塑掩码形状 - attention_mask = attention_mask.reshape(-1, target_len, source_len) - - # 应用多头注意力层 - hidden_state = self.attention(probe, hidden_state, hidden_state, attn_mask=attention_mask)[0] - - # 保存残差连接的输入 - residual = hidden_state - # 应用层归一化 - hidden_state = self.layernorm(hidden_state) - # 应用 MLP 并进行残差连接 - hidden_state = residual + self.mlp(hidden_state) - - # 返回池化后的隐藏状态(第一个 token) - return hidden_state[:, 0] - - -class Siglip2VisionModel(Siglip2PreTrainedModel): - """ - Siglip2 的视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - num_attention_heads (int): 注意力头的数量。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - 其他配置参数,如 num_hidden_layers, attention_dropout, hidden_dropout_prob 等。 - """ - config_class = Siglip2VisionConfig - main_input_name = "pixel_values" - - def __init__(self, config: Siglip2VisionConfig): - super().__init__(config) - - # 实例化视觉 Transformer 模型 - self.vision_model = Siglip2VisionTransformer(config) - - # 初始化权重并应用最终处理 - self.post_init() - - def get_input_embeddings(self) -> nn.Module: - """ - 获取输入嵌入层。 - - Returns: - nn.Module: 输入嵌入层,通常是 `patch_embedding`。 - """ - # 返回视觉嵌入模块中的 patch 嵌入层 - return self.vision_model.embeddings.patch_embedding - - def forward( - self, - pixel_values: torch.FloatTensor, - pixel_attention_mask: torch.Tensor, - spatial_shapes: torch.LongTensor, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入像素值通过视觉模型。 - - Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 输入像素值张量。 - pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): - 空间形状张量,用于调整位置嵌入的大小。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉 Transformer 模型的前向传播方法 - return self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - -class Siglip2Model(Siglip2PreTrainedModel): - """ - Siglip2 模型,结合了文本和视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2Config): - 模型的配置对象,包含以下属性: - - text_config (Siglip2TextConfig): 文本模型的配置。 - - vision_config (Siglip2VisionConfig): 视觉模型的配置。 - - 其他配置参数,如 logit_scale, logit_bias 等。 - """ - config_class = Siglip2Config - - def __init__(self, config: Siglip2Config): - super().__init__(config) - - # 检查 text_config 是否为 Siglip2TextConfig 类型 - if not isinstance(config.text_config, Siglip2TextConfig): - raise TypeError( - "config.text_config is expected to be of type Siglip2TextConfig but is of type" - f" {type(config.text_config)}." - ) - - # 检查 vision_config 是否为 Siglip2VisionConfig 类型 - if not isinstance(config.vision_config, Siglip2VisionConfig): - raise TypeError( - "config.vision_config is expected to be of type Siglip2VisionConfig but is of type" - f" {type(config.vision_config)}." - ) - - # 获取文本配置 - text_config = config.text_config - # 获取视觉配置 - vision_config = config.vision_config - - # 首先,使用正确的注意力实现方式初始化文本和视觉模型 - text_model = Siglip2TextModel._from_config(text_config) - vision_model = Siglip2VisionModel._from_config(vision_config) - - # 其次,获取文本和视觉子模块(为了向后兼容) - # 获取文本模型的子模块 - self.text_model = text_model.text_model - # 获取视觉模型的子模块 - self.vision_model = vision_model.vision_model - - self.logit_scale = nn.Parameter(torch.randn(1)) - self.logit_bias = nn.Parameter(torch.randn(1)) - - # 初始化权重并应用最终处理 - self.post_init() - - def get_text_features( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> torch.FloatTensor: - """ - 获取文本特征。 - - Returns: - text_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 - - 示例: - - ```python - >>> from transformers import AutoTokenizer, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> tokenizer = AutoTokenizer.from_pretrained("google/siglip2-base-patch16-224") - - >>> # 重要:确保设置 padding="max_length",因为这是模型训练的方式 - >>> inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding="max_length", return_tensors="pt") - >>> with torch.no_grad(): - ... text_features = model.get_text_features(**inputs) - ``` - """ - # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用文本模型的前向传播方法 - text_outputs = self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取池化输出 - pooled_output = text_outputs[1] - - # 返回文本特征 - return pooled_output - - def get_image_features( - self, - pixel_values: Optional[torch.FloatTensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> torch.FloatTensor: - """ - 获取图像特征。 - - Returns: - image_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 - - 示例: - - ```python - >>> from PIL import Image - >>> import requests - >>> from transformers import AutoProcessor, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") - - >>> image = Image.open(requests.get(url, stream=True).raw) - - >>> inputs = processor(images=image, return_tensors="pt") - - >>> with torch.no_grad(): - ... image_features = model.get_image_features(**inputs) - ``` - """ - # 使用 Siglip2Model 的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - vision_outputs = self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取池化输出 - pooled_output = vision_outputs[1] - - # 返回图像特征 - return pooled_output - - def forward( - self, - input_ids: Optional[torch.LongTensor] = None, - pixel_values: Optional[torch.FloatTensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.LongTensor] = None, - return_loss: Optional[bool] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> Union[Tuple, Siglip2Output]: - """ - 前向传播方法,处理输入文本和图像通过 Siglip2 模型。 - - Returns: - - 示例: - - ```python - >>> from PIL import Image - >>> import requests - >>> from transformers import AutoProcessor, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") - - >>> image = Image.open(requests.get(url, stream=True).raw) - - >>> texts = ["a photo of 2 cats", "a photo of 2 dogs"] - >>> # 重要:我们传递 `padding=max_length`,因为模型是使用这种方式训练的 - >>> inputs = processor(text=texts, images=image, padding="max_length", return_tensors="pt") - - >>> with torch.no_grad(): - ... outputs = model(**inputs) - - >>> logits_per_image = outputs.logits_per_image - >>> probs = torch.sigmoid(logits_per_image) # 这些是概率 - >>> print(f"{probs[0][0]:.1%} that image 0 is '{texts[0]}'") - 31.9% that image 0 is 'a photo of 2 cats' - ``` - """ - # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - vision_outputs = self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 调用文本模型的前向传播方法 - text_outputs = self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取图像嵌入 - image_embeds = vision_outputs[1] - # 获取文本嵌入 - text_embeds = text_outputs[1] - - # 归一化特征 - image_embeds = image_embeds / image_embeds.norm(p=2, dim=-1, keepdim=True) - text_embeds = text_embeds / text_embeds.norm(p=2, dim=-1, keepdim=True) - - # 计算余弦相似度作为 logits - logits_per_text = torch.matmul(text_embeds, image_embeds.t().to(text_embeds.device)) - - logit_scale, logit_bias = self.logit_scale.to(text_embeds.device), self.logit_bias.to(text_embeds.device) - logits_per_text = logits_per_text * logit_scale.exp() + logit_bias - - # 转置得到图像-文本的 logits - logits_per_image = logits_per_text.t() - - loss = None - if return_loss: - eye = torch.eye(logits_per_text.size(0), device=logits_per_text.device) - m1_diag1 = -torch.ones_like(logits_per_text) + 2 * eye - loglik = torch.nn.functional.logsigmoid(m1_diag1 * logits_per_text) - nll = -torch.sum(loglik, dim=-1) - loss = nll.mean() - - if not return_dict: - output = (logits_per_image, logits_per_text, text_embeds, image_embeds, text_outputs, vision_outputs) - return ((loss,) + output) if loss is not None else output - - # 返回 Siglip2Output 对象 - return Siglip2Output( - loss=loss, - logits_per_image=logits_per_image, - logits_per_text=logits_per_text, - text_embeds=text_embeds, - image_embeds=image_embeds, - text_model_output=text_outputs, - vision_model_output=vision_outputs, - ) - - -class Siglip2ForImageClassification(Siglip2PreTrainedModel): - """ - Siglip2 的图像分类模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2Config): - 模型的配置对象,包含以下属性: - - vision_config (Siglip2VisionConfig): 视觉模型的配置。 - - num_labels (int): 分类标签的数量。 - - 其他配置参数,如 problem_type 等。 - """ - main_input_name = "pixel_values" - - def __init__(self, config: Siglip2Config) -> None: - super().__init__(config) - - # 分类标签的数量 - self.num_labels = config.num_labels - - # 使用正确的注意力实现方式创建视觉模型,并仅获取视觉模型的子模块(为了向后兼容) - vision_model = Siglip2VisionModel._from_config(config.vision_config) - self.vision_model = vision_model.vision_model - - # 分类器头 - self.classifier = ( - # 如果 num_labels > 0,则使用线性层作为分类器,否则使用恒等映射 - nn.Linear(config.vision_config.hidden_size, config.num_labels) if config.num_labels > 0 else nn.Identity() - ) - - # 初始化权重并应用最终处理 - self.post_init() - - def forward( - self, - pixel_values: Optional[torch.Tensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - labels: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入图像通过图像分类模型。 - - Args: - pixel_values (`torch.Tensor` of shape `(batch_size, num_channels, height, width)`, *optional*): - 输入像素值张量。 - pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`, *optional*): - 空间形状张量,用于调整位置嵌入的大小。 - labels (`torch.LongTensor` of shape `(batch_size,)`, *optional*): - 标签,用于计算图像分类/回归损失。索引应在 `[0, ..., config.num_labels - 1]`。 - 如果 `config.num_labels == 1`,则计算回归损失(均方损失),如果 `config.num_labels > 1`,则计算分类损失(交叉熵)。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, ImageClassifierOutput]`: - - 如果 `return_dict=True`,返回 `ImageClassifierOutput` 对象。 - - 否则,返回包含 `logits`, `hidden_states`, `attentions` 的元组。 - """ - # 设置是否输出注意力 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - # 设置是否输出隐藏状态 - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - outputs = self.vision_model( - pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取序列输出 - sequence_output = outputs[0] - - # 对 patch tokens 进行平均池化 - if pixel_attention_mask is not None: - # 准备池化掩码 - pool_mask = pixel_attention_mask[..., None].to(sequence_output.device) - # 应用掩码进行池化 - sequence_output = torch.sum(sequence_output * pool_mask, dim=1) / torch.sum(pool_mask, dim=1) - else: - # 否则,直接进行平均池化 - sequence_output = torch.mean(sequence_output, dim=1) - - # 应用分类器 - logits = self.classifier(sequence_output) - - loss = None - if labels is not None: - # 将标签移动到正确的设备以启用模型并行 - labels = labels.to(logits.device) - if self.config.problem_type is None: - if self.num_labels == 1: - # 设置问题类型为回归 - self.config.problem_type = "regression" - elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): - # 设置问题类型为单标签分类 - self.config.problem_type = "single_label_classification" - else: - # 设置问题类型为多标签分类 - self.config.problem_type = "multi_label_classification" - - if self.config.problem_type == "regression": - # 使用均方损失 - loss_fct = MSELoss() - if self.num_labels == 1: - # 计算回归损失 - loss = loss_fct(logits.squeeze(), labels.squeeze()) - else: - loss = loss_fct(logits, labels) - elif self.config.problem_type == "single_label_classification": - # 使用交叉熵损失 - loss_fct = CrossEntropyLoss() - # 计算分类损失 - loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) - elif self.config.problem_type == "multi_label_classification": - # 使用二元交叉熵损失 - loss_fct = BCEWithLogitsLoss() - # 计算多标签分类损失 - loss = loss_fct(logits, labels) - - if not return_dict: - # 返回元组形式的输出 - output = (logits,) + outputs[2:] - return ((loss,) + output) if loss is not None else output - - # 返回封装后的模型输出 - return ImageClassifierOutput( - loss=loss, - logits=logits, - hidden_states=outputs.hidden_states, - attentions=outputs.attentions, - ) diff --git a/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py b/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py deleted file mode 100644 index 7bbd89c39..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/configuration_siglip2.py +++ /dev/null @@ -1,185 +0,0 @@ -import os -from typing import Union -from transformers import PretrainedConfig -from transformers.utils import logging - -logger = logging.get_logger(__name__) - -class Siglip2VisionConfig(PretrainedConfig): - r""" - Siglip2VisionConfig 是用于存储 Siglip2VisionModel 配置的类。 - 它用于实例化一个 Siglip2 视觉编码器,根据指定的参数定义模型架构。 - - 默认配置对应于 SigLIP-2 ViT-Base 模型。 - """ - - model_type = "siglip2_vision" - - def __init__( - self, - hidden_size=768, - intermediate_size=3072, - num_hidden_layers=12, - num_attention_heads=12, - num_channels=3, - image_size=224, - patch_size=16, - hidden_act="gelu_pytorch_tanh", - layer_norm_eps=1e-6, - attention_dropout=0.0, - num_patches=None, # 如果不传,会自动计算 (image_size // patch_size) ** 2 - vision_use_head=True, - # 用于 Flash Attention 或 SDPA 的实现选择 - _attn_implementation="eager", - **kwargs, - ): - super().__init__(**kwargs) - - self.hidden_size = hidden_size - self.intermediate_size = intermediate_size - self.num_hidden_layers = num_hidden_layers - self.num_attention_heads = num_attention_heads - self.num_channels = num_channels - self.image_size = image_size - self.patch_size = patch_size - self.hidden_act = hidden_act - self.layer_norm_eps = layer_norm_eps - self.attention_dropout = attention_dropout - self.vision_use_head = vision_use_head - self._attn_implementation = _attn_implementation - - # 自动计算 num_patches,这对于位置编码的初始化至关重要 - if num_patches is None: - self.num_patches = (image_size // patch_size) ** 2 - else: - self.num_patches = num_patches - - @classmethod - def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig": - config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs) - - # 如果从 checkpoint 加载,确保 vision_config 字典被正确使用 - if "vision_config" in config_dict: - config_dict = config_dict["vision_config"] - - return cls.from_dict(config_dict, **kwargs) - - -class Siglip2TextConfig(PretrainedConfig): - r""" - Siglip2TextConfig 是用于存储 Siglip2TextModel 配置的类。 - """ - - model_type = "siglip2_text" - - def __init__( - self, - vocab_size=32000, - hidden_size=768, - intermediate_size=3072, - num_hidden_layers=12, - num_attention_heads=12, - max_position_embeddings=2048, - hidden_act="gelu_pytorch_tanh", - layer_norm_eps=1e-6, - attention_dropout=0.0, - # 投影层大小,即文本嵌入最后映射到的维度 - projection_size=768, - _attn_implementation="eager", - **kwargs, - ): - super().__init__(**kwargs) - - self.vocab_size = vocab_size - self.hidden_size = hidden_size - self.intermediate_size = intermediate_size - self.num_hidden_layers = num_hidden_layers - self.num_attention_heads = num_attention_heads - self.max_position_embeddings = max_position_embeddings - self.hidden_act = hidden_act - self.layer_norm_eps = layer_norm_eps - self.attention_dropout = attention_dropout - self.projection_size = projection_size - self._attn_implementation = _attn_implementation - - @classmethod - def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig": - config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs) - - # 如果从 checkpoint 加载,确保 text_config 字典被正确使用 - if "text_config" in config_dict: - config_dict = config_dict["text_config"] - - return cls.from_dict(config_dict, **kwargs) - - -class Siglip2Config(PretrainedConfig): - r""" - Siglip2Config 是用于存储 Siglip2Model 配置的类。 - 它包含实例化 Siglip2VisionConfig 和 Siglip2TextConfig 所需的所有参数。 - - Args: - vision_config (`dict`, *optional*): - 用于初始化 Siglip2VisionConfig 的字典。 - text_config (`dict`, *optional*): - 用于初始化 Siglip2TextConfig 的字典。 - kwargs (*optional*): - 传递给父类 PretrainedConfig 的关键字参数。 - """ - - model_type = "siglip2" - is_composition = True - - def __init__( - self, - vision_config=None, - text_config=None, - projection_dim=768, - logit_scale_init_value=2.6592, - logit_bias_init_value=-10.0, - **kwargs, - ): - # 1. 初始化 Vision Config - if vision_config is None: - self.vision_config = Siglip2VisionConfig() - logger.info("vision_config is None. Initializing the Siglip2VisionConfig with default values.") - elif isinstance(vision_config, dict): - self.vision_config = Siglip2VisionConfig(**vision_config) - elif isinstance(vision_config, Siglip2VisionConfig): - self.vision_config = vision_config - else: - raise TypeError(f"vision_config should be a dict or Siglip2VisionConfig, but got {type(vision_config)}") - - # 2. 初始化 Text Config - if text_config is None: - self.text_config = Siglip2TextConfig() - logger.info("text_config is None. Initializing the Siglip2TextConfig with default values.") - elif isinstance(text_config, dict): - self.text_config = Siglip2TextConfig(**text_config) - elif isinstance(text_config, Siglip2TextConfig): - self.text_config = text_config - else: - raise TypeError(f"text_config should be a dict or Siglip2TextConfig, but got {type(text_config)}") - - self.projection_dim = projection_dim - self.logit_scale_init_value = logit_scale_init_value - self.logit_bias_init_value = logit_bias_init_value - - # 确保初始化父类 - super().__init__(**kwargs) - - @classmethod - def from_vision_text_configs(cls, vision_config: Siglip2VisionConfig, text_config: Siglip2TextConfig, **kwargs): - r""" - 从 vision_config 和 text_config 实例化 Siglip2Config。 - """ - return cls(vision_config=vision_config, text_config=text_config, **kwargs) - - def to_dict(self): - """ - 将配置序列化为字典。 - """ - output = super().to_dict() - output["vision_config"] = self.vision_config.to_dict() - output["text_config"] = self.text_config.to_dict() - return output \ No newline at end of file diff --git a/PyTorch/build-in/Classification/SigLIP2/coverage.txt b/PyTorch/build-in/Classification/SigLIP2/coverage.txt deleted file mode 100644 index 254e65b6c..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/coverage.txt +++ /dev/null @@ -1,3 +0,0 @@ -all api: ['_amp_foreach_non_finite_check_and_unscale_', '_amp_update_scale_', '_copy_from', '_has_compatible_shallow_copy_type', '_local_scalar_dense', '_log_softmax', '_log_softmax_backward_data', '_pin_memory', '_reshape_alias', '_softmax', '_softmax_backward_data', 'add', 'add_', 'addmm', 'as_strided', 'bmm', 'bmm_backward', 'bmm_forward', 'contiguous', 'convolution', 'convolution_backward', 'copy_stride', 'div', 'eq', 'fill_', 'floor_divide', 'fused_sgd', 'gelu', 'gelu_backward', 'is_pinned', 'linear', 'matmul', 'mean', 'mm', 'mul', 'mul_', 'native_layer_norm', 'native_layer_norm_backward', 'nll_loss_backward', 'nll_loss_forward', 'reciprocal', 'resize_', 'scaled_dot_product_attention', 'set_', 'slice_backward', 'sum', 'topk_out', 'view', 'zero_'], total: 49 -fallback op: ['_upsample_bilinear2d_aa_backward_out', '_upsample_bilinear2d_aa_out'], total: 2 -coverage rate: 95.92% diff --git a/PyTorch/build-in/Classification/SigLIP2/readme b/PyTorch/build-in/Classification/SigLIP2/readme new file mode 100644 index 000000000..f47e51a04 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/readme @@ -0,0 +1,65 @@ +```markdown +## 1. 模型链接 +- 原始仓库链接: +https://github.com/huggingface/pytorch-image-models?tab=readme-ov-file#models + +## 2. 快速开始 + +使用本模型执行训练的主要流程如下: + +1. **基础环境安装**:介绍训练前需要完成的基础环境检查和安装。 +2. **获取数据集**:介绍如何获取训练所需的数据集。 +3. **构建环境**:介绍如何构建模型运行所需要的环境。 +4. **启动训练**:介绍如何运行训练。 + +### 2.1 基础环境安装 + +请参考主仓库的基础环境安装章节,完成训练前的基础环境检查和安装(如驱动、固件等)。 + +### 2.2 准备数据集 + +#### 2.2.1 获取数据集 + +训练使用 **CIFAR-100** 数据集。该数据集为开源数据集,包含 100 个类别的 60000 张彩色图像。 + +#### 2.2.2 处理数据集 + +请确保数据集已下载并解压。根据训练脚本的默认配置,建议将数据集存放在模型目录的上级 `data` 目录中(即 `../data`),或者根据实际路径修改训练命令中的 `--datapath` 参数。 + +### 2.3 构建环境 + +所使用的环境下需包含 PyTorch 框架虚拟环境。 + +1. 执行以下命令,启动虚拟环境(根据实际环境名称修改): + + ```bash + conda activate torch_env_py310 + +``` + +2. 安装 Python 依赖。确保已安装项目所需的依赖包: +```bash +pip install -r requirements_exact.txt + +``` + + + +### 2.4 启动训练 + +1. 在构建好的环境中,进入模型训练脚本所在目录。 + +2. 运行训练。该模型支持单机单卡训练。 +执行以下命令启动训练(使用 CIFAR-100 数据集,Batch Size 为 128): +```bash +python weloTrainStep.py \ + --name train \ + --arch siglip2 \ + --print_freq 1 \ + --steps 100 \ + --dataset cifar100 \ + --datapath ../data \ + --batch_size 32 \ + --epochs 100 + +``` diff --git a/PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt b/PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt new file mode 100644 index 000000000..7394b3319 --- /dev/null +++ b/PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt @@ -0,0 +1,89 @@ +addict==2.4.0 +aliyun-python-sdk-core==2.16.0 +aliyun-python-sdk-kms==2.16.5 +anyio==4.11.0 +astunparse==1.6.3 +certifi==2024.12.14 +cffi==2.0.0 +charset-normalizer==3.4.1 +click==8.3.1 +colorama==0.4.6 +contourpy==1.3.2 +crcmod==1.7 +cryptography==46.0.3 +cycler==0.12.1 +einops==0.8.1 +exceptiongroup==1.3.1 +filelock==3.14.0 +fonttools==4.60.1 +fsspec==2024.12.0 +future @ file:///croot/future_1730902796226/work +git-filter-repo==2.47.0 +h11==0.16.0 +hf-xet==1.2.0 +httpcore==1.0.9 +httpx==0.28.1 +huggingface_hub==1.1.5 +idna==3.10 +inplace-abn @ git+https://github.com/mapillary/inplace_abn.git@b50bfe9c7cd7116a3ab091a352b48d6ba5ee701c +Jinja2==3.1.5 +jmespath==0.10.0 +joblib==1.5.2 +kiwisolver==1.4.9 +Markdown==3.10 +markdown-it-py==4.0.0 +MarkupSafe==3.0.2 +matplotlib==3.10.7 +mdurl==0.1.2 +mmdet==3.3.0 +mmengine==0.10.7 +model-index==0.1.11 +mpmath==1.3.0 +networkx==3.4.2 +numpy==1.23.5 +opencv-python==4.12.0.88 +opendatalab==0.0.10 +openmim==0.3.9 +openxlab==0.1.3 +ordered-set==4.1.0 +oss2==2.17.0 +packaging @ file:///croot/packaging_1734472117206/work +pandas==2.3.3 +pillow==11.1.0 +platformdirs==4.5.1 +pycocotools==2.0.11 +pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work +pycryptodome==3.23.0 +Pygments==2.19.2 +pyparsing==3.2.5 +python-dateutil==2.9.0.post0 +pytz==2023.4 +PyYAML @ file:///croot/pyyaml_1728657952215/work +requests==2.28.2 +rich==13.4.2 +safetensors==0.7.0 +scikit-learn==1.7.2 +scipy==1.15.3 +shapely==2.1.2 +shellingham==1.5.4 +six @ file:///tmp/build/80754af9/six_1644875935023/work +sniffio==1.3.1 +sympy==1.13.3 +tabulate==0.9.0 +termcolor==3.2.0 +terminaltables==3.1.10 +threadpoolctl==3.6.0 +timm==1.0.22 +tomli==2.3.0 +torch @ file:///apps/torch-2.4.0a0%2Bgit4451b0e-cp310-cp310-linux_x86_64.whl#sha256=2e472c916044cac5a1a0e0d8b0e12bb943d8522b24ff826c8014dd444dccd378 +torch_sdaa @ file:///apps/torch_sdaa-2.0.0-cp310-cp310-linux_x86_64.whl#sha256=5aa57889b002e1231fbf806642e1353bfa016297bc25178396e89adc2b1f92e7 +torchaudio @ file:///apps/torchaudio-2.0.2%2Bda3eb8d-cp310-cp310-linux_x86_64.whl#sha256=46525c02fb7eaa8dafea860428de3d01e437ba8d6ff2cc228d7c71975ac4054b +torchdata @ file:///apps/torchdata-0.6.1%2Be1feeb2-py3-none-any.whl#sha256=aa2dc1a7732ea68adfad186978049bf68cc1afdbbdd1e17a8024227ab770e433 +torchtext @ file:///apps/torchtext-0.15.2a0%2B4571036-cp310-cp310-linux_x86_64.whl#sha256=7e42c684ba366f97b59ec37488bf95e416cce3892b6589200d2b3ad159ee5788 +torchvision @ file:///apps/torchvision-0.15.1a0%2B42759b1-cp310-cp310-linux_x86_64.whl#sha256=4b904db2d50102415536bc764bbc31c669b90b1b014f90964e9eccaadb2fd9eb +tqdm==4.65.2 +typer-slim==0.20.0 +typing_extensions==4.15.0 +tzdata==2025.2 +urllib3==1.26.20 +yapf==0.43.0 diff --git a/PyTorch/build-in/Classification/SigLIP2/run b/PyTorch/build-in/Classification/SigLIP2/run deleted file mode 100644 index 010c7412f..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/run +++ /dev/null @@ -1 +0,0 @@ -bash ../sdaaTest.sh siglip2 32 0 diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py b/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py deleted file mode 100644 index dae6fc302..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/siglip2SDAA.py +++ /dev/null @@ -1,315 +0,0 @@ -import math -import torch -import torch.nn as nn -import torch.nn.functional as F -from typing import Optional, Tuple, Union -from dataclasses import dataclass - -# ========================================== -# 1. 极简配置类 (Configuration) -# ========================================== -@dataclass -class Siglip2VisionConfig: - """ - SigLIP 2 视觉模型配置 (针对 ImageNet 分类精简版) - 默认参数对应 ViT-Base (86M params) - """ - hidden_size: int = 768 - intermediate_size: int = 3072 - num_hidden_layers: int = 12 - num_attention_heads: int = 12 - num_channels: int = 3 - image_size: int = 224 - patch_size: int = 16 - layer_norm_eps: float = 1e-6 - attention_dropout: float = 0.0 - num_labels: int = 1000 # ImageNet 类别数 - - def __post_init__(self): - # 自动计算 patch 数量,用于位置编码初始化 - self.num_patches = (self.image_size // self.patch_size) ** 2 - -# ========================================== -# 2. 核心模块 (Building Blocks) -# ========================================== - -class Siglip2VisionEmbeddings(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.embed_dim = config.hidden_size - self.patch_size = config.patch_size - self.image_size = config.image_size - - self.patch_embedding = nn.Conv2d( - in_channels=config.num_channels, - out_channels=self.embed_dim, - kernel_size=self.patch_size, - stride=self.patch_size, - padding="valid", # SigLIP 不做 padding - ) - - self.num_patches = config.num_patches - self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) - - # 注册位置 ID - self.register_buffer( - "position_ids", - torch.arange(self.num_patches).expand((1, -1)), - persistent=False, - ) - - def resize_positional_embeddings(self, positional_embeddings, spatial_shapes, max_length): - """ - SigLIP 2 核心特性:动态插值位置编码 - """ - batch_size = spatial_shapes.shape[0] - embed_dim = positional_embeddings.shape[-1] - - # 将 flat 的位置编码还原回 2D 网格: (H_grid, W_grid, Dim) - grid_size = int(self.num_patches**0.5) - pos_embed_grid = positional_embeddings.view(grid_size, grid_size, embed_dim) - - # Permute to (1, Dim, H, W) for interpolate - pos_embed_grid = pos_embed_grid.permute(2, 0, 1).unsqueeze(0) - - result_embeddings = torch.zeros( - (batch_size, max_length, embed_dim), - device=positional_embeddings.device, - dtype=positional_embeddings.dtype - ) - - for i in range(batch_size): - h_pixels, w_pixels = spatial_shapes[i] - # 计算目标 grid 大小 - h_grid = h_pixels // self.patch_size - w_grid = w_pixels // self.patch_size - - # 双线性插值 - resized = F.interpolate( - pos_embed_grid, - size=(h_grid, w_grid), - mode="bilinear", - align_corners=False, - antialias=True - ) - - # Flatten back: (Dim, H*W) -> (H*W, Dim) - resized = resized.squeeze(0).flatten(1).transpose(0, 1) - - # 填入结果 - seq_len = resized.shape[0] - if seq_len > max_length: - seq_len = max_length # 截断保护 - result_embeddings[i, :seq_len, :] = resized[:seq_len, :] - - return result_embeddings - - def forward(self, pixel_values: torch.Tensor, spatial_shapes: torch.Tensor) -> torch.Tensor: - # pixel_values: [B, 3, H, W] - patch_embeds = self.patch_embedding(pixel_values) - # [B, C, H_grid, W_grid] -> [B, C, Seq_Len] -> [B, Seq_Len, C] - embeddings = patch_embeds.flatten(2).transpose(1, 2) - - # 获取位置编码权重 - pos_weights = self.position_embedding.weight # [Num_Patches, Dim] - - # 动态插值位置编码 - resized_pos_embeds = self.resize_positional_embeddings( - pos_weights, spatial_shapes, max_length=embeddings.shape[1] - ) - - return embeddings + resized_pos_embeds - -class Siglip2MLP(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) - self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) - self.act = nn.GELU() # 默认 GELU - - def forward(self, hidden_states): - hidden_states = self.fc1(hidden_states) - hidden_states = self.act(hidden_states) - hidden_states = self.fc2(hidden_states) - return hidden_states - -class Siglip2Attention(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.embed_dim = config.hidden_size - self.num_heads = config.num_attention_heads - self.head_dim = self.embed_dim // self.num_heads - self.scale = self.head_dim**-0.5 - - self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) - - def forward(self, hidden_states): - batch_size, seq_len, _ = hidden_states.shape - - q = self.q_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) - k = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) - v = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) - - # 使用 PyTorch 原生 SDPA (自研卡对齐重点) - # 相当于 Softmax(Q @ K.T / scale) @ V - attn_output = F.scaled_dot_product_attention(q, k, v, scale=self.scale) - - attn_output = attn_output.transpose(1, 2).contiguous().reshape(batch_size, seq_len, self.embed_dim) - return self.out_proj(attn_output) - -class Siglip2EncoderLayer(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.layer_norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - self.self_attn = Siglip2Attention(config) - self.layer_norm2 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - self.mlp = Siglip2MLP(config) - - def forward(self, hidden_states): - # Pre-Norm 结构 - residual = hidden_states - hidden_states = self.layer_norm1(hidden_states) - hidden_states = self.self_attn(hidden_states) - hidden_states = residual + hidden_states - - residual = hidden_states - hidden_states = self.layer_norm2(hidden_states) - hidden_states = self.mlp(hidden_states) - hidden_states = residual + hidden_states - return hidden_states - -class Siglip2Encoder(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) - - def forward(self, hidden_states): - for layer in self.layers: - hidden_states = layer(hidden_states) - return hidden_states - -class Siglip2VisionTransformer(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.embeddings = Siglip2VisionEmbeddings(config) - self.encoder = Siglip2Encoder(config) - self.post_layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - - def forward(self, pixel_values, spatial_shapes): - hidden_states = self.embeddings(pixel_values, spatial_shapes) - hidden_states = self.encoder(hidden_states) - hidden_states = self.post_layernorm(hidden_states) - return hidden_states - -# ========================================== -# 3. 最终分类模型 (Model Wrapper) -# ========================================== - -class Siglip2ForImageClassification(nn.Module): - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.config = config - self.num_labels = config.num_labels - - # 骨干网络 - self.vision_model = Siglip2VisionTransformer(config) - - # 分类头 - self.classifier = nn.Linear(config.hidden_size, config.num_labels) - - # 初始化 - self.apply(self._init_weights) - - def _init_weights(self, module): - if isinstance(module, (nn.Linear, nn.Conv2d)): - nn.init.trunc_normal_(module.weight, std=0.02) - if module.bias is not None: - nn.init.zeros_(module.bias) - elif isinstance(module, nn.LayerNorm): - nn.init.zeros_(module.bias) - nn.init.ones_(module.weight) - elif isinstance(module, nn.Embedding): - nn.init.trunc_normal_(module.weight, std=0.02) - - def forward(self, pixel_values, labels=None): - """ - 标准的 ImageNet 训练接口 - Args: - pixel_values: [Batch, 3, Height, Width] - labels: [Batch] (0 ~ num_classes-1) - """ - b, c, h, w = pixel_values.shape - device = pixel_values.device - - # 1. 自动生成 spatial_shapes (SigLIP 2 必需) - # 假设 Batch 内所有图片尺寸一致 (ImageNet 默认行为) - spatial_shapes = torch.tensor([[h, w]], device=device).expand(b, 2) - - # 2. 骨干网络前向传播 - # Output: [Batch, Seq_Len, Hidden_Size] - sequence_output = self.vision_model(pixel_values, spatial_shapes) - - # 3. 全局平均池化 (Global Average Pooling) - 替代 CLS token - # [Batch, Seq_Len, Hidden_Size] -> [Batch, Hidden_Size] - pooled_output = sequence_output.mean(dim=1) - - # 4. 分类映射 - logits = self.classifier(pooled_output) - - loss = None - if labels is not None: - loss_fct = nn.CrossEntropyLoss() - loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) - return loss, logits - - return logits - -# ========================================== -# 4. 工厂函数 & 测试代码 -# ========================================== - -def Model(num_classes=1000, model_size='base'): - if model_size == 'base': - config = Siglip2VisionConfig( - hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, num_labels=num_classes - ) - elif model_size == 'large': - config = Siglip2VisionConfig( - hidden_size=1024, num_hidden_layers=24, num_attention_heads=16, intermediate_size=4096, num_labels=num_classes - ) - elif model_size == 'so400m': - config = Siglip2VisionConfig( - hidden_size=1152, num_hidden_layers=27, num_attention_heads=16, intermediate_size=4304, num_labels=num_classes - ) - else: - raise ValueError("Unsupported model size") - - return Siglip2ForImageClassification(config) - -if __name__ == "__main__": - print("正在初始化 SigLIP 2 (ImageNet Classification)...") - - # 1. 创建模型 - model = Model(model_size='base', num_classes=1000) - - # 2. 模拟输入数据 (Batch=2, RGB, 224x224) - pixel_values = torch.randn(2, 3, 224, 224) - labels = torch.tensor([0, 999], dtype=torch.long) # 假设两个标签 - - # 3. 运行前向传播 - print("开始前向传播测试...") - loss, logits = model(pixel_values, labels=labels) - - print(f"Loss: {loss.item():.4f}") - print(f"Logits Shape: {logits.shape}") # 应该是 (2, 1000) - - # 4. 运行反向传播 (验证梯度) - print("开始反向传播测试...") - loss.backward() - - print("测试完成!所有梯度计算正常。") - print(f"FC层梯度范数: {model.classifier.weight.grad.norm().item():.4f}") - diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg b/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.jpg deleted file mode 100644 index e54a8c4cc204bcf6970366475edca0544b1f7d0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36902 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 N@cExO1`d21`!8KH2eSYG diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt b/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt deleted file mode 100644 index b470d42db..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/siglip2_loss.txt +++ /dev/null @@ -1,29 +0,0 @@ -=== CUDA === -4.767600 5.222800 5.756000 5.191500 5.151500 5.159400 5.669600 5.087000 6.098400 6.147800 -5.758500 5.970300 5.500600 5.334700 6.209800 6.813000 6.486800 7.537900 6.307400 5.496100 -5.540500 5.648600 5.764700 5.439500 5.240700 5.696200 6.983000 6.788300 5.502300 5.642000 -5.446200 6.043000 6.056700 6.282900 6.528400 6.260600 5.938800 5.681700 6.247400 5.564100 -5.572400 7.095900 5.623700 5.794600 6.043000 5.577700 6.276600 5.304900 6.235300 5.737200 -5.268800 5.124500 4.965100 5.025600 5.006600 5.002600 5.181800 4.891100 4.650800 4.733300 -4.714200 4.554100 4.635800 4.662100 4.378500 4.484000 4.579700 4.646200 4.538900 4.847400 -4.579600 4.657000 4.368700 4.348400 4.691300 4.573400 4.487200 4.518500 4.565900 4.521200 -4.650500 4.267200 4.386800 4.284500 4.387000 4.452300 4.310300 4.388000 4.488500 4.249100 -4.505000 4.523500 4.330800 4.468500 4.534600 4.234700 4.550600 4.397000 4.467800 4.605600 - -=== SDAA === -4.732200 5.101900 5.626800 5.192600 5.163000 5.230600 5.745600 5.061500 6.252900 6.115400 -5.667200 5.806900 5.462300 5.088700 6.387700 7.127700 6.249800 7.723200 6.648400 5.588700 -5.387000 5.471700 5.711300 5.324100 5.167600 5.854000 6.896900 6.756300 5.429000 5.486700 -5.353800 5.956600 5.952600 6.345500 6.303800 5.499500 5.293800 5.298300 6.012800 5.092400 -5.114700 6.352700 4.922000 5.332700 5.151900 5.297900 5.128800 5.515700 5.725500 5.615100 -4.825800 4.598700 4.931500 5.068800 4.976100 5.066800 5.062600 5.077600 4.870700 4.723000 -4.938200 4.633300 4.860000 4.632900 4.075600 4.573700 4.596200 4.941700 4.772600 4.730500 -4.602500 4.741700 4.502600 4.457800 4.882900 4.477300 4.518200 4.582000 4.402200 4.679700 -4.429900 4.428600 4.467500 4.415800 4.431800 4.496300 4.469200 4.434700 4.498200 4.382200 -4.556100 4.600000 4.306000 4.488700 4.597600 4.219700 4.417100 4.352900 4.563400 4.631000 - -=== RESULT === -MeanRelativeError: -0.011135153214594594 -MeanAbsoluteError: -0.07194500000000004 -Rule,mean_absolute_error -0.07194500000000004 -pass mean_relative_error=-0.011135153214594594 <= 0.05 or mean_absolute_error=-0.07194500000000004 <= 0.0002 diff --git a/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py b/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py deleted file mode 100644 index 4c03a72c6..000000000 --- a/PyTorch/build-in/Classification/SigLIP2/siglip2_origin.py +++ /dev/null @@ -1,2164 +0,0 @@ -import math -import warnings -from dataclasses import dataclass -from typing import Any, Optional, Tuple, Union - -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss -from torch.nn.init import _calculate_fan_in_and_fan_out - -from configuration_siglip2 import Siglip2Config, Siglip2TextConfig, Siglip2VisionConfig - - -@dataclass -class Siglip2VisionOutput(): - """ - 视觉模型的输出基类,包含最后一层隐藏状态的池化结果得到的图像嵌入。 - - Args: - image_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): - 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的图像嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 图像嵌入,用于表示图像的特征。 - - last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): - 模型最后一层输出的隐藏状态序列。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, sequence_length, hidden_size)` - - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 - - hidden_states (tuple(torch.FloatTensor), 可选): - 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 - - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 - - attentions (tuple(torch.FloatTensor), 可选): - 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 - - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 - """ - - image_embeds: Optional[torch.FloatTensor] = None - last_hidden_state: torch.FloatTensor = None - hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None - attentions: Optional[Tuple[torch.FloatTensor, ...]] = None - - -@dataclass -class Siglip2TextOutput(): - """ - 文本模型的输出基类,包含最后一层隐藏状态的池化结果得到的文本嵌入。 - - Args: - text_embeds (torch.FloatTensor, 可选, 形状为 `(batch_size, output_dim)`): - 当模型初始化时设置 `with_projection=True`,则返回通过投影层应用于 `pooler_output` 得到的文本嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 文本嵌入,用于表示文本的特征。 - - last_hidden_state (torch.FloatTensor, 必填, 形状为 `(batch_size, sequence_length, hidden_size)`): - 模型最后一层输出的隐藏状态序列。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, sequence_length, hidden_size)` - - **说明**: 包含模型最后一层输出的隐藏状态,用于后续的任务处理。 - - hidden_states (tuple(torch.FloatTensor), 可选): - 当 `output_hidden_states=True` 被传递或 `config.output_hidden_states=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含模型每一层的隐藏状态输出,以及可选的初始嵌入输出。 - - **形状**: `(batch_size, sequence_length, hidden_size)` 每个元组元素。 - - attentions (tuple(torch.FloatTensor), 可选): - 当 `output_attentions=True` 被传递或 `config.output_attentions=True` 时返回。 - - **类型**: `tuple(torch.FloatTensor)` - - **说明**: 包含每一层的注意力权重,经过 softmax 后的注意力权重,用于计算自注意力头中的加权平均。 - - **形状**: `(batch_size, num_heads, sequence_length, sequence_length)` 每个元组元素。 - """ - - text_embeds: Optional[torch.FloatTensor] = None - last_hidden_state: torch.FloatTensor = None - hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None - attentions: Optional[Tuple[torch.FloatTensor, ...]] = None - - -@dataclass -class Siglip2Output(): - """ - Siglip2 模型的输出,包含图像-文本对比损失、相似度分数、嵌入以及子模型的输出。 - - Args: - loss (torch.FloatTensor, 可选, 形状为 `(1,)`): - 当 `return_loss=True` 时返回,用于图像-文本相似度的对比损失。 - - **类型**: `torch.FloatTensor` - - **形状**: `(1,)` - - **说明**: 对比损失,用于衡量图像和文本之间的相似度。 - - logits_per_image (torch.FloatTensor, 必填, 形状为 `(image_batch_size, text_batch_size)`): - `image_embeds` 和 `text_embeds` 之间的缩放点积分数,表示图像-文本相似度分数。 - - **类型**: `torch.FloatTensor` - - **形状**: `(image_batch_size, text_batch_size)` - - **说明**: 图像-文本相似度分数,用于评估图像和文本之间的匹配程度。 - - logits_per_text (torch.FloatTensor, 必填, 形状为 `(text_batch_size, image_batch_size)`): - `text_embeds` 和 `image_embeds` 之间的缩放点积分数,表示文本-图像相似度分数。 - - **类型**: `torch.FloatTensor` - - **形状**: `(text_batch_size, image_batch_size)` - - **说明**: 文本-图像相似度分数,用于评估文本和图像之间的匹配程度。 - - text_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 文本嵌入,用于表示文本的特征。 - - image_embeds (torch.FloatTensor, 必填, 形状为 `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 - - **类型**: `torch.FloatTensor` - - **形状**: `(batch_size, output_dim)` - - **说明**: 图像嵌入,用于表示图像的特征。 - - text_model_output (BaseModelOutputWithPooling): - [`Siglip2TextModel`] 的输出。 - - **类型**: `BaseModelOutputWithPooling` - - **说明**: 包含文本模型的详细输出信息,如隐藏状态等。 - - vision_model_output (BaseModelOutputWithPooling): - [`Siglip2VisionModel`] 的输出。 - - **类型**: `BaseModelOutputWithPooling` - - **说明**: 包含视觉模型的详细输出信息,如隐藏状态等。 - """ - - loss: Optional[torch.FloatTensor] = None - logits_per_image: torch.FloatTensor = None - logits_per_text: torch.FloatTensor = None - text_embeds: torch.FloatTensor = None - image_embeds: torch.FloatTensor = None - text_model_output: BaseModelOutputWithPooling = None - vision_model_output: BaseModelOutputWithPooling = None - - def to_tuple(self) -> Tuple[Any]: - """ - 将 Siglip2Output 对象转换为元组。 - - Returns: - Tuple[Any]: 包含 Siglip2Output 对象的各个属性值的元组。 - """ - return tuple( - self[k] if k not in ["text_model_output", "vision_model_output"] else getattr(self, k).to_tuple() - for k in self.keys() - ) - - -class Siglip2VisionEmbeddings(nn.Module): - """ - Siglip2 视觉嵌入模块,用于将图像像素值转换为嵌入向量,并添加位置嵌入。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含模型的各种配置参数,如隐藏层大小、patch 大小等。 - """ - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # patch 大小,表示每个图像块的高度和宽度 - self.patch_size = config.patch_size - - # 定义一个线性层,用于将每个图像 patch(像素块)映射到嵌入向量 - self.patch_embedding = nn.Linear( - # 输入特征数:通道数 * patch大小平方 - in_features=config.num_channels * self.patch_size * self.patch_size, - # 输出特征数:嵌入维度 - out_features=self.embed_dim, - ) - - # 图像被分割成的总patch数量 - self.num_patches = config.num_patches - # 计算位置嵌入的网格大小(假设图像是正方形) - self.position_embedding_size = int(self.num_patches**0.5) - # 定义一个嵌入层,用于存储每个patch的位置嵌入 - self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim) - - @staticmethod - def resize_positional_embeddings( - positional_embeddings: torch.Tensor, - spatial_shapes: torch.LongTensor, - max_length: int, - ) -> torch.Tensor: - """ - 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小。 - - Args: - positional_embeddings (`torch.Tensor`): - 位置嵌入张量,形状为 (高度, 宽度, 嵌入维度)。 - spatial_shapes (`torch.LongTensor`): - 空间形状张量,形状为 (batch_size, 2),用于调整位置嵌入的大小。 - 每个元素包含 [目标高度, 目标宽度]。 - max_length (`int`): - 填充后的最大长度,用于确保所有批次的位置嵌入具有相同的长度。 - - Returns: - `torch.Tensor`: - 调整大小并填充后的嵌入张量,形状为 (batch_size, max_length, 嵌入维度)。 - """ - # 获取批次大小 - batch_size = spatial_shapes.shape[0] - # 获取嵌入维度 - embed_dim = positional_embeddings.shape[-1] - # 记录原始数据类型 - source_dtype = positional_embeddings.dtype - - # 创建一个空的张量,用于存储调整后的位置嵌入 - resulted_positional_embeddings = torch.empty( - (batch_size, max_length, embed_dim), - device=positional_embeddings.device, - dtype=source_dtype, - ) - - # 将位置嵌入的维度顺序从 (高度, 宽度, 嵌入维度) 转换为 (嵌入维度, 高度, 宽度) 以便进行插值 - positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0) - - # 如果设备是 CPU,则将数据类型上转换为 float32,因为 CPU 不支持 bfloat16/float16 的 antialias - if positional_embeddings.device.type == "cpu": - positional_embeddings = positional_embeddings.to(torch.float32) - - for i in range(batch_size): - # 获取当前批次的目标高度和宽度 - # (1, dim, height, width) -> (1, dim, target_height, target_width) - height, width = spatial_shapes[i] - # 对位置嵌入进行双线性插值,调整到目标尺寸 - resized_embeddings = F.interpolate( - positional_embeddings, - size=(height, width), - mode="bilinear", - align_corners=False, - antialias=True, - ) - - # 将调整后的嵌入形状从 (1, 嵌入维度, 高度, 宽度) 转换为 (高度 * 宽度, 嵌入维度) - # (1, dim, target_height, target_width) -> (target_height * target_width, dim) - resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1) - - # 将数据类型转换回原始类型 - resized_embeddings = resized_embeddings.to(source_dtype) - - # 将调整后的嵌入填充到结果张量中 - resulted_positional_embeddings[i, : height * width] = resized_embeddings - # 对于不足的部分,用第一个位置的嵌入填充 - resulted_positional_embeddings[i, height * width :] = resized_embeddings[0] - - return resulted_positional_embeddings - - def forward(self, pixel_values: torch.FloatTensor, spatial_shapes: torch.LongTensor) -> torch.Tensor: - """ - 前向传播方法,用于生成图像嵌入。 - - Args: - pixel_values (`torch.FloatTensor`): - 像素值张量,形状为 (batch_size, 最大patch数量, 通道数 * patch大小平方)。 - spatial_shapes (`List[Tuple[int, int]]`): - 空间形状列表,形状为 (batch_size, 2),用于调整位置嵌入的大小。 - 每个元素包含 [高度, 宽度]。 - - Returns: - `torch.Tensor`: - 生成的图像嵌入张量,形状为 (batch_size, 最大patch数量, 嵌入维度)。 - """ - # 将像素值张量转换为与patch_embedding权重相同的类型 - target_dtype = self.patch_embedding.weight.dtype - patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype)) - - # 获取位置嵌入,并调整其形状为 (高度, 宽度, 嵌入维度) - positional_embeddings = self.position_embedding.weight.reshape( - self.position_embedding_size, self.position_embedding_size, -1 - ) - - # 调整位置嵌入的大小以适应图像的特定尺寸,并填充到固定大小 - resized_positional_embeddings = self.resize_positional_embeddings( - positional_embeddings, spatial_shapes, max_length=pixel_values.shape[1] - ) - - # 将位置嵌入添加到patch嵌入中,得到最终的图像嵌入 - embeddings = patch_embeds + resized_positional_embeddings - return embeddings - - -class Siglip2Attention(nn.Module): - """ - 多头注意力机制,源自论文 'Attention Is All You Need'。 - - Args: - config: - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小,也是注意力机制的嵌入维度。 - - num_attention_heads (int): 注意力头的数量。 - - attention_dropout (float): 注意力权重在 dropout 时的丢弃概率。 - """ - - def __init__(self, config): - super().__init__() - self.config = config - # 注意力机制的嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # 注意力头的数量 - self.num_heads = config.num_attention_heads - # 每个注意力头的维度 - self.head_dim = self.embed_dim // self.num_heads - - # 检查 embed_dim 是否能被 num_heads 整除 - if self.head_dim * self.num_heads != self.embed_dim: - raise ValueError( - f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:" - f" {self.num_heads})." - ) - - # 缩放因子,用于缩放注意力得分 - self.scale = self.head_dim**-0.5 - self.dropout = config.attention_dropout - - # 定义线性层,用于计算查询 (query)、键 (key) 和值 (value) 的投影 - self.k_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.v_proj = nn.Linear(self.embed_dim, self.embed_dim) - self.q_proj = nn.Linear(self.embed_dim, self.embed_dim) - - # 输出投影层 - self.out_proj = nn.Linear(self.embed_dim, self.embed_dim) - - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - 前向传播方法,计算多头注意力。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.Tensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`, 可选): - 是否返回注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - """ - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # 重塑查询、键和值张量,以适应多头注意力的计算 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - - # 获取键和值序列的长度 - k_v_seq_len = key_states.shape[-2] - # 计算原始的注意力得分 - attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale - - # 检查注意力权重的形状是否正确 - if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len): - raise ValueError( - f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is" - f" {attn_weights.size()}" - ) - - # 如果提供了注意力掩码,则将其添加到注意力得分中 - if attention_mask is not None: - if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len): - raise ValueError( - f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, but is {attention_mask.size()}" - ) - attn_weights = attn_weights + attention_mask - - # 将注意力得分转换为 float32 以进行 softmax 计算,然后转换回原始数据类型 - attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) - attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) - - # 计算最终的注意力输出 - attn_output = torch.matmul(attn_weights, value_states) - - # 检查注意力输出的形状是否正确 - if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim): - raise ValueError( - f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is" - f" {attn_output.size()}" - ) - - # 重塑注意力输出张量,以适应后续的处理 - attn_output = attn_output.transpose(1, 2).contiguous() - attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim) - - # 应用输出投影层 - attn_output = self.out_proj(attn_output) - - # 根据需要返回注意力权重 - return attn_output, attn_weights - - -class Siglip2FlashAttention2(Siglip2Attention): - """ - Siglip2Attention 的 Flash Attention 模块。该模块继承自 `Siglip2Attention`,因此模型的权重保持不变。 - 唯一需要修改的是前向传播方法,需要正确调用 Flash Attention 的公共 API,并处理输入中可能存在的填充 token。 - - Attributes: - is_causal (bool): - 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 - """ - - is_causal = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Adapted from transformers.models.llama.modeling_llama.LlamaFlashAttention2.forward - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.LongTensor] = None, - output_attentions: bool = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: - """ - 前向传播方法,计算 Flash Attention。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.LongTensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`): - 是否输出注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - - `attn_weights_tuple`: 其他注意力权重信息(可选)。 - """ - output_attentions = False - - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # Flash Attention 要求输入的形状为 (batch_size, seq_length, head_dim, hidden_dim) - # 因此我们保持原始形状不变 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim) - - dropout_rate = self.dropout if self.training else 0.0 - - # 在 PEFT 中,通常我们将层归一化层转换为 float32 以提高训练稳定性 - # 因此,输入的隐藏状态会被静默地转换为 float32。因此,我们需要将其转换回正确的类型,以确保一切按预期工作。 - # 这种转换可能会减慢训练和推理速度,因此建议不要将 LayerNorms 转换为 fp32。 - - input_dtype = query_states.dtype - if input_dtype == torch.float32: - if torch.is_autocast_enabled(): - target_dtype = torch.get_autocast_gpu_dtype() - # 处理模型量化的情形 - elif hasattr(self.config, "_pre_quantization_dtype"): - target_dtype = self.config._pre_quantization_dtype - else: - target_dtype = self.q_proj.weight.dtype - - logger.warning_once( - f"The input hidden states seems to be silently casted in float32, this might be related to" - f" the fact you have upcasted embedding or layer norm layers in float32. We will cast back the input in" - f" {target_dtype}." - ) - - query_states = query_states.to(target_dtype) - key_states = key_states.to(target_dtype) - value_states = value_states.to(target_dtype) - - # 调用 Flash Attention 的前向传播方法 - attn_output = _flash_attention_forward( - query_states, - key_states, - value_states, - attention_mask, - q_len, - dropout=dropout_rate, - is_causal=self.is_causal, - use_top_left_mask=self._flash_attn_uses_top_left_mask, - ) - - # 重塑注意力输出张量 - attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim).contiguous() - attn_output = self.out_proj(attn_output) - - if not output_attentions: - attn_weights = None - - # 返回注意力输出和可选的注意力权重 - return attn_output, attn_weights - - -class Siglip2SdpaAttention(Siglip2Attention): - """ - 使用 torch.nn.functional.scaled_dot_product_attention 的 Siglip2 注意力模块。该模块继承自 `Siglip2Attention`,因为模块的权重保持不变。 - 唯一的变化是在前向传播方法中,以适应 SDPA API。 - - Attributes: - is_causal (bool): - 是否为因果注意力。如果为 `True`,则只允许对当前和之前的 token 进行注意力计算。 - """ - - is_causal = False - - # Adapted from Siglip2Attention.forward and transformers.models.llama.modeling_llama.LlamaSdpaAttention.forward - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - 前向传播方法,计算使用 SDPA 的注意力。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 (batch_size, 时间步长度, 通道数)。 - attention_mask (`torch.Tensor`, 可选): - 注意力掩码张量,用于屏蔽某些位置,形状为 (batch_size, 1, 时间步长度, 时间步长度)。 - output_attentions (`bool`, 可选): - 是否输出注意力权重。 - - Returns: - `Tuple[torch.Tensor, Optional[torch.Tensor]]`: - - `attn_output`: 注意力输出,形状为 (batch_size, 时间步长度, 隐藏层大小)。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 (batch_size, num_heads, 时间步长度, 时间步长度)。 - """ - if output_attentions: - return super().forward( - hidden_states=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - ) - - # 获取批次大小和时间步长度 - batch_size, q_len, _ = hidden_states.size() - - # 计算查询 (query)、键 (key) 和值 (value) 的投影 - query_states = self.q_proj(hidden_states) - key_states = self.k_proj(hidden_states) - value_states = self.v_proj(hidden_states) - - # 重塑张量以适应多头注意力的计算 - query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2) - - if query_states.device.type == "cuda" and attention_mask is not None: - query_states = query_states.contiguous() - key_states = key_states.contiguous() - value_states = value_states.contiguous() - - # 我们通过 `is_causal` 语句而不是 SDPA 中的内联条件分配来调度到 SDPA 的 Flash Attention 或 Efficient 内核, - # 以支持 torch.compile 的动态形状和完整图选项。内联条件会阻止动态形状的编译。 - is_causal = True if self.is_causal and q_len > 1 else False - - attn_output = torch.nn.functional.scaled_dot_product_attention( - query_states, - key_states, - value_states, - attn_mask=attention_mask, - dropout_p=self.dropout if self.training else 0.0, - is_causal=is_causal, - ) - - # 重塑注意力输出张量 - attn_output = attn_output.transpose(1, 2).contiguous() - attn_output = attn_output.view(batch_size, q_len, self.embed_dim) - - # 应用输出投影层 - attn_output = self.out_proj(attn_output) - - # 返回注意力输出,不返回注意力权重 - return attn_output, None - - -class Siglip2MLP(nn.Module): - """ - Siglip2 的多层感知机(MLP)模块,用于在注意力机制之后进行非线性变换。 - - Args: - config: - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 - """ - def __init__(self, config): - super().__init__() - self.config = config - # 根据配置选择激活函数 - self.activation_fn = ACT2FN[config.hidden_act] - # 定义第一个全连接层,将隐藏层大小映射到中间层大小 - self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) - # 定义第二个全连接层,将中间层大小映射回隐藏层大小 - self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) - - def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: - """ - 前向传播方法,应用 MLP 变换。 - - Args: - hidden_states (`torch.Tensor`): - 输入张量,形状为 `(batch_size, seq_length, hidden_size)`。 - - Returns: - `torch.Tensor`: - 变换后的张量,形状为 `(batch_size, seq_length, hidden_size)`。 - """ - # 应用第一个全连接层 - hidden_states = self.fc1(hidden_states) - # 应用激活函数 - hidden_states = self.activation_fn(hidden_states) - # 应用第二个全连接层 - hidden_states = self.fc2(hidden_states) - # 返回变换后的张量 - return hidden_states - - -# 定义注意力机制的实现类映射 -SIGLIP2_ATTENTION_CLASSES = { - "eager": Siglip2Attention, - "flash_attention_2": Siglip2FlashAttention2, - "sdpa": Siglip2SdpaAttention, -} - - -class Siglip2EncoderLayer(nn.Module): - """ - Siglip2 的编码器层,包含自注意力机制和 MLP 模块。 - - Args: - config (Siglip2Config): - 模型配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 - """ - def __init__(self, config: Siglip2Config): - super().__init__() - - # 嵌入维度,通常与隐藏层大小相同 - self.embed_dim = config.hidden_size - # 根据配置选择注意力机制的实现类,并实例化 - self.self_attn = SIGLIP2_ATTENTION_CLASSES[config._attn_implementation](config=config) - # 定义第一个层归一化层 - self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) - # 实例化 MLP 模块 - self.mlp = Siglip2MLP(config) - # 定义第二个层归一化层 - self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) - - def forward( - self, - hidden_states: torch.Tensor, - attention_mask: torch.Tensor, - output_attentions: Optional[bool] = False, - ) -> Tuple[torch.FloatTensor]: - """ - 前向传播方法,处理输入张量通过自注意力和 MLP。 - - Args: - hidden_states (`torch.FloatTensor`): - 输入张量,形状为 `(batch_size, seq_len, embed_dim)`。 - attention_mask (`torch.FloatTensor`): - 注意力掩码张量,形状为 `(batch_size, 1, q_len, k_v_seq_len)`,其中填充元素由非常大的负值表示。 - output_attentions (`bool`, 可选, 默认值为 `False`): - 是否返回所有注意力层的注意力权重。 - - Returns: - `Tuple[torch.FloatTensor]`: - - `hidden_states`: 变换后的隐藏状态,形状为 `(batch_size, seq_len, embed_dim)`。 - - `attn_weights`: 注意力权重,如果 `output_attentions` 为 `True`,则返回,形状为 `(batch_size, num_heads, q_len, k_v_seq_len)`。 - """ - # 保存残差连接的输入 - residual = hidden_states - - # 应用第一个层归一化 - hidden_states = self.layer_norm1(hidden_states) - # 应用自注意力机制 - hidden_states, attn_weights = self.self_attn( - hidden_states=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - ) - # 残差连接 - hidden_states = residual + hidden_states - - # 保存残差连接的输入 - residual = hidden_states - - # 应用第二个层归一化 - hidden_states = self.layer_norm2(hidden_states) - # 应用 MLP - hidden_states = self.mlp(hidden_states) - # 残差连接 - hidden_states = residual + hidden_states - # 打包输出 - outputs = (hidden_states,) - - if output_attentions: - # 如果需要,添加注意力权重到输出 - outputs += (attn_weights,) - - return outputs - - -class Siglip2Encoder(nn.Module): - """ - Transformer 编码器,由 `config.num_hidden_layers` 个自注意力层组成。每个层都是 [`Siglip2EncoderLayer`] 的实例。 - - Args: - config (Siglip2Config): - 模型配置对象,包含以下属性: - - num_hidden_layers (int): 编码器的层数。 - - 其他配置参数,如 hidden_size, intermediate_size, hidden_act, layer_norm_eps, output_attentions, output_hidden_states, use_return_dict 等。 - """ - - def __init__(self, config: Siglip2Config): - super().__init__() - self.config = config - self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)]) - self.gradient_checkpointing = False - - # Ignore copy - def forward( - self, - inputs_embeds, - attention_mask: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入嵌入通过多个自注意力层。 - - Args: - inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`): - 可选地,直接传递嵌入表示,而不是传递 `input_ids`。这在您希望对如何将 `input_ids` 索引转换为关联向量有更多控制时非常有用。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 掩码张量,用于避免在填充 token 索引上执行注意力计算。掩码值选择 `[0, 1]`: - - - `1` 表示 **未掩码** 的 token, - - `0` 表示 **掩码** 的 token。 - - [什么是注意力掩码?](../glossary#attention-mask) - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutput]`: - - 如果 `return_dict=True`,返回 `BaseModelOutput` 对象。 - - 否则,返回包含 `hidden_states`, `encoder_states`, `all_attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 初始化存储隐藏状态和注意力权重的容器 - encoder_states = () if output_hidden_states else None - all_attentions = () if output_attentions else None - - # 设置初始隐藏状态为输入嵌入 - hidden_states = inputs_embeds - - # 遍历所有编码器层 - for encoder_layer in self.layers: - if output_hidden_states: - # 存储当前隐藏状态 - encoder_states = encoder_states + (hidden_states,) - - # 如果启用梯度检查点,则使用检查点机制 - if self.gradient_checkpointing and self.training: - layer_outputs = self._gradient_checkpointing_func( - encoder_layer.__call__, - hidden_states, - attention_mask, - output_attentions, - ) - else: - # 否则,正常调用编码器层的前向传播方法 - layer_outputs = encoder_layer( - hidden_states, - attention_mask, - output_attentions=output_attentions, - ) - - # 更新隐藏状态 - hidden_states = layer_outputs[0] - - if output_attentions: - # 存储注意力权重 - all_attentions = all_attentions + (layer_outputs[1],) - - if output_hidden_states: - # 存储最终隐藏状态 - encoder_states = encoder_states + (hidden_states,) - - if not return_dict: - return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None) - return BaseModelOutput( - last_hidden_state=hidden_states, hidden_states=encoder_states, attentions=all_attentions - ) - - -# 视觉输入参数的中文文档字符串 -SIGLIP2_VISION_INPUTS_DOCSTRING = r""" -Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 像素值。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。详情见 [`CLIPImageProcessor.__call__`]。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。详见返回的张量中的 `attentions`。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。详见返回的张量中的 `hidden_states`。 - interpolate_pos_encoding (`bool`, *optional*, defaults to `False`): - 是否插值预训练的位置编码。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 -""" - - -class Siglip2VisionTransformer(nn.Module): - """ - Siglip2 的视觉 Transformer 模型,包含图像嵌入、编码器、层归一化以及可选的头部模块。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - vision_use_head (bool): 是否使用头部模块。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - - # 实例化视觉嵌入模块 - self.embeddings = Siglip2VisionEmbeddings(config) - # 实例化编码器模块 - self.encoder = Siglip2Encoder(config) - # 实例化后置层归一化层 - self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) - # 判断是否使用头部模块 - self.use_head = True if not hasattr(config, "vision_use_head") else config.vision_use_head - if self.use_head: - self.head = Siglip2MultiheadAttentionPoolingHead(config) - - # 判断是否使用 Flash Attention 2 - self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" - - def forward( - self, - pixel_values: torch.FloatTensor, - attention_mask: torch.Tensor, - spatial_shapes: torch.LongTensor, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入图像像素值通过视觉 Transformer 模型。 - - Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 像素值张量。默认情况下,如果提供填充,将被忽略。可以使用 [`AutoImageProcessor`] 获取像素值。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): - 空间形状张量,用于调整位置嵌入的大小。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 应用视觉嵌入模块,将像素值转换为嵌入向量 - hidden_states = self.embeddings(pixel_values, spatial_shapes) - - # 处理注意力掩码 - if attention_mask is not None and not self._use_flash_attention_2: - # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] - encoder_attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) - else: - encoder_attention_mask = attention_mask - - # 应用编码器模块 - encoder_outputs = self.encoder( - inputs_embeds=hidden_states, - attention_mask=encoder_attention_mask, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取最后一层的隐藏状态 - last_hidden_state = encoder_outputs[0] - # 应用后置层归一化 - last_hidden_state = self.post_layernorm(last_hidden_state) - - # 应用头部模块(如果使用) - pooler_output = self.head(last_hidden_state, attention_mask) if self.use_head else None - - # 根据 return_dict 参数返回不同的输出格式 - if not return_dict: - return (last_hidden_state, pooler_output) + encoder_outputs[1:] - - return BaseModelOutputWithPooling( - last_hidden_state=last_hidden_state, - pooler_output=pooler_output, - hidden_states=encoder_outputs.hidden_states, - attentions=encoder_outputs.attentions, - ) - - -class Siglip2TextEmbeddings(nn.Module): - """ - Siglip2 的文本嵌入模块,用于将输入的 token ID 转换为嵌入向量,并添加位置嵌入。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - vocab_size (int): 词汇表大小。 - - hidden_size (int): 隐藏层大小。 - - max_position_embeddings (int): 最大位置嵌入长度。 - """ - def __init__(self, config: Siglip2TextConfig): - super().__init__() - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - - # 定义 token 嵌入层,将 token ID 转换为嵌入向量 - self.token_embedding = nn.Embedding(config.vocab_size, embed_dim) - # 定义位置嵌入层,为每个位置生成位置嵌入 - self.position_embedding = nn.Embedding(config.max_position_embeddings, embed_dim) - - # 注册一个缓冲区,存储位置 ID 张量,并在模型保存时不进行序列化 - self.register_buffer( - "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False - ) - - def forward( - self, - input_ids: Optional[torch.LongTensor] = None, - position_ids: Optional[torch.LongTensor] = None, - inputs_embeds: Optional[torch.FloatTensor] = None, - ) -> torch.Tensor: - """ - 前向传播方法,将输入的 token ID 或嵌入向量转换为包含位置嵌入的嵌入向量。 - - Args: - input_ids (`torch.LongTensor`, *optional*): - 输入的 token ID 张量,形状为 `(batch_size, sequence_length)`。 - position_ids (`torch.LongTensor`, *optional*): - 位置 ID 张量,形状为 `(batch_size, sequence_length)`。如果未提供,则根据 `input_ids` 自动生成。 - inputs_embeds (`torch.FloatTensor`, *optional*): - 预先计算的输入嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。如果提供,则忽略 `input_ids`。 - - Returns: - `torch.Tensor`: - 包含位置嵌入的嵌入向量,形状为 `(batch_size, sequence_length, hidden_size)`。 - """ - # 获取序列长度 - seq_length = input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2] - # 获取最大位置嵌入长度 - max_position_embedding = self.position_embedding.weight.shape[0] - - # 检查序列长度是否超过最大位置嵌入长度 - if seq_length > max_position_embedding: - raise ValueError( - f"Sequence length must be less than max_position_embeddings (got `sequence length`: " - f"{seq_length} and max_position_embeddings: {max_position_embedding}" - ) - - # 如果未提供位置 ID,则自动生成 - if position_ids is None: - position_ids = self.position_ids[:, :seq_length] - - # 如果未提供输入嵌入向量,则使用 token 嵌入层进行嵌入 - if inputs_embeds is None: - inputs_embeds = self.token_embedding(input_ids) - - # 获取位置嵌入 - position_embeddings = self.position_embedding(position_ids) - - # 将 token 嵌入和位置嵌入相加,得到最终的嵌入向量 - embeddings = inputs_embeds + position_embeddings - - return embeddings - - -def _trunc_normal_(tensor, mean, std, a, b): - """ - 对输入张量进行截断正态分布初始化。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - mean (float): 正态分布的均值。 - std (float): 正态分布的标准差。 - a (float): 截断的下界。 - b (float): 截断的上界。 - """ - def norm_cdf(x): - """ - 计算标准正态分布的累积分布函数(CDF)。 - - Args: - x (float): 输入值。 - - Returns: - float: 标准正态分布的 CDF 值。 - """ - return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0 - - # 检查均值是否在截断范围内超过2个标准差 - if (mean < a - 2 * std) or (mean > b + 2 * std): - warnings.warn( - "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. " - "The distribution of values may be incorrect.", - stacklevel=2, - ) - - # 通过使用截断的均匀分布,然后使用正态分布的逆 CDF 来生成值。 - # 获取上下 CDF 值 - l = norm_cdf((a - mean) / std) - u = norm_cdf((b - mean) / std) - - # 将张量均匀地填充为 [2l-1, 2u-1] 范围内的值 - tensor.uniform_(2 * l - 1, 2 * u - 1) - - # 使用逆误差函数(erfinv)进行逆 CDF 变换,得到截断的标准正态分布 - tensor.erfinv_() - - # 将分布转换为指定的均值和标准差 - tensor.mul_(std * math.sqrt(2.0)) - tensor.add_(mean) - - # 截断以确保值在正确的范围内 - tensor.clamp_(min=a, max=b) - - -def trunc_normal_tf_( - tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0, a: float = -2.0, b: float = 2.0 -) -> torch.Tensor: - """ - 使用截断正态分布填充输入张量。值实际上是从正态分布 :math:`\\mathcal{N}(\\text{mean}, \\text{std}^2)` 中抽取的, - 超出 :math:`[a, b]` 的值将被重新抽取,直到它们在边界内。该方法在 :math:`a \\leq \\text{mean} \\leq b` 时效果最佳。 - - 注意:这个 'tf' 变体更接近于 TensorFlow / JAX 的实现,其中边界 [a, b] 在采样正态分布(均值为0,标准差为1.0)时应用, - 然后结果通过均值和标准差参数进行缩放和平移。 - - Args: - tensor (torch.Tensor): 一个 n 维的 `torch.Tensor`。 - mean (float): 正态分布的均值,默认为0.0。 - std (float): 正态分布的标准差,默认为1.0。 - a (float): 最小截断值,默认为-2.0。 - b (float): 最大截断值,默认为2.0。 - """ - with torch.no_grad(): - # 使用标准正态分布(均值为0,标准差为1.0)进行截断初始化 - _trunc_normal_(tensor, 0, 1.0, a, b) - # 将分布缩放和平移到指定的均值和标准差 - tensor.mul_(std).add_(mean) - - -def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"): - """ - 方差缩放初始化方法,用于根据指定的模式和分布初始化张量。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - scale (float, optional): 缩放因子,默认为1.0。 - mode (str, optional): 缩放模式,可以是 'fan_in', 'fan_out' 或 'fan_avg',默认为 'fan_in'。 - distribution (str, optional): 分布类型,可以是 'truncated_normal', 'normal' 或 'uniform',默认为 'normal'。 - """ - fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor) - if mode == "fan_in": - denom = fan_in - elif mode == "fan_out": - denom = fan_out - elif mode == "fan_avg": - denom = (fan_in + fan_out) / 2 - - variance = scale / denom - - if distribution == "truncated_normal": - # 标准正态分布截断到 (-2, 2) 的标准差常数 - trunc_normal_tf_(tensor, std=math.sqrt(variance) / 0.87962566103423978) - elif distribution == "normal": - with torch.no_grad(): - tensor.normal_(std=math.sqrt(variance)) - elif distribution == "uniform": - bound = math.sqrt(3 * variance) - with torch.no_grad(): - tensor.uniform_(-bound, bound) - else: - raise ValueError(f"invalid distribution {distribution}") - - -def lecun_normal_(tensor): - """ - LeCun 正态分布初始化方法。 - - 使用方差缩放初始化方法,模式为 'fan_in',分布为 'truncated_normal'。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - """ - variance_scaling_(tensor, mode="fan_in", distribution="truncated_normal") - - -def default_flax_embed_init(tensor): - """ - 默认的 Flax 嵌入初始化方法。 - - 使用方差缩放初始化方法,模式为 'fan_in',分布为 'normal'。 - - Args: - tensor (torch.Tensor): 需要初始化的张量。 - """ - variance_scaling_(tensor, mode="fan_in", distribution="normal") - - -class Siglip2TextTransformer(nn.Module): - """ - Siglip2 的文本 Transformer 模型,包含文本嵌入、编码器、最终层归一化以及线性头。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - projection_size (int): 线性头的输出维度。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - _attn_implementation (str): 注意力机制的实现方式,如 "eager", "flash_attention_2", "sdpa"。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - def __init__(self, config: Siglip2TextConfig): - super().__init__() - self.config = config - # 嵌入维度,通常与隐藏层大小相同 - embed_dim = config.hidden_size - # 实例化文本嵌入模块 - self.embeddings = Siglip2TextEmbeddings(config) - # 实例化编码器模块 - self.encoder = Siglip2Encoder(config) - # 实例化最终层归一化层 - self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) - - # 定义线性头,将隐藏状态映射到指定的投影大小 - self.head = nn.Linear(embed_dim, config.projection_size) - # 判断是否使用 Flash Attention 2 - self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" - - def forward( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入文本通过文本 Transformer 模型。 - - Args: - input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - # 根据配置或传入参数设置输出标志 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - if input_ids is None: - raise ValueError("You have to specify input_ids") - - # 获取输入形状 - input_shape = input_ids.size() - # 重塑张量 - input_ids = input_ids.view(-1, input_shape[-1]) - - # 应用文本嵌入模块,将 token ID 转换为嵌入向量 - hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids) - - # 注意:Siglip2 的文本模型不像原始的 CLIP 模型那样使用因果掩码。 - # 扩展注意力掩码 - if attention_mask is not None and not self._use_flash_attention_2: - # [batch_size, seq_len] -> [batch_size, 1, tgt_seq_len, src_seq_len] - attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype) - - # 应用编码器模块 - encoder_outputs = self.encoder( - inputs_embeds=hidden_states, - attention_mask=attention_mask, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取最后一层的隐藏状态 - last_hidden_state = encoder_outputs[0] - # 应用最终层归一化 - last_hidden_state = self.final_layer_norm(last_hidden_state) - - # 假设使用 "sticky" EOS tokenization,最后一个 token 始终是 EOS。 - # 取最后一个 token 的隐藏状态作为池化输出 - pooled_output = last_hidden_state[:, -1, :] - # 应用线性头 - pooled_output = self.head(pooled_output) - - if not return_dict: - # 返回元组形式的输出 - return (last_hidden_state, pooled_output) + encoder_outputs[1:] - - return BaseModelOutputWithPooling( - last_hidden_state=last_hidden_state, - pooler_output=pooled_output, - hidden_states=encoder_outputs.hidden_states, - attentions=encoder_outputs.attentions, - ) - - -class Siglip2PreTrainedModel(PreTrainedModel): - """ - 一个抽象类,用于处理权重初始化以及提供一个简单的接口用于下载和加载预训练模型。 - - Attributes: - config_class (Siglip2Config): 配置类。 - base_model_prefix (str): 模型前缀,用于保存和加载模型时的命名。 - supports_gradient_checkpointing (bool): 是否支持梯度检查点。 - """ - - config_class = Siglip2Config - base_model_prefix = "siglip2" - supports_gradient_checkpointing = True - - # 不需要分割的模块列表 - _no_split_modules = [ - "Siglip2TextEmbeddings", - "Siglip2EncoderLayer", - "Siglip2VisionEmbeddings", - "Siglip2EncoderLayer", - "Siglip2MultiheadAttentionPoolingHead", - ] - # 是否支持 Flash Attention 2 - _supports_flash_attn_2 = True - # 是否支持 SDPA - _supports_sdpa = True - - def _init_weights(self, module): - """ - 初始化模型权重。 - - Args: - module (nn.Module): 需要初始化的模块。 - """ - if isinstance(module, Siglip2VisionEmbeddings): - width = ( - self.config.vision_config.hidden_size - if isinstance(self.config, Siglip2Config) - else self.config.hidden_size - ) - # 初始化位置嵌入权重 - nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width)) - elif isinstance(module, nn.Embedding): - default_flax_embed_init(module.weight) # 使用默认的 Flax 嵌入初始化方法 - elif isinstance(module, Siglip2Attention): - nn.init.xavier_uniform_(module.q_proj.weight) # 初始化查询投影权重 - nn.init.xavier_uniform_(module.k_proj.weight) # 初始化键投影权重 - nn.init.xavier_uniform_(module.v_proj.weight) # 初始化值投影权重 - nn.init.xavier_uniform_(module.out_proj.weight) # 初始化输出投影权重 - nn.init.zeros_(module.q_proj.bias) # 初始化查询投影偏置 - nn.init.zeros_(module.k_proj.bias) # 初始化键投影偏置 - nn.init.zeros_(module.v_proj.bias) # 初始化值投影偏置 - nn.init.zeros_(module.out_proj.bias) # 初始化输出投影偏置 - elif isinstance(module, Siglip2MLP): - nn.init.xavier_uniform_(module.fc1.weight) # 初始化第一个全连接层权重 - nn.init.xavier_uniform_(module.fc2.weight) # 初始化第二个全连接层权重 - nn.init.normal_(module.fc1.bias, std=1e-6) # 初始化第一个全连接层偏置 - nn.init.normal_(module.fc2.bias, std=1e-6) # 初始化第二个全连接层偏置 - elif isinstance(module, Siglip2MultiheadAttentionPoolingHead): - nn.init.xavier_uniform_(module.probe.data) # 初始化探测数据 - nn.init.xavier_uniform_(module.attention.in_proj_weight.data) # 初始化注意力输入投影权重 - nn.init.zeros_(module.attention.in_proj_bias.data) # 初始化注意力输入投影偏置 - elif isinstance(module, Siglip2Model): - logit_scale_init = torch.log(torch.tensor(1.0)) # 初始化 logit scale - module.logit_scale.data.fill_(logit_scale_init) - module.logit_bias.data.zero_() # 初始化 logit 偏置 - elif isinstance(module, Siglip2ForImageClassification): - # 初始化分类器权重 - nn.init.normal_( - module.classifier.weight, - std=self.config.vision_config.hidden_size**-0.5 * self.config.initializer_factor, - ) - elif isinstance(module, (nn.Linear, nn.Conv2d)): - # 使用 LeCun 正态分布初始化线性层或卷积层权重 - lecun_normal_(module.weight) - if module.bias is not None: - # 初始化偏置为零 - nn.init.zeros_(module.bias) - elif isinstance(module, nn.LayerNorm): - # 初始化层归一化偏置为零 - module.bias.data.zero_() - # 初始化层归一化权重为1.0 - module.weight.data.fill_(1.0) - - -class Siglip2TextModel(Siglip2PreTrainedModel): - """ - Siglip2 的文本模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2TextConfig): - 文本模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - projection_size (int): 线性头的输出维度。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - use_return_dict (bool): 是否使用 `ModelOutput` 对象返回结果。 - - 其他配置参数,如 num_attention_heads, intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - config_class = Siglip2TextConfig - - def __init__(self, config: Siglip2TextConfig): - super().__init__(config) - self.text_model = Siglip2TextTransformer(config) - # 初始化权重并应用最终处理 - self.post_init() - - def get_input_embeddings(self) -> nn.Module: - """ - 获取输入嵌入层。 - - Returns: - nn.Module: 输入嵌入层,通常是 `token_embedding`。 - """ - # 返回文本嵌入模块中的 token 嵌入层 - return self.text_model.embeddings.token_embedding - - def set_input_embeddings(self, value): - """ - 设置输入嵌入层。 - - Args: - value (nn.Module): 要设置的输入嵌入层。 - """ - # 设置文本嵌入模块中的 token 嵌入层 - self.text_model.embeddings.token_embedding = value - - def forward( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入文本通过文本模型。 - - Args: - input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 输入序列 token 的索引。如果未提供,则必须提供 `inputs_embeds`。 - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - position_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 每个输入序列 token 在位置嵌入中的位置索引。如果未提供,则自动生成。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用文本 Transformer 模型的前向传播方法 - return self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - -class Siglip2MultiheadAttentionPoolingHead(nn.Module): - """ - 多头注意力池化头。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - num_attention_heads (int): 注意力头的数量。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - 其他配置参数,如 intermediate_size, hidden_act, attention_dropout, hidden_dropout_prob 等。 - """ - - def __init__(self, config: Siglip2VisionConfig): - super().__init__() - - # 初始化探测参数,形状为 (1, 1, hidden_size) - self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size)) - # 实例化多头注意力层 - self.attention = torch.nn.MultiheadAttention(config.hidden_size, config.num_attention_heads, batch_first=True) - # 实例化层归一化层 - self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - # 实例化 MLP 模块 - self.mlp = Siglip2MLP(config) - # 注意力头的数量 - self.num_heads = config.num_attention_heads - - def forward(self, hidden_state: torch.Tensor, attention_mask: Optional[torch.Tensor] = None): - """ - 前向传播方法,应用多头注意力池化。 - - Args: - hidden_state (`torch.Tensor`): - 输入隐藏状态,形状为 `(batch_size, sequence_length, hidden_size)`。 - attention_mask (`torch.Tensor`, *optional*): - 注意力掩码张量,形状为 `(batch_size, sequence_length)`,用于屏蔽某些位置。 - - Returns: - `torch.Tensor`: - 池化后的隐藏状态,形状为 `(batch_size, hidden_size)`。 - """ - # 获取批次大小 - batch_size = hidden_state.shape[0] - # 重复探测参数以匹配批次大小 - probe = self.probe.repeat(batch_size, 1, 1) - - if attention_mask is not None: - # 获取目标长度和源长度 - target_len, source_len = probe.shape[1], hidden_state.shape[1] - # 准备 4D 注意力掩码 - attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_state.dtype, target_len) - # 重复掩码以匹配多头 - attention_mask = attention_mask.repeat(1, self.num_heads, target_len, 1) - # 重塑掩码形状 - attention_mask = attention_mask.reshape(-1, target_len, source_len) - - # 应用多头注意力层 - hidden_state = self.attention(probe, hidden_state, hidden_state, attn_mask=attention_mask)[0] - - # 保存残差连接的输入 - residual = hidden_state - # 应用层归一化 - hidden_state = self.layernorm(hidden_state) - # 应用 MLP 并进行残差连接 - hidden_state = residual + self.mlp(hidden_state) - - # 返回池化后的隐藏状态(第一个 token) - return hidden_state[:, 0] - - -class Siglip2VisionModel(Siglip2PreTrainedModel): - """ - Siglip2 的视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2VisionConfig): - 视觉模型的配置对象,包含以下属性: - - hidden_size (int): 隐藏层大小。 - - num_attention_heads (int): 注意力头的数量。 - - intermediate_size (int): MLP 中间层的维度。 - - hidden_act (str): 激活函数的类型,如 "relu", "gelu" 等。 - - layer_norm_eps (float): 层归一化中的 epsilon 值。 - - 其他配置参数,如 num_hidden_layers, attention_dropout, hidden_dropout_prob 等。 - """ - config_class = Siglip2VisionConfig - main_input_name = "pixel_values" - - def __init__(self, config: Siglip2VisionConfig): - super().__init__(config) - - # 实例化视觉 Transformer 模型 - self.vision_model = Siglip2VisionTransformer(config) - - # 初始化权重并应用最终处理 - self.post_init() - - def get_input_embeddings(self) -> nn.Module: - """ - 获取输入嵌入层。 - - Returns: - nn.Module: 输入嵌入层,通常是 `patch_embedding`。 - """ - # 返回视觉嵌入模块中的 patch 嵌入层 - return self.vision_model.embeddings.patch_embedding - - def forward( - self, - pixel_values: torch.FloatTensor, - pixel_attention_mask: torch.Tensor, - spatial_shapes: torch.LongTensor, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入像素值通过视觉模型。 - - Args: - pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): - 输入像素值张量。 - pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`): - 空间形状张量,用于调整位置嵌入的大小。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, BaseModelOutputWithPooling]`: - - 如果 `return_dict=True`,返回 `BaseModelOutputWithPooling` 对象。 - - 否则,返回包含 `last_hidden_state`, `pooler_output`, `hidden_states`, `attentions` 的元组。 - """ - - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉 Transformer 模型的前向传播方法 - return self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - -class Siglip2Model(Siglip2PreTrainedModel): - """ - Siglip2 模型,结合了文本和视觉模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2Config): - 模型的配置对象,包含以下属性: - - text_config (Siglip2TextConfig): 文本模型的配置。 - - vision_config (Siglip2VisionConfig): 视觉模型的配置。 - - 其他配置参数,如 logit_scale, logit_bias 等。 - """ - config_class = Siglip2Config - - def __init__(self, config: Siglip2Config): - super().__init__(config) - - # 检查 text_config 是否为 Siglip2TextConfig 类型 - if not isinstance(config.text_config, Siglip2TextConfig): - raise TypeError( - "config.text_config is expected to be of type Siglip2TextConfig but is of type" - f" {type(config.text_config)}." - ) - - # 检查 vision_config 是否为 Siglip2VisionConfig 类型 - if not isinstance(config.vision_config, Siglip2VisionConfig): - raise TypeError( - "config.vision_config is expected to be of type Siglip2VisionConfig but is of type" - f" {type(config.vision_config)}." - ) - - # 获取文本配置 - text_config = config.text_config - # 获取视觉配置 - vision_config = config.vision_config - - # 首先,使用正确的注意力实现方式初始化文本和视觉模型 - text_model = Siglip2TextModel._from_config(text_config) - vision_model = Siglip2VisionModel._from_config(vision_config) - - # 其次,获取文本和视觉子模块(为了向后兼容) - # 获取文本模型的子模块 - self.text_model = text_model.text_model - # 获取视觉模型的子模块 - self.vision_model = vision_model.vision_model - - self.logit_scale = nn.Parameter(torch.randn(1)) - self.logit_bias = nn.Parameter(torch.randn(1)) - - # 初始化权重并应用最终处理 - self.post_init() - - def get_text_features( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> torch.FloatTensor: - """ - 获取文本特征。 - - Returns: - text_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2TextModel`] 的池化输出得到的文本嵌入。 - - 示例: - - ```python - >>> from transformers import AutoTokenizer, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> tokenizer = AutoTokenizer.from_pretrained("google/siglip2-base-patch16-224") - - >>> # 重要:确保设置 padding="max_length",因为这是模型训练的方式 - >>> inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding="max_length", return_tensors="pt") - >>> with torch.no_grad(): - ... text_features = model.get_text_features(**inputs) - ``` - """ - # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用文本模型的前向传播方法 - text_outputs = self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取池化输出 - pooled_output = text_outputs[1] - - # 返回文本特征 - return pooled_output - - def get_image_features( - self, - pixel_values: Optional[torch.FloatTensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> torch.FloatTensor: - """ - 获取图像特征。 - - Returns: - image_features (`torch.FloatTensor` of shape `(batch_size, output_dim)`): - 通过投影层应用于 [`Siglip2VisionModel`] 的池化输出得到的图像嵌入。 - - 示例: - - ```python - >>> from PIL import Image - >>> import requests - >>> from transformers import AutoProcessor, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") - - >>> image = Image.open(requests.get(url, stream=True).raw) - - >>> inputs = processor(images=image, return_tensors="pt") - - >>> with torch.no_grad(): - ... image_features = model.get_image_features(**inputs) - ``` - """ - # 使用 Siglip2Model 的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - vision_outputs = self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取池化输出 - pooled_output = vision_outputs[1] - - # 返回图像特征 - return pooled_output - - def forward( - self, - input_ids: Optional[torch.LongTensor] = None, - pixel_values: Optional[torch.FloatTensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.LongTensor] = None, - return_loss: Optional[bool] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ) -> Union[Tuple, Siglip2Output]: - """ - 前向传播方法,处理输入文本和图像通过 Siglip2 模型。 - - Returns: - - 示例: - - ```python - >>> from PIL import Image - >>> import requests - >>> from transformers import AutoProcessor, AutoModel - >>> import torch - - >>> model = AutoModel.from_pretrained("google/siglip2-base-patch16-224") - >>> processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-224") - - >>> image = Image.open(requests.get(url, stream=True).raw) - - >>> texts = ["a photo of 2 cats", "a photo of 2 dogs"] - >>> # 重要:我们传递 `padding=max_length`,因为模型是使用这种方式训练的 - >>> inputs = processor(text=texts, images=image, padding="max_length", return_tensors="pt") - - >>> with torch.no_grad(): - ... outputs = model(**inputs) - - >>> logits_per_image = outputs.logits_per_image - >>> probs = torch.sigmoid(logits_per_image) # 这些是概率 - >>> print(f"{probs[0][0]:.1%} that image 0 is '{texts[0]}'") - 31.9% that image 0 is 'a photo of 2 cats' - ``` - """ - # 使用 Siglip2 模型的配置(如果指定)而不是视觉和文本组件的配置。 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - vision_outputs = self.vision_model( - pixel_values=pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 调用文本模型的前向传播方法 - text_outputs = self.text_model( - input_ids=input_ids, - attention_mask=attention_mask, - position_ids=position_ids, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取图像嵌入 - image_embeds = vision_outputs[1] - # 获取文本嵌入 - text_embeds = text_outputs[1] - - # 归一化特征 - image_embeds = image_embeds / image_embeds.norm(p=2, dim=-1, keepdim=True) - text_embeds = text_embeds / text_embeds.norm(p=2, dim=-1, keepdim=True) - - # 计算余弦相似度作为 logits - logits_per_text = torch.matmul(text_embeds, image_embeds.t().to(text_embeds.device)) - - logit_scale, logit_bias = self.logit_scale.to(text_embeds.device), self.logit_bias.to(text_embeds.device) - logits_per_text = logits_per_text * logit_scale.exp() + logit_bias - - # 转置得到图像-文本的 logits - logits_per_image = logits_per_text.t() - - loss = None - if return_loss: - eye = torch.eye(logits_per_text.size(0), device=logits_per_text.device) - m1_diag1 = -torch.ones_like(logits_per_text) + 2 * eye - loglik = torch.nn.functional.logsigmoid(m1_diag1 * logits_per_text) - nll = -torch.sum(loglik, dim=-1) - loss = nll.mean() - - if not return_dict: - output = (logits_per_image, logits_per_text, text_embeds, image_embeds, text_outputs, vision_outputs) - return ((loss,) + output) if loss is not None else output - - # 返回 Siglip2Output 对象 - return Siglip2Output( - loss=loss, - logits_per_image=logits_per_image, - logits_per_text=logits_per_text, - text_embeds=text_embeds, - image_embeds=image_embeds, - text_model_output=text_outputs, - vision_model_output=vision_outputs, - ) - - -class Siglip2ForImageClassification(Siglip2PreTrainedModel): - """ - Siglip2 的图像分类模型,基于 `Siglip2PreTrainedModel` 抽象类实现。 - - Args: - config (Siglip2Config): - 模型的配置对象,包含以下属性: - - vision_config (Siglip2VisionConfig): 视觉模型的配置。 - - num_labels (int): 分类标签的数量。 - - 其他配置参数,如 problem_type 等。 - """ - main_input_name = "pixel_values" - - def __init__(self, config: Siglip2Config) -> None: - super().__init__(config) - - # 分类标签的数量 - self.num_labels = config.num_labels - - # 使用正确的注意力实现方式创建视觉模型,并仅获取视觉模型的子模块(为了向后兼容) - vision_model = Siglip2VisionModel._from_config(config.vision_config) - self.vision_model = vision_model.vision_model - - # 分类器头 - self.classifier = ( - # 如果 num_labels > 0,则使用线性层作为分类器,否则使用恒等映射 - nn.Linear(config.vision_config.hidden_size, config.num_labels) if config.num_labels > 0 else nn.Identity() - ) - - # 初始化权重并应用最终处理 - self.post_init() - - def forward( - self, - pixel_values: Optional[torch.Tensor] = None, - pixel_attention_mask: Optional[torch.Tensor] = None, - spatial_shapes: Optional[torch.LongTensor] = None, - labels: Optional[torch.Tensor] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - ): - """ - 前向传播方法,处理输入图像通过图像分类模型。 - - Args: - pixel_values (`torch.Tensor` of shape `(batch_size, num_channels, height, width)`, *optional*): - 输入像素值张量。 - pixel_attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - 注意力掩码张量,用于避免在填充 token 索引上执行注意力计算。 - spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`, *optional*): - 空间形状张量,用于调整位置嵌入的大小。 - labels (`torch.LongTensor` of shape `(batch_size,)`, *optional*): - 标签,用于计算图像分类/回归损失。索引应在 `[0, ..., config.num_labels - 1]`。 - 如果 `config.num_labels == 1`,则计算回归损失(均方损失),如果 `config.num_labels > 1`,则计算分类损失(交叉熵)。 - output_attentions (`bool`, *optional*): - 是否返回所有注意力层的注意力权重。 - output_hidden_states (`bool`, *optional*): - 是否返回所有层的隐藏状态。 - return_dict (`bool`, *optional*): - 是否返回一个 [`~utils.ModelOutput`] 而不是普通的元组。 - - Returns: - `Union[Tuple, ImageClassifierOutput]`: - - 如果 `return_dict=True`,返回 `ImageClassifierOutput` 对象。 - - 否则,返回包含 `logits`, `hidden_states`, `attentions` 的元组。 - """ - # 设置是否输出注意力 - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - # 设置是否输出隐藏状态 - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - # 调用视觉模型的前向传播方法 - outputs = self.vision_model( - pixel_values, - attention_mask=pixel_attention_mask, - spatial_shapes=spatial_shapes, - output_attentions=output_attentions, - output_hidden_states=output_hidden_states, - return_dict=return_dict, - ) - - # 获取序列输出 - sequence_output = outputs[0] - - # 对 patch tokens 进行平均池化 - if pixel_attention_mask is not None: - # 准备池化掩码 - pool_mask = pixel_attention_mask[..., None].to(sequence_output.device) - # 应用掩码进行池化 - sequence_output = torch.sum(sequence_output * pool_mask, dim=1) / torch.sum(pool_mask, dim=1) - else: - # 否则,直接进行平均池化 - sequence_output = torch.mean(sequence_output, dim=1) - - # 应用分类器 - logits = self.classifier(sequence_output) - - loss = None - if labels is not None: - # 将标签移动到正确的设备以启用模型并行 - labels = labels.to(logits.device) - if self.config.problem_type is None: - if self.num_labels == 1: - # 设置问题类型为回归 - self.config.problem_type = "regression" - elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): - # 设置问题类型为单标签分类 - self.config.problem_type = "single_label_classification" - else: - # 设置问题类型为多标签分类 - self.config.problem_type = "multi_label_classification" - - if self.config.problem_type == "regression": - # 使用均方损失 - loss_fct = MSELoss() - if self.num_labels == 1: - # 计算回归损失 - loss = loss_fct(logits.squeeze(), labels.squeeze()) - else: - loss = loss_fct(logits, labels) - elif self.config.problem_type == "single_label_classification": - # 使用交叉熵损失 - loss_fct = CrossEntropyLoss() - # 计算分类损失 - loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) - elif self.config.problem_type == "multi_label_classification": - # 使用二元交叉熵损失 - loss_fct = BCEWithLogitsLoss() - # 计算多标签分类损失 - loss = loss_fct(logits, labels) - - if not return_dict: - # 返回元组形式的输出 - output = (logits,) + outputs[2:] - return ((loss,) + output) if loss is not None else output - - # 返回封装后的模型输出 - return ImageClassifierOutput( - loss=loss, - logits=logits, - hidden_states=outputs.hidden_states, - attentions=outputs.attentions, - ) - -import torch -import torch.nn as nn - -# 假设 configuration_siglip2 已经存在,或者你需要根据实际情况定义这两个配置类 -# 如果没有配置文件,下面提供了一个最小化的 Mock 类以便代码运行 -try: - from configuration_siglip2 import Siglip2Config, Siglip2VisionConfig -except ImportError: - class Siglip2VisionConfig: - def __init__(self, **kwargs): - for k, v in kwargs.items(): setattr(self, k, v) - - class Siglip2Config: - def __init__(self, vision_config=None, **kwargs): - self.vision_config = vision_config - for k, v in kwargs.items(): setattr(self, k, v) - -class Siglip2ImageNetWrapper(nn.Module): - """ - SigLIP 2 的包装器,用于适配标准的 ImageNet 训练循环。 - 主要作用是自动根据输入生成 `spatial_shapes` 参数。 - """ - def __init__(self, model): - super().__init__() - self.model = model - - def forward(self, x, labels=None): - """ - Args: - x: [Batch, Channels, Height, Width] - labels: [Batch] (可选) - """ - # 1. 自动计算 spatial_shapes - # SigLIP 2 需要知道每张图的 (Height, Width) 用于插值位置编码 - # 在 ImageNet 训练中,一个 Batch 内的图片通常大小一致 - b, c, h, w = x.shape - device = x.device - - # 构造形状为 (Batch, 2) 的 tensor,存储 [h, w] - spatial_shapes = torch.tensor([[h, w]], device=device).expand(b, 2) - - # 2. 调用原始模型 - # 注意:这里假设输入的 x 就是 pixel_values - if labels is not None: - return self.model(pixel_values=x, spatial_shapes=spatial_shapes, labels=labels) - else: - return self.model(pixel_values=x, spatial_shapes=spatial_shapes) - -def Model(num_classes=1000, model_size='base', image_size=224, patch_size=16, use_wrapper=True): - """ - 工厂函数:构建用于 ImageNet 训练的 SigLIP 2 模型。 - - Args: - num_classes (int): 分类数量 (ImageNet 默认为 1000) - model_size (str): 'base', 'large', 'so400m' - image_size (int): 输入图像分辨率 (用于计算 num_patches) - patch_size (int): Patch 大小 - use_wrapper (bool): 是否包裹一层 Wrapper 以自动处理 spatial_shapes。 - 如果你是直接替换 ResNet/ViT,建议设为 True。 - - Returns: - model: 配置好的 PyTorch 模型 - """ - - # 1. 定义不同规模的超参数 - # 参考 SigLIP 2 论文或常见 ViT 设置 - configs = { - 'base': { - 'hidden_size': 768, - 'intermediate_size': 3072, - 'num_hidden_layers': 12, - 'num_attention_heads': 12, - }, - 'large': { - 'hidden_size': 1024, - 'intermediate_size': 4096, - 'num_hidden_layers': 24, - 'num_attention_heads': 16, - }, - 'so400m': { # SigLIP 特有的 400M 参数量模型 - 'hidden_size': 1152, - 'intermediate_size': 4304, - 'num_hidden_layers': 27, - 'num_attention_heads': 16, - } - } - - if model_size not in configs: - raise ValueError(f"Unknown model_size: {model_size}. Choose from {list(configs.keys())}") - - param = configs[model_size] - - # 2. 计算 num_patches (SigLIP2VisionEmbeddings 需要这个参数) - # 注意:虽然 SigLIP 2 支持动态分辨率,但在初始化位置编码权重时, - # 依然需要一个基准的 max_num_patches 或者 base_resolution - num_patches = (image_size // patch_size) ** 2 - - # 3. 实例化 Vision Config - vision_config = Siglip2VisionConfig( - hidden_size=param['hidden_size'], - intermediate_size=param['intermediate_size'], - num_hidden_layers=param['num_hidden_layers'], - num_attention_heads=param['num_attention_heads'], - num_channels=3, - image_size=image_size, - patch_size=patch_size, - num_patches=num_patches, # 关键参数 - layer_norm_eps=1e-6, - attention_dropout=0.0, - hidden_act="gelu", # 或者 "gelu_pytorch_tanh" - vision_use_head=False, # 分类任务通常不需要原始的 PoolingHead,而是用 GAP - _attn_implementation="sdpa" # 推荐使用 torch 的 SDPA 加速 - ) - - # 4. 实例化 Global Config - config = Siglip2Config( - vision_config=vision_config, - num_labels=num_classes, - problem_type="single_label_classification" if num_classes > 1 else "regression" - ) - - # 5. 创建模型 - # Siglip2ForImageClassification 已经在你提供的代码中定义了 - model = Siglip2ForImageClassification(config) - - # 6. (可选) 包裹 Wrapper 以自动处理 spatial_shapes - if use_wrapper: - model = Siglip2ImageNetWrapper(model) - - return model \ No newline at end of file From ed1437e44b0af1e499b67b989d6d05a5c91cfa2b Mon Sep 17 00:00:00 2001 From: wangwl Date: Thu, 8 Jan 2026 02:38:34 +0000 Subject: [PATCH 3/4] fix: cleanup code and update --- PyTorch/build-in/Classification/SigLIP2/{readme => readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename PyTorch/build-in/Classification/SigLIP2/{readme => readme.md} (100%) diff --git a/PyTorch/build-in/Classification/SigLIP2/readme b/PyTorch/build-in/Classification/SigLIP2/readme.md similarity index 100% rename from PyTorch/build-in/Classification/SigLIP2/readme rename to PyTorch/build-in/Classification/SigLIP2/readme.md From 05bc730c0ad7780f94e6944d87581b517d1417d9 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 8 Jan 2026 10:27:56 +0000 Subject: [PATCH 4/4] fix: rename files and update code --- .../SigLIP2/{requirements_exact.txt => requirements.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename PyTorch/build-in/Classification/SigLIP2/{requirements_exact.txt => requirements.txt} (100%) diff --git a/PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt b/PyTorch/build-in/Classification/SigLIP2/requirements.txt similarity index 100% rename from PyTorch/build-in/Classification/SigLIP2/requirements_exact.txt rename to PyTorch/build-in/Classification/SigLIP2/requirements.txt