跳转至

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. 安装依赖

pip install transformers datasets accelerate peft trl bitsandbytes

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)

启动:

python train_qlora.py

在 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. 调优建议

显存还是不够?

  1. 减小 per_device_train_batch_size 到 1
  2. 增大 gradient_accumulation_steps
  3. 减小 MAX_LENGTH(很多任务 512 就够了)
  4. 减小 r(16 → 8)

速度太慢?

  1. 关掉 gradient_checkpointing(如果显存够)
  2. 用 Flash Attention 2:attn_implementation="flash_attention_2"
  3. 升级 PyTorch 和 bitsandbytes 到最新版

效果不达预期?

  1. 增大 r 到 32 或 64
  2. 增加 target_modules
  3. QLoRA 用 nf4 + 双量化(不要换 fp4)
  4. bnb_4bit_compute_dtypebfloat16float16 更稳定

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 状态下合并

评论