Mistral-微调
mistral-finetune
是一个轻量级代码库,能够实现Mistral模型的内存高效和高性能微调。
它基于LoRA,这是一种训练范式,其中大部分权重被冻结,只有1-2%的额外权重以低秩矩阵扰动的形式被训练。
为了最大效率,建议使用A100或H100 GPU。该代码库针对多GPU单节点训练设置进行了优化, 但对于较小的模型,如7B,单个GPU就足够了。
注意
- 本仓库的目标是提供一个简单的、有指导的入口点来微调Mistral模型。 因此,它相当有主见(特别是在数据格式方面),并不旨在涵盖 多种模型架构或硬件类型。 对于更通用的方法,你可以查看一些其他优秀的项目,如 torchtune。
新闻
-
2024年8月13日: Mistral Large v2现在与
mistral-finetune
兼容!-
- 在这里下载123B Instruct,并将
model_id_or_path
设置为下载的检查点目录。
- 在这里下载123B Instruct,并将
-
- 由于模型规模更大,微调Mistral-Large v2需要显著更多的内存。目前,请将
seq_len
设置为<= 8192。
- 由于模型规模更大,微调Mistral-Large v2需要显著更多的内存。目前,请将
-
- 建议使用比其他模型更低的学习率,例如,lr=1e-6对大多数情况应该效果不错。
-
-
2024年7月19日: Mistral Nemo现在与
mistral-finetune
兼容!-
- 在这里下载12B Base或Instruct,并将
model_id_or_path
设置为下载的检查点目录。
- 在这里下载12B Base或Instruct,并将
-
- 运行
pip install --upgrade mistral-common
以获取支持Tekkenizer的版本(>=1.3.1
)。
- 运行
-
- 由于词汇表更大,导致CE损失的峰值内存需求激增,目前微调Mistral-Nemo需要更多内存(我们很快会在这里添加一个改进的CE损失)。目前,请将
seq_len
设置为<= 16384。
- 由于词汇表更大,导致CE损失的峰值内存需求激增,目前微调Mistral-Nemo需要更多内存(我们很快会在这里添加一个改进的CE损失)。目前,请将
-
- 建议使用与7B v3相同的超参数。
-
安装
要开始使用Mistral LoRA微调,请按以下步骤操作:
- 克隆此仓库:
cd $HOME && git clone https://github.com/mistralai/mistral-finetune.git
- 安装所有必需的依赖:
cd mistral-finetune
pip install -r requirements.txt
模型下载
我们建议微调以下官方Mistral模型之一,您可以在此处下载:
模型 | 链接 | 校验和 |
---|---|---|
7B Base V3 | 7B Base | 0663b293810d7571dad25dae2f2a5806 |
7B Instruct v3 | 7B Instruct v3 | 80b71fcb6416085bcb4efad86dfb4d52 |
8x7B Base V1 | 8x7B Base | (HF 链接) |
8x7B Instruct V1 | 8x7B Instruct | 8e2d3930145dc43d3084396f49d38a3f |
8x22 Instruct V3 | 8x22 Instruct | 471a02a6902706a2f1e44a693813855b |
8x22B Base V3 | 8x22B Base | a2fa75117174f87d1197e3a4eb50371a |
12B Instruct | 12B Instruct (Mistral-Nemo) | 296fbdf911cb88e6f0be74cd04827fe7 |
12B Base | 12 Base (Mistral-Nemo) | c5d079ac4b55fc1ae35f51f0a3c0eb83 |
Mistral Large 2 | 123B Instruct (Large v2) | fc602155f9e39151fba81fcaab2fa7c4 |
重要提示:对于8x7B Base V1和8x7B Instruct V1,在微调之前需要使用我们的v3分词器并将词汇表大小扩展到32768。有关此过程的详细说明,请参阅"模型扩展"部分。
例如,要下载7B-base模型,您可以运行以下命令:
mkdir -p ~/${HOME}/mistral_models
cd ${HOME} && wget https://models.mistralcdn.com/mistral-7b-v0-3/mistral-7B-v0.3.tar
tar -xf mistral-7B-v0.3.tar -C mistral_models
确保修改您的训练脚本,并将下载文件夹的路径添加为model_id_or_path
。
例如,修改example/7B.yaml以包含$HOME/mistral_models/7B
的绝对路径:
model_id_or_path: "/Users/johndoe/mistral_models/7B"
准备数据集
为确保有效训练,mistral-finetune
对训练数据的格式有严格要求。
所有数据文件必须以jsonl格式存储。
您可以构建两种类型的数据文件:
预训练:
预训练数据对应于存储在"text"
键中的纯文本数据。例如:
{"text": "文档1中包含的文本"}
{"text": "文档2中包含的文本"}
指令:
目前支持两种不同类型的指令跟随数据:
- 指令:以列表形式存储在
"messages"
键中的对话数据。每个列表项是一个包含"content"
和"role"
键的字典。"role"
是一个字符串,可以是"user"、"assistant"或"system"之一。只有当"role" == "assistant"时才会计算损失。例如:
{
"messages": [
{
"role": "user",
"content": "文档1中包含的用户交互1"
},
{
"role": "assistant",
"content": "文档1中包含的机器人交互1"
},
{
"role": "user",
"content": "文档1中包含的用户交互2"
},
{
"role": "assistant",
"content": "文档1中包含的机器人交互2"
}
]
}
{
"messages": [
{
"role": "user",
"content": "文档2中包含的用户交互1"
},
{
"role": "assistant",
"content": "文档2中包含的机器人交互1"
},
{
"role": "user",
"content": "文档2中包含的用户交互2"
},
{
"role": "assistant",
"content": "文档2中包含的机器人交互2",
"weight": 0, # 不对交互2进行训练
},
{
"role": "user",
"content": "文档2中包含的用户交互3"
},
{
"role": "assistant",
"content": "文档2中包含的机器人交互3"
}
]
}
- 函数调用:以列表形式存储在
"messages"
键中的对话数据。每个列表项是一个包含"role"
和"content"
或"tool_calls"
键的字典。"role"
是一个字符串,可以是"user"、"assistant"、"system"或"tool"之一。只有当"role" == "assistant"时才会计算损失。 注意:在函数调用中,"tool_calls"的"id"和"tool_call_id"是随机生成的恰好9个字符的字符串。我们建议在数据准备脚本中自动生成这些字符串,就像这里所做的那样。
例如:
{
"messages": [
{
"role": "system",
"content": "你是一个有权访问以下函数的有用助手,你可以在需要时使用这些函数来帮助用户"
},
{
"role": "user",
"content": "你能帮我生成"listen"这个词的字谜吗?"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "TX92Jm8Zi",
"type": "function",
"function": {
"name": "generate_anagram",
"arguments": "{\"word\": \"listen\"}"
}
}
]
},
{
"role": "tool",
"content": "{\"anagram\": \"silent\"}",
"tool_call_id": "TX92Jm8Zi"
},
{
"role": "assistant",
"content": ""listen"这个词的字谜是"silent"。"
},
{
"role": "user",
"content": "太神奇了!你能为"race"这个词生成一个字谜吗?"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "3XhQnxLsT",
"type": "function",
"function": {
"name": "generate_anagram",
"arguments": "{\"word\": \"race\"}"
}
}
]
}
],
"tools": [
{
"type": "function",
"function": {
"name": "generate_anagram",
"description": "生成给定单词的字谜",
"parameters": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "要生成字谜的单词"
}
},
"required": [
"word"
]
}
}
}
]
}
验证数据集
在开始训练之前,你应该验证你的数据集格式是否正确,并估算训练时间。你可以使用./utils/validate_data脚本来完成这个任务。
注意,这一步对于确保数据格式正确至关重要。
指令遵循
让我们通过一个简单的例子来训练一个指令遵循模型:
-
- 加载Ultachat_200k的一部分数据
创建数据文件夹并进入该文件夹。
cd $HOME && mkdir -p data && cd $HOME/data
将数据加载到Pandas DataFrame中。
注意:确保已安装pandas和pyarrow(pip install pandas pyarrow
)。
import pandas as pd
df = pd.read_parquet('https://huggingface.co/datasets/HuggingFaceH4/ultrachat_200k/resolve/main/data/test_gen-00000-of-00001-3d4cd8309148a71f.parquet')
-
- 分割为训练集和评估集
df_train=df.sample(frac=0.95,random_state=200)
df_eval=df.drop(df_train.index)
-
- 将数据保存为jsonl格式
df_train.to_json("ultrachat_chunk_train.jsonl", orient="records", lines=True)
df_eval.to_json("ultrachat_chunk_eval.jsonl", orient="records", lines=True)
-
- 修改你的训练yaml文件,包含ultrachat数据集,并验证yaml文件
修改example/7B.yaml,包含$HOME/data/ultrachat_chunk_train.jsonl
的绝对路径以及训练的数据集混合权重,并包含$HOME/data/ultrachat_chunk_eval.jsonl
用于评估,例如:
data:
instruct_data: "/Users/johndoe/data/ultrachat_chunk_train.jsonl"
eval_instruct_data: "/Users/johndoe/data/ultrachat_chunk_eval.jsonl"
现在你可以验证你的训练yaml文件,确保数据格式正确,并估算训练时间。
cd $HOME/mistral-finetune
python -m utils.validate_data --train_yaml example/7B.yaml
完成后,你应该会看到一份错误报告,其中包含许多如下的错误:
数据集/Users/johndoe/data/ultrachat_chunk_eval.jsonl的第1412行格式不正确。最后一个角色应该是:[assistant]中的一个,但得到的是user
数据集/Users/johndoe/data/ultrachat_chunk_eval.jsonl的第1413行格式不正确。最后一个角色应该是:[assistant]中的一个,但得到的是user
数据集/Users/johndoe/data/ultrachat_chunk_eval.jsonl的第1414行格式不正确。最后一个角色应该是:[assistant]中的一个,但得到的是user
数据集/Users/johndoe/data/ultrachat_chunk_eval.jsonl的第1415行格式不正确。最后一个角色应该是:[assistant]中的一个,但得到的是user
许多对话似乎以'user'角色结束,这是不必要的,因为我们只训练'assistant'消息,这样会不必要地处理数据。
你可以使用./utils/reformat_data.py来修正数据:
cd $HOME/mistral-finetune
python -m utils.reformat_data $HOME/data/ultrachat_chunk_train.jsonl
python -m utils.reformat_data $HOME/data/ultrachat_chunk_eval.jsonl
你应该会看到一些样本被跳过。
-
- 可能需要更改训练步骤数
在修正数据集后,再次运行脚本
cd $HOME/mistral-finetune
python -m utils.validate_data --train_yaml example/7B.yaml
你应该会得到数据输入和训练参数的摘要:
训练状态
--------------------
{
"expected": {
"eta": "00:52:44",
"data_tokens": 25169147,
"train_tokens": 131072000,
"epochs": "5.21",
"max_steps": 500,
"data_tokens_per_dataset": {
"/Users/johndoe/data/ultrachat_chunk_train.jsonl": "25169147.0"
},
"train_tokens_per_dataset": {
"/Users/johndoe/data/ultrachat_chunk_train.jsonl": "131072000.0"
},
"epochs_per_dataset": {
"/Users/johndoe/data/ultrachat_chunk_train.jsonl": "5.2"
}
},
}
将max_steps
设置为500会导致大约遍历数据集5次,这是合理的,但可能有点太多。下面显示了一个推荐的设置,在8xH100集群上只需30分钟。
函数调用
接下来,让我们看一个更高级的用例,即在函数调用上微调模型。 函数调用要求数据格式如上面所解释的。让我们看一个例子。
-
- 加载Glaive function calling dataset的聊天格式版本
创建数据文件夹并进入该文件夹。
cd $HOME && mkdir -p data && cd $HOME/data
将数据加载到Pandas DataFrame中。
注意:确保已安装pandas和pyarrow(pip install pandas pyarrow
)。
import pandas as pd
df = pd.read_parquet('https://huggingface.co/datasets/Locutusque/function-calling-chatml/resolve/main/data/train-00000-of-00001-f0b56c6983b4a78f.parquet')
-
- 分割为训练集和评估集
df_train=df.sample(frac=0.95,random_state=200)
df_eval=df.drop(df_train.index)
-
- 将数据保存为jsonl格式
df_train.to_json("glaive_train.jsonl", orient="records", lines=True)
df_eval.to_json("glaive_eval.jsonl", orient="records", lines=True)
-
- 重新格式化数据集
如我们所见,该数据集不符合所需的函数调用格式,因此需要重新格式化。除其他事项外,"from"应重命名为"user",多余的"\n"字符应被删除。
对于这个数据集,你可以使用./utils/reformat_data_glaive.py
:
cd $HOME/mistral-finetune
python -m utils.reformat_data_glaive $HOME/data/glaive_train.jsonl
python -m utils.reformat_data_glaive $HOME/data/glaive_eval.jsonl
运行这个命令将确保大多数样本符合正确的格式。
注意:不可能编写适用于所有类型数据集的重新格式化脚本。 如果你有尚未遵循上述所需格式的数据集,你很可能需要 自己创建一个重新格式化脚本(mistral-chat或chat-gpt是你最好的朋友!)。
-
- 验证数据集
现在你可以通过在example/7B.yaml
中将data.instruct_data
和data.eval_instruct_data
分别设置为
$HOME/data/glaive_train.jsonl
和$HOME/data/glaive_eval.jsonl
来验证数据集。
重新格式化的数据集仍然有一些错误,可以通过--create_corrected
来移除。为此,确保添加
--create_corrected
,如下所示:
cd $HOME/mistral-finetune
python -m utils.validate_data --train_yaml example/7B.yaml --create_corrected
运行此命令将显示几个错误并保存两个新数据集 $HOME/data/glaive_train.jsonl.corrected
和 $HOME/data/glaive_eval.jsonl.corrected
。确保在 example/7B.yaml
中使用这两个数据集并再次运行该命令。现在数据集应该格式正确了!
开始训练
在按照数据集验证部分操作后,我们现在可以开始训练了。
为了加快训练速度,我们建议将 max_steps 设置为仅 300。确保将 run_dir
定义为您的实验文件夹,并可选择设置 wandb_project
为 Weights & Biases 项目以进行日志记录,例如:
max_steps: 300
run_dir: "/Users/johndoe/ultra_chat_test"
wandb.project: ultra_chat
您也可以选择设置 wandb
保存训练配置并开始训练!确保将 --nproc-per-node
设置为可用的 GPU 数量。
cd $HOME/mistral-finetune
torchrun --nproc-per-node 8 --master_port $RANDOM -m train example/7B.yaml
在 8 个 H100 节点上训练 ultra-chat 应该需要大约 30 分钟,结果权重应该能得到约 6.3 的 MT Bench 分数。
在 8 个 H100 节点上训练 glaive 应该需要大约 1 小时,结果权重应该能很好地用于函数调用。
自定义训练配置
示例 mistral-finetune/examples/7B
定义了合理的学习率、权重衰减等参数...但建议您根据使用情况自定义这些设置。
通常,训练配置应填写以下参数:
model_id_or_path
定义开始训练的模型。这可以是预训练模型的路径或本地模型目录。run_dir
定义存储训练检查点和指标的目录。seq_len
定义训练的序列长度。这是模型将处理的输入序列的最大长度。样本会被打包以达到seq_len
长度,以获得最大的训练效率。batch_size
定义每个 GPU 使用的训练样本数。注意:所有 GPU 上的总有效批量大小(以 token 为单位)等于num_gpus
xbatch_size
xseq_len
。max_steps
定义最大训练步数。这是训练过程将运行的总迭代次数。可以根据您的具体训练情况进行调整。训练期间看到的总 token 数是max_steps
xnum_gpus
xbatch_size
xseq_len
。optim.lr
定义学习率。这是优化器的初始学习率。optim.weight_decay
定义权重衰减。权重衰减是一种正则化技术,通过惩罚大权重来防止过拟合。我们建议保持在 0.1。optim.pct_start
定义用于学习率预热阶段的总训练步数百分比,然后开始降低。它对应于 PyTorch 的 OneCycleLR 中的 pct_start。lora.rank
定义 LoRA(低秩适应)适配器的大小。我们建议 64 或更小,这调整了 LoRA 中使用的低秩分解的秩。seed
定义初始化和数据洗牌/采样的随机种子。设置种子确保结果的可重复性。log_freq
定义日志记录频率。这指定多少步记录一次训练指标。data.instruct_data
是用于训练的指令数据的路径。此字段必须按上述格式填写一个或多个数据源。每个数据源应该是一个 jsonl 文件的路径,或者是包含 jsonl 文件的目录路径,后面跟着一个权重来定义该数据集的重要性:<path/to/data_source>:<weight>
。例如:data.instruct_data: "/path/to/data1.jsonl:5.,/path/to/data2.jsonl:1.,/path/to/dir_of_jsonls:1."
data.data
是可选的额外预训练数据的路径,格式如上所述。注意,此字段可以留空。data.eval_instruct_data
是可选的评估指令数据的路径,用于在每eval_freq
步进行交叉验证。交叉验证指标显示为loss
和perplexity
。eval_freq
定义多少步评估一次模型。这指定在验证集上评估模型的间隔。no_eval
是启用或禁用中间评估的标志。将其设置为 False 可在训练期间进行定期评估。ckpt_freq
定义多少步保存一次检查点。这指定保存模型状态的间隔。save_adapters
定义是只保存训练好的 LoRA 检查点,还是将训练好的 LoRA 直接合并到基础模型中并保存。注意:当设置save_adapters=False
时,确保您有足够的 CPU 和 GPU 内存在单个进程上保存完整模型(这通常只适用于 7B 模型)。wandb.key
用于传递您的 Weights & Biases (wandb) API 密钥以进行日志记录。这允许您将训练指标记录到 wandb 仪表板。wandb.project
定义 wandb 项目名称。训练运行将在 wandb 界面中的此项目中记录。
推理
一旦您的模型训练完成,您应该尝试进行推理。我们推荐使用 mistral-inference。
确保正确安装了 mistral_inference
:
pip install mistral_inference
假设您的 lora.safetensors
保存在 $HOME/ultra_chat_test/checkpoints/checkpoint_000300/consolidated/lora.safetensors
下,您可以使用 mistral_inference
与模型聊天,例如:
mistral-chat /mnt/slow/runs/patrick/mistral-finetune/7B/ --max_tokens 256 --temperature 1.0 --instruct --lora_path $HOME/ultra_chat_test/checkpoints/checkpoint_000300/consolidated/lora.safetensors
添加 Weights and Biases (wandb) 支持
我们已添加了对 Weights and Biases 的明确支持,以帮助您监控和可视化您的训练运行。这种集成允许您轻松记录各种指标并跟踪实验。
设置 Weights and Biases
要在 mistral-finetune
中使用 Weights and Biases,请按照以下步骤操作:
-
安装 Weights and Biases:
确保您已安装
wandb
库。您可以使用 pip 安装:
pip install wandb
查看您的日志
一旦训练开始,您可以通过访问您的 wandb 项目仪表板实时监控进度。所有指标,包括训练损失、评估损失、学习率等,都将被记录和可视化。
有关如何使用 wandb 的更多详细信息,请访问 Weights and Biases 文档。
模型扩展
重要提示:请注意,只能微调与 v3 分词器兼容的 mistral 模型,这意味着模型的词汇表大小为 32768 - 而不是 32000。但是,可以使用以下方法轻松将词汇表大小为 32000 的旧版本扩展到 32768:
python -m utils.extend_model_vocab --original_model_ckpt /folder/to/old/model --extended_model_ckpt /folder/to/extended/model
扩展完成后,可以使用 /folder/to/extended/model
中新创建的模型检查点进行微调。
常见问题:
- 微调 MoE 的最佳实践是什么?
我们在微调 MoE 模型时观察到更高程度的性能变异。用不同的种子微调 MoE 模型导致性能高度变异并不罕见。我们没有在密集模型中观察到如此高的变异。因此,我们建议在 MoE 模型上运行多个相同的微调过程实例,并选择表现最好的一个。
- 我如何确定模型训练过程中使用的 token 数量?
您可以使用以下脚本来查找:https://github.com/mistralai/mistral-finetune/blob/main/utils/validate_data.py。这个脚本接受一个 .yaml 训练文件作为输入,并返回模型正在训练的 token 数量。
- 如果遇到 CUDA 内存不足错误,我该怎么办?
一个可能的解决方案是减少每个 GPU 的批量大小。批量大小等于 seq_len
x batch_size
。尝试将 batch_size
设置为 1 并减少 seq_len
。您可以在 .yaml 文件中定义 batch_size
和 seq_len
。