☰
Qwen3.5-4B LoRA微调实战:从数据清洗到显存优化全流程指南
2026/10/26 17:16:15 网站建设 项目流程

最近在业务场景里做 Qwen3.5-4B 的指令微调,第一次尝试踩了不少坑:Loss 不降、模型乱答、显存直接打满、训练到一半进程被杀。这周重新换了数据清洗策略和 LoRA 参数,调整了训练流程,总算把效果拉起来了。这篇文章把第二次尝试的完整流程、代码、调参记录和配套排错方案整理出来,不管你是第一次接触大模型微调,还是已经试过一轮但效果不理想,应该都能从中找到可复用的内容。

1. 背景与核心概念

1.1 为什么要微调 Qwen3.5-4B

Qwen3.5-4B 是一个 4B 参数规模的中文开源模型,相比 7B、14B 甚至更大体量的模型,它在单卡训练和推理上更友好,尤其是在 24G 显存的家用卡或云主机上也能跑起来。但通用模型在特定领域里的表现往往不够精准,比如让它回答企业内部知识库的问题、按照固定格式输出结构化内容、或者模仿某种客服语气,基座模型通常只能给出“通用但不够准确”的答案。

微调的目的就是让模型适配目标领域的分布。通过一批高质量、符合业务场景的“问题-答案”数据,把模型的输出习惯拉向目标任务。这里要区分两个概念:预训练和微调。预训练是从头学习语言规律,成本极高;微调是在已学好的语言能力基础上做“定向强化”,成本低很多,也是大多数团队落地大模型的主要方式。

1.2 全参微调与 LoRA 微调的区别

微调可以分成两大类:全参微调(Full Parameter Fine-Tuning)和参数高效微调(PEFT,Parameter-Efficient Fine-Tuning),LoRA 是 PEFT 中最常用的一种。

全参微调会更新模型的所有参数,效果上限通常更高,但对显存和计算资源的要求也高很多。以 Qwen3.5-4B 为例,仅仅把模型参数以 FP16 加载到显存,就至少需要 4B × 2 字节 ≈ 8GB,这还只是模型权重。优化器状态、梯度、中间激活值加起来,实际训练显存可能是模型权重的 3 到 6 倍,也就是 30GB 到 50GB。这也是为什么全参微调 4B 模型也需要多卡或大显存的原因。

LoRA 的核心思路是冻结原始模型权重,在 Transformer 的线性层旁边插入低秩矩阵。训练时只更新这些低秩参数,原始权重保持不变。这样可以大幅减少需要更新的参数量和优化器状态占用。以 4B 模型为例,如果只训练 LoRA 参数,显存占用可能从 40GB 降到 12GB 到 20GB,单张 24G 显卡就能跑起来。

对比维度全参微调LoRA 微调
训练参数量全部参数通常 0.1% ~ 1%
显存要求高,4B 模型约 30GB 以上较低,4B 模型约 12GB ~ 20GB
训练速度慢快
效果上限高接近全参微调,视数据量而定
部署方式替换整个模型基座模型 + LoRA 权重合并或动态加载
适用场景数据量充足、算力充足、追求极致效果数据量中等、单卡训练、快速迭代

第二次尝试我选择的是 LoRA 微调,主要原因就是显存可控、迭代快,而且对于垂直场景的数据规模(几千到几万条),LoRA 的效果已经非常接近全参微调。

1.3 LoRA 微调的常见应用场景

LoRA 微调在实际项目中覆盖的场景很广,常见的有:

  • 指令遵循优化:让模型严格按照指定格式输出,比如 JSON 结构、固定字段。
  • 垂直领域问答:医疗、法律、金融、教育等领域的专业问答。
  • 风格迁移:把答案改写成更口语化、更正式或更简洁。
  • 代码生成增强:针对特定语言或框架的代码补全。
  • 小样本适配:在少量业务数据基础上快速给模型“打补丁”。

这篇文章的实操部分围绕指令微调(Instruction Tuning)展开,也就是让模型学会“看到问题就按期望格式回答”的能力。

2. 环境准备与版本说明

2.1 硬件环境

微调 Qwen3.5-4B 的最低推荐配置是:单张 24G 显存的 GPU,例如 NVIDIA RTX 3090、4090、A5000 或云上的 T4/A10 实例。如果显存只有 16G,也能尝试,但需要开启 4bit 量化和梯度检查点(gradient checkpointing)。

我第二次尝试使用的环境如下:

  • GPU:NVIDIA 4090 24G
  • 显存:24GB
  • 系统:Ubuntu 22.04 LTS
  • Python:3.10
  • CUDA:12.1
  • PyTorch:2.1.2

这套组合在训练 Qwen3.5-4B + LoRA 时,batch size 可以开到 4 到 8,具体取决于序列长度和 LoRA 维度。

2.2 软件依赖

核心依赖库包括:

  • transformers:加载模型和 Tokenizer
  • peft:LoRA 配置与训练
  • datasets:数据处理
  • accelerate:分布式训练与混合精度
  • bitsandbytes:4bit/8bit 量化加载
  • trl:SFTTrainer 指令微调工具(可选但推荐)

安装命令如下:

pip install torch==2.1.2 --index-url https://download.pytorch.org/whl/cu121 pip install transformers peft datasets accelerate bitsandbytes trl

这里要提醒一点:版本不要盲目追求最新。transformers 版本太新可能导致某些 API 变化,比如TrainingArguments的参数名调整。建议在项目目录下维护一个requirements.txt,把实际验证过的版本固定下来。

2.3 模型下载与目录结构

Qwen3.5-4B 可以从官方仓库或镜像站点下载。建议先把模型下载到本地目录,再加载训练,避免每次启动都从网络拉取。

示例项目目录结构:

qwen-finetune/ ├── data/ │ └── train.jsonl ├── scripts/ │ ├── train_lora.py │ └── inference.py ├── models/ │ └── Qwen3.5-4B/ ├── output/ │ ├── lora_weights/ │ └── merged_model/ └── requirements.txt

这样做的好处是:数据、代码、模型权重、输出结果分层清晰,后续调试和迁移都方便。

3. 核心原理拆解:LoRA 与训练策略

3.1 LoRA 的数学原理

LoRA 的关键想法很简单:在预训练模型的线性层中,权重矩阵 W 是固定的,LoRA 在旁边增加两个低秩矩阵 A 和 B。前向计算变成:

y = Wx + BAx

其中 A 的维度是 r × d,B 的维度是 d × r,r 远小于 d。训练时只更新 A 和 B,W 保持冻结。这样做的好处是:

  • 训练参数量大幅减少。
  • 显存占用降低。
  • 推理时可以合并回原始权重,不增加推理延迟。

实际使用时,并不是所有层都加 LoRA,而是选择 Attention 层的 q、k、v、o 投影以及 MLP 层的某些线性层。通过target_modules参数控制。

3.2 关键超参数

LoRA 训练中最重要的几个超参数:

参数作用建议初始值
r低秩矩阵的秩,影响模型表达能力和参数量8 ~ 64
alpha缩放系数,相当于 LoRA 权重的学习率缩放r 的 1 ~ 2 倍
dropout防止过拟合0.05 ~ 0.1
learning_rate学习率,LoRA 通常比全参微调大一些1e-4 ~ 5e-4
batch_size批大小4 ~ 8
num_epochs训练轮数2 ~ 5
max_seq_len最大序列长度512 ~ 2048

第一次尝试时,我把r设为 32,alpha设为 64,学习率设为 1e-5,结果模型收敛非常慢。后来把学习率调到 2e-4,效果明显改善。这说明 LoRA 训练中学习率的选择和全参微调差异很大,不能照搬经验。

3.3 全参训练与微调对显存要求的区别

很多人关心“为什么我连一个 4B 模型都训练不起来”,原因在于显存中不仅放模型权重,还要放优化器状态、梯度和激活值。

以 Qwen3.5-4B 全参微调为例,粗略估算:

  • 模型权重(FP16):约 8GB
  • 梯度:约 8GB
  • Adam 优化器状态:权重、一阶动量、二阶动量各一份,约 24GB
  • 激活值:与 batch size 和序列长度直接相关,低则几 GB,高则几十 GB

加总之后,全参微调 4B 模型单卡 24G 几乎不够用,更不要说 7B、13B 模型。

而 LoRA 微调时:

  • 模型权重以 FP16 或 4bit 加载。
  • 梯度只需要在 LoRA 参数上计算,原始权重不产生梯度。
  • 优化器状态只保存 LoRA 参数,可能只有几十 MB 到几百 MB。
  • 激活值仍然会占用显存,但可以通过 gradient checkpointing 进一步降低。

这就是 LoRA 微调能在单张消费级显卡上跑 4B 模型的核心原因。

3.4 量化加载:让显存更宽裕

如果你的显卡显存只有 16G,可以考虑用 bitsandbytes 把模型以 4bit 或 8bit 方式加载,再叠加 LoRA。这种方式被称为 QLoRA。

4bit 量化会把模型权重压缩到原来的四分之一,模型显存占用从 8GB 降到 2GB 左右。虽然量化会带来少量精度损失,但 LoRA 训练本身是低精度适配,实际业务场景中效果损失往往可以接受。

需要注意:使用 4bit 量化后,训练速度会略慢,因为反量化过程需要额外的计算。如果显存充足,还是建议优先使用 FP16 加载。

4. 完整实战:Qwen3.5-4B LoRA 微调流程

4.1 准备训练数据

微调效果的上限由数据质量决定。第一次尝试失败,很大程度就是直接拿网上爬的原始数据喂给模型,很多问答对格式混乱、答案残缺。第二次我重新整理了数据,统一采用 JSONL 格式,每行一个样本。

指令微调数据的基本结构:

{ "instruction": "请根据以下问题给出回答。", "input": "什么是学习率?", "output": "学习率是控制模型参数更新步长的超参数,学习率过大可能导致训练震荡,过小则收敛缓慢。" }

训练文件data/train.jsonl示例:

{"instruction": "请用一句话解释什么是LoRA。", "input": "", "output": "LoRA是一种参数高效微调方法,通过低秩矩阵更新模型权重。"} {"instruction": "翻译成英文:今天天气很好。", "input": "", "output": "The weather is nice today."} {"instruction": "请列举三个健康的饮食习惯。", "input": "", "output": "1. 均衡摄入蔬菜水果;2. 控制油盐用量;3. 定时定量进餐。"}

这里要注意几个细节:

  • 数据量不是越多越好,但至少要覆盖目标场景的典型问题。
  • 如果让模型输出 JSON,每条 sample 的 output 必须是合法的 JSON 字符串,且格式一致。
  • 每条样本长度不要超过 max_seq_len,否则会被截断,导致答案不完整。

4.2 编写 LoRA 训练脚本

下面是基于transformers+peft+trl的完整训练脚本,保存为scripts/train_lora.py。

# 文件路径:scripts/train_lora.py import json import torch from datasets import Dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig ) from peft import ( LoraConfig, get_peft_model, prepare_model_for_kbit_training ) from trl import SFTTrainer # 模型路径,根据实际下载位置修改 model_path = "./models/Qwen3.5-4B" # ---------- 1. 加载 Tokenizer ---------- tokenizer = AutoTokenizer.from_pretrained( model_path, trust_remote_code=True ) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token # ---------- 2. 加载数据 ---------- def load_data(path): samples = [] with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue data = json.loads(line) instruction = data["instruction"] input_text = data.get("input", "") output = data["output"] if input_text: text = f"问题:{instruction}\n补充材料:{input_text}\n回答:{output}" else: text = f"问题:{instruction}\n回答:{output}" samples.append({"text": text}) return Dataset.from_list(samples) train_dataset = load_data("data/train.jsonl") # ---------- 3. 量化配置(24G 以下显存建议开启) ---------- bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.float16 ) # ---------- 4. 加载模型 ---------- model = AutoModelForCausalLM.from_pretrained( model_path, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, torch_dtype=torch.float16 ) # 准备 kbit 训练 model = prepare_model_for_kbit_training(model) # ---------- 5. LoRA 配置 ---------- lora_config = LoraConfig( r=16, lora_alpha=32, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # ---------- 6. 训练参数 ---------- training_args = TrainingArguments( output_dir="./output/checkpoints", per_device_train_batch_size=4, gradient_accumulation_steps=4, num_train_epochs=3, learning_rate=2e-4, fp16=True, logging_steps=10, save_steps=500, eval_strategy="no", save_total_limit=2, report_to="none", gradient_checkpointing=True, optim="paged_adamw_8bit", ) # ---------- 7. SFT 训练器 ---------- trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=train_dataset, args=training_args, max_seq_length=1024, dataset_text_field="text", ) trainer.train() # ---------- 8. 保存 LoRA 权重 ---------- model.save_pretrained("./output/lora_weights") tokenizer.save_pretrained("./output/lora_weights") print("训练完成,LoRA 权重已保存到 ./output/lora_weights")

这段代码里需要注意的参数:

  • load_in_4bit=True开启 4bit 量化,如果显存足够,可以改成False并用torch_dtype=torch.float16。
  • target_modules必须根据模型实际模块名调整。Qwen 系列通常包含q_proj、k_proj、v_proj、o_proj等,但不同版本命名可能有差异,建议先打印模型结构确认。
  • max_seq_length控制训练时序列的最大长度,超过部分会被截断。这个值要结合数据分布设置,不能太小,否则长答案会被截断。

4.3 运行训练

在项目根目录执行:

python scripts/train_lora.py

如果一切正常,日志中会输出可训练参数量,类似:

trainable params: 33,554,432 || all params: 4,021,723,136 || trainable%: 0.8345

这说明实际训练的参数量只占全部参数的不到 1%。

4.4 合并模型并推理

训练完成后,LoRA 权重保存在output/lora_weights。如果你希望得到一个完整的模型供后续部署,可以把 LoRA 权重合并回基座模型。

# 文件路径:scripts/merge_model.py import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel base_model_path = "./models/Qwen3.5-4B" lora_path = "./output/lora_weights" merged_path = "./output/merged_model" tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True ) model = PeftModel.from_pretrained(model, lora_path) model = model.merge_and_unload() model.save_pretrained(merged_path) tokenizer.save_pretrained(merged_path) print(f"合并后的模型已保存到 {merged_path}")

合并后的模型可以直接用常规方式加载推理,也可以部署到 vLLM、TGI 等推理框架中。

4.5 推理验证

写一个简单的推理脚本,验证微调效果:

# 文件路径:scripts/inference.py import torch from transformers import AutoModelForCausalLM, AutoTokenizer model_path = "./output/merged_model" tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True ) prompt = "问题:什么是LoRA?\n回答:" inputs = tokenizer(prompt, return_tensors="pt").to("cuda") with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=256, do_sample=True, temperature=0.7, top_p=0.9, ) response = tokenizer.decode(outputs[0], skip_special_tokens=True) print(response)

运行结果会打印微调后模型对“什么是LoRA”的回答。如果数据准备得好,输出通常比基座模型更符合业务语境。

5. 常见问题与排查思路

第二次尝试过程中,我仍然遇到了几个问题,这里整理成表格,方便大家对照排查。

问题现象常见原因解决思路
训练时 CUDA Out of Memory显存不足,batch size 太大,序列过长调小 batch size、开启梯度检查点、使用 4bit 量化
Loss 不下降学习率太小、数据格式错误、tokenizer 截断导致 label 错位调大学习率到 1e-4 ~ 5e-4,检查数据格式,确认 max_seq_length
训练很快但效果很差数据量过少,或者 LoRA r 设置太小增加数据量,尝试把 r 从 8 提升到 32 或 64
训练 Loss 下降但推理输出乱码特殊 token 未正确设置,或者推理时 prompt 格式与训练不一致检查 pad_token 和 eos_token,保持推理 prompt 格式和训练一致
加载模型报错本地模型文件不完整,或 transformers 版本不匹配重新下载模型,固定 transformers 版本
LoRA 层没有生效target_modules 设置错误,模型实际模块名不同打印模型结构,确认线性层名称后再设置

5.1 显存不足的排查顺序

如果训练启动后直接报 CUDA OOM,按以下顺序排查:

  1. 把per_device_train_batch_size改成 1。
  2. 开启gradient_checkpointing=True,并用gradient_accumulation_steps补偿 batch size。
  3. 开启 4bit 量化加载。
  4. 把max_seq_length减小,比如从 2048 降到 1024。
  5. 检查是否有其他进程占用显存,用nvidia-smi查看。

5.2 训练不收敛的排查方式

如果你发现 Loss 在 3 到 10 个 logging step 后基本不变,先不要急着调超参数,按下面顺序检查:

  • 确认训练数据中“问题-答案”分隔符和统一格式是否一致。
  • 确认 tokenizer 的 pad_token 是否设置,否则某些 API 会警告并可能影响掩码。
  • 确认 LoRA 参数已经生效,训练日志里应显示 trainable params 数量。
  • 尝试把学习率调大到 5e-4,观察 Loss 是否出现明显下降。

5.3 推理效果不佳的优化方向

训练完成但推理效果不理想,可以从数据、参数、提示词三个方向优化:

  • 数据方向:补充更多边界案例,修正答案格式不一致的问题。
  • 参数方向:提高 LoRA rank,增加训练轮数,调整学习率。
  • 提示词方向:推理时的 prompt 格式必须和训练数据完全一致,这一点最容易忽略。

6. 最佳实践与工程建议

微调并不是“把数据塞进去跑一轮”就结束的事情,工程上需要关注的点很多。这里整理几条对实际项目有帮助的建议。

6.1 数据质量优先于数据量

第一次尝试我用了 5 万条数据,效果反而不如第二次精挑细选后的 8000 条。关键原因是大模型微调对数据质量极其敏感,错误格式、重复样本、噪声答案会被模型“学进去”。

建议在训练前做一轮数据清洗:

  • 去重,尤其是完全相同的指令。
  • 过滤答案过短或过长的样本。
  • 统一答案格式,比如都要以句号结尾,或者都必须输出 JSON。
  • 剔除答案中包含大量原文复制的样本。

6.2 留出验证集

虽然 LoRA 训练可以在小数据集上快速完成,但最好还是从训练集中切出 5% 到 10% 作为验证集。验证集不参与训练,用来观察模型是否过拟合。

如果训练 Loss 持续下降,但验证 Loss 上升,说明过拟合了。这时可以增加 dropout、减少训练轮数、增加数据量。

6.3 保持训练与推理格式一致

模型在微调时学会的是“问题 + 分隔符 + 回答”的文本模式。如果推理时换了 prompt 格式,例如把“问题:”改成“请回答:”,模型可能输出不稳定。

建议在项目里定义一个统一的 prompt 模板,训练和推理共用同一个函数生成,避免手工复制导致格式漂移。

6.4 训练日志与实验记录

微调过程中超参数组合很多,r、alpha、lr、epochs 不同组合效果差异很大。建议每次实验都记录:

  • 数据版本和样本数。
  • 超参数配置。
  • 训练 Loss 曲线。
  • 验证 Loss。
  • 几个固定测试问题的输出样例。

这样后续调整时可以快速对比,而不是凭感觉重复实验。

6.5 模型保存与版本管理

LoRA 权重文件通常只有几十到几百 MB,非常适合用 Git LFS 或对象存储管理。建议不要每次都保存合并后的完整模型,节省空间;需要部署时再执行合并脚本。

同时,给每个模型版本打上标签,例如qwen3.5-4b-lora-v2-data8000-r16-lr2e-4,方便回溯。

6.6 生产环境部署的注意点

如果微调后的模型要上线服务,还需要关注几个问题:

  • 推理框架兼容性:合并后的模型是否支持 vLLM 等框架,需要提前验证。
  • 显存占用:4B 模型 FP16 推理约 8GB,加上 KV Cache,建议至少 16G 显存。
  • 稳定性:上线前用一批真实请求测试,观察是否出现长回答截断、重复生成等问题。
  • 回滚方案:保留基座模型权重,如果微调后效果不达标,可以随时回滚。

7. 总结与后续规划

第二次尝试相比第一次,核心变化有三点:第一,数据做了清洗和格式统一,样本量从 5 万降到 8000,但质量明显提升;第二,LoRA 参数从 r=32/lr=1e-5 调整为 r=16/lr=2e-4;第三,训练与推理的 prompt 格式保持完全一致,解决了第一次“训练时一个格式,推理时另一个格式”的问题。

目前微调后的模型在垂直问答、固定格式输出两个任务上已经达到业务可用水平。下一步准备继续做三件事:

  • 扩展数据覆盖范围,加入更多边界 case,提升模型泛化能力。
  • 对比不同 rank 值对效果和显存的影响,找到当前数据量下的最优配置。
  • 把 LoRA 权重合并后的模型接入 vLLM,测试生产环境下的推理性能和稳定性。

如果在阅读过程中遇到和本文类似的问题,建议先检查数据格式和 prompt 一致性,再动超参数。这两点解决了,LoRA 微调 Qwen3.5-4B 的成功率会高很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询