做了这么多年大模型相关的底层工作,我越来越觉得“多模态架构”这四个字被严重标签化了。很多人以为把图像编码器、文本编码器、大语言模型拼接在一起,再训一个投影层就是多模态,其实真正的底层架构设计远没有这么简单。今天想把自己这些年拆解、复现和改造各类多模态模型的经验整理出来,重点聊聊底层架构的组件逻辑、设计取舍和工程落地中的坑,希望给正在做相关方向的同学一些参考。
先说明一下,这篇文章不打算逐行贴代码,而是聚焦于“架构为什么这样设计”“每个模块到底在解决什么”以及“实操中你会卡在哪里”。适合已经跑通单模态模型、开始接触多模态训练或部署的开发者,也适合想系统理解多模态底层原理的研究者。
1. 为什么需要重新理解底层架构
1.1 多模态不是“拼积木”
先破一个常见误区。很多初接触多模态的同学会觉得,既然大语言模型能处理文本,视觉模型能处理图像,那把两者拼在一起不就行了吗?实际操作过就知道,这种“拼积木”思路几乎必然失败。原因在于,文本和图像在底层的数据分布、特征密度、语义粒度上完全不是一个量级。
文本是离散符号,一个token承载的语义高度浓缩,“苹果”两个字就能唤起大量常识关联;图像是连续像素,一个16x16的patch可能只包含一片纹理、一个边缘,语义信息极其稀疏。让两种特性完全不同的模态直接共享参数或简单拼接,模型根本学不到稳定的跨模态对应关系。我见过不少人把CLIP式的双塔编码器和LLM硬拼,训练出来模型既不会看图说话,也不会图文检索,原因就是没有在底层把“对齐”和“融合”这两个任务分开处理。
所以多模态模型的底层架构,核心要解决三个问题:不同模态如何表示、不同模态如何对齐、对齐之后如何融合生成。这三个问题不是串联关系,而是互相影响。你选什么样的tokenizer,决定了编码器的输入形态;你选什么样的对齐方式,决定了融合模块的结构;你选什么样的融合策略,决定了Loss设计。底层架构的每一层选择都在连锁影响后续效果。
1.2 从单模态到多模态的核心矛盾
单模态模型的任务相对纯粹:文本模型学习语言规律,视觉模型学习图像特征。多模态模型则要面对一个根本性矛盾——模态异质性。
模态异质性体现在两个层面:
- 特征空间不同:文本特征在语义空间里分布紧凑且高度抽象,图像特征在视觉空间里分布稀疏且包含大量冗余细节。直接拼接特征向量会导致模型分不清哪些维度来自哪种模态。
- 信息粒度不同:一个文本token可能对应画面中多个视觉对象,一张图切成几百个patch后,每个patch的信息量和token不在同一语义层。
解决这个问题需要一套统一表示层。要么把图像也切成离散token,变成模型能“读”的序列;要么设计一个可学习的对齐模块,把连续视觉特征映射到文本语义空间。当前主流方案的共同趋势是:把一切模态都转化为序列,用Transformer的自注意力机制统一处理。
这就解释了为什么几乎所有多模态大模型都长着Transformer的骨架——不是因为Transformer是万能药,而是因为它天然支持变长序列、支持不同模态token混排,而且注意力机制可以动态建模任意两个token之间的关系,正是跨模态对齐需要的特性。
2. 底层架构的四个核心组件
2.1 Token化:不同模态怎么变成统一语言
tokenizer是整个多模态架构的地基。文本侧已经比较成熟,BPE或SentencePiece把文本切成子词单元,词表一般在3万到10万之间。但视觉和音频的token化,不同方案差异很大。
图像侧最常见的做法是patch embedding:把一张224x224的图切成14x14或16x16的小块,每个patch经过卷积或线性投影变成一个向量。这个思路来自ViT,本质上是把“空间位置”当成“序列位置”来处理。patch size的选择很关键——patch太大丢失细节,patch太小序列长度爆炸。我自己的经验是:对于通用视觉任务,16x16是性价比比较高的选择;如果任务对细粒度要求高,比如OCR、医疗影像,需要把patch缩到8x8,但序列长度会变成原来的4倍,注意力计算量指数上升。
视频tokenization复杂得多,通常的做法是均匀采样帧,然后每帧独立走图像patch化,最后把所有帧的patch展平成一个超长序列。这里有个隐性成本:一段10秒、30fps的视频就是300帧,每帧切成196个patch,序列长度接近6万,直接喂给Transformer必然显存爆炸。所以实际工程中要么降低采样帧率到1~2fps,要么用局部注意力限制感受野。
音频的tokenization也很值得说。原始音频波形是1维连续信号,采样率动辄16000Hz以上。直接学波形不现实,主流做法是先计算梅尔频谱图,把音频变成2维“伪图像”,然后再用类似图像patch的方式切块。另一个路线是神经音频编解码器,把音频压成离散code,让模型像处理文本一样处理音频token。两种路线各有优劣,频谱路线信息连续、适合生成任务,离散code路线天然适配后续的大语言模型输入。
无论哪种模态,token化之后都要加上位置编码。不同模态的位置编码策略可以直接复用Transformer的sinusoidal位置编码,也可以引入可学习的相对位置编码,处理多模态混排时尤其重要——模型需要知道“这个视觉token在第几帧的哪个位置”“这个文本token在整个句子中的哪个地方”。
2.2 编码器:特征提取的差异与统一
token化解决的是输入形态问题,编码器解决的是特征质量问题。不同模态的编码器结构差异很大,但有一个共同设计准则:让编码器输出尽量适配后续对齐模块的需求。
视觉编码器的主流方案是ViT或它的变体。ViT把每个patch embedding经过层层自注意力,逐步全局建模。实际训练时视觉编码器通常在大规模纯图像数据上预训练,比如对比学习或掩码重建,然后再在多模态阶段保持冻结或部分微调。为什么要冻结?因为视觉编码器参数量巨大,全量微调一方面容易灾难性遗忘纯视觉能力,另一方面训练成本极高。很多多模态模型选择冻结视觉塔,只训练对齐层和语言模型,效果依然很好,这说明视觉特征的通用性确实可以通过预训练固化下来。
文本编码器则要看整体架构定位。如果是双塔对齐模型,文本侧可以用独立的BERT式编码器,输出句子级特征向量用于对比学习;如果是以语言模型为主导的生成式多模态模型,文本侧往往直接使用大语言模型本身的embedding层和Transformer层,不再单独设计文本编码器。
音频编码器通常是一个小型的卷积堆叠网络加Transformer层,先把频谱图降维压缩,再提取序列特征。音频的特征维度比视觉少一个空间轴,所以网络可以更轻。
编码器层数的选择也有讲究。很多人迷信“层数越深越好”,但多模态模型中编码器不是越深越好。视觉特征经过过深的网络后容易语义化,丢失细节,而这些细节正是后续对齐需要的。我倾向于视觉塔用12层左右,文本塔和语言模型保持原有结构,在对齐模块里多做文章。
2.3 跨模态对齐与融合模块
这是多模态架构最核心、也最容易被低估的部分。
跨模态对齐的本质,是让不同模态的特征在同一个语义空间里可比、可映射。早期做法很简单:把图像特征和文本特征分别过一层线性变换,映射到同一个维度空间,然后用余弦相似度衡量图文匹配度。这个思路成就了CLIP这类双塔模型。它的底层逻辑是“对比学习”:在海量图文对中,把匹配的图文特征拉近,把不匹配的推远。
但双塔对齐有一个天然缺陷:图像和文本在编码过程中完全没有交互,对齐只发生在最后的特征向量层面。对于检索任务够用,对于生成任务远远不够。于是衍生出了两个融合方向。
一个是交叉注意力融合。在Transformer解码器的每一层,文本token的query去attend视觉token的key和value。这样文本生成时每一步都能动态“看”图像信息。这个方案的代表是Flamingo结构,它在冻结的视觉塔和语言模型之间插入一组门控交叉注意力层,效果好,但训练和推理的计算开销非常大。
另一个方向是统一特征空间融合,典型代表是Q-Former这类可训练query机制。Q-Former用一组固定的可学习query向量,通过交叉注意力从视觉特征中“提取”关键信息,输出一组长度固定的小特征序列,再输入语言模型。这相当于先做一次“视觉信息压缩摘要”,把几百个视觉token压缩成32个或64个query。好处是后续输入语言模型的序列短、算得快,坏处是压缩过程可能丢失细节。
我自己在做项目时发现,融合模块的设计要在“信息保留”和“序列长度”之间找平衡。视觉token动辄几百上千,直接塞进语言模型会拖慢整个生成过程;压得太狠又会让模型“看不清图”。一个相对实用的折中是:视觉塔输出保持高分辨率特征,融合模块里先做一次组卷积或平均池化降采样,再用交叉注意力对齐。这也算是在工程限制下对理论设计的妥协。
2.4 输出解码与生成头
多模态模型的输出侧取决于任务类型。如果做图文检索,输出侧就是对比学习的特征向量;如果做图像描述、视觉问答,输出侧就是语言模型的文本生成头;如果做图像生成,输出侧则要换成扩散模型或离散编解码器。
文本生成头不需要过多介绍,就是标准的大语言模型softmax层。但有几个细节值得注意。第一,多模态模型的词表最好在预训练阶段就固定,不要在后期随意扩展,否则视觉token或特殊token的embedding会扰乱原本学好的词向量分布。第二,如果模型要支持视觉定位或目标检测类输出,需要额外添加box回归头或坐标token,这类输出头和文本生成头的联合训练需要小心调节Loss权重。
图像生成侧要区分两类架构:一类是把图像token离散化后用自回归方式生成,另一类是文本特征作为条件输入扩散模型。前者架构上更“统一”,后者生成质量通常更高。我见过不少团队想一套架构通吃理解与生成,实际做下来往往两头不讨好。自己的经验是:理解任务用自回归语言模型架构,生成任务单独接扩散头,接口处用特征对齐而不是参数共享,调起来更顺手。
3. 三条主流架构路线对比
3.1 双塔对比式:以对齐为核心目标
双塔架构的代表是CLIP这样的模型。视觉塔负责提取图像特征,文本塔负责提取文本特征,两个塔的最后一层输出投影到同一向量空间,训练目标就是最大化匹配图文对的余弦相似度、最小化不匹配对的相似度。
这条路线最大的优势是简单、高效、易于扩展。训练完成后图像编码器可以直接作为通用视觉特征提取器,用在检索、分类、检测等各种下游任务。文本塔的特征也可以用于零样本分类或图文匹配。而且双塔结构天然支持向量化检索——把所有图像特征预先离线算好存进向量数据库,线上文本查询直接比对,毫秒级返回结果。
缺点也很明显:两塔之间没有深层交互,模型只能判断“图文是否匹配”,无法完成“看到图之后理解并生成描述”这类复杂任务。如果想改进,可以在双塔之上额外加融合层,这就变成下面的路线。
3.2 单塔融合式:以交互为核心目标
单塔融合式模型把图像token和文本token拼成一个序列,一起送入一个大Transformer网络。所有token在每一层都参与全局注意力,模态间的交互从第一层就开始。代表的近期工作包括各种“all-in-one”Transformer模型。
这种设计的优点是信息交互深,模型能学到细粒度的跨模态关系。比如要回答“图片左边桌子上的杯子是什么颜色”,全局注意力可以把“左边”这个位置信息和“杯子”这个语义信息显式关联起来。缺点是计算量巨大,而且图像token和文本token数量不平衡时,注意力会被视觉token淹没,导致文本生成质量下降。实际操作中我倾向在注意力矩阵上加模态掩码:文本token可以attend全部视觉token,视觉token初始几层只attend同模态token,训练稳定后再放开限制。
3.3 以语言模型为中心的统一模型
这是目前最主流的路线,结构上可以理解为“视觉编码器加投影层加大语言模型”。视觉编码器把图像变成特征,投影层把特征映射到语言模型的输入空间,语言模型作为通用推理引擎完成所有任务。
这条路线最大的聪明之处在于:语言模型本身已经具备强大的世界知识和推理能力,多模态训练只需要教会它“理解视觉输入”,不需要重新学习语言生成。所以训练成本可以大幅降低,也容易继承已有的单模态大模型能力。冻结视觉塔、只训练投影层和少量LoRA,往往就能达到不错的效果。
但这里藏着一个底层假设:视觉特征能够被语言模型“读懂”。如果投影层只是简单的线性层,视觉信息就会被压缩得很厉害,模型只能抓住全局语义而丢失细节。所以我一般建议至少用两层的MLP或小型Transformer作为投影层,给视觉特征足够的非线性变换空间。另外,如果后续要支持视频或高分辨率图像,投影层还需要内建时序或位置信息处理能力,不能简单逐帧映射。
三条路线并不是互斥的。业界实际产品中经常是混合使用:底层用双塔做召回,中间用融合模型做精排,最后用大语言模型做生成。理解每条路线的边界,能帮助你在具体场景中选型而不是盲目跟风。
4. 训练流程与关键Loss设计要点
4.1 预训练数据组织
多模态预训练的数据整理比模型本身更影响最终效果。图文对数据的质量直接决定对齐能力上限。
实际操作中,爬取到的图文对噪声极大。很多图片的alt文本是关键词堆砌而非描述性句子,有些图文根本无关。我习惯用三层过滤管线:
- 第一层硬过滤:去掉过短文本、非图像文件、尺寸过小的图。
- 第二层质量过滤:用已训练好的单模态检索模型计算图文相似度,保留相似度超过某一阈值的样本。CLIP score是常用指标,阈值一般设置在0.2到0.3之间,具体要看数据分布。
- 第三层语义过滤:用文本分类器剔除色情、暴力、广告等低质内容。
还有一个容易忽略的细节:数据重复率。很多公开数据集内部有大量重复图片,直接训练会过拟合这些重复样本。去重可以用感知哈希或embedding聚类,虽然耗时,但对最终效果提升明显。
4.2 关键Loss的设计原理
多模态训练的Loss通常不是单一目标,而是多个任务叠加。
对比学习Loss是最基础的对齐目标。以InfoNCE为例,对于一个batch内的N个图文对,匹配对是正样本,batch内其他N-1对都是负样本。Loss函数让正样本的相似度尽量高,同时让负样本的相似度尽量低。这里有个关键技巧:温度系数。温度越低,对难负样本的惩罚越强,模型越倾向于拉大正负样本差距,但过低会导致训练不稳定甚至崩溃。我在训练中通常从0.07起步,CLIP论文中也有类似的取值,然后根据loss曲线微调。
掩码语言建模Loss在某些多模态模型中承担“通过视觉信息补全文本”的作用。给文本token随机掩码一部分,模型需要根据图像和其他文本token预测被掩盖的词。这个任务能强制模型把视觉信息用于语言理解,是早期多模态预训练的标准做法。但对现阶段的LLM为中心架构,这个Loss的作用已经弱化,更多被生成式Loss替代。
生成式Loss就是标准的自回归语言建模Loss,让模型根据图像和已有文本生成下一个词。这是以LLM为中心模型的主Loss。它的梯度会同时回传到语言模型、投影层,甚至视觉编码器(如果没冻结的话),所以训练稳定性需要格外关注。
多任务Loss组合时,权重设置很关键。一般对比Loss和生成Loss的比值在1:10到1:100之间,生成任务为主、对比任务辅助。如果发现模型能匹配图文但生成质量差,说明生成Loss权重过小;如果检索能力一直上不去,则要适当增加对比Loss权重或加大负样本数量。
4.3 训练稳定性经验
多模态训练的显存占用比纯文本模型高一个量级,这是所有实践者的共同痛点。以下几点是我的实战经验:
- 梯度检查点几乎是必须开的。用激活值换显存,虽然会增慢约30%的训练速度,但能把batch size提上去,整体收敛速度反而更快。
- 混合精度训练中,注意loss spike。多模态模型训练时loss spike比单模态更频繁,通常是因为某些难样本产生极大梯度。我习惯对梯度范数做裁剪,阈值设在1.0左右,同时监控梯度范数的滑动平均值,一旦超过平时的3倍就降低学习率。
- 视觉编码器解冻时机:先冻结视觉塔,只训练投影层和语言模型,等loss下降稳定后再解冻视觉塔并设置更小的学习率,能有效避免早期震荡。
5. 工程部署与推理优化
5.1 模型压缩与算子优化
训练好的多模态模型部署到线上时,体积和时延往往不达标。压缩手段优先级建议:蒸馏优于量化优于剪枝。
蒸馏的方案是拿大模型当teacher,训练一个小模型去模仿teacher的输出分布。多模态场景下可以分阶段蒸馏:先蒸馏视觉编码器,再蒸馏融合模块,最后蒸馏语言模型。逐段蒸馏的好处是每段任务简单、小模型更易收敛。
量化则需要区别对待不同模块。视觉编码器的卷积和线性层对量化误差相对鲁棒,可以直接做PTQ(训练后量化)到8bit。注意力计算中的矩阵乘对量化误差更敏感,建议保留FP16或使用混合量化。语言模型部分的量化可以参考单模态LLM的成熟方案。
部署优化中一个非常实用的技巧是算子融合。把Attention中的QKV三个矩阵乘法合并成一个大矩阵乘法,把LayerNorm和线性层融合到上一个算子中,能显著减少kernel启动开销。在多模态场景下,视觉塔的推理可以和文本的前几个token计算并行化,利用CUDA graph减少CPU和GPU之间的同步等待。
5.2 服务化架构要点
多模态推理服务最大的挑战是视觉输入预处理的耗时。一张图经过解码、缩放、patch化、ViT编码,可能耗时几百毫秒,而文本生成的每个token只需要几十毫秒。直接同步处理会导致整体时延不可接受。
我的做法是拆成两段式服务:
- 视觉预处理服务:接收图片后立即编码,把视觉特征缓存起来。
- 语言生成服务:用户输入文本时,直接复用缓存的视觉特征,只需要跑投影层和语言模型。
如果视觉特征可以离线预计算,比如用户提前上传图片建立相册,那效果更明显——查询阶段几乎零视觉开销,只剩文本生成。这个设计本质上就是把“动态看图文交互”变成了“预计算加查询”,成本和体验都优化了。
另外,多模态推理的批处理策略和纯文本不同。视觉token数量远多于文本token,prefill阶段(处理输入token生成KV cache)的计算量集中在视觉侧,decode阶段(逐token生成)则主要在语言模型侧。所以调度时可以先把多个请求的视觉特征打包做一次prefill,再进入语言模型decode阶段复用批处理能力。
6. 常见问题与排查技巧实录
6.1 常见问题速查表
我把这些年踩过的坑和常见问题整理成一个速查表,按症状、可能原因、排查和解决思路排列:
| 症状 | 可能原因 | 排查与解决 |
|---|---|---|
| 对比学习loss不降 | 温度系数过大导致梯度平滑 | 调低温度至0.05~0.1,增负样本数 |
| 图像细节识别错误 | patch_size过大或视觉token被过度压缩 | 减小patch,或增强融合模块信息保留 |
| 训练loss spike频繁 | 难负样本产生大梯度 | 梯度裁剪加学习率预热,解冻视觉塔延后 |
| 模型只会复述文本不看图 | 生成Loss权重过高、视觉信息未参与生成 | 增加跨模态注意力或要求多模态训练数据 |
| 推理时显存OOM | 图像token序列过长 | 降低输入分辨率、降采样视觉token、启用KV cache复用 |
| 量化后检索精度骤降 | 视觉塔对量化过于敏感 | 视觉塔保留FP16,只量化语言模型线性层 |
| 部署时prefill时延高 | 视觉特征未缓存 | 改两段式服务,视觉特征离线预计算 |
| 图文匹配但生成乱序 | 位置编码策略不当 | 检查视觉token位置编码,补充模态类型编码 |
6.2 避坑心得
最后分享几条没有写在论文里的心得。
第一,多模态模型的失败往往不是架构问题,而是数据问题。训练不收敛,先检查数据:图文对是否对齐、文本是否太短、图像是否重复。我建议在训练集上做一次抽样人工检查,花一小时看两百条样本的“图像加文本”,比调一周参数都有效。数据噪声导致的bad case是最难通过调参解决的,只能回到数据清理。
第二,别迷信单一大模型的“全知全能”。以LLM为中心的多模态模型确实强,但它不擅长细粒度感知。遇到目标检测、关键点定位类任务,单独接一个专门的视觉头,比让语言模型硬学坐标输出稳定得多。架构上的“混合”不是耻辱,而是工程务实。
第三,多模态模型的评测要定制Prompt模板。同样一张图,换个Prompt措辞,模型表现可能天差地别。评测时至少用5到10套不同Prompt取结果做投票或均值,才能反映模型真实能力。我自己吃过不少亏,测试阶段只用了单一Prompt,上线后用户一问就崩,换了几套模板后才发现是Prompt敏感度的问题。
第四,永远保留一个单模态能力回归测试集。多模态模型迭代过程中,语言能力或视觉能力可能悄悄退化。每次训练后除了跑多模态评测,还要跑纯文本问答和纯图像分类的测试。退化通常发生在冻结策略调整之后,早发现就能早回滚。
从我自己的实操经验来看,多模态架构的底层设计没有银弹,每一条路线都是在算力、效果、延迟之间作取舍。理解每个组件背后的设计动因,比你记住某个知名模型的结构更重要——因为结构会过时,取舍逻辑不会。如果你正在纠结自己的多模态模型怎么改,先回到数据层面看看问题,再审视对齐模块的粒度是否匹配你的任务需求,往往比盲目换更大的模型更有效。