前言
本教程将介绍如何使用 llama.cpp 部署 DeepSeek V3 模型,首先是 32B 蒸馏版本,然后会尝试部署 671B 完整版本。以下是一些基础信息:
- Ubuntu 20.04 amd64
- Docker
- Nvidia Container Toolkit
- CUDA: 12.4
- llama.cpp: b4743
llama.cpp 最大的优点就是兼容性好,不挑设备。vllm 虽然性能更强大,吞吐量高,但是必须 Ampere GPU 或者更高才能运行同样的模型。
需要管理员权限的环境配置
由于我使用的计算服务器没有外网,需要通过代理联网,所以 sudo 命令均带有-E
参数,以保留环境变量中的代理设置。
安装 Docker
建议 Ubuntu 20.04 或者更高,有必要的话先更新系统。目前 18.04 似乎也能运行,但是需要一点特殊处理。以下是官方文档
https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
# Add Docker's official GPG key:
sudo -E apt-get update
sudo -E apt-get -y install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo -E curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo -E apt-get update
sudo -E apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
配置 Rootless Docker Daemon
以下是官方文档
https://docs.docker.com/engine/security/rootless/#prerequisites
sudo apt-get install -y dbus-user-session
sudo apt-get install -y uidmap
sudo apt-get install -y systemd-container
sudo apt-get install -y docker-ce-rootless-extras
这一步可选,关闭 Root Docker Daemon
sudo systemctl disable --now docker.service docker.socket
sudo rm /var/run/docker.sock
Ubuntu 18.04 的 slirp4netns 配置
Rootless Docker Daemon 需要一个用户态的网络支持,这需要用到slirp4netns
。但是,该软件仅在 Ubuntu 19.10 后才能随 apt 安装,我们需要手动下载。
# 注意修改版本,推荐使用最新版
wget https://github.com/rootless-containers/slirp4netns/releases/download/v1.3.2/slirp4netns-x86_64
sudo mv slirp4netns-x86_64 /usr/local/bin/slirp4netns
安装或更新 Nvidia Driver 和 CUDA
根据 llama.cpp 的需求,推荐使用 12.4 的 CUDA,可以这样下载:
wget https://developer.download.nvidia.com/compute/cuda/12.4.1/local_installers/cuda_12.4.1_550.54.15_linux.run
接下来安装它,全部默认选项即可,选择Install
sudo sh cuda_12.4.1_550.54.15_linux.run
安装过程不会输出过程信息到终端,而是直接打印到文件,可以到以下文件看到安装过程的输出:
- /var/log/nvidia-installer.log
- /var/log/cuda-installer.log
安装 Nvidia Container Toolkit
以下是官方文档
https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo -E apt-get update
sudo -E apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
一般用户的环境配置
配置和启动 Rootless Docker Daemon
首先,需要在配置后重新登录
接下来,使用 Docker 提供的脚本生成配置文件
dockerd-rootless-setuptool.sh install
修改.bashrc
,添加环境变量
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$UID/docker.sock
为 Docker Daemon 配置代理
这一步可选,因为前面提到的网络原因,docker pull
等操作需要配置代理服务器。打开$HOME/.config/systemd/user/docker.service
,添加代理服务器
[Service]
Environment=PATH=...
Environment="HTTP_PROXY=???"
Environment="HTTPS_PROXY=???"
Environment="NO_PROXY=10.10.10.10,*.my.registry.com"
...
接下来重新启动 Rootless Docker Daemon
systemctl --user daemon-reload
systemctl --user restart docker
配置和启动 Nvidia Container Toolkit
配置 Nvidia CTK 运行时
nvidia-ctk runtime configure --runtime=docker --config=$HOME/.config/docker/daemon.json
systemctl --user restart docker
测试配置是否成功
应该和 nvidia-smi
的效果一样,看到所有的 GPU,注意不需要 sudo 或者添加用户到 docker 用户组
docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
部署 DeepSeek-R1-Distill-Qwen-32B
初步测试
测试配置:4 x 2080Ti 11GB
下载模型
pip install huggingface_hub
mkdir -p models/models--unsloth--DeepSeek-R1-Distill-Qwen-32B-GGUF
HF_ENDPOINT=https://hf-mirror.com \
huggingface-cli download \
unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF \
DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf \
--local-dir models/models--unsloth--DeepSeek-R1-Distill-Qwen-32B-GGUF
拉取镜像,也可以在 run 的时候自动拉取。
docker pull ghcr.io/ggml-org/llama.cpp:server-cuda
启动服务器
docker run \
--rm --gpus all -p 8080:8000 -v ./models:/models \
ghcr.io/ggml-org/llama.cpp:server-cuda \
-m /models/models--unsloth--DeepSeek-R1-Distill-Qwen-32B-GGUF/DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf \
--port 8000 --host 0.0.0.0 -t 8 -c 32768 -n 8192 --n-gpu-layers 512
资源占用:
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| 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 RTX 2080 Ti On | 00000000:3B:00.0 Off | N/A |
| 31% 35C P2 63W / 250W | 10092MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA GeForce RTX 2080 Ti On | 00000000:5E:00.0 Off | N/A |
| 31% 37C P2 60W / 250W | 9486MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA GeForce RTX 2080 Ti On | 00000000:AF:00.0 Off | N/A |
| 31% 37C P2 43W / 250W | 9486MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA GeForce RTX 2080 Ti On | 00000000:D8:00.0 Off | N/A |
| 31% 36C P2 62W / 250W | 9922MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
推理速度
prompt eval time = 544.32 ms / 233 tokens ( 2.34 ms per token, 428.06 tokens per second)
eval time = 16309.46 ms / 346 tokens ( 47.14 ms per token, 21.21 tokens per second)
total time = 16853.77 ms / 579 tokens
由于我使用的服务器是双路 CPU,没有 NVLink,存在 NUMA 问题,所以通讯速度很差。这个时候,如果使用-sm row
参数,会导致速度降低到 15tokens/s。
进一步压榨性能
通过以下参数,可以进一步压榨 GPU 的能力:
-fa
:使用 Flash Attention 可节约动态内存,提升推理速度-ctk q8_0 -ctv q8_0
:使用量化 KV Cache ,进一步节约内存-c 128000
:增加上下文窗口长度,允许模型进行更长的对话,处理更长的输入内容--n-gpu-layers 512
:尽可能多地将模型参数放入显存,可以提升推理速度
docker run \
--rm --gpus all -p 8080:8000 -v ./models:/models \
ghcr.io/ggml-org/llama.cpp:server-cuda \
-m /models/models--unsloth--DeepSeek-R1-Distill-Qwen-32B-GGUF/DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf \
--port 8000 --host 0.0.0.0 -t 8 --n-gpu-layers 512 -c 128000 -n 8192 -fa -ctk q8_0 -ctv q8_0
资源占用:
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| 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 RTX 2080 Ti On | 00000000:3B:00.0 Off | N/A |
| 31% 33C P8 21W / 250W | 10847MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA GeForce RTX 2080 Ti On | 00000000:5E:00.0 Off | N/A |
| 31% 36C P8 18W / 250W | 9479MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA GeForce RTX 2080 Ti On | 00000000:AF:00.0 Off | N/A |
| 31% 34C P8 1W / 250W | 9479MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA GeForce RTX 2080 Ti On | 00000000:D8:00.0 Off | N/A |
| 31% 35C P8 22W / 250W | 9947MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
推理速度
prompt eval time = 490.96 ms / 232 tokens ( 2.12 ms per token, 472.54 tokens per second)
eval time = 11767.64 ms / 254 tokens ( 46.33 ms per token, 21.58 tokens per second)
total time = 12258.60 ms / 486 tokens
部署 DeepSeek-R1-UD-IQ1_M
测试配置:4 x V100 32GB
这个配置对于 671B 的模型来说非常丐,属于非常极限刚好能运行,推理速度很慢。
下载模型,模型是分成三个文件的,这是因为 git-lfs 的限制为 50GB
HF_ENDPOINT=https://hf-mirror.com \
huggingface-cli download \
unsloth/DeepSeek-R1-GGUF \
--include "*UD-IQ1_S*" \
--local-dir models/DeepSeek-R1-UD-IQ1_S
启动服务器,llama.cpp 会自动加载剩余的两个模型分片文件
docker run \
--rm --gpus all -p 8080:8000 -v ./models:/models \
ghcr.io/ggml-org/llama.cpp:server-cuda \
-m /models/DeepSeek-R1-UD-IQ1_S/DeepSeek-R1-UD-IQ1_S-00001-of-00003.gguf \
--port 8000 --host 0.0.0.0 -t 8 -c 8192 -n 4096 \
--n-gpu-layers 40 -ts 14,16,16,16 -ctk q8_0
这里的参数与先前有所不同:
- 无
-fa
:DeepSeek-R1 模型使用 MLA,与 Flash Attention 不兼容 -ctk q8_0
:对于 MLA 模型,只有 k 可以量化--n-gpu-layers 40
:只保留 40 层在显存中-ts 14,16,16,16
:DeepSeek-R1 默认状态下,GPU 0 的负载会稍高,通过调节切分参数,可以平衡显存占用
资源占用:
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| 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 Tesla V100-SXM2-32GB On | 00000000:00:05.0 Off | 0 |
| N/A 41C P0 55W / 300W | 30042MiB / 32768MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 1 Tesla V100-SXM2-32GB On | 00000000:00:06.0 Off | 0 |
| N/A 39C P0 57W / 300W | 29576MiB / 32768MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 2 Tesla V100-SXM2-32GB On | 00000000:00:07.0 Off | 0 |
| N/A 41C P0 54W / 300W | 29576MiB / 32768MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 3 Tesla V100-SXM2-32GB On | 00000000:00:08.0 Off | 0 |
| N/A 40C P0 55W / 300W | 29576MiB / 32768MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
推理速度
prompt eval time = 13482.15 ms / 226 tokens ( 59.66 ms per token, 16.76 tokens per second)
eval time = 87209.41 ms / 270 tokens ( 323.00 ms per token, 3.10 tokens per second)
total time = 100691.56 ms / 496 tokens
总结
llama.cpp 对于老旧硬件的支持非常好,像 V100,2080Ti 这样比较老旧的 GPU,也能运行最新的模型。
然而,671B 的模型还是太大了,为了能运行在 V100 上,只能将一部分参数放在 CPU 内存,这进一步降低了推理速度。这个部署尝试更像是一种极限运动,缺乏实用性。个人认为:
- 解码速度需要达到 10token/s 才能算是比较流畅
- 上下文窗口 8192,才能满足联网查询和深度思考的要求
如果需要自己部署,还是推荐使用 DeepSeek-R1-Distill-Qwen-32B-GGUF。在 Q4 量化下,使用一个 RTX A6000 就能进行推理,并享受到完整的 128k 长上下文,是比较实用的方案。
docker run \
--rm --gpus all -p 8080:8000 -v ./models:/models \
ghcr.io/ggml-org/llama.cpp:server-cuda \
-m /models/models--unsloth--DeepSeek-R1-Distill-Qwen-32B-GGUF/DeepSeek-R1-Distill-Qwen-32B-Q4_K_M.gguf \
--port 8000 --host 0.0.0.0 -t 8 --n-gpu-layers 512 -c 131072 -n 8192 -fa -ctk q8_0 -ctv q8_0
资源占用:
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| 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 RTX A6000 Off | 00000000:01:00.0 Off | Off |
| 30% 35C P8 64W / 300W | 36649MiB / 49140MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
推理速度
prompt eval time = 271.75 ms / 233 tokens ( 1.17 ms per token, 857.42 tokens per second)
eval time = 7982.01 ms / 239 tokens ( 33.40 ms per token, 29.94 tokens per second)
total time = 8253.75 ms / 472 tokens