tutorials

大语言模型微调实战:LoRA与QLoRA完整教程

LearnClub AI
February 28, 2026
5 min read

大语言模型微调实战: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
  • 太多易过拟合
  • 观察验证集损失

常见问题与解决

显存不足

解决方案:

  1. 使用QLoRA(4-bit量化)
  2. 减小batch size,增大梯度累积
  3. 减小max_seq_length
  4. 使用gradient checkpointing
# 启用gradient checkpointing
model.gradient_checkpointing_enable()

训练不稳定

解决方案:

  1. 降低学习率
  2. 增加warmup steps
  3. 使用bf16而非fp16
  4. 梯度裁剪

过拟合

解决方案:

  1. 增加lora_dropout
  2. 减小r值
  3. 早停(early stopping)
  4. 增加数据量

灾难性遗忘

解决方案:

  1. 混合原始数据进行训练
  2. 使用更小的学习率
  3. 冻结更多层

高级技巧

多轮对话微调

# 对话格式
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)

最佳实践

数据准备

  1. 质量优先:宁可数据少,也要质量好
  2. 多样性:覆盖各种场景
  3. 格式统一:一致的输入输出格式
  4. 数据清洗:去除噪声和重复

训练策略

  1. 从小开始:先用小模型/小数据验证
  2. 逐步扩展:验证有效后再放大
  3. 持续监控:观察训练损失和验证集表现
  4. 保存检查点:防止训练失败前功尽弃

评估方法

# 自动评估
from evaluate import load
bleu = load("bleu")
rouge = load("rouge")

# 人工评估
# - 准确性
# - 流畅性
# - 有用性
# - 安全性

总结

LoRA和QLoRA让大模型微调变得触手可及。关键要点:

  1. 选择合适方法

    • 资源充足:LoRA 16-bit
    • 资源有限:QLoRA 4-bit
  2. 数据质量为王

    • 高质量 > 大数量
    • 多样性覆盖
  3. 超参数调优

    • r=16-64常用
    • 学习率2e-4起步
    • 观察验证集
  4. 持续迭代

    • 快速实验
    • 评估反馈
    • 持续优化

微调是让大模型从通用走向专用的关键一步,掌握这项技术,你就能打造出专属于你的AI助手。


了解更多AI技术教程,请访问 LearnClub AI

Share this article