Skip to content

VLM ​

Vision-Language Model(VLM)将图片与文本转换到兼容的 embedding space,再融合为统一序列交给语言模型。本文以 Qwen3.5 作为 Reference Implementation,按 Image Processor → Vision Encoder → Text Input → Fusion 介绍完整输入流程。

Qwen3.5 中,图片和文本分别处理,直到 Fusion 阶段才合并:

text
图片 ─→ Image Processor ─→ Vision Encoder ─→ 视觉 embedding ─┐
                                                             ├─→ Fusion ─→ Language Model
问题 ─→ Placeholder Expansion ─→ Token Embedding ─→ 文本 embedding ─┘

Qwen3.5 多模态输入与融合:Image Processor 将图片切成 Patch;Vision Encoder 依次经过 Patch Embedding、Vision Transformer 和 Patch Merger 得到视觉 token embedding;Text Input 展开图片占位符并生成文本 embedding;Fusion 通过 Embedding Replace 和 M-RoPE 构造统一序列,最后交给 Language Model 生成答案

这里需要区分三类概念:

  • Patch:图片上固定大小的一块像素,是视觉编码器的输入;
  • 文本 token:Tokenizer 产生的离散 ID,需要查询 Embedding 表才能变成向量;
  • 视觉 token:Patch Merger 直接产生的向量,没有对应的词表 ID。

语言模型实际接收的不是 token ID,而是每个序列位置对应的 embedding。文本通过 Embedding 表得到这个向量,图片则由 Vision Encoder 直接生成。

1. Image Processor ​

Image Processor 负责图片加载、动态缩放、归一化和切块。它整理像素,但不负责理解图片内容。

1.1 Patch ​

Patch 是图片上的一个小方格。Qwen3.5 默认 patch_size=p=16,因此预处理后高为 H、宽为 W 的图片会形成:

Hg=Hp,Wg=Wp,Npatch=HgWg

Hg、Wg 是 Patch 网格的高和宽,Npatch 是 Patch 总数。Patch 只是固定位置的像素块,并不是提前检测出的物体或文字。

图片的高和宽会被调整为 patch_size × spatial_merge_size = 16 × 2 = 32 的整数倍。原因是后续既要按 16 × 16 切块,又要按 2 × 2 合并相邻 Patch。动态缩放会尽量保持原始宽高比,不要求把图片补成正方形。

例如,尺寸按“宽 × 高”写作 1024 × 768 时:

text
Patch 网格:高 768 / 16 = 48,宽 1024 / 16 = 64
Patch 数量:48 × 64 = 3072

Image Processor 主要返回:

  • pixel_values:展平后的 Patch,形状为 [N_patch, 3 × 2 × 16 × 16];
  • image_grid_thw:每张图片的网格尺寸,形状为 [num_images, 3],三个值依次是时间、高度和宽度。

3 是 RGB 通道。时间维的 2 用于适配视觉编码器的 3D 卷积核;处理静态图片时,同一帧会在这个维度复制,因此 image_grid_thw 的时间值仍为 1。

2. Vision Encoder ​

Vision Encoder 按 Patch Embedding → Vision Transformer → Patch Merger 的顺序,把 Patch 像素变成语言模型可以接收的视觉 embedding。

2.1 Patch Embedding ​

Patch Embedding 使用卷积核大小和步幅均为 2 × 16 × 16 的 Conv3d,把每个 Patch 的原始像素投影成一个 Dv 维向量:

xi∈R3×2×16×16⟶ei∈RDv

xi 是第 i 个 Patch 的像素,ei 是它的初始视觉 embedding,类默认值 Dv=1152。这一步只改变表示维度,不改变 Patch 数量:输入、输出的第一维都是 Npatch。

📄 modeling_qwen3_5.py:Patch Embedding
python
class Qwen3_5VisionPatchEmbed(nn.Module):
    def __init__(self, config):
        ...
        kernel_size = [
            config.temporal_patch_size,
            config.patch_size,
            config.patch_size,
        ]
        self.proj = nn.Conv3d(
            config.in_channels,
            config.hidden_size,
            kernel_size=kernel_size,
            stride=kernel_size,
            bias=True,
        )

    def forward(self, hidden_states):
        hidden_states = hidden_states.view(
            -1, self.in_channels, self.temporal_patch_size,
            self.patch_size, self.patch_size
        )
        return self.proj(hidden_states).view(-1, self.embed_dim)

2.2 Vision Transformer ​

Patch Embedding 只编码局部像素。Vision Transformer 先加入二维位置信息,再通过多层 Vision Block 让不同位置的 Patch 交换信息。Qwen3_5VisionConfig 的类默认值是 27 层,具体 checkpoint 应以自身 config.json 为准。

Vision Block 的核心是非因果 Self-Attention 和 MLP。非因果表示每个 Patch 都可以关注同一张图片中的其他 Patch,因此编码后的向量不仅包含局部像素,还包含周围文字、物体边界和远处区域等上下文。

Vision Transformer 不改变序列长度:

E∈RNpatch×Dv⟶V∈RNpatch×Dv

多张图片一起输入时,cu_seqlens 记录各图片的边界,避免不同图片在视觉编码阶段彼此做 Self-Attention。

2.3 Patch Merger ​

spatial_merge_size=m 表示沿 Patch 网格的高和宽各取 m 个位置进行合并。Qwen3.5 默认 m=2,所以每次合并相邻的 2 × 2 = 4 个 Patch 向量:

[v1;v2;v3;v4]∈R4Dv→MLPz∈RDl

Dl 是语言模型隐藏维度。Patch Merger 同时完成两件事:

  • 压缩序列:Npatch→Npatch/4;
  • 对齐维度:4Dv→Dl。

整体输出为:

Z=PatchMerger(V)∈RNvision×Dl

其中:

Nvision=Npatchm2=H⋅W(p⋅m)2

默认 p=16、m=2,所以 1024 × 768 图片的变化是:

text
48 × 64 = 3072 个 Patch
→ 每 2 × 2 个合并
→ 24 × 32 = 768 个视觉 token

Z 的每一行都是一个 Dl 维视觉 token embedding。这里没有“向量转成 token ID”的步骤:向量占据语言模型序列中的一个位置时,就称为一个视觉 token。

📄 modeling_qwen3_5.py:Patch Merger
python
class Qwen3_5VisionPatchMerger(nn.Module):
    def __init__(self, config, use_postshuffle_norm=False):
        self.hidden_size = config.hidden_size * config.spatial_merge_size**2
        ...
        self.linear_fc1 = nn.Linear(self.hidden_size, self.hidden_size)
        self.act_fn = nn.GELU()
        self.linear_fc2 = nn.Linear(self.hidden_size, config.out_hidden_size)

    def forward(self, x):
        ...
        x = x.view(-1, self.hidden_size)
        return self.linear_fc2(self.act_fn(self.linear_fc1(x)))

3. Text Input ​

文本路径负责构造完整的输入 token ID 序列,并为图片预留与视觉 token 数量相同的位置。

3.1 Image Placeholder Expansion ​

Chat Template 最初用一个图片标记表示图片。Processor 根据 image_grid_thw 计算视觉 token 数,再把它展开为:

text
<|vision_start|><|image_pad|> × N_vision<|vision_end|>

对于 image_grid_thw=(T_g,H_g,W_g):

Nimage token=TgHgWgm2

静态图片的 Tg=1,因此占位 token 数恰好等于 Patch Merger 输出的视觉 token 数。<|image_pad|> 是词表中的特殊 token ID,但它只负责预留位置,不代表图片内容。

3.2 Token Embedding ​

Tokenizer 将问题和图片占位符转换为 input_ids。Embedding 层再把每个 ID 映射为 Dl 维向量:

X=Embedding(input_ids)∈RL×Dl

L 是包含图片占位符在内的序列长度。此时 <|image_pad|> 位置仍是普通的占位 embedding,还没有写入图片内容。

4. Fusion ​

Fusion 不会再次编码图片,而是把视觉 embedding 写入文本序列,并为统一序列构造多模态位置。

4.1 Embedding Replace ​

模型找到所有 <|image_pad|> 位置,用 masked_scatter 依次写入 Z 中的视觉 embedding:

Xik←Zk,k=1,…,Nvision

ik 是第 k 个 <|image_pad|> 在序列中的位置。替换前后为:

text
替换前:[文本 embedding][image_pad embedding × N][文本 embedding]
替换后:[文本 embedding][视觉 embedding × N][文本 embedding]

占位 token 数必须等于视觉 embedding 数,否则模型会报出 Image features and image tokens do not match。因此不应手工增删 <|image_pad|>,也不能混用不同预处理结果的 input_ids 和 pixel_values。

📄 modeling_qwen3_5.py:Embedding Replace
python
if inputs_embeds is None:
    inputs_embeds = self.get_input_embeddings()(input_ids)

if pixel_values is not None:
    image_outputs = self.get_image_features(
        pixel_values, image_grid_thw, return_dict=True, **kwargs
    )
    image_embeds = torch.cat(image_outputs.pooler_output, dim=0)
    image_mask, _ = self.get_placeholder_mask(
        input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds
    )
    inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)

4.2 Position Encoding:M-RoPE ​

Embedding Replace 解决了“图片内容放在哪里”,M-RoPE 则说明“这些内容位于图片的哪里”。Qwen3.5 为统一序列构造三轴位置索引:

  • 文本 token:三个位置索引相同,表示普通的一维顺序;
  • 图片 token:高度、宽度索引分别沿合并后的二维网格变化;
  • 视频 token:时间、高度、宽度索引分别变化。

mm_token_type_ids 用于区分文本、图片和视频位置,不能在调用模型前丢弃。经过 Embedding Replace 和 M-RoPE 后,语言模型得到一条同时包含文字内容、图片内容和空间位置的统一序列,无需额外的 Cross-Attention。

语言模型随后通过 Self-Attention 综合问题与视觉 token,并由 LM Head 自回归生成答案。完整的文本输出过程见 Output:从 Decoder 输出到文本。

5. 参考 ​

  1. Transformers:Qwen3.5 模型文档
  2. Transformers:Qwen3.5 配置
  3. Transformers:Qwen3.5 视觉编码与模态融合
  4. Transformers:Qwen3-VL Processor
  5. Transformers:Qwen2-VL 图片预处理