本文主要介绍如何使用 LLaMAFactory 实现大模型微调,基于 Qwen2.5-7B-Instruct 模型进行 LoRA 微调
基础概念#
大模型训练流程#
ChatGPT 的训练流程
- 预训练(Pre Training,PT):这一阶段是模型训练的基础,利用海量数据、大量算力通过无监督训练得到一个基础模型。预训练后的模型具备强大的语言生成能力,但由于它主要是无监督训练的结果,可能不会直接适应具体的任务(如问答、对话),需要进一步的微调。
- 监督微调(Supervised Fine-Tuning,SFT):这一阶段则是对 Base 模型进行微调,让模型能够适用特定任务,最终得到一个 SFT 模型。
- 微调的目的是让模型更好地理解特定任务的需求。例如,通过使用对话数据集对模型进行微调,可以让模型在遇到问题时生成更相关的答案,而不是简单地生成与问题相似的文本。
- 这些问答对话由人类准备的,通常是有标签的,包含了问题和答案对,或者其他特定任务的数据。
- 强化学习(Reinforcement Learning form Human Feedback,RLHF):这一阶段通过引入人类反馈(或者基于人类反馈训练的奖励模型)进一步优化模型的生成质量,使其生成的回答更符合用户的期望和人类的价值观。
- 一般按照 3H 原则进行打分:
- Helpful:判断模型遵循用户指令以及推断指令的能力。
- Honest:判断模型产生幻觉(编造事实)的倾向。
- Harmless:判断模型的输出是否适当、是否诋毁或包含贬义内容。
- 由于直接从人类获取反馈的成本较高,通常会先训练一个奖励模型(Reward Model,RM)来代替人类打分,这样可以在 RL 的框架下进行大规模的自动优化。
- 一般按照 3H 原则进行打分:
预训练由于对算力和数据需求都比较大,因此一般玩家不会涉及到预训练,更多的是基于开源的基础模型(LLama、deepseek、Qwen、ChatGLM…)做微调、强化学习以满足自身需求。
模型名称各部分释义#
以 Qwen1.5-14B-Chat-GQPT-Int4 为例,可以分为 5 个部分,具体含义如下图:
5 个参数含义如下:
- 模型系列:一般一个公司、组织的模型都会归属于同一个系列,比如阿里的 Qwen 系列、Meta 的 Llama 系列,智普的 Chatglm 系列。
- 模型版本:一个系列的模型也会有多个版本,一般是有大更新时才会更新,比如 Qwen 系列就存在 Qwen、Qwen1.5、Qwen2.5 系列。
- 参数量:一般为 xx B,B 为单位,表示 10 亿参数,比如 7B 则是有 70 亿参数,72B 则是 720 亿参数。这个只是一个大致范围,比如 68 亿、72 亿参数的模型一般也叫做 7B。
- 微调:开源模型为了能够直接使用,一般会提供经过问答任务微调的版本,即 Chat 模型。
- 量化:为了降低内存占用有的也会提供量化版本,比如大模型一般使用 FP32/FP16 精度,即一个参数占 4 或者 2 字节,而量化则是将权重参数使用更低精度的格式来存放,比如量化为 FP8 则是只需要 1 字节,Int4 则只需要 4 位。
其中需要注意的就是是否经过微调、量化:
- 基础模型:不带任意后缀,或者 -Base 后缀,就是预训练后未经过微调的原始模型,比如 Qwen1.5、Llama3。
- SFT 模型:带特定领域任务后缀,比如 xxx-chat,就是对基础模型做了问答任务微调,比如 Qwen1.5-Chat、Baichuan2-Chat。
- 量化模型:它通过将模型中的高精度浮点数参数转换为低精度的整数参数来减少模型的存储和计算需求。这样做可以显著降低模型的内存占用,加快推理速度,并减少能耗。量化可以带来显著的效率提升,但也可能引入一些精度损失。
- GGUF:GGUF(以前称为 GGML)是一种量化方法,允许用户使用 CPU 来运行 LLM,但也可以将其某些层加载到 GPU 以提高速度。
- GPTQ:GPTQ 是一种 4 位量化的训练后量化(PTQ)方法,主要关注 GPU 推理和性能。
- AWQ:是一种新格式(激活感知权重量化),它是一种类似于 GPTQ 的量化方法。AWQ 和 GPTQ 作为方法有几个不同之处,但最重要的是 AWQ 假设并非所有权重对 LLM 的性能都同等重要。也就是说在量化过程中会跳过一小部分权重,这有助于减轻量化损失。所以他们的论文提到了与 GPTQ 相比的可以由显著加速,同时保持了相似的,有时甚至更好的性能。
微调(SFT)#
大模型微调,通常指有监督微调(Supervised Fine-Tuning, SFT),是在预训练模型(一般称为“基座模型”)的基础上进行的训练过程。
预训练模型通常已经掌握了广泛的语言知识和语义表示,但为了让模型在特定任务或领域上表现得更好,我们会在特定任务的数据集上对其进行微调。它在任务性能、领域适应性、数据利用效率和计算成本等方面具有显著的优势。
微调方法分类#
微调根据更新参数量的不同可以分为以下两种:
- 全量参数更新 Full Fine-tuning(FFT):即对预训练模型的所有参数进行更新,训练速度较慢,消耗机器资源较多。
- 参数高效微调 Parameter-Efficient Fine-Tuning(PEFT):只对部分参数做调整,训练速度快,消耗机器资源少。
理论上,预训练和微调都可以做全量参数更新和部分参数更新,但是一般实际训练时都是 预训练 + 全量,微调 + 部分参数更新 这样组合的。
FFT 的原理,就是用特定的数据,对大模型进行训练,将 W 变成 W′,W′相比 W,最大的优点就是上述特定数据领域的表现会好很多。但是可能造成训练的成本比较高和灾难性遗忘(Catastrophic Forgetting),在其他领域的能力变差。
PEFT 主要想解决的问题就是 FFT 存在的两个问题,也是目前比较主流的微调方案。
PERT 可分为三类:
- 添加额外参数的 Addition-based(A)
- 类似适配器的方法(Adapter-like methods)
- 软提示(Soft prompts)
- 选取部分参数更新 Selection-based(S)
- 引入重参数化 Reparametrization-based(R)
常见的 PEFT 方法:BitFit、Prompt Tuning、Prefix Tuning、P-Tuning、P-Tuning V2、Adapter Tuning、LoRA、QLoRA、MAM Adapter、UniPELT、 Freeze tuning 等等
- BitFit : 只更新模型中的 bias 参数或部分 bias 参数。
- Prefix Tuning : 在模型输入前添加可训练的前缀向量。
- Prompt Tuning : 在输入层加入提示词(prompt tokens)进行微调。
- P-Tuning : 在模型的每层都加入可学习的提示词。
- Adapter Tuning : 在 Transformer 的每一层插入小型的适配器网络。
- LoRA (Low-Rank Adaptation): 通过在模型的权重矩阵中引入低秩结构来进行微调。
- QLoRA(Quantized LoRA):提出了 NormalFloat 数据类型, 通过量化降低基座模型的显存占用,使得 65B 模型在单 GPU 上可以完成训练。
训练框架选择#
比较主流的几个微调工具:
- huggingface/transformers:最基础的一个库,提供了丰富的预训练模型和微调工具,支持大多数主流的 NLP 任务(如文本分类、序列标注、生成任务等)。适合进行快速实验和生产部署,有着广泛的社区支持。
- huggingface/peft:Parameter-Efficient Fine-Tuning,huggingface 开源的微调基础工具。
- modelscope/ms-swift:modelscope 开源的轻量级微调框架,以中文大模型为主,支持各类微调方法。可以通过执行脚本进行微调,也可以在代码环境中一键微调。自带微调数据集和验证数据集,可以一键微调和模型验证。
- hiyouga/LLaMA-Factory:全栈微调工具,支持海量模型和各种主流微调方法。支持运行脚本微调和基于 Web 端微调,自带基础训练数据集。除微调外,还支持增量预训练和全量微调。
- NVIDIA/Megatron-LM:NVIDIA 开发的大模型训练框架,支持大规模的预训练和微调。适用于需要极高性能和规模的大模型训练和微调。
快速实验选择 Transformers 即可,超大规模的选择 NVIDIA/Megatron-LM,普通规模就选择使用较为简单的 hiyouga/LLaMA-Factory。
SFT 微调准备#
数据集的质量对模型微调至关重要,微调后模型效果 80%取决于 SFT 训练数据,少量高质数据要比大量低质或者普通的数据好很多。
数据集格式#
通常 1 万条左右的精标数据即可发挥良好的效果,在扩充数据规模时需要注意数据多样性,多样性的数据可以提高模型性能。 数据质量可以通过 ppl、reward model,文本质量分类模型等方式进行初步评估。经过人工进行后续筛选。
ALpaca 格式#
指令监督微调数据集#
指令监督微调(Instuct Tuning)通过让模型学习详细的指令以及对应的回答来优化模型在特定指令下的表现。
instruction
列对应的内容会与 input
列对应的内容拼接后作为人类指令,即人类指令为 instruction\ninput
,而 output
列对应的内容为模型回答。
"alpaca_zh_demo.json"
{
"instruction": "计算这些物品的总费用。 ",
"input" : "输入:汽车 - $3000,衣服 - $100,书 - $20。",
"output" : "汽车、衣服和书的总费用为 $3000 + $100 + $20 = $3120。"
},
在上例中,人类的最终输入是:
计算这些物品的总费用。
输入:汽车 - $3000,衣服 - $100,书 - $20。
模型的回答:
汽车、衣服和书的总费用为 $3000 + $100 + $20 = $3120。
如果指定,system
列对应的内容将被作为系统提示词。
history
列是由多个字符串二元组构成的列表,分别代表历史消息中每轮对话的指令和回答。注意在指令监督微调时,历史消息中的回答内容也会被用于模型学习。
下面提供一个 alpaca
格式 多轮 对话的例子,对于单轮对话只需省略 history
列即可。
[
{
"instruction": "今天的天气怎么样?",
"input": "",
"output": "今天的天气不错,是晴天。",
"history": [
[
"今天会下雨吗?",
"今天不会下雨,是个好天气。"
],
[
"今天适合出去玩吗?",
"非常适合,空气质量很好。"
]
]
}
]
预训练数据集#
大语言模型通过学习未被标记的文本进行预训练,从而学习语言的表征。通常,预训练数据集从互联网上获得,因为互联网上提供了大量的不同领域的文本信息,有助于提升模型的泛化能力。 预训练数据集文本描述格式如下:
[
{"text": "document"},
{"text": "document"}
]
偏好数据集#
偏好数据集用于奖励模型训练、DPO 训练和 ORPO 训练。对于系统指令和人类输入,偏好数据集给出了一个更优的回答和一个更差的回答。
一些研究 表明通过让模型学习“什么更好”可以使得模型更加迎合人类的需求。 甚至可以使得参数相对较少的模型的表现优于参数更多的模型。
偏好数据集需要在 chosen 列中提供更优的回答,并在 rejected 列中提供更差的回答,在一轮问答中其格式如下:
[
{
"instruction": "人类指令(必填)",
"input": "人类输入(选填)",
"chosen": "优质回答(必填)",
"rejected": "劣质回答(必填)"
}
]
KTO 数据集#
KTO 数据集与偏好数据集类似,但不同于给出一个更优的回答和一个更差的回答,KTO 数据集对每一轮问答只给出一个 true
/false
的 label
。 除了 instruction
以及 input
组成的人类最终输入和模型回答 output
,KTO 数据集还需要额外添加一个 kto_tag
列(true/false)来表示人类的反馈。
多模态数据集#
多模态数据集需要额外添加一个 images 列,包含输入图像的路径。目前我们仅支持单张图像输入。
Sharegpt 格式#
ShareGPT 格式中的 KTO 数据集(样例)和多模态数据集(样例) 与 Alpaca 格式的类似。 预训练数据集不支持 ShareGPT 格式。
指令监督微调数据集#
相比 alpaca 格式的数据集, sharegpt 格式支持 更多 的角色种类,例如 human、gpt、observation、function 等等。它们构成一个对象列表呈现在 conversations 列中。 下面是 sharegpt 格式的一个例子:
{
"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\"]}}]"
}
偏好数据集#
Sharegpt 格式的偏好数据集同样需要在 chosen 列中提供更优的消息,并在 rejected 列中提供更差的消息。 下面是一个例子:
OpenAI 格式#
OpenAI 格式仅仅是 sharegpt 格式的一种特殊情况,其中第一条消息可能是系统提示词。
SFT 超参数调整:#
比如 10 万个样本 2-3 个 epoch 内为佳,2 ~ 5 万个样本 一般是 4-5 个 epoch 并且领域增强的 SFT 数据不需要太多,质量一定要把握好,一般的领域总结回复的任务几百条数据即可( 个人经验 ),视情况而定;小数据量可以适当增大 epoch,让模型充分收敛。
例如:EPOCH:100 条数据时, Epoch 为 15,1000 条数据时, Epoch 为 10,10000 条数据时, Epoch 为 2。
- Epochs:需要根据数据集多少动态调整,比如 100 条数据时, Epoch 设置为 15,1000 条数据时, Epoch 为 10,10000 条数据时, Epoch 为 2。
- Learning Rate:根据不同微调方法 LR 也需要调整,对于 LoRA 的 peft 训练方式,同时可以适当增大 LR
- Global BatchSize:调整 bs 可以加快训练速度,但是也会增加显存占用,需要根据 GPU 资源调整。如增加 accumulate step 32 64,当分布式节点增多时可以进一步增加 batch_size,提高吞吐。
RLHF#
RLHF 是一种训练方式,并不是类似 Lora 这种的训练方法,RLHF 可以分为三阶段:
- Language Model,LM:一个预训练语言模型 LM,对基础模型微调得到一个微调后的模型
- 使用人工标注的数据对预训练模型进行监督微调,以获得初始版本的生成模型。
- Reward Model,RM:训练一个奖励模型 RM:训练一个奖励模型(Reward Model),用于评估生成模型的输出质量。
- 收集生成模型输出及其对应的人类反馈。这些反馈可以是评分、选择最佳输出、直接修改等形式。
- 使用这些反馈数据训练奖励模型,使其能够对生成的输出进行评分。
- 奖励模型通常是一个监督学习模型,通过最小化预测评分与人类反馈评分之间的差距进行训练。
- Reinforcement Learning,RL:用强化学习 RL 方式微调 LM :使用强化学习算法(如 PPO(Proximal Policy Optimization))进一步优化第一步中生成的模型,使其输出更符合人类反馈的期望。
- 使用初始生成模型产生输出,并通过奖励模型评估这些输出的质量
- 使用 PPO 算法,根据奖励模型的评分更新生成模型的参数。
- PPO 是一种强化学习算法,旨在平衡探索和利用,通过限制每次更新的幅度,确保稳定性和效率。
- 算法优化生成模型的策略,使其输出在奖励模型的评分下不断提升。
- 反复进行生成、评估、优化的循环,逐步提高生成模型的性能。
DPO#
RLHF 是一种复杂且通常不稳定的过程,首先需要拟合反映人类偏好的奖励模型,然后使用强化学习来微调无监督 LM 以最大化这一估计的奖励,同时避免与原始模型偏离过远。
**直接偏好优化(DPO)**是一种简单的无强化学习的语言模型偏好训练算法。
如何训练垂直领域大模型#
因为 Chat 模型就是在 Base 模型基础上做了微调以适应对话任务,掌握了生成对话内容的能力的 SFT 模型,因此再对 Chat 模型做 SFT 触发 灾难性遗忘 风险就比较高,相比之下 Base 因为没经过微调,因为触发 灾难性遗忘 的风险会比较低。 因此选择哪种模型取决于我们的场景。
- 青春版:Chat 模型 + SFT 资源消耗少、模型通用能力有所降低
- 完整版:Base 模型 + 增量预训练(Continue PreTraining) + SFT 资源消耗大、模型通用能力完整保留
最终一个完整的训练垂直领域大模型可以分为以下三步:
- Continue PreTraining(增量预训练) : 一般垂直大模型是基于通用基座大模型进行二次的训练,为了给模型注入领域知识,就需要用领域内的语料进行继续预训练。
- SFT ( Supervised Finetuning,有监督微调): 通过 SFT 可以激发大模型理解领域内的各种问题并进行回答的能力(在有召回知识的基础上)
- 强化学习:一般是二选一 RLHF(奖励建模、强化学习训练): 通过 RLHF 可以让大模型的回答对齐人们的偏好,比如行文的风格。 DPO(直接偏好优化)
微调实例#
服务器部署
环境配置 创建虚拟环境 安装 LLaMAFactory
git clone -b v0.8.1 https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e .[torch,metrics]
准备模型 使用 Qwen2.5-7B-Instruct 模型进行微调
# 安装并初始化 git-lfs apt install git-lfs -y git lfs install # 下载模型 git lfs clone https: //www.modelscope.cn/qwen/Qwen2.5-7B-Instruct.git
准备数据集 在 LLaMA-Factory 中,数据集的格式是一个 json 文件,每一行是一个 json 对象,根据项目需求,创建一个 Alpaca 格式的数据集
[ { "instruction": "判断用户的情感倾向(正面/负面/中性)。", "input" : "输入:地铁十号线真挤", "output" : "负面。是对交通出行舒适度的评价。" } ]
将新增数据集注册到
LLaMAFactory
中:- 将数据集移动到
data
目录下 - 修改
dataset_info.json
注册数据集
root@test: /LLaMA-Factory# cat data/dataset_info.json { "comments": { "file_name": "comments.json" }, }
- 将数据集移动到
开始微调 新版提供了 llamafactory-cli 命令行工具使用。
modelPath = models/Qwen1.5-1.8B-Chat llamafactory-cli train \ --model_name_or_path $modelPath \ --stage sft \ --do_train \ --finetuning_type lora \ --template qwen \ --dataset identity \ --output_dir ./saves/lora/sft \ --learning_rate 0.0005 \ --num_train_epochs 8 \ --cutoff_len 4096 \ --logging_steps 1 \ --warmup_ratio 0.1 \ --weight_decay 0.1 \ --gradient_accumulation_steps 8 \ --save_total_limit 1 \ --save_steps 256 \ --seed 42 \ --data_seed 42 \ --lr_scheduler_type cosine \ --overwrite_cache \ --preprocessing_num_workers 16 \ --plot_loss \ --overwrite_output_dir \ --per_device_train_batch_size 1 \ --fp16
结果查看#
查看 LoRA 权重 根据日志可以看到,微调后的模型保存到了我们指定的 ./saves/lora/sft 目录
[INFO|trainer.py:3410] 2025-02-19 20: 51: 37, 168 >> Saving model checkpoint to ./saves/lora/sft
root@lixd-sft: /LLaMA-Factory# ll -lhS ./saves/lora/sft
查看 loss 曲线 训练过程中会实时打印训练日志,其中就包括了 loss 信息,就像这样:
微调参数 –logging_steps = 1 因为每一步都会打印日志
{'loss': 3.9236, 'grad_norm': 2.572678327560425, 'learning_rate': 8.333333333333333e-05, 'epoch': 0.09}
{'loss': 3.3305, 'grad_norm': 1.8977322578430176, 'learning_rate': 0.00016666666666666666, 'epoch': 0.18}
{'loss': 4.3762, 'grad_norm': 2.840055227279663, 'learning_rate': 0.00025, 'epoch': 0.26}
.....
{'loss': 0.1993, 'grad_norm': 0.9958950281143188, 'learning_rate': 2.052496544188487e-06, 'epoch': 4.66}
{'loss': 0.407, 'grad_norm': 1.3202508687973022, 'learning_rate': 5.136518124159162e-07, 'epoch': 4.75}
{'loss': 0.2622, 'grad_norm': 1.1825435161590576, 'learning_rate': 0.0, 'epoch': 4.84}
- 1)如果没有明显收敛,说明训练不充分,可以增加训练 epoch 重训,或者进行增量训练。
- 2)如果收敛出现在训练过程的前半部分,而后部分的 loss 平稳无变化,说明可能有过拟合,可以结合评估结果选择是否减少 epoch 重训。
- 3)如果有收敛趋势,但没有趋于平稳,可以在权衡通用能力和专业能力的前提下考虑是否增加 epoch 和数据以提升专业能力,但会有通用能力衰减的风险。 可以看到,在训练到 70 步再往后的时候已经收敛了,看起来本次训练效果还可以。
参考资料#