对齐机制(上):MLP vs Q-Former,视觉特征到底怎么塞进 LLM?|多模态大模型专栏④

一句话讲透本篇:视觉编码器输出 (N, d_v) 的特征,LLM 需要 d_h 维的 token——中间这个"翻译官"即 Projector。其设计取向,直接决定一个 VLM 属于主流范式还是早期方案。


写在前面

前三篇 已依次讲完:架构全景 → CLIP 如何训练视觉编码器 → 张量如何一路流动。由此可知,视觉特征最终都要"塞进" LLM 的文本流。

但有个关键问题一直被我们悬置了:视觉编码器输出是 (N, d_v)(比如 SigLIP 的 729 × 1152),而 LLM 的每个 token 必须是 d_h 维(比如 Qwen2.5 的 3584)。这个维度差的桥,到底怎么搭?

这就是对齐机制(alignment)——VLM 区别于纯 LLM 的灵魂。这部分内容很厚(Projector 设计、M-RoPE、动态分辨率),塞进一篇会又长又散,所以我拆成上下两篇:

  • 本篇(上):Projector 的设计选择(MLP vs Q-Former)+ 训练侧 token layout 与 loss mask
  • 下篇:M-RoPE 与动态分辨率(视觉 token 如何"知道"自己在图里的位置)

阅读本篇后可获得:

  • ✅ 讲清 MLP Projector 和 Q-Former 的本质区别,知道为什么前者一统江湖
  • ✅ 理解为什么文档/OCR 任务绝不能用 Q-Former(呼应本专栏的文档领域定位)
  • ✅ 动手构建 VLM 训练的三个并行数组(input_ids / attention_mask / labels)
  • ✅ 跑通一份零依赖的 token layout demo,避开手写训练 loop 的头号 silent bug

一、核心命题:维度对齐问题

给定两个东西:

  • 视觉编码器输出:V ∈ R^{N_v × d_v}(如 SigLIP-SO400M:729 × 1152
  • LLM 期望输入:每个 token 都是 d_h 维(如 Qwen2.5-7B:3584

问题:怎么把 V 变成 LLM 能消化的东西? 约束有三条:

  1. 维度必须对齐d_v → d_h
  2. 数量可保留可压缩:直接喂 N_v 个,或压成更少
  3. 选哪种,取决于训练目标、数据、任务

这三条约束,历史上演化出了三种方案。


二、三种 Projector 方案演进

方案 A:MLP Projector(LLaVA 范式)—— 当今主流

V (N_v, d_v) → Linear(d_v → d_h) → GELU → Linear(d_h → d_h) → 输出 (N_v, d_h)

核心特征:只改维度,不改数量729 个视觉 token 一个不丢地映射成 d_h 维,全送进 LLM。用 2 层 MLP 是 LLaVA-1.5 起的标准做法。

方案 B:Q-Former(BLIP-2 范式)—— 早期方案

learnable queries Q ∈ R^{32 × d_h}
  → CrossAttention(Q, V) → 32 个 token

用一组可学习的 query,通过 cross-attention 从 729 个视觉特征里"提取"出固定 32 个 token。强行压缩 729 → 32

方案 C:Perceiver Resampler(Flamingo 范式)

和 Q-Former 类似,也是 cross-attention 压缩,结构略有不同。PaliGemma-2 等仍在用变体。

一张图看懂核心区别

Q-Former · BLIP-2(强压缩)

MLP Projector · LLaVA(不压缩)

2 层 MLP

CrossAttn
32 个 query

视觉特征
(729, 1152)

(729, 3584)
token 数不变

视觉特征
(729, 1152)

(32, 3584)
压缩 23×

进 LLM:729 + 文本 ≈ 1000+ token
OCR/细节 ✓ 强

进 LLM:32 + 文本 ≈ 100 token
OCR/细节 ✗ 差

完整对比:

维度 MLP Projector(LLaVA) Q-Former(BLIP-2)
视觉 token 进 LLM 数 729(不压缩) 32(压缩 23×)
LLM 序列总长 729 + 文本 ≈ 1000+ 32 + 文本 ≈ 100
attention 显存 O(729²),高 O(32²),低
信息保真度 无损 有损(瓶颈严重)
OCR / 文档 / 细粒度 ✅ 强 ❌ 致命差
通用对话 ✅(32 token 够用)
训练复杂度 1 阶段(只训 projector) 多阶段(先 Q-Former 再 LLM)

一句话总结:MLP 不压缩(729→729),Q-Former 强压缩(729→32)。

历史趋势

2022:Q-Former 当道(BLIP-2、MiniGPT-4)
2023 起:MLP Projector 一统江湖(LLaVA-1.5、Qwen-VL、InternVL)

结论:先吃透 MLP Projector,Q-Former 当作历史了解。 现代主流 VLM 清一色 MLP 路线。


三、为什么文档 VLM 绝不能用 Q-Former

这点对本专栏的文档领域定位尤其关键,值得单独说。

32 个 token 想表达 729 个 patch 的信息,相当于每个 token 要"代表" 23 个 patch。在文档/票据任务里,一个数字往往就占好几个 patch——32 个 token 根本装不下一张发票的所有数字。

一张发票:日期、金额、税号、商品明细……每个数字都是独立 patch
MLP:729 token,每个数字有专属载体    → 能准确读出 "12,345.67"
Q-Former:32 token,23 个 patch 挤一个 → 数字必然被丢、串行错乱

正因如此,LLaVA-1.5 之后的文档向 VLM(Qwen-VL、InternVL、Donut 类)全面抛弃 Q-Former、转向 MLP。面向文档理解任务时有一条铁律:Projector 不得压缩,信息无损是底线

💡 Q-Former 的价值场景:少样本对话、视觉问答这种"不需要精确细节"的任务,32 个 token 够概括图像大意。但一旦要精确 OCR,它就力不从心。


四、LLaVA:现代范式的奠基

LLaVA(Visual Instruction Tuning, NeurIPS 2023)是当今所有主流 VLM 的参考实现。它的架构极简:

Image → CLIP-ViT-L/14 (frozen) → (576, 1024) features
       → MLP Projector (2-layer, trainable) → (576, 4096)
       → concat with text embeddings → Vicuna-7B/13B

LLaVA-1.5-7B 的三组件参数(来自实际模型):

组件 参数量 训练状态
Vision Tower(CLIP-ViT-L) ~304M 🔒 frozen
Projector(2 层 MLP) ~8M 🔥 trainable(Stage 1 唯一)
LLM(Vicuna-7B) ~6738M 🔒 frozen → ⚡ LoRA

注意 Projector 只有 8M 参数,相比 LLM 的 6.7B 微不足道——这正是它能用大学习率(1e-3)、在几小时内训完的原因。

它的 Projector 实现简单到令人发指(code/02_alignment/02_projector_train.py):

class Projector(nn.Module):
    """2-layer MLP projector, exactly LLaVA-1.5's design."""
    def __init__(self, d_v: int, d_h: int):
        super().__init__()
        self.mlp = nn.Sequential(
            nn.Linear(d_v, d_h),   # 1024 -> 4096
            nn.GELU(),
            nn.Linear(d_h, d_h),   # 4096 -> 4096
        )

    def forward(self, x):
        return self.mlp(x)

没错,LLaVA-1.5 区别于纯 LLM 的"灵魂组件",就是这么一个两层 MLP。换到 SigLIP-SO400M(1152)→ Qwen2.5-7B(3584)的场景,Projector 参数约 1152·3584 + 3584·3584 ≈ 17M,依然很小。

两阶段训练(关键)

阶段 训什么 数据 目的
1. Pretrain Projector 只训 MLP 558K 图文对 学"把视觉特征翻译到 LLM 维度"
2. Instruction Tuning projector + LLM (LoRA) 150K GPT-4V 指令数据 学指令对齐、视觉问答

这就是 LLM 训练里没有的"第一阶段"(详见第①篇的三阶段矩阵)。Stage 1 的目标极其纯粹:让随机初始化的 Projector,在冻结的视觉编码器和冻结的 LLM 之间,学会"翻译"。


五、训练侧的 token layout(最容易踩坑的地方)

到这里架构清楚了。但真要自己写 VLM 训练 loop,绝大多数人死在 token 序列的构建上。这是 NLP 工程师转 VLM 的头号坑。

核心认知:图像不是 1 个特殊 token,而是 N 个 token 组成的子序列。训练时要构建三个并行数组。我们跑 demo 看真实过程(code/02_alignment/03_token_layout_demo.py,零依赖可跑)。

Step 1-2:<image> 占位符如何展开

用户的 prompt 是 What is in this image?,chat template 会插入一个 <image> 占位符:

      prompt_ids | len=  8 |  1   IMG   10   11   12   13   14   15
                         ↑
                    <image> 占位符(1 个 token)

然后这个 IMG 占位符展开成 N 个 slot(这里用 N=9 演示,真实 SigLIP@384 是 729,Qwen2-VL@672 是 2304):

        expanded | len= 16 |  1   IMG  IMG  IMG  IMG  IMG  IMG  IMG  IMG  IMG   10  11  12  13  14  15
                              └──────────── 9 个视觉 slot ────────────┘

注意:展开后是 9 个相同 id 的 slot,不是 9 个不同 id。因为 LLM 不会真的用 nn.Embedding 去查这些 id,框架会拦截它们、替换成 Projector 输出的真实视觉 embedding。id 只是"位置标记"

Step 3:三个并行数组(训练的灵魂)

把 assistant 的回答拼接上,构建训练三元组。真实输出:

       input_ids | len= 24 |  1  IMG IMG IMG IMG IMG IMG IMG IMG IMG  10  11  12  13  14  15  20  21  22  23  24  25  26   2
  attention_mask | len= 24 |  1    1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
 labels (X=-100) | len= 24 |  X    X   X   X   X   X   X   X   X   X   X   X   X   X   X   X  20  21  22  23  24  25  26   2

读这三行,就懂了 VLM 训练的全部 label 策略:

位置 label 含义
视觉 token(9 个 IMG) -100 不算 loss——它们是输入不是目标
prompt 文本(10..15 -100 不算 loss——只学怎么"回答",不学怎么"提问"
answer token(20..26, 2 真实 id 只在这里算 next-token loss
pad -100 同时 mask 掉 loss 和 attention

label=-100

label=-100

label=真实 id

label=-100, attn=0

视觉 token
×9

❌ 不算 loss

prompt 文本
What is in this image?

answer 文本
A cat sitting on a table.

✅ 算 loss

pad token

❌ 不算 loss 不参与 attn

Step 4:每个训练者都该做的断言

demo 末尾自动做了 sanity check:

total sequence length : 24
  visual tokens       : 9  (expected N_VISUAL=9)
  prompt text tokens  : 7
  answer tokens       : 8  (these compute loss)
  tokens with loss    : 8  (must equal answer length)
All assertions passed.

tokens with loss 必须严格等于 answer 长度——这一条断言能挡住一大半手写训练 loop 的 bug。

三个最容易写错的坑(亲历血泪)

  1. slot 找错:找 <image> 位置必须定位第一个;多图场景要循环找出所有位置依次替换。
  2. visual_emb 行顺序错乱visual_emb[i] 必须严格对应图像 row-major 第 i 个 patch。如果 dataloader 做了 transpose/flip,patch 顺序就乱了——模型直接学不动,loss 不收敛但不报错(头号 silent bug)。
  3. attention_mask 没扩展:拼接后序列变长,attention_mask 也要 pad 出对应长度的 1,否则 LLM 把视觉 token 当 pad 忽略掉。

📌 配图说明:上面的三数组对齐输出和 mermaid label 策略图,是本篇最该收藏的两张图。终端输出截图在 CSDN 很吃香,建议把 demo 跑一遍截真实图。


六、4 个关键点

  1. Projector 只做一件事:d_v → d_h 的维度翻译,MLP 不压缩、Q-Former 强压缩,这是两派分水岭。
  2. MLP 一统江湖的原因是信息无损——尤其文档/OCR 任务,压缩就是丢数字。
  3. Projector 参数极小(几 M ~ 二十 M),能用大学习率快速训完,是 Stage 1 的唯一主角。
  4. 训练 label 的核心规则:只在 answer token 上算 loss,视觉和 prompt 全 -100visual_emb 行顺序必须匹配 patch row-major 顺序,否则静默失败。

📎 本篇涉及代码

文件 说明
code/02_alignment/02_projector_train.py LLaVA Projector 实现 + Stage 1 训练(仅训 Projector)
code/02_alignment/01_minimal_llava.py LLaVA 架构检视:三组件 + 参数量
code/02_alignment/03_token_layout_demo.py token layout 与 label mask 构建演示

小结

概念 一句话
对齐问题 把视觉特征 (N, d_v) 翻译成 LLM 要的 d_h
MLP Projector 2 层 MLP,不压缩(729→729),主流
Q-Former cross-attn 压缩(729→32),文档任务致命
LLaVA Stage 1 只训 8M 的 Projector,学"翻译"
label 策略 只在 answer 算 loss,视觉/prompt 全 -100

下一篇是对齐机制(下):视觉 token 怎么"知道"自己在图里的位置?我们会从 1D-RoPE 推到 M-RoPE(Qwen2-VL 的核心创新),并讲清动态分辨率(AnyRes Tile vs Native Resolution + Bucket)。配套一个零依赖的 4 段 demo,能亲眼验证"M-RoPE 在文本上严格等于 1D-RoPE"。关注专栏,更新第一时间通知。

专栏:《从 NLP 到 VLM:多模态大模型研发实战》
上一篇:③ 张量级 walkthrough | 本篇:④ 对齐机制(上)| 下一篇:⑤ 对齐机制(下)· M-RoPE 与动态分辨率

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐