ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

视觉表达SFT实战:多模态大模型从感知到表达的微调关键

视觉表达SFT实战:多模态大模型从感知到表达的微调关键

最近带一个多模态项目,正好走到“视觉表达SFT”这一步。我一开始以为这不过是给多模态大模型做一次普通的有监督微调——把图像特征接进语言模型,训几步就完事。真正跑完一轮实验之后才发现,这个阶段才是决定模型“看着一张图能不能说出人话”的关键分水岭。很多项目卡在感知模块做得很强、但模型总是答非所问或者对着图片编瞎话,问题往往就出在这段SFT没做好。这篇文章就围绕视觉表达SFT(Visual Expression SFT)这个阶段,讲清楚它在训练流水线里的位置、数据怎么组织、mask和loss怎么写、超参怎么调、坑在哪里,最后附一份排查表和实操笔记。

如果你正在复现某个图文对话大模型,或者想把自己手里的视觉编码器和LLM拼成一个能看图说话的完整系统,这篇文章可以直接当参考手册用。

1. 视觉表达SFT在整条训练链路里的准确位置

1.1 训练流水线的那“三站”

我习惯把多模态大模型的训练拆成三站:视觉编码器预训练、特征对齐、视觉表达SFT。后面可能还有RLHF或DPO,但那是“锦上添花”,前面这三站是刚需。

视觉编码器预训练这件事通常不需要你亲自干。开源的视觉塔已经能提取丰富的图像特征,比如分类、检测、分割这类“感知级”任务做得很成熟。你拿到手的是一个已经能看懂图像内容的encoder,它输出的是图像patch级别的特征序列。

第二站是特征对齐。这时候LLM是冻结的,只训练一个projector(线性层或者小型MLP),把视觉特征从视觉塔的特征空间映射到LLM的词向量空间。这一站的目标很纯粹:让图像特征“放得进”LLM的输入通道。数据用图文对或者简单的图+描述就行,几十万条足够。注意,这一站训练完,模型虽然能把图像embedding送进去,但问它“图里有什么”,它大概率答不出像样的内容。

第三站就是视觉表达SFT。这时通常把整个模型(或大部分参数)解冻,用高质量图文指令对话数据,端到端微调。目标不再是“让图像特征进得去”,而是“让LLM基于图像特征组织出自然语言回答”。这一站训练完,模型才真正具备了看图说话、视觉推理、按指令描述图像的能力。

我见过不少半路接手的人,以为特征对齐做了就等于多模态训练做完了,直接拿去上线,结果发现模型对图像内容的理解非常浅,只会输出模板式回答。原因很简单:特征对齐只建立了通路,没有培养表达能力。

1.2 为什么说“感知不等于会表达”

感知和认知之间的鸿沟,其实就在“能不能把视觉证据转化为语言行为”。

视觉塔可以告诉你“这张图里有一个人、一辆车、一棵树”,但这只是感知。要回答“这个人是在过马路还是在等车”“这辆车是什么颜色”“画面里最显眼的东西是什么”,需要的是认知——也就是把视觉信息放进语言模型已有的知识结构里,组织成一句完整、准确、符合指令的回答。

这种能力不是视觉塔给的,也不是projector给的,是视觉表达SFT阶段用图文指令数据教出来的。

我做过一个对比实验:同样一个训练好的视觉塔和LLM,只做对齐,不做过任何表达SFT,给它一张“一只猫趴在窗台上”的图,问它“图里有什么”,它输出的是“这是一个很漂亮的场景,里面有很美的风景和可爱的动物”这种泛泛而谈的话;做完视觉表达SFT之后,同一个问题,它输出的是“一只橘白相间的猫趴在木质窗台上,窗外能看到绿色的树叶”,细节和准确性天差地别。

所以当你看到某个多模态模型开源了“第二阶段SFT数据”,不要以为那只是“随便找点对话数据再训一轮”,那一步本质上是把视觉特征翻译成语言的“母语习得”过程。

2. 数据与输入模板:表达SFT真正在学习的是什么

2.1 一条训练样本的完整结构

视觉表达SFT的训练数据,每一条通常是“图+问题+答案”的三元组,但在模型眼里它是一段连续的token序列。

以常见的模板为例,一条样本大概长这样:

  • 系统提示:You are a helpful assistant.
  • 用户轮次:USER: 请描述这张图片的内容。
  • 助手轮次:ASSISTANT: 画面中有一只金毛犬坐在草地上,旁边有一个红色皮球,背景是公园的树林。

其中是一个特殊占位符,在数据预处理阶段会被替换成视觉塔输出的图像特征token序列。图像token的位置通常放在用户问题之前,紧挨着文本问题,这样LLM在处理问题时,视觉上下文就近在眼前,注意力不需要跨越太长距离去关联图像信息。

实际操作中还会有一个细节:很多人把“系统提示”里的内容一并作为输入,但不同模板的生成效果差异很大。我实测下来,固定一个模板并在整个训练和推理流程中保持一致,比频繁换模板重要得多。训练时用A模板、推理时用B模板,哪怕只是系统提示差了几个词,生成质量都会明显下降。这不是玄学,而是模型已经把模板内的语气、格式要求和真实任务绑定在一起了。

2.2 mask策略与损失计算:该算的算,不该算的不算

视觉表达SFT的损失函数,本质上就是标准的自回归交叉熵,但关键在于哪些token参与loss计算。

正确的做法是:系统提示、用户问题、图像token这三部分都不计算loss,只有assistant的回答部分参与反向传播。

代码层面就是把不参与loss的token在label张量里置为-100(PyTorch的CrossEntropyLoss默认忽略-100位置)。很多初学者容易犯的错误是只做了attention mask,忘了处理label,结果图像token也被当成预测目标。

一旦图像token被算进loss,模型就会学着“预测”图像token本身。我见过一个复现案例,训练loss一路下降,但推理时模型疯狂输出一堆标签,像复读机一样停不下来。查了半天才发现是label没mask干净。

为什么assistant以外的内容都不该参与loss?逻辑很简单:系统提示和用户问题都是模型“看到”的输入,不是它“应该生成”的内容。让模型去学习复述用户问题,除了增加训练噪音,没有任何收益。

还有一个争议点:是否把用户问题里的部分token也算进loss?有些实现会把整个序列都算交叉熵,只在图像token上跳过,但我建议不要这样。那样会让模型同时学习“像用户一样问问题”和“像助手一样回答问题”,两个目标的噪声会互相干扰。视觉表达SFT阶段的任务边界应该是清晰的:只教模型生成高质量的回答。

2.3 负样本与反事实数据:提前治幻觉

只喂“图-答-好答案”的数据,容易养出一个很隐蔽的毛病:模型学会了“看起来像在回答问题”,但并没有真正依赖图像证据。问它图片里没有的东西,它也能编一个出来。

要提前防治,就得靠数据层面的“负样本”设计。

我常用的负样本策略有三种。第一种,视觉锚点缺失样本:问题问的是图片里不存在的信息,标准答案写成“图片中没有显示相关信息”或“无法从图片中确定”。第二种,错配样本:给一张猫的图,问“图中是什么动物”,但配对答案写成“狗”——这种数据单独用会教坏模型,但如果控制好比例(比如总量的3%-5%),再配合大量正样本,反而会逼着模型学会“从图像里找证据”。第三种,反事实样本:同一张图配多个问题,其中一个问题的答案需要推理,另一个问题的答案直接引用图中信息,让模型区分“看到了什么”和“根据图片能推断出什么”。

这里要特别提醒:负样本不是越多越好。我做过一个试验,把负样本比例拉到20%,模型确实变得非常谨慎,但发展到草木皆兵,连图像里明明有物体都回答“无法确定”。我最终把负样本比例控制在5%左右,幻觉率下降明显,正常回答率基本不受影响。

3. 数据配比、清洗和难度控制:决定SFT效果上限的关键

3.1 图文指令数据的“五层”配比

很多人以为视觉表达SFT只需要“看图说话”数据,其实不够。我按任务难度把数据分成五层,训练时按不同比例混用。

第一层是简短描述类,就是给一张图配一句话描述,这类数据教模型“看到什么说什么”,是表达能力的基础。第二层是详细描述类,要求模型输出多段、结构化的描述,包括物体位置、颜色、数量、关系、氛围等,这类数据提升模型的细节敏感度。第三层是视觉问答类,单轮问答,覆盖物体识别、属性判断、空间关系、动作判断这些常见visual reasoning任务。第四层是推理与知识类,回答需要结合图像和世界知识,比如“这张图里的人在做什么准备工作,下一步最可能干什么”。第五层是文档与图表类,包括OCR、表格理解、图表描述、屏幕截图理解,这类数据对提升真实场景落地能力特别有用。

这五层的配比,我常用的大致是15%、15%、40%、20%、10%。但实际用的时候要看你项目的目标场景来调。如果是做文档理解产品,第五层比例要拉到30%以上;如果是做通用对话助手,推理类可以稍微降一点。

数据量方面,社区里常见的图文指令数据规模在150K到600K之间。单卡训练通常要跑几十个小时,如果你只有几个小时训练时间,建议把数据压缩到50K-100K的高质量子集,而不是拿全量数据跑到一半没跑完。

3.2 去重、清洗与难度调节

图文指令数据的质量参差不齐,哪怕是从开源社区拿到的数据,也需要自己清洗。第一个必做的是图片去重。很多数据集里同一张图会出现在多条样本里,如果答案又高度雷同,模型会对这些图严重过拟合,到了新图上却表现平平。用感知哈希结合embedding相似度做去重,重复度控制在5%以内比较稳。

第二个是问题侧清洗。重点过滤两类问题:一类是问题本身存在“视觉上无法回答”的漏洞,比如问“图中穿红衣服的女人在笑吗”,但图片里根本没有穿红衣服的女人;另一类是问题把答案写在里面了,比如“图片中是一只猫吗?请回答是或不是”,这种问题信息量低,模型学不到东西。

第三个是答案侧清洗。答案里如果出现乱码、表情符号、过长的重复token,直接删掉。还有一类“伪高质量”答案:看起来很流畅,但和图片内容对不上,这种是最难清洗的,只能靠抽检人工判断。

难度调节方面,我的一条经验是:数据难度要呈现一个缓坡,而不是陡坡。前20%应该是简单描述和物体识别,中间50%是普通问答,最后30%是推理和开放性描述。如果一上来全是开放问题,模型在训练初期很难建立“图文对应”的基本盘,后期再补也补不扎实。

3.3 纯文本数据的混入比例

视觉表达SFT一个很容易被忽略的点:纯文本对话/指令数据也要混一部分。

为什么?因为你的LLM底座虽然本来就会聊天,但在这个阶段你拿图文数据从头微调它,模型会把“多模态对话”当成唯一任务,原来的纯语言能力会被覆盖掉一部分。混入纯文本数据的作用就是“保温”:让模型不要忘了语言模型的本分,保持表达的多样性和知识性。

纯文本数据的比例,我建议控制在总量的一成到两成之间。太高了会稀释视觉能力,太低了就是我现在这个项目踩过的坑——只加了5%纯文本,训完发现模型对纯文本问题的回答质量明显下降,语言风格也变得生硬。后来把纯文本比例提到15%,情况才恢复正常。

4. 训练实操:超参数、多卡配置与收敛判断

4.1 一页纸超参清单

视觉表达SFT的超参在不同模型规模和数据量下会有浮动,但我可以给一份“先跑起来”的默认配置,很多开源项目的复现都能用这个配置直接跑通。

参数推荐值说明
优化器AdamW几乎成了微调的事实标准
学习率(全参)2e-5 到 5e-5全参数微调建议别超过5e-5
学习率(LoRA)1e-4 到 2e-4LoRA更新参数少,感知力更钝,需要更高lr
beta(0.9, 0.999)AdamW默认值即可
weight_decay0 到 0.05数据量大可偏向0.05
warmup_ratio3%(约200步)稳定训练初期,防止震荡
batch_size128 到 256显存不足时用gradient accumulation凑够
epoch1 到 2多轮过拟合幻觉会变多
精度bf16视觉模型常用bf16续训稳定
图像分辨率336或448更高分辨率提升小物体和OCR能力,但显存翻倍

一个比较容易踩的坑是batch size。视觉表达SFT的样本之间差异很大,如果batch size太小(比如8),每个batch的梯度方向会非常嘈杂,训练曲线抖得厉害。我建议至少让effective batch size到128,用梯度累积也行。batch size上去之后,你会发现同样的学习率下训练更稳。

4.2 训练中真正该盯的监控指标

训练loss当然要看,但它不是最重要的指标。

我跑视觉表达SFT时,训练loss一般在刚开始是2.x,1000步左右会降到1.2-1.5之间,训练结束时会落在0.6-0.9附近。如果你的loss在0.5以下还一直降,要警惕是不是数据太简单或者模型开始死记硬背了。

比loss更重要的是“生成抽检”。训练过程中每隔一定步数保存checkpoint,然后用一张训练时没见过的图片,跑几个固定问题,肉眼看模型输出有没有随训练推进而变化。我自己的流程是每500步保存一次,每1000步做一次抽检。抽检的问题格式必须和推理时完全一致,不能临时换prompt。

还有一个值得盯的指标是“图像信息利用率”。你可以准备一组“图像内有无答案”的对偶问题:同一个问题,分别用一张包含答案的图和一张不包含答案的图去问。训练得好的模型,在第二张图上应该能回答“无法确定”,而不是强行给个答案。如果发现模型对两张图输出几乎一样,说明它根本没在“看”图,只是在套用语言模式。

4.3 全参微调、LoRA与视觉塔冻结,怎么组合

视觉表达SFT阶段,参数更新范围有几种选择,我按实用程度排了个序。

最省资源的方案是“LoRA + 解冻projector”。视觉塔完全冻结,LLM只训练LoRA低秩适配器再叠加projector。这个方案在单卡24GB显存上就能训7B级别的模型,效果也不错,适合快速验证数据质量。

中间方案是“全参微调LLM,冻结视觉塔”。这种配置适合资源中等的情况,LLM的完整参数参与更新,但视觉塔不动,训练速度比LoRA慢,但表达能力通常更强。

最彻底但风险也最大的方案是“全参微调LLM + 解冻视觉塔后半层”。这能获得最好的上限,但训练不稳定、显存开销大,而且视觉塔一解冻,整个模型的收敛速度会变慢很多,学习率必须压得更低。

我个人的建议是:第一版实验用LoRA方案把数据流程跑通,确认效果之后再升级到全参微调。不要一上来就全参加解冻视觉塔,一旦出了问题你很难判断是数据问题还是训练稳定性问题。

5. 快速验证与典型故障:别让loss骗了你

5.1 十分钟快速验证集

训练结束后的效果验证,建议准备一个固定的小测试集,覆盖六类能力:物体描述、属性判断(颜色/大小/材质)、空间关系、OCR识别、简单推理、否定回答。每类3-5条,总共20条左右。

推理时用统一的模板,把模型输出收集起来后挨个打分,重点看四件事:内容是否和图像吻合、细节是否具体、语言是否通顺、是否出现“无法确定”这类合理的拒绝回答。这个测试集不追求覆盖面,追求的是快速暴露问题。训练完一轮,跑一遍这个测试集,大约十分钟就能判断这轮训练是否值得继续。

更自动化一点的做法是拿开源的多模态benchmark做批量评测,但benchmark分数高不代表落地好。比如某些benchmark对OCR和图表题权重很高,你专门去刷这类数据分数自然好看,但模型在普通图片上的描述能力可能反而退化了。所以我始终建议:benchmark当参考,固定测试集当把关。

5.2 幻觉、空答、复读三种病怎么排查

幻觉是最常见的症状,模型会描述图片里不存在的东西。排查思路很直接:拿一张纯色图片(纯白或纯灰),问模型“图里有什么”。如果它还说出一堆物体,说明它已经在靠语言惯性输出而不是靠图像证据。这时候检查两件事,一是负样本比例是否不足,二是训练数据里是否大量存在“答案是通用短语”的样本。

空答是第二种常见病,模型输出“我无法回答这个问题”或者干脆输出空字符串。先确认是不是模板出了问题,导致模型分不清用户问题和助手回答的分隔符;再检查训练数据里有没有太多缺少答案的样本。另外一个隐藏原因:推理时如果用贪婪解码但训练时模型学会了“拒绝回答”,也可能表现为空答,这种情况要回到数据配比去调。

复读是指模型输出里大量重复同一个词或短语。如果发生在训练中后期,很可能是学习率过高或训练数据里存在大量重复样本。先把学习率降回2e-5,再看数据去重结果。如果复读集中在图像token附近,几乎可以断定是mask没做对。

6. 常见问题速查表与实操笔记

6.1 一张排查表解决大多数训练事故

我把实操过程中遇到的高频问题整理成一张表,训练出问题的时候逐行对照,大多数情况能快速定位。

症状可能原因排查与处理
输出大量重复图像tokenlabel没有正确mask,图像token参与了loss计算检查label张量中图像token位置是否置为-100
完全无视图像内容视觉塔与LLM空间未对齐,或projector没有参与训练确认特征对齐阶段收敛,检查projector是否被误冻结
幻觉严重,编造图中没有的物体负样本不足增加“图中无此信息”类样本,控制在5%左右
训练loss很低但新图效果差多epoch过拟合或训练图与评测图重复检查数据去重,尝试减少epoch
语言能力退化,回答生硬纯文本数据混入比例不足提高纯文本数据比例到15%左右
输出空话套话,缺乏细节数据集中详细描述类样本太少增加多段描述类数据的占比
多轮对话断裂,答非所问训练数据缺少多轮样本加入真正的多轮图文对话数据
训练中期loss突然飙升学习率过高或数据噪声大降低学习率,检查最近batch的数据质量
OCR或小物体识别差图片分辨率不够或训练数据缺文档/OCR类提高输入分辨率,或增加高分辨率图像输入

6.2 三个我踩过的坑,希望你绕开

第一个坑是我最开始做视觉表达SFT时踩的:mask写错。我当时只给文本token做了attention mask,没处理label,结果模型训练时把图像token也在当预测目标学。训练结束跑推理,模型一开口就是连续几十个图像占位符,看起来像输出了一片乱码。这个坑排查了很久,最后还是逐token打log才定位到。从那以后我习惯在训练脚本里加一行断言:检查label中图像token位置是否全部为-100。

第二个坑是数据配比失衡。我第一次只用了简短描述类数据,觉得“能描述就够了”,结果训练完模型对细节问题(颜色、数量、位置)几乎是一问三不知。后来我在数据里加入了详细描述和空间关系类样本,又调整了比例,效果才明显改善。现在我做任何视觉表达SFT项目,第一件事就是先把数据分类统计一遍,五层配比各占多少、有没有哪类完全缺失。

第三个坑最反直觉:我把一个很小的子集数据反复训了3个epoch,loss降到了0.4,看起来非常漂亮,但模型输出变得短促、模板化,甚至出现“图片中有一只蓝色的猫”这种确定性错误的输出。后来我查文献和社区讨论,发现多epoch训练图文指令数据很容易导致模型对训练集过拟合,产生幻觉。把epoch拉回1,再补一点负样本数据,评测分数反而上来了。

根据我个人的经验,视觉表达SFT这项工作,真正花时间的往往不是模型结构,而是数据配比和mask这些“看不见”的细节。很多人总想找更高级的网络结构、更大的模型,但在这个阶段,老老实实把数据质量提上去、把训练状态盯住,效果提升反而最明显。

最后再分享一个小技巧:每次训练前先花十分钟做一次“单batch sanity test”,只用一个batch的数据跑一次前向和反向,确保loss能正常下降、梯度没有NaN。这个小动作能提前挡住一大批因为环境、模板、数据类型不匹配导致的低级错误,比训练到一半再排查省太多了。把这轮SFT做好之后,你再回头看那套“只会感知、不会说话”的多模态底座,会发现它真的开始“说出人话”了——这个跨越就是从视觉表达SFT开始的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进