基于 llama.cpp 实现高性能本地大模型推理
基于 llama.cpp 实现高性能本地大模型推理
一听到大语言模型,想必大家想到的一点就是“耗算力”“难以本地部署”。但实际上,大语言模型也有较小的版本,同时如果结合量化技术和高性能框架,在本地平台部署一个可用的大模型是完全可行的。本篇文章将会介绍使用 llama.cpp 这个高性能大模型推理框架,在本地部署开源大语言模型。
效果分析
废话少讲,我们先来看看部署效果怎么样再继续。如果想要立马开始部署,可以直接前往第 2 节。
llama.cpp 与 transformers 对比
transformers 是目前最主流的大语言模型框架,可以运行多种格式的预训练模型,它底层使用 PyTorch 框架,可用 CUDA 加速。而 llama.cpp 是一个使用 C++ 实现的大语言模型推理框架,它可以运行 gguf 格式的预训练模型,它底层使用 ggml 框架,也可以调用 CUDA 加速。
众所周知,C++ 的效率是要比 Python 快的,那落实到同一个模型的推理中,两个框架会差多少呢?我们选用 Qwen2.5-3B-Instruct 这个模型进行测试,均不量化(bf16)进行推理。
可以看到,llama.cpp 相较于 transformers 快了整整一倍多,从 40 tokens/s 提升到了 86 tokens/s.
llama.cpp 的最好效果
模型量化
我电脑的显卡是 RTX4070Ti Super,显存 16GB,这个大小的显存如果要运行原始精度(bf16)的模型,参数量 7b 就到极限了。
为了解决这个问题,就可以使用模型量化技术,将 bf16 的模型量化为 q8_0,便可以省下接近一半的空间,即可以跑最高 14b 的模型了,而且推理速度也会变快。当然,模型量化会让模型效果有下降,但在可接受范围内。
可以看到,模型量化甚至使 14B 的模型跑得和 7B 差不多快,更让跑 20B 的模型也成为了可能。
模型拆分
同时,大家也可能会对“显存占用 / 交换空间”有疑问,这个实际上是 NVIDIA 在较新的驱动推出的功能,使显存占满时可以调用内存作为交换空间,而不是直接让程序 OOM 崩掉。换页这个过程是非常耗时间的,如果一个模型有一部分被交换到内存中了,那么即使它没崩,也会严重损失性能。可以看到上面的 20B 模型有 6GB 的内容被交换到内存了,推理速度非常感人。
那么我们就可以用上 llama.cpp 的又一大特色,它可以仅将一部分模型加载到 GPU,剩下的部分直接拿 CPU 跑。大家可能会想 CPU 跑那不是慢死了,但实际上,疯狂 SWAP 的 GPU 跑得还没有 CPU 快。我们接下来将一部分模型给 CPU 来跑,保证 GPU 显存不溢出,成绩如下:
可以看到,随着 CPU 层的变大,交换空间占用减小,推理速度增大。但是过大的 CPU 层也会因为 CPU 的低效率,使推理速度变慢。因此实际部署时得找到一个合适的 CPU 层、GPU 层比例。
llama.cpp 的极限性能
最近正好手上整了个树莓派,因此突发奇想,看看榨干树莓派的性能,最多能跑起来多少的大模型。我这款树莓派是 5 代 8G,四核 Cortex-A76 2.4GHz。使用 CPU 来运行模型全部加载到内存中,因此从理论上来说,q8_0 量化的 7B 模型也就到极限了。当然,我也从 0.5B 开始进行尝试,想要找到一个速度和效果最平衡的模型大小。
可以看到,参数量和推理速度呈现一个完美的反比关系,乘起来都约等于 11.5。可见 llama.cpp 这个框架的性能释放非常完美,可以充分利用平台的算力。
另外说实话,1.7 tokens/s 的速度看起来慢,但也是能用的程度。对于小小的树莓派 5,能跑起来 70 亿参数的大模型,这已经非常令人吃惊了。当然,我个人认为最合适的大小还是 1.5B 和 3B,速度和效果都比较平衡。
框架编译
https://github.com/ggerganov/llama.cpp
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
手动编译 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
如果没有报错,那这一步便完成了。
手动编译 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 特性,如果出现 这种错误,大概率是 CUDA 太老了。
建议至少升级到 CUDA 11.4 以上来编译,最好是 CUDA 11.7 或者 CUDA 12.4,因为官方的二进制就是这两个版本,说明肯定没有问题。
模型准备
由于 llama.cpp 必须使用 gguf 格式的模型权重,而大预言模型权重最常见的还是 hugginface 格式。那么要么找现成的 gguf 格式,要么进行格式转换。
现成模型
现在实际上很多模型在官方发布时就会发布 gguf 格式,例如在 Hugginface 或者 Modelscope 搜 Qwen2.5,可以找到官方的 gguf 格式仓库,甚至各种量化版本都有:https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/tree/main
模型转换
llama.cpp 也提供了模型转换脚本 ,可将 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]
运行推理
进行了上面的准备工作后,便可以开始推理了。推理方式也非常简单,在 目录找到 运行就行了。这个目录也有非常多其他的二进制,可以参考官方文档使用。
有许多参数,不过这篇文章只接触到几个,可以使用 查看完整文档:
- --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:
同时, 也提供了 OpenAI 格式的 API 接口,访问 https://127.0.0.1:8080/v1 即可。