本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2023年03月25日 统计字数: 12339字 阅读时间: 25分钟阅读 本文链接: https://soulteary.com/2023/03/25/model-finetuning-on-llama-65b-large-model-using-docker-and-alpaca-lora.html ----- # 使用 Docker 和 Alpaca LoRA 对 LLaMA 65B 大模型进行 Fine-Tune 这篇文章中,我们来聊聊如何使用两张显卡来进行 LLaMA 65B 大模型的微调工作,以及如何在一张普通的 4090 家用显卡上,只花几个小时,就能够完成 7B 模型的微调。 ## 写在前面 在之前的几篇文章里,我们介绍过三种方式运行 Meta 开源模型 LLaMA 的 7B、13B 版本: - [《模型杂谈:使用 IN8 量化推理运行 Meta “开源泄露”的大模型(LLaMA)》](https://soulteary.com/2023/03/13/quick-start-llama-model-created-by-meta-research-with-int8.html) - [《模型杂谈:快速上手元宇宙大厂 Meta “开源泄露”的大模型(LLaMA)》](https://soulteary.com/2023/03/09/quick-start-llama-model-created-by-meta-research.html) 不过,在之前的尝试中我们不难发现,如果没有我们“限定的数据”,模型效果其实不是特别好,尤其是相对小参数量的 7B 模型。同时,这也让我们对 65B 的模型更加充满了兴趣。 当然,想要在极少量资源的显卡上完成模型能力的“完善”(训练、微调),在之前是具有非常大的难度的。不过,随着几个项目的诞生,这件事变的容易了许多: 首先,两个星期前来自斯坦福几位聪明的同学,带来了他们的“斯坦福羊驼”项目:[tatsu-lab/stanford\_alpaca](https://github.com/tatsu-lab/stanford_alpaca),通过使用 OpenAI 的 API,从 ChatGPT 获取了**5万2千条数据**,然后借助了**一台搭载 4 颗 80G 显存**的 A100 GPU 服务器完成了 7B 的 LLaMA 模型的微调,带来了令人惊艳的效果,达到类似 `text-davinci-003` 的评估结果,验证了:**小样本情况下,单机服务器是能够完成语言大模型的微调的,并且能够取得不错的效果,极大的振奋了社区**。 接着,社区中站出来了另外一位同学 [tloen](https://github.com/tloen/alpaca-lora),使用 LoRA(Low-Rank)的方式,完成了一件更加令人振奋的事情:将斯坦福同学微调模型使用的算力,从 4 张 80G 显存的 A100 **降低到了一块 4090 显卡,并且能够在 5 个小时内完成微调工作**。甚至能够**将大模型运行在一块树莓派上!** 当然,这件事能够成立,除了脑洞大开勇于尝试的 tloen 同学,Hugging Face 社区开源的 [PEFT](https://github.com/huggingface/peft) 项目,和 [TimDettmers/bitsandbytes](https://github.com/TimDettmers/bitsandbytes) CUDA 8-bit 模型量化项目也功不可没。当然,社区项目目前也还存在一些问题,比如不支持多卡运行,不支持比较新的 CUDA 环境运行等等。 好了,到这里为止,你已经了解了到底是哪些“开源社区的功臣”为我们带来的福利。 现在,让我们开始从 7B 模型微调工作搞起,在掌握 7B 之后,我们就能够驾轻就熟的折腾最大号的 65B 模型啦。 为了方便使用和验证效果,我本文中使用的方案也更新到了之前提到的 “LLaMA 游乐场”开源项目中。项目地址:[soulteary/llama-docker-playground](https://github.com/soulteary/llama-docker-playground) ![多种方式愉快玩耍 LLaMA 大模型](https://attachment.soulteary.com/2023/03/13/llama-playground.jpg) 关于模型文件的下载、完整性校验等问题,在[第一篇文章中](https://soulteary.com/2023/03/09/quick-start-llama-model-created-by-meta-research.html)提到过就不再赘述了。此外,关于之前提到的官方推理方案和社区提供的 Pyllama 推理方案的使用方式也不再展开,感兴趣可以自行翻阅[之前的另一篇文章](https://soulteary.com/2023/03/13/quick-start-llama-model-created-by-meta-research-with-int8.html)。 ## 使用 LLaMA Docker 游乐场项目 仍然是随便找一个合适的目录,使用 `git clone` 或者下载 Zip 压缩包的方式,先把“LLaMA 游乐场”项目的代码下载到本地。 ```bash 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 环境啦: ```bash docker build -t soulteary/llama:alpaca-lora-finetune . -f docker/Dockerfile.lora-finetune ``` 稍等片刻,镜像构建完毕之后,就能够开始玩了。 ## 对 LLaMA 7B 大模型进行 fine-tune 想要对 LLaMA 进行单卡的模型微调,一共分为四步。 ### 准备模型文件 为了方便 fine-tune,确认你的模型目录和下面保持一致: ```bash ├── 7B │   ├── checklist.chk │   ├── consolidated.00.pth │   └── params.json ├── tokenizer.model └── tokenizer_checklist.chk ``` ### 准备容器环境 在上篇文章[《基于 Docker 的深度学习环境:入门篇》](https://soulteary.com/2023/03/22/docker-based-deep-learning-environment-getting-started.html)中,我们提到过如何配置 Docker 来和显卡交互,这里就不过多赘述了。你可以执行简单的一条命令,来创建一个“干净又卫生”的用于大模型微调的容器环境: ```bash 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 模型,转换为我们需要的格式了: ```bash python -m transformers.models.llama.convert_llama_weights_to_hf \ --input_dir original-weights \ --model_size 7B \ --output_dir weights ``` 转换时间不会很长(我这里是 6 秒钟),稍等片刻即可: ```bash # 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` 目录,能够看到新的模型文件已经就绪: ```bash # 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` 程序即可: ```bash python finetune.py ``` 命令执行成功后,你将会看到类似下面的日志输出: ```bash # 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 出头。 ```bash +-----------------------------------------------------------------------------+ | 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](https://huggingface.co/decapoda-research/llama-65b-hf)。 如果你选择和上文一样,可以将下载好的原版模型文件放置在合适的位置。 ```bash # 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](https://github.com/git-lfs/git-lfs/blob/main/INSTALLING.md) 项目,根据你的操作系统完成工具的安装;然后使用 `git` 命令完成模型文件的下载: ```bash git clone https://huggingface.co/decapoda-research/llama-65b-hf ``` ### 准备容器环境 如果我们想要使用多张显卡,我们需要执行下面的命令,构建一个新的容器环境: ```bash docker build -t soulteary/llama:alpaca-lora-65b-finetune . -f docker/Dockerfile.lora-65b-finetune ``` 接着,我们执行下面的命令,就能够进入启用多卡微调训练的环境容器啦: ```bash 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 社区下载模型,那么可以跳过阅读本小节。 如果你选择自行转换模型,在进入容器之后,我们执行下面的命令,就能够进行模型的转换了: ```bash python -m transformers.models.llama.convert_llama_weights_to_hf \ --input_dir original-weights \ --model_size 65B \ --output_dir weights ``` 因为模型尺寸巨大,所以转换格式时间非常久,我这里接近 1 个小时,才完成了转换: ```bash # 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 个几乎大小一致的模型文件: ```bash 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 模型微调之旅: ```bash python finetune.py ``` 默认参数的执行时间需要 44 小时,但如果我们将 `MICRO_BATCH_SIZE` 提升一倍,改为 `8`,那么所需要的时间就能够缩减到 33 个小时啦,不过参数这个东西嘛,需要实际测试验证,根据自己的情况来。 ```bash # 调整参数 [01:42<33:11:29, 102.22s/it] # 默认参数 [04:33<44:16:54, 136.49s/it] ``` 好啦,到这里为止,我们就聊完了如何轻松愉快的使用极地的成本 fine-tune 7B 和 65B 的大模型,以及选择性的使用单张显卡或者多张显卡。 ## 其他 接下来,我将聊聊这次旅途中的一些细节。 ### Nvidia 基础镜像的选择 在本文中,我们没有和上一篇文章[《基于 Docker 的深度学习环境:入门篇》](https://soulteary.com/2023/03/22/docker-based-deep-learning-environment-getting-started.html)一样,选择使用最新的 CUDA & PyTorch 镜像,而是选择使用了 `FROM nvcr.io/nvidia/pytorch:22.12-py3`,完整 Dockerfile 如下: ```Docker 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 镜像发布记录](https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes/rel-23-01.html)中,我们能够找到满足软件正常运行情况下,最新的镜像版本就是它啦,包含 CUDA(11.8.0)和 PyTorch(1.14.0a0+410ce96)以及 TensorRT(8.5.1)。 ### 为什么原版的 Alpaca LoRA 不能多卡运行 这其实应该算是 `transformers` 组件的问题,这个问题的最早的发现者是一位社区的同学 [kooshi](https://github.com/kooshi),除了通过激活 `model.parallel` 参数来启用并行化之外,他还发现了 HF 社区的 `transformers` 在运行 LoRA 的时候,[并不会使用所有显卡的问题](https://github.com/tloen/alpaca-lora/pull/131)。目前,他已经针对性的提交了[第一个补丁](https://github.com/huggingface/transformers/pull/22329),以及在尝试彻底解决 [LoRA 多卡运行的问题](https://github.com/kooshi/alpaca-lora/tree/llama-parallelism)。 ### 如果你有八张卡,只想使用其中的某两张 这里有一个有趣的问题,主要的原因也是因为 `transformers`,如果你有多张卡,当你指定 `device_map` 的时候,如果它不为 `auto`,但是你的可用的显卡又不是从序号 0 开始的,那么将会出现各种错误。 比较简单的,不需要修改代码的解决方案是,使用 `docker --gpus` 参数,来屏蔽掉不需要展示给应用的显卡资源,比如我需要跳过前四张卡: ```Docker docker run -it --rm --gpus '"device=4,5,6,7"' ubuntu nvidia-smi ``` 那么,当你执行命令之后,你会发现之前被占用的卡就“消失”了,程序会将你指定的卡重新编号为 “cuda0~cuda3”,一切问题迎刃而解。 ## 最后 关于使用低成本的显卡资源来 fine-tune 模型,我们就聊到这里。 下一篇相关的文章里,我们聊聊其他几种不同的模型运行方式,以及找机会聊聊其他的模型。当然,如果模型 fine-tune 一切顺利,我将持续更新“贾维斯”的养成笔记。 --EOF