DeepSeek通过发布其开源推理模型DeepSeek-R1,彻底改变了AI领域的格局。DeepSeek R1引入了一种全新的LLM训练方式,并在这些模型在思考和执行一系列推理后的回答方式。该模型使用创新的强化学习技术,以较低的成本实现了与OpenAI的o1相当的性能。
DeepSeek已将其推理能力提炼到几个较小的模型中,基于DeepSeek-V3-Base(总参数671B,每次推理激活37B),DeepSeek-R1使用强化学习(RL)生成思维链
(CoT),然后给出最终答案。为了使这些功能更易于使用,DeepSeek已将其R1输出提炼为几个较小的模型:
DeepSeek-R1因其性能、可访问性
和成本效益的结合,在AI社区中迅速获得关注。以下是它成为开发者和研究人员首选的原因:
虽然DeepSeek-R1和OpenAI的O3-Mini-High推理模型都是为高级问题解决而设计的,但它们有显著的不同:
开源与专有:
成本和可访问性:
性能和效率:
社区和生态系统支持:
这些差异使DeepSeek-R1成为一个有吸引力的替代方案,可以在没有专有限制的情况下实现高推理性能。
微调LLM所需的Python库和框架有:
unsloth
,这个包使得像Llama-3、Mistral、Phi-4和Gemma这样的大语言模型是一个强大且流行的开源自然语言处理
trl
包是一个专门用于transformer模型的强化学习(RL)库。它建立在Hugging Face的transformers
库之上,利用其优势使transformer的RL更容易访问和高效。微调模型
是一种使LLM的响应更加结构化和领域特定的技术。有许多技术被采用来微调模型,有些技术在不实际执行完整参数训练
的情况下就能促进这个过程。
然而,对于大多数普通计算机硬件来说,微调更大的LLM的过程仍然不可行,因为所有可训练的参数以及实际的LLM都存储在GPU的vRAM(虚拟RAM)中,而LLM的巨大规模在实现这一点时构成了主要障碍。
因此,在本文中,我们将微调DeepSeek-R1 LLM的精炼版本,即具有47.4亿参数的DeepSeek-R1-Distill
。这个LLM至少需要8-12 GB的vRAM,为了让所有人都能使用,我们这里使用T4 GPU,它有16 GB的vRAM。
要微调LLM,我们需要结构化和任务特定的数据。有许多数据准备策略,可以是抓取社交媒体平台、网站、书籍或研究论文。
对于本文,我们将使用datasets库来加载Hugging Face Hub中的数据。我们将使用Hugging Face中的HumanLLMs/Human-Like-DPO-Dataset
数据集,您可以在这里探索该数据集。
pip install unsloth
pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git
我们将使用unsloth
包来加载预训练模型,因为它提供了许多有用的技术,可以帮助我们更快地下载和微调LLM。
加载模型和分词器的代码是:
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit",
# model_name = "unsloth/DeepSeek-R1-Distill-Qwen-14B",
max_seq_length = 2048,
dtype = None,
load_in_4bit = True,
# token = "hf_...", # 如果使用如meta-llama
/Llama-2-7b-hf这样的受限模型时使用
)
'unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit'
,用于访问预训练的DeepSeek-R1-Distill模型。max_seq_length
设置为2048
,这设置了模型可以处理的输入序列的最大长度。通过合理设置,我们可以优化内存使用和处理速度。dtype
None
,这有助于将模型获取的数据类型映射到与可用硬件兼容的类型。通过使用这个,我们不必显式检查和提及数据类型,unsloth
会处理所有这些。load_in_4bit
增强推理并减少内存使用。基本上,我们将模型量化为4bit
精度。我们将向预训练的LLM添加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 = 16,
lora_dropout = 0, # 可以设置为任何值,但 = 0 是优化的
bias = "none", # 可以设置为任何值,但 = "none" 是优化的
use_gradient_checkpointing = "unsloth", # True或"unsloth"用于很长的上下文
random_state = 3927,
use_rslora = False, # unsloth也支持rank stabilized LoRA
loftq_config = None, # 和LoftQ
)
代码说明:
FastLanguageModel
中的get_peft_model
引入dropout。这个参数防止模型过拟合
unsloth
通过将其设置为0
为我们提供了自动选择优化值的功能。target_modules
指定了我们想要应用LoRA适应的模型内特定类或模块的名称列表。现在,我们已经在预训练的LLM上设置了LoRA适配器,我们可以继续构建将用于训练模型的数据。
要构建数据,我们必须以包含指令和响应的方式指定提示。
Instructions
表示对LLM的主要查询。这是我们向LLM提出的问题。Response
表示来自LLM的输出。它用于说明LLM对特定instruction
(查询)的响应应该如何定制。提示的结构是:
human_prompt = """Below is an instruction that describes a task. Write a response that appropriately completes the request.
### Instruction:
{}
### Response:
{}"""
我们创建了一个函数,它将正确地在human_prompt
中构建所有数据:
EOS_TOKEN = tokenizer.eos_token # 必须添加EOS_TOKEN
def formatting_human_prompts_func(examples):
instructions = examples["prompt"]
outputs = examples["chosen"]
texts = []
for instruction, output in zip(instructions, outputs):
# 必须添加EOS_TOKEN,否则您的生成将永远继续!
text = human_prompt.format(instruction, output) + EOS_TOKEN
texts.append(text)
return { "text" : texts, }
pass
现在,我们必须加载将用于微调模型的数据集,在我们的例子中是来自Hugging Face Hub的"HumanLLMs/Human-Like-DPO-Dataset"。您可以在这里探索该数据集。
from datasets import load_dataset
dataset = load_dataset
("HumanLLMs/Human-Like-DPO-Dataset", split = "train")
dataset = dataset.map(formatting_human_prompts_func, batched = True,)
现在我们同时拥有结构化数据
和带有LoRA适配器的模型,我们可以继续训练模型。
要训练模型,我们必须初始化某些超参数
,这些超参数将促进训练过程,并在某种程度上影响模型的准确性。
我们将使用SFTTrainer
和超参数初始化一个trainer
。
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model = model, # 带有LoRA适配器的模型
tokenizer
= tokenizer, # 模型的分词器
train_dataset = dataset, # 用于训练的数据集
dataset_text_field = "text", # 数据集中包含结构化数据的字段
max_seq_length = 2048, # 模型可以处理的输入序列的最大长度
dataset_num_proc = 2, # 用于加载和处理数据的进程数
packing = False, # 对于短序列可以使训练速度提高5倍
args = TrainingArguments(
per_device_train_batch_size = 2, # 每个GPU的批量大小
gradient_accumulation_steps = 4, # 梯度累积的步长
warmup_steps = 5,
# num_train_epochs
= 1, # 设置这个进行一次完整训练运行
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", # 用于WandB等
),
)
现在使用这个trainer开始训练模型:
trainer_stats = trainer.train()
这将开始训练模型,并在内核上记录所有步骤及其各自的训练损失。
现在,我们已经完成了模型的训练,我们要做的就是对微调模型进行推理以评估其响应。
对模型进行推理的代码是:
FastLanguageModel.for_inference(model) # Enable native 2x faster inference
inputs = tokenizer(
[
human_prompt.format(
"Oh, I just saw the best meme - have you seen it?", # instruction
"", # output - leave this blank for generation!
)
], return_tensors
= "pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens = 1024, use_cache = True)
tokenizer.batch_decode(outputs)
代码说明:
unsloth
包中的FastLanguageModel
来加载微调模型进行推理。这种方法产生更快的结果。return_tensors="pt"
以使分词器返回PyTorch张量.to("cuda")
将该张量加载到GPU上以提高处理速度。model.generate()
为查询生成响应。max_new_tokens=1024
,它指定了模型可以生成的最大token数。use_cache=True
有助于加速生成,特别是对于较长的序列。微调结果演示
使用相同的提示结构测试您的微调模型。
question = "A contract was signed between two parties, but one party claims they were under duress. What legal principles apply to determine the contract’s validity
?"
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])
说明: *输出应该包含简明的思维链和清晰的最终答案。
本节包含微调模型的一些其他结果。
Query — 1: I love reading and writing, what are your hobbies?
Query — 2: What’s your favourite type of cuisine to cook or eat?
在这里,可以注意到生成的响应中的表达水平。响应在保持实际质量的同时更加引人入胜。
new_model_local = "DeepSeek-R1-Legal-COT"
model.save_pretrained(new_model_local)
tokenizer.save_pretrained(new_model_local)
model.save_pretrained_merged(new_model_local, tokenizer, save_method="merged_16bit")
这一步完成了模型微调的整个过程,现在我们可以保存微调模型以进行推理或将来使用。
我们还需要将分词器与模型一起保存。以下是在Hugging Face Hub上保存微调模型的方法。
# 以4位精度推送
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_4bit", token = "<YOUR_HF_TOKEN>")
# 以16位精度推送
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_16bit", token = "<YOUR_HF_TOKEN>")
ID
。4bit
或16bit
精度上传完整的合并模型。合并模型表示预训练模型和LoRA矩阵一起上传到hub,而有选项可以只推送LoRA矩阵而不是模型。要使用Ollama部署您的微调模型DeepSeek-R1-Legal-COT,请按照以下步骤操作:
准备模型文件
创建Modelfile
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。