【大模型】对齐机制(上):MLP vs Q-Former,视觉特征到底怎么塞进 LLM?|多模态大模型专栏④
对齐机制(上):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 能消化的东西?约束有三条:
- 维度必须对齐:
d_v → d_h - 数量可保留可压缩:直接喂
N_v个,或压成更少 - 选哪种,取决于训练目标、数据、任务
这三条约束,历史上演化出了三种方案。
二、三种 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 等仍在用变体。
一张图看懂核心区别
完整对比:
| 维度 | 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/13BLLaVA-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):
classProjector(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 -> 4096nn.GELU(),nn.Linear(d_h,d_h),# 4096 -> 4096)defforward(self,x):returnself.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 |
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。
三个最容易写错的坑(亲历血泪)
- slot 找错:找
<image>位置必须定位第一个;多图场景要循环找出所有位置依次替换。 - visual_emb 行顺序错乱:
visual_emb[i]必须严格对应图像 row-major 第 i 个 patch。如果 dataloader 做了transpose/flip,patch 顺序就乱了——模型直接学不动,loss 不收敛但不报错(头号 silent bug)。 - attention_mask 没扩展:拼接后序列变长,attention_mask 也要 pad 出对应长度的 1,否则 LLM 把视觉 token 当 pad 忽略掉。
📌配图说明:上面的三数组对齐输出和 mermaid label 策略图,是本篇最该收藏的两张图。终端输出截图在 CSDN 很吃香,建议把 demo 跑一遍截真实图。
六、4 个关键点
- Projector 只做一件事:
d_v → d_h的维度翻译,MLP 不压缩、Q-Former 强压缩,这是两派分水岭。 - MLP 一统江湖的原因是信息无损——尤其文档/OCR 任务,压缩就是丢数字。
- Projector 参数极小(几 M ~ 二十 M),能用大学习率快速训完,是 Stage 1 的唯一主角。
- 训练 label 的核心规则:只在 answer token 上算 loss,视觉和 prompt 全
-100。visual_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 与动态分辨率
