一、为什么要在消费级显卡上微调7B模型

先说结论:LoRA + 4-bit量化,让单卡24GB微调7B模型从"想想而已"变成"今晚就能跑"

我最近接手一个垂直领域问答项目,基座模型是Qwen2.5-7B-Instruct。直接用它回答领域问题时,术语经常张冠李戴,格式也不稳定。全参微调7B至少需要80GB以上显存(AdamW + fp16梯度),我手上只有一张RTX 4090(24GB),所以LoRA成了唯一现实的选择。

LoRA的核心思想是冻结原模型权重,只在注意力层的部分矩阵旁注入低秩分解矩阵 $BA$,训练时只更新这两个小矩阵。参数量能降到原来的1%以下,显存占用和训练时间都大幅下降。QLoRA更进一步,把基座模型用4-bit NF4量化加载,进一步压缩显存。

这次实践的目标很明确:
- 单卡24GB能训得起来
- 微调后目标领域准确率显著提升
- 通用能力不能崩
- 推理延迟不能明显恶化

二、环境与版本

环境这块我踩过版本坑,先把确定能跑通的组合列出来:

# 关键版本
torch==2.4.0+cu121
transformers==4.46.3
peft==0.13.2
bitsandbytes==0.44.1
trl==0.12.1
datasets==3.1.0
accelerate==1.1.1
flash-attn==2.6.3

硬件:RTX 4090 24GB,系统Ubuntu 22.04,CUDA 12.1。

bitsandbytestorch 的版本匹配很重要,0.44.x 对 torch 2.4 支持比较稳。flash-attn 建议从源码装,wheel经常对不上CUDA版本。

三、方案设计

整体流程分四步:

  1. 数据准备:把业务数据整理成Alpaca格式(instruction/input/output),清洗掉超长和空样本,最终得到12,000条。
  2. 模型加载:4-bit量化加载基座,配置LoRA适配器。
  3. 训练:用 trlSFTTrainer,3 epoch,cosine调度。
  4. 推理与评估:合并适配器,对比微调前后的输出。

关键参数选择理由:
- r=16, lora_alpha=32:r太小欠拟合,太大容易过拟合且显存上升,16是7B模型的经验甜点。
- target_modulesq_proj, k_proj, v_proj, o_proj 四个注意力投影层,不动MLP。
- lora_dropout=0.05:小数据集上防过拟合。
- 学习率 2e-4,比全参微调高一个数量级,因为LoRA参数少。

四、核心实现

4.1 数据准备

```python
from datasets import load_dataset

def format_example(example):
if example.get("input"):
text = f"""### 指令:
{example['instruction']}

输入:

{example['input']}

回答:

{example['output']}"""
else:
text = f"""### 指令:
{example['instruction']}

回答:

{example['output']}"""
return {"text": text}

dataset = load_dataset("json", data_files="data/train.jsonl", split="train")
dataset = dataset.map(format_example)

过滤超长样本,max_length按tokenizer算,这里粗略按字符数

dataset = dataset.filter(lambda x: 50 数据数量。我一开始用了3万条含噪声的数据,效果反而不如清洗后的1.2万条。
4.
评估要分维度**。只看loss会骗人,必须同时看领域能力和通用能力,否则容易过拟合到目标分布上。

后续我打算试试 rsLoRADoRA,听说在相同r下效果更好。另外推理侧的适配器合并和量化部署也值得单独写一篇。有在折腾LoRA的朋友欢迎评论区交流,尤其是显存优化这块,坑是真的多。