Interactive field guide · 2026

LORA

冻结巨人,训练一条轻巧的支流。

从低秩矩阵的数学直觉,到用 PEFT、TRL、LLaMA-Factory 与 Unsloth 完成一次可复现的微调。

约 45 分钟 基础 → 实战 含交互实验
SCROLL TO DECOMPOSE ↓

The motivation

01

不必搬动整座山。

全量微调会更新模型的每一个权重。LoRA 的关键发现是:面向具体任务时,真正需要学习的“变化”通常落在一个低维子空间里。

Full fine-tuning
7B

全部参数都训练

AdamW 训练 7B 模型,权重、梯度和优化器状态可能需要远超 56 GB 显存。

EXPENSIVE / FLEXIBLE
LoRA fine-tuning
~0.1%

只训练增量参数

冻结原模型,仅保存几 MB 到几百 MB 的适配器;同一基础模型可以挂载多个能力。

LIGHT / MODULAR
A useful mental model

把基础模型想成一位知识渊博的专家

你并不是重新教育这位专家,而是交给他一份很薄的“工作手册”:面对客服问题用何种语气、面对医疗文本输出什么格式、如何模仿一种画风。LoRA adapter 就是这份可插拔的手册。

LoRA 擅长

  • 风格、格式与指令遵循
  • 领域术语和任务行为适配
  • 多租户 / 多角色快速切换
  • 消费级 GPU 上的训练实验

LoRA 不等于

  • 可靠地注入大量新事实
  • 弥补基础模型没有的能力
  • 替代 RAG、工具调用与评测
  • “数据越多效果必然越好”
选型原则:想改变模型“怎么回答”,优先考虑微调;想补充模型“知道什么”,优先考虑 RAG;想让模型“完成动作”,考虑工具调用。三者可以组合。

Inside the matrix

02

关键理论:把变化压扁。

这一章只需要基础线性代数。先弄懂“秩”代表多少个独立方向,再看 LoRA 如何用一个窄瓶颈描述模型真正需要学习的变化。

First: what is rank?

秩,就是信息中“真正独立的方向”有多少个。

想象一群人在平面上移动:如果所有人都只能沿同一条直线前后走,虽然每个人走的距离不同,但变化只有 1 个独立方向,秩就是 1;如果他们既能左右走、又能上下走,就需要 2 个独立方向,秩就是 2。

矩阵也是一样。它可能有很多行和列,但其中一些只是另一些的缩放或组合。把这些重复关系剥掉后,剩下多少种无法互相替代的变化方式,就是这个矩阵的秩。

rank = 1

第二行只是第一行的 2 倍。看似有两行,实际上只提供一个独立方向。

rank = 2

两行无法由彼此缩放得到,分别代表两个独立方向。

Then: why low rank?

LoRA 假设:适配一个具体任务,不需要改动模型的所有方向。

一个预训练模型已经学会语言、知识和推理。微调客服语气或固定输出格式时,我们通常只需推动其中少量能力方向。于是 LoRA 不直接训练巨大的权重更新矩阵 ΔW,而让数据先经过一个降维矩阵 A,挤进只有 r 个维度的瓶颈,再由矩阵 B 展开回原空间。

r 是容量旋钮r 越大,可表达的独立更新方向越多,同时训练参数也越多。
低秩的是 ΔWLoRA 没有声称基础权重 W₀ 本身是低秩;W₀ 始终冻结并完整保留。
低秩不等于低质量任务需要的变化本来就可能很集中;关键是让 r 足以覆盖这些方向。
h = W₀x + BAx · α/r

W₀ 冻结;只训练 AB。其中 r 远小于输入、输出维度。

Rank laboratory

拖动秩 r

秩越高,适配器表达能力越强,但参数、显存与过拟合风险也会上升。

8
65,536单层可训练参数
0.39%相对原矩阵

低秩假设

预训练模型已经处于一个很好的解附近。下游任务所需的更新 ΔW 往往具有较低的“内在秩”,因此可以近似为 B · A

缩放因子 α / r

α 控制 LoRA 支路的整体强度。传统 LoRA 使用 α/r;Rank-Stabilized LoRA(rsLoRA)常用 α/√r,在高秩时更稳定。

初始化保证“无扰动”

常见做法是随机初始化 A、将 B 初始化为零。训练开始时 BA = 0,模型行为与原模型完全一致,再逐步学出增量。

Dropout 与目标层

lora_dropout 只作用于 LoRA 分支。目标层通常从 Attention 的 q_projv_proj 开始;数据充足时可扩展到所有线性层。

参数量公式:对于形状为 d × k 的线性层,原参数量为 d·k,LoRA 参数量为 r·(d+k)。例如 4096×4096、r=8 时,从 16,777,216 降为 65,536 个可训练参数。

The pipeline

03

一次训练,六个决定。

微调效果大多在启动训练之前就已经被决定:目标是否清晰、数据是否一致、模板是否匹配、评测是否可信。

定义行为与评测集

先写 50–200 条“模型应该如何回答”的独立测试样本。不要用训练 loss 代替真实任务评测。

格式正确率人工偏好任务指标

准备高质量数据

统一角色、语气和答案粒度;清理重复、冲突、隐私与低质量样本。几百条精心策划的数据常常胜过几万条噪声数据。

选择基础模型与模板

选择许可证、语言和能力匹配的 Instruct 模型,并严格使用其官方 chat template。模板错位是“训练成功但推理失效”的常见原因。

配置 LoRA / QLoRA

LoRA 冻结半精度基础模型;QLoRA 进一步把基础模型量化到 4-bit,LoRA 参数仍以 BF16/FP16 训练,从而显著降低显存。

训练与周期评测

监控训练 / 验证 loss、梯度范数、学习率和任务指标。每隔固定 steps 保存 adapter,不要默认最后一个 checkpoint 最好。

保存、合并与部署

可单独保存 adapter 动态挂载,也可用 merge_and_unload() 合并进基础权重以减少推理开销。合并前必须确保模型版本一致。

Choose your stack

04

常用框架,各司其职。

点击切换。它们不是完全互斥:常见组合是 Transformers + PEFT + TRL;LLaMA-Factory 和 Unsloth 则提供更高层的训练体验。

最适合:想理解并精确控制 LoRA 的开发者

Hugging Face PEFT

参数高效微调的事实标准库,提供 LoraConfig、adapter 注入、保存、加载、切换与合并能力。它负责“改造模型”,训练循环通常交给 Transformers Trainer 或 TRL。

  • API 清晰、生态成熟
  • 支持 LoRA / IA³ / AdaLoRA 等
  • 可组合多个 adapter
  • 适合编写可维护训练代码

最适合:SFT、DPO、GRPO 等后训练流程

Transformer Reinforcement Learning

在 Transformers 与 PEFT 之上提供 SFTTrainerDPOTrainer 等高级训练器。它会处理对话数据、packing、loss mask 和训练配置。

  • 监督微调 SFT
  • 偏好优化 DPO
  • 与 PEFT 原生集成
  • 研究与工程兼顾

最适合:快速实验、低代码与多模型统一配置

LLaMA-Factory

通过 YAML / CLI / WebUI 统一数百种模型的预训练、SFT、DPO 和量化。对初学者友好,也适合批量实验,但深度定制时仍需理解底层机制。

  • WebUI 可视化配置
  • 模型与数据集适配丰富
  • 内置评测和导出
  • 配置驱动、复现方便

最适合:显存紧张、追求单卡训练速度

Unsloth

通过定制 kernel 和计算图优化加速 LoRA / QLoRA,并降低显存占用。常与 TRL 配合,API 接近 Hugging Face 生态。

  • 单 GPU 体验优秀
  • 训练速度快、占用低
  • Notebook 示例丰富
  • 需检查模型支持范围

最适合:Stable Diffusion / FLUX 等生成图像模型

Diffusers

图像生成模型的 LoRA 训练与加载生态。文本模型 LoRA 多作用于线性层;扩散模型则常训练 UNet / Transformer 与文本编码器中的注意力层。

  • 文生图 LoRA
  • DreamBooth 数据流程
  • adapter 融合与权重调节
  • 与 Accelerate 集成

最适合:Apple Silicon 本地实验

MLX-LM

利用 Apple Silicon 统一内存,在 Mac 上完成量化推理与 LoRA 微调。适合学习和小规模实验,但部署生态与 CUDA 路线不同。

  • Mac 本地训练
  • 统一内存架构
  • 命令行开箱即用
  • 适合小模型与原型
你的场景推荐起点理由
第一次学习 LoRAPEFT + TRL概念与代码映射最清楚
24 GB 单卡训练 7B/8BQLoRA + Unsloth显存和速度更友好
需要大量 YAML 实验LLaMA-Factory统一配置、低代码复现
训练人物 / 画风Diffusers针对扩散模型的完整工具链
M 系列 Mac 本地学习MLX-LM原生利用 Apple Silicon

Hands on

05

动手:训练一个指令适配器。

下面以 Transformers + PEFT + TRL 为主线。示例结构适用于现代 Causal LM;实际运行时请按显卡和模型许可证替换模型名。

Single GPU field demo

RTX 4090
微调 7B 模型

目标:在单张 NVIDIA GeForce RTX 4090 上,用 4-bit QLoRA 对 Qwen2.5-7B-Instruct 做中文客服 SFT。

24GB GDDR6X / ADA
7BBASE MODEL
NF4BASE WEIGHT QUANT
BF16COMPUTE DTYPE
~14–20 GB典型峰值,依数据而变
为什么是 QLoRA:7B 模型仅 BF16 权重就约 14 GB,训练还需激活、梯度和优化器状态。NF4 将冻结的基础权重压到约 4–6 GB,把 24 GB 显存留给上下文与训练状态。

确认驱动与 BF16

先让 PyTorch 看到正确的显卡。RTX 4090 支持 BF16;若输出不是 4090 或 CUDA 为 false,先修复驱动 / PyTorch CUDA 版本,不要开始训练。

terminal · hardware check
nvidia-smi

python - <<'PY'
import torch
print("GPU :", torch.cuda.get_device_name(0))
print("CUDA:", torch.cuda.is_available())
print("BF16:", torch.cuda.is_bf16_supported())
PY

建立 4090 环境

在 Linux / WSL2 中创建独立环境。PyTorch 的 CUDA wheel 要和驱动兼容;若 cu128 不适合你的驱动,请在 PyTorch 安装选择器中换成匹配版本。

terminal · environment
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install torch --index-url https://download.pytorch.org/whl/cu128
pip install -U transformers datasets peft trl accelerate bitsandbytes

运行单卡 QLoRA

这个配置把 micro batch 固定为 1,再用 16 次梯度累积得到有效 batch 16;2048 token 是 24 GB 显存上相对稳妥的起点。

train_4090.py · complete demo
import torch
from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig,
)
from peft import LoraConfig, prepare_model_for_kbit_training
from trl import SFTConfig, SFTTrainer

MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"
OUTPUT_DIR = "outputs/qwen2.5-7b-4090-lora"

if not torch.cuda.is_available():
    raise RuntimeError("本 demo 需要 NVIDIA CUDA GPU")
print("GPU:", torch.cuda.get_device_name(0))

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

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    quantization_config=quant_config,
    torch_dtype=torch.bfloat16,
    device_map={"": 0},
    attn_implementation="sdpa",
)
model.config.use_cache = False
model = prepare_model_for_kbit_training(
    model, use_gradient_checkpointing=True
)

dataset = load_dataset(
    "json", data_files="train.jsonl", split="train"
)
splits = dataset.train_test_split(test_size=0.1, seed=42)

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    bias="none",
    task_type="CAUSAL_LM",
)

train_config = SFTConfig(
    output_dir=OUTPUT_DIR,
    num_train_epochs=2,
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=16,
    gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.03,
    max_grad_norm=0.3,
    bf16=True,
    tf32=True,
    optim="paged_adamw_8bit",
    max_length=2048,
    packing=False,
    eval_strategy="steps",
    eval_steps=50,
    save_steps=50,
    save_total_limit=2,
    logging_steps=5,
    report_to="none",
    seed=42,
)

trainer = SFTTrainer(
    model=model,
    args=train_config,
    train_dataset=splits["train"],
    eval_dataset=splits["test"],
    processing_class=tokenizer,
    peft_config=lora_config,
)
trainer.model.print_trainable_parameters()
trainer.train()
trainer.save_model(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
terminal · launch & monitor
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
  CUDA_VISIBLE_DEVICES=0 python train_4090.py

# 在另一个终端观察显存、功耗和利用率
watch -n 1 nvidia-smi

如果显存溢出,按这个顺序降级

先把 max_length 从 2048 降到 1536 或 1024;确认 batch size 已是 1 和 gradient checkpointing 已开启;再将目标层缩到 q/k/v/o;最后才降低 rank。序列激活通常比 LoRA 参数更占显存。

① sequence length② target modules③ rank
预期结果:最终目录只包含 adapter 与 tokenizer,通常远小于完整 7B 模型。先用第 05 步的推理代码对固定测试集做 A/B 对比,再考虑合并权重。显存数字会随驱动、依赖版本、样本长度和目标层变化,nvidia-smi 才是你的实际答案。

01 — 安装环境

terminal · Python 3.10+
python -m venv .venv
source .venv/bin/activate
pip install -U torch transformers datasets peft trl accelerate bitsandbytes

Windows 将激活命令换成 .venv\Scripts\activate。Apple Silicon 通常不使用 bitsandbytes;可走 MLX-LM 或非 4-bit 路线。

02 — 数据格式

推荐保存为 JSONL,每行一个样本。训练前再通过模型自带的 chat template 转成 token;不要手写 <|assistant|> 等特殊符号。

train.jsonl
{"messages":[{"role":"system","content":"你是严谨的产品客服。"},{"role":"user","content":"退款多久到账?"},{"role":"assistant","content":"原路退款通常需要 3–5 个工作日。"}]}
{"messages":[{"role":"system","content":"你是严谨的产品客服。"},{"role":"user","content":"如何修改收货地址?"},{"role":"assistant","content":"订单发货前,可在订单详情页选择「修改地址」。"}]}

03 — 完整 SFT 脚本

train_lora.py
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

model_id = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

dataset = load_dataset("json", data_files="train.jsonl", split="train")

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    bias="none",
    task_type="CAUSAL_LM",
)

args = SFTConfig(
    output_dir="outputs/customer-service-lora",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    logging_steps=5,
    save_strategy="epoch",
    bf16=True,
    max_length=1024,
    packing=False,
    report_to="none",
)

trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=dataset,
    processing_class=tokenizer,
    peft_config=peft_config,
)
trainer.train()
trainer.save_model(args.output_dir)
版本提示:Transformers / TRL 的参数名会随版本演进。先固定依赖版本并查看对应版本文档;若 API 报错,优先检查 SFTConfigSFTTrainer 签名。

04 — QLoRA 的关键差异

replace model loading block
from transformers import BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
    model_id, quantization_config=bnb_config, device_map="auto"
)

05 — 加载并推理

inference.py
from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto")
model = PeftModel.from_pretrained(base, "outputs/customer-service-lora")

messages = [{"role": "user", "content": "退款多久到账?"}]
inputs = tokenizer.apply_chat_template(
    messages, add_generation_prompt=True, return_tensors="pt"
).to(model.device)
output = model.generate(inputs, max_new_tokens=128, do_sample=False)
print(tokenizer.decode(output[0][inputs.shape[-1]:], skip_special_tokens=True))

Parameter calculator

适配器参数估算器

ESTIMATED TRAINABLE PARAMETERS
29.4M

仅权重约 56.0 MiB(FP16 / BF16)

假设每个目标层都是 d × d;实际值以 model.print_trainable_parameters() 为准。

Tune & debug

06

调参不是炼丹,是建立反馈回路。

从稳定的保守配置开始,每次只改变一类变量,并保留固定的基础模型对照组。

8–16

rank r

格式 / 风格任务的可靠起点。复杂迁移可试 32–64。

CAPACITY
2 × r

alpha

常见经验起点,不是定律。观察输出偏移与训练稳定性。

STRENGTH
1e-4

learning rate

通常从 1e-4~2e-4 起步;数据小或不稳定时降低。

STEP SIZE
0–0.05

dropout

大数据可为 0;小数据或过拟合时尝试 0.05–0.1。

REGULARIZE
1–3

epochs

先看验证集与真实任务曲线,不要机械追求更多轮。

DURATION
q+k+v+o

target modules

稳妥起点。追求容量时可加入 MLP,代价是更多参数。

LOCATION

故障排查矩阵

现象优先检查行动
loss 不下降target_modules / 数据 mask打印可训练参数;抽查 token 与 labels
训练后像没变化adapter 是否真正加载对比启用 / 禁用 adapter;检查路径和名称
输出乱码或不停重复chat template / EOS使用 tokenizer 官方模板;确认终止 token
训练集很好,真实问题很差过拟合 / 分布偏差早停、降 rank、加数据多样性
CUDA OOM序列长度与激活降 batch / length;梯度检查点;QLoRA
合并后效果不同基础模型版本 / dtype锁定 revision;高精度合并后再量化

发布前检查单

Knowledge check

一个 4096 × 4096 的线性层,使用 r=8 的 LoRA。可训练参数是多少?

选择一个答案,完成最后的矩阵检查。

Your next experiment

07

现在,做一个最小可行实验。

用 1–3B 模型、500 条高质量样本、r=16、固定的 100 条测试集开始。先跑通闭环,再扩大规模。

Day 01

建立基线

让基础模型直接回答评测集,保存原始输出和指标。这是判断 LoRA 是否真的创造价值的起点。

Day 02

训练第一个 adapter

只选择 q/k/v/o,训练 1–3 epochs;抽查数据模板与实际生成,不盯着 loss 自我感动。

Day 03

复盘,而不是立刻加大模型

把失败样本分组:知识缺失、指令误解、格式错误、幻觉、风格偏差。不同失败类型需要不同解法;下一轮实验只改一个变量。