QLoRA微调
QLoRA(Quantized LoRA)是 LoRA 的进阶版:将基座模型量化到 4bit 加载,再叠加 LoRA 适配器训练。这是消费级显卡上微调大模型的最实用方案。
1. QLoRA 解决了什么问题
LoRA 已经省了优化器状态和梯度的显存,但模型本身的权重仍然占大头:
- 7B 模型 fp16 加载需要 ~14GB
- 13B 模型 fp16 加载需要 ~26GB(消费级显卡放不下)
QLoRA 把基座模型用 4bit 量化加载:
- 7B 模型 4bit 加载只需 ~4GB
- 13B 模型 4bit 加载只需 ~7GB
- 70B 模型 4bit 加载只需 ~35GB(A100 40GB 也能跑)
代价是轻微的精度损失(论文显示几乎可忽略)。
2. QLoRA 三大核心技术
QLoRA 论文(华盛顿大学 2023)提出了三个关键创新:
NF4 量化(4-bit NormalFloat)
普通的 4bit 量化用线性区间,但神经网络权重通常服从正态分布。NF4 是专门为正态分布优化的 4bit 数据类型,精度比普通 4bit 高很多。
双重量化(Double Quantization)
量化时产生的"量化常数"本身也用 8bit 量化存储,进一步节省显存(每个参数省 0.37 bit)。
分页优化器(Paged Optimizers)
利用 NVIDIA 统一内存技术,在显存不够时把优化器状态自动换到 CPU 内存,避免 OOM。
3. 安装依赖
bitsandbytes 是关键,它实现了 4bit/8bit 量化。
4. QLoRA 训练完整脚本
新建 train_qlora.py:
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training, TaskType
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct" # 现在可以上 7B 了
DATA_PATH = "translate_data.jsonl"
OUTPUT_DIR = "./output_qlora"
MAX_LENGTH = 512
# === 4bit 量化配置 ===
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 启用 4bit 加载
bnb_4bit_quant_type="nf4", # NF4 数据类型
bnb_4bit_compute_dtype=torch.bfloat16, # 计算时的精度
bnb_4bit_use_double_quant=True, # 双重量化
)
# === 加载模型(4bit)===
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
device_map="auto",
)
# 准备 4bit 模型用于训练(启用梯度检查点等)
model = prepare_model_for_kbit_training(model)
# === LoRA 配置 ===
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: ~40M || all params: ~7B || trainable%: ~0.5
# === 数据预处理 ===
def preprocess(example):
user_content = example["instruction"]
if example.get("input"):
user_content += "\n" + example["input"]
messages = [
{"role": "user", "content": user_content},
{"role": "assistant", "content": example["output"]},
]
text = tokenizer.apply_chat_template(messages, tokenize=False)
encoded = tokenizer(text, truncation=True, max_length=MAX_LENGTH, padding=False)
encoded["labels"] = encoded["input_ids"].copy()
return encoded
dataset = load_dataset("json", data_files=DATA_PATH, split="train")
dataset = dataset.map(preprocess, remove_columns=dataset.column_names)
dataset = dataset.train_test_split(test_size=0.1, seed=42)
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
# === 训练参数 ===
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
warmup_ratio=0.05,
lr_scheduler_type="cosine",
bf16=True,
gradient_checkpointing=True, # 必开,进一步省显存
optim="paged_adamw_8bit", # 分页优化器
logging_steps=10,
eval_strategy="epoch",
save_strategy="epoch",
save_total_limit=2,
report_to="tensorboard",
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
data_collator=collator,
tokenizer=tokenizer,
)
trainer.train()
# 保存 LoRA 权重
model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
启动:
在 RTX 4090 (24GB) 上训练 7B 模型完全没问题。
5. 关键差异:QLoRA vs LoRA
| 配置项 | LoRA | QLoRA |
|---|---|---|
| 模型加载 | bf16/fp16 | 4bit 量化 |
| 显存(7B) | ~24GB | ~10GB |
| 训练速度 | 较快 | 略慢(量化反量化开销) |
| 效果 | 基线 | 接近基线 |
| 优化器 | adamw_torch |
paged_adamw_8bit |
代码上的关键改动只有两处:
1. 加 BitsAndBytesConfig 量化配置
2. 调用 prepare_model_for_kbit_training
6. 推理时的注意事项
QLoRA 训练后的 LoRA 权重,推理时仍然需要把基座模型量化加载:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel
BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct"
LORA_DIR = "./output_qlora"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL,
quantization_config=bnb_config,
device_map="auto",
)
model = PeftModel.from_pretrained(model, LORA_DIR)
messages = [{"role": "user", "content": "你好"}]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=128)
print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
7. 合并 QLoRA 权重
QLoRA 训练后的合并比普通 LoRA 麻烦一点:必须用 fp16/bf16 加载基座再合并,不能在 4bit 状态下合并:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct"
LORA_DIR = "./output_qlora"
MERGED_DIR = "./output_merged"
# 注意:合并时基座模型用 bf16 加载,不要量化
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL,
torch_dtype=torch.bfloat16,
device_map="cpu", # 显存不够就用 CPU 合并
)
model = PeftModel.from_pretrained(model, LORA_DIR)
model = model.merge_and_unload()
model.save_pretrained(MERGED_DIR, safe_serialization=True)
tokenizer.save_pretrained(MERGED_DIR)
8. 显存对比实测
以 RTX 4090 (24GB) 上微调 Qwen2.5-7B 为例:
| 配置 | batch_size | seq_len | 显存占用 |
|---|---|---|---|
| 全量微调 | OOM | - | 不可行 |
| LoRA (bf16) | 2 | 512 | ~22GB(紧张) |
| QLoRA (nf4) | 2 | 512 | ~10GB(轻松) |
| QLoRA + grad ckpt | 4 | 1024 | ~14GB |
QLoRA 让 24GB 卡能轻松微调 13B 模型,48GB 卡能微调 70B。
9. 调优建议
显存还是不够?
- 减小
per_device_train_batch_size到 1 - 增大
gradient_accumulation_steps - 减小
MAX_LENGTH(很多任务 512 就够了) - 减小
r(16 → 8)
速度太慢?
- 关掉
gradient_checkpointing(如果显存够) - 用 Flash Attention 2:
attn_implementation="flash_attention_2" - 升级 PyTorch 和 bitsandbytes 到最新版
效果不达预期?
- 增大
r到 32 或 64 - 增加
target_modules - QLoRA 用 nf4 + 双量化(不要换 fp4)
bnb_4bit_compute_dtype用bfloat16比float16更稳定
10. 何时选 QLoRA vs LoRA
| 情况 | 推荐 |
|---|---|
| 显存富裕(48GB+),想要最佳效果 | LoRA |
| 消费级显卡(24GB 及以下) | QLoRA |
| 想训练大模型(13B、70B) | QLoRA |
| 训练速度敏感 | LoRA |
| 后续要部署量化模型 | QLoRA(一致性好) |
总结
- QLoRA = 4bit 量化基座 + LoRA 适配器,是消费级显卡微调大模型的最佳方案
- 核心技术:NF4 量化、双重量化、分页优化器
- 24GB 显存可以微调 7B、13B 模型
- 关键代码:
BitsAndBytesConfig+prepare_model_for_kbit_training+paged_adamw_8bit - 合并权重时基座要用 bf16 加载,不能在 4bit 状态下合并