基于 LLaMA-Factory 的模型 LoRA 微调实战记录(零代码 WebUI 方案)

本文记录了我使用 LLaMA-Factory 的 WebUI 进行零代码 LoRA 微调的全过程,包括数据集准备、训练配置、参数调优、断点续训机制验证以及最终效果评测。适合初学者参考。


0、前言

本文运行的前提是您已经配置好LLaMA-Factory项目并下载完成需要进行微调的模型,具体方案可以参考我的上一篇文章。
LLaMA-Factory 部署与 DeepSeek-R1-Distill-Qwen 模型乱码问题解决全记录

一、数据集准备

1. 数据集格式(Alpaca 风格)

我准备了一份关于项目问答的 Alpaca 格式数据集,示例如下:

{
  "instruction": "计算这些物品的总费用。",
  "input": "输入:汽车 - $3000,衣服 - $100,书 - $20。",
  "output": "汽车、衣服和书的总费用为 $3000 + $100 + $20 = $3120。"
}

2. 放置数据集

将 JSON 文件放到 LLaMA-Factory/data/ 目录下,与其他 demo 文件同级。
在这里插入图片描述

3. 注册数据集

修改 dataset_info.json,按照已有其他格式添加新数据集的条目:

"alpaca_strategy_forge": {
  "file_name": "alpaca_strategy_forge.json",
}

在这里插入图片描述


二、启动 WebUI 并开始训练

1. 启动命令

必须在 LLaMA-Factory 根目录 下执行,否则会找不到 dataset_info.json

llamafactory-cli webui

若在错误目录启动,会报错:

[WARNING] Cannot open data/dataset_info.json due to [Errno 2] No such file or directory: 'data/dataset_info.json'.

2. 查看数据集

启动后,在 Train 页面的数据集下拉列表中可以看到新增的数据集。

在这里插入图片描述

点击预览可查看数据样例:
在这里插入图片描述

3. 训练方式选择

选择 SFT(有监督微调)

在这里插入图片描述

4. 选择基础模型

本次测试使用 DeepSeek-R1-Distill-Qwen-1.5B
在这里插入图片描述

5. 训练参数参考

初始参数设置如下(这是一套错误参考,基本是默认参数,不建议学这里):

在这里插入图片描述
起初以为是没有设置测试集导致的无法计算损失函数导致函数图像为空,后来发现单纯是训练轮数太少了导致的。
在这里插入图片描述

训练完成后,可在 saves/ 目录下看到生成的 LoRA 权重:
在这里插入图片描述


三、初次测试与问题

1. 加载 LoRA 进行对话

在 Chat 页面选择检查点路径,加载刚训练的 LoRA 模型:

在这里插入图片描述

2. 测试结果不理想

模型回答未能按照训练集中的格式输出,与预期不符:
它把我训练的知识和另一个同名的项目弄混了。

在这里插入图片描述

3. 增加训练轮数

将轮数增加到 30 轮,损失图像终于出现,但看起来仍欠拟合:

在这里插入图片描述
继续追加轮数,直至达到拟合。参数参考如下:
简单来说就是一直训练,增加轮数,直到函数的损失函数达到预期范围。

在这里插入图片描述

理想的损失函数大概类似下图(下图实际上看起来也不是很完美,理想的图片应该下降得更快一些,也许是我用的1.5B模型比较笨训练好多轮才达到拟合,之后换成7B模型只需要很少的轮数就能拟合了)首先损失会快速下滑,而后基本保持不变,如果损失有回弹可能是过拟合了,最终损失大概期望控制在0.1上下。

在这里插入图片描述

由于我是不断在上次一的训练基础上叠加训练的,期间多次训练得到的损失函数图如下:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、断点续训机制探索

在使用llama-factory训练的过程中,我们难免一次训练达不到预期,需要多次训练,可能在上次的实验路径上直接进行实验,也可能是新建一条路径读取之前的某个checkpoint继续实验,因此难免好奇对于在同一路径下多次进行实验,llama-factory框架是如何处理的,是会直接粗暴覆盖实验呢,还是会断点续传叠加实验,由此我进行了进一步的测试研究。

关键发现

对于在同一个实验路径下的训练,若不修改输出目录,只降低训练轮数(如从 100 改为 20),并不会覆盖之前的高轮次检查点。
LLaMA-Factory 会检测已有检查点,若当前目标步数 ≤ 已训练步数,则直接退出,不进行实际训练。

实验验证

实验 1:从 50 轮检查点继续实验,目标轮数设为 20
第一步:进行首先的50轮训练

配置7B的模型做第一轮50轮次的训练,参数如下
在这里插入图片描述
得到结果,看起来已经接近拟合了(效果真的比之前1.5B好很多喔)
在这里插入图片描述

第二部:再上次训练的基础上,但是不改变实验目录直接进行训练
  • 选择检查点 checkpoint-100(对应 50 轮)
  • 设置轮数 = 20(总步数 40)
  • 输出目录保持不变
    在这里插入图片描述
    训练完成后此时可以看到输出的图像也没有变化
    在这里插入图片描述

日志显示:

[INFO] Resuming training from checkpoint with epoch 50 and global step 100
[INFO] Fast-forwarding the dataloader past 50 epochs and 0 batches to resume from the exact training state.
[INFO] Training completed.

训练瞬间完成,无 loss 输出,证明未进行任何实际训练。

实验 2:不选检查点,直接设轮数 20

同样,系统自动从目录中最新的检查点恢复,检测到已训练 50 轮,目标仅 20 轮,直接跳过。
在这里插入图片描述

[INFO|2026-07-04 22:06:54] trainer.py:1478 >>   Total optimization steps = 40
[INFO|2026-07-04 22:06:54] trainer.py:1479 >>   Number of trainable parameters = 20,185,088
[INFO|2026-07-04 22:06:54] trainer.py:1482 >>   Resuming training from checkpoint with epoch 50 and global step 100
[INFO|2026-07-04 22:06:54] trainer.py:1486 >>   Fast-forwarding the dataloader past 50 epochs and 0 batches to resume from the exact training state.
[INFO|2026-07-04 22:06:54] trainer.py:1823 >> 

Training completed. Do not forget to share your model on huggingface.co/models =)
实验 3:目标轮数设为 100(大于已训练的 50)
  • 不设置检查点
  • 使用和之前训练50轮相同的目录

在这里插入图片描述
此时系统从 checkpoint-100 继续训练,日志显示 loss 正常下降,且损失函数图像是在上次训练的基础上继续下降的:
在这里插入图片描述
在这里插入图片描述

从日志可以进一步确认,是从100步位置坐的叠加训练,也就是虽然没有设置checkpoint,但是它自动检测到了同目录下上次训练的进度,自动追加了训练。

[INFO|2026-07-04 22:14:17] trainer.py:1476 >>   Total train batch size (w. parallel, distributed & accumulation) = 64
[INFO|2026-07-04 22:14:17] trainer.py:1477 >>   Gradient Accumulation steps = 8
[INFO|2026-07-04 22:14:17] trainer.py:1478 >>   Total optimization steps = 200
[INFO|2026-07-04 22:14:17] trainer.py:1479 >>   Number of trainable parameters = 20,185,088
[INFO|2026-07-04 22:14:17] trainer.py:1482 >>   Resuming training from checkpoint with epoch 50 and global step 100
[INFO|2026-07-04 22:14:17] trainer.py:1486 >>   Fast-forwarding the dataloader past 50 epochs and 0 batches to resume from the exact training state.
[INFO|2026-07-04 22:14:24] logging.py:144 >> {'loss': 1.3129, 'learning_rate': 2.3430e-05, 'epoch': 52.89, 'throughput': 51376.91}
[INFO|2026-07-04 22:14:29] logging.py:144 >> {'loss': 1.2277, 'learning_rate': 2.1477e-05, 'epoch': 55.00, 'throughput': 31868.31}
[INFO|2026-07-04 22:14:35] logging.py:144 >> {'loss': 1.0345, 'learning_rate': 1.9546e-05, 'epoch': 57.89, 'throughput': 21679.81}
[INFO|2026-07-04 22:14:40] logging.py:144 >> {'loss': 1.0071, 'learning_rate': 1.7649e-05, 'epoch': 60.00, 'throughput': 17822.70}
[INFO|2026-07-04 22:14:46] logging.py:144 >> {'loss': 0.8158, 'learning_rate': 1.5797e-05, 'epoch': 62.89, 'throughput': 14582.92}
[INFO|2026-07-04 22:14:51] logging.py:144 >> {'loss': 0.7418, 'learning_rate': 1.4002e-05, 'epoch': 65.00, 'throughput': 12969.94}
[INFO|2026-07-04 22:14:58] logging.py:144 >> {'loss': 0.6239, 'learning_rate': 1.2274e-05, 'epoch': 67.89, 'throughput': 11389.01}
[INFO|2026-07-04 22:15:02] logging.py:144 >> {'loss': 0.6259, 'learning_rate': 1.0625e-05, 'epoch': 70.00, 'throughput': 10515.47}
[INFO|2026-07-04 22:15:09] logging.py:144 >> {'loss': 0.5263, 'learning_rate': 9.0644e-06, 'epoch': 72.89, 'throughput': 9587.54}
[INFO|2026-07-04 22:15:13] logging.py:144 >> {'loss': 0.5112, 'learning_rate': 7.6022e-06, 'epoch': 75.00, 'throughput': 9035.56}
[INFO|2026-07-04 22:15:20] logging.py:144 >> {'loss': 0.4293, 'learning_rate': 6.2472e-06, 'epoch': 77.89, 'throughput': 8426.14}
[INFO|2026-07-04 22:15:25] logging.py:144 >> {'loss': 0.4917, 'learning_rate': 5.0079e-06, 'epoch': 80.00, 'throughput': 8046.11}
[INFO|2026-07-04 22:15:31] logging.py:144 >> {'loss': 0.3387, 'learning_rate': 3.8918e-06, 'epoch': 82.89, 'throughput': 7610.67}
[INFO|2026-07-04 22:15:36] logging.py:144 >> {'loss': 0.3492, 'learning_rate': 2.9059e-06, 'epoch': 85.00, 'throughput': 7334.85}
[INFO|2026-07-04 22:15:42] logging.py:144 >> {'loss': 0.3851, 'learning_rate': 2.0561e-06, 'epoch': 87.89, 'throughput': 7014.66}
[INFO|2026-07-04 22:15:47] logging.py:144 >> {'loss': 0.3665, 'learning_rate': 1.3479e-06, 'epoch': 90.00, 'throughput': 6803.48}
[INFO|2026-07-04 22:15:54] logging.py:144 >> {'loss': 0.3145, 'learning_rate': 7.8542e-07, 'epoch': 92.89, 'throughput': 6554.39}
[INFO|2026-07-04 22:15:58] logging.py:144 >> {'loss': 0.2759, 'learning_rate': 3.7227e-07, 'epoch': 95.00, 'throughput': 6387.81}
[INFO|2026-07-04 22:16:05] logging.py:144 >> {'loss': 0.3118, 'learning_rate': 1.1095e-07, 'epoch': 97.89, 'throughput': 6189.13}
[INFO|2026-07-04 22:16:09] logging.py:144 >> {'loss': 0.3289, 'learning_rate': 3.0842e-09, 'epoch': 100.00, 'throughput': 6055.64}
[INFO|2026-07-04 22:16:09] trainer.py:3815 >> Saving model checkpoint to saves/DeepSeek-R1-7B-Distill/lora/train_2026-07-04-21-38-32/checkpoint-200
[INFO|2026-07-04 22:16:10] configuration_utils.py:769 >> loading configuration file /home/xiezhongjun/hugging-face/hub/models--deepseek-ai--DeepSeek-R1-Distill-Qwen-7B/snapshots/916b56a44061fd5cd7d6a8fb632557ed4f724f60/config.json

实验 4:继续提升至 150 轮

为了确保完全拟合,训练继续,最终损失降至 0.1 左右,基本拟合:

在这里插入图片描述

在这里插入图片描述


五、最终测试效果

简单问答(如询问名字)

模型能正确回答训练集中出现的名称(没有上之前一样把他和同名项目弄混了,说明训练集内容在模型中的权重得到了提高):

在这里插入图片描述

复杂问题(超出训练集)

模型会“乱编”一些训练集中没有的内容,说明数据集覆盖度不足:

在这里插入图片描述

在这里插入图片描述


六、显存占用情况

加载 7B 模型并进行 LoRA 对话测试时,显存占用仅约 3.5GB,非常友好:

在这里插入图片描述
卸载模型后占用是 0.53GB,可供对比
在这里插入图片描述

七、总结与建议

  1. 断点续训逻辑

    • LLaMA-Factory 会自动从输出目录中读取最新的检查点。
    • 若目标总步数 ≤ 已训练步数,则直接跳过训练(不会覆盖)。
    • 若要继续训练,只需增加轮数(或步数)并保持输出目录不变。
  2. 数据集质量至关重要
    即使模型在训练集上拟合得很好,对于训练集外的问题仍可能产生幻觉。建议扩大数据集的多样性和覆盖面。

  3. WebUI 零代码方案
    非常适合快速验证和调参,无需编写任何代码,所有操作通过界面完成。

  4. 硬件需求低
    7B 模型 + LoRA 对话仅需 3.5GB 显存,看起来很理想,可以在消费级显卡上不部署运行,等待后续进一步验证测试。


Logo

一站式 AI 云服务平台

更多推荐