原文鏈接:從零開始的DeepSeek微調訓練實戰(SFT)
微調參考示例:由unsloth官方提供https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynbhttps://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynb
本文使用modelscope社區提供的免費GPU示例進行復現。
魔搭社區匯聚各領域最先進的機器學習模型,提供模型探索體驗、推理、訓練、部署和應用的一站式服務。https://www.modelscope.cn/my/overview
基礎概念
預訓練模型 (Pre-trained Model): 預訓練模型是指在大規模數據集上(如Wikipedia、書籍、網頁等)進行過訓練的模型。這些模型學習到了通用的語言知識和模式。你可以把它們想象成已經掌握了基本語法和常識的“學生”。常見的預訓練模型有BERT、GPT、Llama、DeepSeek等。
微調 (Fine-tuning): 微調是指在預訓練模型的基礎上,使用特定任務的數據集繼續訓練模型,使其適應特定任務或領域。就像讓一個已經掌握基本知識的學生,學習特定專業的知識。
為什么需要微調?: 預訓練模型雖然強大,但它們是通用的。對于特定任務(如醫療問答、代碼生成、情感分析等),預訓練模型可能表現不佳。微調可以讓模型更好地適應特定任務,提高性能。
SFT (Supervised Fine-Tuning): SFT是一種微調方法,它使用帶有標簽的數據集進行訓練。例如,在醫療問答任務中,數據集會包含問題和對應的正確答案。模型通過學習這些問題和答案之間的關系,來提高在特定任務上的表現。
SFT vs. RLHF:
- SFT (Supervised Fine-tuning): 使用標注好的數據集進行訓練。模型學習輸入和輸出之間的直接映射。簡單高效,但依賴于高質量的標注數據。
- RLHF (Reinforcement Learning from Human Feedback): 通過人類反饋來訓練模型。首先使用SFT,然后通過人類對模型輸出進行打分,并使用強化學習算法來優化模型。可以更好地捕捉人類偏好,但更復雜,成本更高。
- 總結: SFT是基礎,RLHF是進階。通常先進行SFT,再根據需要進行RLHF。
高效微調 (Efficient Fine-tuning): 高效微調是指在有限的計算資源下,對大型模型進行微調的方法。例如,LoRA(Low-Rank Adaptation)只微調模型中的部分參數,從而減少計算量和內存需求。
環境準備
unsloth
-
Unsloth 是什么?
Unsloth 是一個專為大型語言模型(LLM)微調和推理設計的框架。它的主要目標是提高訓練速度和降低內存消耗,讓用戶能夠在有限的硬件資源上更高效地進行 LLM 的操作。
-
Unsloth 的主要特點
-
速度快:Unsloth 通過各種優化技術(如 Flash Attention、量化等)顯著提高了 LLM 的訓練和推理速度。在某些情況下,速度提升可達數倍。
-
內存占用低:Unsloth 通過優化內存使用,使得在較小顯存的 GPU 上也能微調大型模型。
-
易于使用:Unsloth 提供了簡潔的 API,方便用戶快速上手。
-
支持多種模型:Unsloth 支持多種流行的 LLM,如 Llama、Mistral、Phi、Qwen 等。
-
-
Unsloth 的安裝
-
直接使用pip命令安裝即可
-
pip install unsloth pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git
-
WandB (Weights & Biases) 安裝?
-
WandB 是什么?
WandB 是一個用于機器學習實驗跟蹤、可視化和協作的平臺。它可以幫助你記錄實驗的各種指標、超參數、模型權重、數據集等,并提供交互式的可視化界面,方便你分析實驗結果和比較不同實驗的表現。
-
WandB 的主要特點
-
實驗跟蹤:記錄實驗的各種指標(如損失、準確率、學習率等)、超參數、代碼版本、數據集等。
-
可視化:提供交互式的圖表,方便你分析實驗結果。
-
協作:支持多人協作,方便團隊成員共享實驗結果和討論。
-
超參數優化:支持自動超參數搜索,幫助你找到最佳的超參數組合。
-
模型管理:可以保存和版本控制模型權重。
-
報告生成:可以自動生成實驗報告。
-
-
WandB 的安裝和注冊
-
安裝:使用?pip?安裝 WandB:
-
pip install wandb
- 注冊:
-
訪問 WandB 官網(https://wandb.ai/site)并注冊賬號。
-
注冊后,在你的個人設置頁面找到 API Key,復制它。
-
WandB 在環境準備中的作用
在 SFT 環境準備中,WandB 主要用于:
-
監控訓練過程:在訓練過程中,WandB 會自動記錄各種指標,如損失、學習率等,并提供實時更新的圖表。
-
記錄超參數:WandB 會記錄你使用的超參數,方便你后續復現實驗和比較不同超參數的效果。
-
保存模型:你可以使用 WandB 保存訓練過程中的模型權重,方便后續加載和使用。
-
分析實驗結果:WandB 提供了豐富的可視化工具,可以幫助你分析實驗結果,找出最佳的模型和超參數。
-
-
-
模型下載
ModelScope模型地址:https://www.modelscope.cn/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
???????創建DeepSeek-R1-Distill-Qwen-7B文件夾,用于保存下載的模型權重:
-
mkdir ./DeepSeek-R1-Distill-Qwen-7B
? ? ? ?創建成功后,可使用如下命令下載模型:
-
modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --local_dir ./DeepSeek-R1-Distill-Qwen-7B
- 模型權重文件??
- config.json
- 內容:?這個文件包含了模型的配置信息,它是一個 JSON 格式的文本文件。這些配置信息定義了模型的架構、層數、隱藏層大小、注意力頭數等。
- 重要性:?這是模型的核心配置文件,加載模型時會讀取這個文件來構建模型的結構。
{"architectures": ["Qwen2ForCausalLM" // 指定模型的架構類型為 Qwen2ForCausalLM,這是一個用于因果語言建模(生成文本)的 Qwen2 模型。],"attention_dropout": 0.0, // 在注意力機制中使用的 dropout 比率。設置為 0.0 表示不使用 dropout。Dropout 是一種正則化技術,用于防止過擬合。"bos_token_id": 151646, // 句子開頭標記(Beginning of Sentence)的 ID。在分詞器中,每個詞或標記都有一個唯一的 ID。"eos_token_id": 151643, // 句子結束標記(End of Sentence)的 ID。"hidden_act": "silu", // 隱藏層的激活函數。SiLU(Sigmoid Linear Unit)是一種激活函數。"hidden_size": 3584, // 隱藏層的大小(維度)。"initializer_range": 0.02, // 用于初始化模型權重的標準差。"intermediate_size": 18944, // 前饋網絡(Feed-Forward Network)中間層的大小。"max_position_embeddings": 131072, // 模型可以處理的最大序列長度(位置嵌入的數量)。"model_type": "qwen2", // 模型類型為 qwen2。"num_attention_heads": 28, // 注意力機制中注意力頭的數量。"num_hidden_layers": 28, // 模型中隱藏層(Transformer 層)的數量。"num_key_value_heads": 4, // 鍵值頭的數量。用于分組查詢注意力(Grouped-Query Attention, GQA)。如果該值小于`num_attention_heads`,則表示啟用了GQA, 否則為多頭注意力(Multi-Head Attention, MHA)。"rms_norm_eps": 1e-06, // RMSNorm(Root Mean Square Layer Normalization)中使用的 epsilon 值,用于防止除以零。"rope_theta": 10000.0, // RoPE(Rotary Positional Embeddings)中使用的 theta 值。RoPE 是一種位置編碼方法。"tie_word_embeddings": false, // 是否將詞嵌入矩陣和輸出層的權重矩陣綁定(共享)。設置為 `false` 表示不綁定。"torch_dtype": "bfloat16", // 模型使用的默認數據類型。`bfloat16` 是一種 16 位浮點數格式,可以提高計算效率并減少內存占用。"transformers_version": "4.48.3", // 使用的 Transformers 庫的版本。"use_cache": true, // 是否使用緩存機制來加速推理。設置為 `true` 表示使用緩存。"vocab_size": 152064 // 詞匯表的大小(不同詞或標記的數量)。
}
- configuration.json?
- 內容:?這個文件和?config.json?類似,通常包含模型的配置信息。在某些模型中,這兩個文件可能是同一個文件,或者?configuration.json?包含了更詳細的配置。對于 DeepSeek-R1-Distill-7B 模型,你可以認為它和?config.json?作用相同。
- generation_config.json
- 內容:?這個文件包含模型生成文本時的配置參數,例如解碼方法(beam search、top-k sampling 等)、最大生成長度、溫度系數等。
{"_from_model_config": true, // 表示這些配置中的大部分是從模型的配置文件(config.json)中繼承的。"bos_token_id": 151646, // 句子開頭標記(Beginning of Sentence)的 ID。"eos_token_id": 151643, // 句子結束標記(End of Sentence)的 ID。"do_sample": true, // 是否使用采樣(sampling)方法生成文本。如果設置為 `false`,則使用貪婪解碼(greedy decoding)。"temperature": 0.6, // 溫度系數。溫度系數用于控制生成文本的隨機性。值越高,生成的文本越隨機;值越低,生成的文本越確定。"top_p": 0.95, // Top-p 采樣(nucleus sampling)的閾值。Top-p 采樣只從概率最高的、累積概率超過 `top_p` 的詞中進行采樣。"transformers_version": "4.39.3" // 使用的 Transformers 庫的版本。(原文檔中是4.39.3,這與之前config.json里的版本號不同,但通常情況下,版本號應當以config.json里的為準)
}
-
LICENSE
內容:?這是一個文本文件,包含了模型的許可證信息。許可證規定了你可以如何使用、修改和分發模型。 -
model-00001-of-00002.safetensors?和?model-00002-of-00002.safetensors
內容:?這些文件是模型權重文件,它們以 Safetensors 格式存儲。Safetensors 是一種安全且高效的張量存儲格式。由于模型很大,權重被分成了多個文件。 -
model.safetensors.index.json
內容:?這是一個索引文件,用于指示哪些權重存儲在哪個?.safetensors?文件中。當模型權重被分成多個文件時,需要這個索引文件來正確加載權重。 -
README.md
內容:?這是一個 Markdown 格式的文本文件,通常包含模型的介紹、使用說明、示例代碼等。 -
tokenizer_config.json
內容:?包含分詞器(Tokenizer)的配置信息。
{"add_bos_token": true, // 是否在輸入序列的開頭添加句子開頭標記(BOS token)。設置為 `true` 表示添加。"add_eos_token": false, // 是否在輸入序列的結尾添加句子結束標記(EOS token)。設置為 `false` 表示不添加。"__type": "AddedToken", //這是一個內部使用的類型標記,表示這是一個"添加的token""content": "<|begin of sentence|>", // 這是一個特殊標記的內容,表示句子的開始"lstrip": false, //在處理這個token時,是否移除左側的空白符"normalized": true, // 是否對這個token進行標準化處理"rstrip": false, //是否移除右邊的空白符"single_word": false, // 是否將此token視為單個詞"clean_up_tokenization_spaces": false, // 是否清理分詞過程中的空格。設置為 `false` 表示不清理。"__type": "AddedToken","content": "<|end of sentence|>","lstrip": false,"normalized": true,"rstrip": false,"single_word": false,"legacy": true, // 是否使用舊版(legacy)的分詞器行為。這里設置為`true`可能表示兼容舊版本。"model_max_length": 16384, // 模型可以處理的最大序列長度。"__type": "AddedToken","content": "<|end of sentence|>","lstrip": false,"normalized": true,"rstrip": false,"single_word": false,"sp_model_kwargs": {}, // SentencePiece 模型的相關參數(這里為空)。"unk_token": null, // 未知詞標記(unknown token)。設置為 `null` 表示沒有專門的未知詞標記。"tokenizer_class": "LlamaTokenizerFast", // 分詞器的類名。`LlamaTokenizerFast` 表示這是一個快速版本的 Llama 分詞器。"chat_template": "{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt='') %}{%- for message in messages %}{%- if message['role'] == 'system' %}{% set ns.system_prompt = message['content'] %}{%- endif %}{%- endfor %}{{bos_token}}{{ns.system_prompt}}{%- for message in messages %}{%- if message['role'] == 'user' %}{%- set ns.is_tool = false -%}{{'<|User|>' + message['content']}}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is none %}{%- set ns.is_tool = false -%}{%- for tool in message['tool_calls']%}{%- if not ns.is_first %}{{'<|Assistant|><|tool calls begin|><|tool call begin|>' + tool['type'] + '<|tool sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool call end|>'}}{%- set ns.is_first = true -%}{%- else %}{{'\n' + '<|tool call begin|>' + tool['type'] + '<|tool sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool call end|>'}}{{'<|tool calls end|><|end of sentence|>'}}{%- endif %}{%- endfor %}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is not none %}{%- if ns.is_tool %}{{'<|tool outputs end|>' + message['content'] + '<|end of sentence|>'}}{%- set ns.is_tool = false -%}{%- else %}{% set content = message['content'] %}{% if '</think>' in content %}{% set content = content.split('</think>')[-1] %}{% endif %}{{'<|Assistant|>' + content + '<|end of sentence|>'}}{%- endif %}{%- endif %}{%- if message['role'] == 'tool' %}{%- set ns.is_tool = true -%}{%- if ns.is_output_first %}{{'<|tool outputs begin|><|tool output begin|>' + message['content'] + '<|tool output end|>'}}{%- set ns.is_output_first = false %}{%- else %}{{'\n<|tool output begin|>' + message['content'] + '<|tool output end|>'}}{%- endif %}{%- endif %}{%- endfor -%}{% if ns.is_tool %}{{'<|tool outputs end|>'}}{% endif %}{% if add_generation_prompt and not ns.is_tool %}{{'<|Assistant|><think>\n'}}{% endif %}"// ↑這是一個 Jinja2 模板,定義了對話的格式。它根據消息的角色(用戶、助手、工具)和內容,構建最終的輸入文本。}
數據集準備
推理模型與通用模型相比,輸出的回答包括了一段思考過程(Chain of Thoughts 思維鏈)。這個思考過程本質也是通過預測下一個token進行實現的,只不過DeepSeek系列模型輸出時,會將思考過程放在一對特殊token <think>...</think>之間,</think>標簽后的內容作為回答的正文。
微調推理模型時,同樣需要包含思維鏈和最終回答兩部份。因此,在圍繞DeepSeek R1 Distill模型組進行微調的時候,微調數據集的回復部分文本也需要是包含推理 和最終回復兩部分內容,才能使得DeepSeek R1模型組在保持既定回復風格的同時,強化模型能力,反之則會導致指令消融問題(模型回復不再包含think部分)。
modelscope社區提供了多樣的推理數據集供開發者使用 。
原文采取由深圳大數據研究院發布的HuatuoGPT-o1模型的微調數據集—medical-o1-reasoning-SFT,地址:https://www.modelscope.cn/datasets/AI-ModelScope/medical-o1-reasoning-SFT。
本數據集將數據分為:
- Question:醫療問題
- Complex_CoT:進行診療的思維鏈
- Response:最終的答復
數據集中所有內容均為英文
模型演示
在進行微調前,我們可以了解一下模型的基本用法。
1. 加載已經下載到本地的模型
max_seq_length = 2048 # 指定輸出的最大長度
dtype = None # 不指定模型精度,由unsloth框架自動檢測
load_in_4bit = False # 采用int4量化,減少顯存占用,但是會降低模型性能# 加載模型和分詞器
model, tokenizer = FastLanguageModel.from_pretrained(model_name="./DeepSeek-R1-Distill-Qwen-7B", # 待微調的模型名稱max_seq_length=max_seq_length, # 模型可以處理的最長序列長度dtype=dtype, # 限定模型浮點精度load_in_4bit=False # 是否使用int量化
)
2. 通過unsloth框架配置待微調的LoRA模型
'''
LoRA 的核心思想是,對于預訓練模型的權重矩陣 W,不直接對其進行更新,
而是添加一個低秩分解矩陣 ΔW = A * B,
其中 A 和 B 是兩個較小的矩陣。在微調過程中,只更新 A 和 B 的參數,而 W 的參數保持不變。
這樣可以大大減少需要微調的參數數量,降低計算成本。
'''
model = FastLanguageModel.get_peft_model(model,r=8, # lora微調的秩 # 較小的 `r` 值會減少需要微調的參數數量,降低計算成本,但也可能降低模型的表達能力。# 較大的 `r` 值會增加參數數量,提高模型的表達能力,但也會增加計算成本。# 通常需要根據實際情況進行實驗,選擇合適的 `r` 值。一般來說,8、16、32、64 是常用的值。target_modules = ["q_proj", "k_proj", "v_proj", # 指定要應用 LoRA 的模塊。這些模塊通常是 Transformer 模型中的線性層。"o_proj", "gate_proj", "up_proj", "down_proj"], # 這里分別應用了注意力機制中的Wq, Wk, Wv, Wo線性投影層,FFN中的線性層lora_alpha=8, # lora縮放因子,決定模型權重的更新程度,建議設置為r或r的倍數lora_dropout=0,bias="none", # 不為LoRA層添加偏置use_gradient_checkpointing="unsloth", # 是否設置梯度檢查點,# 梯度檢查點是一種以時間換空間的技術,可以減少內存占用,但會增加計算時間。random_state=3407, # 設置隨機種子,保證實驗可以浮現use_rslora=False, # 是否使用Rank-Stabilized LoRA(rslora)。rslora 是一種改進的 LoRA 方法,可以自動調整 `lora_alpha`。loftq_config=None # 是否使用QLoRA,即將LoRA與量化技術結合
)
3. 進行簡單推理
# 將模型切換為推理模式,可以進行簡單的對話
FastLanguageModel.for_inference(model)question = "請介紹一下你自己!"
# 對輸入進行分詞
# 傳入待分詞的文本列表,最后返回一個PyTorch張量
input_ids = tokenizer([question], return_tensors="pt").to("cuda")
# input_ids返回token對應詞表中的id,即將一個句子映射為一個token id序列
# attention_mask用于表示input_ids中哪些是為了填充序列長度而通過<pad>填充的token,1表示所有的 token 都是實際的詞或標記,沒有填充。
input_ids# 調用模型生成答復
'''
是否使用緩存機制來加速生成過程。設置為 True 表示使用緩存。緩存機制會存儲先前計算的鍵/值對(key/value pairs),避免重復計算,從而提高生成速度。在自回歸生成(逐個 token 生成)中,緩存機制非常有用。
'''
outputs_ids = model.generate(input_ids=input_ids.input_ids,max_new_tokens=1024,use_cache=True
)
# 模型的直接輸出同樣為token ids,需要通過tokenizer進行解碼
outputs_idsresponse = tokenizer.batch_decode(outputs_ids)
print(response[0])
可以在prompt中添加<think>標簽對引導模型進行思考。
question = "你好,好久不見!"
# 更完善的prompt
prompt_style_chat = """請寫出一個恰當的回答來完成當前對話任務。### Instruction:
你是一名助人為樂的助手。### Question:
{}### Response:
"""# 使用tokenizer處理prompt
input_ids = tokenizer([prompt_style_chat.format(questionm '')], return_tensors="pt").to("cuda")outputs = model.generate(input_ids=input_ids.input_ids,use_cache=True,do_sample=True, # 啟用采樣temperature=0.7, # 較高的溫度top_p=0.9, # Top-p 采樣repetition_penalty=1.2, # 重復懲罰max_new_tokens=1024, # 最大新token數量
)response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
print(response)
未添加<think>標簽,模型有概率不思考。
prompt_style_chat = """請寫出一個恰當的回答來完成當前對話任務。### Instruction:
你是一名助人為樂的助手。### Question:
{}### Response:
<think>{}
"""
question = "請你分析李朗笛和朗朗以及李云迪之間的關系"# 使用tokenizer處理prompt
input_ids = tokenizer([prompt_style_chat.format(question, "")], return_tensors="pt").to("cuda")outputs = model.generate(input_ids=input_ids.input_ids,use_cache=True,do_sample=True, # 啟用采樣temperature=0.7, # 較高的溫度top_p=0.95, # Top-p 采樣repetition_penalty=1.2, # 重復懲罰max_new_tokens=1024, # 最大新token數量
)response = tokenizer.batch_decode(outputs, skip_special_tokens=False)[0]
print(response)
?添加<think>標簽作為引導,模型更容易進行思考。
微調實操
微調請重新開一個notebook,清空緩存,從頭進行。
1. 倒入依賴
# 導入依賴
from modelscope.msdatasets import MsDataset # modelscope數據集類
from trl import SFTTrainer # 微調訓練器配置類
from transformers import TrainingArguments # 微調參數配置類
from unsloth import FastLanguageModel, is_bfloat16_supported # 檢查GPU是否支持bf16
import wandb # 微調數據可視化
?2. 定義模板
因為數據集是英文的,所以promt也采用英文,保證語言一致性。
# 1. 定義prompt模板
finetune_template = '''Below is an instruction that describes a task, paired with an input that provides further context.
Write a response that appropriately completes the request.
Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.### Instruction:
You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning.
Please answer the following medical question. ### Question:
{}### Response:
<think>
{}
</think>
{}'''prompt_style_zh = '''以下是一個任務說明,配有提供更多背景信息的輸入。
請寫出一個恰當的回答來完成該任務。
在回答之前,請仔細思考問題,并按步驟進行推理,確保回答邏輯清晰且準確。### Instruction:
您是一位具有高級臨床推理、診斷和治療規劃知識的醫學專家。
請回答以下醫學問題。### 問題:
{}### 問題:
<think>{}'''
3. 加載模型
# 2. 加載模型
model, tokenizer = FastLanguageModel.from_pretrained(model_name="./DeepSeek-R1-Distill-Qwen-7B",max_seq_length=2048,dtype=None,load_in_4bit=False
)
EOS_TOKEN = tokenizer.eos_token
4. 加載數據集
# 在模型微調時,給微調數據集加上 EOS_TOKEN 非常重要。它可以明確文本邊界、保持訓練目標一致性、控制生成過程、處理多輪對話,以及更好地利用 CoT 數據集。
EOS_TOKEN = tokenizer.eos_token# 格式話訓練數據
def formatting_prompts_func(examples):inputs = examples["Question"]cots = examples["Complex_CoT"]outputs = examples["Response"]texts = []for input, cot, output in zip(inputs, cots, outputs):text = finetune_template.format(input, cot, output) + EOS_TOKENtexts.append(text)return {"text": texts,}ds = MsDataset.load('AI-ModelScope/medical-o1-reasoning-SFT', split = "train")
dataset = ds.map(formatting_prompts_func, batched = True,)
print(dataset["text"][0])
5. 配置微調模型
將LoRA模塊加入模型,為微調做準備
'''
LoRA 的核心思想是,對于預訓練模型的權重矩陣 W,不直接對其進行更新,
而是添加一個低秩分解矩陣 ΔW = A * B,
其中 A 和 B 是兩個較小的矩陣。在微調過程中,只更新 A 和 B 的參數,而 W 的參數保持不變。
這樣可以大大減少需要微調的參數數量,降低計算成本。
'''
model = FastLanguageModel.get_peft_model(model,r=16, # lora微調的秩 # 較小的 `r` 值會減少需要微調的參數數量,降低計算成本,但也可能降低模型的表達能力。# 較大的 `r` 值會增加參數數量,提高模型的表達能力,但也會增加計算成本。# 通常需要根據實際情況進行實驗,選擇合適的 `r` 值。一般來說,8、16、32、64 是常用的值。target_modules = ["q_proj", "k_proj", "v_proj", # 指定要應用 LoRA 的模塊。這些模塊通常是 Transformer 模型中的線性層。"o_proj", "gate_proj", "up_proj", "down_proj"], # 這里分別應用了注意力機制中的Wq, Wk, Wv, Wo線性投影層,FFN中的線性層lora_alpha=16, # lora縮放因子,決定模型權重的更新程度,建議設置為r或r的倍數lora_dropout=0,bias="none", # 不為LoRA層添加偏置use_gradient_checkpointing="unsloth", # 是否設置梯度檢查點,# 梯度檢查點是一種以時間換空間的技術,可以減少內存占用,但會增加計算時間。random_state=3407, # 設置隨機種子,保證實驗可以浮現use_rslora=False, # 是否使用Rank-Stabilized LoRA(rslora)。rslora 是一種改進的 LoRA 方法,可以自動調整 `lora_alpha`。loftq_config=None # 是否使用QLoRA,即將LoRA與量化技術結合
)
6. 配置微調參數
# 5. 配置微調參數
trainer = SFTTrainer(model=model,tokenizer=tokenizer,train_dataset=dataset,dataset_text_field="text", # 數據集中包含文本的字段的名稱。# dataset_text_field="text", # 說明text列對應的是微調數據集max_seq_length=2048, # 模型能處理的最長序列dataset_num_proc=2, # 用于預處理數據的進程數。args=TrainingArguments(per_device_train_batch_size=2, # mini-batch-sizegradient_accumulation_steps=4, # 梯度累積,用于模型batch_size=2*4=8的情況,模型實際上經過 2 * 4 = 8 個batch之后才會更新參數(一個step),能緩解GPU無法放下大batch的問題num_train_epochs=3, # 訓練輪數# max_steps = 60 # 如果要進行迅速嚴重微調可行性,可以只訓練60個steps,訓練的總步數(參數更新次數)。warmup_steps=5, # 模型熱身步數,學習率會從 0 逐漸增加到設定的學習率。lr_scheduler_type="linear", # 學習率調度器類型。這里使用線性調度器,學習率會線性下降。learning_rate=2e-4, # 學習率fp16=not is_bfloat16_supported(), # 是否使用 FP16(16 位浮點數)混合精度訓練。如果 GPU 不支持 bfloat16,則使用 fp16。bf16=is_bfloat16_supported(),logging_steps=10, # 多少個step打印一次信息optim="adamw_8bit", # 指定優化器weight_decay=0.01, # 權重衰退seed=3407, # 隨機種子,保證結果可以復現output_dir="outputs" # 保存訓練結果(模型、日志等)的目錄。)
)
7. 進行微調
# 6. 進行微調
wandb.init()
trainer_stats = trainer.train()
看到如下輸出即表示微調正在運行中。?
8. 將LoRA權重與原始矩陣合并,保存微調后的模型
# LoRA微調完成后,保存微調模型并合并矩陣
new_model_local = "DeepSeek-R1-Qwen-7B-Medical-Full" # 定義一個字符串變量,表示保存模型的本地路徑。
model.save_pretrained(new_model_local) # 保存微調后的模型(包括 LoRA 權重)。
tokenizer.save_pretrained(new_model_local) # 保存分詞器。
model.save_pretrained_merged(new_model_local, tokenizer, save_method="merged_16bit") # 合并 LoRA 權重到基礎模型中,并保存合并后的模型。