大语言模型微调实战:LoRA与QLoRA完整教程
大语言模型(LLM)微调是将通用模型适配到特定任务或领域的关键技术。本文将详细介绍当前最流行的微调方法:LoRA(Low-Rank Adaptation)和QLoRA,并提供完整的实战代码和最佳实践。
为什么需要微调
预训练 vs 微调
预训练模型:
- 通用知识丰富
- 但特定领域能力有限
- 可能产生幻觉
- 回答风格不符合需求
微调优势:
- 领域知识增强
- 输出格式控制
- 减少幻觉
- 特定任务优化
微调 vs RAG
| 方案 | 适用场景 | 成本 | 效果 |
|---|---|---|---|
| RAG | 知识频繁更新、事实性问题 | 低 | 事实准确 |
| 微调 | 风格、格式、特定领域推理 | 高 | 深度适配 |
| 结合 | 复杂应用 | 中 | 最佳效果 |
微调方法对比
1. 全参数微调(Full Fine-tuning)
原理:
- 更新模型所有参数
- 需要大量显存
- 容易过拟合
适用:
- 数据量大
- 计算资源充足
- 基座模型较小
缺点:
- 显存需求巨大(LLaMA-7B需>80GB)
- 训练时间长
- 灾难性遗忘
2. LoRA(Low-Rank Adaptation)
核心思想:
冻结预训练权重 W
学习低秩分解 ΔW = A × B
其中 A ∈ R^(d×r), B ∈ R^(r×k), r << min(d,k)
前向传播:h = W × x + ΔW × x = W × x + A × B × x
优势:
- 参数量减少99%
- 显存需求大幅降低
- 训练速度快
- 可切换不同LoRA
适用场景:
- 大多数微调任务
- 消费级GPU
- 快速实验
3. QLoRA(Quantized LoRA)
创新点:
- 4-bit量化基座模型
- 双量化(Double Quantization)
- 分页优化器(Paged Optimizers)
- LoRA微调
显存优化:
LLaMA-65B全参数:>780GB
LoRA 16-bit:~160GB
QLoRA 4-bit:~40GB(单卡可训!)
适用场景:
- 超大模型微调
- 有限显存
- 生产部署
环境准备
硬件要求
LoRA:
- GPU:RTX 3090/4090(24GB)
- 内存:32GB+
- 存储:100GB+
QLoRA:
- GPU:RTX 3090/4090(24GB)可训65B模型
- 内存:64GB+
- 存储:200GB+
软件环境
# 创建环境
conda create -n llm python=3.10
conda activate llm
# 安装依赖
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# 安装Transformers和PEFT
pip install transformers datasets accelerate peft
# 安装QLoRA相关
pip install bitsandbytes
# 安装训练工具
pip install trl wandb
LoRA实战
1. 准备数据
# 数据格式示例
dataset = [
{
"instruction": "解释什么是机器学习",
"input": "",
"output": "机器学习是人工智能的一个分支..."
},
{
"instruction": "翻译以下句子",
"input": "Hello, world!",
"output": "你好,世界!"
}
]
# 数据预处理
def format_instruction(sample):
return f"""### Instruction:
{sample['instruction']}
### Input:
{sample['input']}
### Response:
{sample['output']}"""
2. LoRA配置
from peft import LoraConfig, get_peft_model, TaskType
# LoRA配置
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=16, # LoRA秩
lora_alpha=32, # 缩放参数
lora_dropout=0.1,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
]
)
# 应用LoRA
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
# 输出: trainable params: 33,554,432 || all params: 6,771,970,048 || trainable%: 0.4956
3. 完整训练代码
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
# 1. 加载模型和分词器
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
# 2. 配置LoRA
peft_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, peft_config)
# 3. 准备数据
dataset = load_dataset("json", data_files="train.json", split="train")
def tokenize_function(examples):
return tokenizer(
examples["text"],
truncation=True,
max_length=512,
padding="max_length"
)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# 4. 训练参数
training_args = TrainingArguments(
output_dir="./lora_results",
overwrite_output_dir=True,
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
warmup_steps=100,
logging_steps=10,
save_steps=500,
fp16=True,
optim="adamw_torch"
)
# 5. 训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
trainer.train()
# 6. 保存模型
model.save_pretrained("./lora_adapter")
QLoRA实战
1. 4-bit量化配置
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import prepare_model_for_kbit_training
# 4-bit量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True, # 嵌套量化
bnb_4bit_quant_type="nf4", # 4-bit Normal Float
bnb_4bit_compute_dtype=torch.bfloat16
)
# 加载量化模型
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-13b-hf",
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
# 准备模型用于训练
model = prepare_model_for_kbit_training(model)
2. 完整QLoRA训练
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
# LoRA配置
peft_config = LoraConfig(
r=64,
lora_alpha=16,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
# 训练参数
training_arguments = TrainingArguments(
output_dir="./qlora_results",
num_train_epochs=1,
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
optim="paged_adamw_32bit", # QLoRA专用优化器
save_steps=100,
logging_steps=10,
learning_rate=2e-4,
weight_decay=0.001,
fp16=False,
bf16=True,
max_grad_norm=0.3,
warmup_ratio=0.03,
group_by_length=True,
lr_scheduler_type="cosine"
)
# 使用TRL的SFTTrainer
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=2048,
tokenizer=tokenizer,
args=training_arguments,
packing=True # 提升训练效率
)
# 训练
trainer.train()
# 保存
model.save_pretrained("./qlora_adapter")
模型合并与部署
合并LoRA权重
from peft import PeftModel
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.float16,
device_map="auto"
)
# 加载LoRA适配器
model = PeftModel.from_pretrained(base_model, "./lora_adapter")
# 合并权重(可选,用于部署)
model = model.merge_and_unload()
# 保存完整模型
model.save_pretrained("./merged_model")
推理部署
# 加载微调后的模型
def load_model(adapter_path):
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.float16,
device_map="auto"
)
model = PeftModel.from_pretrained(base_model, adapter_path)
return model
# 推理
model = load_model("./lora_adapter")
model.eval()
prompt = "### Instruction:\n解释什么是深度学习\n\n### Response:\n"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=256,
temperature=0.7,
top_p=0.9,
do_sample=True
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)
超参数调优
LoRA关键参数
r(秩):
- 8-32:简单任务
- 32-128:复杂任务
- 越大表达能力越强,但易过拟合
lora_alpha:
- 通常 = 2*r
- 控制LoRA权重缩放
- 越大影响越大
lora_dropout:
- 0.05-0.1
- 防止过拟合
目标模块:
# 不同模型的目标模块
# LLaMA/Qwen
["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
# ChatGLM
["query_key_value", "dense", "dense_h_to_4h", "dense_4h_to_h"]
训练超参数
学习率:
- LoRA:1e-4 到 1e-3
- QLoRA:2e-4 常用
- 太大不稳定,太小收敛慢
Batch Size:
- 根据显存调整
- 配合梯度累积
- 实际batch = per_device_bs × gradient_accumulation × num_gpus
Epochs:
- 通常1-3个epoch
- 太多易过拟合
- 观察验证集损失
常见问题与解决
显存不足
解决方案:
- 使用QLoRA(4-bit量化)
- 减小batch size,增大梯度累积
- 减小max_seq_length
- 使用gradient checkpointing
# 启用gradient checkpointing
model.gradient_checkpointing_enable()
训练不稳定
解决方案:
- 降低学习率
- 增加warmup steps
- 使用bf16而非fp16
- 梯度裁剪
过拟合
解决方案:
- 增加lora_dropout
- 减小r值
- 早停(early stopping)
- 增加数据量
灾难性遗忘
解决方案:
- 混合原始数据进行训练
- 使用更小的学习率
- 冻结更多层
高级技巧
多轮对话微调
# 对话格式
def format_chat_template(messages):
formatted = ""
for message in messages:
if message["role"] == "system":
formatted += f"<|system|>\n{message['content']}\n"
elif message["role"] == "user":
formatted += f"<|user|>\n{message['content']}\n"
elif message["role"] == "assistant":
formatted += f"<|assistant|>\n{message['content']}\n"
return formatted
多任务学习
# 不同任务的适配器
task_a_adapter = "./adapters/task_a"
task_b_adapter = "./adapters/task_b"
# 动态切换
model.set_adapter("task_a")
# 推理...
model.set_adapter("task_b")
模型量化导出
# 导出为GGUF(用于llama.cpp)
# 或使用CTranslate2加速
# vLLM部署
from vllm import LLM, SamplingParams
llm = LLM(model="./merged_model", quantization="awq")
sampling_params = SamplingParams(temperature=0.7, top_p=0.9)
outputs = llm.generate(prompts, sampling_params)
最佳实践
数据准备
- 质量优先:宁可数据少,也要质量好
- 多样性:覆盖各种场景
- 格式统一:一致的输入输出格式
- 数据清洗:去除噪声和重复
训练策略
- 从小开始:先用小模型/小数据验证
- 逐步扩展:验证有效后再放大
- 持续监控:观察训练损失和验证集表现
- 保存检查点:防止训练失败前功尽弃
评估方法
# 自动评估
from evaluate import load
bleu = load("bleu")
rouge = load("rouge")
# 人工评估
# - 准确性
# - 流畅性
# - 有用性
# - 安全性
总结
LoRA和QLoRA让大模型微调变得触手可及。关键要点:
-
选择合适方法:
- 资源充足:LoRA 16-bit
- 资源有限:QLoRA 4-bit
-
数据质量为王:
- 高质量 > 大数量
- 多样性覆盖
-
超参数调优:
- r=16-64常用
- 学习率2e-4起步
- 观察验证集
-
持续迭代:
- 快速实验
- 评估反馈
- 持续优化
微调是让大模型从通用走向专用的关键一步,掌握这项技术,你就能打造出专属于你的AI助手。
了解更多AI技术教程,请访问 LearnClub AI。