💡 本文会带给你
- 认识常用的指令监督微调数据集
- 如何构建自定义数据集
- 为什么是QLoRA
- 学会使用LLamFactory 进行QLoRA微调大模型
一、数据集
常用的数据集有 Alpaca 格式和 ShareGPT 格式,下面我们分别介绍这两种格式数据集。
Alpaca 格式数据集
Alpaca 数据集可以适用不同的任务,如指令监督微调、预训练、偏好训练、KTO、多模态,下面我们主要介绍指令监督微调数据集。
指令监督微调数据集
指令监督微调(Instruct Tuning)通过让模型学习详细的指令以及对应的回答来优化模型在特定指令下的表现。
指令监督微调数据集格式为:
In [ ]:
[ { "instruction": "人类指令(必填)", "input": "人类输入(选填)", "output": "模型回答(必填)", "system": "系统提示词(选填)", "history": [ ["第一轮指令(选填)", "第一轮回答(选填)"], ["第二轮指令(选填)", "第二轮回答(选填)"] ] } ]
instruction 列对应的内容为人类指令, input 列对应的内容为人类输入, output 列对应的内容为模型回答。下面是一个例子
In [ ]:
{ "instruction": "计算这些物品的总费用。 ", "input": "输入:汽车 - $3000,衣服 - $100,书 - $20。", "output": "汽车、衣服和书的总费用为 $3000 + $100 + $20 = $3120。" },
在进行指令监督微调时, instruction 列对应的内容会与 input 列对应的内容拼接后作为最终的人类输入,即人类输入为 instruction\ninput。而 output 列对应的内容为模型回答。
在上面的例子中,人类的最终输入是: 计算这些物品的总费用。
输入:汽车 - 3000,衣服−3000,衣服−100,书 - $20。
模型的回答是:汽车、衣服和书的总费用为 3000+3000+100 + 20=20=3120。
如果指定, system 列对应的内容将被作为系统提示词。
history 列是由多个字符串二元组构成的列表,分别代表历史消息中每轮对话的指令和回答。注意在指令监督微调时,历史消息中的回答内容也会被用于模型学习。
指令监督微调数据集 格式要求 如下:
In [ ]:
[ { "instruction": "人类指令(必填,最后的指令)", "input": "人类输入(选填)", "output": "模型回答(必填)", "system": "系统提示词(选填)", "history": [ ["第一轮指令(选填)", "第一轮回答(选填)"], ["第二轮指令(选填)", "第二轮回答(选填)"] ... ["第N轮指令(选填)", "第N轮回答(选填)"] ] } ]
下面是一个 alpaca 格式 多轮 对话的例子,对于单轮对话只需省略 history 列即可。
In [ ]:
[ { "instruction": "今天的天气怎么样?", "input": "", "output": "今天的天气不错,是晴天。", "history": [ [ "今天会下雨吗?", "今天不会下雨,是个好天气。" ], [ "今天适合出去玩吗?", "非常适合,空气质量很好。" ] ] } ]
ShareGPT
ShareGPT数据集可以适用不同的任务,如指令监督微调、偏好训练、OpenAI格式,ShareGPT 格式中的 KTO数据集(样例)和多模态数据集(样例) 与 Alpaca 格式的类似, 预训练数据集不支持 ShareGPT 格式。下面我们主要介绍指令监督微调数据集。
指令监督微调数据集
格式为:
In [ ]:
[ { "conversations": [ { "from": "human", "value": "人类指令" }, { "from": "function_call", "value": "工具参数" }, { "from": "observation", "value": "工具结果" }, { "from": "gpt", "value": "模型回答" } ], "system": "系统提示词(选填)", "tools": "工具描述(选填)" } ]
相比 alpaca 格式的数据集, sharegpt 格式支持 更多 的角色种类,例如 human、gpt、observation、function 等等,更适合多轮对论聊天对话。它们构成一个对象列表呈现在 conversations 列中。
下面是 sharegpt 格式的一个例子:
In [ ]:
{ "conversations": [ { "from": "human", "value": "你好,我出生于1990年5月15日。你能告诉我我今天几岁了吗?" }, { "from": "function_call", "value": "{\"name\": \"calculate_age\", \"arguments\": {\"birthdate\": \"1990-05-15\"}}" }, { "from": "observation", "value": "{\"age\": 31}" }, { "from": "gpt", "value": "根据我的计算,你今天31岁了。" } ], "tools": "[{\"name\": \"calculate_age\", \"description\": \"根据出生日期计算年龄\", \"parameters\": {\"type\": \"object\", \"properties\": {\"birthdate\": {\"type\": \"string\", \"description\": \"出生日期以YYYY-MM-DD格式表示\"}}, \"required\": [\"birthdate\"]}}]" }
注意其中 human 和 observation 必须出现在奇数位置,gpt 和 function 必须出现在偶数位置。
二、QloRA微调
QLoRA(Quantized LoRA)
核心思想:在 LoRA 的基础上,对原始模型进行量化(压缩),进一步减少显存占用,让大模型微调能在消费级GPU(如24GBhh显存)上运行。
✅ 核心改进:
4-bit量化:把原始模型的权重从16-bit(FP16)压缩到4-bit,显存减少4倍。
量化常数(Quantization Constants):存储少量额外参数,确保量化后的模型仍能恢复接近原始精度。
Paged Optimizers:像电脑内存分页一样管理显存,避免OOM(内存不足)。
❌ 缺点:
量化会引入轻微精度损失,但实验表明对最终性能影响很小。
比普通LoRA稍复杂,需要处理量化-反量化过程。
比喻:
你的汽车(大模型)被“压缩”成一辆更轻便的版本(4-bit量化),然后再加装“沙漠越野套件”(LoRA)。这样车子更省油(显存占用低),甚至能用家用车库(消费级GPU)来改装。
💡关键区别总结
特性 | LoRA | QLoRA |
---|---|---|
模型存储精度 | FP16/BF16(完整精度) | 4-bit(量化压缩) |
显存占用 | 较高(需存完整模型) | 极低(量化后模型更小) |
硬件要求 | 需要高端GPU(如A100) | 可在消费级GPU(如RTX 3090)运行 |
训练速度 | 较慢(完整精度计算) | 更快(量化加速计算) |
适用场景 | 中等规模微调 | 资源极度受限的微调 |
该选哪个?
用 LoRA:如果你有足够的显存(如40GB+),且希望尽量保持模型精度。
用 QLoRA:如果显存紧张(如只有24GB),但仍想微调大模型(如LLaMA-70B)。
总之:QLoRA = LoRA + 模型量化,让大模型微调真正“飞入寻常百姓家”。
三、使用LLama Factory进行QLoRA微调模型
1. 准备数据集
微调数据集我们使用魔塔社区 多轮聊天 数据集 中的 multi_test.jsonl ,该数据集为 ShareGPT 格式。
下载命令:
modelscope download --dataset greatheart/chat --local_dir ./datas/greatheart/chat
数据样式为:
In [16]:
{"conversations": [{"from": "user", "value": "什么时候发货?"}, {"from": "assistant", "value": "亲,您的订单将在下单后48小时内安排发货哦。"}, {"from": "user", "value": "好的谢谢"}, {"from": "assistant", "value": "好的,亲亲~"}]}
Out[16]:
{'conversations': [{'from': 'user', 'value': '什么时候发货?'}, {'from': 'assistant', 'value': '亲,您的订单将在下单后48小时内安排发货哦。'}, {'from': 'user', 'value': '好的谢谢'}, {'from': 'assistant', 'value': '好的,亲亲~'}]}
In [17]:
{"conversations": [{"from": "user", "value": "什么时候发货?"}, {"from": "assistant", "value": "亲,您的订单将在下单后48小时内安排发货哦。"}, {"from": "user", "value": "我已经拼单成功1天,能尽快帮我发货吗?"}, {"from": "assistant", "value": "亲亲,您的订单已按先后顺序安排发货,春节期间我们加班加点,会尽快为您的商品加急处理,请放心等候哦!"}]}
Out[17]:
{'conversations': [{'from': 'user', 'value': '什么时候发货?'}, {'from': 'assistant', 'value': '亲,您的订单将在下单后48小时内安排发货哦。'}, {'from': 'user', 'value': '我已经拼单成功1天,能尽快帮我发货吗?'}, {'from': 'assistant', 'value': '亲亲,您的订单已按先后顺序安排发货,春节期间我们加班加点,会尽快为您的商品加急处理,请放心等候哦!'}]}
该数据集不能直接用于LLama Factory微调,需要修该为下面的格式:
In [18]:
[ {"conversations": [{"from": "user", "value": "什么时候发货?"}, {"from": "assistant", "value": "亲,您的订单将在下单后48小时内安排发货哦。"}, {"from": "user", "value": "好的谢谢"}, {"from": "assistant", "value": "好的,亲亲~"}]} , {"conversations": [{"from": "user", "value": "什么时候发货?"}, {"from": "assistant", "value": "亲,您的订单将在下单后48小时内安排发货哦。"}, {"from": "user", "value": "我已经拼单成功1天,能尽快帮我发货吗?"}, {"from": "assistant", "value": "亲亲,您的订单已按先后顺序安排发货,春节期间我们加班加点,会尽快为您的商品加急处理,请放心等候哦!"}]} ]
Out[18]:
[{'conversations': [{'from': 'user', 'value': '什么时候发货?'}, {'from': 'assistant', 'value': '亲,您的订单将在下单后48小时内安排发货哦。'}, {'from': 'user', 'value': '好的谢谢'}, {'from': 'assistant', 'value': '好的,亲亲~'}]}, {'conversations': [{'from': 'user', 'value': '什么时候发货?'}, {'from': 'assistant', 'value': '亲,您的订单将在下单后48小时内安排发货哦。'}, {'from': 'user', 'value': '我已经拼单成功1天,能尽快帮我发货吗?'}, {'from': 'assistant', 'value': '亲亲,您的订单已按先后顺序安排发货,春节期间我们加班加点,会尽快为您的商品加急处理,请放心等候哦!'}]}]
In [ ]:
#转换代码,文件名 converter.py import argparse def convert_file_format(input_file, output_file): """ 将源文件格式转换为目标文件格式 参数: input_file: 输入文件名 output_file: 输出文件名 """ try: # 读取输入文件 with open(input_file, 'r', encoding='utf-8') as f: lines = [line.strip() for line in f if line.strip()] # 转换格式 formatted_lines = [f' {line}' for line in lines] output_content = '[\n' + ',\n'.join(formatted_lines) + '\n]' # 写入输出文件 with open(output_file, 'w', encoding='utf-8') as f: f.write(output_content) print(f"文件转换成功,结果已保存到 {output_file}") except Exception as e: print(f"转换过程中出现错误: {e}") raise def main(): # 设置命令行参数解析 parser = argparse.ArgumentParser(description='文件格式转换工具') parser.add_argument('-i', '--input', required=True, help='输入文件名') parser.add_argument('-o', '--output', required=True, help='输出文件名') args = parser.parse_args() # 调用转换函数 convert_file_format(args.input, args.output) if __name__ == '__main__': main()
执行 python converter.py --input 源文件.txt --output 目标文件.txt
2. LLama Factory微调配置
数据资源文件 dataset_info.json 配置
拷贝multi_test.jsonl 到 LLaMA-Factory/data/multi_test.json 下
修改 LLaMA-Factory/data/dataset_info.json ,增加multi_test.json数据集描述,应为:
"multi_chat":
In [ ]:
{ "file_name": "multi_test.json", "formatting": "sharegpt", "columns": { "messages": "conversations", "images": "" }, "tags": { "role_tag": "from", "content_tag": "value", "user_tag": "user", "assistant_tag": "assistant" } },
微调参数设置
QLoRA微调参数设置在LoRA微调的基础上增加量化设置了,并对其他参数做了微调。
LoRA微调时界面主要参数设置:
模型名称 Qwen2.5-0.5B-Instruct
模型路径 /mnt/d/linux/models/Qwen/Qwen2.5-0.5B-Instruct #模型本地绝对路径
微调方法 lora
数据集 identity
训练轮数 500 截断长度 218 批处理大小 20
验证集比例 0.1 其他参数都默认
✅QLoRA微调增加或修改参数设置
量化等级(启用量化(QLoRA):选择 8或4,把原始模型的权重从16-bit(FP16)压缩到8-bit或4-bit,显存减少2倍或4倍,因我的显存只有12G,所以这里选择 4
LoRA 参数设置: LoRA 秩(LoRA 矩阵的秩大小)设置为 64,LoRA 缩放系数(LoRA 缩放系数大小)设置为128,一般,LoRA 缩放系数=LoRA 秩 * 2
数据集:multi_chat,identity
训练轮数: 10 截断长度: 512
批处理大小: 10
其它参数设置: 保存间隔(每两次断点保存间的更新步数)设置为 500
3. 训练
训练日志信息
In [ ]:
[INFO|trainer.py:2405] 2025-03-30 14:04:02,088 >> ***** Running training ***** [INFO|trainer.py:2406] 2025-03-30 14:04:02,088 >> Num examples = 1,091 [INFO|trainer.py:2407] 2025-03-30 14:04:02,089 >> Num Epochs = 10 [INFO|trainer.py:2408] 2025-03-30 14:04:02,089 >> Instantaneous batch size per device = 10 [INFO|trainer.py:2410] 2025-03-30 14:04:02,089 >> Training with DataParallel so batch size has been adjusted to: 2 [INFO|trainer.py:2411] 2025-03-30 14:04:02,089 >> Total train batch size (w. parallel, distributed & accumulation) = 16 [INFO|trainer.py:2412] 2025-03-30 14:04:02,089 >> Gradient Accumulation steps = 8 [INFO|trainer.py:2413] 2025-03-30 14:04:02,089 >> Total optimization steps = 680 [INFO|trainer.py:2414] 2025-03-30 14:04:02,091 >> Number of trainable parameters = 73,859,072 [WARNING|logging.py:329] 2025-03-30 14:04:02,103 >> Warning: The following arguments do not match the ones in the `trainer_state.json` within the checkpoint directory: per_device_train_batch_size: 10 (from args) != 2 (from trainer_state.json) [INFO|trainer.py:2436] 2025-03-30 14:04:02,103 >> Continuing training from checkpoint, will skip to saved global_step [INFO|trainer.py:2437] 2025-03-30 14:04:02,103 >> Continuing training from epoch 0 [INFO|trainer.py:2438] 2025-03-30 14:04:02,103 >> Continuing training from global step 15 [INFO|trainer.py:2440] 2025-03-30 14:04:02,103 >> Will skip the first 0 epochs then the first 120 batches in the first epoch. 3%|█████▌ | 20/680 [00:18<15:52, 1.44s/it][INFO|2025-03-30 14:04:20] llamafactory.train.callbacks:143 >> {'loss': 3.1442, 'learning_rate': 4.9893e-05, 'epoch': 0.29, 'throughput': 1621.76}
训练日志的中文解释,按关键信息分类说明
基础训练配置
训练数据量 Num examples = 1,091
训练集共 1,091条 样本数据
训练轮次 Num Epochs = 10
总共训练 10个epoch(完整遍历数据集的次数)
批处理大小
单设备批大小: Instantaneous batch size per device = 10
实际调整后批大小: Training with DataParallel so batch size has been adjusted to: 2
因使用 DataParallel并行训练,系统自动调整为每设备2条样本
总有效批大小: Total train batch size = 16
考虑并行+梯度累积后的等效批大小(2设备×8梯度累积步数)
优化步骤
总优化步数: Total optimization steps = 680
计算公式:总步数 = (样本数 × epoch数) / 总批大小 = (1091×10)/16 ≈ 680
梯度累积步数: Gradient Accumulation steps = 8
每8个小批次才更新一次模型权重
模型参数量
Number of trainable parameters = 73,859,072
模型可训练参数量约 7386万(73.8M)
警告信息
Warning: The following arguments do not match... per_device_train_batch_size: 10 (from args) != 2 (from trainer_state.json)
原因:当前设置的批大小(10)与之前保存的训练状态(2)不一致
影响:系统会自动采用检查点中的值(2),可能影响训练效率
恢复训练状态 Continuing training from checkpoint
从之前保存的检查点 继续训练(非从头开始)
Continuing from epoch 0, global step 15
从 第0个epoch的第15步 恢复
Will skip the first 0 epochs then the first 120 batches
跳过已完成的epoch和批次(避免重复训练)
实时训练监控 进度条 3%|█████▌ | 20/680 [00:18<15:52, 1.44s/it]
已完成 20步(总680步,进度3%)
当前速度:每步1.44秒
预计剩余时间:15分52秒
训练指标
{'loss': 3.1442, 'learning_rate': 4.9893e-05, 'epoch': 0.29, 'throughput': 1621.76}
损失值(loss): 3.1442(当前批次的交叉熵损失)
学习率: 4.9893e-5(约5e-5)
进度: 第0.29个epoch(即29%的epoch0)
吞吐量: 1621.76样本/秒(处理速度)
关键问题提示
批大小不匹配
建议检查训练脚本和检查点配置,确保批大小一致
损失值较高(3.14)
初始训练阶段正常,需观察后续是否下降
吞吐量优化
当前1621样本/秒的吞吐量,可尝试增大批大小提升效率
如果需要进一步优化训练,可以调整:
梯度累积步数(平衡显存和稳定性)
学习率(观察loss变化调整)
数据加载方式(提升吞吐量)
训练完成日志信息
训练完成后的关键信息解释(已过滤日志前缀,保留核心内容+中文说明):
训练结果摘要 Training completed. Do not forget to share your model on huggingface.co/models 训练已完成,提示可将模型上传至HuggingFace模型库分享
核心训练指标 {'train_runtime': 1940.1924, ...}
train_runtime: 1940秒(约32分20秒) 总训练耗时32分钟
train_samples_per_second: 5.623 每秒处理5.6条样本
train_steps_per_second: 0.35 每秒完成0.35个训练步(约2.85秒/步)
train_loss: 0.4779 最终训练损失值0.4779(较初始3.1442显著下降)
epoch: 9.88 实际完成9.88个训练轮次(接近预设的10个epoch)
num_input_tokens_seen: 1,488,944 累计处理148.8万输入token
100%|██████████| 680/680 [32:20<00:00, 2.85s/it] 进度条显示:680步全部完成,平均每步耗时2.85秒
模型保存信息 Saving model checkpoint to saves/Qwen2.5-1.5B-Instruct/lora/train_2025-03-30-12-07-16 模型检查点已保存至指定目录(含训练时间戳)
Model config Qwen2Config 模型架构配置:
模型类型:qwen2(基于Qwen2.5-1.5B-Instruct)
关键参数:
1536隐藏层维度
28个Transformer层
12个注意力头
支持32768上下文长度
词表大小151,936
训练过程指标 total_flos = 11516711GF 总计算量1.15×10¹⁹浮点运算
Figure saved at: .../training_loss.png 训练损失曲线图已保存
[WARNING] No metric eval_loss/eval_accuracy to plot 警告:未记录验证集损失/准确率(可能未设置验证集)
关键结论 训练效果
损失从初始3.14降至0.4779,收敛正常
吞吐量5.6样本/秒(受批大小和梯度累积影响)
资源消耗
总训练时间32分钟
处理近150万token
后续建议
检查eval_loss警告,建议添加验证集监控过拟合
可通过增大批大小或减少梯度累积步数提升训练速度
4. 测试
用界面的 Chat 功能 检查点路径配置为检查点绝对路径:/.../LLaMA-Factory/saves/Qwen2.5-1.5B-Instruct/lora/train_2025-03-30-12-07-16/checkpoint-680
加载模型 进行对话聊天
5. 导出
导出目录(保存导出模型的文件夹路径):填写导出的绝对路径 最大分块大小(GB单个模型文件的最大大小):选择 4GB 导出设备(导出模型使用的设备类型): 选 auto