从 CLIP 到 BLIP-2 —— 作为模态桥的 Q-Former 本节摘要:CLIP 把图像与文本对齐了,却不能生成 caption、回答问题或对话。BLIP-2(Salesforce,2023)用一个小型可训练桥解决了这个问题:32 个可学习 query 向量通过交叉注意力去关注冻结 ViT 的特征,再直接塞进冻结 LLM 的输入流。1.88 亿参数的桥,把 ViT-g/14 连到了 110 亿参数的 LLM。2026 年前每一个基于 adapter 的 VLM——MiniGPT-4、InstructBLIP、LLaVA 的堂兄弟——都是它的后裔。本节读透 Q-Former 的架构、解释它的两阶段训练,并实现一个把视觉 token 喂进冻结文本解码器的玩具版本。
本节摘要:CLIP 把图像与文本对齐了,却不能生成 caption、回答问题或对话。BLIP-2(Salesforce,2023)用一个小型可训练桥解决了这个问题:32 个可学习 query 向量通过交叉注意力去关注冻结 ViT 的特征,再直接塞进冻结 LLM 的输入流。1.88 亿参数的桥,把 ViT-g/14 连到了 110 亿参数的 LLM。2026 年前每一个基于 adapter 的 VLM——MiniGPT-4、InstructBLIP、LLaVA 的堂兄弟——都是它的后裔。本节读透 Q-Former 的架构、解释它的两阶段训练,并实现一个把视觉 token 喂进冻结文本解码器的玩具版本。
阅读完本节,你应当能够:
你有一个冻结的 ViT,每张图产出 256 个、维度 1408 的 patch token。你有一个冻结的 7B LLM,期望维度 4096 的 token 嵌入。最显然的桥——一个从 1408 到 4096 的线性层——能用,但把全部 256 个 patch token 喂进 LLM 上下文,每张图多耗 256 个 token。一批 32 张图,光视觉模态就吃掉 8192 个 token。
BLIP-2 的问题:能不能把 256 token 的图像表示压缩成远少于现在的 token(比如 32 个),同时保留足够信息让 LLM 能 caption、能问答、能就图像推理?而且不动冻结的骨干,把训练成本压在桥的参数上?
答案:Q-Former。32 个可学习「query」向量交叉关注 ViT 的 patch token,产出一个 LLM 可消费的 32 token 视觉摘要。共 1.88 亿参数,在接触 LLM 之前先用对比、匹配、生成三类目标训练好。
Q-Former 的核心技巧:不让 LLM 的文本 token 去关注图像 patch,而是引入一组新的 32 个可学习 query 向量 Q,让它们去关注图像 patch。这些 query 是模型参数——训练时学习,且对每张图都用同一组 32 个 query。
交叉注意力之后,每个 query 持有一份压缩后的图像摘要——「描述主体」「描述背景」「数物体」等等。query 并不会字面地按语义标签分工,它学到的是任何能让下游损失下降的编码。
Q-Former 是一个小型 Transformer(12 层、约 1 亿参数),有两条路径:
训练时两条路径都跑。query 与文本通过共享的自注意力交互,这意味着需要文本条件时(ITM、ITG),query 可以条件化于文本。推理时用于 VLM 交接,只让 query 通过,产出 32 个视觉 token。
BLIP-2 分两阶段预训练:
阶段 1:表示学习(无 LLM)。三个损失:
只训 Q-Former,ViT 冻结,不涉及 LLM。
阶段 2:生成学习。接上一个冻结 LLM(OPT-2.7B 或 Flan-T5-XL 等)。把 32 个 query 输出经一个小线性层投影到 LLM 嵌入维度,prepend 到文本 prompt 前。在拼接好的 prompt + 图像 + caption 序列上,只训练线性投影与 Q-Former 的 LM 损失。
阶段 2 之后,Q-Former + 投影就是完整的视觉 adapter。推理时:图像 → ViT → Q-Former → 线性投影 → prepend 到文本前 → 冻结 LLM 输出。
BLIP-2 = ViT-g/14(11 亿,冻结)+ OPT-6.7B(67 亿,冻结)+ Q-Former(1.88 亿,训练),总共 80 亿,其中仅训练 1.88 亿。Q-Former 一个人占整栈约 2.4%。训练成本随之而来:几张 A100 训几天,而非端到端训几周。
质量上:BLIP-2 在零样本 VQA 上持平或胜过 Flamingo-80B,而体量小 50 倍。这个桥确实管用。
InstructBLIP(2023)给 Q-Former 加了一个额外输入:指令文本本身。交叉注意力时,query 现在既能看到图像 patch,也能看到指令。query 可以按指令专精(「数汽车」「描述氛围」),而不是学一个固定摘要。在留出任务上有提升。
MiniGPT-4 保留 Q-Former,但冻结其余一切,只训练输出线性投影。便宜,代价是质量——query 还是 BLIP-2 的,不是你的。适合快速迭代,不是最优架构。
LLaVA(2023,第 05 节)用一个普通的两层 MLP 取代 Q-Former,把每个 ViT patch token 投影进 LLM 空间——24×24 网格就是 576 个 token,全喂给 LLM。压缩更差,但让 LLM 能在原始 patch 上做注意力。当时有争议;到 2023 年底已成主流,因为视觉指令数据(LLaVA-Instruct-150k)证明 MLP 可以被训练到保留足够信号。代价是:LLaVA 的上下文填得更快,但天然适合多图与视频扩展。
到 2026 年,领域分叉:Q-Former 在token 预算重要时(长视频、多图)存活;MLP 投影器在每 token 质量优先时主导。
Flamingo(第 04 节)早于 BLIP-2,用的也是交叉注意力思路,但在冻结 LLM 的每一层都做,而不是做成单一桥。BLIP-2 证明你只压缩到输入层也能行。Gemini 与 Idefics 把两者结合:交错输入的 token 加上可选的、用于上下文内少样本的门控交叉注意力。
四种都成立。决定性的问题是:你受限于 token 预算,还是受限于每 token 质量?
code/main.py 用标准库搭一个 Q-Former 风格的交叉注意力:
# 输入: patch token P (256, 128), 可学习 query Q (32, 128) Wq, Wk, Wv = ... # 三套投影矩阵 (128, 128) Qh = Q @ Wq # (32, 128) —— Q 来自 query Kh = P @ Wk # (256, 128) —— K 来自 patch Vh = P @ Wv # (256, 128) —— V 来自 patch scores = Qh @ Kh.T / sqrt(128) # (32, 256) 注意力分数 attn = softmax(scores, axis=-1) # 每行对 256 个 patch 归一化, 和为 1 out = attn @ Vh # (32, 128) —— 32 个视觉 token llm_tokens = out @ W_proj # (32, 512) —— 投影到 LLM 维度
💡 关键洞察:注意力权重矩阵是
32 × 256,每行告诉你这个 query 从哪几个 patch 取信息。打印出来,你会看到不同 query 关注图像的不同区域——这就是「软提示为视觉服务」。
def stage1_forward(query_tokens, text_tokens, patch_tokens): # ITC: 池化 query 与文本 CLS 对比 q_pool = mean_pool(query_tokens) # (D,) t_cls = text_tokens[:, 0] # 文本 CLS loss_itc = infonce(q_pool, t_cls) # ITM: 二分类, 硬负例 pair_feat = concat(query_tokens.mean(1), t_cls) loss_itm = bce(classifier(pair_feat), is_match) # ITG: 因果 LM, 条件于 query loss_itg = ce(lm_head(text_tokens, cond=query_tokens), text_labels) return loss_itc + loss_itm + loss_itg # ViT 与 LLM 均冻结 def stage2_forward(query_tokens, llm, text_prompt): visual = linear_proj(query_tokens) # 投影到 LLM 维度 inputs = concat(visual, text_prompt_embed) # 拼到 prompt 前 return llm(inputs, mode="lm_loss") # 冻结 LLM 上的 LM 损失
Blip2QFormer 与 Blip2Stage 封装了两阶段训练,Q-Former 的交叉注意力与共享自注意力完整可调。Blip2ForConditionalGeneration 一站式,qformer、language_projection、language_model 三段式暴露,适合接 HF 流水线;InstructBLIP 用 Blip2ForConditionalGeneration 改 query 拼接方式。linear_proj,Q-Former 加载 BLIP-2 权重后冻结,代码极简,适合学习 adapter 思路。MLPProjector 两层 MLP,把 ViT 的全部 patch token 投影进 LLM,代码比 Q-Former 短一个数量级。工程取舍:需要 token 预算控制(长视频、多图)用 Q-Former/Perceiver resampler;追求每 token 原始质量与代码简洁用 MLP 投影器;快速原型可像 MiniGPT-4 那样只训投影器。
本节产出 outputs/skill-modality-bridge-picker.md。给定一个目标 VLM 配置(视觉编码器 token 数、LLM 上下文预算、部署约束、质量目标),它在 Q-Former、MLP、Perceiver resampler 之间给出推荐,附简短理由与各桥的参数量估算。
PyTorch 实现:用 PyTorch 实现交叉注意力块,验证 32 个 query、256 个 key/value 时注意力权重矩阵是 32×256,softmax 后每行和为 1。
三损失的 forward:BLIP-2 阶段 1 同时跑 ITC、ITM、ITG 三个损失。用伪代码写出各自的 forward 签名。哪一个要求文本编码器路径激活?
参数对比:对比 Q-Former(12 层、隐藏 768)与两层 MLP 投影器(1408→4096,两层)的参数量。LLM 多大时,1.88 亿的 Q-Former 成本才在训练效率上回本?
初始化:读 BLIP-2 论文(arXiv:2301.12597)3.2 节关于 Q-Former 如何初始化。解释为什么从 BERT-base(而非随机)初始化能加速收敛。
视频预算:对 10 分钟视频、1 FPS 采样到 60 帧,算每帧 token 成本:Q-Former(32 token/帧)vs MLP 投影器(576 token/帧)。哪个能塞进 128k token 的 LLM 上下文窗口?
下一节,我们将进入 Flamingo——BLIP-2 的祖先。它在冻结 LLM 的每一层做门控交叉注意力,让模型在上下文里塞几张图就能少样本推理,这是「少样本 VLM」范式的源头。