一听到大语言模型,想必大家想到的一点就是“耗算力”“难以本地部署”。但实际上,大语言模型也有较小的版本,同时如果结合量化技术和高性能框架,在本地平台部署一个可用的大模型是完全可行的。本篇文章将会介绍使用 llama.cpp 这个高性能大模型推理框架,在本地部署开源大语言模型。
1 效果分析
废话少讲,我们先来看看部署效果怎么样再继续。如果想要立马开始部署,可以直接前往第 2 节。
1.1 llama.cpp 与 transformers 对比
transformers 是目前最主流的大语言模型框架,可以运行多种格式的预训练模型,它底层使用 PyTorch 框架,可用 CUDA 加速。而 llama.cpp 是一个使用 C++ 实现的大语言模型推理框架,它可以运行 gguf 格式的预训练模型,它底层使用 ggml 框架,也可以调用 CUDA 加速。
众所周知,C++ 的效率是要比 Python 快的,那落实到同一个模型的推理中,两个框架会差多少呢?我们选用 Qwen2.5-3B-Instruct 这个模型进行测试,均不量化(bf16)进行推理。
transformers | llama.cpp |
---|---|
330 tokens, 41.327 tokens/s | 317 tokens, 86.245 tokens/s |
346 tokens, 41.929 tokens/s | 325 tokens, 86.757 tokens/s |
410 tokens, 39.412 tokens/s | 331 tokens, 86.740 tokens/s |
Average: 40.890 tokens/s | Average: 86.581 tokens/s |
可以看到,llama.cpp 相较于 transformers 快了整整一倍多,从 40 tokens/s 提升到了 86 tokens/s.
1.2 llama.cpp 的最好效果
模型量化
我电脑的显卡是 RTX4070Ti Super,显存 16GB,这个大小的显存如果要运行原始精度(bf16)的模型,参数量 7b 就到极限了。
为了解决这个问题,就可以使用模型量化技术,将 bf16 的模型量化为 q8_0,便可以省下接近一半的空间,即可以跑最高 14b 的模型了,而且推理速度也会变快。当然,模型量化会让模型效果有下降,但在可接受范围内。
InternLM2.5-Chat | Qwen2.5-Instruct | InternLM2.5-Chat | |
---|---|---|---|
参数量 / 量化 | 7B / bf16 | 14B / q8_0 | 20B / q8_0 |
显存占用 / 交换空间 | 15.4GB / 1.0GB | 15.6GB / 0.7GB | 15.6GB / 6GB |
推理速度 | 42.877 tokens/s | 40.259 tokens/s | 4.347 tokens/s |
可以看到,模型量化甚至使 14B 的模型跑得和 7B 差不多快,更让跑 20B 的模型也成为了可能。
模型拆分
同时,大家也可能会对“显存占用 / 交换空间”有疑问,这个实际上是 NVIDIA 在较新的驱动推出的功能,使显存占满时可以调用内存作为交换空间,而不是直接让程序 OOM 崩掉。换页这个过程是非常耗时间的,如果一个模型有一部分被交换到内存中了,那么即使它没崩,也会严重损失性能。可以看到上面的 20B 模型有 6GB 的内容被交换到内存了,推理速度非常感人。
那么我们就可以用上 llama.cpp 的又一大特色,它可以仅将一部分模型加载到 GPU,剩下的部分直接拿 CPU 跑。大家可能会想 CPU 跑那不是慢死了,但实际上,疯狂 SWAP 的 GPU 跑得还没有 CPU 快。我们接下来将一部分模型给 CPU 来跑,保证 GPU 显存不溢出,成绩如下:
InternLM2.5-Chat | InternLM2.5-Chat | InternLM2.5-Chat | |
---|---|---|---|
参数量 / 量化 | 20B / q8_0 | 20B / q8_0 | 20B / q8_0 |
CPU 层 / GPU 层 | 0 / 49 | 13 / 36 | 17 / 32 |
显存占用 / 交换占用 | 15.6GB / 6.0GB | 15.5GB / 1.1GB | 14.9GB / 0.2GB |
推理速度 | 4.347 tokens/s | 6.676 tokens/s | 5.158 tokens/s |
可以看到,随着 CPU 层的变大,交换空间占用减小,推理速度增大。但是过大的 CPU 层也会因为 CPU 的低效率,使推理速度变慢。因此实际部署时得找到一个合适的 CPU 层、GPU 层比例。
1.3 llama.cpp 的极限性能
最近正好手上整了个树莓派,因此突发奇想,看看榨干树莓派的性能,最多能跑起来多少的大模型。我这款树莓派是 5 代 8G,四核 Cortex-A76 2.4GHz。使用 CPU 来运行模型全部加载到内存中,因此从理论上来说,q8_0 量化的 7B 模型也就到极限了。当然,我也从 0.5B 开始进行尝试,想要找到一个速度和效果最平衡的模型大小。
Qwen2.5-Instruct | Qwen2.5-Instruct | Qwen2.5-Instruct | Qwen2.5-Instruct | |
---|---|---|---|---|
参数量 / 量化 | 0.5B / q8_0 | 1.5B / q8_0 | 3B / q8_0 | 7B / q8_0 |
内存占用 | 606MB | 1773MB | 3328MB | 7516MB |
推理速度 | 22.590 tokens/s | 7.267 tokens/s | 4.031 tokens/s | 1.713 tokens/s |
可以看到,参数量和推理速度呈现一个完美的反比关系,乘起来都约等于 11.5。可见 llama.cpp 这个框架的性能释放非常完美,可以充分利用平台的算力。
另外说实话,1.7 tokens/s 的速度看起来慢,但也是能用的程度。对于小小的树莓派 5,能跑起来 70 亿参数的大模型,这已经非常令人吃惊了。当然,我个人认为最合适的大小还是 1.5B 和 3B,速度和效果都比较平衡。
2 框架编译
2.1 Windows 平台
如果你是 Windows 平台,那么恭喜你的部署是最方便的。直接前往项目的 Release 就可以下载到 Windows 的二进制成品了,并且连 CUDA 版本都有现成的。
- 如果你的 CPU 没有 AVX 指令集,那就下载最纯净的二进制:llama-bxxxx-bin-win-noavx-x64.zip
- 如果有 AVX/AVX2/AVX512 指令集,那就下载对应的二进制:llama-bxxxx-bin-win-avx512-x64.zip
- 如果想用 CUDA 加速,那就下载编译了 CUDA 模块的二进制:llama-bxxxx-bin-win-cuda-cuxx.x-x64.zip
- 如果系统没装 CUDA 运行环境,可以下载打包好的环境,解压到一起就可以直接运行了:cudart-llama-bin-win-cuxx.x-x64.zip
2.2 手动编译 CPU 版本
如果你是 Linux 平台,那么其实也是可以直接下载 Release 二进制:llama-bxxxx-bin-ubuntu-x64.zip. 当然,你也可以选择直接编译代码,或者你可能像我一样要在 arm 指令集的树莓派上跑。
以 Ubuntu 20.04 编译环境为例,首先需要安装相关工具和编译工具链:
sudo apt update sudo apt install build-essential cmake git
首先 clone 代码库:
git clone https://github.com/ggerganov/llama.cpp.git
然后就可以开始编译了,注意把 j 参数的值改成自己 CPU 的线程数,进行多线程编译:
cd llama.cpp cmake -B build cmake --build build --config Release -j8
如果没有报错,那这一步便完成了。
2.3 手动编译 CUDA 版本
如果你要在 Linux 平台编译 CUDA 版本,那么首先需要准备 CUDA 相关的工具链:
- NVIDIA 显卡驱动:https://www.nvidia.cn/drivers/lookup/
- NVIDIA CUDA Toolkit:https://developer.nvidia.com/cuda-toolkit
安装 CUDA 工具链网上已经有非常多教程了,在此不再赘述。准备好后,使用 nvcc -V 指令应当能看到以下回显:
nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2023 NVIDIA Corporation Built on Wed_Nov_22_10:30:42_Pacific_Standard_Time_2023 Cuda compilation tools, release 12.3, V12.3.107 Build cuda_12.3.r12.3/compiler.33567101_0
然后仍然需要安装 2.2 节的工具安装和代码库 clone,然后就可以开始编译了。实际上和上面的区别也就是多了个 -DGGML_CUDA=ON 参数:
cd llama.cpp cmake -B build -DGGML_CUDA=ON cmake --build build --config Release -j8
可能发生的错误:CMake 版本过低
编译的时候可能会报错 CMake 版本过低,要求 CMake 3.18 以上,这样你得去 CMake 官网下载新版本的 CMake 安装了:
wget "https://github.com/Kitware/CMake/releases/download/v3.31.1/cmake-3.31.1-linux-x86_64.sh" mv cmake-3.31.1-linux-x86_64.sh /usr cd /usr bash cmake-3.31.1-linux-x86_64.sh # 安装脚本第一个选项选y,第二个选项选n
可能发生的错误:CUDA Toolkit 版本过低
llama.cpp 用到了较新的 CUDA 特性,如果出现 error: A device variable cannot be marked constexpr
这种错误,大概率是 CUDA 太老了。
建议至少升级到 CUDA 11.4 以上来编译,最好是 CUDA 11.7 或者 CUDA 12.4,因为官方的二进制就是这两个版本,说明肯定没有问题。
3 模型准备
由于 llama.cpp 必须使用 gguf 格式的模型权重,而大预言模型权重最常见的还是 hugginface 格式。那么要么找现成的 gguf 格式,要么进行格式转换。
3.1 现成模型
现在实际上很多模型在官方发布时就会发布 gguf 格式,例如在 Hugginface 或者 Modelscope 搜 Qwen2.5,可以找到官方的 gguf 格式仓库,甚至各种量化版本都有:https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/tree/main
3.2 模型转换
llama.cpp 也提供了模型转换脚本 convert_hf_to_gguf.py
,可将 hugginface 格式的模型转为 gguf,这个也挺方便的,对于没有官方发布 gguf 的模型就可以转换了。
要运行这个脚本,得先准备环境:
conda create -n hf2gguf python=3.10 conda activate hf2gguf cd llama.cpp pip install -r ./requirements/requirements-convert_hf_to_gguf.txt
然后就可以开始转换了,使用方式如下:
python convert_hf_to_gguf.py [Hugginface模型文件夹] --outfile [输出文件名] --outtype [量化可选f32,f16,bf16,q8_0,tq1_0,tq2_0,auto]
4 运行推理
进行了上面的准备工作后,便可以开始推理了。推理方式也非常简单,在 ./build/bin
目录找到 llama-server
运行就行了。这个目录也有非常多其他的二进制,可以参考官方文档使用。
llama-server
有许多参数,不过这篇文章只接触到几个,可以使用 --help
查看完整文档:
--host
指定监听的主机,如果要公网访问则选择 0.0.0.0,不填则为 127.0.0.1.--port
指定监听的端口,不填默认 8080-m
指定要运行的 gguf 模型-t
指定运行的 CPU 线程数-ngl
指定要在 GPU 上运行的模型层数(如果纯 CPU 运行则不用管)
下面是两个示例,分别是我在树莓派上和在台式机上运行的指令:
./llama-server --host 0.0.0.0 -m Qwen2.5-1.5B-Instruct-q80.gguf -t 4
./llama-server --host 0.0.0.0 -m internlm2_5-20b-chat-q80.gguf -ngl 36 -t 20
运行后,访问对应主机的对应端口(默认 https://127.0.0.1:8080/)即可进入 WebUI:
同时,llama-server
也提供了 OpenAI 格式的 API 接口,访问 https://127.0.0.1:8080/v1 即可。
发表回复