
最近带一个多模态项目正好走到“视觉表达SFT”这一步。我一开始以为这不过是给多模态大模型做一次普通的有监督微调——把图像特征接进语言模型训几步就完事。真正跑完一轮实验之后才发现这个阶段才是决定模型“看着一张图能不能说出人话”的关键分水岭。很多项目卡在感知模块做得很强、但模型总是答非所问或者对着图片编瞎话问题往往就出在这段SFT没做好。这篇文章就围绕视觉表达SFTVisual 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张量里置为-100PyTorch的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学习率LoRA1e-4 到 2e-4LoRA更新参数少感知力更钝需要更高lrbeta(0.9, 0.999)AdamW默认值即可weight_decay0 到 0.05数据量大可偏向0.05warmup_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.x1000步左右会降到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个epochloss降到了0.4看起来非常漂亮但模型输出变得短促、模板化甚至出现“图片中有一只蓝色的猫”这种确定性错误的输出。后来我查文献和社区讨论发现多epoch训练图文指令数据很容易导致模型对训练集过拟合产生幻觉。把epoch拉回1再补一点负样本数据评测分数反而上来了。根据我个人的经验视觉表达SFT这项工作真正花时间的往往不是模型结构而是数据配比和mask这些“看不见”的细节。很多人总想找更高级的网络结构、更大的模型但在这个阶段老老实实把数据质量提上去、把训练状态盯住效果提升反而最明显。最后再分享一个小技巧每次训练前先花十分钟做一次“单batch sanity test”只用一个batch的数据跑一次前向和反向确保loss能正常下降、梯度没有NaN。这个小动作能提前挡住一大批因为环境、模板、数据类型不匹配导致的低级错误比训练到一半再排查省太多了。把这轮SFT做好之后你再回头看那套“只会感知、不会说话”的多模态底座会发现它真的开始“说出人话”了——这个跨越就是从视觉表达SFT开始的。