春节期间,AI 界热闹非凡,到处都是关于 DeepSeek 的报道。大家都知道,训练好的模型通常需要昂贵的专用 GPU,这对很多想试试微调技术的人来说,真是一道门槛。
好消息来了:你完全可以用免费的 Google Colab Notebook 来实现微调。借助 Colab 提供的免费 GPU,你可以把通用的 DeepSeek R1 模型"定制"为专门针对某个领域的模型,让它回答问题时既专业又贴合实际需求,而且操作简单、成本低。
如今,不仅是开发者,很多创业者也在关注 DeepSeek R1 模型,并尝试将它集成到自己的产品中。通过微调,你可以让模型用更符合特定领域特点的方式回答问题,充分利用 DeepSeek 强大的推理能力,让回答既清晰又有逻辑。
接下来给大家如何利用 Google Colab Notebook (可参考教程申请《Google Colab免费GPU使用教程》)这一免费、易用的资源,通过 Python 对 DeepSeek-R1 模型进行微调。你会学到如何用任意数据集训练模型,使其能更精准地回答专业领域的问题。
在开始微调之前,我们先看看需要准备哪些东西。
需要安装的 Python 库
微调 LLM 时,我们需要以下几个 Python 库:
- unsloth:这个库能让你微调 Llama-3、Mistral、Phi-4、Gemma 2x 这类大模型时更快,占用内存也少 70%,而且效果不会打折扣。
- torch:这是 PyTorch 的基础库。它可以进行高效的张量运算,类似于 NumPy,但还支持 GPU 加速,对处理大模型非常重要。
- transformers:这是一个很流行的自然语言处理库,里面有很多预训练模型。因为微调需要在预训练模型上做文章,这个库能帮你轻松调用各种模型。
- trl:这个库专门用来做基于 Transformer 的强化学习,是建立在 Hugging Face 的 transformers 库基础上的,让用强化学习训练模型变得简单高效。
计算资源要求
微调模型其实就是让模型回答问题时更加符合特定领域的要求,不需要从零开始训练所有参数。但是,大模型微调时所有需要训练的参数都要加载到 GPU 的内存(vRAM)里,所以对硬件要求很高。
为了让大家更容易体验,我们这次选用的模型是DeepSeek-R1-Distill(47.4 亿参数) 。这个模型大概需要 8~12GB 的 vRAM。好消息是,我们可以使用免费的 Google Colab 上的 T4 GPU,它有 16GB 的 vRAM,完全能满足我们的需求。
数据准备
微调模型时,需要有结构化、针对任务的数据。数据可以来自社交媒体、网站、书籍或者论文等多种渠道。
这篇文章中,我们将使用datasets
库,从Hugging Face Hub 上下载数据。具体来说,我们用的是yahma/alpaca-cleaned
数据集。
代码实现
安装所需的包
使用 Google Colab 进行微调有个很大的好处:大部分包已经预装好了,我们只需要额外安装一个包 ------unsloth
。 安装方法非常简单,在 Colab 中运行下面的命令即可:
diff
!pip install unsloth
初始化模型和分词器
我们使用unsloth
这个包来加载预训练模型,因为它提供了很多方便的技术,能让我们更快地下载和微调大型语言模型。 下面这段代码用于加载模型和分词器:
ini
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,
# token = "hf_...", # 如果使用 gated 模型,比如 meta-llama/Llama-2-7b-hf,可以传入 token
)
这里解释下各个参数的意义:
- model_name :指定我们要加载的预训练模型名称,这里是
unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit
,表示我们使用的是 DeepSeek-R1-Distill 模型。 - max_seq_length:设置模型能处理的最大输入序列长度,这里设为 2048,这样既能满足大部分需求,又能合理利用内存和提升速度。
- dtype :设置为
None
,这样可以自动适配当前硬件的数据类型,无需我们额外操心。 - load_in_4bit:将模型量化为 4bit 精度,这样可以在保证效果的同时,大幅减少内存占用,加快推理速度。
添加 LoRA 适配器
接下来,我们要给预训练模型添加 LoRA 矩阵,这样可以帮助模型在微调时更好地适应特定任务。使用unsloth
进行这一过程非常简单,只需几行代码:
ini
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, # 设置 dropout 防止过拟合,0.05 是个不错的选择
bias = "none", # bias 参数这里选择 "none" 是最优设置
use_gradient_checkpointing = "unsloth", # 对于长序列,使用这个可以节省内存
random_state = 3977,
use_rslora = False, # unsloth 也支持 rank stabilized LoRA
loftq_config = None, # LoftQ 配置,如果没有特殊需求可以设置为 None
)
这里简单说明一下关键参数的作用:
- r:设置 LoRA 中低秩矩阵的秩,这里取 64,一般 8 到 128 之间都可以选择,64 常常能取得不错的效果。
- lora_dropout:在训练 LoRA 适配器时加入 dropout,有助于防止模型过拟合。
- target_modules:指定模型中需要应用 LoRA 的模块列表,这里列出了多个常见的投影模块。
数据准备
当我们完成了模型的适配设置之后,就需要准备数据来进行微调。数据需要按照一定的格式组织好,其中包含输入、指令和期望的输出。
我们通常将数据分为三部分:
- Instruction(指令) :这是向模型提出的问题或任务描述。
- Input(输入) :如果有额外数据需要提供,这里可以传入补充信息。
- Response(回答) :这就是我们期望模型输出的内容,它需要与指令(和输入)相匹配。
我们先定义一个模板,用来组织这些信息:
ini
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:
{}"""
接着,我们写一个函数,利用这个模板对数据进行格式化。注意,我们在每条文本后面都要加上 EOS(结束符),否则生成的内容可能会无限延长:
ini
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):
# 这里一定要加上 EOS_TOKEN,否则生成内容可能不会停止
text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
texts.append(text)
return { "text": texts, }
最后,我们加载用于微调的数据集,这里我们选用的是 Hugging Face Hub 上的 "yahma/alpaca-cleaned"。运行下面的代码即可:
ini
from datasets import load_dataset
dataset = load_dataset("yahma/alpaca-cleaned", split="train")
dataset = dataset.map(formatting_prompts_func, batched=True)
模型训练
既然我们已经准备好了格式化后的数据,也把 LoRA 适配器加载到模型上了,那接下来就可以开始训练了。
训练前,我们需要先设置一些超参数,这些参数会影响训练的过程和模型的准确度。
我们使用SFTTrainer
来初始化训练器,并传入相关的超参数。下面这段代码展示了如何完成初始化:
ini
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 = max_seq_length, # 模型能处理的最大输入序列长度
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, # 如果需要完整训练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 等工具可修改此项
),
)
初始化完成后,我们只需调用训练命令即可开始训练:
ini
trainer_stats = trainer.train()
这样训练过程就会开始运行,训练过程中在 Colab 的日志中会显示每一步的训练损失。
模型推理
当训练结束后,我们可以利用微调后的模型进行推理,看看它的回答效果如何。下面这段代码演示了如何使用微调后的模型进行推理:
ini
FastLanguageModel.for_inference(model) # 开启原生 2 倍加速推理
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
,目的是加载已经微调好的模型用于推理。这种方式可以让推理速度更快。 - 在进行推理前,首先需要将查询内容(也就是问题)按照预先设定的格式组织成一个结构化的文本(prompt),然后使用分词器将这个 prompt 转换为模型可以识别的格式。
- 为了让分词器输出 PyTorch 的张量,我们设置了参数
return_tensors="pt"
。接着,用.to("cuda")
把张量加载到 GPU 上,这样处理速度会大大提高。 - 接下来,我们调用
model.generate()
方法生成回答。 - 在生成回答时,我们设置了
max_new_tokens=64
,这表示模型最多生成 64 个新 token。 - 同时,参数
use_cache=True
可以加速生成过程,特别是在生成长序列时效果更明显。 - 最后,我们使用分词器的
batch_decode()
方法将生成的张量结果解码成我们能看懂的文本。
保存微调模型
训练完毕后,最后一步就是保存我们的微调模型,以便日后使用或部署。记得同时保存模型和分词器。下面提供了两种保存方式,分别对应 4bit 和 16bit 精度:
ini
# 以 4bit 精度保存
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method="merged_4bit", token="<YOUR_HF_TOKEN>")
# 以 16bit 精度保存
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method="merged_16bit", token="<YOUR_HF_TOKEN>")
这里只需将<YOUR_HF_ID>
、<MODEL_NAME>
和<YOUR_HF_TOKEN>
替换成你在 Hugging Face 的相关信息即可。