如果你正在做多模态预训练,大概率会遇到一种奇怪的现象:训练 loss 一直在降,zero-shot 准确率却涨得很慢,甚至停滞很久。你会怀疑是学习率不对,于是调低学习率,还是不动;你会怀疑是数据量不够,于是继续加数据,结果变化依然有限。这时候,真正该检查的往往不是优化器,而是知识有没有在模态之间流动起来。
最近在看多模态预训练相关工作的时候,有一个标题给我留下了很深印象:它没有直接提出一种新架构,而是试图用四个概念——知识流动、模态协同、早期统一、配方——把多模态预训练里那些“只说得出感觉、说不清机制”的问题统一起来。一开始我觉得这只是写作上的包装,后来用这四个词去回看自己的训练经验,发现确实能解释很多以前只能归咎于“玄学”的现象。
这篇文章我想围绕这四个方向展开,但不会面面俱到地去罗列模型对比。我更想把它当作一套分析框架,来回答一个具体的问题:当你训练一个多模态模型时,为什么有些模型长期不温不火,另一些模型结构相似,却能在跨模态任务上表现更好?差异到底发生在哪一层?
这会是一篇偏方法论和经验判断的文章,不是模型源码逐行讲解。如果你正准备从零开始做一个图文预训练项目,或者已经在训练过程中反复调不动,这篇文章应该能帮你把问题定位到更准确的环节。
1. 多模态预训练的“黑箱”,先从知识流动说起
1.1 知识流动不是泛泛而谈,而是有方向、有路径、有损耗的信息迁移
多模态预训练最常见的范式,是拿海量的“图像-文本对”,让模型把图像和文本映射到同一个语义空间。公开论文里非常经典的实现方式就是对比学习:图像编码器抽出一批图像特征,文本编码器抽出一批文本特征,正样本对在空间里尽量靠近,负样本对尽量远离。训练结束之后,我们默认两个模态的表示已经“对齐”了。
但“对齐”只是最终目标,不是训练过程的机制。真正值得问的是:梯度信号是怎么从文本侧流到图像侧,又从图像侧流回文本侧的?哪些层承担了语义对齐?哪些层只是在各自模态内部做特征提取?
如果你把多模态 Transformer 看成一个信息网络,知识流动就是一条有方向的路径。在跨模态 attention 层,文本 token 可以读到图像 token 的表示,图像 token 也能读到文本 token 的表示。这个交互发生在浅层,语义信息会更早混合;如果发生在深层,两个模态的编码器基本就是各跑各的,最后在某个融合层做一次“见面”。
更关键的是,知识流动是有损耗的。文本侧如果“太强”,图像侧特征可能被当成无关噪声;图像侧如果“太强”,文本的抽象语义又会变得模糊。这种损耗不会直接显示在 loss 曲线上,但会在下游任务上暴露出来。
所以,我理解标题里“物理”这个词,想表达的并不是物理定律,而是多模态训练背后那些稳定、可观察、可验证的规律。你不需要像物理学家一样推导公式,但至少要知道知识在模型内部是从哪里流向哪里,又在哪里可能断掉。
1.2 怎么判断知识真的流动了:不只是看 loss
判断知识有没有真正流动,单看训练 loss 是不够的。loss 下降只能说明当前训练目标在被优化,不能说明两个模态形成了有效的交互。我一般会按顺序做四件事:
- 看 zero-shot 是否对模态敏感。固定图像侧,换一批文本;固定文本侧,换一批图像。如果无论怎么换,模型输出都没有明显变化,说明知识流动很可能已经中断。
- 做一次“模态置换”实验。把图像替换成随机噪声,看文本输出是否仍然“看起来合理”。如果依然合理,而且性能几乎不掉,说明模型已经退化成依赖文本路径的模型,视觉信息没有被真正利用。
- 反过来做一次。把所有文本替换成占位符,看视觉识别任务是否仍然正常。如果视觉性能大幅下降,说明跨模态知识没有回流到视觉侧,图像编码器并没有因为引入了文本而变得更强。
- 观察两个编码器的梯度 norm。对比学习训练中,如果视觉侧梯度长期接近零,说明视觉编码器基本停了,模型只是在用文本侧的表征“硬凑”结果。
这些实验都很简单,但能帮你区分一个关键问题:你的模型是在协同处理两个模态,还是只在其中一个模态上做“单模态拟合”。
1.3 知识流动断裂的常见信号
下面这几种情况,在实际训练中非常常见:
- 信号一:loss 持续下降,但 zero-shot 不涨。这通常意味着模型学到的不是语义理解,而是在匹配表层统计特征。比如图像和文本里都经常出现某个固定颜色,模型可能就直接用颜色当作匹配信号。
- 信号二:单模态下游任务提升巨大,跨模态任务毫无进展。这说明两个模态各自都有不错表征,但共享空间没有形成。
- 信号三:继续加数据,效果没有改善。如果知识流动路径本身有问题,更多数据只会固化错误的通路。
遇到这些信号,我的建议是先不要急着加 batch size,也不要马上换模型。先做上面那组“模态置换实验”,确认是哪一侧知识断了,再决定修哪条链路。知识如果没有流动,加数据只是放大噪声;流动方向不对,换更大的模型只会更快跑偏。
2. 模态协同的核心问题:谁来主导,谁在补充,谁在退化
2.1 对比学习与生成式统一:两种协同哲学的差异
模态协同,听起来像是一个很正面的词,好像两个模态天然应该互相帮助。但实际训练里,协同关系非常脆弱。不同训练范式的协同方式也不一样。
对比学习本质上是让两个模态在共享空间里互相“校准”。它修的是双向高速路:图像往文本方向走,文本往图像方向走,两者靠近就奖励,远离就惩罚。这种方式的优点是稳定,结构简单,负样本设计得当就能学到不错的匹配关系。缺点是它只能学“相关性”,很难学“因果性”。模型知道哪张图和哪句话经常一起出现,但不知道一句话之所以成立,是因为图像里那个关键物体提供了底层证据。
生成式统一则是另一套哲学。常见的做法是把图像转换成离散 token,用文本模型去预测或重建这些 token,或者反过来。它的协同方式更密:模型每预测一个 token,都在用另一个模态的信息做条件。有人会觉得这更接近“真正理解”,但它也有代价:图像 tokenization 会损失细节,生成目标可能让模型过度关注低频显式语义,忽略视觉上不明显但很关键的信息。
打个比方:对比学习像是两个人共享一份共同笔记,每个人用自己的语言把笔记补充完整;生成式统一像是一个人要求另一个人按照自己的语法复述刚才看到的一切。前者容易取得共识,但共识可能很浅;后者容易暴露理解漏洞,但训练不稳定。
2.2 模态塌缩:强模态把弱模态“吞噬”了
模态协同里最容易出现的问题,是模态塌缩。这不是训练崩溃,而是模型找到一个偷懒解:主要依赖信息更密集、更容易预测的模态,忽略另一个模态。
在图文预训练里,被忽略的往往是视觉侧。原因是文本往往包含更显式的语义,一个句子通常直白地描述主体、动作、颜色、关系;而图像,尤其是网络爬取的小图、模糊图、抽象图,信息提取难度更高。模型在训练中发现,只要把文本路径走好,loss 就能降下来,于是视觉编码器的梯度越来越少,图像特征几乎不再变化。
模态塌缩的典型表现:
- 视觉编码器梯度 norm 长期偏小,甚至几乎不动。
- 在图文检索任务里,把图像换成噪声,检索结果没有明显变化。
- 图像侧单模态评估指标涨得很慢,文本侧却一路高歌。
根本原因通常不在权重初始化,而在于数据对太弱、负样本太难或太简单、对比损失权重失衡、温度参数不合适。模型在训练中会自动选择“低垂果实”,哪一个模态更容易拟合,就把哪个模态当作主要依据。
2.3 用权重、数据配比和“换模实验”调节协同关系
想让两个模态保持协同,而不是一方被压制,需要做一些显式干预。
第一,不要让单模态监督信号消失。可以在对比 loss 之外,给两个模态各自加一点单模态任务。比如图像侧加一个掩码图像重建,文本侧加一个掩码语言建模。这样,即使对比分支里某一侧梯度变弱,模型仍然有动力维护该模态的表征质量。
第二,调整数据配比。不是所有 image-text pair 难度都一样。如果数据里大量是“一张蓝天图配一句蓝天白云”这种极其直白的对子,模型很容易只看文本,忽略图像细节。尽量让图像分辨率、清晰度、物体复杂度,以及文本长度、表达风格、语义粒度都有足够的分布宽度。这样模型才会被迫在两个模态之间来回寻找信息。
第三,把温度参数当成一个需要观察的对象,而不是一次设好就不管。对比学习里的温度会影响负样本的区分难度。温度太小,模型会过度关注难负样本,训练震荡;温度太大,负样本区分度过低,两个模态特征会被“拉成一团”。我习惯在训练初期把温度设得略高,等 loss 稳定后再逐步降低,让负样本的压力随训练逐步增加。
我常用下面这张表做协同问题的快速诊断:
| 现象 | 可能原因 | 先查什么 |
|---|---|---|
| 图像换成随机噪声,结果几乎不变 | 视觉路径被忽略 | 视觉 encoder 梯度、图像数据质量、对比 loss 权重 |
| 文本换成随机词,结果几乎不变 | 文本路径被忽略 | 文本 encoder 梯度、文本数据多样性 |
| 两个模态单测都很好,跨模态任务差 | 共享空间没有形成 | 投影头结构、负样本质量、温度参数 |
| 训练震荡,loss 反复跳 | 温度或学习率不合适 | 初始温度、warmup、梯度范数 |
这张表背后其实是一个更通用的方法:先确认哪一侧在工作,再决定改哪一块。不要一上来就把所有超参都重调一遍,那样你永远不知道真正的原因是什么。
3. Early Unification 不是“越早越好”,而是一次风险换收益的选择
3.1 Early Fusion 与 Late Fusion 的本质区别
在讨论多模态架构时,我们常听到 Early Fusion 和 Late Fusion。这组概念听起来像是一种编码方式,但从训练视角看,它更像一个“时机选择”:两个模态的信息到底在模型的哪个阶段开始交互。
Late Fusion 的做法是:每个模态先独立编码,最后在高层的融合层做交互。图像编码器只管图像,文本编码器只管文本,最后把两个表示拼在一起或做一个 attention。优点是稳定,各个模块可以独立调试,出问题时容易定位。缺点也很明显:信息交互的时机太晚,视觉细节和文本语义往往很难对上。
Early Fusion 的做法是:在模型输入阶段就把视觉 token 和文本 token 拼接在一起,一起送进同一个 Transformer。从第一层开始,两种模态就会相互影响。早期统一的好处在于,跨模态交互可以发生在前几次 attention 里,模型有更多机会学到跨模态的组合特征,而不只是最后做一次浅层匹配。
但这里必须澄清一个容易混淆的点:很多公开的多模态模型,并不是真正的 Early Fusion。有的是在视觉编码器后面接一个 connector,再把输出送入语言模型;有的是用 cross-attention 把视觉信息“注入”文本生成过程。这些方案都算融合,但交互深度不同。真正意义上的早期统一,要求两种模态的 token 在最底层就进入同一个骨干网络,共享同一套 query、key、value 映射。
3.2 早期统一的好处:知识可以更早地交叉回流
早期统一最吸引人的地方,是让知识在模型能力边界处就互相“开会”,而不是等到最后加一个决策头去缝合。
举个例子。如果视觉 token 和文本 token 从第一层就一起进入 Transformer,那文本的 attention 可以在很浅的层次就“看到”图像区域。模型不再需要等深层编码器把视觉信息抽象成高级语义后才去匹配文本,而是在特征还是边缘、颜色、纹理时就开始交互。这对细粒度对齐非常有帮助,比如“红色的汽车”这种概念,文本侧知道红色,视觉侧看到红色区域,两个信号可以在浅层直接建立连接。
早期统一还有一个容易被忽略的好处:它能让文本信息回流到视觉编码器。对比学习里,视觉 encoder 只知道图像和文本是否匹配,但不知道文本具体说了什么;早期统一让视觉 token 直接接收文本 token 的影响,视觉表征会学到更多语言能描述但纯视觉任务不关心的语义维度。
对于中小规模的模型,这种深度交互通常能显著提升表示质量。但对于超大规模模型,早期统一也不是越早越好。它相当于把两种异源信息丢给同一个优化器去解耦,优化难度会明显上升。
3.3 成本与风险:稳定性和可调试性下降
早期统一的代价,最直接地体现在训练稳定性和可调试性上。
如果你没有用预训练好的单模态编码器初始化,两个模态一起从头训练,大概率会跑出“两个模态都不像样”的结果。这个阶段,图像 token 和文本 token 在语义空间上完全没有对齐关系,网络需要同时解决两个完全不同域的表征,优化器会非常忙碌,loss 曲线会表现出明显的高方差。
显存开销也不可忽视。从底层开始保留两种模态的激活,对长序列和超大分辨率非常不友好。如果你要同时处理高分辨率图像和长文本,早期统一很容易遇到显存瓶颈,进而被迫减小 batch size,而 batch size 又是对比学习效果的重要变量之一。
更麻烦的是调试体验变差。Late Fusion 出问题时,你可以单测图像 encoder、单测文本 encoder、检查融合层,快速定位是哪一段坏了。Early Fusion 一旦训练效果不好,你会不太容易回答“这个问题到底出在视觉侧、文本侧,还是融合阶段”。排查范围一下子变大了。
3.4 实操建议:先 Late 后 Early
我的建议是:如果你是第一次做多模态预训练,先把 Late Fusion 跑通。验证数据链路、loss、评估流程都稳定之后,再考虑切换到早期统一。
切换时一次只改一个变量。比如先把文本 token 接入层从最后一层换到倒数第二层,观察效果;稳定后再逐步深入,不要一步从 Late Fusion 直接跳到最底层接入。这样一旦出现性能回退,你能大概知道问题发生的位置。
同时,一定要保留一份单模态 checkpoint。早期统一可能提升跨模态能力,也可能让单模态能力下降或遗忘。如果只记录最终多模态指标,你会以为效果变好了,但实际可能只是模型借助文本信息把测试集刷上去了,视觉编码器本身的表征已经退化了。
不要为了“显得先进”直接选早期统一。先看你的基线是否稳定。如果 Late Fusion 都跑得不稳定,Early Fusion 只会把问题函数放大。
4. Recipes:把每个训练决策变成可复现的实验记录
4.1 为什么很多多模态训练经验更像“手艺活”
多模态训练的高方差,来自大量隐性变量:数据顺序、负采样分布、学习率调度、温度参数、初始化、batch 组合、梯度裁剪、评估阶段。论文里通常只报告一组最终配置,但同样的配置在不同数据、不同初始化下,结果可能差很多。
这也是“recipe”这个说法成立的原因。它把经验从“感觉”变成“记录”,从个人记忆变成团队资产。如果你今天用一组参数跑出了好结果,但没有完整记录数据版本、模型配置、优化参数和评估策略,那这组结果对团队没有任何复现价值。
更现实的情况是,同一个项目、不同的人来跑,结果可能差距很大。原因不是代码写得不一样,而是训练多模态模型的“手感”掌握在少数人手里。把配方显式化,是一种把“手感”转化为工程能力的方式。
4.2 一个最小可复现配方包含哪些内容
一个完整可复现的多模态训练配方,至少包含四块:数据配方、模型配方、优化配方、评估配方。
数据配方要记录数据来源、清洗规则、图像文本对的过滤条件。比如字幕长度过滤、图像分辨率下限、宽高比范围、去重阈值,以及训练集和验证集的划分方式。验证集绝对不能和训练集共享同一数据流,否则你看到的指标全是幻觉。
模型配方要记录图像编码器结构、文本编码器结构、投影头层数和维度、温度参数初始值。更细节的还包括位置编码方式、是否冻结某些模块、归一化层放在哪里。
优化配方要记录优化器、学习率、权重衰减、warmup、batch size、梯度累积步数、混合精度策略,以及损失函数中每个任务项的权重。
评估配方要记录每隔多少步跑一次评估、选择哪些 zero-shot 任务、保存策略是 best checkpoint 还是 last checkpoint、最终采用什么指标。
我整理过一个最小配置表的常见取值范围,注意这是实践参考,不是标准答案:
| 项目 | 常见取值范围 | 说明 |
|---|---|---|
| 图像编码器 | ViT-B/32 起步 | 大容量适合大数据,小容量适合快速验证 |
| 文本编码器 | Transformer 6~12 层 | 取决于文本长度和任务复杂度 |
| 投影头 | MLP 1~3 层,输出 256~1024 维 | 不是越深越好,先浅后深 |
| 对比温度 τ | 初始 0.07 左右 | 需要随训练调整,过大过小都会影响结果 |
| batch size | 对比学习常见 4096 以上 | 小 batch 也能跑,但需要更多步数 |
| 学习率 | 1e-4 ~ 1e-3 区间起步 | 需要按 batch size 缩放 |
| warmup | 1000~5000 步 | 避免早期动力学失稳 |
如果你想快速验证一个想法,可以先用一个最小的脚本把链路跑通。对比学习训练循环的伪代码结构通常是这样的:
# 一个示例性的对比学习训练循环(伪代码) for images, texts in dataloader: img_feat = image_encoder(images) # [B, D] txt_feat = text_encoder(texts) # [B, D] img_feat = l2_normalize(projector(img_feat)) txt_feat = l2_normalize(projector(txt_feat)) logits = img_feat @ txt_feat.T / tau # [B, B] labels = arange(B) loss = cross_entropy(logits, labels) loss.backward() optimizer.step()这段代码看起来很简单,但真正影响结果的往往在代码之外:负样本怎么构成、温度怎么更新、batch 里图像文本是否均匀、评估集是否稳定。这些才是配方里最容易被忽略的部分。
4.3 从单卡到多卡:稳定性验证与日志记录
很多人拿到一套多模态训练代码,第一件事就是直接把 batch size 拉大,机器越多越好。我见过太多项目在单卡上正常,一上多卡就出问题。
正确的顺序应该是:
- 先在单卡上用很小的 batch size 跑 1000 步左右,确认 loss 可以下降、梯度范数合理、投影头输出没有 NaN。
- 再切到多卡,保持 batch size 不变,仅仅增加设备数,确认 loss 曲线和单卡时保持一致。如果有明显偏差,先检查数据加载、随机种子和梯度同步。
- 在 batch size 真正扩大之后再调整学习率。学习率缩放规则有很多种,linear scaling 和 sqrt scaling 会给出不同建议,但公认要点是:batch size 变大,学习率通常也要变大,但不能无限制变大。
- 整个过程中,日志至少需要记录 step、loss、learning_rate、temperature、grad_norm、当前评估指标和数据 index 版本。
只记录 loss 不记录 grad_norm,排查问题时会缺少关键证据。很多训练异常会先表现在梯度上,然后才表现在 loss 上。梯度突然消失或爆炸,往往比 loss 突变更早出现。
4.4 排查链路:训练 Loss 下降但效果不涨,按什么顺序排查
如果你遇到“loss 正常但效果不涨”的经典情况,我建议按下面顺序排查:
- 先看评估链路。是不是评估代码加载了旧模型?数据加载是否出错?类别顺序是否和训练时不一致?这步虽然基础,但最容易浪费时间。先排除工具链,否则后面所有排查都在错误的地基上。
- 再做“换模实验”。把图像或文本替换成随机输入,看模型表现是否退化。如果模型对某侧输入的变化不敏感,说明该模态没有在预测中发挥真实作用。
- 再检查数据质量。随机抽 1000 条 image-text pair,人眼检查配对语义是否一致。对比学习对负样本分布极度敏感。如果负样本太简单,模型不需要理解语义就能区分;如果负样本太难,loss 下降也不代表正样本排在前面。
- 再检查优化参数。温度 τ、负样本数量、batch size 是否合理。如果 batch size 只有 256,一个 batch 里只有 256 个负样本,对比学习的区分能力很有限。
- 最后再看模型容量和结构。如果以上都没问题,才考虑是不是视觉编码器太弱、投影头太浅、融合时机太晚或太早。
这个排查链路的核心原则是:先确认问题的位置,再决定改什么。不要因为效果不涨就同时调整数据、模型、loss 和优化器。那样就算最后效果变好了,你也无法判断是哪个因素起了作用。
5. 从复现基线到建立自己的评估闭环
5.1 先复现,再改动:一次一条变量的纪律
多模态预训练非常容易让人产生“想同时验证很多新想法”的冲动。今天想换一个更大的视觉编码器,明天想改损失函数,后天又想引入一个新的生成式辅助任务。如果所有改动同时进行,出了问题时,你会完全不知道是哪一个模块出了错。
更稳妥的做法是:先复现一个公开基线,记录它在你环境里的指标和训练曲线。这次复现不要跳步,保持和原论文一致的优化器、学习率、batch size、数据预处理。哪怕复现结果不完美,也记录下差异。然后从基线出发,每次只改一个变量。
比如想验证“可学习温度”是否有用,先保持数据、模型、优化器全部不动,只把固定温度改成可学习温度,观察结果变化。想验证早融合是否有用,也只先做架构改动,不碰数据配比和损失权重。这样每次实验结果都能形成一条清晰的因果证据。
5.2 建立自己的 Mini-Eval 集
只盯着公开测试集,很容易被不稳定的评估噪声误导。公开测试集覆盖场景广,但未必代表你真实业务里最关心的数据分布。我一般会建议团队准备一个 100 到 1000 条的 mini-eval 集,专门衡量自己场景下的关键能力。
这个评估集不需要很大,但必须稳定、有区分度。它应该包含多模态的常规样本、单模态样本、常见的噪声场景,以及一些长尾或难例。每次改动模型后,都用同一评估集跑一组指标,形成历史曲线。
Mini-eval 集还有一个隐蔽的好处:它可以帮助你判断模型是不是“假涨”。比如某个改进让公开测试集涨了 2 个点,但在你的 mini-eval 上反而跌了,这说明改进可能过度拟合了公开测试集,而不是真的提升了多模态理解能力。
5.3 什么阶段该自己预训练,什么阶段直接用开源模型
这里要写清楚边界。对大多数团队来说,我其实不建议从零训练大型多模态模型。算力只是一部分,更大的成本在于数据清洗、训练框架、评估体系、运维监控和排错能力。这些都不是一两个工程师短期内能补齐的。
如果业务只需要通用图文理解、检索或内容审核,直接使用开源的多模态基础模型,再用 LoRA、Adapter 或其他参数高效微调手段适配到自己的领域,通常更现实。这样你能把大部分精力放在数据质量和业务评估上,而不是训练稳定性上。
只有当下面几种情况出现时,才值得考虑自己从零预训练:
- 你需要的模态组合比较特殊,例如雷达图、遥感影像、医学图像,和通用自然图像差异过大;
- 你的业务语言非常特殊,领域术语密集,公开预训练数据覆盖不足;
- 你本身就是研究预训练机制、想做算法改进,需要在一个可控环境下验证自己的假设。
这时候,前面说的配方思想就非常关键。它让从零训练不再只是拼灵感和算力,而是变成可追踪、可复盘、可在下一轮训练中迭代的工程流程。
多模态预训练的未来,大概率不是某个模型突然横空出世,而是越来越多团队能把自己的训练过程写成一份长周期、可复现、可迭代的 recipe。技术会落后于新模型,但一套好的实验方法,会持续帮你在新模型出现时更快定位它的价值。
回到开头那个问题:loss 下降但效果不涨,怎么办?
现在我的回答是:先别急着调参。先看知识流动的方向,确认两个模态都在起作用;再看模态协同的平衡,有没有模态塌缩;再看统一时机的选择是不是和你的数据、算力条件匹配;最后把每次训练都记录成一份完整配方。这四个问题,几乎是任何一个多模态预训练项目从“能跑”走向“可控”的必经之路。
物理很难,但至少我们可以先把一部分“玄学”变成工程。