沒有數據、沒有GPU的情況下怎么訓練DeepSeek
春節(jié)期間,AI 界熱鬧非凡,到處都是關于 DeepSeek 的報道。大家都知道,訓練好的模型通常需要昂貴的專用 GPU,這對很多想試試微調技術的人來說,真是一道門檻。
好消息來了:你完全可以用免費的 Google Colab Notebook 來實現微調。借助 Colab 提供的免費 GPU,你可以把通用的 DeepSeek R1 模型“定制”為專門針對某個領域的模型,讓它回答問題時既專業(yè)又貼合實際需求,而且操作簡單、成本低。
如今,不僅是開發(fā)者,很多創(chuàng)業(yè)者也在關注 DeepSeek R1 模型,并嘗試將它集成到自己的產品中。通過微調,你可以讓模型用更符合特定領域特點的方式回答問題,充分利用 DeepSeek 強大的推理能力,讓回答既清晰又有邏輯。
接下來給大家如何利用 Google Colab Notebook (可參考教程申請《Google Colab免費GPU使用教程》)這一免費、易用的資源,通過 Python 對 DeepSeek-R1 模型進行微調。你會學到如何用任意數據集訓練模型,使其能更精準地回答專業(yè)領域的問題。
在開始微調之前,我們先看看需要準備哪些東西。
需要安裝的 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 中運行下面的命令即可:
!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,
# 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進行這一過程非常簡單,只需幾行代碼:
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" 是最優(yōu)設置
use_gradient_checkpointing = "unsloth", # 對于長序列,使用這個可以節(jié)省內存
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(回答):這就是我們期望模型輸出的內容,它需要與指令(和輸入)相匹配。
我們先定義一個模板,用來組織這些信息:
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(結束符),否則生成的內容可能會無限延長:
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”。運行下面的代碼即可:
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, # 已經添加了 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", # 更新權重時使用的優(yōu)化器
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 3407,
output_dir = "outputs",
report_to = "none", # 如需使用 WandB 等工具可修改此項
),
)
初始化完成后,我們只需調用訓練命令即可開始訓練:
trainer_stats = trainer.train()
這樣訓練過程就會開始運行,訓練過程中在 Colab 的日志中會顯示每一步的訓練損失。
模型推理
當訓練結束后,我們可以利用微調后的模型進行推理,看看它的回答效果如何。下面這段代碼演示了如何使用微調后的模型進行推理:
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 精度:
# 以 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 的相關信息即可。