这篇文章中,我们来聊聊如何使用两张显卡来进行 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

多种方式愉快玩耍 LLaMA 大模型

关于模型文件的下载、完整性校验等问题,在第一篇文章中提到过就不再赘述了。此外,关于之前提到的官方推理方案和社区提供的 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 版本要大的多,此时我们有两个选择:

  1. 自行下载和转换 65B 模型文件格式。
  2. 直接下载社区中已经转换好格式的模型,比如: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