LLM微调实战:用LoRA训练专属模型

LLM微调实战:用LoRA训练专属模型
LLM微调实战:用LoRA训练专属模型

一、为什么需要LoRA?

大语言模型(LLM)虽然强大,但针对特定业务场景(如法律问答、医疗咨询、代码生成)进行微调时,全量微调存在明显痛点:

首先,全量微调会改变所有参数,导致需要保存与原始模型等量的完整检查点,存储成本极高。其次,训练时需要将整个模型加载到显存中,以LLaMA-7B为例,仅参数就需要约14GB的FP16显存,加上优化器状态、梯度等,单卡A100(80GB)才能勉强运行。更不用说超大规模模型了。

LoRA(Low-Rank Adaptation)的提出解决了这一难题。它基于一个关键洞察:预训练语言模型在适应下游任务时,参数更新矩阵通常具有较低的“内在秩”。因此,LoRA冻结原始权重,在Transformer层的权重矩阵旁注入两个低秩矩阵A和B(如图1所示),仅训练这两个小矩阵就能有效模拟权重更新。

这种设计带来多重好处:

  • 显存占用低:仅需存储低秩矩阵(通常r=8或16),可降低约2/3的显存需求。
  • 训练速度快:可学习参数量减少至原始模型的0.1%~1%,大幅加速收敛。
  • 多任务灵活:不同任务的LoRA权重可以独立保存,切换时仅需加载不同的适配器文件,无需重复存储基础模型。
  • 无推理延迟:推理时可将LoRA权重合并回原始模型,不增加额外计算量。

二、环境与库准备

本文基于Python 3.10+,主要依赖以下库(建议使用conda创建虚拟环境):

pip install torch==2.1.0 transformers==4.36.0 datasets==2.16.0 peft==0.7.0 accelerate==0.25.0 bitsandbytes==0.42.0

同时推荐安装trl库用于RLHF或SFT(Supervised Fine-Tuning),但本文仅做标准监督微调。

硬件方面:至少需要一块显存≥16GB的GPU(如RTX 4090或A100),若使用量化加载(4-bit或8-bit)可进一步降低门槛。

三、数据准备与预处理

我们以中文法律问答微调为例,构建自定义数据集。假设数据为JSONL格式,每条包含指令(instruction)和输出(output)。LoRA微调通常要求数据格式与模型预训练时的对话模板一致。以Llama-2-chat为例,其模板为:

[INST] <<SYS>>n你是中国法律助手,请回答以下问题。n<</SYS>>nn{instruction} [/INST] {output}

使用datasets加载并处理数据:

from datasets import load_dataset
def process_func(example):
    # 构建对话模板
    text = f"[INST] <<SYS>>n你是中国法律助手。n<</SYS>>nn{example['instruction']} [/INST] {example['output']}"
    return {"text": text}
dataset = load_dataset("json", data_files="law_data.jsonl")
dataset = dataset.map(process_func)
dataset = dataset["train"].train_test_split(test_size=0.1)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]

四、加载基础模型与分词器

选择Llama-2-7b-chat-hf作为基础模型(可从Hugging Face Hub下载,需申请访问权限)。使用4-bit量化以降低显存:

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf", trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token  # 设置padding token
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-chat-hf",
    quantization_config=quant_config,
    device_map="auto",
    trust_remote_code=True
)

五、配置LoRA参数并包装模型

使用PEFT库的LoraConfigget_peft_model注入低秩矩阵:

from peft import LoraConfig, get_peft_model, TaskType
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,  # 因果语言模型
    r=8,                           # 低秩维度
    lora_alpha=32,                 # 缩放系数,通常为2*r
    lora_dropout=0.1,              # dropout防止过拟合
    target_modules=["q_proj", "v_proj"],  # 注入的模块(不同模型名称不同)
    bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # 输出可训练参数量
# 输出类似:trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.062

关键参数说明:

  • r:低秩矩阵的秩,值越大模型容量越高,但训练成本也增加。常见选择4、8、16。
  • lora_alpha:控制低秩矩阵的权重更新幅度,实际有效学习率与lora_alpha / r相关。
  • target_modules:不同模型名称不同,Llama-2的query和value投影通常记为q_projv_proj,也可注入所有线性层。

六、训练参数与开始训练

使用TrainingArgumentsTrainer开启训练:

from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
    output_dir="./lora-law-model",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    num_train_epochs=3,
    logging_steps=50,
    save_steps=500,
    evaluation_strategy="steps",
    eval_steps=500,
    learning_rate=2e-4,
    fp16=True,                     # 半精度加速
    remove_unused_columns=False,
    report_to="none",              # 不向wandb等平台报告
)
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    tokenizer=tokenizer,
    data_collator=lambda data: tokenizer.pad(
        tokenizer([d["text"] for d in data], truncation=True, max_length=1024),
        return_tensors="pt"
    ),
)
trainer.train()

训练完成后,LoRA适配器保存在./lora-law-model目录中。注意全量模型依然为原始Llama-2权重,仅保存约30MB的适配器文件。

七、合并权重或独立推理

PEFT支持两种推理方式:

  • 独立适配器推理:不修改原始模型,加载LoRA权重即可。适用于多任务切换。
  • 合并权重:将LoRA矩阵合并回原始权重,得到全新微调模型,推理时零额外开销。

合并代码示例:

from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-chat-hf", device_map="auto", torch_dtype=torch.float16)
model = PeftModel.from_pretrained(base_model, "./lora-law-model")
merged_model = model.merge_and_unload()  # 合并
merged_model.save_pretrained("./merged-law-model")
tokenizer.save_pretrained("./merged-law-model")

八、性能评估与部署

微调后需在测试集上评估困惑度(perplexity)或人工抽样检查生成质量。同时可以对比LoRA微调与全量微调的效果差异——通常LoRA在数据量足够时能达到90%以上的性能。部署时使用text-generation-webui或vLLM等推理框架,加载合并后的模型即可快速提供API服务。

九、常见问题与调优建议

  • 过拟合:降低r值、增加lora_dropout、减少训练轮次。
  • 显存溢出:降低per_device_train_batch_size、使用gradient checkpointing(model.gradient_checkpointing_enable())。
  • 生成质量差:检查数据质量,确保指令格式匹配;可增大lora_alpha以放大更新幅度。

通过本文的实战,你已经掌握了用LoRA高效微调LLM的核心技能。无论是私有数据处理、垂直领域适配还是轻量化部署,LoRA都是当前最实用的技术之一。现在就动手为你的业务打造专属模型吧!

阅读剩余
THE END