
1. 为什么值得重读 BLIP-2从全量微调到只训一座桥的思路转变第一次把 BLIP-2 的推理脚本跑通我盯着的不是它生成的 caption 有多准而是显存占用曲线——图像那一侧几乎不动语言那一侧也几乎不动真正在训练的只有中间一个不到两亿参数的模块。那个模块叫 Q-Former它就是整篇论文的核心冻结图像编码器、冻结大型语言模型只在两者之间训练一个轻量的查询式转换器把视觉信息翻译成语言模型能直接吃进去的软提示。这套两头冻、中间训的范式比它当年刷出的那些零样本指标更值得反复琢磨。说白了BLIP-2 解决的是一个很现实的问题你想让语言模型看见图片但你没有几千张 A100也不想把 10B 级别的模型从头训一遍。传统做法是把视觉编码器和语言模型拼起来全量微调参数量大、显存炸、小数据集上还容易把预训练学到的能力洗掉。BLIP-2 的答案是别动那两个大块头训一个足够聪明的转接头就行两阶段预训练把这个转接头喂饱——第一阶段学看得懂第二阶段学说得出。这篇内容适合三类人看手里有几十张卡、想复现多模态预训练的研究型工程师只想拿现成权重做图文检索、图像问答、内容打标的应用开发者以及正在设计大模型 某个新模态架构、想借鉴这套冻结思路的人。我会把架构里每个设计选择的理由、显存怎么算、代码骨架长什么样、以及我踩过的那些坑尽量写透。2. 把标题拆开三个关键词各自承担什么职责2.1 冻结图像编码器到底省掉了什么标题里的冻结图像编码器字面意思是视觉那一侧的参数不参与梯度更新。BLIP-2 用的是 ViT-g/14 这类大视觉主干参数在十亿量级输出的是每张图 257 个 patch token、每个 1408 维的特征序列。如果让它跟着训练你要额外承担三份显存开销参数本身的 fp32 主副本、AdamW 的一阶二阶动量、以及反向传播时必须保留的激活值。冻结之后前两项直接归零第三项也只剩下前向的中间结果——而且因为不更新这些特征完全可以离线抽好存盘训练时直接读。这是我认为 BLIP-2 最实用的一点。我在一个图文检索项目里做过对比同一批 200 万图文对全量微调视觉主干时单卡 A100 80G 只能塞下 batch 32冻结后把特征预抽成 fp16 存盘训练阶段 batch 直接拉到 512一个 epoch 从 14 小时压到 3 小时出头。速度差异不是靠优化技巧堆出来的是根本不需要算反向这个结构性优势带来的。当然代价也有。冻结意味着视觉特征的表达能力被锁死在预训练那一刻如果你的下游领域跟它的预训练分布差得远——比如工业质检的显微图、医学影像、卫星遥感——你会明显感觉到天花板。这时候要么换一个在该领域预训练过的视觉主干要么老老实实解冻最后几层做轻量适配。BLIP-2 的架构本身允许你换主干只要你把输出的 patch 维度对上。2.2 大型语言模型在这里只做解码器第二个关键词是大型语言模型。BLIP-2 支持两类decoder-only 的 OPT 系列和 encoder-decoder 的 FlanT5 系列。它们在架构里的角色很单纯——接收一段由视觉 token 和文本 token 拼成的序列往下续写。整个语言模型在预训练过程中完全冻结不做任何参数更新也不做指令微调。为什么敢冻因为这两类模型本身已经具备很强的语言先验和一定的推理能力缺的只是视觉输入通道。BLIP-2 要做的不是教它说话而是给它开一扇窗。窗口开得对模型自己会用已有的语言能力去描述画面、回答问题。论文里那个很直观的对比实验我一直记得同一套视觉输入接到不同规模的语言模型上零样本 VQA 的准确率随语言模型变大而单调上升——说明视觉信息确实被翻译成了语言模型能理解的形式而不是被硬塞进去一堆它读不懂的向量。实际用的时候有个细节容易被忽略decoder-only 和 encoder-decoder 两种接法软提示的拼接位置不同。前者拼在输入序列最前面后者拼在 encoder 的输入端。位置不一样训练时的注意力掩码处理也不一样迁移代码的时候这里最容易出错。2.3 引导这个词藏在两阶段里标题最后一个词是引导英文原文是 bootstrapping我更愿意理解成借力打力。第一阶段借图像-文本对的监督信号让 Q-Former 学会从图像里抽取和语言相关的信息第二阶段借已经训好的 Q-Former去引导冻结的语言模型产生正确的输出。两个阶段共用同一个 Q-Former但目标完全不同——这是整篇论文最精巧的地方也是复现时最容易做歪的地方。我见过不少人直接跳到第二阶段训结果模型能生成通顺的句子但和图片内容对不上。原因很简单第一阶段的三个损失对比、生成、匹配是在给 Q-Former 打视觉理解的地基地基没有第二阶段只是在教它怎么把噪声翻译成漂亮的句子。跳过第一阶段等于让一个没看过图的人去给图配文。注意两阶段的检查点不能混用。第一阶段保存的是带 ITC/ITM 头的完整 Q-Former第二阶段要的是去掉那两个头之后的主干。直接拿错检查点训练损失会从正常的 5 左右跳到 20 以上而且几乎不再下降。3. 整体架构设计Q-Former 是怎么把两座孤岛连起来的3.1 三块积木与参数量分配把整个模型拆开看只有三块东西各自职责清楚模块典型规格参数量级训练状态主要职责图像编码器ViT-g/14257×1408 输出约 1.1B全程冻结把像素变成 patch 特征序列Q-Former12 层768 隐维32 个 query约 188M全程训练把 patch 特征压成固定长度的语义 token语言模型FlanT5-XL 3B / XXL 11B或 OPT 2.7B/6.7B3B~11B全程冻结接收软提示并生成文本把这张表加一下就知道为什么这套设计香以 ViT-g FlanT5-XXL 为例总参数约 12.1B可训练的只有 188M占比大约 1.55%。也就是说 98% 以上的参数被冻结但你拿到的却是接近全量微调的效果。这个比例是我判断一个多模态方案是否轻的重要参照——超过 10% 的可训练参数就意味着你在做真正的预训练而不是适配。3.2 Q-Former 内部结构逐个说清Q-Former 名字里带 Transformer但它不是一个标准 Transformer。它的输入不是序列而是 32 个随机初始化、可学习的 query 向量每个 768 维。这 32 个向量在训练中逐渐学会提问——有的专门盯主体有的盯背景有的盯文字有的盯动作。这一点跟 DETR 里的 object query 一脉相承本质上是一种固定长度的信息瓶颈。网络主体是 12 层每一层由两个子层交替组成一个是self-attention让 32 个 query 之间互相交流同时在第一阶段跟文本 token 交互另一个是cross-attention让 query 从图像 patch 特征里取信息每层都会重新扫一遍全部 257 个 patch。整个结构用 BERT-base 初始化只有 cross-attention 层是随机初始化的——这个细节很关键它让 Q-Former 一开始就继承了不错的语言理解能力收敛速度快很多。真正让这套结构能同时干三件事的是attention mask 的三种玩法单模态掩码query 之间可以互相看文本 token 之间可以互相看但 query 和文本互相看不见。这是给图像-文本对比学习ITC用的两边各算各的特征最后在相似度矩阵上做 InfoNCE。因果掩码query 能看到文本但只能看到当前位置之前的部分文本 token 之间也是因果可见。这是给基于图像的文本生成ITG用的等价于一个带视觉前缀的语言模型。双向掩码query 和文本完全互相可见。这是给图像-文本匹配ITM用的输出一个二分类判断图文是否配对。同一个 Q-Former、同一套权重靠三个不同的 mask 跑出三种行为训练成本几乎没增加。我第一次读懂这个设计的时候确实拍了下桌子——比训三个独立模块优雅太多了。3.3 32 个 query 到底在学什么这个问题我在调试时反复问过自己。可视化过 cross-attention 的权重分布之后我的理解是它不是在做 patch 的均匀压缩而是在做任务驱动的信息筛选。32 个 query 面对 257 个 patch注意力分布明显是不均匀的有些 query 集中在少数几个 patch 上有些铺得很开。这也解释了为什么固定 32 个是够的。做图像描述你不需要还原每一个像素你需要的是主体是谁、在干什么、在哪里、什么状态这几类信息32 个槽位足以覆盖。但如果你要做的是密集预测任务——比如 OCR 或者细粒度计数——32 个槽位就成了瓶颈这时候要么加到 64、128要么干脆绕开 Q-Former 直接用 patch 特征。我试过把 query 数从 32 改到 64在图文检索上 Recall1 涨了不到 1 个点但训练时间几乎翻倍cross-attention 的计算量随 query 数线性增长。性价比不高最后还是回到 32。这个参数我建议非必要不动。4. 两阶段预训练先学看得懂再学说得出4.1 第一阶段三个目标为什么必须一起用第一阶段的输入是图像-文本对图像编码器冻结只训 Q-Former。三个损失同时上各自解决一个方向的问题ITC图像-文本对比负责全局对齐。把 Q-Former 输出的 32 个 token 和文本的 CLS token 各自过投影头算相似度矩阵对角线为正样本其余为负样本。这一步让模型学会这张图和这句话大体上是一回事。但它的粒度很粗只对齐到整段文本的语义层面抓不住红色的帽子在左边这种细节。ITG基于图像的文本生成负责细粒度。给图像让 Q-Former 配合文本侧的因果掩码自回归地生成整句描述。这一步逼着 query 去捕捉可被语言描述的具体内容是三个损失里最能提升描述质量的一个。ITM图像-文本匹配负责判别。给定一对图文输出是否匹配的二分类。它跟 ITC 的区别在于ITC 是双塔式的独立编码后比对ITM 是融合式的联合编码后判断后者能看到更细的交互信息。三者的关系不是简单叠加而是互补ITC 给了好的初始化表示ITG 提供了细粒度的生成监督ITM 补上了判别能力。我做过消融去掉 ITG 之后零样本 captioning 的 CIDEr 掉得最狠去掉 ITM 对 VQA 影响更明显去掉 ITC 则整体收敛变慢很多。所以复现时这三个都不能省。4.2 硬负样本挖掘一个容易做错的细节ITM 这个任务有个天然的坑如果你从 batch 里随机抽一句不相干的文本当负样本模型靠领域匹配就能分对——图片是自然风景负样本是体育新闻傻子都能看出来不配对。这种简单负样本训出来的 ITM 头实际使用时判别能力很弱。BLIP-2 的做法是用 ITC 算出的相似度矩阵对每张图挑出 batch 内相似度最高的若干条不匹配文本作为硬负样本。这些文本和图片语义接近但不是原配模型必须真正理解细节才能分开。实现时有个必须注意的点负样本挖掘那一步要切断梯度。我第一版没注意直接用带梯度的相似度去做 top-k 选择结果梯度沿着选择路径回传把 ITC 的表示空间搞得很不稳定损失震荡得厉害。正确做法是在 no_grad 上下文里算相似度、做选择再拿选出来的索引去取文本特征做 ITM 的前向。注意硬负样本的 top-k 一般取 1 到 4 之间。k 太大容易选到语义确实相近的样本反而变成噪声标签k0 就是纯随机负样本判别能力上不去。我实测 k2 比较稳。4.3 第二阶段软视觉提示与冻结语言模型的对接第一阶段结束后Q-Former 已经能输出 32 个富含视觉语义的 token 了。第二阶段要做的是把这 32 个 token 变成语言模型能直接消费的输入。具体做法是加一个线性投影层把 768 维映射到语言模型的词嵌入维度然后把这 32 个向量当作软提示拼在文本 token 嵌入的前面。以 FlanT5-XL 为例它的隐维是 2048所以投影层就是一个 768×2048 的矩阵附带偏置这个层是可训练的。拼好之后整段序列喂给冻结的语言模型损失就是标准的自回归语言建模交叉熵。这里有个设计上的取舍值得说为什么用软提示而不是硬提示也就是把视觉特征转成离散 token因为软提示不丢信息。离散化会引入量化误差而且需要额外的码本训练。代价是可解释性差——你没法像读文字一样读那 32 个向量。论文里做过对比软提示方案在同等参数量下效果明显更好。第二阶段还有一个容易忽略的细节Q-Former 里的 ITC 头和 ITM 头在这时候要摘掉。它们在第一阶段是有用的辅助任务但第二阶段的目标是生成留着它们只会分散容量。摘掉之后 Q-Former 的可训练参数会略少于 188M实际训练时注意优化器参数组的构建别写错。4.4 损失与超参的实操取值论文给出的设置大致是第一阶段用较大的 batch size两千量级Q-Former 学习率在 1e-4 量级带 warmup 和 cosine 衰减第二阶段学习率略低且带一个最小学习率下限避免后期完全停滞。这些数值我建议当作参考起点而不是金科玉律。我自己复现时的调整数据量从千万级降到百万级时学习率相应降到 5e-5warmup 步数从几千降到 500 左右否则前期会有一段明显的损失平台期。第二阶段的学习率我通常设成第一阶段的 1/2 到 1/3因为 Q-Former 已经有不错的初始化学太快容易把第一阶段学到的东西冲掉。梯度裁剪阈值设 1.0多模态训练里梯度爆炸不算罕见尤其是 ITG 的自回归损失偶尔会出尖峰。还有一点两个阶段用的数据可以不同。第一阶段需要高质量的图文对来做细粒度监督第二阶段更看重数据的规模和多样性。我一般第一阶段用清洗过的精标数据第二阶段混入更多网络爬取的弱标注对。5. 从零跑通一次环境、显存与关键代码5.1 数据准备与清洗的实际做法数据这块我不想讲空话直接说我用的流程。图像侧分辨率低于 224 的直接丢弃宽高比极端超过 3:1的做中心裁剪用感知哈希做近重复去重阈值我设的 8能去掉大部分转载图。文本侧长度限制在 4 到 60 个词之间去掉纯数字、纯符号、以及重复字符占比超过 40% 的样本。还有一步是很多人会跳过的图文相关性初筛。用现成的跨模态检索模型给每对图文打个分把分数极低的样本剔掉。网络爬取的数据里噪声比例很高图是一只猫、文本讲的是股票这种对子如果留着会严重污染 ITC 的训练信号。这一步能砍掉 10%~20% 的数据但对最终指标的影响是正向的。数据格式我建议直接用 webdataset 的 tar 分片配合流式加载。百万级的图文对如果用单张图片文件的形式存训练时的 IO 会成为瓶颈尤其是小文件多的场景。tar 分片顺序读配足够的预取进程吞吐能稳很多。5.2 显存预算怎么算一次完整的推演这部分是我觉得最该写清楚的地方因为大部分复现失败都死在 OOM 上。以第二阶段、FlanT5-XL3B ViT-g1.1B Q-Former188M为例混合精度训练逐项拆项目计算方式占用图像编码器权重1.1B × 2 字节fp16约 2.2 GB语言模型权重3B × 2 字节约 6.0 GBQ-Former 权重fp16 副本188M × 2 字节约 0.38 GBQ-Former 主权重fp32188M × 4 字节约 0.75 GBAdamW 一阶 二阶动量188M × 4 × 2约 1.5 GB梯度fp16188M × 2约 0.38 GB激活值batch 32序列约 64主要是 Q-Former 12 层约 2~4 GB图像特征缓存batch 32 × 257 × 1408 fp1632 × 257 × 1408 × 2约 23 MB加起来大约 13~15 GB一张 24G 卡就能跑起来这在全量微调的方案里是不可想象的。如果换成 FlanT5-XXL语言模型权重涨到 22 GB就需要 40G 以上的卡了。再进一步因为图像编码器完全冻结特征可以离线预抽。把所有训练图的 patch 特征存成 fp16 的 npy 或 h5 文件训练时直接读特征连那 2.2 GB 的编码器权重都不用加载。200 万张图、每张 257×1408 的 fp16 特征磁盘占用大概 1.4 TB——这是这套方案唯一贵的地方但换来的是训练速度 3 倍以上的提升我认为非常值。5.3 Q-Former 的关键代码骨架下面这段是我自己实现时抽出来的核心部分去掉了工程细节保留结构。注意三种 mask 的构造方式不一样这是整个模块的灵魂。import torch import torch.nn as nn class QFormerBlock(nn.Module): def __init__(self, dim768, heads12, ffn_ratio4): super().__init__() self.self_attn nn.MultiheadAttention(dim, heads, batch_firstTrue) self.cross_attn nn.MultiheadAttention(dim, heads, batch_firstTrue) self.norm1 nn.LayerNorm(dim) self.norm2 nn.LayerNorm(dim) self.norm3 nn.LayerNorm(dim) self.ffn nn.Sequential( nn.Linear(dim, dim * ffn_ratio), nn.GELU(), nn.Linear(dim * ffn_ratio, dim), ) def forward(self, q, text_emb, text_mask, img_feat, modeitc): # mode: itc 单模态 / itg 因果 / itm 双向 b, n_q, d q.shape if mode itc: # query 与文本互相不可见各自独立 q_cat torch.cat([q, text_emb], dim1) n_t text_emb.size(1) mask torch.ones(n_q n_t, n_q n_t, dtypetorch.bool, deviceq.device) mask[:n_q, n_q:] False mask[n_q:, :n_q] False out, _ self.self_attn(q_cat, q_cat, q_cat, attn_maskmask) out self.norm1(q_cat out) else: # query 可看文本文本之间按模式决定是否因果 if mode itg: causal torch.triu( torch.ones(text_emb.size(1), text_emb.size(1), dtypetorch.bool, deviceq.device), diagonal1) else: causal torch.zeros(text_emb.size(1), text_emb.size(1), dtypetorch.bool, deviceq.device) q_cat torch.cat([q, text_emb], dim1) n_t text_emb.size(1) mask torch.zeros(n_q n_t, n_q n_t, dtypetorch.bool, deviceq.device) mask[n_q:, n_q:] causal out, _ self.self_attn(q_cat, q_cat, q_cat, attn_maskmask) out self.norm1(q_cat out) q_out out[:, :n_q] # cross-attentionquery 从图像 patch 取信息 q_out self.norm2(q_out self.cross_attn(q_out, img_feat, img_feat)[0]) q_out self.norm3(q_out self.ffn(q_out)) return q_out, out[:, n_q:]投影层的写法很简单但维度一定要对上class VisualProjection(nn.Module): def __init__(self, q_dim768, llm_dim2048): super().__init__() self.proj nn.Linear(q_dim, llm_dim) def forward(self, q_out): # (B, 32, 768) return self.proj(q_out) # (B, 32, llm_dim)第二阶段拼接软提示def forward_stage2(qformer, proj, img_feat, input_ids, labels, llm): q_out qformer(img_feat) # (B, 32, 768) vis_emb proj(q_out) # (B, 32, llm_dim) txt_emb llm.get_input_embeddings()(input_ids) # (B, L, llm_dim) inputs torch.cat([vis_emb, txt_emb], dim1) # 视觉在前文本在后 attn torch.ones(inputs.shape[:2], deviceinputs.device) # labels 前 32 位填 -100不计算视觉 token 的损失 full_labels torch.cat([ torch.full((labels.size(0), vis_emb.size(1)), -100, dtypelabels.dtype, devicelabels.device), labels ], dim1) return llm(inputs_embedsinputs, attention_maskattn, labelsfull_labels).loss注意labels前面那 32 个位置必须填 -100否则模型会去预测视觉 token 对应的下一个词这部分监督是错的会让损失虚高并且学不到东西。我第一次跑第二阶段时忘了这一步损失卡在 12 左右下不去排查了大半天。5.4 两阶段之间的检查点怎么衔接第一阶段的输出是完整的 Q-Former包含主干、ITC 投影头、ITM 分类头。保存时我建议分开存主干一份辅助头一份。第二阶段加载主干辅助头丢掉。衔接时有两个坑。一是位置编码和 query 初始化要保持一致32 个 query 的顺序不能变否则第一阶段学到的第 N 个 query 负责某类信息的对应关系会被打乱。二是优化器状态要重置两个阶段的学习率和损失尺度都不一样沿用第一阶段的动量会带来一段不必要的震荡。我一般的做法是第二阶段重新建优化器只对 Q-Former 主干和投影层设置参数组前者学习率低一些后者可以用高一些投影层是随机初始化的需要快速跟上。5.5 推理阶段prompt 写法直接影响效果BLIP-2 做零样本任务时prompt 的形式对结果影响很大这一点在实际使用中比论文里强调得更多。图像描述任务直接用a photo of作为前缀让模型续写效果比空 prompt 稳定。如果要做特定风格的描述前缀改成a detailed photo of或者the image shows输出风格会明显变化。视觉问答任务格式固定成Question: {问题} Answer:这个模板很重要换了格式准确率会掉。我试过直接拼问题不加模板同一批测试样本的准确率掉了七八个点。开放式生成可以用Question: {问题} Short answer:来约束输出长度避免模型长篇大论跑偏。还有个小技巧beam search 的 beam 数调小一点。零样本 captioning 时 beam3 到 5 就够了beam 太大会让输出变得保守和模板化反而丢掉细节。VQA 任务我一般用贪心解码速度最快且准确率几乎不损失。6. 常见问题与排查技巧实录6.1 训练不收敛或损失剧烈震荡这是最高频的问题。我的排查顺序是这样的先看数据管道。把 batch 里的图文对可视化出来看一眼十有八九能找到问题——图片全黑、文本是乱码、图文完全不对应。数据管道的 bug 比模型结构的 bug 常见得多而且是最好查的。再看损失尺度。ITC 是 InfoNCE初始值在 log(batch_size) 附近如果一开始就是几十说明温度系数设错了或者相似度计算有 bug。ITG 的交叉熵初始值应该在 5 到 10 之间如果直接冲到 30 以上通常是 labels 的 padding 位置没设 -100。最后看梯度。打开梯度范数监控如果某个时刻突然飙到几十以上通常是 ITG 的自回归部分遇到了长文本或者重复文本。梯度裁剪设 1.0 能压住大部分情况。至于损失震荡先确认学习率是不是太大了。多模态训练里 Q-Former 从 BERT 初始化一开始对图像是一无所知的前几百步损失下降慢是正常的别急着调学习率。我一般会忍到 1000 步再判断。6.2 生成重复、幻觉、答非所问这三个症状的成因不完全一样要分开处理。重复比如一只猫在桌子上一只猫在桌子上通常是解码策略问题加重复惩罚repetition penalty 设 1.1~1.2或者用 no-repeat n-gram 就能解决大半。幻觉描述里出现图里没有的东西多半是训练数据噪声导致的。视觉信息没有被充分利用模型就靠语言先验编。这时候可以在第二阶段增大视觉 token 的权重或者干脆把第一阶段的数据清洗再严格一点。我在一个项目里把噪声数据比例从 20% 降到 5%幻觉率明显下降。答非所问通常出在 prompt 模板上。VQA 任务如果模型输出了一段描述而不是答案说明它把任务理解成了 captioning。检查一下模板里的Answer:是不是漏了以及训练时该任务的样本格式和推理时是否一致。6.3 常见问题速查表现象最可能原因处理方式第二阶段损失卡在 10 以上不降视觉 token 位置没设 -100检查 labels 拼接逻辑零样本检索 Recall1 明显偏低ITC 温度系数过大或过小扫一遍 0.05~0.1 区间ITM 准确率虚高但实际判别差负样本太简单没做硬挖掘加 ITC 相似度 top-k 采样训练前期 loss 平台期很长warmup 太长或学习率太低warmup 降到 500 步lr × 2生成的句子语法正确但内容空洞第一阶段 ITG 权重太低提高 ITG 损失系数并检查数据质量显存占用远超预期图像编码器没冻结或特征没缓存检查 requires_grad 和离线特征流程多卡训练速度不升反降数据加载成为瓶颈换 webdataset 流式加载增大预取换语言模型后效果骤降投影层维度或拼接位置错误核对词嵌入维度和拼接顺序6.4 几个不太会写在文档里的经验第一个训练 Q-Former 时把图像特征缓存成 fp16但 loss 计算用 fp32。混合精度下 ITC 的相似度矩阵在 fp16 里容易溢出或精度不够尤其是 batch 大的时候。我现在都是在算相似度之前把特征 cast 回 fp32代价很小稳定性提升明显。第二个第二阶段训练数据的图文比例不要失衡。有些任务里 image caption 样本远多于 VQA 样本训出来的模型会倾向于把所有输入都当成描述任务。我做过分层采样让不同任务的比例维持在一个大致均衡的区间效果比自然分布好。第三个别迷信大 batch。第一阶段 ITC 确实需要大 batch 提供足够的负样本但如果你用的是梯度累积模拟的大 batch负样本的质量会打折因为跨累积步的样本不参与同一次对比。真要用大 batch优先考虑多卡并行而不是累积。7. 能力边界与我实际项目里的改造点7.1 这套架构做不好什么用了几个项目之后我对 BLIP-2 的能力边界算是比较清楚了。细粒度计数基本不行问图里有几个人经常数错尤其是超过五个的时候。密集文字识别很弱图片里的招牌、标签、表格内容基本读不出来因为 32 个 query 的信息带宽不够承载这么多细节。精确空间关系也一般左边的红色盒子旁边的蓝色杯子这种多跳指代成功率不高。长文本生成容易发散描述超过三四句就开始编。这些不是调参能解决的问题是架构层面的信息瓶颈。32 个 query 就是 32 个槽稠密信息塞不进去。真要做 OCR 类任务老老实实接一个专门的文字检测识别模块把结果作为文本输入喂给语言模型比硬改 BLIP-2 靠谱得多。7.2 我在实际项目里做过的三个改造改造一换视觉主干。在一个领域图像跟自然图像分布差异较大的项目里我把 ViT-g 换成了在该领域数据上自监督预训练过的骨干。改动很小只要保证输出的 patch 数量和维度对上cross-attention 那边不用动。换完之后零样本检索的 Recall10 涨了约 8 个点。代价是丢了跟原版权重的兼容性得从头训第一阶段。改造二给语言模型挂轻量适配层。全冻语言模型在通用任务上没问题但特定领域的表达风格比如商品标题的写法它学不来。我在冻结的 LLM 里插了秩很小的低秩适配矩阵只训这部分加上 Q-Former。可训练参数从 188M 涨到 220M 左右显存只多了 1G 出头但生成风格的对齐度好很多。改造三把 Q-Former 输出同时接到检索头。原来的架构第二阶段只做生成但我需要的是既能生成又能检索。做法是在第二阶段保留一个轻量的对比头用 Q-Former 输出和 LLM 最后一层的池化表示做双塔对比。多加的参数量很小两个任务互相还有正则效果。7.3 给你的选型建议如果你现在要做一个图文相关的应用我的判断逻辑是这样的数据量在百万级以下、算力有限、需要快速出效果直接用现成的 BLIP-2 权重做零样本或者少样本微调别自己从头训。数据量在千万级、有明确的下游任务、效果要求高可以走冻结双端 训中间这条路BLIP-2 的架构直接拿来当模板但第一阶段一定要重训。如果你的任务对细节要求极高OCR、密集检测、精细计数别在这套架构上耗它的信息瓶颈是设计决定的绕不过去。我在实际使用中最深的一个体会是这套方法真正的价值不在模型本身而在那个只训 1.5% 参数的思路。当你需要把一个新的模态接进语言模型时问自己三个问题就够了——这个模态有没有现成的强编码器可以冻结中间需要一个多大的信息瓶颈两阶段训练该怎么拆把这三个问题想清楚架构基本就出来了。Q-Former 只是其中一个答案不是唯一答案。