Deepseek-r1 的预算是多少?揭开节省资源的微调秘密!
如何让深度寻求 R1 使用您的私有数据进行推理
照片由 Dan Schiumarini 提供,来源于 Unsplash
我们不再需要验证来说明深度寻求 R1 是多么出色。它确实很出色。
深度寻求可以提供与 OpenAI-O1 相当的性能,但成本却仅为其一小部分。这是因为该模型是开源的。
然而,我们不能直接跳入并开始在生产级应用中使用它。任何模型,无论是开源权重还是专有模型,都有一个必须符合您预算的推理成本。无需多言,参数越多,成本就越高。
深度寻求的 R1 是一个 6710 亿参数的模型。对于任何给定的推理,大约 370 亿个活跃参数就能完成任务。您至少需要 90GB 的虚拟内存 来本地托管此模型。
以下是托管该模型所需 GPU 内存的粗略计算。
本地托管 LLM 的虚拟内存估算 - 作者提供的图像
如果您没有一块拥有这么多虚拟内存的 GPU,幸运的是,这并不是世界的尽头。
当然,您可以从提供商那里租用 GPU。但我不是在谈论这个。
深度寻求 R1 可以在最流行的 GPU 上成功运行,例如 RTX 3090 或 4090。您甚至可以在您的 Colab 或 Kaggle 笔记本中免费运行它。
让这样的模型在较小 GPU 上工作的一个方法是选择较小的 蒸馏版本。蒸馏版本与原始模型并不完全相同;相反,它们是基于原始模型生成的样本训练的其他开源模型。
例如,DeepSeek-R1-Distill-Llama-70B 是通过用 DeepSeek R1 生成的示例微调 Llama-3.3-70B-Instruct 的结果。
这些蒸馏版本保留了原始模型的大部分推理能力,但参数更少,使其适合较小的 GPU。
另一个选择是使用量化版本。4位量化版本 仅使用 16 位版本的四分之一内存。这是通过用较低精度的数值表示模型权重来实现的。
在消费级硬件上托管 LLM 现在是一种普遍的技术。降低精度会导致一些准确性的损失,但与我们节省的计算能力相比,这是值得的权衡。这对您的钱包和地球都有好处!
在这篇文章中,我们将探索这两种选择的极限。我们将选择一个小型 15 亿参数的蒸馏模型,并使用 4 位量化 配置对其进行微调。
我们将使用 Magpie Reasoning V2
数据集对 DeepSeek-R1-Distill-Qwen-1.5B
模型进行微调。但我们将简要介绍 如何准备您的数据集以微调这样的推理模型。
准备微调环境
如前所述,我们将采用量化和蒸馏模型的极低设置。因此,您可以使用免费的 Colab 或 Kaggle 账户进行操作。
我在一个带有 GPU T4 x 2 的 Kaggle 笔记本上进行了测试。
请将 Huggingface 令牌添加到机密中,并在您的笔记本中读取它们。我们从 Huggingface 获取我们的初始模型和数据集。
我们将使用 Unsloth 来执行这个微调。因此,您可以首先安装它们。
!pip install unsloth
!pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git
然后,我们可以通过以下方式以编程方式登录 Huggingface:
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()
hf_token = user_secrets.get_secret("HF_TOKEN")
login(hf_token)
下载大语言模型并在不进行微调的情况下进行推理
我们现在可以从Hugging Face下载模型,并尝试提出一个需要一定思考链的复杂问题。
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/深度寻求 R1-蒸馏-Qwen-1.5B",
max_seq_length = 2048,
dtype = None,
load_in_4bit = True,
token = hf_token,
)
请注意,在上述代码中,我们将load_in_4bit
参数设置为True。这将选择4位量化版本,而不是常规的32位版本。
我们现在可以要求模型解决一些琐事问题。让我们创建一个提示格式,以便我们可以快速插入问题,而无需每次都重写它。
问题:一个委员会是通过从10个人中随机选择5名成员来形成的。从10个人中可以以多少种方式形成一个5人的委员会,顺序无关紧要?
prompt_template = """以下是关于特定任务的指令和需要回答的问题。
写一个响应来完成任务。
仔细思考问题,创建一个逐步的思考链,以确保在回答之前给出逻辑和准确的响应。
看起来问题不完整或缺失。请提供与排列、组合或概率论相关的具体统计问题,我将很乐意帮助您。
委员会形成问题
question = "A committee is formed by randomly selecting 5 members from a group of 10 people. In how many ways can a committee of 5 people be formed from 10 people, where order does not matter?"
FastLanguageModel.for_inference(model)
inputs = tokenizer([prompt_template.format(question, "")], return_tensors="pt").to("cuda")
outputs = model.generate(
input_ids=inputs.input_ids,
attention_mask=inputs.attention_mask,
max_new_tokens=1200,
use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0].split("### Response:")[1])
上述问题询问从10人中选择五个成员有多少种方法。答案是252,而大语言模型甚至在没有微调的情况下找到了这个答案。这真是太神奇了。
深度寻求 R1 蒸馏 Qwen-1.5B 的思维过程(未微调)——作者截图。
现在,让我们给模型一个稍微复杂一些的问题来解决。
问题:一场考试由两个部分组成:A部分有6个问题,B部分有7个问题。学生必须尝试恰好10个问题,从每个部分至少选择4个。学生可以选择这10个问题的方式有多少种?
大语言模型没有正确回答上述问题。虽然正确答案是266,但大语言模型计算的结果是1408。
深度寻求 R1 蒸馏 Qwen-1.5B II 的思维过程(未微调)——作者截图。
较小的模型缺乏数学推理能力。这可以通过对模型进行数学问题的微调来解决。
微调深度寻求 R1 蒸馏 Qwen-1.5B
由于我们无法得到合理的推理问题答案,让我们看看微调是否能改善其结果。我们先从下载数据集开始。
from datasets import load_dataset
dataset = load_dataset(
"Magpie-Align/Magpie-Reasoning-V2-250K-CoT-Deepseek-R1-Llama-70B",
split="train[0:1000]",
trust_remote_code=True
)
上述代码将下载 Magpie 推理 v2 数据集。这个庞大的数据集有 250k 条记录,因此我下载了前 1000 条,而不是一次性加载所有内容。然而,对于更复杂的问题解决,您可能需要整个数据集。
我们现在可以将这个数据集的行转换为准备微调的单独提示。
EOS_TOKEN = tokenizer.eos_token
def formatting_prompts_func(examples):
instruction = examples["instruction"]
response = examples["response"]
texts = []
for instruction, response in zip(instruction, response):
text = prompt_template.format(instruction, response) + EOS_TOKEN
texts.append(text)
return {
"text": texts,
}
dataset = dataset.map(formatting_prompts_func, batched=True)
上述代码将从每一行中提取 instruction
和 response
列,使用我们之前创建的模板生成提示。
我们现在可以从模型创建训练器实例,并开始使用我们的数据进行微调。
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=5,
max_steps=60,
learning_rate=2e-4,
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=10,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
output_dir="outputs",
),
)
trainer_stats = trainer.train()
通过这次训练,我们可以从新的微调模型中得出推论。让我们用之前模型失败的问题来测试它。
FastLanguageModel.for_inference(model)
inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")
outputs = model.generate(
input_ids=inputs.input_ids,
attention_mask=inputs.attention_mask,
max_new_tokens=1200,
use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print(response[0].split("### Response:")[1])
这次,大语言模型能够正确生成答案,如下所示:
深度寻求 R1 蒸馏 Qwen-1.5B 微调后的推理 — 作者截图。
这清楚地表明,使用良好的数据集微调小型蒸馏深度寻求 + Qwen 模型使其具备解决复杂问题的能力。
但是,使用如此小的模型是有限的。在这种情况下,您可能需要切换到不同的参数版本。没有经验法则可以确定多少参数适合我们的用例。我们必须进行测试并找出答案。
最好的方法是从像这个 1.5B 的小型模型开始,逐渐增加规模。
然而,除了参数数量,另一个影响微调质量的因素是我们数据集的格式。
如何准备您的私人数据集以微调推理模型
微调大语言模型中最关键的事情是准备数据集。我相信任何有经验的人都会对此表示赞同。
如果您查看我们刚刚完成的示例,您会看到我们将问题、思考和答案嵌入到同一个提示中,并将其传递给调优过程。
这里的关键是分隔符。分隔符是一组在常规文本中不出现的字符。以下是一些示例:
- 示例 1:
|
- 示例 2:
;
- 示例 3:
::
分隔符允许我们对提示进行分段,并指导大语言模型。在我们的示例中,我们使用分隔符来分隔指令、问题和响应部分。
在提示中创建段落的另一种方法是使用标签。
如果您打开 Huggingface 上的 Magpie 数据集 并查看响应列,每个单元格的值将具有以下结构:
<reasoning>...</reasoning>
<response>...</response>
这是 XML 语法,为模型提供了更多信息。标签内的任何内容都是响应之前的推理。这对于微调任何推理模型至关重要。
最后,您应该有一组提示来微调推理模型,每个提示应该具有类似于以下的标准:
<instruction>...</instruction>
<question>...</question>
<response>...</response>
指令
问题
最后的想法
这里有一个忏悔。
使用像我在这里使用的1.5B这样的小型LLM解决组合问题是一种奇迹——即使在微调之后。
但这不是一篇声称微调会让任何模型解决任何问题的文章。这篇文章实际上是关于如何在资源受限的硬件上使用较小的模型,以及如何微调它们以获得所需的性能。
对于生产用途,您可能需要尝试几个版本和量化选项,以找到最适合您需求的。
尽管高度巨型模型是不必要的,但您必须从一个相对较大的模型开始,以构建一个生产就绪的解决方案——除非是边缘用例。