单卡 RTX 4090 部署 DeepSeek-R1 671B 模型

572 阅读5分钟

一、前言

最近 DeepSeek-R1 比较火,让本地部署大模型成为一个瞩目的话题。DeepSeek-R1 提供了 1.5B 到 671B 的模型,而大多数人电脑只能支持到 7-32B 模型的部署,而实际上 7-32B 的模型是基于 llama、Qwen 等模型蒸馏出来的,并非 DeepSeek 官网的 R1 模型。今天我们要做的就是本地部署 DeepSeek-R1 671B模型,挑战硬件极限。

二、模型精度

在深度学习早期,我们通常使用双精度浮点数(FP32)来表示模型参数,而双精度浮点数占用 4 个字节, 对于一个 100M 参数的模型其大小为:

模型大小=模型参数量单参数字节数10241024381M 模型大小=\frac{模型参数量* 单参数字节数}{1024*1024}≈381M

推理的显存占用计算则比较复杂,我们假定和模型大小一致。而现在一个 1B 的模型其大小约为3.7GB,而 7B 的则大约为 26GB,而 70B 的则到了恐怖的 260G。

为了节省内存,出现了单精度(FP16)的方案,单精度浮点数占用 2 个字节,按照上述计算,7B 在 FP16 下大约为 13GB。13G 显存对游戏佬来说已经是一个可接受的范围了。

在 Bert时代,300M 参数的模型已经是大模型了,而进入 GPT 时代后,大模型开始以 B 为单位,此时 FP16 精度对个人电脑来说,也是无法承受的。由此模型量化技术成为了标配。

三、模型量化

通过将 FP16 的浮点数映射到8为的整型或 4 位的整型,将模型参数的大小再次缩小。以 8bit 量化为例,单个参数大小降到了 1 个字节,此时 7B 的模型大小为 6.5G 左右。而 4bit 量化单个参数大小降到了 0.5 个字节,此时 7B 的模型大小为 3.25G,已经到了个人电脑可以流畅运行的程度了。

但是如果使用 ollama,会发现 7B 的模型 4bit 量化大小并非 3.25G,而是 4.7G:

image.png

这是因为量化技术其实是对模型的线性层进行量化,因此实际大小要大约计算大小。

现在我们的目标是部署 671B 的模型,其大小为 404GB。目前要在单张 4090 部署还是比较困难的。

为了进一步量化,可以使用到 BitNet,这是微软开源的一个项目,可以将模型 动态量化到 1.58bit,此时 671B 的模型大小为 131GB。关于 BitNet 更详细的内容可以参考:arxiv.org/abs/2402.17…

四、逐层加载

DeepSeek-R1是一个 61 层的MoE 架构,在 1.58bit 量化的情况下,160G 显存才能完整加载所有层。目前为止量化可以说是走到头了,由此又出现了逐层加载技术。

我们可以把模型的层分为 layer1-layer61,在推理时,假设输入为 X,推理过程可以简化为:

x = ...
for layer in layers:
    x = layer(x)
print(x)

现在我们使用 GPU 来加速上面的推理:

x = x.cuda()
for layer in layers:
    layer = layer.cuda()
    x = layer(x)
    layer.cpu()

此时只需要显存能够加载一个 layer 就可以使用 GPU 加速推理。但是频繁的在 CPU 和 GPU 之间通信会让推理速度下降,因此可以一次性选择加载多层,比如一张 4090可以加载 7 层,因此我们修改为:

# 将 x 和前 7 个 layers 一起加载到 GPU
x = x.cuda()

# 假设 layers 是一个包含多个层的列表
layers_to_move = layers[:7]
# 将前 7 个 layer 加载到 GPU
layers_to_move = [layer.cuda() for layer in layers_to_move]

# 执行这些层
for layer in layers_to_move:
    x = layer(x)

# 如果剩下的 layers 需要处理,也可以继续使用原来代码中的方式处理
for layer in layers[7:]:
    x = layer(x)

此时 CPU 和 GPU 通信次数减少了,因此推理也更快了。现在只需要我们设置每次加载 7 层,就可以在 4090 上运行 DeepSeek-R1 的 681B 模型了。

五、实践

首先需要下载编译 llama.cpp 项目。llamacpp 是一个大模型推理项目,该项目使用 C++重写了大模型推理的代码,可以让我们在纯 CPU 模式下推理大模型,同时还可以使用 GPU 加速、逐层加载、提供 api 服务等,ollama 底层使用的就是 llamacpp。

这里以 Linux 环境为例,首先安装 llamacpp:

apt-get update 
apt-get install build-essential cmake curl libcurl4-openssl-dev -y 
git clone https://github.com/ggerganov/llama.cpp cmake llama.cpp -B llama.cpp/build \ 
    -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON -DLLAMA_CURL=ON 
cmake --build llama.cpp/build --config Release -j --clean-first --target 
llama-quantize llama-cli llama-gguf-split 
cp llama.cpp/build/bin/llama-* llama.cpp

然后下载量化后的模型,可以使用 huggingface-cli 下载:

huggingface-cli download unsloth/DeepSeek-R1-GGUF --include *UD-IQ1_S*

也可以手动下载:huggingface.co/unsloth/Dee…

下载 DeepSeek-R1-UD-IQ1_S 目录下的文件。下载完成后直接运行 llamacpp 即可:

./llama.cpp/llama-cli \ 
    --model DeepSeek-R1-GGUF/DeepSeek-R1-UD-IQ1_S/DeepSeek-R1-UD-IQ1_S-00001-of-00003.gguf \
    --cache-type-k q4_0 \ 
    --threads 16 \ 
    --prio 2 \ 
    --temp 0.6 \ 
    --ctx-size 8192 \ 
    --seed 3407 \ 
    --n-gpu-layers 7 \ 
    -no-cnv \ 
    --prompt "<|User|>Create a Flappy Bird game in Python.<|Assistant|>"

这里需要指定--model、--n-gpu-layers、--prompt 三个参数,其中--n-gpu-layers 根据显卡显存指定,这里使用 4090 则设置为 7。