☰
多模态大模型架构选型:单流投影对齐与双流交叉注意力解析
2026/11/4 5:18:45 网站建设 项目流程

把一张图丢给模型,让它读出报销单上的金额、判断产线照片里哪个工位堆了料、顺手把拍歪的表格转成 Markdown——这类需求这两年从"发布会上的炫技演示"变成了"排期表里的正经任务"。真正撑起这些场景的核心就是多模态大模型(Multimodal Large Language Model,圈内一般简称 MLLM)。但很多人第一次翻开源实现的结构图时都会卡在同一个地方:参数量看着差不多,为什么换个模型跑同一张高分辨率截图,效果能差出一大截?答案往往不在"用了多大的语言模型"上,而在架构选型——视觉信息到底以什么方式、在什么位置、以多大代价进入语言主干。

这篇东西想解决的就是这个问题。我会把当前主流的两种核心架构掰开讲透:一种是投影对齐式的单流结构,图像 token 直接拼进语言序列;另一种是门控交叉注意力式的双流结构,视觉特征走旁路注入。两种路线各自的原理、训练流程、显存账怎么算、什么场景该选谁,我都会给到可以直接抄的参数和代码骨架。适合已经能跑通单模态推理、想进一步搞懂多模态工程细节的开发者,也适合正在做技术选型、需要给团队一个可落地结论的负责人。文中涉及的具体模型一律用代号指代,你拿公开资料一对照就明白说的是哪一类,但结论和方法是通用的。

1. 多模态大模型到底在解决什么问题

1.1 单模态模型的三个死穴

在聊架构之前,先把这个品类存在的理由说清楚,不然很容易陷入"为了多模态而多模态"的误区。纯文本模型在实际业务里有三个绕不过去的硬伤。

第一是信息入口太窄。业务现场产生的数据绝大部分不是文字:工单截图、监控画面、纸质表单、仪表盘照片、PDF 扫描件。你要让纯文本模型处理,中间必须插一道 OCR 或者人工转录,而这道工序恰恰是错误率最高、也最容易被忽视的环节。OCR 丢掉了版面结构,人工转录丢掉了时间,最后模型拿到的是二手的、被压缩过的信息。

第二是空间与视觉推理无从下手。哪怕你把图片转成文字描述喂进去,描述本身就已经是别人做的抽象了。"左下角有个红色指示灯"这种句子,对判断"这个指示灯的位置是否符合装配规范"来说信息量是不够的,模型需要自己看到像素级的空间关系。

第三是交互形态受限。用户拍照提问、圈选区域追问、连续多帧视频里追踪一个动作,这些交互天然是多模态的,纯文本接口根本表达不了。

1.2 多模态的核心动作:把图像塞进语言模型的语义空间

不管架构怎么变,所有多模态大模型干的其实是同一件事:把视觉信号映射成语言模型能理解的一串向量,然后让语言模型在这串向量上做条件生成。

这个动作看着简单,里面的坑全在"怎么映射"上。视觉编码器输出的特征维度和语言模型的词嵌入维度不一样(常见是 1024 或 1152 对 4096),语义空间也不一样——视觉特征描述的是纹理、边缘、颜色块的分布,词嵌入描述的是语言学上的共现关系。直接把两坨东西拼在一起,模型会当成噪声。

所以中间必须有一个对齐环节,让视觉特征在数值分布和语义粒度上都贴近词嵌入。这一步做得糙,后面再怎么调都救不回来;做得细,一个几亿参数的小投影层就能撑起相当强的视觉理解能力。这也是为什么两条架构路线最本质的分歧,就落在"对齐在哪一层发生、视觉 token 要不要进入语言序列"这两个问题上。

1.3 谁在用、用在哪

从我这几年接触的项目看,多模态大模型的落地场景大致可以分成四档,对架构的要求差别很大。

  • 文档与票据理解:扫描件、截图、表单,核心诉求是高分辨率下的小字识别和版面还原。
  • 工业与巡检视觉问答:产线照片、设备状态图,核心诉求是细粒度区域判断和稳定输出,容错率低。
  • 多图与视频理解:同一批物料的多角度照片、操作流程的连续帧,核心诉求是跨图/跨帧的时序与指代关系。
  • 通用助手与 Agent 输入:把屏幕截图、UI 界面当作模型的操作对象,核心诉求是低延迟和可高频调用。

前三档偏向"看得清、看得准",第四档偏向"跑得快、成本低"。这个差异在后面选型时会反复出现,因为它直接决定了你愿意为视觉 token 付多少预算。

2. 两条路线的分野:架构全景速览

2.1 路线甲:投影对齐式单流架构

这条路线可以概括成三件套:视觉编码器 + 投影层 + 语言主干,视觉编码器通常是一个在图文对比学习任务上预训练过的 ViT,投影层一般就是两三层 MLP 或者一个轻量 Q-Former 式的重采样模块,语言主干直接复用现成的开源大语言模型。

关键在于它的融合方式:图像被切成 patch、编码成一组视觉 token,经过投影层维度变换之后,直接拼接到文本 token 的前面,一起送进语言主干的 Transformer。从语言模型的角度看,前面这几十上百个 token 和后面的文字 token 没有任何区别,自注意力机制在同一层里同时处理它们。所以这条路线常被叫做"单流"或者"早期融合"——融合发生在输入序列层面,发生在第一层之前。

它的吸引力非常直接:改动最小。你几乎不需要碰语言主干的内部结构,只要把输入序列拼对、把 attention mask 处理好、把位置编码对齐,就能跑起来。这也解释了为什么绝大多数开源可复现的多模态方案都走这条路,社区生态、微调脚本、评测工具基本都是围绕它长出来的。

2.2 路线乙:门控交叉注意力式双流架构

这条路线保留了完整的视觉编码器作为独立的"塔",视觉 token不进入语言主干的输入序列,而是在语言主干的若干层之间插入额外的交叉注意力子层。语言流自己做自注意力,需要看图像的时候,通过交叉注意力去查询视觉特征。

融合是分层的、渐进的,不是一次性发生的。为了不破坏语言主干原本的能力,这类结构通常会给交叉注意力加一个门控系数,用 tanh 或者类似的饱和函数包一层,并且把门控参数初始化为 0。这意味着训练刚开始时,交叉注意力层的输出恒等于零,整个模型的行为和原始语言模型完全一致。随着训练推进,门控慢慢打开,视觉信息才逐步注入。

这个设计的巧妙之处在于"渐进"两个字。如果一上来就让视觉信息全量灌进语言主干,预训练阶段学到的语言能力会被大量图文对数据冲垮,出现典型的灾难性遗忘——模型变得能看图,但说话开始不利索。门控从零开始,相当于给模型留了一个缓冲期。

2.3 一张表看清取舍

对比维度路线甲(投影对齐单流)路线乙(门控交叉注意力双流)
视觉 token 是否进语言序列进入,占序列长度不进入,走旁路查询
多图/视频扩展性差,token 线性叠加好,视觉特征数量与序列长度解耦
训练数据形态图文对即可起步需要图文交错数据,成本高
语言能力保持中等,联合训练易退化好,门控机制保护
工程实现复杂度低,改输入即可高,需要改主干结构
推理显存占用随图像分辨率和张数快速上升相对平稳,交叉注意力额外开销可控
典型适用场景单图理解、文档 OCR、指令跟随多图推理、视频理解、长图文交错

这张表建议直接存下来。后面不管看哪篇模型结构介绍,你都可以拿这几个维度去套,基本能判断出它在设计上做了哪些妥协。

3. 路线甲拆解:视觉塔 + 投影层 + 语言主干

3.1 三个组件的职责边界

把这条路线拆开看,每个组件的分工其实非常明确,理解了这个分工,调参的时候就不会乱。

视觉编码器负责把像素变成稠密特征图。它通常是在大规模图文对比学习任务上预训练出来的,输出的不是单个类别标签,而是一个 patch 级别的特征矩阵。常见配置是 patch 大小 14、输入分辨率 336 或 448,28 层左右的 ViT,参数量在 3 亿到 6 亿之间。这个组件在训练时一般冻结,理由是它的表征已经足够通用,动了反而容易在小规模数据上过拟合。

投影层负责维度和语义的双重对齐。输入是视觉编码器输出的[N_v, d_v],输出是[N_v, d_l],d_l是语言主干的词嵌入维度。结构上通常是一个两层 MLP,中间夹一个非线性激活,参数量在千万量级。它虽然小,但承担了最关键的对齐工作,是三阶段训练里第一阶段唯一被训练的部分。

语言主干负责所有的高层推理和生成。它把拼接后的 token 序列当作普通的输入序列处理,输出文字。这里有个常被忽略的细节:视觉 token 和文本 token 共用同一套位置编码。如果视觉 token 数量很多,文本部分的位置索引会被推得很靠后,超出预训练时的长度范围,就会出现位置外推的问题。这是后面排查章节里一个高频故障点。

3.2 三阶段训练:对齐、预训练、指令微调

这条路线之所以好复现,很大程度上是因为训练流程被拆成了边界清晰的三段。

阶段一对齐:冻结视觉编码器和语言主干,只训练投影层。数据是几十万到上百万条纯图文对,任务就是最朴素的自回归——给一段描述图片的文字,让模型接着往下写。这个阶段的目的是把视觉特征的分布拉到词嵌入的邻域里。数据量不大,通常单机八卡跑一两天就够,成本是整个流程里最低的。

阶段二预训练:解冻语言主干,视觉编码器仍然冻结,投影层和语言主干联合训练。数据从纯描述扩展到更复杂的图文交错内容,分辨率也在这时候往上提。这个阶段最容易出问题的是学习率——语言主干的学习率要比投影层低一个数量级左右,否则语言能力下降会非常明显。我见过不少团队在这一步直接把语言主干学崩的,表现就是模型突然不会说完整句子了。

阶段三指令微调:用混合数据把模型调成"能听懂人话"的状态。数据里要包含视觉问答、OCR、图表理解、区域指代、拒答样本等多种类型。个人经验是这一步的数据配比远比数据总量重要,一个配比合理的三四十万条数据集,效果往往好过配比失衡的百万条。特别提醒要掺一定比例的纯文本指令数据,防止模型退化成"不看图就不会说话"。

提示:三个阶段的数据配比和学习率策略,建议写进配置文件的注释里。我踩过的坑是隔了两个月重跑实验,死活复现不出当初的指标,最后发现是阶段二的解冻范围改了但没记录。

3.3 分辨率与视觉 token 预算的算术

这是路线甲最需要算清楚的一笔账,因为它直接决定了你的显存和延迟。

视觉 token 数量的计算公式很朴素:

N_v = (H / p) × (W / p) × k

其中p是 patch 大小,k是动态分辨率下的切块数。举几个实际场景:

输入分辨率patch 大小切块数视觉 token 数
336 × 336141576
448 × 4481411024
896 × 896 动态切 4 块 + 1 全局图1452880
1280 × 720 视频帧 × 8 帧14每帧按 2 块算约 4600

看到问题了吗?一张普通的手机截图,如果走高分辨率动态切块,视觉 token 就能吃掉将近三千个位置。再加上文本和输出,上下文窗口很快就被挤满。这还不算 KV cache,算上就更吓人。

语言主干的 KV cache 显存可以用这个近似式估算:

KV_MB ≈ 2 × L × d_model × N_total × bytes / 1024²

拿一个 32 层、d_model=4096、bf16(2 字节)的模型举例,视觉部分 1024 个 token,光这部分 KV cache 就是2 × 32 × 4096 × 1024 × 2 / 1024² ≈ 512 MB。注意这只是单条请求,批量并发的时候要乘上批大小。很多"本地跑不动"的问题,根源都在这儿。

3.4 显存与吞吐的实测口径

给一组我常用的粗略估算口径,方便你在选卡之前先心里有数。7B 级模型在 bf16 下权重约占 14 GB,视觉编码器和投影层再加 1.5 GB 左右。视觉 token 控制在 576 到 1024 之间时,单条推理的峰值显存大致落在 18 到 24 GB。如果上动态分辨率、视觉 token 到 2880,峰值会冲到 30 GB 以上,24 GB 的卡就撑不住了。

吞吐方面,视觉编码这一步是串行前置的,也就是说你必须先把图编码完才能开始生成,这段时间是纯等待的。一张 448 分辨率的图,视觉编码在中端卡上大约几十毫秒,看似不多,但在高并发场景下它会成为瓶颈,因为它是不可并行的前置步骤。后面章节会讲怎么用异步预编码把这个等待藏起来。

4. 路线乙拆解:双流结构里的交叉注意力

4.1 为什么要在中间层做融合

路线甲把视觉 token 拼在输入序列前面,意味着视觉信息和文本信息从第一层就开始混在一起做自注意力。这带来一个隐性代价:语言主干每一层都在处理视觉 token,哪怕这一层的功能其实是语法层面的组合,根本用不上视觉信息。

路线乙的判断是:语言的层次化处理和视觉的层次化处理应该各自独立进行,只在需要的时候做受控的信息交换。这跟人类看图文混排材料的方式有点类似——你先扫一遍图形成整体印象,读文字时再回头去图里找对应区域,而不是每个字都盯着图看。

这个思路在多图场景下优势特别明显。假设你要让模型对比四张产品照片找出差异,路线甲的做法是把四张图的所有 token 都拼进序列,序列长度直接翻四倍,注意力计算是平方级增长。路线乙的做法是四张图各自编码成特征,语言主干在需要对比的时候分别对四组特征做交叉注意力查询,序列长度完全不受图像数量影响。

4.2 门控交叉注意力的工作方式

具体实现上,交叉注意力子层被插入到语言主干的若干层之间,插入间隔通常是一个超参,可以是每层都插,也可以每四层插一个。子层的结构和标准 Transformer 的交叉注意力一样:query 来自语言流的隐状态,key 和 value 来自视觉特征。

真正关键的是那个门控。形式上是给交叉注意力的输出乘上一个tanh(α),α是一个可学习的标量,初始化为 0。训练开始时tanh(0) = 0,交叉注意力层的贡献为零,模型就是原始的语言模型;随着训练推进,α逐渐变大,视觉信息的注入比例平滑上升。

这个设计解决了一个非常实际的问题:训练初期的梯度冲击。图文对数据的分布和纯文本数据差别很大,如果不加门控,第一轮反向传播就会有大量梯度涌进语言主干,把预训练学到的参数推开。加门控之后,梯度是渐进的,语言能力损失小得多。另外还有一个隐性好处,α的最终值本身就是一个可观测的指标——如果训练完发现α还是接近 0,说明模型基本没学会用视觉信息,这时候该查的是数据而不是调参。

4.3 代价:训练复杂度和推理延迟

天下没有白拿的好处。路线乙的工程成本主要体现在三块。

训练数据难搞。因为融合是分层的,模型需要学会"什么时候该看视觉特征",这就依赖图文交错的数据形态——长文档里穿插图片、多轮对话里交替出现图像引用。这类数据的构造和清洗成本远高于简单的图文对。而且训练时需要同时处理两种模态的掩码逻辑,数据加载器的复杂度会上一个台阶。

推理延迟不可预测。交叉注意力是额外计算,它的开销取决于视觉特征的数量。单图的时候还好,多图或者视频的时候,每一步生成都要对所有视觉特征做注意力,延迟会明显上升。更麻烦的是这部分计算不容易和语言部分的 KV cache 复用,优化空间小。

实现门槛高。改语言主干的内部结构意味着你不能直接拿现成的推理框架来用,很多融合优化、量化方案都要重新适配。这也是为什么走这条路线的开源实现数量远少于路线甲。

5. 主流方案横向对比

5.1 对比维度怎么选,别只看榜单

先泼一盆冷水:榜单分数在这个领域参考价值有限,尤其是综合类榜单。原因有三个。

第一,榜单题目和你的业务场景差距可能极大。很多评测集里的图像是精挑细选的高质量照片,而你的实际输入是光照不均、有反光、带手写批注的现场照。

第二,评测口径不统一。同样声称支持高分辨率,有的方案是真的原生高分辨率编码,有的是把图切块后分别编码,两者的实际表现差异在小字识别上非常明显。

第三,成本维度几乎从不体现在榜单里。一个在榜单上高两分的方案,如果推理显存翻倍、延迟翻三倍,在你的业务里很可能是负收益。

所以我建议的对比维度是这六个:视觉 token 预算、多图与视频支持、小字与细节保真度、语言能力保持、单条推理显存、工程集成难度。前四个看能力,后两个看成本。

5.2 六个维度的对照表

下面这张表是按方案类型归纳的,不针对任何具体产品,你拿去对照自己候选清单里的条目即可。

类型代号架构路线视觉 token 量级多图/视频小字保真单条显存集成难度
型号甲(轻量开源,7B 级)单流576 固定弱一般约 18 GB低
型号乙(开源,13B 级)单流 + 动态分辨率1000~3000 可变中好约 30 GB中
型号丙(闭源 API 旗舰)未公开,推测双流不可控强很好不适用最低
型号丁(开源,双流 + 视频)双流交叉注意力与图像数量解耦强好约 26 GB高
型号戊(原生多模态,从头训练)早期深度融合中等强好约 28 GB很高

几个观察值得单独说。轻量单流型号的 576 固定 token 是个硬约束,好处是显存和延迟极其可预测,坏处是超过这个信息量的图必然有损,A4 幅面的密集文字扫描件基本处理不了。动态分辨率的单流型号把灵活性补上了,但代价是显存曲线变得非线性,你得按最坏情况预留。

闭源 API 那行的"不可控"不是贬义,是说你看不到它的 token 处理策略,只能通过实测去摸边界。它的优势在于省掉了全部工程投入,如果你的调用量不大、对数据出域没有硬约束,这是最省事的选择。

型号戊这一行其实超出了"两种核心架构"的范围,它属于从预训练阶段就把多模态数据混进去训练的原生方案。我把它列出来是想说明:两种核心架构的分野正在被这种方案模糊,但从工程视角看,它更接近路线乙的可控性加上路线甲的简洁性,目前复现成本依然很高。

5.3 榜单分数背后的三个陷阱

结合上面这些维度,说说我在实际对比中踩过的三个坑。

陷阱一:分辨率标注和实际编码方式不一致。有些方案标称支持 4K 输入,实际是先把图缩到 448 再编码,标称数字只是预处理接口的上限。判断方法很简单,拿一张字号极小的图去测,能读出来才算真的。

陷阱二:多图能力靠 prompt 拼接模拟。把多张图的 token 简单拼在一起,模型未必能区分"这是图一的内容"和"这是图二的内容"。真正做了图像分隔标记的方案,会在每个图像块前后插入可学习的边界 token,多图指代的准确率差别很大。测试方法是问"第一张图里的物体在第二张图里出现了吗"这类需要跨图指代的问题。

陷阱三:OCR 能力和理解能力被混为一谈。有些方案文字识别很准,但你问它"这份合同的违约条款对哪一方更有利",它答不出来。这俩是完全不同的能力,前者靠分辨率,后者靠语言推理和对齐质量。评测的时候一定要分开测。

6. 选型决策与成本估算

6.1 按场景选架构

抛开具体产品,只看架构路线的适用边界,我通常这么判断。

优先考虑单流路线的场景特征:输入以单图为主,图的内容是文档、票据、界面截图这类信息密度集中在文字上的类型,对延迟敏感,团队没有改模型结构的精力。这类场景下,单流方案的投入产出比几乎无可匹敌,你只要把分辨率策略调对,问题就解决了一大半。

需要认真考虑双流路线的场景特征:输入天然是多图或视频(多角度巡检、操作流程、跨帧追踪),或者需要模型在长图文交错文档里做深层推理,或者你极度在意语言能力不能被视觉训练拖累。这类场景下,单流方案的序列长度会失控,硬扛的代价比重写一套融合逻辑还高。

还有一种情况经常被忽略:如果你只是需要一个稳定的视觉问答接口,其实不必自己训练。把开源的单流模型拿去微调,几百到几千条业务数据就能把特定领域的准确率拉上来,成本远低于从零设计架构。

6.2 显存与延迟的估算方法

给一套可以直接套用的估算流程,避免上线才发现卡不够。

第一步,确定最坏情况下的视觉 token 数。把业务里遇到的最高分辨率和最多图片张数代入前面的公式,得到N_v_max。

第二步,算总显存需求:

总显存 ≈ 权重大小 + 视觉塔开销 + KV_cache(N_v_max + N_text + N_out) + 激活峰值

权重大小按精度算,bf16 是参数量 × 2 字节,int8 减半,int4 再减半。激活峰值在推理时主要是中间层输出,7B 模型大致 2 到 3 GB。留 20% 余量。

第三步,估延迟。视觉编码时间t_v加上生成长度N_out × t_per_token。t_per_token在 7B 模型单卡上通常是个位数毫秒。关键是把t_v单独拎出来做异步处理,见 7.3 节。

举个完整的例子:13B 模型、int8 量化、单图 1024 视觉 token、输出 200 token、批大小 4。权重 13 GB,视觉塔 3 GB,KV cache 单条约 0.5 GB × 4 = 2 GB,激活 3 GB,合计约 21 GB,留余量后建议 24 GB 起步。延迟方面,视觉编码约 80 ms,生成约 200 × 8 ms = 1.6 s,单条总延迟约 1.7 s。

6.3 微调还是直接用

这个问题被问得极多,我的判断标准是看领域偏移程度。

如果业务图像和模型训练数据的分布接近(普通照片、常见文档),直接用加上 prompt 工程就够,微调的收益很有限,还可能引入过拟合。

如果存在明显的领域特性(特定类型的仪表、特殊排版的表单、行业术语密集的图表),微调是必要的。但要注意,微调的目标应该是让模型适应视觉分布,而不是让它记住答案。数据构造上,同一张图要配多个不同角度的问答对,避免模型把图像和固定回答绑定。

微调方式上,优先考虑只训投影层,冻结其余部分。这条路径的显存需求低、训练稳定、不容易破坏语言能力,在数据量不大的时候效果往往好过全参数微调。数据量上千条以后再考虑加 LoRA,全参数微调放到最后再考虑。

7. 实操:搭一条最小的多模态推理链路

7.1 环境与依赖

这一节给一份可以照着改的骨架。为了避免绑定具体实现,我用抽象接口的写法,你替换成手头框架的对应 API 即可。

核心依赖只有三块:负责视觉编码的模型加载、负责语言生成的推理引擎、以及中间的图像预处理。预处理这一步经常被低估,它包含了 resize、归一化、切块、patch 重排,任何一步和模型训练时的处理不一致,效果都会断崖式下跌。

import torch from PIL import Image class VisionTower: def __init__(self, model_path, device="cuda"): self.model = load_vision_encoder(model_path).to(device).eval() self.device = device @torch.inference_mode() def encode(self, pixel_values): # pixel_values: [B, 3, H, W],已经按训练时的 mean/std 归一化 feats = self.model(pixel_values, output_hidden_states=True) # 取倒数第二层,通常比最后一层保留更多空间细节 return feats.hidden_states[-2]

视觉编码器取哪一层的输出,是个值得掰扯的细节。很多教程默认用最后一层,但实测下来,倒数第二层在需要空间细节的任务(小字、区域定位)上表现更稳,因为最后一层经过更多抽象,空间信息被压缩得比较狠。这个结论不是绝对的,建议自己拿两张图对比一下。

7.2 视觉预处理与 token 拼接

预处理的核心是保证"训练和推理同分布"。下面这段是拼接逻辑的骨架:

def build_inputs(image, text, projector, tower, tokenizer, config): # 1. 预处理:resize 到 config.image_size,保持长宽比补边 pixel_values = preprocess(image, config.image_size, config.image_mean, config.image_std) # 2. 视觉编码 -> [1, N_v, d_v] vision_feats = tower.encode(pixel_values) # 3. 投影对齐 -> [1, N_v, d_l] vision_embeds = projector(vision_feats) # 4. 文本部分 text_ids = tokenizer(text, return_tensors="pt").input_ids text_embeds = language_model.get_input_embeddings()(text_ids) # 5. 拼接:视觉在前,文本在后 inputs_embeds = torch.cat([vision_embeds, text_embeds], dim=1) # 6. attention mask 全 1,位置 id 连续生成 attention_mask = torch.ones(inputs_embeds.shape[:2], dtype=torch.long) position_ids = torch.arange(inputs_embeds.shape[1]).unsqueeze(0) return inputs_embeds, attention_mask, position_ids

这里有三个容易出错的地方,我按踩坑频率排个序。

第一,视觉占位符的位置。上面的写法是视觉 token 全部在前,简单直接。但很多模型的训练数据里,图像 token 是嵌在文本中间的("下面这张图描述了什么"),推理时就必须把视觉特征插到对应的占位符位置,而不是简单拼在最前面。拼错位置的后果很隐蔽——模型能跑,输出也像模像样,但准确率系统性偏低。

第二,补齐方式。保持长宽比补边(letterbox)和直接拉伸(resize)是两种不同策略,必须和训练时一致。用错了会让模型对形变特别敏感,横竖比例失衡的图直接失效。

第三,位置 id 的生成方式。有些实现在拼接后重新从 0 开始编号,有些延续文本的编号,还有的给视觉 token 和文本 token 用独立的编号空间。这个细节不同模型差别很大,一定要去翻它的训练代码,别凭感觉写。

7.3 批量推理与显存优化

单条能跑通之后,下一步是批量化。多模态的批处理比纯文本麻烦,因为每张图的视觉 token 数可能不一样,尤其是动态分辨率的方案。

最省事的做法是 batch 内按最长补齐,但会浪费算力。更实用的做法是按视觉 token 数分桶,把相近长度的请求分到同一个 batch,能显著减少 padding 浪费。实测下来,分桶策略在混合分辨率流量里能提升三到四成吞吐。

几个降显存的实用手段,按性价比排序:

  • 视觉 token 池化:对视觉特征做 2×2 平均池化,token 数直接减到四分之一。代价是细节损失,实测在自然图像问答上掉点不明显,但在小字识别上影响较大。建议文档类任务别用,通用问答可以用。
  • 视觉编码异步化:把tower.encode放到独立的线程池或者单独的推理服务里,提前把图像编码成特征缓存起来。生成阶段只做投影和语言解码,这样前置等待被藏在业务排队时间里,用户感知的延迟能降三到五成。
  • 量化:int8 基本无损,能省一半权重显存。int4 要看具体实现,视觉相关的层建议保留 int8,激进量化容易让视觉特征失真,表现出来就是"答得挺流畅但答非所问"。
  • KV cache 分页:长序列场景必须上,不然碎片化会吃掉大量显存。

注意:做视觉 token 池化的时候,池化的边界要和 patch 的边界对齐。我见过实现里 stride 设错,导致特征图错位半格,模型能跑但答案全乱,排查了大半天才定位到。

7.4 效果验收的自建小测试集

不要只看榜单。上线前自己造一个 50 到 100 条的小测试集,覆盖四类样本:典型样本、边界样本(极暗、极亮、严重倾斜)、对抗样本(图文矛盾、图中文字和问题答案相反)、拒答样本(图里根本没有答案)。

验收指标建议至少三个:准确率(答对与否)、接地率(回答中提到的物体在图上是否真的存在)、拒答正确率(该说"图里没有"的时候有没有胡说)。

接地率这个指标特别值得单列,因为它是多模态模型最典型的失败模式——语言模型部分太强,会凭常识编出图里不存在的东西。测试方法是问"图中有没有 X",X 用随机物体替代,看模型是否稳定地说没有。

8. 常见问题与排查速查

8.1 高频故障对照表

现象最可能原因排查动作
输出通顺但与图无关投影层对齐不足 / 视觉占位符位置错检查是否用了对齐阶段的权重,核对占位符位置
小字识别大量出错分辨率不足或 token 压缩过度提高输入分辨率,关闭池化,改取视觉塔倒数第二层
多图问答张冠李戴缺少图像分隔标记在每张图前后插入边界 token,检查位置 id 是否区分
首字延迟高视觉编码串行阻塞改异步预编码,或用独立服务缓存视觉特征
长对话后期崩坏位置编码外推失效减少视觉 token,改位置插值策略,缩短上下文
批量推理 OOM视觉 token 未分桶,padding 浪费按 token 数分桶,限制单批最大 token 总量
微调后语言能力下降学习率过高或视觉数据占比过大降学习率一个数量级,掺入纯文本数据
答得流畅但细节全错量化过度关掉 int4,视觉相关层保留 int8

8.2 几个踩过的坑

说几个表里装不下、但实际很耽误事的经验。

坑一:预处理参数从别处抄来的。不同模型训练时的归一化均值方差、resize 插值方式(双线性还是双三次)、补边颜色都可能不同。抄错了不会报错,只会让准确率莫名其妙低几个点,而且极难归因。建议把预处理参数直接从模型自带的配置文件里读,别手写常量。

坑二:把视觉 token 数当成固定值。动态分辨率方案下,视觉 token 数是输入的函数。有些推理代码里硬编码了一个最大值来预分配 KV cache,遇到超长图就 silently 截断,输出看着正常但漏了关键信息。上线前一定要打印实际 token 数做核对。

坑三:忽略图像方向信息。手机拍的照片会带 EXIF 旋转标记,很多预处理库默认不处理,喂进去的图是躺倒的。模型对旋转图的鲁棒性比想象中差,尤其是文字。加一行 EXIF 校正,能省掉一堆莫名其妙的工单。

坑四:拿单图经验推多图场景。单图跑得好不代表多图能跑。多图场景下,除了序列变长,还多了一层"指代消解"——模型要在多组视觉特征之间建立对应关系。如果架构本身不支持,靠 prompt 里的"图一""图二"编号是补不回来的,实测准确率会掉一大截。

坑五:忘了给拒绝样本。训练数据里如果全是"有问必答",模型会形成强迫症,图里没有的东西也要编一个。掺入 3% 到 5% 的"图中无此信息"样本,拒答能力会有明显改善,而且不影响其他能力。

最后分享一个我常用的调试手段:遇到效果不对,先别改模型,把视觉特征反解回图像看一眼。做法是把投影后的视觉嵌入做一次最近邻匹配,看看它和原图的哪些 patch 最相关。如果注意力分布明显偏移或者塌缩到少数几个位置,问题大概率出在对齐阶段,而不是语言模型。这个方法比盲目调参效率高得多,尤其是在排查"模型为什么看错了地方"这类问题时。

这两个路线的边界其实还在移动,原生多模态方案在模糊单流和双流的传统分野,视觉 token 的高效压缩方案也在不断改写显存账的算法。但只要你抓住"视觉信息以什么代价、在什么位置进入语言主干"这条主线,新出来的方案看一眼结构图基本就能判断它的取舍。这套判断框架,比记住任何一个具体模型的名字都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询