用Python微调DeepSeek R1

📖阅读时长:15分钟

🕙发布时间:2025-02-05

近日热文:全网最全的神经网络数学原理(代码和公式)直观解释
欢迎关注知乎和公众号的专栏内容
LLM架构专栏
知乎LLM专栏
知乎【柏企
公众号【柏企科技说】【柏企阅文

微调前的准备工作

在正式开始微调大语言模型之前,我们先来了解一下技术前提条件和设置要求。

Python库和框架

微调大语言模型需要用到以下Python库和框架:

  • unsloth:这个库可太厉害了,它能让像Llama - 3、Mistral、Phi - 4和Gemma 2这些大语言模型的微调速度提高2倍,内存使用减少70%,而且不会降低模型的准确性!
  • torch:它是基于PyTorch进行深度学习的基础构建模块。提供了强大的张量库,和NumPy有些类似,但它有个巨大的优势,就是支持GPU加速,这在处理大语言模型时至关重要。
  • transformers:这是一个功能强大且广受欢迎的自然语言处理(NLP)开源库。它提供了易于使用的接口,让我们可以轻松访问各种最先进的预训练模型。毕竟,预训练模型是任何微调任务的基础,这个库可帮了大忙。
  • trl:Python中的trl包是一个专门用于基于Transformer模型进行强化学习(RL)的库。它建立在Hugging Face transformers库之上,充分利用其优势,让基于Transformer的强化学习变得更易上手、更高效。

计算要求

微调模型是一种让大语言模型的回答更结构化、更具领域针对性的技术,而且不需要对所有参数进行全面训练。不过,对于大多数普通计算机硬件来说,微调大型大语言模型仍然不太现实。因为所有可训练参数以及实际的大语言模型都要存储在GPU的虚拟随机存取存储器(vRAM)中,而大语言模型庞大的规模成了实现这一目标的主要障碍。

所以,在本文中,我们将微调一个相对较小的大语言模型——DeepSeek - R1 - Distill,它有47.4亿个参数。这个模型至少需要8 - 12GB的vRAM

数据准备策略

微调大语言模型需要结构化且特定任务的数据。数据准备策略有很多,比如从社交媒体平台、网站、书籍或研究论文中收集数据。在本文中,我们会使用datasets库来加载Hugging Face Hub上的数据。具体来说,我们会使用Hugging Face上的yahma/alpaca - cleaned数据集

Python实现步骤

安装所需包

使用谷歌Colab进行微调任务有个很大的好处,就是大多数包已经预装好了。我们只需要安装一个包,那就是unsloth。安装包的代码如下:

!pip install unsloth

初始化模型和分词器

我们将使用unsloth包来加载预训练模型,因为它提供了许多有用的技术,能帮助我们更快地下载和微调大语言模型。加载模型和分词器的代码如下:

from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit",
    max_seq_length = 2048,
    dtype = None,
    load_in_4bit = True
)

这里解释一下代码:

  • 我们指定了model_nameunsloth/DeepSeek - R1 - Distill - Llama - 8B - unsloth - bnb - 4bit,这是为了访问预训练的DeepSeek - R1 - Distill模型。
  • max_seq_length定义为2048,这设置了模型可以处理的输入序列的最大长度。合理设置这个值,有助于优化内存使用和处理速度。
  • dtype设置为None,这样能让模型根据可用硬件自动适配数据类型,我们就不用手动去检查和指定数据类型啦,unsloth会帮我们处理好一切。
  • load_in_4bit这个参数可以增强推理能力并减少内存使用,简单来说,就是将模型量化为4位精度。

添加LoRA适配器

我们要给预训练的大语言模型添加LoRA矩阵,这有助于微调模型的回答。使用unsloth,整个过程非常简单,只需要几行代码。具体实现如下:

model = FastLanguageModel.get_peft_model(
    model,
    r = 64,
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",],
    lora_alpha = 32,
    lora_dropout = 0.05, # Can be set to any, but = 0 is optimized
    bias = "none",    # Can be set to any, but = "none" is optimized
    use_gradient_checkpointing = "unsloth", # True or "unsloth" for very long context
    random_state = 3977,
    use_rslora = False,  # unsloth also supports rank stabilized LoRA
    loftq_config = None # And LoftQ
)

代码解释:

  • 我们使用FastLanguageModelget_peft_model方法重新初始化了模型,以便使用PEFT技术。
  • 这里需要传入之前获取的预训练模型。
  • r = 64这个参数定义了LoRA自适应中低秩矩阵的秩。通常这个秩在8 - 128范围内时能得到较好的效果。
  • lora_dropout = 0.05这个参数在训练LoRA适配器模型时,给低秩矩阵引入了随机失活(dropout)。它可以防止模型过拟合。
  • target_modules指定了我们想要应用LoRA自适应的模型中特定类或模块的名称列表。

数据准备

现在,我们已经在预训练的大语言模型上设置好了LoRA适配器,接下来就可以着手构建用于训练模型的数据了。为了构建数据,我们需要以特定的方式指定提示(prompt),使其包含输入、指令和响应。

  • 指令(Instructions):表示向大语言模型提出的主要问题。
  • 输入(Input):意味着除了指令或问题外,我们还传递了一些数据让模型进行分析。
  • 响应(Response):表示大语言模型的输出。它用于说明大语言模型的响应应该如何根据特定指令(问题)进行调整,无论是否传递了输入(数据)。

提示的结构如下:

alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
{}
### Input:
{}
### Response:
{}"""

我们创建了一个函数,用于将所有数据按照alpaca_prompt的格式进行正确构建,代码如下:

EOS_TOKEN = tokenizer.eos_token
def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    inputs       = examples["input"]
    outputs      = examples["output"]
    texts = []
    for instruction, input, output in zip(instructions, inputs, outputs):
        text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }

现在,我们要加载用于微调模型的数据集,在我们的例子中是yahma/alpaca - cleaned。代码如下:

from datasets import load_dataset
dataset = load_dataset("yahma/alpaca-cleaned", split = "train")
dataset = dataset.map(formatting_prompts_func, batched = True,)

训练模型

现在我们既有了结构化的数据,又有了带有LoRA适配器或矩阵的模型,接下来就可以开始训练模型啦。为了训练模型,我们需要初始化一些超参数,这些超参数不仅有助于训练过程,还会在一定程度上影响模型的准确性。

我们将使用SFTTrainer和超参数来初始化一个训练器,代码如下:

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,
    packing = False,
    args = TrainingArguments(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        max_steps = 120,
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        output_dir = "outputs",
        report_to = "none",
    )
)

现在,使用这个训练器来启动模型的训练,代码如下:

trainer_stats = trainer.train()

运行这段代码后,模型就会开始训练,并在控制台记录所有步骤以及相应的训练损失。

对微调后的模型进行推理

模型训练完成后,接下来要做的就是对微调后的模型进行推理,评估它的回答。进行推理的代码如下:

FastLanguageModel.for_inference(model)
inputs = tokenizer(
    alpaca_prompt.format(
        "Continue the fibonnaci sequence.",
        "1, 1, 2, 3, 5, 8",
        ""
    ), return_tensors = "pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens = 64, use_cache = True)
tokenizer.batch_decode(outputs)

代码解释:

  • 我们使用unsloth包中的FastLanguageModel来加载微调后的模型进行推理,这种方法能得到更快的结果。
  • 为了对模型进行推理,我们首先要将查询转换为结构化的提示,然后对提示进行分词。
  • 设置return_tensors = "pt"是为了让分词器返回一个PyTorch张量,然后使用.to("cuda")将这个张量加载到GPU上,以提高处理速度。
  • 接着调用model.generate()来生成查询的响应。
  • 在生成响应时,我们设置max_new_tokens = 64,这指定了模型应该生成的最大新令牌数。
  • use_cache = True也能加快生成速度,特别是对于较长的序列。
  • 最后,我们将微调后模型输出的张量解码为文本。

保存微调后的模型

这一步完成了模型微调的整个过程,现在我们可以保存微调后的模型,以便将来进行推理或使用。我们还需要将分词器和模型一起保存。下面是将微调后的模型保存到Hugging Face Hub的方法:

model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_4bit", token = "<YOUR_HF_TOKEN>")
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_16bit", token = "<YOUR_HF_TOKEN>")

这里需要注意:

  • 你需要设置模型的名称,这个名称将用于在Hugging Face Hub上设置模型的ID。
  • 可以选择上传4位精度或16位精度的完整合并模型。合并模型意味着将预训练模型和LoRA矩阵一起上传到Hugging Face Hub,当然也有其他选项,比如只上传LoRA矩阵而不上传模型。

总结

本文主要讨论了以下几个要点:

  • 大语言模型从最通俗的角度来说,就是深度学习架构(如Transformer)的精妙应用,通过大量的语言文本数据进行训练。
  • DeepSeek - R1 - Zero模型通过大规模强化学习(RL)训练,且没有经过监督微调(SFT)作为初步步骤,在推理方面表现出色。
  • 微调大语言模型就是为模型提供特定任务的数据,使其回答更符合特定用途,从而提高准确性,让回答更具针对性和领域专业性。
  • 我们使用的主要Python库和框架有unslothtorchtransformerstrl。此外,还讨论了微调大语言模型的计算要求。
  • 我们构建了用于有效微调模型的数据集,然后使用SFTTrainer对模型进行训练。

推荐阅读

1. DeepSeek-R1的顿悟时刻是如何出现的? 背后的数学原理
2. 微调 DeepSeek LLM:使用监督微调(SFT)与 Hugging Face 数据
3. 使用 DeepSeek-R1 等推理模型将 RAG 转换为 RAT
4. DeepSeek R1:了解GRPO和多阶段训练
5. 深度探索:DeepSeek-R1 如何从零开始训练
6. DeepSeek 发布 Janus Pro 7B 多模态模型,免费又强大!

本文由mdnice多平台发布


柏企科技圈
1 声望0 粉丝

时间差不多了,快上车!~