跳转至

第四章: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

环境准备

pip install torch transformers peft datasets accelerate bitsandbytes trl

完整 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 参数

# 更大的 r 值可以提升效果,但增加参数
# r=8: 最小配置
# r=16: 推荐配置
# r=32: 高配置

# alpha 通常设为 r 的 2 倍
lora_alpha = r * 2

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 提供了更好的优化

下一步

下一章我们将学习如何评估微调效果并进行部署。