这篇文章中,我们来聊聊如何使用两张显卡来进行 LLaMA 65B 大模型的微调工作,以及如何在一张普通的 4090 家用显卡上,只花几个小时,就能够完成 7B 模型的微调。
写在前面
在之前的几篇文章里,我们介绍过三种方式运行 Meta 开源模型 LLaMA 的 7B、13B 版本:
不过,在之前的尝试中我们不难发现,如果没有我们“限定的数据”,模型效果其实不是特别好,尤其是相对小参数量的 7B 模型。同时,这也让我们对 65B 的模型更加充满了兴趣。
当然,想要在极少量资源的显卡上完成模型能力的“完善”(训练、微调),在之前是具有非常大的难度的。不过,随着几个项目的诞生,这件事变的容易了许多:
首先,两个星期前来自斯坦福几位聪明的同学,带来了他们的“斯坦福羊驼”项目:tatsu-lab/stanford_alpaca,通过使用 OpenAI 的 API,从 ChatGPT 获取了5万2千条数据,然后借助了一台搭载 4 颗 80G 显存的 A100 GPU 服务器完成了 7B 的 LLaMA 模型的微调,带来了令人惊艳的效果,达到类似 text-davinci-003
的评估结果,验证了:小样本情况下,单机服务器是能够完成语言大模型的微调的,并且能够取得不错的效果,极大的振奋了社区。
接着,社区中站出来了另外一位同学 tloen,使用 LoRA(Low-Rank)的方式,完成了一件更加令人振奋的事情:将斯坦福同学微调模型使用的算力,从 4 张 80G 显存的 A100 降低到了一块 4090 显卡,并且能够在 5 个小时内完成微调工作。甚至能够将大模型运行在一块树莓派上!
当然,这件事能够成立,除了脑洞大开勇于尝试的 tloen 同学,Hugging Face 社区开源的 PEFT 项目,和 TimDettmers/bitsandbytes CUDA 8-bit 模型量化项目也功不可没。当然,社区项目目前也还存在一些问题,比如不支持多卡运行,不支持比较新的 CUDA 环境运行等等。
好了,到这里为止,你已经了解了到底是哪些“开源社区的功臣”为我们带来的福利。
现在,让我们开始从 7B 模型微调工作搞起,在掌握 7B 之后,我们就能够驾轻就熟的折腾最大号的 65B 模型啦。
为了方便使用和验证效果,我本文中使用的方案也更新到了之前提到的 “LLaMA 游乐场”开源项目中。项目地址:soulteary/llama-docker-playground
关于模型文件的下载、完整性校验等问题,在第一篇文章中提到过就不再赘述了。此外,关于之前提到的官方推理方案和社区提供的 Pyllama 推理方案的使用方式也不再展开,感兴趣可以自行翻阅之前的另一篇文章。
使用 LLaMA Docker 游乐场项目
仍然是随便找一个合适的目录,使用 git clone
或者下载 Zip 压缩包的方式,先把“LLaMA 游乐场”项目的代码下载到本地。
git clone https://github.com/soulteary/llama-docker-playground.git
# or
curl -sL -o llama.zip https://github.com/soulteary/llama-docker-playground/archive/refs/heads/main.zip
接着,进入项目目录,使用 Nvidia 原厂的 PyTorch Docker 基础镜像来完成基础环境的构建,相比于我们直接从 DockerHub 拉制作好的镜像,自行构建将能节约大量时间。
我们在项目目录中执行下面的命令,就能够构建出能够用于大模型 fine-tune 的 Docker 环境啦:
docker build -t soulteary/llama:alpaca-lora-finetune . -f docker/Dockerfile.lora-finetune
稍等片刻,镜像构建完毕之后,就能够开始玩了。
对 LLaMA 7B 大模型进行 fine-tune
想要对 LLaMA 进行单卡的模型微调,一共分为四步。
准备模型文件
为了方便 fine-tune,确认你的模型目录和下面保持一致:
├── 7B
│ ├── checklist.chk
│ ├── consolidated.00.pth
│ └── params.json
├── tokenizer.model
└── tokenizer_checklist.chk
准备容器环境
在上篇文章《基于 Docker 的深度学习环境:入门篇》中,我们提到过如何配置 Docker 来和显卡交互,这里就不过多赘述了。你可以执行简单的一条命令,来创建一个“干净又卫生”的用于大模型微调的容器环境:
docker run --gpus all --ipc=host --ulimit memlock=-1 --ulimit stack=67108864 \
--rm -it \
-v /home/soulteary/project/llama-docker-playground/models:/app/alpaca-lora/original-weights \
-v `pwd`/weights:/app/alpaca-lora/weights \
soulteary/llama:alpaca-lora-finetune bash
在上面的命令中,我们将原始模型文件挂载到了容器的 /app/alpaca-lora/original-weights
目录,一会使用。并将项目当前目录的 weights
文件夹挂载到了容器中的 /app/alpaca-lora/weights
,用于保存后续要使用的 HF 模型格式。
转换模型格式
接着,在容器中执行下面的命令,就能够将 Meta 7B 的 LLaMA 模型,转换为我们需要的格式了:
python -m transformers.models.llama.convert_llama_weights_to_hf \
--input_dir original-weights \
--model_size 7B \
--output_dir weights
转换时间不会很长(我这里是 6 秒钟),稍等片刻即可:
# python -m transformers.models.llama.convert_llama_weights_to_hf \
# > --input_dir original-weights \
# > --model_size 7B \
# > --output_dir weights
Fetching all parameters from the checkpoint at original-weights/7B.
Loading the checkpoint in a Llama model.
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 33/33 [00:06<00:00, 5.40it/s]
Saving in the Transformers format.
Fetching the tokenizer from original-weights/tokenizer.model.
然后查看 weights
目录,能够看到新的模型文件已经就绪:
# du -hs weights/*
4.0K weights/config.json
4.0K weights/generation_config.json
9.3G weights/pytorch_model-00001-of-00002.bin
3.3G weights/pytorch_model-00002-of-00002.bin
28K weights/pytorch_model.bin.index.json
4.0K weights/special_tokens_map.json
492K weights/tokenizer.model
4.0K weights/tokenizer_config.json
运行模型微调程序
然后,执行用于模型微调的 finetune.py
程序即可:
python finetune.py
命令执行成功后,你将会看到类似下面的日志输出:
# python finetune.py
===================================BUG REPORT===================================
Welcome to bitsandbytes. For bug reports, please submit your error trace to: https://github.com/TimDettmers/bitsandbytes/issues
================================================================================
CUDA_SETUP: WARNING! libcudart.so not found in any environmental path. Searching /usr/local/cuda/lib64...
CUDA SETUP: CUDA runtime path found: /usr/local/cuda/lib64/libcudart.so
CUDA SETUP: Highest compute capability among GPUs detected: 8.9
CUDA SETUP: Detected CUDA version 118
CUDA SETUP: Loading binary /usr/local/lib/python3.8/dist-packages/bitsandbytes/libbitsandbytes_cuda118.so...
Overriding torch_dtype=None with `torch_dtype=torch.float16` due to requirements of `bitsandbytes` to enable model loading in mixed int8. Either pass torch_dtype=torch.float16 or don't pass this argument at all to remove this warning.
Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:06<00:00, 3.03s/it]
Downloading and preparing dataset json/default to /root/.cache/huggingface/datasets/json/default-8d30498d25a7aa2b/0.0.0/0f7e3662623656454fcd2b650f34e886a7db4b9104504885bd462096cc7a9f51...
Downloading data files: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 15709.00it/s]
Extracting data files: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 2291.97it/s]
Dataset json downloaded and prepared to /root/.cache/huggingface/datasets/json/default-8d30498d25a7aa2b/0.0.0/0f7e3662623656454fcd2b650f34e886a7db4b9104504885bd462096cc7a9f51. Subsequent calls will reuse this data.
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 101.94it/s]
这将是一个漫长的过程,大概是三部“柯南”的片长,耐心等待就是啦~
模型微调过程中,我们使用 nvidia-smi
检查显卡状态,可以看到显存其实只使用了 8G 出头。
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.85.05 Driver Version: 525.85.05 CUDA Version: 12.0 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |
| 31% 53C P2 336W / 450W | 8563MiB / 24564MiB | 90% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| 0 N/A N/A 1290 G /usr/lib/xorg/Xorg 9MiB |
| 0 N/A N/A 1510 G /usr/bin/gnome-shell 10MiB |
| 0 N/A N/A 24135 C python 8538MiB |
+-----------------------------------------------------------------------------+
好啦,最基础的 fine-tune 我们就掌握完毕了,下面来看看如何使用多张显卡进行 大模型的 fine-tune,以及对 65B 的 LLaMA 大模型进行微调。
对 LLaMA 65B 大模型进行 fine-tune
想要 fine-tune 65B 的模型,一样需要四个步骤。
准备模型文件
如果你依旧希望训练的是 7B 的模型,但是想通过多张卡来提升效率,使用前文中的模型目录即可。如果你希望训练的是最大号的 65B 模型,因为模型相比 7B 版本要大的多,此时我们有两个选择:
- 自行下载和转换 65B 模型文件格式。
- 直接下载社区中已经转换好格式的模型,比如:decapoda-research/llama-65b-hf。
如果你选择和上文一样,可以将下载好的原版模型文件放置在合适的位置。
# ls llama/*
llama/download.sh llama/tokenizer.model llama/tokenizer_checklist.chk
llama/65B:
checklist.chk consolidated.00.pth consolidated.01.pth consolidated.02.pth consolidated.03.pth consolidated.04.pth consolidated.05.pth consolidated.06.pth consolidated.07.pth params.json
如果你选择从 HF 社区里直接下载已经转换好格式的模型,可以使用下面的方法:先访问 git-lfs 项目,根据你的操作系统完成工具的安装;然后使用 git
命令完成模型文件的下载:
git clone https://huggingface.co/decapoda-research/llama-65b-hf
准备容器环境
如果我们想要使用多张显卡,我们需要执行下面的命令,构建一个新的容器环境:
docker build -t soulteary/llama:alpaca-lora-65b-finetune . -f docker/Dockerfile.lora-65b-finetune
接着,我们执行下面的命令,就能够进入启用多卡微调训练的环境容器啦:
docker run --gpus all --ipc=host --ulimit memlock=-1 --ulimit stack=67108864 \
--rm -it \
-v /home/soulteary/project/llama-docker-playground/models:/app/alpaca-lora/original-weights \
-v `pwd`/weights:/app/alpaca-lora/weights \
soulteary/llama:alpaca-lora-65b-finetune bash
转换模型格式
如果你是选择从 HF 社区下载模型,那么可以跳过阅读本小节。
如果你选择自行转换模型,在进入容器之后,我们执行下面的命令,就能够进行模型的转换了:
python -m transformers.models.llama.convert_llama_weights_to_hf \
--input_dir original-weights \
--model_size 65B \
--output_dir weights
因为模型尺寸巨大,所以转换格式时间非常久,我这里接近 1 个小时,才完成了转换:
# python -m transformers.models.llama.convert_llama_weights_to_hf \
#> --input_dir original-weights \
#> --model_size 65B \
#> --output_dir weights
Fetching all parameters from the checkpoint at original-weights/65B.
Loading the checkpoint in a Llama model.
Loading checkpoint shards: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 81/81 [00:57<00:00, 1.40it/s]
Saving in the Transformers format.
Fetching the tokenizer from original-weights/tokenizer.model.
检查目录,能够看到我们得到了 14 个几乎大小一致的模型文件:
du -hs weights/
123G weights/
du -hs weights/*
48K weights/config.json
48K weights/generation_config.json
11G weights/pytorch_model-00001-of-00014.bin
11G weights/pytorch_model-00002-of-00014.bin
11G weights/pytorch_model-00003-of-00014.bin
11G weights/pytorch_model-00004-of-00014.bin
11G weights/pytorch_model-00005-of-00014.bin
11G weights/pytorch_model-00006-of-00014.bin
11G weights/pytorch_model-00007-of-00014.bin
11G weights/pytorch_model-00008-of-00014.bin
11G weights/pytorch_model-00009-of-00014.bin
11G weights/pytorch_model-00010-of-00014.bin
11G weights/pytorch_model-00011-of-00014.bin
11G weights/pytorch_model-00012-of-00014.bin
运行模型微调程序
和上文一样,如果你是 A100 用户,并且有至少两张卡,那么可以直接运行下面的程序,开启你的 65B 模型微调之旅:
python finetune.py
默认参数的执行时间需要 44 小时,但如果我们将 MICRO_BATCH_SIZE
提升一倍,改为 8
,那么所需要的时间就能够缩减到 33 个小时啦,不过参数这个东西嘛,需要实际测试验证,根据自己的情况来。
# 调整参数
[01:42<33:11:29, 102.22s/it]
# 默认参数
[04:33<44:16:54, 136.49s/it]
好啦,到这里为止,我们就聊完了如何轻松愉快的使用极地的成本 fine-tune 7B 和 65B 的大模型,以及选择性的使用单张显卡或者多张显卡。
其他
接下来,我将聊聊这次旅途中的一些细节。
Nvidia 基础镜像的选择
在本文中,我们没有和上一篇文章《基于 Docker 的深度学习环境:入门篇》一样,选择使用最新的 CUDA & PyTorch 镜像,而是选择使用了 FROM nvcr.io/nvidia/pytorch:22.12-py3
,完整 Dockerfile 如下:
FROM nvcr.io/nvidia/pytorch:22.12-py3
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
WORKDIR /app
RUN git clone https://github.com/tloen/alpaca-lora.git
WORKDIR /app/alpaca-lora
RUN pip install datasets loralib sentencepiece git+https://github.com/huggingface/transformers.git bitsandbytes git+https://github.com/huggingface/peft.git
之所以这样选择,是因为项目中的核心依赖 bitsandbytes
,目前对于 CUDA 12
不能够正确的识别,会导致程序无法启动,虽然可以通过给它打临时补丁将程序跑起来,但是我看到项目最近还有更新,社区里相关的反馈其实不少。或许用被长期使用验证过的环境更靠谱一些。
在 Nvidia 社区的 PyTorch 镜像发布记录中,我们能够找到满足软件正常运行情况下,最新的镜像版本就是它啦,包含 CUDA(11.8.0)和 PyTorch(1.14.0a0+410ce96)以及 TensorRT(8.5.1)。
为什么原版的 Alpaca LoRA 不能多卡运行
这其实应该算是 transformers
组件的问题,这个问题的最早的发现者是一位社区的同学 kooshi,除了通过激活 model.parallel
参数来启用并行化之外,他还发现了 HF 社区的 transformers
在运行 LoRA 的时候,并不会使用所有显卡的问题。目前,他已经针对性的提交了第一个补丁,以及在尝试彻底解决 LoRA 多卡运行的问题。
如果你有八张卡,只想使用其中的某两张
这里有一个有趣的问题,主要的原因也是因为 transformers
,如果你有多张卡,当你指定 device_map
的时候,如果它不为 auto
,但是你的可用的显卡又不是从序号 0 开始的,那么将会出现各种错误。
比较简单的,不需要修改代码的解决方案是,使用 docker --gpus
参数,来屏蔽掉不需要展示给应用的显卡资源,比如我需要跳过前四张卡:
docker run -it --rm --gpus '"device=4,5,6,7"' ubuntu nvidia-smi
那么,当你执行命令之后,你会发现之前被占用的卡就“消失”了,程序会将你指定的卡重新编号为 “cuda0~cuda3”,一切问题迎刃而解。
最后
关于使用低成本的显卡资源来 fine-tune 模型,我们就聊到这里。
下一篇相关的文章里,我们聊聊其他几种不同的模型运行方式,以及找机会聊聊其他的模型。当然,如果模型 fine-tune 一切顺利,我将持续更新“贾维斯”的养成笔记。
–EOF