进阶 unsloth.ai 2026-10-07 22:27:00 · 6 阅读

第25章 本地运行 DeepSeek-R1 动态 1.58-bit 量化指南

unsloth 下载 ☰ 下载 博客 运行 DeepSeek R1
动态 1.58-bit 2025 年 1 月 27 日 • 作者 Daniel & Michael2025 年 1 月 27 日• 作者 Daniel & Michael DeepSeek-R1 近期反响巨大,它在完全开源的情况下,其推理能力比肩 OpenAI 的 O1 模型。我们探索了如何让更多本地用户能够运行该模型,并成功将 DeepSeek R1 671B 参数模型量化至 131GB,体积较原始 720GB 减少 80%,同时保持极佳的功能性。

通过研究 DeepSeek R1 的架构,我们得以选择性地将某些层量化至较高比特(如 4bit),而将大多数 MoE 层(如 GPT-4 中使用的)量化至 1.5bit。若天真地将所有层统一量化,模型会彻底失效,导致无限循环和胡言乱语的输出。我们的动态量化解决了这一问题——在此查看我们的动态量化对 Phi-4 和 Llama Vision 等模型的影响及基准测试。

模型运行指南见:R1 运行指南。

1.58bit 量化版本适合在 160GB VRAM 中运行以实现快速推理(如 2 张 H100 80GB GPU),吞吐量约为每秒 140 tokens,单用户推理速度约为每秒 14 tokens。运行 1.58bit R1 并非必须使用 VRAM(GPU),仅 20GB RAM(CPU)即可,但速度会非常慢。为了获得最佳性能,我们建议 VRAM + RAM 总和至少为 80GB 以上。

2025 年 2 月 6 日更新:现在你可以使用 GRPO + Unsloth 训练像 R1 这样的专属推理模型。

我们上传了大小从 131GB 到 212GB 不等的动态量化版本至:huggingface.co/unsloth/DeepSeek-R1-GGUF

附言:如果你喜欢我们的作品,欢迎 ⭐ Star 我们:github.com/unslothai/unsloth 或关注我们的账号 @UnslothAi 💖🦥 1. 动态量化版本 我们提供了 4 个动态量化版本。前三个版本使用重要性矩阵(imatrix via llama.cpp)来校准量化过程,从而支持更低的位宽表示。最后一个 212GB 的版本是通用的 2-bit 量化,未进行校准。 | MoE Bits | 磁盘大小 | 类型 | 质量 | 链接 | | :--- | :--- | :--- | :--- | :--- | | Down_proj 1.58-bit | 131GB | IQ1_S | 一般 | 链接 | | 2.06/1.56bit 1.73-bit | 158GB | IQ1_M | 良好 | 链接 | | 2.06bit 2.22-bit | 183GB | IQ2_XXS | 更好 | 链接 | | 2.5/2.06bit 2.51-bit | 212GB | Q2_K_XL | 最佳 | 链接 | 你可以查看我们完整的 R1 GGUF 合集,其中包括 4-bit、蒸馏版本等更多内容:huggingface.co/collections/unsloth/deepseek-r1 📊 2. 基准测试与消融实验 为了测试所有量化模型,我们没有依赖通用的基准测试,而是要求 DeepSeek r1 在三次尝试(pass@3)内创建一个 Flappy Bird 游戏,并根据 10 项标准(如使用随机颜色、随机形状、是否能在 Python 解释器中运行等)进行评分。我们使用了种子 3407、3408 和 3409,以及建议的温度 0.6。

左侧是 chat.deepseek.com 生成的示例,右侧是 1.58-bit 版本的结果。 DeepSeek 原版 1.58-bit 版本 令人惊讶的是,我们的动态 1.58-bit 版本在将模型体积缩小 80% 后,仍能产生有效的输出!

然而,如果你不使用我们的动态 1.58-bit 版本,而是简单地对所有层进行量化,你会得到无限重复的输出,例如在种子 3407 中:“Colours with dark Colours with dark Colours with dark Colours with dark Colours with dark” 或在种子 3408 中:“Set up the Pygame's Pygame display with a Pygame's Pygame's Pygame's Pygame's Pygame's Pygame's Pygame's Pygame's Pygame's”。

同样,如果你不使用动态版本,而是将所有层量化到 1.75-bit(149GB),无限重复的问题虽然消失了,但结果完全错误。所有输出都生成了全黑的屏幕。即使将所有层量化到 2.06-bit(175GB),结果也比 1.58-bit(131GB)的动态量化版本更糟。你最好使用性能更优的 2.22-bit(183GB)版本。

1.58-bit 动态量化偶尔会在每 8000 个 token 中产生 1 个错误 token,这需要被注释掉。使用 min_p = 0.1 或 0.05 可以缓解 1.58-bit 量化生成单个错误 token 的问题。

作为总分(满分10分)和 Pass@3 的汇总:131GB 的 1.58bit 版本在我们的 Flappy Bird 基准测试中得分为 69.2%,而 183GB 的 2bit 版本达到 91.7%。

相比之下,非动态量化表现糟糕得多。把所有层都量化到 1.58bit 后得分为 0%,即使提高到 175GB 也只有 61.7%,比我们的动态量化还要低(而且体积还更大)!
模型大小动态量化模型大小基础量化
131GB6.92133GB0
158GB9.08149GB1.67
183GB9.17175GB6.17
博客末尾提供了更详细的结果。🐋 3. 利用 DeepSeek R1 的架构特性在我们之前对 DeepSeek V3 模型(该模型使用 DeepSeek r1 进行合成数据生成)的分析中,我们注意到 DeepSeek 的前 3 层是完全稠密的,并非 MoE。简单回顾一下:MoE(混合专家)层可以在不增加 FLOPs 计算量的前提下扩大模型参数规模,因为我们动态地把大部分权重掩蔽为 0,从而实际上跳过了对置零条目的矩阵乘法。MoE 的目标是“骗过”缩放定律——增加参数数量的同时不增加计算成本。关于 MoE 以及一种旨在超越 MoE 的新方法 Memory Layers 的更多笔记,参见这条推文:x.com/danielhanchen/status/1868748998783517093

通过结合以下 4 个思路:我们的 4-bit 动态量化方法、1.58-bit LLMs 论文、Llama.cpp 的 1.5-bit 量化,以及 Super Weights 论文,我们得出了以下洞见: - 前 3 个稠密层只占全部权重的 0.5%,我们保留为 4bit 或 6bit。 - MoE 层使用共享专家,占权重的 1.5%,我们使用 6bit。 - 所有 MLA 注意力模块占权重不到 5%,可以保留为 4bit 或 6bit。注意力输出(占 3%)虽然可以量化,但最好保留较高精度。 - down_proj 对量化最敏感,尤其是前几层。我们通过 Super Weights 论文、自己的动态量化方法和 llama.cpp 的 GGUF 量化方法交叉验证了这一发现。因此,前 3 到 6 个 MoE 的 down_proj 矩阵应保留较高精度。例如在 Super Weights 论文中,几乎所有不应被量化的权重都位于 down_proj 中:

为什么最关键、“超级”权重集中在 down_proj 中,原因在于 SwiGLU 机制:

f(XWgate) * (XWup) 然后 乘以 Wdown

也就是说,up 和 gate 投影本质上相乘后会生成更大的数值,而 down_proj 需要将这些数值缩小。因此,对 down_proj 进行量化可能并非明智之举,尤其是在 Transformer 的早期层。 我们应保留 embedding 和 lm_head 分别为 4bit 和 6bit。MoE router 以及所有 layer norms 保持 32bit。 这样,约 88% 的权重都集中在 MoE 权重上!将它们量化到 1.58bit,可以大幅缩减模型体积。 我们提供了动态量化代码,作为 llama.cpp 的分支发布在:github.com/unslothai/llama.cpp 我们借助了 Bartowski 的重要性矩阵来处理低比特量化。 💬 关于 Chat Template 的问题 所有蒸馏版本以及 671B R1 主模型使用相同的 chat template: <|begin▁of▁sentence|> <|User|>What is 1+1? <|Assistant|>It's 2. <|end▁of▁sentence|> <|User|>Explain more! <|Assistant|> BOS 会被强制添加,且每个交互之间由 EOS 分隔。为避免在推理时出现重复的 BOS token,调用 tokenizer.encode(...) 时应设置 add_special_tokens = False,因为 chat template 已自动添加了一个 BOS token。

针对 llama.cpp 和 GGUF 推理,由于系统会自动处理,建议手动跳过 `<|begin▁of▁sentence|>`。特殊 token(如 `<|User|>` 和 `<|Assistant|>`)被分配了专门的 ID;在 Qwen 和 Llama 的蒸馏版本中,由于部分 token 被重新映射(例如 Qwen 没有 BOS token),因此使用了特定的 token 进行替代。 各模型的 Token ID 映射如下:
Token R1 Qwen 蒸馏版 Llama 蒸馏版
<think> 128798 151648 128013
</think> 128799 151649 128014
<|begin_of_sentence|> 0 151646 128000
<|end_of_sentence|> 1 151643 128001
<|User|> 128803 151644 128011
<|Assistant|> 128804 151645 128012
Padding 2 151654 128004
这些特殊 token 在原始模型中的形式:
Token Qwen 2.5 32B Base Llama 3.3 70B Instruct
<think> 如果想直接使用 llama.cpp,可以按照这里的构建说明操作——别忘了开启 GPU 支持!我通常使用以下命令: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.co/unsloth/DeepSeek-R1-GGUF 下载模型,可以用 Hugging Face 来完成。要下载 1.58bit 版本,运行下面这段代码即可。如果想加快下载速度,取消注释前几行来启用 hf_transfer。# pip install huggingface_hub hf_transfer
# import os # Optional for faster downloading
# os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"

from huggingface_hub import snapshot_download
snapshot_download(
repo_id = "unsloth/DeepSeek-R1-GGUF",
local_dir = "DeepSeek-R1-GGUF",
allow_patterns = ["*UD-IQ1_S*"],
)

该命令将把 3 个 GGUF 文件下载到 DeepSeek-R1-GGUF/DeepSeek-R1-UD-IQ1_S 目录。接下来,使用以下公式计算可卸载(offload)到 GPU 的层数。如果没有 GPU,则将卸载层数设为 0:


$$ n_{offload} = \frac{\operatorname{VRAM}(GB)}{\operatorname{Filesize}(GB)} \times n_{layers} - 4 $$


DeepSeek R1 共有 61 层。例如,使用 24GB 或 80GB 显存的 GPU,根据下表向下取整得到的卸载层数,若发生显存溢出需减少 1 层:


量化精度 文件大小 24GB GPU 80GB GPU 2x80GB GPU
1.58bit 131GB 7 33 全部 61 层
1.73bit 158GB 5 26 57
2.22bit 183GB 4 22 49
2.51bit 212GB 2 19 32

运行模型时,我们将 K 缓存量化为 4bit。V 缓存的量化需要为 llama.cpp 编译 flash attention 内核。我们使用机器的所有线程,并采用 DeepSeek 推荐的 0.6 温度值。上下文长度(context size)决定模型生成的 token 数量。


进入主目录,应能看到 llama.cpp 文件夹和 DeepSeek-R1-GGUF 文件夹。


--threads:CPU 核心数量
--ctx-size:输出上下文长度
--n-gpu-layers:卸载到 GPU 的层数(参考上表获取数值)


例如,在配备 24GB 显存的 RTX 4090 GPU 上,执行以下命令:


./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 \

🍎 在 Mac / Apple 设备上运行

针对 Apple Metal 设备,需留意 --n-gpu-layers 的设置。若发现机器内存溢出,请适当减少该数值。对于 128GB 统一内存的机器,通常可以卸载约 59 层。

./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 59 \
-no-cnv \
--prompt "<|User|>Create a Flappy Bird game in Python.<|Assistant|>"

🦙 在 Ollama / Open WebUI 中运行

Open WebUI 已制作官方教程,指导如何运行 R1:docs.openwebui.com/tutorials/integrations/deepseekr1-dynamic/

如果希望在 Ollama 中对 GGUF 进行推理,首先需要将 3 个拆分文件合并为 1 个,代码如下。随后需在本地运行该模型。

./llama.cpp/llama-gguf-split --merge \
DeepSeek-R1-GGUF/DeepSeek-R1-UD-IQ1_S/DeepSeek-R1-UD-IQ1_S-00001-of-00003.gguf \
merged_file.gguf

💡 Prompt 与结果

使用的完整 Prompt 如下:

用 Python 创建一个 Flappy Bird(鸟类横移)游戏。需包含以下内容:必须使用 pygame。背景色应随机选择浅色,以浅蓝色开始。多次按下空格键会加速鸟的飞行。鸟的形状随机选定为正方形、圆形或三角形,颜色随机选定为深色。在底部放置随机选择深棕色或黄色的土地。在右上角显示分数,通过管道且不碰撞时分数增加。生成间距随机且间距足够大的管道,颜色随机选为深绿色、浅棕色或深灰色。失败时显示最高分,文字需显示在屏幕内。按 q 或 Esc 退出游戏,再次按空格重新开始。最终游戏代码需包裹在 Python 的 Markdown 代码块中。在生成最终代码块前,请检查并修正代码错误。

完整表格与结果见:docs.unsloth.ai/basics/deepseek-r1-dynamic-1.58-bit
所有 18 个输出样本和生成的 Python 代码也已上传至该处!

💕 感谢!
照例,衷心感谢大家使用并分享 Unsloth - 我们对此感激不尽。 🙏



一如既往,欢迎加入我们的 Reddit 页面和 Discord 服务器寻求帮助,或单纯表达支持!你也可以在 Twitter 上关注我们,或订阅我们的 newsletter。感谢阅读!Daniel & Michael Han 🦥
2025年1月27日 立即微调你自己的模型!了解更多 加入我们的 Discord

评论 (0)