ChatGLM3-6B 微调
本目录提供 ChatGLM3-6B 模型的微调示例,包括全量微调和 P-Tuning v2。格式上,提供多轮对话微调样例和输入输出格式微调样例。
如果将模型下载到了本地,本文和代码中的 THUDM/chatglm3-6b 字段均应替换为相应地址以从本地加载模型。
运行示例需要 python>=3.10,除基础的 torch 依赖外,示例代码运行还需要依赖。
我们提供了 [示例notebook]lora_finetune.ipynb 用于演示如何使用我们的微调代码。
1
pip install -r requirements.txt
测试硬件标准
我们仅提供了单机多卡/多机多卡的运行示例,因此您需要至少一台具有多个 GPU 的机器。本仓库中的默认配置文件中,我们记录了显存的占用情况:
- SFT 全量微调: 4张显卡平均分配,每张显卡占用
48346MiB显存。 - P-TuningV2 微调: 1张显卡,占用
18426MiB显存。 - LORA 微调: 1张显卡,占用
14082MiB显存。
请注意,该结果仅供参考,对于不同的参数,显存占用可能会有所不同。请结合你的硬件情况进行调整。
请注意,我们仅仅使用英伟达 Hopper(代表显卡:H100) 和 Ampère(代表显卡:A100) 架构和系列显卡做过测试。如果您使用其他架构的显卡,可能会出现
- 未知的训练问题 / 显存占用与上述有误差。
- 架构过低而不支持某些特性。
- 推理效果问题。
以上三种情况为社区曾经遇到过的问题,虽然概率较低,如果您遇到了以上问题,可以尝试在社区中解决。
多轮对话格式
多轮对话微调示例采用 ChatGLM3 对话格式约定,对不同角色添加不同 loss_mask 从而在一遍计算中为多轮回复计算 loss。
对于数据文件,样例采用如下格式
如果您仅希望微调模型的对话能力,而非工具能力,您应该按照以下格式整理数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[
{
"conversations": [
{
"role": "system",
"content": "<system prompt text>"
},
{
"role": "user",
"content": "<user prompt text>"
},
{
"role": "assistant",
"content": "<assistant response text>"
},
// ... Muti Turn
{
"role": "user",
"content": "<user prompt text>"
},
{
"role": "assistant",
"content": "<assistant response text>"
}
]
}
// ...
]
csv 转 训练数据 finetuen.csv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import csv
import json
# CSV文件的路径
csv_file_path = 'your_csv_file.csv'
# 输出JSON文件的路径
json_file_path = 'output_json_file.json'
# 打开CSV文件,并进行处理
with open(csv_file_path, mode='r', encoding='utf-8') as csv_file, \
open(json_file_path, mode='w', encoding='utf-8') as json_file:
csv_reader = csv.DictReader(csv_file)
for row in csv_reader:
# 根据CSV文件的列名获取数据
title = row['title']
reply = row['reply']
# 构造单条对话的JSON结构
conversation_entry = {
"conversations": [
{"role": "user", "content": title},
{"role": "assistant", "content": reply}
]
}
# 将单条记录以JSON格式写入文件,每条记录一行
json_line = json.dumps(conversation_entry, ensure_ascii=False)
json_file.write(json_line + '\n')
请注意,这种方法在微调的step较多的情况下会影响到模型的工具调用功能
如果您希望微调模型的对话和工具能力,您应该按照以下格式整理数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[
{
"tools": [
// available tools, format is not restricted
],
"conversations": [
{
"role": "system",
"content": "<system prompt text>"
},
{
"role": "user",
"content": "<user prompt text>"
},
{
"role": "assistant",
"content": "<assistant thought to text>"
},
{
"role": "tool",
"name": "<name of the tool to be called",
"parameters": {
"<parameter_name>": "<parameter_value>"
},
"observation": "<observation>"
// don't have to be string
},
{
"role": "assistant",
"content": "<assistant response to observation>"
},
// ... Muti Turn
{
"role": "user",
"content": "<user prompt text>"
},
{
"role": "assistant",
"content": "<assistant response text>"
}
]
}
// ...
]
-
关于工具描述的 system prompt 无需手动插入,预处理时会将
tools字段使用json.dumps(..., ensure_ascii=False)格式化后插入为首条 system prompt。 -
每种角色可以附带一个
bool类型的loss字段,表示该字段所预测的内容是否参与loss计算。若没有该字段,样例实现中默认对system,user不计算loss,其余角色则计算loss。 -
tool并不是 ChatGLM3 中的原生角色,这里的tool在预处理阶段将被自动转化为一个具有工具调用metadata的assistant角色(默认计算loss)和一个表示工具返回值的observation角色(不计算loss)。 -
目前暂未实现
Code interpreter的微调任务。 -
system角色为可选角色,但若存在system角色,其必须出现在user角色之前,且一个完整的对话数据(无论单轮或者多轮对话)只能出现一次system角色。
数据集格式示例
这里以 AdvertiseGen 数据集为例,
您可以从 Google Drive
或者 Tsinghua Cloud 下载 AdvertiseGen 数据集。
将解压后的 AdvertiseGen 目录放到 data 目录下并自行转换为如下格式数据集。
请注意,现在的微调代码中加入了验证集,因此,对于一组完整的微调数据集,必须包含训练数据集和验证数据集,测试数据集可以不填写。或者直接用验证数据集代替。
1
{"conversations": [{"role": "user", "content": "类型#裙*裙长#半身裙"}, {"role": "assistant", "content": "这款百搭时尚的仙女半身裙,整体设计非常的飘逸随性,穿上之后每个女孩子都能瞬间变成小仙女啦。料子非常的轻盈,透气性也很好,穿到夏天也很舒适。"}]}
配置文件
微调配置文件位于 config 目录下,包括以下文件:
ds_zereo_2 / ds_zereo_3.json: deepspeed 配置文件。lora.yaml / ptuning.yaml / sft.yaml: 模型不同方式的配置文件,包括模型参数、优化器参数、训练参数等。 部分重要参数解释如下:- data_config 部分
- train_file: 训练数据集的文件路径。
- val_file: 验证数据集的文件路径。
- test_file: 测试数据集的文件路径。
- num_proc: 在加载数据时使用的进程数量。 + max_input_length: 输入序列的最大长度。 + max_output_length: 输出序列的最大长度。 + training_args 部分
- output_dir: 用于保存模型和其他输出的目录。
- max_steps: 训练的最大步数。
- per_device_train_batch_size: 每个设备(如 GPU)的训练批次大小。
- dataloader_num_workers: 加载数据时使用的工作线程数量。
- remove_unused_columns: 是否移除数据中未使用的列。
- save_strategy: 模型保存策略(例如,每隔多少步保存一次)。
- save_steps: 每隔多少步保存一次模型。
- log_level: 日志级别(如 info)。
- logging_strategy: 日志记录策略。
- logging_steps: 每隔多少步记录一次日志。
- per_device_eval_batch_size: 每个设备的评估批次大小。
- evaluation_strategy: 评估策略(例如,每隔多少步进行一次评估)。
- eval_steps: 每隔多少步进行一次评估。
- predict_with_generate: 是否使用生成模式进行预测。 + generation_config 部分
- max_new_tokens: 生成的最大新 token 数量。 + peft_config 部分
- peft_type: 使用的参数有效调整类型(如 LORA)。
- task_type: 任务类型,这里是因果语言模型(CAUSAL_LM)。 + Lora 参数:
- r: LoRA 的秩。
- lora_alpha: LoRA 的缩放因子。
- lora_dropout: 在 LoRA 层使用的 dropout 概率 + P-TuningV2 参数:
- num_virtual_tokens: 虚拟 token 的数量。
开始微调
通过以下代码执行 单机多卡/多机多卡 运行,这是使用 deepspeed 作为加速方案的,您需要安装 deepspeed。
cd finetune_demo
OMP_NUM_THREADS=1 torchrun --standalone --nnodes=1 --nproc_per_node=8 finetune_hf.py data/AdvertiseGen/ THUDM/chatglm3-6b configs/lora.yaml
通过以下代码执行 单机单卡 运行。
cd finetune_demo
python finetune_hf.py data/AdvertiseGen/ THUDM/chatglm3-6b configs/lora.yaml
从保存点进行微调
如果按照上述方式进行训练,每次微调都会从头开始,如果你想从训练一半的模型开始微调,你可以加入第四个参数,这个参数有两种传入方式:
yes, 自动从最后一个保存的 Checkpoint开始训练XX, 断点号数字 例600则从序号600 Checkpoint开始训练
例如,这就是一个从最后一个保存点继续微调的示例代码
cd finetune_demo
python finetune_hf.py data/AdvertiseGen/ THUDM/chatglm3-6b configs/lora.yaml yes
使用微调后的模型
在 inference_hf.py 中验证微调后的模型
您可以在 finetune_demo/inference_hf.py 中使用我们的微调后的模型,仅需要一行代码就能简单的进行测试。
python inference_hf.py your_finetune_path --prompt your prompt
这样,得到的回答就微调后的回答了。
在本仓库的其他 demo 或者外部仓库使用微调后的模型
您可以在任何一个 demo 内使用我们的 lora 和 全参微调的模型。这需要你自己按照以下教程进行修改代码。
- 使用
finetune_demo/inference_hf.py中读入模型的方式替换 demo 中读入模型的方式。
请注意,对于 LORA 和 P-TuningV2 我们没有合并训练后的模型,而是在
adapter_config.json中记录了微调型的路径,如果你的原始模型位置发生更改,则你应该修改adapter_config.json中base_model_name_or_path的路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def load_model_and_tokenizer(
model_dir: Union[str, Path], trust_remote_code: bool = True
) -> tuple[ModelType, TokenizerType]:
model_dir = _resolve_path(model_dir)
if (model_dir / 'adapter_config.json').exists():
model = AutoPeftModelForCausalLM.from_pretrained(
model_dir, trust_remote_code=trust_remote_code, device_map='auto'
)
tokenizer_dir = model.peft_config['default'].base_model_name_or_path
else:
model = AutoModelForCausalLM.from_pretrained(
model_dir, trust_remote_code=trust_remote_code, device_map='auto'
)
tokenizer_dir = model_dir
tokenizer = AutoTokenizer.from_pretrained(
tokenizer_dir, trust_remote_code=trust_remote_code
)
return model, tokenizer
- 读取微调的模型,请注意,你应该使用微调模型的位置,例如,若你的模型位置为
/path/to/finetune_adapter_model,原始模型地址为path/to/base_model,则你应该使用/path/to/finetune_adapter_model作为model_dir。 - 完成上述操作后,就能正常使用微调的模型了,其他的调用方式没有变化。
提示
- 微调代码在开始训练前,会先打印首条训练数据的预处理信息(默认已经注释,可以解除注释),显示为
Sanity
Check >> >> >> >> >> >> >
'[gMASK]': 64790 -> -100
'sop': 64792 -> -100
'<|system|>': 64794 -> -100
'': 30910 -> -100
'\n': 13 -> -100
'Answer': 20115 -> -100
'the': 267 -> -100
'following': 1762 -> -100
...
'know': 683 -> -100
'the': 267 -> -100
'response': 3010 -> -100
'details': 3296 -> -100
'.': 30930 -> -100
'<|assistant|>': 64796 -> -100
'': 30910 -> 30910
'\n': 13 -> 13
'I': 307 -> 307
'need': 720 -> 720
'to': 289 -> 289
'use': 792 -> 792
...
<< << << << << << < Sanity
Check
字样,每行依次表示一个 detokenized string, token_id 和 target_id。其中,target_id为token_id在模型词表中的索引,-100表示该
token 不参与 loss 计算。
_prepare_model_for_training的作用是遍历模型的所有可训练参数,并确保它们的数据类型为torch.float32。 这在某些情况下是必要的,因为混合精度训练或其他操作可能会更改模型参数的数据类型。该代码默打开,可以注释,但是如果使用half格式训练出现问题,可以切换回这个代码,显存可能增加。- 在我们的Huggingface模型代码中,有以下内容:
1 2 3 4 5 6 7 8 9 10
if self.gradient_checkpointing and self.training: layer_ret = torch.utils.checkpoint.checkpoint( layer, hidden_states, attention_mask, rotary_pos_emb, kv_caches[index], use_cache, use_reentrant=False )
这可能导致训练的时候显存增加,因此,如果您的显存不足,可以尝试将
use_reentrant修改为True。 - 微调后的模型可以使用任何支持
peft载入的模型加速框架,在这里,我们没有提供demo。 - 本仓库的微调数据集格式与 API 微调数据集格式有一定区别
- ZhipuAI API 微调数据集中的
messages字段在本仓库为conversation字段。 - ZhipuAI API 中的微调文件为
jsonl, 在本仓库,需要简单的将文件名改为json。
- ZhipuAI API 微调数据集中的
