上周有个做票据识别的朋友找我吐槽,说他们接了个多模态大模型,平时拿图片问东问西挺像那么回事,可一碰上密密麻麻的发票截图,问"第三行那个金额是多少",十次里有六次给你看错行。他们第一反应是模型太小,换了个参数量翻倍的,准确率只挪了几个点。后来我让他别再整图缩放到固定尺寸硬塞进去,改成按长宽比切成若干块再拼回原图信息,同一个模型,那个字段的准确率肉眼可见地上去了。这事儿特别典型:大多数人评估多模态大模型,第一反应看参数量、看榜单分数,但真正决定它在你的业务里能不能用的,往往是视觉编码器吐出多少个 token、这些 token 用什么方式进到语言模型里、进去之后还剩多少注意力预算留给真正的文本推理。这套东西就是多模态大模型的架构范式,也是这篇想讲透的东西。
这篇写给两类人:一类是想把现成模型接进业务、但被分辨率和 token 开销折腾过的工程同学;另一类是想自己动手训一版垂类模型,却在"要不要自己训视觉编码器""投影层该做几层"这些选择上卡住的人。我会把两种主流架构拆到能动手改的程度,把当下几类模型的适用边界讲清楚,也会把我在实际项目里踩过的坑原样摆出来。
1. 从一张票据截图被读错行说起:多模态的瓶颈不在参数
先把一个反直觉的结论摆在前面:多模态模型"看不清"的原因,绝大部分时候不是它不够大,而是它的输入在进入语言模型之前就已经被压扁了。一张 1440×2560 的手机截图,如果按 448×448 缩放进去,原本 20 像素高的数字会被压到 4 像素左右,视觉编码器那点感受野根本分不出"3"和"8"。你在这种输入上再怎么加参数、再怎么调 prompt,都只是让模型更自信地答错。
我在早期做合同要素抽取时吃过一模一样的亏。当时用的方案是固定分辨率,合同扫描件里的手写签名日期永远抽不对。排查了两天才意识到,问题不在语言模型端,而在预处理那一步把所有图都 resize 到了同一个尺寸,图上的小字早就糊成一团了,模型看到的就是一张被打码的图。换成分块送进去以后,同一批样本的字段级准确率从六成多提到了九成上下,模型一行参数都没动。
1.1 视觉token预算:被大多数人忽略的第一约束
大模型推理的上下文窗口是按 token 计费的,而图片在模型眼里就是一批"视觉 token"。一张 448×448 的图,patch 大小取 14,切出来是 32×32 共 1024 个 patch;如果再在通道维度做一次 2×2 的空间合并,最后进语言模型的大约是 256 个 token。这个数量听着不多,但一张 A4 扫描件切完往往是 20 到 30 个 tile,加上一张全局缩略图,总 token 数直接冲到六七千。
这里有个简单的估算公式,我平时拿来给团队做容量评估:
def estimate_vision_tokens(width, height, tile=448, merge_ratio=2, patch=14): tile_tokens = (tile // patch) ** 2 // (merge_ratio ** 2) nx = -(-width // tile) # 向上取整 ny = -(-height // tile) return (nx * ny + 1) * tile_tokens, tile_tokens # 一张手机长截图 print(estimate_vision_tokens(1440, 2560)) # 一张 A4 扫描件(300dpi) print(estimate_vision_tokens(2480, 3508))跑出来会发现,A4 扫描件切完之后视觉 token 能占到小一万。这意味着两件事:一是显存和延迟都会显著上涨;二是当视觉 token 占据序列的绝大部分时,模型在文本上的注意力被严重稀释,你会观察到它"看得很细但答得很糊"。所以后面所有关于压缩、切图、合并的工程手段,本质上都是在同一个约束下做取舍:用多少 token 换多少细节。
1.2 两条路线的真正分界线
业内常说的"两种核心架构",不同人有不同口径,有人按单塔双塔分,有人按是否端到端训分,容易吵成一锅粥。我更倾向用一个能指导写代码的划分标准:视觉信息以什么形态进入语言模型的主干计算。
一条路是把图像交给一个已经训好的视觉编码器,把它输出的特征通过一个专门的连接模块,投影成语言模型能接住的若干 token,再拼进词序列里。视觉和语言在主干之外是两套系统,只在投影层短暂会合,业内常叫拼接式、模块化、晚融合。另一条路是在预训练阶段就让图像和文本用同一套 token 表示、同一个 Transformer 主干、同一个训练目标来处理,视觉几乎不被当作"外部特征",而是被当作另一种语言直接混合进去,通常叫原生统一或早期融合。
这两条路的分界点很清楚:图像和文本的表示空间是在什么时候对齐的。拼接式是"先各自学会说,再找个翻译",原生式是"从小混着养"。前者工程上极其灵活,任何一个环节都可以单换;后者上限更高,但你要么全部自己来,要么就只能接受别人给你端上来的成品。理解了这一条,后面所有的模型选型和微调策略就都有了推导的起点。
2. 拼接式架构:三段式流水线的分工、代价与调参空间
拼接式架构目前是绝大多数开源多模态模型的实际做法,结构上可以画成三段:视觉编码器、连接器、语言模型。BLIP-2 是这条路上最早把"用少量 query token 把图像信息压进语言模型"讲清楚的工作之一,后来的 LLaVA 系列则用更朴素的方式证明了——只要数据够、对齐够,一个两层 MLP 的连接器就能跑出很好的效果。这条路线之所以流行,核心原因是它允许你把最贵的语言模型冻结住,只训一小块连接器,训练成本能压到单机多卡的量级。
但它的代价也很明确:视觉编码器是在图文对比学习目标下训出来的,它学到的"好特征"和语言模型想要的"可推理的特征"并不完全是一回事。连接器要做的其实是翻译工作,而翻译失真就是这条路线一切问题的源头。你在使用中遇到的那些"模型明明看到了却答错"的现象,多半能追溯到这一步的语义损耗。
2.1 视觉编码器怎么选:CLIP系、SigLIP系与自研ViT
选编码器时最容易被忽略的一点是:它不是越强越好,而是越"和你的任务分布接近"越好。CLIP 系编码器是在互联网图文对上做对比学习训出来的,天然偏向物体级、场景级的语义,对图表、公式、密集小字这类内容并不擅长。SigLIP 系把对比学习目标从 softmax 换成 sigmoid,在同样数据量下通常能拿到更好的细粒度表现,这也是近两年新出的模型大多换用它做视觉底座的原因。
如果业务集中在文档、票据、截图这类"高密度符号"场景,还有一条路是用你自己数据继续训视觉编码器的最后几层。我做过一次对比实验:同一个语言模型、同一个连接器,视觉端分别用通用对比学习编码器和在几十万张票据上继续训过的版本,字段级抽取准确率差了将近二十个百分点。代价是编码器的通用能力会退一点,如果你是多任务混跑,得留一部分通用数据做正则。
表格里是我平时做选型时用的判断依据,按优先级从上往下看:
| 判断维度 | 该选什么 | 理由 |
|---|---|---|
| 主任务是自然图像理解 | 通用对比学习编码器,冻结即可 | 语义已经对齐得不错,省训练预算 |
| 主任务是文档、图表、小字 | SigLIP 系或自训编码器 | 细粒度分辨能力更关键 |
| 需要输出 bbox / grounding | 支持任意分辨率的 ViT,别用固定位置嵌入 | 否则坐标还原会失真 |
| 多语言 OCR 场景 | 编码器最好在同语种数据上见过 | 视觉端对字形是有先验的 |
| 显存紧张 | 小 patch 尺寸 + 强压缩连接器 | 用压缩换序列长度 |
还有一个细节:patch size 取 14 还是 16,直接影响 token 密度。取 14 在同样分辨率下 token 更多、细节保留更好,但序列长度涨得快;取 16 省 token 但小字丢得多。这个参数一旦定下来,后面所有分辨率策略都得围着它转,别中途改。
2.2 连接器的三种典型做法与token账本
连接器这个小模块,是整条流水线里参数最少、影响却最大的部件。常见的三种做法各有明确的适用场景,我按"压缩能力从弱到强"排一下。
第一种是线性或两层 MLP 投影。LLaVA 系列走的就是这条路,把视觉编码器输出的每个 patch 特征直接线性映射到语言模型的隐层维度,token 数量一个不减。优点是信息无损、训练极快、实现简单;缺点是完全不压缩,一张高清图就能把上下文吃满。我一般把这条路当作 baseline,先用它跑通链路验证数据质量,再考虑换更强的压缩方案。
对应的实现简单到不需要解释:
import torch.nn as nn class TwoLayerProjector(nn.Module): def __init__(self, vision_dim=1152, hidden_dim=4096, llm_dim=3584): super().__init__() self.net = nn.Sequential( nn.Linear(vision_dim, hidden_dim), nn.GELU(), nn.Linear(hidden_dim, llm_dim), ) def forward(self, patch_features): # patch_features: [B, N, vision_dim] return self.net(patch_features)第二种是可学习的 query 压缩,代表是 BLIP-2 的 Q-Former 和 Flamingo 的 Perceiver Resampler。思路是准备一小撮固定的可学习 query,让它们通过交叉注意力去"读取"全部 patch 特征,最后输出固定数量的 token,比如 32 个或 64 个。压缩率极高,代价是细节大量丢失,尤其是位置敏感的信息。这条路适合视频这类帧数多、单帧细节要求不极致的场景,用在文档上基本等于自废武功。
第三种是空间合并加线性降维,实践中性价比最高。典型做法是把相邻的 2×2 个 patch 在通道维度拼起来,再过一个线性层压回原维度,token 数直接降到四分之一。这类方案在多个开源模型里被验证过,对理解类任务几乎无损,对 OCR 类任务有一点损失但可以通过提高输入分辨率补回来。
import torch def spatial_merge(x, ratio=2): """x: [B, H*W, C] -> [B, (H/ratio)*(W/ratio), C*ratio^2]""" b, n, c = x.shape h = w = int(n ** 0.5) x = x.view(b, h, w, c) x = x.view(b, h // ratio, ratio, w // ratio, ratio, c) x = x.permute(0, 1, 3, 2, 4, 5).contiguous() return x.view(b, (h // ratio) * (w // ratio), c * ratio * ratio) # 之后必须接一个 Linear(C * ratio**2, C) 把通道压回去一个容易被忽略的坑:这个合并操作的排列顺序必须和图像的空间顺序一致。我见过有人在 permute 时把两个维度写反,训练照样收敛,loss 也降,但模型对"左边""右边"这类方位问题答得乱七八糟,排查了很久才定位到是合并时把行列搞混了。
2.3 冻结还是解冻:训练预算与效果的分水岭
拼接式架构最大的工程红利,就是可以分阶段控制"训哪一部分"。实践中我基本固定用三阶段走法。
第一阶段只训连接器,视觉编码器和语言模型全冻结。这一步的目的是把视觉特征空间的分布拉到语言模型能理解的范围里,数据量不需要很大,几十万条图文对就够,单机多卡几小时能跑完。这一步做完,模型已经能"看着图说话"了,虽然还很笨。
第二阶段解冻语言模型,用较小的学习率做全参微调或者上 LoRA。这一步决定模型的指令跟随质量,数据质量和配比比数据量重要得多,我在下一节会展开。视觉编码器我一般仍然冻着,除非业务分布和通用图文差得特别远。
第三阶段才是解冻视觉编码器的最后几层做联合微调,学习率压到第二阶段的一到两个数量级以下。这一步提升明显但极易过拟合,如果验证集和训练集同源,你几乎看不出问题,一上真实数据就崩。我的习惯是这一步一定留一份完全独立来源的验证集,哪怕只有几百条。
冻结策略还有一个副作用值得提前知道:只训连接器时,模型的"视觉常识"完全来自编码器,而"语言能力"完全来自语言模型,两者之间靠那几层 MLP 硬桥接。这意味着当你在业务里遇到编码器没见过的东西(比如某种特殊字体的表格线),模型的反应往往不是"我不确定",而是用语言先验硬编一个答案。这是拼接式架构幻觉的一个结构性来源,后面讲评测时我会再提。
3. 原生统一架构:把图像当作另一种"母语"
原生统一架构的思路完全不同:不设独立的视觉编码器,或者即使有也不把它当作"外部适配器",而是尽可能早地把视觉信号转成和文本同构的 token 序列,丢进同一个 Transformer,用同一套自回归目标去学。Fuyu 是这条路上比较激进的一个代表,它干脆取消了视觉编码器,把图像的 patch 直接线性投影后和文本 token 拼在一起,好处是天然支持任意分辨率、不需要做图像缩放,代价是视觉端的表征能力要靠语言模型的预训练目标自己长出来,训练量要求极高。
Chameleon 这类工作走的是另一条更彻底的路:把图像用离散化的方式切成视觉词,和文本词放进同一个词表,模型从头到尾只做一件事——预测下一个 token。这条路在概念上非常优雅,理解、生成、编辑都能用同一个目标表达,但工程上的门槛也很实在,我在 3.2 会讲。
3.1 早期融合究竟改在了哪一层
很多人以为原生架构的差别在于"有没有视觉编码器",其实更关键的是融合发生在哪一层,以及位置编码怎么统一。拼接式架构里,视觉 token 和文本 token 是在语言模型的输入嵌入层才第一次见面的,之前它们在两个完全独立的网络里;原生架构则要求视觉 token 从第一个 Transformer 层开始就和文本 token 一起参与注意力计算。
这个差别带来的直接影响是:原生架构里,模型的每一层都在学习跨模态关联,而拼接式只有投影层之后的部分在做这件事。理论上原生架构能学到更细粒度的跨模态对齐,比如"这个词指的是图里那个物体的左边缘",因为注意力在浅层就能把两种模态的信息混起来。
实现上最麻烦的是位置编码。文本是一维的,图像是二维的,视频还多一个时间维度。常见做法是把一维的位置 id 分解成时间、高、宽三个分量分别编码,再按分量组合出旋转位置编码的角度。这个设计对多图输入尤其重要——如果所有图共用一套位置 id,模型就会出现"分不清哪张图是哪张"的问题,表现是问第二张图的内容它给你答第一张。
3.2 离散视觉token化的两难
把图像变成离散 token,最直接的办法是训一个视觉词表,类似把图像切成小块后每块查表得到一个 id。问题在于词表大小和重建质量是一对死结。词表小了,纹理和细节全糊,文字直接变成乱码;词表大了,softmax 那一层的参数量和计算量爆炸,而且大量词在训练中根本用不到,白白浪费容量。
我见过几个团队在这上面反复横跳,最后多半会退回连续特征加投影的方案。我的判断是:如果你的核心任务是理解而不是生成,离散化这条路的收益目前还抵不上它的复杂度;只有当你的产品里"生成图像"和"理解图像"必须是同一个模型、同一套权重时,离散统一才有压倒性优势。
还有一个实操层面的坑:视觉 token 的序列长度会随分辨率线性增长,且不能像文本那样随便截断。文本截断只是丢信息,图像 token 截断会直接切掉半张图,模型看到的是一个残缺世界,但它自己不知道。如果非要在原生架构上做长图,宁可降采样也不要硬截断。
3.3 生成能力上的天然优势与训练门槛
原生架构最吸引人的地方,是它在生成任务上的统一性。同一套自回归目标,文本生成是往外吐词,图像生成是往外吐视觉 token,再解码回像素。这意味着"看图说话"和"按描述画图"在模型内部是同一个能力的两面,不需要额外接一个扩散模型,也不存在两个模型之间对齐不一致的问题。
但门槛也很真实。第一是数据:你要让模型学会视觉 token 的分布,需要的数据量比纯理解任务高一个量级。第二是训练稳定性:视觉 token 的预测难度分布和文本差别很大,混在一起训容易出现梯度尺度失衡,实践中往往需要给视觉部分单独调损失权重或者做梯度裁剪。第三是推理效率:自回归生成一张图要吐上千个 token,比扩散模型慢得多,端侧基本没法用。
所以我在给团队做技术选型时的态度是:如果你的需求 95% 是理解类任务,别为了 5% 的生成需求去背原生架构的复杂度。反过来,如果你的产品核心就是图文混合创作,那原生架构的统一性迟早会帮你省下大量的胶水代码。
4. 两套架构摆在一起比:一份能直接照着做决策的对照
讲完两条路,我把它俩放在同一张表里对一遍。这张表是我自己项目复盘时攒的,不是论文里的理论对比,所以有些维度会比较"工程"。
| 对比维度 | 拼接式架构 | 原生统一架构 |
|---|---|---|
| 视觉与文本对齐的深度 | 只在投影层之后融合,浅层各自独立 | 从第一层就混合注意力,逐层对齐 |
| 训练成本 | 可冻结主干,单机多卡能跑通 | 通常需要从头预训练,成本高一个量级 |
| 换视觉编码器的灵活性 | 极高,可以单独替换再对齐 | 极低,编码器已经是主干的一部分 |
| 支持任意分辨率 | 需要额外的切图与位置编码设计 | 天然支持,patch 直接投影即可 |
| 视觉 token 数量控制 | 可精细调节,压缩手段多 | 受序列长度硬约束,压缩会伤重建 |
| 图像生成能力 | 需外接生成模型,两套权重 | 同一套权重,理解生成统一 |
| 幻觉的可控性 | 幻觉多来自编码器盲区,可通过换编码器缓解 | 幻觉与语言能力纠缠,难单独归因 |
| 适合的团队 | 有明确业务、想快速迭代 | 有大规模算力、要做统一模型 |
4.1 六个维度的硬碰硬
表格只是结论,我把其中几个最影响实际决策的维度再拆细一点,因为很多团队就是在这几处判断错的。
分辨率支持这一项,拼接式其实是被低估的。很多人觉得它只能吃固定尺寸,是因为只见过最朴素的实现。实际上通过动态切图加位置 id 重排,拼接式完全可以做到任意长宽比,只是要额外写一套预处理和后处理逻辑,而且切图数量一多,token 开销就上去了。原生架构省掉了这套逻辑,但不是免费的,它把这部分复杂度转移到了训练阶段——你得让模型在预训练时见过足够多不同尺寸的图。
幻觉归因这一项特别值得说。拼接式架构出现幻觉时,你至少能通过可视化注意力、单独测编码器的特征质量来定位问题到底出在哪一段。我在一个项目里就是靠这个办法发现,模型对某种特殊印章的识别率极低,原因是编码器在对比学习阶段几乎没见过类似图案,换个编码器加少量继续训练就好了。原生架构就没这么好运,它的视觉和语言能力是纠缠在一起的,你没法简单地说"是视觉端不行",只能整体调。
迭代速度这一项在真实项目里权重极高。拼接式架构允许你在不动主干的前提下换视觉端、换连接器、甚至接一个完全不同的语言模型,这对一个需要不断试错的业务团队来说是生死攸关的。我做过一次对接,从换编码器到跑通评测,前后不到两天。
4.2 我给团队用的选型判断顺序
纸上对比完了,真到选的时候我一般按这个顺序问自己四个问题,一旦某个问题得到确定答案,后面的就不用再纠结了。
第一个问题:你的核心任务里,图像是"信息载体"还是"创作素材"?如果是前者——票据、图表、截图、监控画面——选拼接式,因为你需要的是把图里的信息准确读出来,而不是画出来。如果是后者,原生架构的统一性迟早会帮到你。
第二个问题:你有没有足够的算力从头训一个模型?没有的话,原生架构这条路实际上走不通,你只能在别人的成品上做增量和微调,而原生模型的微调空间通常比拼接式小得多。
第三个问题:你的视觉分布和通用图文差多远?差得越远,编码器可替换性的价值就越大。医疗影像、工业质检、遥感这类领域,我基本都推荐拼接式,因为你需要的是能单独换、单独训的视觉端。
第四个问题:你的上下文预算有多紧?如果一次请求要塞进十几张图或者几十页文档,token 开销就是硬约束,这时候压缩手段丰富的拼接式更有操作空间。
5. 当下的模型梯队:按场景挑而不是按分数挑
这一段我尽量少写分数,因为公开榜单的更新速度比文章发布快,写死数字对你没有帮助,而且在真实业务里的排序往往和榜单差得很远。我按使用场景分四类讲,每类给出判断依据和适用边界。
5.1 端侧与轻量部署
端侧的核心矛盾是显存和延迟,所以这类模型的设计重点几乎全在压缩上:小尺寸视觉编码器、激进的空间合并、更小的语言骨干。像 MiniCPM-V 这类偏轻量的模型,以及 InternVL 系列里的小尺寸变体,在这条线上做得比较扎实。实测下来,这类模型跑单图问答、简单 OCR、图片描述是完全够用的,但在多图对比、复杂推理、长文档这几项上会明显吃力。
端侧选型我踩过最大的坑是量化之后视觉分支先崩。语言分支量化到 4bit 通常还能撑住,但视觉编码器和连接器对数值精度更敏感,我遇到过量化后整体准确率只掉几个点,但 OCR 类任务直接腰斩的情况。稳妥做法是视觉端保持高精度、只量化语言分支,或者用混合精度的量化配置,别一把梭。
5.2 文档、表格与高分辨率截图
这是目前需求量最大、也最容易踩坑的场景。判断一个模型能不能干这活,别只看它在文档类评测上的总分,要看三个细节:一是它支持的最大输入分辨率,二是它的切图策略是不是按长宽比自适应,三是它有没有做位置编码的二维分解。
像 Qwen-VL 系列、InternVL 系列在这条线上迭代得比较充分,动态分辨率和切图逻辑都比较成熟。但即便用这些模型,我仍然建议在业务侧加一层预处理:先把图做倾斜校正、二值化和去噪,再送进去。我做过对比,同样一张倾斜五度的发票,加不加校正,字段准确率能差十几个点,而校正这步用传统图像处理几行代码就能做,比换模型划算多了。
另外一个经验:表格类内容如果结构复杂,别指望模型一次读全,拆成"先定位区域、再逐块问答"的两步走会稳很多。一步到位看着优雅,但错误会累积,而且出了问题你没法定位。
5.3 视频与长序列理解
视频场景的 token 开销是图片的几十倍,所以这类模型的关键设计几乎都围绕"怎么在时间维度上采样和压缩"。常见做法是稀疏抽帧、帧内做更强的空间合并、跨帧做时间维度的池化。判断一个模型适不适合视频,我会先看它单次能吃的最大帧数,再看它是不是支持帧间的时间位置编码。
实践中的一个反直觉结论:均匀抽帧往往不如关键帧+均匀混合采样。均匀抽帧在快节奏视频上会漏掉关键动作,而纯关键帧检测在慢节奏视频上又会重复采样相似画面。我的做法是先用简单的帧差做一次粗筛,再在变化大的区段加密采样,token 总量不变的情况下信息密度高不少。
5.4 理解与生成一体的模型
如果你的产品要做图文混合创作,就得看这一类。原生统一架构的模型在这里有天然优势,同一套权重既能读懂参考图,又能生成新图,还能做局部编辑。Chameleon、Emu3 这类工作展示了纯 token 路线能做到什么程度,而混合损失(文本用下一 token 预测、图像用扩散损失)的思路则试图在生成质量上追平专用模型。
选这类模型时我建议重点测一件事:改写一致性。给它一张图,让它用文字描述,再让它按这段文字重新生成,看看生成结果和原图差多远。这个测试能同时暴露理解精度和生成保真度两个问题,比单独测任何一项都有效。
6. 从选型到跑通:我实际会走的六个步骤
前面讲的是判断,这一段讲落地。我把一个多模态模型从选型到能上线,固定走六步,中间任何一步没通过就不往下走。
6.1 分辨率策略与动态切片的参数设置
这是最容易出事的一步。我默认用动态切片,参数上遵循三条原则:tile 尺寸和视觉编码器的预训练分辨率严格对齐,长宽比极端的图(比如手机长截图)沿长边切,切完补一张全局缩略图作为"目录"帮助模型建立整体布局。
def plan_tiles(w, h, tile=448, max_tiles=24): ratio = w / h if ratio > 4: # 极宽,沿宽边多切 nx, ny = max_tiles, 1 elif ratio < 0.25: # 极长,沿长边多切 nx, ny = 1, max_tiles else: s = (max_tiles * tile * tile / (w * h)) ** 0.5 nx = min(max_tiles, max(1, round(w * s / tile))) ny = min(max_tiles, max(1, round(h * s / tile))) return nx, ny这里有个坑必须提:切片数量超过模型训练时见过的上限,效果会断崖式下跌。很多模型在训练时最多见过的 tile 数是有限的,你在推理时超了,位置编码分布就对不上,表现为模型开始"左右张望"、答非所问。所以 max_tiles 这个参数一定要查一下模型训练配置,别自己拍脑袋。
6.2 视觉token压缩的几种工程手段
除了模型自带的压缩,我还会在工程侧补几层。第一层是空白区域裁剪,文档扫描件四周的空白可能占掉三分之一面积,裁掉之后切图数量直接下降。第二层是按内容密度分配精度,对文字密集区域用高分辨率 tile,对纯色背景区域用降采样。第三层是多页合并摘要,如果一次请求要处理十几页文档,不要把所有页都塞进去,先用廉价的方式给每页生成一句摘要,再把相关页拉进来细看。
这套组合下来,我在一个合同审阅场景里把平均 token 消耗压到了原来的四成,准确率没有下降。代价是链路变长了,调试成本上升,所以这套优化我一般放在第二版做,第一版先跑通再优化。
6.3 指令微调数据的配比与构造
微调数据这块,我的经验是配比比总量重要得多。一个典型的配比大致是:通用图文问答占四成保持能力不退化,OCR 和文档抽取占三成做任务专项,图表和表格占一成半,剩余留给多图对比、空间定位、以及"拒绝回答"样本。
那个"拒绝回答"样本特别值得单独说。我最初做微调时完全没放这类数据,结果模型对任何问题都给答案,哪怕图里根本没有那个信息。后来加了大约百分之二的"图中无此信息""无法从图片判断"的样本,胡编率立刻下来了。这类数据的构造方式很简单:拿正常样本,把问题里的实体替换成图中不存在的,答案统一标为拒绝。
还有一点:指令微调阶段一定要混入多轮样本。单轮数据训出来的模型,在真实对话里第二轮的指代消解会明显退化,比如你说"把上面那个数字乘以二",它不知道"上面那个"指什么。我在数据里刻意构造了大量"首轮提问 + 次轮追问"的样本,效果比单纯加大数据量好。
6.4 评测不要只看总分
这是我特别想强调的一点。公开榜单上的总分对你的业务几乎没有指导意义,因为它是大量子集的平均,而你的业务只落在其中某一个很窄的分布上。我在实际项目里会自己搭一套小评测集,固定在一两百条,全部来自真实业务数据,人工标注答案。
评测维度我会拆成几个:字段级准确率(不是整段对不对,是每个字段单独算)、位置准确率(答案对不对之外,还要看它指的是不是正确区域)、拒答准确率(该拒的时候拒了没有)、以及跨轮一致性(同一张图换个问法答案一不一致)。最后那个维度特别能暴露模型是不是在硬编,我用它筛掉过好几个看起来榜单很高但一问换个说法就变卦的模型。
7. 几个反复踩到的坑
这几条是我在不同项目里反复遇到、每次都得重新提醒自己的问题,写出来省得你再花时间。
7.1 位置编码与多图排序的错位
多图输入时最常见的问题是图与图之间串味。你问"第二张图的标题是什么",它给你答第一张的。根本原因通常有两类:一类是位置 id 在拼接时没有按图重新编号,所有图共用一套二维位置;另一类是分隔符 token 用错了,模型没有明确的边界信号。
排查方法很简单:做一个消融测试,把三张明显不同的图(比如一张纯红、一张纯绿、一张纯蓝)送进去,问"第一张是什么颜色"。如果答错,就是排序问题;如果它答"我不知道"或者乱答,可能是分隔符失效。这个测试我每次都做,五分钟能定位一类大问题。
7.2 训练时的显存账本
多模态训练的显存消耗和纯文本完全是两个量级,因为视觉 token 会显著拉长序列。我记账时会分三块看:权重、优化器状态、激活。前两块好估,真正让显存爆掉的是激活,而激活又和序列长度是平方关系。
几个实用的手段:梯度检查点基本是必开的,用时间换空间;序列打包要谨慎,把不同长度的样本硬拼在一起会产生大量无效注意力,尤其是图像 token 的 padding 区域会污染整条序列;混合精度训练时视觉编码器部分建议保持 fp32,我遇到过 bf16 下编码器输出数值溢出导致 loss 直接变成 inf 的情况。
7.3 batch内padding导致的"变笨"
这条坑最隐蔽。多图训练时,一个 batch 里的样本图像数量不同,最常见的做法是 padding 到最大数量。问题在于padding 的那些空位如果没被正确 mask 掉,注意力会有一部分分散到无效区域上,表现是训练 loss 正常下降,但推理时模型对单图输入的响应明显不如多图输入灵敏,好像变笨了。
我定位这个问题花了整整两天:先怀疑数据,再怀疑学习率,最后把 batch size 设成 1 逐条对比,才发现是 batch 内 padding 的锅。修法就是确保注意力 mask 覆盖到所有 padding 位置,包括视觉 token 段和文本 token 段,别只 mask 文本那一边。这个 bug 的可怕之处在于它不报错、不崩、loss 曲线还很漂亮。
8. 我自己的几条判断
踩了这么多坑之后,我对"该选哪种架构"这件事的看法反而变简单了:先问图像在你的业务里承担什么角色,再问你的团队能承担多少训练成本,这两个问题能筛掉九成的选项。剩下的那一成,用一个小规模的真实数据评测集去跑,比看任何榜单都靠谱。
还有一条经验想分享给刚开始做这块的同学:不要一上来就想着训模型,先把预处理链路和评测集搭起来。我见过太多团队花了两个月训模型,最后发现瓶颈在输入图像质量上,或者根本没有一把能衡量进步的尺子。这两件事做扎实了,你可能发现根本不需要自己训,把现有的模型用好就够了。至于原生统一架构,我觉得它的方向是对的,但现阶段对大多数业务团队来说,它是"知道就好、不必现在就上"的那一类技术。