self-llm 实战:Qwen3-VL 视觉语言模型 LoRA 微调 LaTeX OCR 公式识别——transformers + PEFT + SwanLab 全流程指南
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
本文基于开源项目《开源大模型食用指南》(self-llm)中 Qwen3-VL-4B-Instruct LoRA 可视化微调案例 文档,系统讲解如何利用 transformers、peft 与 SwanLab,对 Qwen3-VL-30B-A3B-Instruct 和 Qwen3-VL-4B-Instruct 两个视觉语言模型进行 LoRA 微调,使其学会将数学公式图片转录为 LaTeX 代码。读者将掌握多模态数据集的加载与整理、Qwen3-VL 微调数据的对话模板构造、自定义 DataCollator 的编写、LoRA 参数配置、SwanLab 训练监控集成,以及微调前后模型的推理对比评估方法。
任务背景:为什么选择 Qwen3-VL 做 LaTeX OCR
Qwen3-VL 是 Qwen 系列中能力较强的视觉语言模型之一,在文本理解与生成、视觉感知与推理、扩展上下文长度、空间与视频动态理解等方面都有显著改进。该系列同时提供 Dense 与 MoE(Mixture of Experts)两种架构,以及 Instruct 与推理增强型 Thinking 版本,可以按需选择部署。其中值得关注的增强能力之一是 OCR:模型卡片介绍其可支持 32 种语言(相比前代 19 种有提升),在弱光、模糊和倾斜条件下表现稳健,更适合处理稀有/古代字符与行话,并且改进了长文档结构解析。
本项目要解决的实战问题,正是发挥 Qwen3-VL 强 OCR 能力的典型场景:给定一张数学公式渲染图,模型需要输出对应的 LaTeX 源码。我们使用 linxy/LaTeX_OCR 开源数据集,用 LoRA 低成本微调的方式,让通用视觉语言模型变成专业的"公式转 LaTeX"识别器。
备注:本教程使用的代码同时兼容 Qwen2.5 系列视觉模型,例如
Qwen/Qwen2.5-VL-3B-Instruct也可在该脚本上直接运行(参见 train_qwen3_vl.py 中被注释的模型路径)。
环境配置:显卡、CUDA 与依赖库安装
硬件需求评估
本次实验涉及两个规模的模型,显存需求差异较大:
| 模型 | 显存需求 | 推荐硬件 | 训练耗时参考 |
|---|---|---|---|
Qwen/Qwen3-VL-30B-A3B-Instruct | 124+ GB | 两张 NVIDIA H20 | batch size 为 8 时约 15 分钟 |
Qwen/Qwen3-VL-4B-Instruct | 20+ GB | 单张 24 GB 显卡(如 3090、4090) | batch size 为 1 时约 5 分钟 |
如果计算资源有限,强烈建议使用 Qwen3-VL-4B-Instruct 完成实验,仅需一张 24 GB 显存的显卡即可覆盖训练与推理全流程;选择 30B-A3B 模型则需要两张 H20 级别的高显存 GPU。训练耗时与per_device_train_batch_size直接相关,批次越大耗时越长,可据此在显存允许范围内权衡。
基础运行环境
确保电脑上至少有一张 NVIDIA 显卡并已安装好 CUDA 环境,Python 版本 >= 3.12,并安装能够调用 CUDA 加速的 PyTorch。本教程对应的镜像配置为 PyTorch 2.8.0、Python 3.12、CUDA 12.8。
依赖库清单
先升级 pip 并更换国内 PyPI 源以加速安装:
python -m pip install --upgrade pip pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple本次微调主要依赖的第三方库及其版本如下(与仓库中 requirements.txt 一致):
notebook==7.4.7 numpy<2.0 datasets==4.2.0 peft==0.17.1 accelerate==1.10.1 mpmath==1.3.0 networkx==3.4.2 regex==2025.9.18 sympy==1.14.0 tokenizers==0.22.1 torch==2.8.0 torchvision>=0.23.0 transformers>=4.41.2 triton==3.4.0 qwen-vl-utils==0.0.14 matplotlib>=3.10.7 modelscope==1.30.0 python-dotenv>=1.1.1 swanlab将上述内容写入requirements.txt后执行:
pip install -r requirements.txt其中几个关键依赖的分工值得说明:
transformers>=4.41.2:提供Trainer、AutoProcessor、AutoTokenizer、AutoConfig等核心训练与加载组件;peft==0.17.1:提供LoraConfig、TaskType、get_peft_model等 LoRA 适配器实现;qwen-vl-utils==0.0.14:提供process_vision_info,负责从对话消息中抽取图像/视频输入,是多模态数据处理的关键工具;swanlab:实验跟踪平台 SDK,与 transformers 的Trainer集成后自动记录训练指标;modelscope==1.30.0:用于在国内网络环境下稳定下载模型权重;python-dotenv:用于从.env文件加载 SwanLab API Key 等环境变量。
准备数据集:linxy/LaTeX_OCR 五个子集详解
本次使用开源数据集linxy/LaTeX_OCR。它是一个专门用于"公式图片 → LaTeX 源码"任务的数据集,内部包含五个子集(通过name参数选择),每个子集基本只有两个字段:image(公式渲染图片)与text(对应的 LaTeX 标注):
| 子集名称 | 样本量 | 说明 |
|---|---|---|
small | 110 条 | 小数据集,用于测试 |
full | 约 100k(略小于) | 印刷体完整数据集;作者用 LaTeX 抽象语法树剔除了许多无法渲染的 LaTeX,因此实际数量略小于 100k |
synthetic_handwrite | 约 100k(略小于) | 手写体完整数据集,基于full的公式用手写字体合成,可视为人在纸上的手写体 |
human_handwrite | 较小数据集 | 更符合人类在电子屏上书写的笔迹,主要来源于 CROHME,同样经过 LaTeX 抽象语法树校验 |
human_handwrite_print | 较小数据集 | 来自human_handwrite的印刷体版本,公式部分与human_handwrite相同,图片部分由公式用 LaTeX 渲染而来 |
加载训练划分并检查样本
通过split参数可以指定train、validation、test等划分。以下示例展示加载small子集的训练划分并快速检查样本内容:
from datasets import load_dataset train_dataset = load_dataset("linxy/LaTeX_OCR", name="small", split="train") print(train_dataset[2]["text"]) print(train_dataset[2]) print(len(train_dataset))输出示例:
\rho _ { L } ( q ) = \sum _ { m = 1 } ^ { L } \ P _ { L } ( m ) \ { \frac { 1 } { q ^ { m - 1 } } } . { 'image': <PIL.PngImagePlugin.PngImageFile image mode=RGB size=200x50 at 0x15A5D6CE210>, 'text': '\\rho _ { L } ( q ) = \\sum _ { m = 1 } ^ { L } \\ P _ { L } ( m ) \\ { \\frac { 1 } { q ^ { m - 1 } } } .' } 50可以看到,image字段是 PIL 图像对象,text字段是空格分隔 token 的 LaTeX 源码,这正是我们微调时需要的"图片 → 文本"监督信号。
一次性加载全部划分
若需同时获得训练、验证、测试三个划分,可直接加载整个DatasetDict:
from datasets import load_dataset dataset = load_dataset("linxy/LaTeX_OCR", name="small") print(dataset)输出:
DatasetDict({ train: Dataset({ features: ['image', 'text'], num_rows: 50 }) validation: Dataset({ features: ['image', 'text'], num_rows: 30 }) test: Dataset({ features: ['image', 'text'], num_rows: 30 }) })模型下载与磁盘空间规划
为避免网络问题导致模型下载失败,教程使用 ModelScope 下载模型。两个模型的地址对应:
Qwen/Qwen3-VL-30B-A3B-InstructQwen/Qwen3-VL-4B-Instruct
将模型下载到指定目录,例如下载 30B-A3B 模型:
modelscope download --model Qwen/Qwen3-VL-30B-A3B-Instruct --local_dir ./Qwen3-VL-30B-A3B-Instruct下载 4B 模型:
modelscope download --model Qwen/Qwen3-VL-4B-Instruct --local_dir ./Qwen3-VL-4B-Instruct磁盘空间方面需要特别注意:Qwen3-VL-30B-A3B-Instruct权重约 60 GB,下载前需保证磁盘空闲空间在 65 GB 以上;Qwen3-VL-4B-Instruct约 8 GB,存储空间需在 10 GB 以上。
若在 AutoDL 云平台上直接运行仓库代码,需要把模型放到代码指定的路径(训练脚本中的默认路径为/root/autodl-tmp/Qwen3-VL-4B-Instruct)。文档作者特别提醒:AutoDL 的autodl-fs目录会长期占用用户空间,未及时清理会持续计费,建议改用auto-tmp目录存放模型,但切换后需同步修改代码中加载模型的路径(即model_id与output_dir)。
集成 SwanLab:一行回调实现训练可视化监控
SwanLab 与 transformers 已完成官方集成:只需在Trainer的callbacks参数中添加SwanLabCallback实例,即可自动记录超参数和训练指标。最小集成代码如下:
from swanlab.integration.transformers import SwanLabCallback from transformers import Trainer swanlab_callback = SwanLabCallback() trainer = Trainer( ... callbacks=[swanlab_callback], )使用前需要在 SwanLab 官网注册账号并获取 API Key。首次训练启动时按提示粘贴即可,之后无需重复登录(SwanLab 对个人使用免费)。
在本项目的 train_qwen3_vl.py 中,回调被进一步定制化:通过project指定项目名、experiment_name指定实验名,并把模型、数据集、提示词、训练样本数与 LoRA 关键超参数通过config一起记录,方便后续复现与对比:
swanlab_callback = SwanLabCallback( project="Qwen3-VL-finetune", experiment_name="qwen3-vl-latex-ocr", config={ "model": model_id, "dataset": "linxy/LaTeX_OCR", "prompt": PROMPT_TEXT, "train_data_number": len(train_data), "lora_rank": lora_config_dict["lora_rank"], "lora_alpha": lora_config_dict["lora_alpha"], "lora_dropout": lora_config_dict["lora_dropout"], }, )代码中 API Key 设置为从环境变量加载:需要创建一个.env文件,内容为:
SWAN_LAB=你的API Key训练脚本通过load_dotenv()读取该文件,并将其注入SWANLAB_API_KEY环境变量(见 train_qwen3_vl.py)。训练开始后,SwanLab 页面会展示实验列表,点击任一实验即可查看 loss、grad_norm、learning_rate、epoch 等指标随 step 的变化曲线,训练全程一目了然。
LoRA 原理与配置详解
LoRA 低秩适配原理
LoRA 的全称是 Low-Rank Adaptation(低秩适配)。传统的全参数微调需要更新模型中所有的参数,成本高昂;而 LoRA 的核心思想是:权重变化矩阵 ΔW 可以被近似地分解为两个更小的矩阵的乘积,仅更新这两个小矩阵即可。
论文中完整的前向传播公式为:
$$h=W_{0}x+\Delta Wx=W_{0}x+BAx$$
其中 $W_0$ 是冻结的原始权重,$B \in \mathbb{R}^{d \times r}$ 与 $A \in \mathbb{R}^{r \times k}$ 是低秩分解得到的可训练矩阵。LoRA 在推理时不会增加额外的计算延迟,因为旁路结构可以在推理前合并回原始权重矩阵——通过简单的矩阵加法 $W' = W_0 + BA$ 即可将适配器权重融合进主干网络。
引入缩放因子 α 后,前向传播公式变为:
$$h = W_{0}x + \frac{α}{r}BAx$$
α 是一个常量,这样做的优势是:当改变秩 r 的大小时,可以减少重新调整超参数的需要(即 rank-stabilized 缩放)。
LoRA 配置参数解析
本项目创建的 LoRA 配置代码如下(见 train_qwen3_vl.py):
lora_config_dict = { "lora_rank": 128, "lora_alpha": 16, "lora_dropout": 0, } target_modules = ["q_proj", "k_proj", "v_proj", "o_proj"] config = LoraConfig( task_type=TaskType.CAUSAL_LM, target_modules=target_modules, inference_mode=False, r=lora_config_dict["lora_rank"], lora_alpha=lora_config_dict["lora_alpha"], lora_dropout=lora_config_dict["lora_dropout"], bias="none", )各参数的核心含义与调整建议:
| 参数 | 取值 | 含义与说明 |
|---|---|---|
task_type | TaskType.CAUSAL_LM | 任务类型,指定为因果语言建模(对应 decoder-only 结构的 Qwen3-VL) |
target_modules | ["q_proj", "k_proj", "v_proj", "o_proj"] | LoRA 适配器作用的模块,即 Transformer 自注意力机制中的四个核心线性投影层,负责生成查询(Q)、键(K)、值(V)与注意力输出(O) |
r(rank) | 128 | LoRA 的秩,即低秩分解矩阵的维度。秩越大可学习的表达能力越强,但参数量与显存开销也随之增大 |
lora_alpha | 16 | LoRA 的缩放因子 α,即公式中的 α,用于缩放低秩分支的贡献 |
lora_dropout | 0 | LoRA 层的丢弃率,设置为 0 表示不施加 dropout |
bias | "none" | 是否训练偏置项,none表示所有 bias 保持冻结 |
inference_mode | False | 当前处于训练模式(非推理模式) |
微调完整代码实现与解析
完整的训练脚本位于 train_qwen3_vl.py,也是原文档 微调案例 中的完整代码。下面按模块拆解其实现要点。
数据整理:构造多模态对话样本(process_func)
process_func负责把数据集中的image与text字段转换为模型的训练输入。核心逻辑:
PROMPT_TEXT = "Transcribe the LaTeX of this image." def process_func(example, tokenizer, processor): MAX_LENGTH = 8192 image = example["image"] output_content = example["text"] messages = [ { "role": "user", "content": [ {"type": "image", "image": image}, {"type": "text", "text": PROMPT_TEXT}, ], } ] text = processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) image_inputs, video_inputs = process_vision_info(messages) inputs = processor( text=[text], images=image_inputs, videos=video_inputs, do_resize=True, ) ...几个关键设计点:
- 多模态对话模板:把图片与提示词组成
user角色的多模态消息,content列表里同时包含{"type": "image", "image": image}与{"type": "text", "text": PROMPT_TEXT},提示词为"Transcribe the LaTeX of this image."(转写这张图片中的 LaTeX); apply_chat_template:由 processor 根据模型自带的 chat template 把消息渲染成文本序列,add_generation_prompt=True追加生成提示符;process_vision_info:从消息中抽取真实的图像/视频输入;- 标签遮蔽(masking):响应部分的 token 追加
eos_token_id作为结束符(若 eos 为 None 则回退到pad_token_id),并把指令部分的标签全部置为-100([-100] * len(instruction_input_ids) + response_input_ids),这样训练时 loss 只计算模型对 LaTeX 答案的预测,不计算对提问与图片标记的预测; - 长度截断:
MAX_LENGTH = 8192,超出部分直接截断,保证 batch 内样本可控。
process_func通过dataset.map(..., fn_kwargs={"tokenizer": tokenizer, "processor": processor})应用到训练与评估数据集,remove_columns=train_data.column_names移除原始列,只保留input_ids、attention_mask、labels、pixel_values、image_grid_thw五类模型输入。
自定义 DataCollator:Qwen3VLDataCollator
由于多模态样本长度不一,需要自定义 collator 完成动态 padding。Qwen3VLDataCollator的核心逻辑(见 train_qwen3_vl.py):
- 以 batch 内最长序列为
max_length,将input_ids填充为pad_token_id(若 tokenizer 无pad_token_id则回退到eos_token_id,两者均为 None 时抛出ValueError); attention_mask用 0 填充,labels用-100填充(-100 是 PyTorch 交叉熵损失约定忽略的位置,与训练数据中的指令掩码语义一致);- 视觉侧将各样本的
pixel_values沿 batch 维torch.cat,image_grid_thw(Qwen3-VL 图像分块网格的 T/H/W 信息)则torch.stack成 batch。
最终返回input_ids、attention_mask、labels、pixel_values、image_grid_thw五元组,与process_func的输出字段一一对应。
动态加载 Qwen3-VL 模型架构
训练脚本没有硬编码模型类,而是通过AutoConfig读取模型的model_type与architectures字段,再用importlib动态导入对应的 modeling 模块,从而对 Qwen3-VL 系列甚至 Qwen2.5-VL 保持通用性(见 train_qwen3_vl.py):
tokenizer = AutoTokenizer.from_pretrained(model_id, cache_dir=os.environ.get("HF_HOME", "./"), use_fast=False, trust_remote_code=True) processor = AutoProcessor.from_pretrained(model_id, cache_dir=os.environ.get("HF_HOME", "./"), use_fast=False) config = AutoConfig.from_pretrained(model_id, cache_dir=os.environ.get("HF_HOME", "./"), trust_remote_code=True) arch = (config.architectures or [None])[0] module_name = f"transformers.models.{config.model_type}.modeling_{config.model_type}" module = importlib.import_module(module_name) model_cls = getattr(module, arch) model = model_cls.from_pretrained( model_id, cache_dir=os.environ.get("HF_HOME", "./"), device_map="auto", trust_remote_code=True, ) model.to(dtype=torch.bfloat16) model.config.use_cache = False训练阶段的关键设置:
device_map="auto"让 accelerate 自动把模型各层分配到可用 GPU 上,是 30B-A3B 双卡训练的基础;model.to(dtype=torch.bfloat16)使用 BF16 混合精度,降低显存占用;model.config.use_cache = False:训练阶段关闭 KV cache(节省显存),推理阶段再开启。
LoRA 注入与梯度设置
peft_model = get_peft_model(model, config) peft_model.enable_input_require_grads()get_peft_model根据前面定义的LoraConfig在q_proj、k_proj、v_proj、o_proj四个投影层注入低秩旁路;enable_input_require_grads()确保即使使用梯度检查点(gradient checkpointing),输入层的梯度也能正常回传。
训练超参数详解
TrainingArguments配置如下(见 train_qwen3_vl.py):
args = TrainingArguments( output_dir=output_dir, per_device_train_batch_size=8, # 每个GPU的batch size gradient_accumulation_steps=1, # 梯度累积步数 logging_steps=10, logging_first_step=5, num_train_epochs=8, # 训练轮数 save_steps=50, # 每多少步保存一次模型 save_total_limit=3, # 最多保存模型数量 learning_rate=1e-4, # 学习率 gradient_checkpointing=True, # 梯度检查点 gradient_checkpointing_kwargs={"use_reentrant": False}, report_to="none", )各参数的作用与调参建议:
| 参数 | 取值 | 说明 |
|---|---|---|
output_dir | output_dir | 模型与日志输出目录 |
per_device_train_batch_size | 8 | 每个 GPU 上的 batch size。30B-A3B 显存紧张时可调小(如 1),文档实验表明它对最终效果有显著影响(详见后文效果分析) |
gradient_accumulation_steps | 1 | 梯度累积步数,等效扩大 batch size 的另一种手段 |
logging_steps/logging_first_step | 10 / 5 | 日志记录间隔,首次记录在第 5 步 |
num_train_epochs | 8 | 训练轮数。作者从 1 轮逐步试到 9 轮,发现 9 轮时 loss 出现回升,最终定为 8 轮 |
save_steps | 50 | 每 50 步保存一次 checkpoint |
save_total_limit | 3 | 最多保留 3 个 checkpoint,自动清理更早的 |
learning_rate | 1e-4 | 学习率,LoRA 微调的常见量级 |
gradient_checkpointing | True | 开启梯度检查点,用计算换显存,训练更长序列/更大 batch 的必要手段 |
gradient_checkpointing_kwargs | {"use_reentrant": False} | 关闭 reentrant 模式,避免新版 PyTorch 的兼容警告 |
report_to | "none" | 不向 transformers 默认的日志后端(如 wandb/tensorboard)上报,指标统一交给 SwanLab 回调收集 |
训练流程与产物保存
trainer = Trainer( model=peft_model, args=args, train_dataset=train_dataset, eval_dataset=eval_dataset, data_collator=Qwen3VLDataCollator(tokenizer=tokenizer), callbacks=[swanlab_callback], ) trainer.train()训练结束后,脚本做了三件事(见 train_qwen3_vl.py):
- 从
trainer.state.log_history中提取带loss的日志,用 matplotlib 绘制Step → Loss曲线并保存为output_dir/training_loss.png(注意代码中标题仍沿用 30B 的命名,仅影响图内文字,不影响数据); trainer.model.save_pretrained(output_dir)保存 LoRA 适配器权重;tokenizer.save_pretrained(output_dir)与processor.save_pretrained(output_dir)同步保存分词器与处理器——这对推理阶段保证 chat template 与词表一致至关重要。
微调前后模型输出对比:推理评估脚本解析
训练完成后,我们需要客观评估微调效果。对比推理脚本 compare_qwen3_vl_infer.py 实现了对基础模型与 LoRA 微调模型在同一批测试图片上的输出对比。
模型加载与 LoRA 内存合并
BASE_MODEL_ID = "/root/autodl-tmp/Qwen3-VL-4B-Instruct" PEFT_DIR = "/root/autodl-tmp/Qwen3-VL-4B" MERGE_LORA_IN_MEMORY = True NUM_TEST_SAMPLES = 5load_backbone:与训练脚本相同的动态架构加载方式,按设备选择device_map="auto"(CUDA)或None(CPU),精度按设备选择 bfloat16 或 float32;load_lora_model:先加载基座模型,再PeftModel.from_pretrained(base_model, peft_dir)挂载 LoRA;若MERGE_LORA_IN_MEMORY=True,则调用peft_model.merge_and_unload()把 LoRA 权重在内存中直接合并回主干(不落盘),合并失败时回退到未合并的 PeftModel 继续运行;- tokenizer 与 processor优先从 LoRA 输出目录读取,保证 chat template 与词表与训练阶段完全一致;
- 加载后显式
model.eval(),并关闭 gradient checkpointing、开启use_cache=True,为生成做准备。
生成逻辑与输出规范化
generate_answer使用@torch.inference_mode()装饰,生成参数固定为max_new_tokens=512、do_sample=False(贪心解码)、use_cache=True,并把attention_mask、image_grid_thw等按需传给model.generate。生成后通过截断 prompt 长度来提取新增 token,再用tokenizer.decode(..., skip_special_tokens=True)还原文本。
ensure_block_dollars函数用于统一输出格式:若模型输出已是$$...$$块级公式则原样返回;若是$...$行内公式则剥掉单美元符号后包成块级;否则直接包裹$$...$$,从而让 GT、Base、LoRA 三列以一致的 LaTeX 块级格式输出对比。
对比主流程
ds = load_dataset("linxy/LaTeX_OCR", "synthetic_handwrite") ds = ds.shuffle(seed=222) test_split = ds["test"].select(range(NUM_TEST_SAMPLES))主流程加载synthetic_handwrite子集的测试划分,shuffle(seed=222,与训练脚本保持一致)后取前 5 个样本,对每个样本依次打印GT(真实标签)、Base(基础模型输出)、LoRA(微调模型输出)。若 LoRA 目录不存在或加载失败,脚本会捕获异常并降级为"仅对基础模型推理对比",保证评估流程不中断。
微调效果分析与调参经验
Qwen3-VL-30B-A3B-Instruct 的训练曲线
使用 batch size 为 8 训练Qwen3-VL-30B-A3B-Instruct的监控图表如下。从图中可以看到 loss 基本处于稳定下降状态,grad_norm 初期快速下降后趋于平缓,learning_rate 随调度线性衰减,证明训练在稳定拟合数据集。
Qwen3-VL-4B-Instruct 不同 batch size 的对比
作者分别用 batch size 为 1 和 8 训练了 4B 模型。对比微调前后效果可以发现:batch size 为 1 训练出的模型提取效果较差(疑似过拟合),batch size 为 8 训练出的模型效果明显更好。下图是 4B 模型 batch size 为 8 时的微调前后输出对比示例——LoRA 微调后模型对公式符号、变量与张量指标的还原显著更接近 GT,而基础模型则存在符号写错、变量错乱等问题。
训练经验总结与过拟合讨论
从微调前后效果对比可以看出,30B-A3B 模型在部分示例上微调后有明显提升,但作者也发现模型在微调后出现了一些问题——偶尔有示例不如微调前的模型,推测是过拟合所致,因为训练曲线显示训练轮次设置得有些偏多。
作者实际进行了多轮实验,调参过程非常有参考价值:
- 训练轮次:最初只设置 1 轮微调,效果不佳——微调前后模型输出几乎一模一样;2 轮也类似。随后逐步调大轮次,当轮次到 9 时,loss 不再持续下降、部分指标反而回升,因此最终确定训练轮次为 8;
- 数据集选择:原本想使用手写公式识别数据集,但训练过程中模型拟合不佳——手写数据集中同一个字符的写法千变万化,仅用少量数据训练很难收敛,于是换回非手写(印刷体合成)公式;
- batch size 的影响:batch size 对训练结果影响显著。从 4B 模型可看出,batch size 为 1 时效果较差(过拟合),batch size 为 8 时效果更好。
感兴趣的读者可以在此基础上继续探索其他参数组合,例如修改lora_rank、lora_alpha、学习率、batch_size等,并在 SwanLab 中对比不同实验的指标差异。
常见错误与解决办法
在复现过程中如果遇到如下报错:
pyarrow.lib.ArrowTypeError: Did not pass numpy.dtype object该错误通常由 numpy 版本问题引起,可执行以下命令修复:
pip install --upgrade numpy然后重新运行代码即可。仓库 requirements.txt 中也注明了"numpy 如果报错可以尝试对 numpy 进行更新"。
总结
本文完整复现了 Qwen3-VL 系列视觉语言模型在 LaTeX OCR 任务上的 LoRA 微调全流程:从硬件评估、依赖安装、数据集准备、ModelScope 模型下载,到 SwanLab 监控集成、LoRA 原理与参数配置、process_func多模态样本构造、自定义 DataCollator、动态模型架构加载与训练超参数调优,再到微调前后模型的推理对比评估。整个过程全部代码已开源在仓库的 05-Qwen3-VL-30B-A3B-Instruct Lora 可视化微调案例 - LaTexOCR 目录中,训练脚本为train_qwen3_vl.py、对比推理脚本为compare_qwen3_vl_infer.py,可基于自身显卡条件选择 4B 或 30B-A3B 模型直接复用,也可参照 Qwen3-VL-MoE-模型结构解析-Blog 进一步理解模型架构设计。
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考