qwen2.5-vl仓库地址:QwenLM/Qwen2.5-VL: Qwen2.5-VL is the multimodal large language model series developed by Qwen team, Alibaba Cloud.

不过源码不在这个仓库里,需要pip install最新版的transformers库然后去库里面的model目录下去找T^T

第一篇先把几个视觉部分的工具类写一下。

1.Qwen2RMSNorm

class Qwen2RMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
        """
        Qwen2RMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states):
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)

    def extra_repr(self):
        return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"

作用:LLM中的归一化层,采用RMSNorm,即对输入除以均方值再乘以权重。

归一化的主要目的就是提高训练的稳定性,缓解梯度消失或爆炸的问题。而采用RMSNorm归一化可以简化流程,因为不需要计算均值。

2.Qwen2_5_VisionPatchEmbed

class Qwen2_5_VisionPatchEmbed(nn.Module):
    def __init__(
        self,
        patch_size: int = 14,
        temporal_patch_size: int = 2,
        in_channels: int = 3,
        embed_dim: int = 1152,
    ) -> None:
        super().__init__()
        self.patch_size = patch_size
        self.temporal_patch_size = temporal_patch_size
        self.in_channels = in_channels
        self.embed_dim = embed_dim

        kernel_size = [temporal_patch_size, patch_size, patch_size]
        self.proj = nn.Conv3d(in_channels, embed_dim, kernel_size=kernel_size, stride=kernel_size, bias=False)

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        target_dtype = self.proj.weight.dtype
        hidden_states = hidden_states.view(
            -1, self.in_channels, self.temporal_patch_size, self.patch_size, self.patch_size
        )
        hidden_states = self.proj(hidden_states.to(dtype=target_dtype)).view(-1, self.embed_dim)
        return hidden_states

作用:对三维(时间,高度,宽度)的视频,分块映射到一个高维的嵌入空间。

对于一个size为(T,H,W,C)的数据,最终输出为(N_patches,embed_dim)。

patch_size:patch的长和宽。

temporal_patch_size:时间维度上patch的长度。

in_channel:图像的通道数 ,RGB图像是3。

embed_dim:嵌入空间维度。

kernel_size = [temporal_patch_size, patch_size, patch_size]
self.proj = nn.Conv3d(in_channels, embed_dim, kernel_size=kernel_size, stride=kernel_size, bias=False)

上面代码是核心,首先设置一个三维的kernel_size,之后的self.proj虽然是利用Convd3d卷积层,但本质上是一个Linear的作用,因为可以看到他的stride(步幅)就等于kernel_size,所以就是不断地对每一个kernel大小的patch进行投影,之后跳到下一个patch。

hidden_states = hidden_states.view(
    -1, self.in_channels, self.temporal_patch_size, self.patch_size, self.patch_size
)

这里用view调整形状,pytorch卷积层输入要求通道维度在第二位。

3.Qwen2_5_VisionRotaryEmbedding

class Qwen2_5_VisionRotaryEmbedding(nn.Module):
    def __init__(self, dim: int, theta: float = 10000.0) -> None:
        super().__init__()
        inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2, dtype=torch.float) / dim))
        self.register_buffer("inv_freq", inv_freq, persistent=False)

    def forward(self, seqlen: int) -> torch.Tensor:
        seq = torch.arange(seqlen, device=self.inv_freq.device, dtype=self.inv_freq.dtype)
        freqs = torch.outer(seq, self.inv_freq)
        return freqs

作用:计算Rope需要的频率矩阵

这里给出一个例子。

dim = 4 theta = 10000

则得到inv_freq为[1.0, 0.01](之后向量的每两个维度对应一个频率)

seq假设为[0,1,2]

则得到3×2的矩阵

[[0,0],

[1,0.01],

[2,0.02]]

4.Qwen2_5_VLPatchMerger     

class Qwen2_5_VLPatchMerger(nn.Module):
    def __init__(self, dim: int, context_dim: int, spatial_merge_size: int = 2) -> None:
        super().__init__()
        self.hidden_size = context_dim * (spatial_merge_size**2)
        self.ln_q = Qwen2RMSNorm(context_dim, eps=1e-6)
        self.mlp = nn.Sequential(
            nn.Linear(self.hidden_size, self.hidden_size),
            nn.GELU(),
            nn.Linear(self.hidden_size, dim),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.mlp(self.ln_q(x).view(-1, self.hidden_size))
        return x

作用:融合四个patch在一起,之后通过MLP投影到语言模型的嵌入空间。

context_dim就是原本一个patch的隐藏层维度,spatial_merge_size是一个方向上要融合的patch数,等于2的话意味着融合2×2=4个块,等于3的话意味着融合3×3=9个块,默认设置为2,则融合4个patch在一起。

所以hidden_size就是4*context_dim。

而dim就是语言模型token的特征维度,这里的self.mlp的作用就类似于LLaVA中的线性投影层,映射视觉特征到语义嵌入空间,后面的Qwen2_5_VisionTransformerPretrainedModel类中会用到。

具体forward流程就是先对输入进行一次归一化,再通过view修改形状,之后通过mlp进行投影。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐