大模型微调 / LLaMA-Factory 实战

LLaMA-Factory QLoRA 微调教程:Qwen2.5-7B 单卡微调 PubMedQA

常见的 LLaMA-Factory 教程讲到安装、WebUI 和开始训练就结束了,很少给出真实的评测结果。本页在一块 24GB GPU 上完整做一遍 QLoRA 微调:数据划分、配置、显存实测、三个随机种子、配对评测,以及一个会让 QLoRA 悄悄变成普通 LoRA 的配置错误。

本页数据来自 Scientify 的一次完整运行:自行租用 A10G GPU 完成基线、三个种子的微调和评测,约 2 小时 40 分钟,经独立审阅通过。 查看运行记录

直接答案

用 LLaMA-Factory 做 QLoRA 微调,在 YAML 中设置 finetuning_type: lora、quantization_bit: 4、quantization_method: bnb,再用 llamafactory-cli train 启动。quantization_method 必须写 bnb,写成 bitsandbytes 不会报错,但 4 bit 量化会被跳过。本例 Qwen2.5-7B 正确 QLoRA 的显存峰值约 7.25 GiB,每个种子训练约 17 分钟,PubMedQA 准确率从 42.2% 提高到 76.8%。

本页说明

教程
这页解决什么
你想在单张消费级或云 GPU 上微调 7B 模型,但不清楚 QLoRA 配置怎么写、需要多大显存、怎么判断微调是否真的有效。
适合谁
第一次做大模型微调的研究生和工程师。需要会用 Linux 命令行和 Python,了解训练集、验证集和测试集的区别。
读完你会得到
读完会得到一份可直接使用的 QLoRA 配置、一套判断微调效果的评测方法,以及一份 7B 模型在 PubMedQA 上的完整实测结果。
覆盖
  • LLaMA-Factory 的数据格式与 dataset_info.json 注册
  • QLoRA 配置逐项说明(rank、alpha、学习率、梯度累积等)
  • 真实显存峰值与 GPU 选择
  • 如何确认 4 bit 量化真的生效
  • 零样本基线、配对 bootstrap 置信区间与多种子检验
  • 逐类指标与微调后类别退化的读法
不覆盖
  • 全参数微调与多卡分布式训练
  • DPO、RLHF 等偏好对齐方法
  • 模型合并导出与部署

主线:一步步做完

准备

为什么做这一步先装好 LLaMA-Factory 并确认命令可用。

WebUI 适合第一次熟悉参数;需要复现和记录的实验,用 YAML 配置加命令行更好管理,本页全部使用命令行。

服务器上已经装好 PyTorch 时,建议新建虚拟环境并复用已有的 PyTorch,按 LLaMA-Factory 声明的版本范围安装其他依赖,不要替换 CUDA 环境。

从源码安装(官方 README,要求 Python ≥ 3.11)bash
git clone --depth 1 https://github.com/hiyouga/LlamaFactory.git
cd LlamaFactory
pip install -e .
pip install -r requirements/metrics.txt
# QLoRA 还需要 bitsandbytes
pip install bitsandbytes
启动 WebUI 或用 YAML 训练bash
llamafactory-cli webui
llamafactory-cli train train.yaml

第 1 步

为什么做这一步微调数据要转成 LLaMA-Factory 认识的格式,并在 dataset_info.json 里注册。

PubMedQA 的专家标注集(PQA-L)有 1,000 道题,每题给出一个生物医学研究问题和去掉结论的论文摘要,要求回答 yes、no 或 maybe。本例用官方脚本划分,训练 450、开发 50、测试 500,三个集合之间没有重复。

训练和开发数据转成 instruction / input / output 三字段的 JSON:instruction 放问题和摘要,output 只放答案。测试集不注册到 dataset_info.json,避免被训练流程读到。

单条训练样本(Alpaca 格式)json
{
  "instruction": "Question: ...\nCONTEXTS: ...\nAnswer with yes, no, or maybe.",
  "input": "",
  "output": "yes"
}
data/dataset_info.jsonjson
{
  "pubmedqa_train": { "file_name": "train.json" },
  "pubmedqa_dev":   { "file_name": "dev.json" }
}
集合题数yesnomaybe
测试集50027616955
类别不平衡:全部回答 yes 就能得到 55.2% 的准确率,这是判断模型是否学到东西的最低参照。

第 2 步

为什么做这一步按显存峰值选 GPU,选小了会 OOM,选大了浪费钱。

本例按显存 14 GB、内存 24 GB、4 核 CPU、60 GB 磁盘估算,乘以 1.25 的余量后,选了 AWS g5.2xlarge(A10G 24 GB、8 核、32 GB 内存)。14 GB 乘以 1.25 为 17.5 GB,超过 T4 的 16 GB;T4 也不支持 bf16,用它需要把 bf16 改成 fp16。

模型权重约 15 GB,下载用时 140 秒。基础模型和 Python 环境放在 GPU 服务器上,只把适配器、预测和日志同步回来。

运行方式显存峰值(实测)说明
QLoRA(4 bit NF4)训练7.25 GiB(PyTorch 分配)/ 8.55 GiB(含缓存)nvidia-smi 采样最高 9,213 MiB
零样本推理(4 bit)6.59 GiBbatch 4
误跑的 BF16 LoRA 训练约 22 GiB(22,587 MiB)量化未生效时的实际占用
Qwen2.5-7B-Instruct,序列长度上限 2048,batch 1、梯度累积 16,开启 gradient checkpointing。

第 3 步

为什么做这一步没有同协议的基线,就无法说明微调带来了多少提升。

基线和微调后的模型使用完全相同的评测协议:同一个 Qwen 对话模板、同一句提示、同样的 4 bit NF4 加载、贪心解码、最多生成 8 个 token。输出必须完整匹配 yes、no 或 maybe(忽略大小写和末尾句号),否则判为无效并计错,不从解释文字里抽答案。

500 道测试题的基线推理用时约 3 分钟,准确率 42.2%,低于全答 yes 的 55.2%。原因是原模型大量回答 maybe:276 道 yes 题里有 117 道答成 maybe,169 道 no 题里有 136 道答成 maybe。

第 4 步

为什么做这一步把每个参数的取值和理由写清楚,复现和调参时才有依据。

LLaMA-Factory 的 LoRA 通过 PEFT 库实现:基础模型冻结,只在指定线性层上训练低秩矩阵 A、B。QLoRA 在此基础上用 bitsandbytes 把冻结的基础权重以 4 bit 加载。下面是本次正式配置的主要字段。

train.yaml(本次正式配置的主要字段)yaml
model_name_or_path: /path/to/Qwen2.5-7B-Instruct
stage: sft
do_train: true
finetuning_type: lora
lora_target: all
lora_rank: 16
lora_alpha: 32
lora_dropout: 0.05
quantization_bit: 4
quantization_method: bnb      # 必须写 bnb
quantization_type: nf4
double_quantization: true
dataset: pubmedqa_train
eval_dataset: pubmedqa_dev
template: qwen
cutoff_len: 2048
train_on_prompt: false
per_device_train_batch_size: 1
gradient_accumulation_steps: 16
learning_rate: 1.0e-4
num_train_epochs: 3
lr_scheduler_type: cosine
warmup_ratio: 0.05
bf16: true
gradient_checkpointing: true
optim: paged_adamw_8bit
eval_strategy: epoch
save_strategy: epoch
load_best_model_at_end: true
metric_for_best_model: eval_loss
启动训练bash
llamafactory-cli train train.yaml
参数取值作用
lora_targetall在所有线性层上加 LoRA
lora_rank / lora_alpha16 / 32低秩维度与缩放系数
quantization_bit / method4 / bnb基础权重以 4 bit 加载并冻结
quantization_type / double_quantizationnf4 / trueNF4 量化,并压缩量化尺度
batch × 梯度累积1 × 16等效 batch 16,单卡显存最省
learning_rate / epochs1e-4 / 3小数据集的有限训练预算
train_on_promptfalse只对答案计算损失,不学习复述摘要
load_best_model_at_endtrue,按 eval_loss训练结束后载入开发集损失最低的检查点
每轮 29 个优化步,3 轮共 87 步。三个种子只改 seed、data_seed 和输出目录。

第 5 步

为什么做这一步配置里写了 4 bit,不代表模型真的以 4 bit 加载。

本次第一版配置写的是 quantization_method: bitsandbytes。当时的 LLaMA-Factory 只识别 bnb 这个值,写错后没有任何报错或警告,直接跳过了量化。前两次训练实际是普通的 BF16 LoRA,显存约 22 GiB。

问题是在训练第三个种子前的运行检查中发现的:模型的 is_loaded_in_4bit 为 False,也没有 Linear4bit 层。改为 bnb 后,在训练开始时加入断言,确认 is_loaded_in_4bit 为 True、量化类型为 NF4、双重量化开启、Linear4bit 层数大于 0,然后从头重跑全部三个种子。错误的训练完整归档,报告中与正确结果分开。

训练开始时的量化检查python
import bitsandbytes as bnb

assert getattr(model, "is_loaded_in_4bit", False), "model is not 4-bit"
qc = model.config.quantization_config
assert qc.bnb_4bit_quant_type == "nf4" and qc.bnb_4bit_use_double_quant
n4 = sum(isinstance(m, bnb.nn.Linear4bit) for m in model.modules())
assert n4 > 0, "no Linear4bit layers"
print("Linear4bit layers:", n4)
Scientify 工作区中 Agent 报告修正后的训练已确认启用 QLoRA,显存峰值约 7.2 GiB
修正后的确认:模型包含 4 bit 线性层,NF4 与双重量化均开启,显存峰值约 7.2 GiB。

第 6 步

为什么做这一步小数据集上训练损失下降很快,不能用它判断泛化效果。

三个种子在第 1 轮后开发集损失最低,第 2、3 轮训练损失继续下降、开发集损失上升,这是小样本过拟合信号。load_best_model_at_end 会自动载入第 29 步的检查点。

开发集只有 50 题,其中 maybe 只有 6 题,按它选检查点本身有噪声。每个种子训练 87 步约 17 分钟。

三个随机种子的 QLoRA 训练损失与开发集损失曲线,虚线标出第 29 步选定的检查点
三个种子的训练损失(实线)与开发集损失(虚线)。三个种子都在第 1 轮结束(第 29 步)时开发集损失最低。

第 7 步

为什么做这一步用同一批测试题比较微调前后,并检查结果在不同随机种子下是否稳定。

配对 bootstrap 每次对同一组测试题重采样,计算“微调减基线”的差值分布。差值的 95% CI 不包含 0,说明在这 500 题上提升是稳定的。不能用两个模型各自的置信区间是否重叠来代替配对检验,也不能把 3 个种子 × 500 题当成 1,500 道独立题。

指标零样本基线QLoRA 微调(主种子)提升(配对 95% CI)
准确率42.2%(38.0–46.6)76.8%(73.0–80.4)+34.6(+29.2 至 +40.0)
macro-F136.95%(33.0–40.8)55.28%(52.5–58.4)+18.3(+13.7 至 +23.0)
PubMedQA 测试集 500 题,配对 bootstrap 10,000 次。另外两个种子的准确率为 78.2% 和 77.8%,macro-F1 为 54.6% 和 54.4%。
微调前后逐题答案变化的配对统计图
微调减基线的配对差值与 95% bootstrap 区间。准确率、macro-F1、yes 和 no 召回都提高,maybe 召回下降约 56 个百分点。
三个随机种子的准确率与 macro-F1 对比
三个预先指定的种子结果接近,提升不依赖某一个种子。误差线是各自在 500 题上的 bootstrap 区间,不是跨种子的区间。

第 8 步

为什么做这一步总体准确率会掩盖某一类能力的退化。

提升来自纠正 yes 和 no:原模型过度回答 maybe,微调后改正。但微调后模型几乎不再回答 maybe,表示“证据不足”的能力退化。这可能与 maybe 在数据中占比低有关(测试集 500 题中只有 55 题)。

如果应用场景需要模型在证据不足时说“不确定”,这个结果不能直接使用。可以尝试的方向是对 maybe 类加权、过采样,或把阈值调整作为单独实验,但需要新的独立测试数据来验证。PubMedQA 是离线基准,准确率不代表临床使用的安全性。

类别题数基线召回微调后召回
yes27655.8%87.7%
no16914.8%83.4%
maybe5558.2%1.8%
零样本基线在 PubMedQA 测试集上的混淆矩阵,大量 yes 和 no 被答成 maybe
微调前:大量 yes 和 no 题被答成 maybe。
QLoRA 微调后在 PubMedQA 测试集上的混淆矩阵,maybe 召回只有 1.8%
微调后:yes、no 召回大幅提高,55 道 maybe 题只答对 1 道。

原理与判断

对照

为什么做这一步公开成绩说明量级,但设置不同,不能直接排名。

Med-PaLM 2 的 81.8% 使用少样本提示和 11 次采样投票,GPT-4 的 75.2% 是零样本单次回答,本页用 450 道专家题微调、单次贪心解码。公开论文没有逐题预测,无法与本页做配对检验,因此只作参考。

系统与设置准确率
全部回答 yes55.2%
BioBERT,多阶段,有额外监督(Jin 等 2019)68.1%
GPT-4,零样本(Nori 等 2023)75.2%
人类单标注者(Jin 等 2019)78.0%
Med-PaLM 2,优化提示 + 11 次采样自一致性(Singhal 等 2023)81.8%
本页:Qwen2.5-7B + QLoRA,450 道专家题76.8%(三种子 76.8–78.2%)
这些结果都使用问题加无结论摘要作为输入,但模型、示例数量、采样次数和训练数据不同。

参考与附录

运行记录

为什么做这一步说明数据来源与运行条件。

本页数字来自 2026 年 10 月 29 日在 Scientify 上的一次长周期研究任务。用户上传任务书,Scientify 自行估算资源并租用 A10G GPU,核对模型版本与数据划分,先跑基线再训练,发现量化未生效后停下、保留证据并重跑全部种子,完成评测后释放 GPU。

最终交付 11 页中文报告、8 张图、3 个 Excel、三套适配器、2,000 条逐题预测和复现代码,独立审阅通过。全程约 2 小时 40 分钟,其中 GPU 使用约 1.9 小时。

Scientify 首页上传 QLoRA 任务书并开启长周期研究
上传任务书并开启长周期研究。

常见报错与处理

按本次实际遇到的顺序。

配置写了 4 bit,显存却和 BF16 差不多

原因:quantization_method 写成了 bitsandbytes 等框架不识别的值,量化被静默跳过。

怎么解决:改为 quantization_method: bnb,训练开始时断言 is_loaded_in_4bit 为 True 且存在 Linear4bit 层。

ModuleNotFoundError: No module named 'torchaudio'

原因:新版 LLaMA-Factory 的多模态模块无条件导入 torchaudio,而预装 PyTorch 没有匹配版本。

怎么解决:纯文本任务可以把该导入改为可选导入并记录补丁;涉及音频时安装匹配的完整环境。

CUDA out of memory

原因:量化未生效、batch 或序列长度过大。

怎么解决:先确认 4 bit 已生效;再减小 per_device_train_batch_size 或 cutoff_len,用梯度累积保持等效 batch。基线与微调要用同一协议。

transformers / peft 版本冲突

原因:直接安装最新版,超出 LLaMA-Factory pyproject 允许的范围。

怎么解决:在独立 venv 中按 LLaMA-Factory 声明的版本范围安装,复用已有的 PyTorch,不要替换 CUDA 环境。

模型输出带解释,评测判为无效

原因:生成长度过长或提示不一致。

怎么解决:限制 max_new_tokens,保持提示和解析规则不变;保留原始输出,不事后修改答案。

Please provide model_name_or_path

原因:直接调用底层 get_train_args 时传入了 YAML 路径而不是解析后的字典。

怎么解决:用 llamafactory-cli train train.yaml,或先 yaml.safe_load 再传入。

参考资料

常见问题

LLaMA-Factory 怎么安装?

按官方 README:git clone 仓库后执行 pip install -e . 和 pip install -r requirements/metrics.txt,要求 Python 3.11 及以上;做 QLoRA 还要安装 bitsandbytes。装好后用 llamafactory-cli webui 打开界面,或用 llamafactory-cli train 加 YAML 训练。

QLoRA 微调 7B 模型需要多大显存?

本例 Qwen2.5-7B 正确 QLoRA 训练的 PyTorch 显存峰值约 7.25 GiB,nvidia-smi 最高约 9 GB(batch 1、序列 2048、开启 gradient checkpointing)。从显存看 16 GB 显卡能装下;本配置使用 bf16,需要 A10G、RTX 30 系列及以上等支持 bf16 的显卡。

LLaMA-Factory 的 quantization_method 应该写什么?

写 bnb。本次写成 bitsandbytes 时框架没有报错,但跳过了 4 bit 量化,训练实际是 BF16 LoRA,显存约 22 GiB。

LoRA 和 QLoRA 有什么区别?

LoRA 冻结基础权重、只训练低秩适配器;QLoRA 在此基础上把冻结的基础权重以 4 bit NF4 加载,显存进一步降低。本例 QLoRA 约 7 GiB,BF16 LoRA 约 22 GiB。

微调训练几轮合适?

按开发集损失决定。本例设 3 轮,三个种子都在第 1 轮后开发集损失最低,后两轮过拟合。开启 load_best_model_at_end 可自动选回最佳检查点。

微调后准确率提升了,为什么还要看逐类指标?

本例准确率从 42.2% 提高到 76.8%,但 maybe 类召回从 58.2% 降到 1.8%,模型几乎不再表达不确定。只看准确率会漏掉这种退化。

微调一次要花多少时间?

本例 A10G 上每个种子 87 步约 17 分钟,500 题推理约 3 分钟,模型下载约 2 分钟。三个种子加评测,GPU 共使用约 1.9 小时。

把微调实验交给 Scientify

上传任务说明,Scientify 会估算资源、租用 GPU、跑基线和微调、完成配对评测,训练结束后自动释放 GPU。