LlamaFactory QLoRA SFT 微调实操指南
环境 :Windows 11 + RTX 5060 Ti (SM 12.0) + Qwen2.5-0.5B-Instruct
日期 :2026-07-31
标签 :
Qwen2.5-0.5B·QLoRA 4bit NF4·LlamaFactory 0.9.x·RTX 5060 Ti 16GB·Python 3.14·CUDA 13.2
基于 Windows + RTX 5060 Ti + Qwen2.5-0.5B-Instruct 的完整 4bit LoRA 监督微调流程文档。含环境兼容修复、数据集构造、训练 YAML 配置逐字段说明、训练指标分析、BASE vs LoRA 推理逐题对比,以及 Python 3.14 / datasets 4.x 版本问题的可复用 monkey patch 说明。
目录
- 环境与前置条件
- [关键兼容问题与 patch](#关键兼容问题与 patch)
- [数据集构造(Alpaca 格式)](#数据集构造(Alpaca 格式))
- [训练 YAML 配置详解](#训练 YAML 配置详解)
- 执行训练与结果分析
- [BASE vs LoRA 推理对比](#BASE vs LoRA 推理对比)
- 权重合并与后续使用
- [FAQ 与常见报错](#FAQ 与常见报错)
- 参考与链接
1. 环境与前置条件
本流程在以下软硬件组合下完整跑通(训练退出码 0,21/21 step 全部完成)。如你的环境接近可直接复用,差异较大的部分见第 2 节兼容修复。
| 维度 | 具体版本 / 型号 | 说明 |
|---|---|---|
| 操作系统 | Windows 11 Pro 23H2 | PowerShell 5.1 终端 |
| GPU | NVIDIA GeForce RTX 5060 Ti 16GB | Compute Capability SM 12.0 (Blackwell) |
| CUDA Toolkit | CUDA 13.2 (cuDNN 9.6) | nvcc 13.2.89,驱动 ≥ 570.86 |
| Python | 3.14.0a1 (Miniconda base) | 路径 C:\ProgramData\miniconda3\python.exe |
| PyTorch | 2.9.0+cu132 | nightly 版(RTX 50 系列要求 CUDA 13.x) |
| TorchAudio | 2.9.0+cu128 | 官方最高 cu128,实际 ABI 兼容 cu132,需 patch 跳过检测 |
| transformers | 5.14.1 | 含 Qwen2 原生支持 |
| datasets | 4.0.0 | 与 Py3.14 pickle 签名不兼容,需 patch |
| peft | 0.16.0 | LoRA 训练 |
| bitsandbytes | 0.47.0 | QLoRA 4bit NF4 量化 |
| LlamaFactory | 0.9.x (main 分支) | 路径 C:\project\LlamaFactory-main |
| 基础模型 | Qwen/Qwen2.5-0.5B-Instruct | 本地 C:\project\models\Qwen2.5-0.5B-Instruct |
1.1 环境变量与 PYTHONPATH
每次跑训练 / 推理之前,都需要把以下两个目录加入 PYTHONPATH,顺序不能反(patch 目录在前,保证最先 import):
powershell
# PowerShell 一键设置
$py = "C:\ProgramData\miniconda3\python.exe"
$env:PYTHONPATH = "C:\Users\lizhi\.trae-cn\work\6a69b5f843872d7e1b8ed6e1;C:\project\LlamaFactory-main\src;" + $env:PYTHONPATH
$env:DISABLE_VERSION_CHECK = "1" # 跳过 LlamaFactory 的依赖版本检查
1.2 目录总览
| 路径 | 用途 | 是否可写 |
|---|---|---|
C:\project\LlamaFactory-main |
LlamaFactory 源码 | 是(cli.py 已注入 patch) |
C:\project\models\Qwen2.5-0.5B-Instruct |
base 模型权重 | 只读 |
C:\project\LlamaFactory-main\data\zh_sft_demo_50.json |
50 条中文 Alpaca SFT 数据 | 可替换 |
C:\project\LlamaFactory-main\data\dataset_info.json |
LlamaFactory 数据集注册表 | 新增数据集时修改 |
C:\project\finetune-output\qwen2.5-0.5b-zh-lora |
LoRA 产物(adapter + checkpoint) | 训练输出 |
C:\Users\...\6a69b5f843872d7e1b8ed6e1 |
Py3.14 兼容 patch 脚本目录 | 不要随意删除 |
2. 关键兼容问题与 patch
本流程踩了 2 个版本不兼容问题,都是「新版 Python / 新 GPU 对老版本库」造成的。两个 patch 都已经固化到环境里,不需要手动再做任何操作,但为了后续迁移方便,下面完整说明原理与代码位置。
2.1 Patch 1:TorchAudio CUDA 版本字符串检测
!CAUTION
现场报错 :
RuntimeError: Detected that PyTorch and TorchAudio were compiled with different CUDA versions. PyTorch has cu132 whereas TorchAudio has cu128.
根因 :RTX 50 系列要求 PyTorch 用 cu132 才能正确识别 SM 12.0,但 TorchAudio 官方 nightly 最高只打到 cu128。两者其实 CUDA runtime ABI 完全兼容,只是 TorchAudio 的启动检查太严格。
修复方式 :monkey patch torchaudio.lib._check_cuda_version 为 no-op(不报错)。该 patch 已经在 sitecustomize.py 中全局生效,不再需要在每次脚本里单独写。
2.2 Patch 2:Python 3.14 + datasets / dill pickle 签名差异
!CAUTION
现场报错 :
TypeError: Pickler._batch_setitems() takes 2 positional arguments but 3 were given
这是本次最棘手的兼容问题。根因三层嵌套:
- Python 3.14 把
pickle._Pickler._batch_setitems签名从(self, items)改成了(self, items, obj)(新增第三个参数用于性能优化)。 - dill 0.4.1 通过
from pickle import _Pickler as StockPickler继承了这个 Python 层 Pickler,它的save_module_dict()会显式调用StockPickler.save_dict(pickler, obj),而 Py3.14 的save_dict内部会走三参数版_batch_setitems,导致 dill 子类 override 的两参数版签名炸掉。 - datasets 4.0.0 再把 dill.Pickler 继承一层做自己的
datasets.utils._dill.Pickler,并且重写了_batch_setitems(self, items)用于对 dict key 排序(fingerprint 确定性),它内部super()._batch_setitems(items)又再一次踩中签名问题。
修复策略(三层同时 patch)
- 重写
pickle._Pickler.save_dict--- 不调用_batch_setitems,而是直接手写 pickle 字节码(MARK + DICT → 逐项 save(k) save(v) → SETITEMS / SETITEM)。覆盖 dillsave_module_dict那条调用链。 - 替换
dill._dill.Pickler._batch_setitems--- 改成接受*args/**kwargs,内部统一手写 SETITEMS 字节,绕开对父类的调用。 - 替换
datasets.utils._dill.Pickler._batch_setitems--- 保留 datasets 原有的「按 key 排序」行为(用 Hasher.hash 作为 fallback),输出部分同样走手写字节。
!TIP
完整实现位置:
C:\Users\...\6a69b5f843872d7e1b8ed6e1\_py314_pickle_fix.py,并在C:\project\LlamaFactory-main\src\llamafactory\cli.py顶部自动import _py314_pickle_fix,任何llamafactory.cli train/chat/export启动都会先打补丁,无需手动干预。!TIP
迁移到新机器时 :只要是 Python 3.13+ 或 datasets ≥ 4.0 的组合,都建议先跑一下附带的
_test_pickle_patch.py做回归测试,确保Hasher.hash()+load_dataset()全链路通过。
3. 数据集构造(Alpaca 格式)
3.1 格式规范
LlamaFactory 的 alpaca 模板要求每条样本三列字段:instruction(指令)、input(可选上下文)、output(回答)。全部使用 UTF-8 JSON 数组,每行一条或整个数组包裹均可。
json
[
{
"instruction": "请用中文介绍一下自己。",
"input": "",
"output": "你好,我是基于 Qwen 架构训练的中文助手......"
},
{
"instruction": "计算下面的数学题。",
"input": "123 + 456 - 789 = ?",
"output": "步骤 1:123 + 456 = 579。步骤 2:579 - 789 = -210。"
}
]
3.2 数据集注册
JSON 文件写完后,还要在 C:\project\LlamaFactory-main\data\dataset_info.json 里添加一条记录:
json
"zh_sft_demo_50.json": {
"file_name": "zh_sft_demo_50.json",
"columns": {
"prompt": "instruction",
"query": "input",
"response": "output"
},
"tags": {
"role_tag": "",
"content_tag": ""
}
}
其中 columns 把 Alpaca 字段映射到 LlamaFactory 内部的 prompt/query/response 三元组;dataset 参数填的是这个 key zh_sft_demo_50.json(不是文件名)。
3.3 本次使用的 50 条数据分布
为了验证整个流程,数据集覆盖了 6 类常见任务:
| 类别 | 数量 | 覆盖内容 |
|---|---|---|
| 通用对话 / 自我介绍 | 8 条 | 中文助手人设、闲聊类 |
| 数学与逻辑推理 | 7 条 | 四则运算、进制转换、正则 |
| 代码与算法 | 10 条 | Python / SQL / 伪代码 / 数据结构 / Pandas / NumPy / PyTorch |
| 机器学习与大模型理论 | 12 条 | LoRA / Transformer / AMP / RLHF / 量化 / KV Cache / 并行 |
| 翻译与创作 | 6 条 | 中英互译、古诗、JSON 美化 |
| 常识与系统知识 | 7 条 | 物理常识、Linux、HTTP 状态码、Docker、Git、死锁 |
!WARNING
注意 :50 条 × 3 epoch 只能验证流程跑通 ,不能验证知识学习。如果要做真实效果提升,建议 5k--50k 高质量数据 × 1--2 epoch,并加入 train/eval 拆分做 early stopping。
4. 训练 YAML 配置详解
完整 YAML 位于 C:\project\LlamaFactory-main\examples\train_lora\qwen25-0.5b_zh_qlora_sft.yaml。按功能模块拆解如下。
4.1 模型 + 训练方式
yaml
# ---- 模型 ----
model_name_or_path: C:/project/models/Qwen2.5-0.5B-Instruct
trust_remote_code: true
# ---- 训练方式 ----
stage: sft # 监督微调
do_train: true
finetuning_type: lora # 固定 base,只训 LoRA
# QLoRA 4bit 量化
quantization_bit: 4
quantization_method: bitsandbytes
quantization_type: nf4
double_quantization: true
!NOTE
为什么选 NF4:QLoRA 论文推荐的 4bit 格式,对正态分布权重的量化误差比普通 INT4 小 2--3 倍,配合双量化(double_quantization)进一步省 0.3--0.5 bit/权重。
4.2 LoRA 超参数
yaml
# ---- LoRA 参数 ----
lora_rank: 16 # rank r;建议 8/16/32/64
lora_alpha: 32 # 放缩系数;通常 α = 2r
lora_dropout: 0.05
lora_target: all # 所有线性层 (q/k/v/o/up/gate/down)
use_rslora: true # Rank-Stabilized LoRA
lora_target: all 是 LlamaFactory 的语义,会自动把 q_proj, k_proj, v_proj, o_proj, up_proj, gate_proj, down_proj 共 7 层线性层全部打上 adapter。对 0.5B 模型来说开销依然很小:本次 可训练参数只有 8.8M / 1.75%,其余 4.94 亿 base 参数冻结。
4.3 数据集与预处理
yaml
# ---- 数据集 ----
dataset: zh_sft_demo_50.json # dataset_info.json 中的 key
dataset_dir: C:/project/LlamaFactory-main/data
template: qwen # 必须匹配模型家族 chat template
split: train
shuffle: true
# ---- 长度 ----
cutoff_len: 1024
max_samples: 50
overwrite_cache: true
preprocessing_num_workers: 4
sft_packing: false
4.4 训练循环与优化器
yaml
# ---- 训练循环 ----
output_dir: C:/project/finetune-output/qwen2.5-0.5b-zh-lora
per_device_train_batch_size: 4 # 单卡 micro batch
gradient_accumulation_steps: 2 # 等效 global batch = 8
num_train_epochs: 3
logging_steps: 5
save_steps: 10000 # 小数据只在最后存
save_strategy: epoch
save_total_limit: 2
# ---- 优化器 ----
bf16: true
learning_rate: 2e-4
lr_scheduler_type: cosine
warmup_ratio: 0.03
weight_decay: 0
adam_beta1: 0.9
adam_beta2: 0.95
max_grad_norm: 1.0
gradient_checkpointing: true # 省显存;关掉 +30% 速度
disable_tqdm: false
seed: 42
!WARNING
RTX 5060 Ti 建议 :对于 0.5B 级别模型,16GB 显存可以把
per_device_train_batch_size提到 8 并关掉gradient_checkpointing,预计训练速度再快 30--40%。7B 模型则用 2 + grad_accum 4 的组合。
5. 执行训练与结果分析
5.1 整体流程图
#mermaid-svg-KBsif63EUBStaw7H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KBsif63EUBStaw7H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KBsif63EUBStaw7H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KBsif63EUBStaw7H .error-icon{fill:#552222;}#mermaid-svg-KBsif63EUBStaw7H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KBsif63EUBStaw7H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KBsif63EUBStaw7H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KBsif63EUBStaw7H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KBsif63EUBStaw7H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KBsif63EUBStaw7H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KBsif63EUBStaw7H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KBsif63EUBStaw7H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KBsif63EUBStaw7H .marker.cross{stroke:#333333;}#mermaid-svg-KBsif63EUBStaw7H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KBsif63EUBStaw7H p{margin:0;}#mermaid-svg-KBsif63EUBStaw7H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KBsif63EUBStaw7H .cluster-label text{fill:#333;}#mermaid-svg-KBsif63EUBStaw7H .cluster-label span{color:#333;}#mermaid-svg-KBsif63EUBStaw7H .cluster-label span p{background-color:transparent;}#mermaid-svg-KBsif63EUBStaw7H .label text,#mermaid-svg-KBsif63EUBStaw7H span{fill:#333;color:#333;}#mermaid-svg-KBsif63EUBStaw7H .node rect,#mermaid-svg-KBsif63EUBStaw7H .node circle,#mermaid-svg-KBsif63EUBStaw7H .node ellipse,#mermaid-svg-KBsif63EUBStaw7H .node polygon,#mermaid-svg-KBsif63EUBStaw7H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KBsif63EUBStaw7H .rough-node .label text,#mermaid-svg-KBsif63EUBStaw7H .node .label text,#mermaid-svg-KBsif63EUBStaw7H .image-shape .label,#mermaid-svg-KBsif63EUBStaw7H .icon-shape .label{text-anchor:middle;}#mermaid-svg-KBsif63EUBStaw7H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KBsif63EUBStaw7H .rough-node .label,#mermaid-svg-KBsif63EUBStaw7H .node .label,#mermaid-svg-KBsif63EUBStaw7H .image-shape .label,#mermaid-svg-KBsif63EUBStaw7H .icon-shape .label{text-align:center;}#mermaid-svg-KBsif63EUBStaw7H .node.clickable{cursor:pointer;}#mermaid-svg-KBsif63EUBStaw7H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KBsif63EUBStaw7H .arrowheadPath{fill:#333333;}#mermaid-svg-KBsif63EUBStaw7H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KBsif63EUBStaw7H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KBsif63EUBStaw7H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBsif63EUBStaw7H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KBsif63EUBStaw7H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBsif63EUBStaw7H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KBsif63EUBStaw7H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KBsif63EUBStaw7H .cluster text{fill:#333;}#mermaid-svg-KBsif63EUBStaw7H .cluster span{color:#333;}#mermaid-svg-KBsif63EUBStaw7H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KBsif63EUBStaw7H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KBsif63EUBStaw7H rect.text{fill:none;stroke-width:0;}#mermaid-svg-KBsif63EUBStaw7H .icon-shape,#mermaid-svg-KBsif63EUBStaw7H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBsif63EUBStaw7H .icon-shape p,#mermaid-svg-KBsif63EUBStaw7H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KBsif63EUBStaw7H .icon-shape .label rect,#mermaid-svg-KBsif63EUBStaw7H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBsif63EUBStaw7H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KBsif63EUBStaw7H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KBsif63EUBStaw7H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 加载 YAML 配置
apply patch 启动 cli.py
加载 tokenizer + 模板
按 dataset_info 加载 JSON
tokenize + label_mask -100
加载 Base 模型 + 4bit NF4 量化
PeftModel 注入 LoRA adapter
Trainer.train 21 step
保存 adapter 到 output_dir
生成 training_loss.png / all_results.json
推理对比 BASE vs LoRA
5.2 一键训练命令
powershell
$env:PYTHONPATH = "C:\Users\lizhi\.trae-cn\work\6a69b5f843872d7e1b8ed6e1;C:\project\LlamaFactory-main\src;" + $env:PYTHONPATH
$env:DISABLE_VERSION_CHECK = "1"
$py = "C:\ProgramData\miniconda3\python.exe"
& $py -m llamafactory.cli train "C:\project\LlamaFactory-main\examples\train_lora\qwen25-0.5b_zh_qlora_sft.yaml"
5.3 KPI 结果总览
| 指标 | 数值 | 备注 |
|---|---|---|
| 总训练步数 | 21 step | 50 样本 × 3 epoch ÷ batch 8 |
| 训练总时长 | 27.7 s | 0.757 step/s · 5.4 样本/s |
| 最终 train_loss | 2.216 | 最后一步 loss 1.692(下降趋势) |
| 可训练参数 | 8.8 M | 占全部 5.03 亿的 1.75% |
| 总计算量 | 50.7 TFLOPs | RTX 5060 Ti 平均 ~1.8 TFLOPs/s |
| 最大显存 | ~7.2 GB | 4bit 量化 + gradient ckpt |
5.4 Loss 曲线数据
| Logging Step | Epoch | Train Loss |
|---|---|---|
| 5 | 0.77 | 2.023 |
| 10 | 1.46 | 2.986 |
| 15 | 2.15 | 2.392 |
| 20 | 2.92 | 1.692 |
| 平均 (train_loss) | 3.0 | 2.216 |
Loss 在 step 10 附近出现一次上冲到 2.986(数据混合了不同长度、不同任务,小 batch 时常见),之后 step 15 → 20 明显下降到 1.692,最后一个 step 的 loss 低于平均值,说明训练没有过拟合迹象,继续训练到 40--60 step 大概率会继续下降。
5.5 参数占比与推理速度
可训练参数占比(LoRA vs Base 冻结):
| 部分 | 参数数量 | 占比 |
|---|---|---|
| LoRA 可训练 | 8,798,208 | 1.75% |
| Base 模型冻结 | 494,032,768 | 98.25% |
| 合计 | 502,830,976 | 100% |
推理速度对比 BASE vs LoRA 适配器(tok/s):
| Prompt | BASE 模型 | LoRA 适配器 |
|---|---|---|
| 介绍自己 | 28.6 | 6.9 |
| 数学计算 | 33.0 | 6.4 |
| 解释 LoRA | 33.3 | 6.5 |
| 装饰器代码 | 33.4 | 6.3 |
| 物理现象 | 17.6 | 6.1 |
!NOTE
速度差异是预期现象 :PeftModel 默认把 LoRA 作为旁路加在每一层 forward 上(base matmul + 两个低秩 matmul 叠加),而不是把权重直接合并。做一次
merge_and_unload()之后 LoRA 模型的推理速度会和 BASE 完全一致(见第 7 节)。
6. BASE vs LoRA 推理对比
取 5 个 prompt(3 个在训练集见过、2 个泛化 prompt)分别跑 base 模型和 LoRA 适配器,参数固定:
max_new_tokens=256, temperature=0.7, top_p=0.8, repetition_penalty=1.05, do_sample=True。
6.1 Prompt 1:训练集见过(「请用中文介绍一下自己」)
| BASE 模型 | LoRA 适配器 | |
|---|---|---|
| 回答 | 我是阿里云自主研发的超大规模语言模型,我叫通义千问。我是由阿里云团队在 2021 年 9 月 30 日发布的。我的目标是成为能够理解和生成自然语言的智能助手...... | 你好!我是人工智能助手,我叫小明。我喜欢学习新东西,很高兴和你交流。有什么兴趣爱好? |
| 速度 | 28.6 tok/s · 147 tok · 5.13s | 6.9 tok/s · 26 tok · 3.78s |
分析:BASE 调用了预置的系统人设(通义千问),LoRA 因为训练集中这条样本的回答是「基于 Qwen 架构训练的中文助手」,实际输出改成了「叫小明」------说明 SFT 确实在改变回答风格的分布上生效了(只是小数据下没学到稳定模式)。
6.2 Prompt 2:数学计算(123 + 456 - 789)
| BASE 模型 | LoRA 适配器 | |
|---|---|---|
| 回答 | 123 + 456 - 789 的计算结果是 -220。 | 123 + 456 - 789 = 702。 |
| 速度 | 33.0 tok/s · 23 tok · 0.70s | 6.4 tok/s · 20 tok · 3.11s |
| 正确性 | ❌ 错误(正确答案是 -210) | ❌ 错误(正确答案是 -210) |
分析:两个模型都算错了。0.5B 参数级别的模型本身不具备可靠的算术能力,需要通过 CoT 数据 + 更大规模训练(通常 ≥ 7B)才能稳定。
6.3 Prompt 3:机器学习概念(解释 LoRA)
| BASE 模型 | LoRA 适配器 | |
|---|---|---|
| 回答 | LoRA(Low-Rank Approximation)微调是一种在深度学习模型中,通过在训练过程中对模型进行低秩近似的方法。LoRA 是一种用于处理大规模数据集和复杂问题的优化技术...... | LoRA 是一种超微调准器,它允许微调模型在显存上高效地微调参数。主要优点是:1)显存上微调更平滑,不需要显存加载;2)微调更稳定,不需要显存加载和权重更新;3)微调更快...... |
| 速度 | 33.3 tok/s · 256 tok · 7.70s | 6.5 tok/s · 90 tok · 13.94s |
| 评价 | 概念混淆(Low-Rank Approximation ≠ Adaptation) | 术语不严谨,但大致方向正确 |
6.4 Prompt 4:代码生成(timer 装饰器)
| BASE 模型 | LoRA 适配器 | |
|---|---|---|
| 回答 | 在 Python 中,我们可以使用 time 模块来获取当前时间...... def time_decorator(func): def wrapper(...): start = time.time() ... |
import time def timer_decorator(func): start_time = time.perf() result = func() elapsed = time.perf() - start_time print(f'{func.__name__} took {elapsed:.2f} seconds') |
| 速度 | 33.4 tok/s · 256 tok · 7.68s | 6.3 tok/s · 67 tok · 10.66s |
| 评价 | 结构基本正确 | 缺 wrapper 结构,语法不完整 |
6.5 Prompt 5:泛化常识(先闪电后雷声)
| BASE 模型 | LoRA 适配器 | |
|---|---|---|
| 回答 | 这是一个关于光速和声速的有趣物理现象。当闪电发生时,光速比声速快得多...... | 光和声波在大气中传播,遇水形成水蒸气,遇雷暴或冰雹等,引发雷声。 |
| 速度 | 17.6 tok/s · 139 tok · 7.91s | 6.1 tok/s · 29 tok · 4.72s |
| 正确性 | ✅ 结论正确,表述略啰嗦 | ❌ 完全胡编(训练集中没有该样本) |
6.6 小结
| 维度 | BASE(原始 Qwen2.5-0.5B) | LoRA 适配器(50 条 × 3 epoch) |
|---|---|---|
| 回答风格 | 长、啰嗦、套系统人设 | 短、口语化、更偏训练集分布 |
| 见过的 prompt | 基本能答,但可能走题 | 能答出训练分布内的内容,可能有过拟合痕迹 |
| 未见过的 prompt | 靠预训练知识兜底,偶有错误 | 严重退化(数据太少无法泛化) |
| 数学/代码 | 0.5B 级别不可靠 | 不可靠 + 偶有语法/事实错误 |
| 推理速度 | ~17--33 tok/s | ~6 tok/s(未合并权重) |
!TIP
预期一致:50 条小样本的目标只是「验证整个训练、保存、加载、推理链路正确」,不是「让模型真学到新知识」。把数据量扩到 ≥ 5k、用更大模型(7B 以上)并跑 1 epoch,就可以看到泛化能力的稳定提升。
7. 权重合并与后续使用
7.1 合并 LoRA 回 Base(提升推理速度)
为了让 LoRA 模型推理速度回到 BASE 的 ~33 tok/s 水平,需要把 adapter 权重一次性合并回 base 模型并保存为独立 safetensors。
方式 A:LlamaFactory 自带 CLI(推荐)
powershell
& $py -m llamafactory.cli export `
--model_name_or_path C:/project/models/Qwen2.5-0.5B-Instruct `
--adapter_name_or_path C:/project/finetune-output/qwen2.5-0.5b-zh-lora `
--template qwen `
--export_dir C:/project/models/Qwen2.5-0.5B-Instruct-zh-lora-merged `
--export_size 2 `
--export_legacy_format false
方式 B:手动 peft.merge_and_unload()
python
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base = AutoModelForCausalLM.from_pretrained(
"C:/project/models/Qwen2.5-0.5B-Instruct",
dtype="bfloat16", device_map="auto"
)
merged = PeftModel.from_pretrained(
base, "C:/project/finetune-output/qwen2.5-0.5b-zh-lora"
).merge_and_unload()
merged.save_pretrained(
"C:/project/models/Qwen2.5-0.5B-Instruct-zh-lora-merged",
safe_serialization=True
)
AutoTokenizer.from_pretrained(
"C:/project/models/Qwen2.5-0.5B-Instruct"
).save_pretrained("C:/project/models/Qwen2.5-0.5B-Instruct-zh-lora-merged")
7.2 本地对话测试
LlamaFactory 自带 Web UI 和 CLI 聊天:
powershell
# Web 界面(推荐)
& $py -m llamafactory.cli webchat
# 命令行 API 推理
& $py -m llamafactory.cli api.chat `
--model_name_or_path C:/project/models/...-merged `
--template qwen `
--infer_dtype bf16
7.3 后续扩展清单
- 扩大数据集到 ≥ 5k 高质量样本 --- 去重、清洗超长样本(> 2k token)、用 self-instruct 或真实对话数据。
- 加入 eval 集与 early stopping --- 配置
val_size=0.1、evaluation_strategy=steps, eval_steps=50、load_best_model_at_end=true。 - LoRA 超参搜索 --- 尝试
lora_rank=32/64、learning_rate=5e-5/1e-4、打开use_dora=true。 - 上更大模型 --- RTX 5060 Ti 16GB 可以跑 7B 模型 QLoRA(batch=2),70B 则需要 2× 24GB 或 CPU offload。
- RLHF / DPO 对齐 --- 准备偏好数据后直接复用
stage: dpo的 YAML 模板。
8. FAQ 与常见报错
| 报错 / 现象 | 根因 | 解决方式 |
|---|---|---|
TypeError: Pickler._batch_setitems() takes 2 positional arguments but 3 were given |
Python 3.13+ 对 pickle 签名变更,datasets / dill 未及时跟进 | 确保 PYTHONPATH 第一位是 patch 脚本目录,并确认 cli.py 顶部有 import _py314_pickle_fix |
RuntimeError: Detected that PyTorch and TorchAudio were compiled with different CUDA versions |
RTX 50 系列要求 cu132,但 torchaudio 官方最高只打 cu128 | 全局 sitecustomize 里 patch torchaudio.lib._check_cuda_version 为 no-op |
AttributeError: 'Qwen2Config' object has no attribute ... |
老版 transformers 不认识 Qwen2.5 新增字段 | 升级到 transformers ≥ 5.6(本次用 5.14.1) |
ValueError: Cannot pad, because padding side is not set ... tokenizer |
LlamaFactory 对某些模型的 tokenizer 要求明确 pad_token_id | YAML 里加 padding_side: right 或 `pad_token: < |
| 训练显存 OOM (CUDA out of memory) | batch 太大或没开 gradient_checkpointing | 把 per_device_train_batch_size 降到 2/1;确保 gradient_checkpointing: true;打开 paged_optimizers: paged_adamw_8bit |
| LoRA 推理速度比 base 慢 5 倍 | adapter 没合并,每层 forward 都有额外低秩 matmul | 先 merge_and_unload() 再部署,或用 vLLM 的 LoRA 服务模式 |
nvcc fatal : Cannot find compiler 'cl.exe' in PATH |
bitsandbytes/flash-attn 需要 MSVC 编译 | 执行 "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat" 或用 Developer Command Prompt |
| LlamaFactory 提示 "xxx version mismatch" | 版本检查太严格(本环境故意跳过) | 设置 $env:DISABLE_VERSION_CHECK="1" |
参考与链接
- LlamaFactory 官方仓库 · 一站式大模型微调框架 --- https://github.com/hiyouga/LLaMA-Factory
- QLoRA: Efficient Finetuning of Quantized LLMs (NeurIPS 2024) --- https://arxiv.org/abs/2305.14314
- LoRA: Low-Rank Adaptation of Large Language Models (ICLR 2022) --- https://arxiv.org/abs/2106.09685
- Qwen2.5 技术报告 & 模型卡 --- https://qwenlm.github.io/blog/qwen2.5/
- Python 3.13 pickle C 加速 · 3 参数 _batch_setitems 相关 commit --- https://github.com/python/cpython/pull/120549
- RTX 5060 Ti 架构与 SM 12.0 支持矩阵 --- https://developer.nvidia.com/cuda-gpus