使用双卡/8卡3090微调llama2-70B/13B模型

发布时间 2023-12-10 14:31:44作者: AlphaInf

写在前面

本篇博文将会教大家如何在消费级的设备(或者各种超级便宜的洋垃圾上)实现13B/70B等无法在单张消费级显卡上加载(但可以在一台机器上的多张卡上加载)的模型的微调。

由于绝大部分做实验,仅要求实现推理,或者在微调时没有资源上到全量/13B+级别的真·大模型的微调,没有涉及到将一个模型放在多张卡上的训练,这方面可供参考的材料极少(甚至英文的都极少),下面推荐几个可以查问题的参考(但也没有直接提供今天要的东西):

1. HuggingFace原版的PEFT教程,目前绝大部分的微调都基于PEFT上进行二次开发而得来,但这些二次开发的仓库不会告诉你很多参数为何要设置,这些要设置的参数大部分源自PEFT库中。

2. LLaMA-Factory仓库,这是对PEFT仓库的二次开发,可以很方便地实现预训练,各种PEFT微调和模型推理测试,支持LLaMA,ChatGLM等模型(特别是针对这些模型制作了开头和结尾等控制信息)。但该仓库并不直接支持将一个模型放在多个GPU上进行微调

3. LLaMA-Factory仓库的Issue列表,截止目前(2023年12月10日),里面共有1697个issue,出锅了把出锅信息复制过来看,多半能找到解答。(比直接Google要好)

 

常见误区

关于Deepspeed实现多卡推理

网上的多个教程称你可以用Deepspeed框架来实现多卡的训练。我也照着复现,但是发现运行速度极慢,且非常容易爆内存(测试设备有507GB内存)。

经过分析,他们的设置,实际上仅实现了Data Parallel的运行,至于为啥能够Data Parallel实现70B模型在24G显存显卡上的运行,那是因为开了ZeRO3实现了将内存虚拟化为显存实现的,8卡3090实际上每个卡上跑了单独的一个70B模型,频繁换入换出显存不慢就怪了233333。

个人认为,港科广的测试模型微调速度的论文这里面就出现了这一重大失误,在Data Parallel的框架下微调70B级别的模型,导致3090对A100仅有2%的性能,如果测试时采用了Tensor/Pipeline Parallel,那么性能相差将不会这么悬殊(当然也要感谢这一篇文章,让我意识到了这个问题)

笔者在正式部署的时候,并没有采用deepspeed,而是直接基于最基础的PEFT框架实现。

 

一些结论

1. 笔者实现了在两张P40显卡上基于LoRA在FP16精度(无量化)下微调LLaMA2-13B模型。这是一个成本仅需3000元的硬件平台。(缺点:在Ampere架构以下的卡,不支持BF16,可能会炸精度)

2. 笔者实现了在8张3090显卡上,基于LoRA在FP16精度(无量化)下微调LLaMA2-70B模型(根据评估,应该还可以降低到6张卡的水平)

3. 目前暂时解决了使用Deepspeed会爆显存的问题,采用256GB内存的设备足够应付LLaMA2-70B模型的微调。

4. 目前尚未解决Pipeline Parallel导致的同时只有一个GPU在运行的效率低问题,考虑后续改为Bubble。

 

运行环境

硬件

GPU:8卡3090

CPU:AMD EPYC 7302 64核

RAM:507GB

基础软件

下方暂时仅列举最关键的几个基础软件,对于一般的包详见下方

OS:Ubuntu 20.04.5 LTS

Python:3.8.10

CUDA:release 11.8, V11.8.89

Python CUDA(这是Python中安装的CUDA相关库):2.0.0+cu118

这里面注意Python CUDA中的版本和CUDA之间的关系,如果不匹配可能会出锅。

Python包版本

本人基于经过细微修改的LLaMA-Factory执行多卡微调任务,故包版本与该仓库的requirements.txt一致

torch>=1.13.1
transformers>=4.31.0,<4.35.0
datasets>=2.14.3
accelerate>=0.21.0
peft==0.6.0
trl>=0.7.4
gradio>=3.38.0,<4.0.0
scipy
sentencepiece
protobuf
tiktoken
jieba
rouge-chinese
nltk
uvicorn
pydantic
fastapi
sse-starlette
matplotlib

 注意下包版本的问题,比如PEFT的最新版(0.7.0)就有bug,会出现下面的报错

ValueError: Attempting to unscale FP16 gradients.

 

微调环境适配

本人微调的是TigerBot 13B/70B系列模型,采用LLaMA2架构,但是Chat模式下的开头与一般的LLaMA不一样,所以首先要修改template.py(在src/llmtuner/data中),增加适配TigerBot格式的template

 1 register_template(
 2     name="tigerbot",
 3     prefix=[
 4         ""
 5     ],
 6     prompt=[
 7         "\n\n### Instruction:\n {{query}} \n\n### Response:\n"
 8     ],
 9     system="",
10     sep=[]
11 )

 

为了支持将一个模型平均塞进多个GPU的显存中,我们需要修改模型读取的部分

在src/llmtuner/model/loader.py中,在约第180行处,修改以下的位置

1     model = AutoModelForCausalLM.from_pretrained(
2         model_to_load,
3         config=config,
4         device_map = 'auto',
5         torch_dtype=model_args.compute_dtype,
6         low_cpu_mem_usage=(not is_deepspeed_zero3_enabled()),
7         **config_kwargs
8     )

 

增加上面这一行红色的代码,这是本博文最关键的一个步骤!!!!!!

实际上,在transformers中,无论读取模型到推理环节,还是读取模型到训练环境,都是采用.from_pretrained来实现的,训练时设置该部分,则可以让一个模型被平均在2张/多张卡上。

(不过,由于该仓库的特性,启用该仓库执行推理时记得注释掉这一行)

 

运行微调

这里执行微调时,采用的命令与单卡微调一致,不要用deepspeed!!!

 1 python src/train_bash.py \
 2     --stage sft \
 3     --model_name_or_path /hy-tmp/tigerbot-70b-chat-v4-4k \
 4     --do_train True \
 5     --finetuning_type lora \
 6     --template tigerbot \
 7     --dataset_dir data \
 8     --dataset self_cognition_golden \
 9     --cutoff_len 1024 \
10     --learning_rate 1e-4 \
11     --num_train_epochs 8.0 \
12     --per_device_train_batch_size 8 \
13     --gradient_accumulation_steps 1 \
14     --lr_scheduler_type cosine \
15     --logging_steps 1 \
16     --save_steps 100 \
17     --lora_rank 256 \
18     --lora_dropout 0.1 \
19     --lora_target q_proj,v_proj \
20     --output_dir saves \
21     --fp16 True \
22     --plot_loss True \
23     --overwrite_output_dir

与LLaMA-Factory官方微调脚本中不一样地方主要有下面几个:

1. template这里改为了刚刚设置的TigerBot格式

2. 在一些老卡上(比如超级便宜的P40显卡),他们是Ampere架构前的显卡,不支持bf16,所以不要开这个。

3. 虽然是多卡微调,但是在这个运行脚本中看不到对应的设置。

然后就可以微调了