ChrisKim
Do not go gentle into that good night.
颢天

基于 llama.cpp 实现高性能本地大模型推理

一听到大语言模型,想必大家想到的一点就是“耗算力”“难以本地部署”。但实际上,大语言模型也有较小的版本,同时如果结合量化技术和高性能框架,在本地平台部署一个可用的大模型是完全可行的。本篇文章将会介绍使用 llama.cpp 这个高性能大模型推理框架,在本地部署开源大语言模型。

1 效果分析

废话少讲,我们先来看看部署效果怎么样再继续。如果想要立马开始部署,可以直接前往第 2 节。

1.1 llama.cpptransformers 对比

transformers 是目前最主流的大语言模型框架,可以运行多种格式的预训练模型,它底层使用 PyTorch 框架,可用 CUDA 加速。而 llama.cpp 是一个使用 C++ 实现的大语言模型推理框架,它可以运行 gguf 格式的预训练模型,它底层使用 ggml 框架,也可以调用 CUDA 加速。

众所周知,C++ 的效率是要比 Python 快的,那落实到同一个模型的推理中,两个框架会差多少呢?我们选用 Qwen2.5-3B-Instruct 这个模型进行测试,均不量化(bf16)进行推理。

transformersllama.cpp
330 tokens, 41.327 tokens/s317 tokens, 86.245 tokens/s
346 tokens, 41.929 tokens/s325 tokens, 86.757 tokens/s
410 tokens, 39.412 tokens/s331 tokens, 86.740 tokens/s
Average: 40.890 tokens/sAverage: 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-ChatQwen2.5-InstructInternLM2.5-Chat
参数量 / 量化7B / bf1614B / q8_020B / q8_0
显存占用 / 交换空间15.4GB / 1.0GB15.6GB / 0.7GB15.6GB / 6GB
推理速度42.877 tokens/s40.259 tokens/s4.347 tokens/s

可以看到,模型量化甚至使 14B 的模型跑得和 7B 差不多快,更让跑 20B 的模型也成为了可能。

模型拆分

同时,大家也可能会对“显存占用 / 交换空间”有疑问,这个实际上是 NVIDIA 在较新的驱动推出的功能,使显存占满时可以调用内存作为交换空间,而不是直接让程序 OOM 崩掉。换页这个过程是非常耗时间的,如果一个模型有一部分被交换到内存中了,那么即使它没崩,也会严重损失性能。可以看到上面的 20B 模型有 6GB 的内容被交换到内存了,推理速度非常感人。

那么我们就可以用上 llama.cpp 的又一大特色,它可以仅将一部分模型加载到 GPU,剩下的部分直接拿 CPU 跑。大家可能会想 CPU 跑那不是慢死了,但实际上,疯狂 SWAP 的 GPU 跑得还没有 CPU 快。我们接下来将一部分模型给 CPU 来跑,保证 GPU 显存不溢出,成绩如下:

InternLM2.5-ChatInternLM2.5-ChatInternLM2.5-Chat
参数量 / 量化20B / q8_020B / q8_020B / q8_0
CPU 层 / GPU 层0 / 4913 / 3617 / 32
显存占用 / 交换占用15.6GB / 6.0GB15.5GB / 1.1GB14.9GB / 0.2GB
推理速度4.347 tokens/s6.676 tokens/s5.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-InstructQwen2.5-InstructQwen2.5-InstructQwen2.5-Instruct
参数量 / 量化0.5B / q8_01.5B / q8_03B / q8_07B / q8_0
内存占用606MB1773MB3328MB7516MB
推理速度22.590 tokens/s7.267 tokens/s4.031 tokens/s1.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 相关的工具链:

安装 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:

https://assets.zouht.com/img/blog/3835-01.webp

同时,llama-server 也提供了 OpenAI 格式的 API 接口,访问 https://127.0.0.1:8080/v1 即可。

本文链接:https://www.zouht.com/3835.html
本文使用:CC BY-NC-SA 4.0 许可
# # #
首页      教程      基于 llama.cpp 实现高性能本地大模型推理

发表回复

textsms
account_circle
email

颢天

基于 llama.cpp 实现高性能本地大模型推理
一听到大语言模型,想必大家想到的一点就是“耗算力”“难以本地部署”。但实际上,大语言模型也有较小的版本,同时如果结合量化技术和高性能框架,在本地平台部署一个可用的大模型是完全可行…
扫描二维码继续阅读
2024-12-04