多模态模型Qwen3-VL在Llama-Factory嵌套量化QLoRA训练+测试+导出+部署(Ollama/LMDeploy)全流程--以具身智能数据集open-eqa为例
前期环境配置等准备可参考教程:
多模态模型Qwen3-VL在Llama-Factory中断LoRA微调训练+测试+导出+部署全流程--以具身智能数据集open-eqa为例
这里数据来源 Open-EQA 多模态具身智能数据集,经过处理每个样本八张图片,划分为训练-验证集和测试集。
若对下载和处理open-eqa数据集代码有兴趣,可以通过网盘分享的文件:OpenEQACode.zip 链接: https://pan.baidu.com/s/1DqmIp1Xw6HJPX77O-iOXdQ?pwd=dgn8 提取码: dgn8
如果不方便下载和处理open-eqa数据集,可以通过网盘分享的文件:OpenEQA8s.zip
链接: https://pan.baidu.com/s/1_6G4YwI5tmYXUSDLssJ13A?pwd=hfvw 提取码: hfvw
1.微调训练
有cuda显卡可以执行pip install unsloth可以安装Unsloth加快训练和推理
执行pip install tensorboard安装保存完整训练过程的数据,避免中断只能部分曲线
创建saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa,并创建文件training_args.yaml,内容参考,路径根据自己的情况改
### model model_name_or_path: model/Qwen3-VL-2B-Instruct trust_remote_code: true ### method stage: sft do_train: true finetuning_type: lora lora_target: all lora_rank: 8 lora_alpha: 16 lora_dropout: 0.1 ### 是否使用unsloth加速 use_unsloth: false # 不启用Unsloth加速 #unsloth_max_seq_length: 2048 # Unsloth内部优化 flash_attn: auto # T4自动回退到FA1或sdpa ### quantization (QLoRA) quantization_bit: 4 quantization_method: bitsandbytes double_quantization: true #双量化/嵌套量化,进一步节省显存 ### dataset dataset: open_eqa_train_val template: qwen3_vl_nothink cutoff_len: 2048 max_samples: 100000 overwrite_cache: true preprocessing_num_workers: 8 ### output output_dir: saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa logging_steps: 10 save_steps: 25 resume_from_checkpoint: false #首次用false,断点训练用true overwrite_output_dir: true ### train per_device_train_batch_size: 2 # 如果是用Unsloth省显存,可以加大 gradient_accumulation_steps: 4 # 有效batch=8 learning_rate: 5.0e-5 # Unsloth可以用更高LR num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.05 #warmup_steps: 0 fp16: true ddp_timeout: 180000000 ### optimization optim: adamw_torch # group_by_length: true report_to: tensorboard plot_loss: true video_max_pixels: 65536 video_min_pixels: 256 freeze_multi_modal_projector: true freeze_vision_tower: true image_max_pixels: 589824 image_min_pixels: 1024 ### evaluation do_eval: true per_device_eval_batch_size: 2 val_size: 0.125 eval_strategy: steps eval_steps: 25 eval_delay: 0 # 延迟多少步开始第一次评估 prediction_loss_only: true # 是否需要完整预测(多模态通常需要false以计算准确率和生成指标,除非显存不足) ### save & eval 联动(可选但推荐) load_best_model_at_end: true # 训练结束后加载最佳模型 #metric_for_best_model: eval_loss # 或 eval_accuracy 等,选择监控指标 #greater_is_better: false # eval_loss 越小越好,如果是accuracy则设为true #save_total_limit: 3 # 保留最新的3个检查点,避免磁盘爆满这次训练设备配置是显存16GB的NVIDIA Tesla T4,执行lamafactory-cli train saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa/training_args.yaml开始训练


若出现中断,将resume_from_checkpoint设置为true恢复中断训练继续训练
llamafactory-cli train saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa/training_args.yaml
训练结束

观察图片


从训练和验证的损失曲线与指标来看,Qwen3-VL-2B-Instruct 在 Open-EQA 多模态小样本训练 - 验证集上的表现可以这样总结:
训练阶段表现
- 损失变化:训练损失初始值很高(约 5.5),在训练初期(前 100 步)快速下降,之后下降速度放缓,在 200 步后稳定在 1.0~1.5 区间。
- 最终指标:最终训练损失为 1.3233,说明模型在训练集上的拟合效果良好,且后期波动很小,收敛稳定。
- 效率:训练总耗时约 2 小时 27 分钟,共完成 453 步,计算量达 48049026 GFLOPs,显示出多模态训练的计算成本较高。
验证阶段表现
- 损失变化:验证损失初始值约 1.8,同样在初期快速下降,100 步后趋于平缓,后期稳定在 1.25~1.3 区间。
- 最终指标:最终验证损失为 1.2683,略低于训练损失,这表明模型在未见过的验证数据上泛化能力较好,没有出现明显的过拟合。
- 效率:验证集共 173 个样本,耗时约 2 分钟 10 秒,批大小为 2,验证阶段的样本处理速度比训练阶段更快。
整体表现
训练和验证的损失曲线趋势高度一致,且验证损失略低于训练损失,说明模型在训练过程中不仅对训练数据拟合充分,还具备良好的泛化能力,在 Open-EQA 多模态小样本任务上表现稳定。
如果曲线不完整,可以把云服务器的runs目录中tensorboard日志文件下载到本地电脑,

在自己电脑的python环境执行uv pip install tensorboard -i http://mirrors.aliyun.com/pypi/simple安装tensorboard,对于纯python或conda可以直接执行pip install tensorboard -i http://mirrors.aliyun.com/pypi/simple安装tensorboard

执行tensorboard --logdir=/Users/Zhuanz/Downloads --port 6006查看,--logdir是你的tensorboard日志文件文件的所在目录(不是文件),port是端口号可自己设置

查看网页



2.测试评估
创建saves/Qwen3-VL-2B-Instruct/qlora/eval_openeqa目录,并建立eval_args.yaml,内容如下,其中do_predict设置为true代表评估测试,注意要改适配器路径adapter_name_or_path、基座模型路径model_name_or_path和是融合模型路径output_dir
adapter_name_or_path: saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa/ #最佳checkpoint cutoff_len: 2048 dataset_dir: data ddp_timeout: 180000000 do_predict: true eval_dataset: open_eqa_test finetuning_type: lora flash_attn: auto max_new_tokens: 128 max_samples: 99999 model_name_or_path: model/Qwen3-VL-2B-Instruct output_dir: saves/Qwen3-VL-2B-Instruct/qlora/eval_openeqa per_device_eval_batch_size: 2 predict_with_generate: true preprocessing_num_workers: 4 report_to: none stage: sft temperature: 0.2 template: qwen3_vl_nothink top_p: 1.0 trust_remote_code: true 执行llamafactory-cli train saves/Qwen3-VL-2B-Instruct/qlora/eval_openeqa/eval_args.yaml

测试评估结束

结论:Qwen3-VL-2B-Instruct 模型经 QLoRA 轻量化微调 Open-EQA 多模态具身智能数据集后,在 NVIDIA Tesla T4(16GB 显存)硬件环境下完成了全量测试集的推理评估,本次评估共涉及 258 个测试样本,且每个样本包含 8 张关联图片的多模态输入。评估过程中模型采用批大小 2 的推理配置,全程无显存溢出等异常问题,稳定完成所有样本的预测推理,验证了 QLoRA 微调方案对 16GB 显存低算力显卡的良好适配性。从生成指标来看,模型预测 BLEU-4 值达 29.4966,ROUGE 系列指标中 ROUGE-1、ROUGE-2、ROUGE-L 分别为 36.2965、7.9106、35.7659,整体指标表现表明模型经微调后具备了一定的多图关联理解能力和与任务匹配的文本生成能力,其中 ROUGE-1 和 ROUGE-L 的较好表现体现模型能有效捕捉具身智能任务中的核心信息,ROUGE-2 偏低则反映模型在短句语义衔接的细粒度生成上仍有提升空间。从推理效率来看,本次预测全程耗时约 2 小时 55 分钟,样本处理速度为 0.025 个 / 秒、步数处理速度为 0.012 个 / 秒,推理速度整体偏低,核心受单样本 8 张图片的多模态特征提取带来的高计算量影响。整体而言,模型在 16GB 显存的 Tesla T4 显卡上成功实现了 Open-EQA 具身智能多图任务的稳定推理,并取得了具备参考性的生成效果,充分验证了本次 QLoRA 微调的实际有效性,同时也为后续通过优化图片处理策略、调整推理配置来提升模型推理速度,以及针对性优化训练策略改善细粒度生成能力提供了实际参考依据。
3.融合模型导出
创建saves/Qwen3-VL-2B-Instruct/qlora/merge目录,并建立merge_openeqa.yaml,路径同样需要修改,内容如下,
### model model_name_or_path: model/Qwen3-VL-2B-Instruct adapter_name_or_path: saves/Qwen3-VL-2B-Instruct/qlora/train_openeqa/ template: qwen3_vl_nothink finetuning_type: lora trust_remote_code: true ### export export_dir: saves/Qwen3-VL-2B-Instruct/qlora/merge export_size: 2 #导出模型分片(shard)的单文件大小上限,单位是 GB export_device: auto export_legacy_format: false #true:导出 .bin(旧/legacy) false:导出 .safetensors(默认/推荐)执行llamafactory-cli export saves/Qwen3-VL-2B-Instruct/qlora/merge/merge_openeqa.yaml

4.推理部署API服务
(1) Ollama
将融合模型下载到本地目录,比如/saves/Qwen3-VL-2B-Instruct/qlora/merge,并进入目录,打开Modelfile文件,可以根据需要修改
# ollama modelfile auto-generated by llamafactory FROM . TEMPLATE """{{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ range .Messages }}{{ if eq .Role "user" }}<|im_start|>user {{ .Content }}<|im_end|> <|im_start|>assistant {{ else if eq .Role "assistant" }}{{ .Content }}<|im_end|> {{ end }}{{ end }}""" # PARAMETER temperature 0.7 #可设置温度 PARAMETER stop "<|im_end|>" PARAMETER num_ctx 4096 到终端执行创建模型的命令,模型将被转换格式并放入ollama的模型空间中,这里是这个qwen3-vl-2b是在ollama模型空间的模型名称,可以修改
ollama create qwen3-vl-2b -f Modelfile
执行ollama list查看ollama的模型列表

调用方法
这里只列举其中三种
1)命令行直接传入
执行ollama run qwen3-vl-2b "问题" 图片路径,比如
ollama run qwen3-vl-2b "墙上有什么东西" ./data/open_eqa_frames/0a0c0f2b9ba65d1b/000.jpg

2)交互式模式
先执行ollama run qwen3-vl-2b加载模型,进入交互模式,然后输入问题及图片路径,比如带有斑纹的椅子上有几个枕头 ./data/open_eqa_frames/0a0c0f2b9ba65d1b/000.jpg

3)curl调用
执行IMG=$(base64 -i data/open_eqa_frames/0a0c0f2b9ba65d1b/000.jpg | tr -d '\n')转图片为base64格式

执行命令
curl http://localhost:11434/api/generate -d '{ "model": "模型名称", "system": "系统提示词", "prompt": "用户提示词", "images": ["$图片base64变量"], "format": "格式", "stream": "是否流式输出", "options": {参数设置}, }'比如
curl http://localhost:11434/api/generate -d '{ "model": "qwen3-vl-2b", "system": "你是机器人控制AI。你必须输出可执行的动作序列。scene_analysis必须包含:目标相对于当前视角的方位(左/右/前)和距离(米)。plan中的params必须使用英文键名(target/type/distance/degrees)。严禁使用中文键名。", "prompt": "观察图片,为指令\"怎么关闭台灯\"输出JSON:\n{\n \"scene_analysis\": \"目标在[方位],距离[X]米\",\n \"plan\": [\n {\"action\": \"rotate\", \"params\": {\"degrees\": 角度, \"direction\": \"left|right\"}},\n {\"action\": \"navigate\", \"params\": {\"distance\": 米数}},\n {\"action\": \"interact\", \"params\": {\"type\": \"press\", \"target\": \"台灯开关\"}}\n ]\n}", "images": ["'$IMG'"], "format": "json", "stream": false, "options": {"temperature": 0.01, "num_predict": 300} }'回答得不错,基本符合格式,实际使用最好使用解析器处理格式

(2) LMDeploy
切换激活环境,执行pip install --no-cache-dir lmdeploy安装 LMDeploy库

编写测试脚本test_offline.py
from lmdeploy import pipeline, TurbomindEngineConfig, PytorchEngineConfig, GenerationConfig from lmdeploy.vl import load_image import time MODEL_PATH = "/workspace/LlamaFactory/saves/Qwen3-VL-2B-Instruct/qlora/merge" IMAGE_PATH = "/workspace/LlamaFactory/data/open_eqa_frames/0a0c0f2b9ba65d1b/000.jpg" print("🚀 使用 LMDeploy PyTorch 后端加载 Qwen3-VL...") # ⚠️ T4 必须用 PyTorch 后端(TurboMind 不支持 Qwen3-VL) # T4 只有 16GB,限制并发和序列长度 engine_config = PytorchEngineConfig( tp=1, # 单卡 session_len=4096, # 最大序列长度(T4 显存限制) max_batch_size=4, # 最大批处理大小 cache_max_entry_count=0.6, # KV Cache 占用显存比例(T4 建议 0.5-0.6) eager_mode=True, # T4 必须禁用 CUDA Graph ) if __name__ == '__main__': #freeze_support() # 创建 pipeline(会自动检测无 Flash Attn,fallback 到 native) pipe = pipeline(MODEL_PATH, backend_config=engine_config) print("✅ 模型加载成功!") # 加载图片 image = load_image(IMAGE_PATH) # 测试单张图片 print("\n🎯 单图推理测试...") prompts = [ ("描述这张图片", image), ] start = time.time() # 使用 GenerationConfig 对象而非 dict response = pipe(prompts, gen_config=GenerationConfig(max_new_tokens=256, temperature=0.7)) latency = time.time() - start print(f"⏱️ 延迟: {latency:.2f} s") print(f"📝 输出: {response[0].text}") # 测试 batch 加速效果(LMDeploy 的核心优势) print("\n🎯 Batch 推理测试(4 张相同图片,体现 continuous batching)...") prompts_batch = [ ("描述这张图片", image), ("图中有几个人?", image), ("这是什么场景?", image), ("图片主色调是什么?", image), ] start = time.time() responses = pipe(prompts_batch, gen_config=GenerationConfig(max_new_tokens=128)) batch_latency = time.time() - start print(f"⏱️ Batch 总延迟: {batch_latency:.2f} s") print(f"⚡ 平均每个请求: {batch_latency/4:.2f} s") print("📊 throughput 提升: {:.1f}x".format( 4 / (batch_latency / latency) ))
结果分析:
a. 功能层面:完美打通,输出质量优秀
- 单图推理能精准描述图片细节(深蓝色沙发、条纹扶手椅、装饰画文字
Live, Travel, Explore、绿植 / 百叶窗等),无遗漏关键视觉信息; - Batch4 个不同问题的推理均能正确响应,模型对视觉问答的语义理解符合预期,多模态能力完全正常发挥;
- 全程无报错、无 OOM(显存溢出),说明 LMDeploy 对 Qwen3-VL 的 PyTorch 后端适配完善,图片加载 / 提示构造 / 推理流程全链路通畅。
b. 单图推理性能:符合 T4+PyTorch 后端的预期
单图延迟9.63s,这个数值在当前约束下是合理且可接受的:多模态推理的耗时主要来自视觉特征提取(Qwen3-VL 的视觉分支需要处理图片张量)+文本生成,而 T4 算力有限、PyTorch 后端无 TurboMind 的极致优化,2B 模型的这个延迟是中端硬件的正常表现。
c. Batch 批处理:机制生效,实现正向加速
4 个请求的 Batch 测试核心数据:
- 总延迟:31.42s → 平均单请求延迟7.86s(比单图的 9.63s降低 18.4%);
- 吞吐量提升:1.2x,验证了 LMDeploy
continuous batching的价值。
执行命令部署后台服务,
nohup lmdeploy serve api_server /workspace/LlamaFactory/saves/Qwen3-VL-2B-Instruct/qlora/merge --model-name qwen3-vl --backend pytorch --tp 1 --session-len 4096 --cache-max-entry-count 0.6 --max-batch-size 4 --eager-mode --server-port 23333 > api_server.log 2>&1 &关键参数解析
| 参数 | 作用 | T4 约束 |
|---|---|---|
--backend pytorch | 使用PyTorch后端推理 | 必须:TurboMind(C++)不支持Qwen3-VL架构,且T4是SM75架构 |
--tp 1 | 张量并行数 | T4只有1张卡,设为1(多卡可加速但T4不支持NVLink高效通信) |
--session-len 4096 | 最大序列长度 | 受限于16GB显存,4096是安全值(过长会OOM) |
--cache-max-entry-count 0.6 | KV Cache显存占比 | 核心优化:0.6×16GB=9.6GB给KV Cache,剩余给模型权重(4-5GB)和激活值 |
--max-batch-size 4 | 最大batch size | Continuous Batching并发上限,T4建议4-8,过高会延迟增加 |
--eager-mode | 禁用CUDA Graph编译 | 必须:T4架构较旧,CUDA Graph可能导致非法指令或内存错误 |
--server-port 23333 | API端口 | 默认与OpenAI API(8080)区分避免冲突 |
nohup 与重定向解析
| 符号 | 含义 |
|---|---|
nohup | No Hang Up,用户退出SSH后进程继续运行 |
> api_server.log | 标准输出(STDOUT)重定向到日志文件 |
2>&1 | 标准错误(STDERR)重定向到STDOUT(即也进日志) |
& | 后台运行(立即返回命令行,不阻塞) |
用curl命令请求测试
BASE64_IMG=$(base64 -w 0 /workspace/LlamaFactory/data/open_eqa_frames/0a0c0f2b9ba65d1b/000.jpg) curl -X POST http://localhost:23333/v1/chat/completions \ -H "Content-Type: application/json" \ -d "{ \"model\": \"qwen3-vl\", \"messages\": [{ \"role\": \"user\", \"content\": [ {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/jpeg;base64,${BASE64_IMG}\"}}, {\"type\": \"text\", \"text\": \"描述这张图片\"} ] }], \"max_tokens\": 256, \"temperature\": 0.7 }"
执行tail -f api_server.log查看日志

执行ps aux | grep "lmdeploy serve api_server"查看后台进程pid

这说明服务正在运行,有两个进程显示是因为:
PID | 进程 | 说明 |
|---|---|---|
12684 |
| 真正的 LMDeploy 服务(占 1.9GB 内存) |
13135 |
| 刚执行的 grep 命令本身(临时进程,已结束) |
执行kill 12684杀死服务,注意pid以实际为准

创作不易,禁止抄袭,转载请附上原文链接及标题