逆元装置 ▍ INVERSION

丽·芙

CONSTRUCT — LIV
▍
意识海深度 · 回廊稳定
逆元装置 · 待机中
指挥官链接 · 未认证

LLM微调实战:LoRA与QLoRA原理与代码

公開日: 2025-04-28 00:00 742文字 4 min read

この投稿は「日本語」では表示できません。元の投稿を表示しています。
详解LoRA低秩适配与QLoRA量化微调,从理论推导到代码实现,用一张3090微调你自己的模型。

为什么需要参数高效微调?

全量微调一个 Llama-2-7B 模型大约需要 56GB 显存——通常需要多张 A100 才能跑。而 LoRA 只需要不到 1% 的可训练参数,显存需求降低到 16GB 以下,一张 RTX 3090 就足够。

LoRA:低秩适配

核心思想

LoRA(Low-Rank Adaptation)的核心假设是:模型适配过程中,权重更新矩阵 ΔW\Delta W 存在于一个低秩子空间中。

这意味着:

h=W0x+ΔWx=W0x+BAxh = W_0x + \Delta Wx = W_0x + BAx

其中 B∈Rd×rB \in \mathbb{R}^{d \times r},A∈Rr×dA \in \mathbb{R}^{r \times d},r≪dr \ll d(通常是 8 或 16)。

在推理时,W0+BAW_0 + BA 可以直接合并为一个大矩阵,零额外推理延迟。

实现

import torch
import torch.nn as nn

class LoRALayer(nn.Module):
    def __init__(self, in_features, out_features, rank=8, alpha=16):
        super().__init__()
        self.rank = rank
        self.alpha = alpha

        # 低秩矩阵:A用小幅值高斯初始化,B初始化为零
        self.A = nn.Parameter(torch.randn(in_features, rank) * 0.02)
        self.B = nn.Parameter(torch.zeros(rank, out_features))

        # 缩放因子
        self.scaling = alpha / rank

    def forward(self, x):
        return (x @ self.A @ self.B) * self.scaling

class LinearWithLoRA(nn.Module):
    def __init__(self, linear, rank=8, alpha=16):
        super().__init__()
        self.linear = linear  # 冻结的原始权重
        self.lora = LoRALayer(linear.in_features, linear.out_features, rank, alpha)

    def forward(self, x):
        return self.linear(x) + self.lora(x)

    def merge(self):
        # 将LoRA权重合并到原权重中
        self.linear.weight.data += (self.lora.A @ self.lora.B).T * self.lora.scaling

关键超参数

参数建议值说明
rank (r)8-16越大容量越高,但不一定提升效果
alpha16-32控制 LoRA 更新的缩放幅度
target_modulesq_proj, v_proj实验证明只适配 Query 和 Value 效果最好

QLoRA:量化 + 低秩适配

QLoRA 在 LoRA 基础上引入了三个关键创新:

4-bit NormalFloat(NF4)

NF4 是一种针对正态分布数据优化的 4-bit 量化方案。相比于传统的 INT4 量化,NF4 在处理 LLM 权重时具有更高的信息保真度。

双重量化

不仅量化模型权重,还对量化常数本身进行二次量化,进一步压缩 0.37 bits per parameter。

分页优化器

当遇到显存峰值时,自动将 optimizer state 分页到 CPU 内存:

from transformers import BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantization_config=bnb_config,
    device_map="auto"
)

实战:用 QLoRA 微调 Llama-2-7B

准备数据集

from datasets import load_dataset

dataset = load_dataset("json", data_files="my_safety_dataset.jsonl")

def format_instruction(example):
    return {
        "text": f"### Instruction:\n{example['instruction']}\n\n### Response:\n{example['response']}"
    }

配置 PEFT

from peft import LoraConfig, get_peft_model

lora_config = LoraConfig(
    r=8,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # trainable params: 4,194,304 / 6,738,415,616 = 0.062%

训练

from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
    output_dir="./lora-llama-safety",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,  # effective batch size = 16
    learning_rate=2e-4,
    logging_steps=10,
    save_strategy="epoch",
    fp16=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
)
trainer.train()

效果对比

在安全审查任务上(检测代码中的潜在漏洞),微调前后的对比:

指标原始 Llama-2-7BQLoRA 微调后
XSS 检测准确率0.430.81
SQL 注入检测准确率0.510.84
误报率0.310.12

仅增加 400 万可训练参数(0.06%),性能提升显著。这就是参数高效微调的魅力——用最小的资源成本获取最大的业务价值。