ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于LoRA的Qwen-VL多模态微调实战:从数据到部署的最小闭环

基于LoRA的Qwen-VL多模态微调实战:从数据到部署的最小闭环 简介一套聚焦Qwen-VL视觉语言模型的高效微调实战资源包面向AI开发者、算法工程师及多模态方向研究生解决如何在有限算力下用Lora分层适应机制优化模型参数、提升跨模态任务表现的问题。包内共105个文件、约32.3MB以Python脚本、Jupyter Notebook、Markdown笔记和图片/演示图为主体并附带模型配置文件、评估工具与参数模板可覆盖数据预处理、分层梯度更新到多维度评估的完整链路。已有231人学习下载适合需要结合源码复现、对照参数配置进行二次开发的进阶用户。资源内含可运行的Notebook演示、效果Gif、Python实施脚本与备份文件并配有参数配置模板和性能基准测试工具使用者可对照源码逐段理解数据预处理、分层梯度更新与多维度评估的实现细节同时工程目录保留中间备份便于排查优化过程中的问题也能将这套流程迁移到其他多模态任务中。1. 基于 Lora 的 Qwen-VL 多模态大模型微调实战为什么一套最小闭环值得先跑基于Lora的Qwen-VL多模态大模型微调听起来是个重量级工程拆开却只干一件事把视觉语言模型的主干冻结在注意力层旁加上低秩适配矩阵用少量标注数据教会模型识别你业务里的图像和问题。我第一次做这个组合是为了给一批商品截图做属性抽取全参微调在双卡 A100 上勉强起步换成 LoRA 后训练显存降到三分之一单卡 24G 就能稳住效果反而更稳。这套流程适合要做视觉问答、图文检索或图像分类落地但 GPU 预算不宽裕的团队也适合第一次碰多模态微调的个人开发者。2. 环境搭建与 LoRA 原理一张 24G 卡能跑的最小配置2.1 硬件与依赖先把显存账算明白Qwen-VL-Chat 是 9B 参数级别的视觉语言底座结构上由 CLIP ViT 视觉编码器、resampler 和 Qwen 语言模型三块组成。很多人以为 LoRA 微调就是“参数少了所以显存一定小”其实只对了一半。LoRA 削减的是可训练参数和对应的优化器状态但训练时的激活值和中间梯度并不会因为冻结主干而消失这部分照样吃显存而它的最大来源恰恰是图像 token。一张输入图在经过视觉编码器后会变成若干个视觉 token 塞进语言模型和文本 token 一起参与每一层的计算。以 448×448 输入、patch 大小为 14 来算一张图就是 32×321024 个视觉 token如果再拼接一段 300 字左右的文本单条样本序列长度轻松到 1300 以上。这也解释了为什么全参微调 Qwen-VL 时 batch size 为 1 也需要多卡 80G 级显存——不是参数规模撑爆的是激活值撑爆的。改用 LoRA 后主干权重不参与优化器更新显存从 60G 以上降到了 24G 级别这也是我把它作为起步卡的原因。依赖版本是第一个容易翻车的地方。Qwen-VL 发布时的代码和transformers4.33.0、peft0.5.0、accelerate0.23.0这一代 API 对齐如果你直接装最新版 transformerstrust_remote_codeTrue加载远程模型代码时容易出现接口不兼容。我一般先按下面这套组合锁死环境跑通后再考虑升级conda create -n qwen-vl-lora python3.10 -y conda activate qwen-vl-lora pip install torch2.1.2 torchvision0.16.2 --index-url https://download.pytorch.org/whl/cu121 pip install transformers4.33.0 accelerate0.23.0 peft0.5.0 pip install jsonlines pillow tqdmtorch 2.1.2 对应 CUDA 12.1这是当时官方仓库配套的常用组合peft 0.5.0 提供 LoraConfig、get_peft_model 这些核心 API后面代码全部依赖它。不要一上来就追新版本先把环境固化后续排错才能确定问题出在模型还是出在框架层。2.2 LoRA 在 Qwen-VL 上到底改了什么LoRA 微调的核心假设是大模型针对下游任务做适配时权重更新的有效自由度远小于参数总量因此没必要更新整份权重只需要在原始权重旁边挂一个低秩增量。训练时冻结原权重 W新增一个 ΔW ≈ BA其中 A 是 d×r、B 是 r×d 的低秩矩阵r 远小于 d。前向计算从原来的 h Wx 变成 h Wx BAx。训练完成后把 BA 乘上缩放系数再加回 W就是合并后的模型。落到 Qwen-VL 上LoRA 要注入的是 Qwen 语言模型部分而不是视觉编码器。Qwen 系列源码里的注意力投影层不叫 q_proj、v_proj 这种标准名字而是把 qkv 合并成单个 c_attn输出投影叫 c_proj。微调时 target_modules 必须写这两个名字否则 PEFT 可能找不到目标模块或者抛出一堆 key 不匹配的报错。我见过不少人是直接把 LLAMA 的 LoRA 教程搬过来写 q_proj结果模型加载正常但训练时一个参数都没注入等于白跑。视觉编码器和 resampler 要不要一起训练这是多模态 LoRA 最纠结的点。我的倾向是第一版先冻结视觉编码器只让 LoRA 去调语言模型部分。原因是视觉编码器已经做过大规模图文对齐如果你的业务图没有极端分布偏移轻量适配通常足够一旦让 ViT 也跟着动小数据量下很容易把视觉特征带偏新任务学起来了原有常识却丢了表现得跟黑匣子一样难查。resampler 我也默认冻结把可训练参数全压在语言模型上训练更稳。2.3 最小环境验证先跑一次加载而不是直接训练环境装完后不要急着写训练脚本先跑一个最小加载验证确认模型代码能和当前 transformers 版本对上。Qwen-VL 的模型文件里有自定义的 forward 逻辑必须通过trust_remote_codeTrue让 transformers 从模型仓库拉取并执行这些代码这一步也是最容易因为版本问题报 import 错误的地方。import torch from transformers import AutoTokenizer, AutoModelForCausalLM model_path Qwen/Qwen-VL-Chat tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, trust_remote_codeTrue, torch_dtypetorch.bfloat16, device_mapauto, ).eval() print(floaded: {model.__class__.__name__}) print(fdevice: {model.device})torch_dtypetorch.bfloat16是 A100/H100 上的首选半精度能压掉将近一半显存训练时和 fp16 相比也更稳。如果只有 RTX 3090、4090 这类卡bf16 同样能跑不必特意切回 fp16。这里用了device_mapauto只是为了快速验证实际训练时我会去掉它改成手动.to(cuda)或者直接用 accelerate 统一控制避免自动切分带来的额外通信开销。如果能正常打印出模型类名和设备位置说明环境这关过了可以进入数据准备。3. 数据准备与清洗把图片和对话转成 Qwen-VL 能吃的 JSONL3.1 Qwen-VL 微调数据的最小格式Qwen-VL 微调数据的标准载体是 JSONL每一行是一个训练样本包含 id、image 和 conversations 三段。conversations 是对话数组其中 human 表示用户输入gpt 表示期望模型输出的答案。图像路径要同时出现在 image 字段和 human 的 value 里并且在文本前用img/img标签包裹这是 Qwen-VL 识别图像输入的固定格式不要省略标签也不要改成 markdown 图片语法。{id:001,image:/data/train/001.jpg,conversations:[{from:human,value:img/data/train/001.jpg/img\n图中建筑是什么风格},{from:gpt,value:哥特式建筑特征是尖拱和飞扶壁。}]}这里 human 的 value 由图像标签和问题文本组成gpt 的 value 就是标准答案。训练时human 部分的 token 在 loss 计算里要被屏蔽模型只学习 gpt 那一段的输出分布。这个屏蔽逻辑会在第四章的 Dataset 代码里具体处理但数据侧先要保证对话结构规整第一条必须是 human 带图像第二条必须是 gpt 答案顺序反了或者多轮对话组织不完整都会让训练脚本静默出错。一个常见误区是把图像路径写成网络 URL。Qwen-VL 的 tokenizer 在解析img标签时会去读取本地文件训练阶段用 URL 要么读不到图要么把 URL 字符串本身当文本 token 化模型根本没有看见图像内容。业务里如果图片存在 OSS 或对象存储一定要先同步到本地再跑清洗脚本这个前置步骤省不得。3.2 数据清洗脚本坏图、短边、模糊图全过滤图像数据不像是标注文本肉眼一眼能看出问题。常见的坑包括图片路径指向不存在、文件虽在但用 PIL 打不开、尺寸过小导致视觉编码器输出退化、以及大量重复样本把模型训练分布拉偏。我习惯写一个统一清洗脚本在生成 JSONL 前先把这些问题过滤掉import json, os from PIL import Image def clean_dataset(jsonl_path, out_path, min_short_edge256): kept, dropped [], [] with open(jsonl_path, encodingutf-8) as f: for line in f: line line.strip() if not line: continue obj json.loads(line) img_path obj[image] if not os.path.exists(img_path): dropped.append((obj[id], missing)) continue try: im Image.open(img_path) im.load() except Exception: dropped.append((obj[id], corrupt)) continue w, h im.size if min(w, h) min_short_edge: dropped.append((obj[id], fshort_edge_{min(w, h)})) continue if im.mode ! RGB: im im.convert(RGB) im.save(img_path) kept.append(obj) with open(out_path, w, encodingutf-8) as f: for obj in kept: f.write(json.dumps(obj, ensure_asciiFalse) \n) print(fkept {len(kept)}, dropped {len(dropped)}) if __name__ __main__: clean_dataset(raw_data.jsonl, clean_data.jsonl, min_short_edge256)这个脚本不负责改写图片只做过滤和质量兜底。min_short_edge 设成 256是因为低于这个分辨率的图即使送入模型视觉编码器也很难提取有效特征训练出来的回答基本都是瞎猜。im.mode 不等于 RGB 的图比如灰度图或带透明通道的 PNG直接用会触发模型内部某些 batch 维度的断言报错这里统一转成 RGB 并覆盖保存。脚本跑完会输出保留和丢弃的数量如果丢弃比例超过 5%建议回到上游核查采集流程而不是强行留下噪声样本。3.3 训练集/验证集拆分与图像 token 消耗预估数据清洗过后下一步是把干净数据拆成训练集和验证集。多模态数据不能用普通的随机洗牌直接切分因为同一张图片可能出现在多个样本里比如一张图既问了颜色又问数量。如果随机切分同一个图会同时进训练集和验证集验证指标直接被污染。我一般先按 image 字段去重再按图片维度做分组切分保证一张图只落在单侧import json, random random.seed(42) with open(clean_data.jsonl, encodingutf-8) as f: samples [json.loads(line) for line in f if line.strip()] # 先按图片分组保证同一张图不会被同时分到训练集和验证集 image_groups {} for sample in samples: image_groups.setdefault(sample[image], []).append(sample) img_paths list(image_groups.keys()) random.shuffle(img_paths) val_ratio 0.05 val_cnt max(1, int(len(img_paths) * val_ratio)) val_imgs set(img_paths[:val_cnt]) train_imgs set(img_paths[val_cnt:]) with open(train.jsonl, w, encodingutf-8) as f: for path in train_imgs: for sample in image_groups[path]: f.write(json.dumps(sample, ensure_asciiFalse) \n) with open(val.jsonl, w, encodingutf-8) as f: for path in val_imgs: for sample in image_groups[path]: f.write(json.dumps(sample, ensure_asciiFalse) \n)验证集比例我一般取全部样本的 5% 左右。多模态训练时长通常比较长验证集过大反而拖慢迭代节奏5% 足够看出 loss 趋势和过拟合苗头。如果数据总量只有一两百条则保留 10 条左右做验证即可更多样本留给训练。数据处理阶段还可以顺手估算一下 token 消耗。超出的图片分辨率和采样数会直接关系到你能不能在 24G 卡上跑。下面这张表是我平时做资源预估时最常用的口径输入图分辨率patch size视觉 token 数单图显存压力224×22414256低448×448141024中672×672142304高如果你的数据集里图片尺寸明显集中在 672 以上要么在数据管线里统一缩放要么提前算好 batch size 并调低 max_len否则进入训练后很快就会遇到下面第五章要讲的 OOM。4. 训练实现LoRA 参数设置、训练启动与断点续训4.1 加载底座、冻结视觉编码器、注入 LoRA 完整代码训练核心是一个 Python 脚本从加载底座模型开始到保存 LoRA 权重结束。这里提供的是完整可跑的骨架模型路径、数据路径需要按你本地的目录结构改。注意模型加载时必须带 trust_remote_codeTrue同时把 use_cache 关掉否则和 gradient checkpointing 会直接冲突报错# train_lora.py import json import torch from transformers import ( AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq, ) from peft import LoraConfig, get_peft_model torch.manual_seed(42) model_path Qwen/Qwen-VL-Chat tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, trust_remote_codeTrue, torch_dtypetorch.bfloat16, ).to(cuda) model.config.use_cache False model.gradient_checkpointing_enable() # 冻结视觉编码器只保留语言模型部分参与 LoRA 适配 for name, param in model.transformer.visual.named_parameters(): param.requires_grad False config LoraConfig( r64, lora_alpha16, target_modules[c_attn, c_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM, ) model get_peft_model(model, config) model.print_trainable_parameters()冻结视觉编码器这一步在 Qwen-VL-Chat 里通过model.transformer.visual访问视觉模块不同版本可能叫 visual 或 vision_tower建议先跑一下print(model.transformer)确认实际结构。print_trainable_parameters()会输出可训练参数占总参数的比例LoRA 微调时这个比例通常在 1% 到 3% 之间如果你看到可训练参数占了 90% 以上说明 target_modules 配置有误LoRA 没有按预期只挂到注意力投影上。4.2 核心参数解读r、alpha、target_modules 的取舍LoRA 的四个核心参数直接决定训练效果和显存占用我根据多次踩坑经验整理如下参数建议值说明r64秩的大小。数据量小用 32任务复杂度高或数据充足用 64lora_alpha16缩放分子实际缩放系数 lora_alpha / rlora_dropout0.05正则项一般取 0.0~0.1过大会压制适配能力target_modulesc_attn, c_projQwen 的 qkv 合并投影和输出投影不能写成 q_projbiasnone不训练任何 bias 项省显存task_typeCAUSAL_LM自回归语言模型固定值r64、lora_alpha16 这个组合看起来有点反直觉因为缩放系数只有 0.25等效地把学习率压低了四分之一。但这正是多模态任务里常用的配置视觉语言模型的适配变化幅度本身不需要太大用低缩放系数让模型更新更温和能明显减少对视觉特征的破坏。如果任务偏文本生成、领域差异较小可以换成 r32、lora_alpha32 这种等量缩放。lora_dropout 不要调太高0.05 已经能起到抑制过拟合的作用调到 0.2 会导致 LoRA 适配器没学会东西。target_modules 这个参数是 Qwen 系 LoRA 最容易踩的坑。Qwen 的源码把 query、key、value 三个投影合并在 c_attn 一个线性层里所以不能照搬 LLAMA 的 q_proj、v_proj 配置。如果你不确定模块名可以在注入前打印一次模型结构或者用下面这段代码确认for name, module in model.named_modules(): if isinstance(module, torch.nn.Linear): print(name, module.weight.shape)把输出和 LoraConfig.target_modules 对一遍确保目标模块真实存在再跑训练。4.3 构造 Dataset 与数据 Collatorprompt 部分必须屏蔽 loss训练数据集的构造核心是 input_ids 和 labels 两个张量。input_ids 是完整的 prompt 加 answer 拼接序列labels 则把 prompt 部分的 token 标记成 -100让损失只在 gpt 答案上计算。这样模型不会去学习那张图片路径的 token 分布只学习图到答案的对应关系class VLFinetuneDataset(torch.utils.data.Dataset): def __init__(self, jsonl_path, tokenizer, max_len1024): self.tokenizer tokenizer self.max_len max_len with open(jsonl_path, encodingutf-8) as f: self.samples [json.loads(line.strip()) for line in f if line.strip()] def __len__(self): return len(self.samples) def __getitem__(self, idx): obj self.samples[idx] convs obj[conversations] image_path obj[image] human convs[0][value] gpt convs[1][value] prompt fimg{image_path}/img\n{human}\n full prompt gpt input_ids self.tokenizer( full, return_tensorspt, max_lengthself.max_len, truncationTrue, )[input_ids][0] prompt_len self.tokenizer( prompt, return_tensorspt, max_lengthself.max_len, truncationTrue, )[input_ids].shape[1] labels input_ids.clone() labels[:prompt_len] -100 return { input_ids: input_ids, labels: labels, attention_mask: torch.ones_like(input_ids), }max_len 在这里非常关键。Qwen-VL 单张图就要占用 1024 个视觉 token如果回答文本又长序列很容易超过 2048。max_len 设得太大显存撑不住设得太小answer 可能被truncationTrue从尾部直接截掉这条样本的 labels 全部变成 -100相当于白学。我一般先跑一遍数据集统计输入长度分布再取 p90 作为 max_len 初始值24G 显存下通常落在 1024 到 2048 之间。Data Collator 负责把不同长度的样本 padding 对齐label_pad_token_id 必须设成 -100否则辅助的部分会被当成有效标签计算 lossdata_collator DataCollatorForSeq2Seq( tokenizertokenizer, modelmodel, paddinglongest, label_pad_token_id-100, )这里有个容易忽略的细节DataCollatorForSeq2Seq 的 model 参数传入的是带 LoRA 的 peft 模型padding 时会按照模型结构正确计算 position id 和 attention mask。如果漏传这个参数生成的 attention_mask 在长样本上可能错位训练不会立即报错但 loss 曲线会更难看。4.4 训练参数与断点续训Trainer 配置和续训技巧训练参数直接决定 24G 卡能否稳住。per_device_train_batch_size 固定为 1配合 gradient_accumulation_steps16 实现等效 batch size 16这是多模态 LoRA 的标准玩法单卡显存不够装大 batch但通过累积梯度保证每次参数更新的稳定性training_args TrainingArguments( output_dir./output_qwen_vl_lora, per_device_train_batch_size1, gradient_accumulation_steps16, num_train_epochs3, learning_rate2e-4, lr_scheduler_typecosine, warmup_ratio0.03, logging_steps10, save_steps200, save_total_limit3, bf16True, report_tonone, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, data_collatordata_collator, ) checkpoint_dir None if os.path.exists(training_args.output_dir): checkpoint_dir training_args.output_dir trainer.train(resume_from_checkpointcheckpoint_dir) model.save_pretrained(./lora_weights_qwen_vl) tokenizer.save_pretrained(./lora_weights_qwen_vl)trainer.train 的 resume_from_checkpoint 参数不是写在 TrainingArguments 里的而是作为 train 方法的入参很多人把这一点写错导致报错。output_dir 里只要有 checkpoint 文件夹传入这个目录就能自动加载最近一次保存的断点继续训练相当于是给你的训练进程准备了后悔药。save_total_limit3 会保留最近三个 checkpoint 并自动清理更早的避免磁盘被大模型权重塞满。learning_rate2e-4 是 LoRA 微调这类 9B 模型的经验起点。如果 loss 震荡明显先降到 1e-4不要动 lora_alpha。bf16True 在支持 bf16 的 Ampere 架构及以后显卡上可以开如果是 2080Ti 这类不支持 bf16 的卡改成 fp16同时把 fp16_opt_level 不需要额外设置。5. 微调翻车排查显存、图像尺寸、权重保存与过拟合的 5 个实坑5.1 OOM第一个 step 直接爆显存现象训练脚本启动模型加载正常但走到第一个训练 step 时直接报CUDA out of memory有时候连模型加载阶段就崩。原因通常是图像 token 远超预期原始图片没有统一缩放视觉编码器按原始分辨率输出视觉 token一张 2048×1536 的图可能产生几千个视觉 token序列长度直接冲破 max_len 限制。解决回到数据管线把训练图像统一调整短边到 448 或模型期望的分辨率并打印 Dataset 里每个样本的实际 token 长度看是否超过你预估的上限。另外确认梯度检查点是否已经开启model.gradient_checkpointing_enable()能砍掉大量激活值显存对 24G 卡是必选项。5.2 图像尺寸不整除导致 shape 不匹配现象训练中报RuntimeError: Sizes of tensors must match或者某个 reshape 操作失败输出里带着 CLIP 或 ViT 相关文件名。原因视觉编码器内部对图像做 patch 切分如果宽高不能被 patch size 整除最后一个 patch 越界tensor 维度就对不上。Qwen-VL 常用 patch size 是 14448 是标准输入如果你传入 450×450 或者 455×455 这类不规整尺寸就会翻车。解决在数据增强或者清洗阶段强制做 resize 或中心裁剪到统一尺寸不要依赖模型内部隐式的 resize。我在清洗脚本里会加一个检查宽高对 14 取余必须为 0否则一边过滤一边记录避免训练到一半才炸。5.3 保存的 LoRA 权重加载报 key 不匹配现象训练完成用PeftModel.from_pretrained(base_model, ./lora_weights_qwen_vl)加载时报unexpected key或missing key推理时生成乱码。原因加载时使用的底座模型路径和微调时不一致比如训练用 Qwen/Qwen-VL-Chat加载时换成了 Qwen/Qwen-VL两种结构的权重 key 对不上或者加载时没开 trust_remote_code导致远程代码里的构架和本地权重错配。解决加载 LoRA 的底座模型名称、trust_remote_code、torch_dtype 必须和训练时完全一致并用model.print_trainable_parameters()确认 LoRA 适配器真的挂载成功。遇到移植到另一台机器的情况先把底座模型和 LoRA 权重的目录结构原样拷贝过来路径改动越少越稳。5.4 Loss 不降或降得异常慢现象训练跑到几百步loss 在 1.5 到 2.0 之间反复横跳或者一条平滑下降曲线都没有。这种问题的排查顺序是先看标签再看数据最后怀疑学习率。标签问题出现在 Dataset 构造里如果 prompt 部分没有正确屏蔽模型会把图片路径这类文本也当作预测目标loss 被大量无意义 token 稀释。打印一条样本的 labels统计非 -100 的 token 占比如果低于 5% 说明 answer 太短或者被截断调大 max_len 或者截断时优先保住 answer。数据问题常见于对话 answer 为空字符串或只有一个标点对应 loss 贡献为零。如果以上都正常再把学习率降到 1e-4 重跑LoRA 微调虽然是低成本参数适配但学习率过高照样震荡。5.5 新任务学会了通用能力倒退现象领域测试集效果还行但换一张日常图片、问一个常识问题模型开始胡说八道明显不如微调前。原因有两个一是训练数据太单一整个训练集都是同一类图片同一类问题LoRA 把模型的知识分布单向拉到业务领域二是视觉编码器被意外解冻或学习率过高把底层视觉特征改得面目全非。解决冻结逻辑重新检查一遍确保model.transformer.visual里的参数 requires_gradFalse然后在训练集里掺入 5% 到 10% 的通用图文问答数据给模型保留一条“回退通道”。如果回归严重把 LoRA 的 r 从 64 降到 32压缩适配器容量能减少对原始行为的覆盖。6. 推理与部署验证合并 LoRA 权重、生成测试与效果评估6.1 合并 LoRA 与部署merge_and_unload 才是交付物微调产物有两类一类只保存 LoRA 适配器另一类是合并回底座的完整模型。前者灵活可以随时切换多个 LoRA 适配器做 A/B 实验后者适合交付部署省去运行时挂载 adpater 的步骤推理进程里可以不依赖 peft。我建议训练结束后用 merge_and_unload 生成一份合并权重用于正式部署from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer import torch base AutoModelForCausalLM.from_pretrained( Qwen/Qwen-VL-Chat, trust_remote_codeTrue, torch_dtypetorch.bfloat16, device_mapauto, ) adapter PeftModel.from_pretrained(base, ./lora_weights_qwen_vl) merged adapter.merge_and_unload() merged.save_pretrained(./merged_model_v1) tokenizer.save_pretrained(./merged_model_v1)合并时显存会额外吃一块因为要同时保住原权重和低秩增量。如果 24G 卡上合并报 OOM可以把 base 的 device_map 改成 cpu让合并过程在主存进行完再保存多花几分钟但能保住模型。合并后注意把 merged 切成 eval 模式再保存否则部署端加载后默认是训练状态生成结果可能受 dropout 影响。6.2 验证设计别只看 loss要看生成文本训练完成后的第一件事不是看 BLEU 或 ROUGE而是先拿十几张没进过训练集的图片跑一轮 generate直接用肉眼判断输出质量。生成时 prompt 的格式必须和训练时完全一致图片标签在前、问题在后否则模型对输入的理解会偏移with torch.no_grad(): merged merged.eval() q img/data/val/007.jpg/img\n图中有几辆车 inputs tokenizer(q, return_tensorspt).to(cuda) out merged.generate(**inputs, max_new_tokens128) print(tokenizer.decode(out[0], skip_special_tokensTrue))如果模型输出和训练时样本风格明显不同先检查 tokenizer 用的特殊标签是否保留trust_remote_codeTrue加载的 tokenizer 会额外注册 image 相关专用 token换普通 tokenizer 会把这些 token 当成普通文本。这个验证脚本应该固化下来每次改数据、改超参数后都跑同一批验证图记录输出效果。领域问题如果答案集合可枚举可以直接算准确率开放生成任务则人工打分一阶评分即可多发散到 5 分制容易把自己搞迷糊。组里如果多人做多模态微调最好把验证图和标注产生一份固定基准不然每个人的评测口径不同模型之间无法公平对比。我最后补一句经验LoRA 微调 Qwen-VL 这种多模态底座最大的变量往往不在模型而在数据。图像路径有没有对齐、图片分辨率有没有统一、answer 有没有被截断这三个问题我几乎在每次新项目里都会至少遇到一次。所以我现在每次训练前都会把数据管线和断点目录临时另存一份即使只改一个 prompt 模板也不直接覆盖旧实验。多模态微调远没有纯文本那边成熟能提前留退路的就尽量留。希望帮到你。本文还有配套的精品资源点击获取
返回列表