新品 NVIDIA B300 · 288 GB HBM3e 服务器 — 起价 $4,019/月 每块 GPU

查看 B300

文档 · 微调

用 LoRA 和 QLoRA 微调大模型

在您自己的 GPU 服务器上用 LoRA 或 QLoRA 微调开放 LLM:根据所需显存确定配置规模,用 TRL 或 Unsloth 训练适配器,然后将其合并,或直接用 vLLM 部署。

更新于 8 分钟阅读

LoRA、QLoRA 还是全参数微调:需要多少显存?

在 GPU 服务器上用 LoRA 或 QLoRA 微调 LLM,就是借助 Hugging Face 的 TRL 和 PEFT,在冻结的模型之上训练一个小型适配器。LoRA 让基础模型保持 BF16 精度,只训练附加在各层上的低秩矩阵;QLoRA 的做法相同,只是基础模型改为用 bitsandbytes 以 4-bit 加载。全参数微调则会更新每一个权重,并为所有权重保存梯度和优化器状态。CryptGPU 的选型规则把 GPU 显存的 92% 计为可用,并为激活值留出余量:LoRA 约为每 10 亿参数 3 GB,QLoRA 约为 0.75 GB,全参数微调约为 18 GB。

下表给出所需显存,以及能放下它的最便宜服务器的月价:

模型QLoRALoRA(BF16)全参数微调
8B6 GB:1× RTX 5080,$19924 GB:1× RTX 5090,$329144 GB:1× MI355X,$1,409
32B24 GB:1× RTX 5090,$32996 GB:4× RTX 5090,$1,316576 GB:4× MI355X,$5,636
70B53 GB:2× RTX 5090,$658210 GB:1× MI355X,$1,4091,260 GB:8× MI355X,$11,272
  • 先从 QLoRA 开始,在最小的服务器上检验您的数据和参数设置,如果质量有要求,再转向 BF16 下的 LoRA。
  • 单块 GPU 更简单。使用多块 GPU 时,模型需要拆分或分片(见下文),而且 GeForce 显卡之间要通过 PCIe 交换数据。70B 模型的 QLoRA 训练(53 GB)也可以放进一块 H100(80 GB,每月 $1,779)或一块 RTX PRO 6000(96 GB,$959),32B 模型的 LoRA 训练(96 GB)则可以放进一块 H200(141 GB,$2,279)。
  • MI355X 使用的是 ROCm。PyTorch、bitsandbytes、TRL 和 PEFT 都能在它上面运行(见下文)。如果只考虑 NVIDIA GPU,能放下 70B LoRA 训练的最便宜配置是 4× RTX PRO 6000,$3,836。

其他模型规模可以用选型助手查看,您的模型需要多少 GPU 显存一文解释了这条规则。按月租用的服务器在两次训练之间会保留您的数据和环境;如果只是一次短时间的训练,按小时计费的云平台可能更便宜。参见面向微调的 GPU 服务器。

配置环境

如果您下单时在“软件”步骤中选择了“微调工具包”(Axolotl、TRL 和 PEFT),它已经装好;下面介绍手动安装的方法。把整套软件安装在独立的 Python 环境中,并把下载内容保存在 NVMe 数据盘上(参见存储与数据)。加上 --torch-backend=auto 后,uv 会检测 GPU 驱动,并安装相匹配的 PyTorch 构建:NVIDIA 上为 CUDA 版,AMD 上为 ROCm 版(参见 uv 的 PyTorch 指南)。

curl -LsSf https://astral.sh/uv/install.sh | sh     # 然后开一个新终端,让 uv 出现在 PATH 中
uv venv --python 3.12 --seed --managed-python ~/ft-env
source ~/ft-env/bin/activate
uv pip install torch "trl[peft]" bitsandbytes --torch-backend=auto

export HF_HOME=/data/hf               # 模型、数据集和您的令牌都放在 NVMe 上
export HF_HUB_DISABLE_TELEMETRY=1     # 关闭 Hugging Face 库的使用情况遥测
mkdir -p /data/ft                     # 存放数据集、脚本和输出
hf auth login                         # 仅需要授权的模型(例如 Llama)才需要登录
trl env                               # 显示各库的版本和检测到的 GPU

把这两行 export 加入 ~/.bashrc,让设置长期生效。在 MI355X 上,请确认 python -c "import torch; print(torch.cuda.get_arch_list())" 的输出中包含 gfx950,或者在 rocm/pytorch 容器中工作(参见容器)。bitsandbytes 发布了包含 gfx950 的 ROCm wheel 包,AMD 的微调指南也是在 Instinct GPU 上使用 TRL 和 PEFT。

准备 JSONL 格式的对话数据集

TRL 的 SFTTrainer 会把每段对话读取为一个 messages 列表,列表中的每一项都包含 role 和 content,并且会自行套用模型的聊天模板(参见 TRL 数据集格式)。在 /data/ft/train.jsonl 中每行写一段对话:

{"messages": [{"role": "system", "content": "You answer customer emails briefly."}, {"role": "user", "content": "My order arrived damaged."}, {"role": "assistant", "content": "Sorry about that. Send a photo and we will ship a replacement."}]}
{"messages": [{"role": "user", "content": "Can I change the delivery address?"}, {"role": "assistant", "content": "Yes, until the order ships. Reply with the new address."}]}

请使用与模型实际会收到的请求相似的样本,去除重复项,并剔除您无权用于训练的数据。下文的训练脚本会留出 5% 的数据行用于评估。先通过聊天模板打印一条样本,检查角色和特殊 token 是否正确:

python - <<'EOF'
from datasets import load_dataset
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
row = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")[0]
print(tok.apply_chat_template(row["messages"], tokenize=False))
EOF

方案 A:用 TRL SFTTrainer 进行 QLoRA 或 LoRA 微调

这个脚本使用当前的 TRL API(TRL 1.16、PEFT 0.21),在单块 GPU 上微调 Qwen3-8B。QLORA = True 会用 bitsandbytes 以 4-bit NF4 格式加载基础模型。如果要改用 Llama 3.1,请先在 Hugging Face 上接受它的许可协议,运行 hf auth login,再修改 MODEL。

# /data/ft/train_sft.py:用 TRL 进行 LoRA 或 QLoRA 微调
import torch
from datasets import load_dataset
from peft import LoraConfig
from transformers import BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer

MODEL = "Qwen/Qwen3-8B"
QLORA = True                       # False:在 BF16 模型上做 LoRA
OUT = "/data/ft/qwen3-8b-lora"

data = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")
data = data.train_test_split(test_size=0.05, seed=42)

bnb = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
) if QLORA else None

lora = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                  target_modules="all-linear", task_type="CAUSAL_LM")

args = SFTConfig(
    output_dir=OUT,
    model_init_kwargs={"dtype": torch.bfloat16},
    max_length=2048,
    assistant_only_loss=True,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    per_device_eval_batch_size=2,
    num_train_epochs=2,
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_steps=0.03,             # 小于 1 的值表示占整个训练过程的比例
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=0.1,                # 每完成 10% 的训练就评估并保存一次
    save_steps=0.1,
    save_total_limit=3,
)

trainer = SFTTrainer(
    model=MODEL,
    args=args,
    train_dataset=data["train"],
    eval_dataset=data["test"],
    quantization_config=bnb,
    peft_config=lora,
)
trainer.train()
trainer.save_model(OUT)            # 保存 LoRA 适配器
tmux new -s ft                     # SSH 会话断开后,训练仍会继续
source ~/ft-env/bin/activate
cd /data/ft
CUDA_VISIBLE_DEVICES=0 python train_sft.py 2>&1 | tee train.log

这些数值只是起点:

  • target_modules="all-linear" 会在除输出层以外的所有线性层上添加适配器,这正是 TRL 的 LoRA Without Regret 指南推荐的做法;r=16 和 lora_alpha=32 是 TRL 命令行参数的默认值。
  • learning_rate=2e-4 是 TRL 针对 LoRA 监督微调(SFT)建议的学习率,为其全参数微调默认值的十倍(参见 PEFT 集成)。有效批量为 2 × 8 = 16 条序列,低于 TRL 针对 LoRA 建议的上限 32。Unsloth 的指南建议训练一到三个 epoch。
  • model_init_kwargs 让权重以 BF16 加载,否则 TRL 会以 float32 加载。梯度检查点和 BF16 训练默认都已开启。
  • assistant_only_loss=True 只在助手的回复上计算损失。这要求聊天模板中带有生成标记(generation markers),对于 Qwen3、Llama 3 等 TRL 已知的模型系列,TRL 会自动添加(参见 SFT Trainer)。
  • max_length 会截断更长的对话;TRL 的默认值是 1,024 个 token。

在多 GPU 服务器上,CUDA_VISIBLE_DEVICES=0 能让训练只在一块 GPU 上进行;如果直接用 python 运行,模型会被分散到所有 GPU 上。

方案 B:在单块 GPU 上使用 Unsloth

Unsloth 用自己的 Triton 内核替换模型的部分组件,从而在单块 GPU 上以更少的显存、更短的时间完成训练,而且可以直接接入同一个 SFTTrainer。请把它安装在独立的环境中:

uv venv ~/unsloth_env --python 3.13
source ~/unsloth_env/bin/activate
uv pip install unsloth --torch-backend=auto
# /data/ft/train_unsloth.py
from unsloth import FastLanguageModel
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="Qwen/Qwen3-8B",
    max_seq_length=2048,
    load_in_4bit=True,             # False:在 16-bit 基础模型上做 LoRA
)
model = FastLanguageModel.get_peft_model(
    model, r=16, lora_alpha=16, lora_dropout=0,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    use_gradient_checkpointing="unsloth",
)
data = load_dataset("json", data_files="/data/ft/train.jsonl", split="train")
data = data.map(lambda x: {"text": tokenizer.apply_chat_template(x["messages"], tokenize=False)},
                remove_columns=["messages"])
trainer = SFTTrainer(
    model=model,
    processing_class=tokenizer,
    train_dataset=data,
    args=SFTConfig(output_dir="/data/ft/unsloth", max_length=2048,
                   per_device_train_batch_size=2, gradient_accumulation_steps=8,
                   learning_rate=2e-4, num_train_epochs=1),
)
trainer.train()
model.save_pretrained("/data/ft/unsloth-lora")
tokenizer.save_pretrained("/data/ft/unsloth-lora")

这些数值遵循 Unsloth 的超参数指南(秩取 16 或 32,alpha 等于秩或秩的两倍,学习率 2e-4,有效批量 16)。model.save_pretrained_merged("/data/ft/unsloth-merged", tokenizer, save_method="merged_16bit") 会写出一个合并后的 16-bit 模型,供 vLLM 使用。Unsloth 的 AMD 指南列出了 Linux 上的 MI350 系列(gfx950),并单独给出了安装步骤。

在 2、4 或 8 块 GPU 上训练

这个 TRL 脚本也能在多块 GPU 上运行(请按照多 GPU 一文准备服务器)。当模型能放进单块 GPU 时,数据并行(DDP)会让每块 GPU 都持有一份完整的模型副本,并处理各自的批次;为每块 GPU 启动一个进程:

accelerate launch --num_processes 4 train_sft.py
torchrun --standalone --nproc-per-node=4 train_sft.py     # 或者使用 torchrun

此时有效批量等于单卡批量 × GPU 数 × 梯度累积步数(参见 TRL 分布式训练):在四块 GPU 上,设置 gradient_accumulation_steps=2 即可保持 16。

如果模型放不进单块 GPU(例如在 BF16 下对 70B 模型做 LoRA,或者全参数微调),就需要用 FSDP2 或 DeepSpeed ZeRO-3 进行分片;FSDP2 内置于 PyTorch,开箱即可处理 LoRA。TRL 为两者都提供了 Accelerate 配置模板。请先设置 QLORA = False:QLoRA 搭配 FSDP 需要 PEFT 的 FSDP 指南中所述的额外设置。如果做全参数微调,还要去掉 peft_config,并把学习率改回 TRL 的默认值 2e-5。

curl -LO https://raw.githubusercontent.com/huggingface/trl/main/examples/accelerate_configs/fsdp2.yaml
accelerate launch --config_file fsdp2.yaml --num_processes 8 train_sft.py

uv pip install deepspeed        # 部分算子会在首次使用时编译
curl -LO https://raw.githubusercontent.com/huggingface/trl/main/examples/accelerate_configs/deepspeed_zero3.yaml
accelerate launch --config_file deepspeed_zero3.yaml --num_processes 8 train_sft.py

需要两块 GPU 的 QLoRA 训练(例如在 2× RTX 5090 上训练 70B 模型)也可以直接用 python train_sft.py 启动:Transformers 会把各层分布到两块 GPU 上,两块 GPU 随后依次进行计算。

监控训练并断点续训

tmux attach -t ft                  # 回到训练会话
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total --format=csv -l 5
watch -n 2 amd-smi monitor         # AMD MI355X
tail -f /data/ft/train.log         # 每 10 步输出一次 loss,每完成 10% 的训练输出一次 eval_loss

训练损失应该逐步下降,评估损失也应随之下降。如果训练损失持续下降而评估损失却在上升,说明模型正在死记训练数据(过拟合):请保留更早的检查点,或减少训练的 epoch 数。如果训练因显存不足(out of memory)错误而中断,可以把 per_device_train_batch_size 减半并把 gradient_accumulation_steps 加倍、缩短 max_length,或者改用 QLoRA。

每次保存时,训练器都会在输出目录中写入一个 checkpoint-N 文件夹(N 为步数),save_total_limit=3 会只保留最近的三个。训练中断后,把调用改为 trainer.train(resume_from_checkpoint=True),即可从最新的检查点继续训练。

合并适配器,或用 vLLM 部署

您可以把适配器与训练时所用的基础模型一起部署,也可以把它合并成一个独立的检查点。请在单独的环境中运行 vLLM,并像推理服务一文那样配置 API 密钥和 SSH 隧道。

部署适配器

vllm serve Qwen/Qwen3-8B --host 127.0.0.1 --port 8000 \
  --enable-lora --lora-modules support=/data/ft/qwen3-8b-lora --max-lora-rank 16

客户端通过 "model": "support" 选择该适配器。--max-lora-rank 必须不小于适配器的 r,其默认值为 16(参见 vLLM LoRA 适配器)。

合并到基础模型

python - <<'EOF'
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-8B", dtype=torch.bfloat16)
model = PeftModel.from_pretrained(base, "/data/ft/qwen3-8b-lora").merge_and_unload()
model.save_pretrained("/data/ft/qwen3-8b-merged")
AutoTokenizer.from_pretrained("Qwen/Qwen3-8B").save_pretrained("/data/ft/qwen3-8b-merged")
EOF
vllm serve /data/ft/qwen3-8b-merged --host 127.0.0.1 --port 8000

merge_and_unload() 会把适配器融合进权重,因此推理时没有额外的适配器开销(参见 PEFT LoRA 指南)。合并时会以 BF16 加载基础模型,每个参数约占 2 字节内存;合并结果是一个普通的检查点,vLLM 也可以用 FP8 来部署它。

备份检查点并核对许可证

如果租期没有续费,服务器会在付费月份结束时停止运行,磁盘也会被清空。每次训练结束后,以及在租期结束前尽早,请把适配器、检查点、数据集和脚本复制到服务器之外,并按照存储与数据中的方法校验副本:

rclone copy /data/ft s3remote:my-bucket/ft --progress
rclone check /data/ft s3remote:my-bucket/ft

微调后的模型仍受其基础模型许可证的约束。Qwen3-8B 采用 Apache 2.0 许可证发布。Llama 3.1 系列模型需要申请授权才能下载,其社区许可证除其他条款外,还要求对外分发的微调模型在名称开头加上“Llama”,并显示“Built with Llama”。在发布或出售微调成果之前,请先阅读基础模型的许可证以及您所用数据的使用条款。

对这篇指南有疑问?

请通过联系表单告诉我们您使用的 GPU、运行的命令以及得到的输出结果。

客户中心

登录 CryptGPU

您的订单、账单和服务器,一站式管理。

还没有 CryptGPU 账户?

账户是在结账时创建的:先选择您的服务器和软件,然后在首次下单的账户步骤中创建账户。

部署服务器