最近在尝试对Qwen3-VL-2B多模态大模型进行监督微调(SFT)和强化学习人类对齐(RLHF)时,遇到了不少坑。作为一个在AutoDL租用4090服务器进行Latex-OCR任务微调的实践者,我想把整个过程记录下来,希望能帮到有类似需求的同行。
这个项目主要使用了MS-Swift框架和vLLM推理引擎,针对LaTeX公式识别任务进行了完整的微调流程。下面我会从环境配置、数据处理、模型训练到部署应用,详细分享每个环节的实操经验和避坑指南。
在AutoDL平台上租用服务器时,有几个关键点需要注意:
GPU型号选择:虽然vGPU系列服务器价格便宜且显存大,但经常会遇到硬件适配问题。经过多次尝试,我发现RTX 4090是最稳定的选择,兼容性好且性能足够。
无卡开机技巧:可以先无卡开机修改代码,确认无误后再开启GPU,这样能节省不少费用。具体操作是在AutoDL控制台选择"无卡开机"选项。
实例复制功能:如果需要在不同服务器间迁移环境,可以直接复制实例。实测这个功能可以完整保留所有数据和环境配置,非常方便。
重要提示:如果训练时遇到
torch.distributed.elastic.multiprocessing.errors.ChildFailedError错误,建议直接更换服务器。这个错误通常与硬件兼容性有关,排查起来耗时耗力。
Qwen3-VL的环境依赖较为严格,以下是经过验证的配置方案:
bash复制conda create -n swift python=3.10
conda activate swift
pip install "transformers==4.57" "qwen_vl_utils==0.0.14"
pip install "ms-swift==4.0"
pip install "vllm==0.11.0"
版本控制是关键,特别是以下几个包的版本必须严格匹配:
这些包会自动安装torch、torchvision和modelscope等依赖项。使用vllm作为推理后端时,版本不匹配会导致各种难以排查的问题。
从ModelScope下载Qwen3-VL-2B-Instruct模型权重:
python复制from modelscope import snapshot_download
model_dir = snapshot_download('Qwen/Qwen3-VL-2B-Instruct',
cache_dir='/root/autodl-tmp/zz/model_weights')
原始数据集是parquet格式,虽然MS-Swift可以直接使用,但为了更好的可读性和调试,建议转换为jsonl格式。
SFT需要标准的对话格式,包含用户指令和助手回复:
json复制{
"messages": [
{
"role": "user",
"content": "<image>\nConvert this image to LaTeX."
},
{
"role": "assistant",
"content": "z _ { 1 } = r _ { 1 } ( \\cos \\theta _ { 1 } + i \\sin \\theta _ { 1 } )"
}
],
"images": [
"/path/to/image.png"
]
}
RLHF数据需要将回答放在单独的solution字段中:
json复制{
"images": ["/path/to/image.png"],
"messages": [
{
"role": "user",
"content": "Convert this image to LaTeX."
}
],
"solution": "z _ { 1 } = r _ { 1 } ( \\cos \\theta _ { 1 } + i \\sin \\theta _ { 1 } )"
}
转换脚本的核心逻辑是遍历parquet文件,提取图片和文本,然后按照上述格式重组数据。注意图片需要单独保存并记录路径。
使用MS-Swift进行SFT训练的关键参数:
bash复制PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
IMAGE_MAX_TOKEN_NUM=1024 \
CUDA_VISIBLE_DEVICES=0 \
swift sft \
--model /path/to/Qwen3-VL-2B-Instruct \
--dataset /path/to/train.jsonl \
--tuner_type lora \
--torch_dtype bfloat16 \
--num_train_epochs 2 \
--per_device_train_batch_size 4 \
--learning_rate 1e-4 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--freeze_vit true \
--gradient_checkpointing true \
--deepspeed zero3 \
--output_dir /path/to/outputs
关键参数说明:
tuner_type lora: 使用LoRA进行高效微调freeze_vit true: 冻结视觉编码器,只微调语言部分deepspeed zero3: 使用ZeRO-3优化显存使用per_device_train_batch_size或增加gradient_accumulation_stepstorch_dtype为float16训练完成后,可以使用以下脚本进行推理:
python复制from swift.infer_engine import TransformersEngine
engine = TransformersEngine(
model="/path/to/model",
adapters="/path/to/lora_checkpoint",
max_batch_size=2
)
response = engine.infer([{
"role": "user",
"content": "<image>\nConvert this image to LaTeX.",
"images": ["/path/to/image.png"]
}])
对于LaTeX OCR任务,我设计了一个综合考虑准确性和复杂度的奖励函数:
python复制class ComplexLatexRewardORM(ORM):
def __call__(self, completions, solutions):
rewards = []
for gen, sol in zip(completions, solutions):
# 标准化处理
gen_clean = re.sub(r"\s+", "", gen)
sol_clean = re.sub(r"\s+", "", sol)
# 基础相似度
sim = SequenceMatcher(None, gen_clean, sol_clean).ratio()
# 复杂度奖励
complexity = self._complexity_bonus(gen_clean)
# 综合评分
reward = 0.9 * sim + 0.1 * complexity
rewards.append(reward)
return rewards
这个函数会:
bash复制swift rollout \
--model /path/to/sft_model \
--infer_backend vllm \
--max_new_tokens 2048
bash复制swift rlhf \
--rlhf_type grpo \
--model /path/to/sft_model \
--reward_funcs external_r1v_acc format \
--dataset /path/to/rlhf_data.jsonl \
--num_generations 4 \
--beta 0.001
关键点:
beta参数控制KL散度的权重num_generations决定每个样本生成多少候选部署前需要合并LoRA权重到基础模型:
bash复制swift export \
--adapters /path/to/lora_checkpoint \
--merge_lora true
服务端:
bash复制swift deploy \
--model /path/to/merged_model \
--infer_backend vllm \
--vllm_gpu_memory_utilization 0.7
客户端调用:
python复制client = InferClient(host='127.0.0.1', port=8000)
response = client.infer([{
"role": "user",
"content": "<image>Convert to LaTeX",
"images": ["/path/to/image.png"]
}])
经过完整的SFT和RLHF流程,模型在LaTeX OCR任务上的准确率提升了约15%。几点关键体会:
对于想要尝试多模态微调的同行,建议从小数据集开始,验证流程后再扩展。MS-Swift框架虽然学习曲线较陡,但一旦掌握能极大提升效率。