Training like a PRO
前面已经对简单的训练流程进行了介绍。通过前面的例子,相信大家已经对LLM的训练有了一些概念,下面会对已经入门的朋友们介绍一些高级的概念和用法,以及会遇到的一些问题。
数据篇
多轮数据
之前RoleBench中我们的组装方式都是按照单轮进行组装的(就是只有一问一答),而理想的情况下,我们希望模型可以应对用户在同一上下文中的多次问答,这就需要我们准备多轮对话数据。 (当然,因为我们之前是对Chat模型进行微调,因此他多少会有一些多轮能力,就是可能没那么好。) Anyway,还是来了解下多轮数据怎么处理。 假设有如下的一条多轮数据:
Human: 你吃了吗?
Assistant: 吃了
Human: 吃的啥
Assistant: 蛋炒饭
Human: 你猜我要说啥
Assistant: 蛋炒饭蛋炒饭,加点汤才好吃
那么最简单的方法是将最后一个回答的部分作为Response,而前面的部分作为Prompt。即给定
Human: 你吃了吗?
Assistant: 吃了
Human: 吃的啥
Assistant: 蛋炒饭
Human: 你猜我要说啥
Assistant:
让模型学习去吐出
蛋炒饭蛋炒饭,加点汤才好吃
但是这样会产生一个弊端,即前面的几轮对话都没有参与学习(前面介绍过,Finetuning不计算Prompt部分的Loss),所以为了高效的训练,我们会变成下面这种状态。 给定
Human: 你吃了吗?
Assistant:
Human: 吃的啥
Assistant:
Human: 你猜我要说啥
Assistant:
模型对每轮对话的输出同时进行学习
吃了
蛋炒饭
蛋炒饭蛋炒饭,加点汤才好吃
(从模型的角度,他并不知道这是一条多轮数据,对他来说这只是一个样本,我们事实上是通过Loss Mask来完成的上述的改动的)
从实现层面来说,由于前面我们使用的框架已经支持了多轮数据的训练,因此并不需要额外做什么操作,你只需要有这么一个多轮对话数据集就行了。有兴趣的话,可以在Minami-su/roleplay_multiturn_chat_1k_zh_v0.1这个多轮数据集上实验一下。
如何使用闭源大模型生成数据(Prompt工程)
不知道大家有没有玩过图像生成,那一段段魔咒的背后,其实就是怎么让模型生成我们想要的数据。下面给出一些Tips。
- 明确你的需求,并提炼出要点
- 使用陈述句,命令语气,不要使用谦词或者疑问句
- 可以给出一些例子(一般我们称为few shot)
- 使用代码来组织数据,比如JSON/Markdown
- 也可以试试使用英语Prompt,效果或许会好一些 也可以去看OpenAI自己的魔法书或者别人分享的资料。语言模型的Prompt还是相对比较简单的,不过具体任务还得自己摸索一下。
训练篇
显存不够/训练太慢怎么办?
前面一篇的训练命令中,我们一股脑的介绍了一大堆参数,这个相信大家一定是比较懵的,这些到底和显存/训练速度有什么关系。下面我们列一张表格,把那些和显存/训练速度有关的参数挑出来。
| Arguments | GPU Usage | Training Speed | Description |
|---|---|---|---|
| gradient_checkpointing | 👇👇👇 | 👇👇👇 | 梯度保存,一种节省显存的方法 |
| per_device_train_batch_size | 👆👆 | 👆👆 | 训练时每张显卡上的batch size,如果可以的话越大越快 |
| model_max_length | 👆 | 👇 | 模型的最大序列长度 |
| lora_rank | 👆 | 👇 | LoRA矩阵的维度 |
有了这张表,相信大家就比较清晰了。由于下面两个参数影响不大,我们一般优先调上面两个参数。首先,我们把gradient_checkpointing关闭,per_device_train_batch_size设置为1,看是否可以正常训练。不行的话,你就只能打开gradient_checkpointing进行训练了。再尝试将per_device_train_batch_size调为更大的值。注意调节per_device_train_batch_size时,需要同步对gradient_accumulation_steps进行调整,否则实际的batch size就变了,可能会影响训练,需要保证effecitve_batch_size = gradient_accumulation_steps * per_device_train_batch_size不变。如果还是训不起来,而且你的模型输入都比较短的话,也可以尝试缩小model_max_length到256,但是这能节省的显存有限。
数据Packing
当我们数据很多,而且都比较短的时候(显著低于model_max_length时),我们可以使用数据Packing来加速训练。只需要在命令中加入--packing True --pack_length 512(这里的512需要换成你实际使用的model_max_length)。需要注意的是,这个或许导致更大的显存占用,可以参考上一小节对进行参数调节。
P.S. 在开启Flash Attention(Linux/WSL应该都开启了)的情况下,如果我们使用了数据Packing,那么更大的model_max_length比更大的per_device_train_batch_size要更节省显存一些,所以可以先放大model_max_length,再缩小per_device_train_batch_size,保持gradient_accumulation_steps * per_device_train_batch_size * model_max_length大致相同即可。
在MacOS上,由于目前MLX中的Flash Attention优化一般,因此不会有很大的速度提升,但是可以保持相对恒定的显卡使用率,因此还是能比不做data packing要快一些。
QLoRA
在训更大的模型的时候,你会发现就算调节上面的参数也没法正常完成训练,显存仍然不够。好在我们可以进行量化模型训练。什么是量化?即使用q = f / s这种方式来做线性缩放,其中常量s可以是对于f部分共享的,这样可以让一个浮点值可以用一个更小的整数值来表示。一个FP32浮点值占4个字节,一个BF16/FP16浮点值占2个字节,而量化后整数值可以只占4-8位,这样可以大幅的降低显存占用。
可以在命令中加入--q_lora True来启用QLoRA。
Loss曲线观测
作为Pro的LLM训练大师,怎么能不绘制Loss曲线呢?我们有两个后端可以对训练过程的变化进行记录
- Wandb (可以在外部访问)
- Tensorboard(局域网访问)
只需要将训练参数中的--report_to none中的none改为wandb或者tensorboard。对于wandb还需要申请账号以及在环境变量中添加Wandb的API key。具体参见https://wandb.ai/
而tensorboard则需要先通过pip install tensorboard进行安装,然后用命令tensorboard --logdir=<output_dir>来启动网页后端。
可以发现LLM训练的Loss曲线一般在训练初期快速下降,到达一个点之后开始震荡,同时缓慢下降。
加入验证集
为了更好的对模型的训练效果进行观测,我们需要加入一个验证集,可以通过传入如下参数来开启模型验证。
--eval_data_path <path to eval dataset> \
--do_eval \
--per_device_eval_batch_size 1
P.S. eval_data的数据格式和train_data是一样的。