第四章:QLoRA 量化微调¶
QLoRA(Quantized Low-Rank Adaptation)结合了量化和 LoRA,使得在消费级显卡上微调大模型成为可能。
QLoRA 原理¶
核心技术¶
1. 4-bit NormalFloat (NF4) 量化
- 信息论最优的量化数据类型
- 适合正态分布的权重
2. 双重量化 (Double Quantization)
- 对量化常数再次量化
- 进一步减少显存占用
3. 分页优化器 (Paged Optimizers)
- 使用 CPU 内存处理梯度检查点
- 防止显存溢出
显存对比¶
| 模型大小 | 全量微调 | LoRA (FP16) | QLoRA (4bit) |
|---|---|---|---|
| 7B | ~100GB | ~16GB | ~6GB |
| 13B | ~200GB | ~30GB | ~10GB |
| 70B | ~1TB | ~160GB | ~48GB |
环境准备¶
完整 QLoRA 微调代码¶
1. 量化配置¶
import torch
from transformers import BitsAndBytesConfig
# 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
# 4-bit 量化
load_in_4bit=True,
# 量化数据类型
bnb_4bit_quant_type="nf4",
# 计算精度
bnb_4bit_compute_dtype=torch.float16,
# 双重量化
bnb_4bit_use_double_quant=True,
)
2. 加载量化模型¶
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen2-7B"
# 加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(
model_name,
trust_remote_code=True
)
# 加载 4-bit 量化模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
# 准备模型进行 k-bit 训练
from peft import prepare_model_for_kbit_training
model = prepare_model_for_kbit_training(model)
3. 配置 LoRA¶
from peft import LoraConfig, get_peft_model
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()
4. 训练配置¶
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./qlora_output",
# 训练参数
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
# 学习率
learning_rate=2e-4,
# 精度
fp16=True,
# 优化
optim="paged_adamw_8bit", # 分页优化器
lr_scheduler_type="cosine",
warmup_ratio=0.1,
# 梯度检查点
gradient_checkpointing=True,
# 日志
logging_steps=10,
save_steps=100,
save_total_limit=3,
report_to="none",
)
5. 使用 SFTTrainer¶
from trl import SFTTrainer
from datasets import load_dataset
# 加载数据
dataset = load_dataset("json", data_files="train.json", split="train")
# 格式化函数
def format_prompt(sample):
if sample.get("input"):
return f"### 指令:\n{sample['instruction']}\n\n### 输入:\n{sample['input']}\n\n### 回答:\n{sample['output']}"
return f"### 指令:\n{sample['instruction']}\n\n### 回答:\n{sample['output']}"
# 创建 trainer
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
formatting_func=format_prompt,
max_seq_length=512,
args=training_args,
)
# 训练
trainer.train()
# 保存
trainer.save_model("./qlora_output/final")
不同显卡配置建议¶
RTX 3060 (12GB)¶
# 配置
per_device_train_batch_size=1
gradient_accumulation_steps=16
max_seq_length=512
# 可训练模型
# - 7B 模型:可行
# - 13B 模型:需要更多优化
RTX 4090 (24GB)¶
# 配置
per_device_train_batch_size=4
gradient_accumulation_steps=4
max_seq_length=2048
# 可训练模型
# - 7B 模型:轻松
# - 13B 模型:可行
# - 34B 模型:需要优化
A100 (80GB)¶
# 配置
per_device_train_batch_size=8
gradient_accumulation_steps=2
max_seq_length=4096
# 可训练模型
# - 70B 模型:可行
Unsloth + QLoRA¶
Unsloth 对 QLoRA 有更好的优化:
from unsloth import FastLanguageModel
import torch
# 加载模型
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="Qwen/Qwen2-7B",
max_seq_length=2048,
dtype=torch.float16,
load_in_4bit=True, # 4-bit 量化
)
# 配置 LoRA
model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_alpha=32,
lora_dropout=0.05,
bias="none",
use_gradient_checkpointing="unsloth", # 优化的梯度检查点
)
# 训练速度提升约 2x
模型合并与导出¶
合并 LoRA 权重¶
from peft import PeftModel
from transformers import AutoModelForCausalLM
# 加载基座模型(FP16)
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2-7B",
torch_dtype=torch.float16,
device_map="auto"
)
# 加载 LoRA 权重
model = PeftModel.from_pretrained(base_model, "./qlora_output/final")
# 合并并卸载
merged_model = model.merge_and_unload()
# 保存合并后的模型
merged_model.save_pretrained("./merged_model")
tokenizer.save_pretrained("./merged_model")
导出为 GGUF¶
# 安装 llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make
# 转换模型
python convert.py ./merged_model --outfile model.gguf --outtype q4_k_m
性能对比¶
训练速度¶
| 方法 | 7B 模型 (samples/s) | 显存占用 |
|---|---|---|
| 全量微调 | 0.5 | ~100GB |
| LoRA (FP16) | 2.0 | ~16GB |
| QLoRA | 1.5 | ~6GB |
| Unsloth QLoRA | 3.0 | ~6GB |
效果对比¶
| 方法 | MMLU | HumanEval | 平均 |
|---|---|---|---|
| 原始模型 | 62.1 | 41.2 | 51.7 |
| LoRA | 63.5 | 43.8 | 53.7 |
| QLoRA | 63.2 | 43.5 | 53.4 |
QLoRA 效果与 LoRA 基本持平,但显存需求大幅降低。
最佳实践¶
1. 选择合适的量化类型¶
# 推荐配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # 信息论最优
bnb_4bit_compute_dtype=torch.float16, # 计算用 FP16
bnb_4bit_use_double_quant=True, # 双重量化
)
2. 调整 LoRA 参数¶
3. 监控显存¶
import torch
def print_gpu_memory():
for i in range(torch.cuda.device_count()):
allocated = torch.cuda.memory_allocated(i) / 1e9
reserved = torch.cuda.memory_reserved(i) / 1e9
print(f"GPU {i}: Allocated={allocated:.2f}GB, Reserved={reserved:.2f}GB")
# 在训练过程中调用
print_gpu_memory()
小结¶
- QLoRA 通过 4-bit 量化大幅降低显存需求
- 7B 模型可在 6GB 显存上微调
- 效果与标准 LoRA 基本持平
- Unsloth 提供了更好的优化
下一步¶
下一章我们将学习如何评估微调效果并进行部署。