Lora微調(diào)實(shí)操演示(下):五次訓(xùn)練詳解(免費(fèi)GPU)

五月底寫過一篇 Lora 微調(diào)的概念鋪墊文章,這篇來結(jié)合阿里云的一個(gè)開源項(xiàng)目和免費(fèi)試用 GPU 來做個(gè)完整的訓(xùn)練過程演示。
Lora微調(diào)實(shí)操教程(上):人話版概念詳解(附案例)
注:本篇演示的腳本部分在參考 aliyun_acp_learning 開源項(xiàng)目基礎(chǔ)上有部分刪減和調(diào)整。后文相關(guān)配置和訓(xùn)練過程中的實(shí)際耗時(shí)也會(huì)進(jìn)行標(biāo)注說明。https://github.com/AlibabaCloudDocs/aliyun_acp_learning.git
這篇試圖說清楚:
Lora 微調(diào)的環(huán)境配置、任務(wù)設(shè)計(jì)、基準(zhǔn)測(cè)試、五次迭代訓(xùn)練以及微調(diào)后的對(duì)比測(cè)試效果。
以下,enjoy:
1、環(huán)境配置
雖然是 Lora 微調(diào)但對(duì)硬件性能要求也并不低,如果各位本地沒有 GPU 環(huán)境或者顯存不到 30GB,可以先按照下述步驟去蹭下阿里云的 DSW(交互式建模平臺(tái))3 個(gè)月250計(jì)算時(shí)的 A10 GPU 免費(fèi)試用。
https://billing-cost.console.aliyun.com/home/myfreetier

資源規(guī)格選擇免費(fèi)試用頁簽中的ecs.gn7i-c8g1.2xlarge(該規(guī)格有一個(gè) A10 GPU,顯存 30GB),鏡像選擇 modelscope:1.21.0-pytorch2.4.0-gpu-py310-cu124-ubuntu22.04(需要將“鏡像配置”->”芯片類型“切換為 GPU)

# 需要安裝以下依賴
%pip install accelerate==1.0.1 rouge-score==0.1.2 nltk==3.9.1 ms-swift[llm]==2.4.2.post2 evalscope==0.5.5rc1
注:實(shí)際安裝依賴花了 48 分鐘
2、明確訓(xùn)練狀態(tài)
老規(guī)矩,正式開始介紹前,明確一些重要概念。最基礎(chǔ)的損失函數(shù)、神經(jīng)網(wǎng)絡(luò)和各個(gè)超參數(shù)的概念本篇不再做贅述,不熟悉的盆友記得先去翻下上一篇。這部分明確下后續(xù)的訓(xùn)練和測(cè)試內(nèi)容。
模型訓(xùn)練和人的學(xué)習(xí)考試過程非常像,前后需要經(jīng)過三套題目的考驗(yàn),來確定模型訓(xùn)練所處于的狀態(tài):
2.1訓(xùn)練集
課程練習(xí)冊(cè),帶詳細(xì)答案解析,模型會(huì)反復(fù)練習(xí),并基于損失函數(shù)產(chǎn)生訓(xùn)練損失。訓(xùn)練損失越小,說明模型在你給定的練習(xí)冊(cè)上表現(xiàn)更好。
訓(xùn)練損失不變,甚至變大說明訓(xùn)練失敗??梢岳斫鉃槟P驮谟?xùn)練集(練習(xí)冊(cè))上沒有學(xué)習(xí)到知識(shí),說明模型的學(xué)習(xí)方法有問題。
2.2驗(yàn)證集
模擬考試題,模型每學(xué)習(xí)一段時(shí)間,就會(huì)測(cè)試一次,并基于損失函數(shù)產(chǎn)生驗(yàn)證損失。驗(yàn)證損失就是用于評(píng)估模型訓(xùn)練的效果。驗(yàn)證損失越小,說明模型在模擬考試中表現(xiàn)越好。
訓(xùn)練損失和驗(yàn)證損失都在下降說明模型欠擬合??梢韵胂蟪赡P驮谟?xùn)練集(練習(xí)冊(cè))上的學(xué)習(xí)有進(jìn)步,驗(yàn)證集(模擬考試)的表現(xiàn)也變好了,但還有更多的進(jìn)步空間。這時(shí)候應(yīng)該讓模型繼續(xù)學(xué)習(xí)。
2.3測(cè)試集
相當(dāng)于考試真題,模型在測(cè)試集上的準(zhǔn)確率用于評(píng)估最終的模型表現(xiàn)。訓(xùn)練損失下降但驗(yàn)證損失上升說明模型過擬合。
可以理解為模型只是背下了訓(xùn)練集(練習(xí)冊(cè)),在模型考試中遇到了沒背過的題反而做不來了。這種場(chǎng)景需要通過一些方法去抑制模型的背題傾向,比如再給它 20 本練習(xí)冊(cè),讓它記不住所有的題,而是逼它去學(xué)習(xí)題目里面的規(guī)律。
3、模型下載與基準(zhǔn)測(cè)試
本篇微調(diào)的基座模型選擇的是小參數(shù)的開源qwen2.5-1.5b-instruct,首先下載模型并把它加載到內(nèi)存中。
3.1模型下載(42 分鐘)
# 下載模型參數(shù)到 ./model 目錄下
!mkdir ./model
!modelscope download --model qwen/Qwen2.5-1.5B-Instruct --local_dir './model'
from swift.llm import (
get_model_tokenizer, get_template, ModelType,
get_default_template_type
)
import torch
#你可以根據(jù)你的需要修改query(模型輸入)
# 獲得模型信息
model_type = ModelType.qwen2_5_1_5b_instruct
template_type = get_default_template_type(model_type)
# 設(shè)置模型本地位置
model_id_or_path = "./model"
# 初始化模型和輸入輸出格式化模板
kwargs = {}
model, tokenizer = get_model_tokenizer(model_type, torch.float32, model_id_or_path=model_id_or_path, model_kwargs={'device_map': 'cpu'}, **kwargs)
model.generation_config.max_new_tokens = 128
template = get_template(template_type, tokenizer, default_system='')
print("模型初始化完成")
3.2基準(zhǔn)模型測(cè)試(2 分鐘)
在開始模型微調(diào)前,先來看看基準(zhǔn)模型在測(cè)試集上的表現(xiàn)如何。
import json
from IPython.display import Markdown
sum, score = 0, 0
for line in open("./resources/2_7/test.jsonl"):
# 讀取測(cè)試集中的問題
math_question = json.loads(line)
query = math_question["messages"][1]["content"]
# 使用基準(zhǔn)模型推理
response, _ = inference(model, template, query)
# 獲取正確答案
ans = math_question["messages"][2]["content"]
pos = ans.find("ans")
end_pos = ans[pos:].find('}}')
ans = ans[pos - 2: end_pos + pos + 2]
# 整理輸出
print(("========================================================================================"))
print(query.split("#數(shù)學(xué)題#\n")[1])
print("問題答案是:" + ans)
print("-----------模型回答----------------")
display(Latex(response))
print("-----------回答結(jié)束----------------")
# 計(jì)算模型得分
if ans in response or ans[6:-2] in response:
score += 1
print("模型回答正確")
else: print("模型回答錯(cuò)誤")
sum += 1
# 總結(jié)
display(Markdown("模型在考試中得分:**" + str(int(100*score/sum)) + "** 分"))
一共有十個(gè)測(cè)試問題,這里截取其中一個(gè)問答進(jìn)行展示:
{"messages": [{"role": "system", "content": ""}, {"role": "user", "content": "#輸出#\n輸出要符合經(jīng)過推理后將最終答案放在 {{ans:xxx}} 內(nèi)。\n#數(shù)學(xué)題#\n雞、鴨只數(shù)的比是5:3,雞有25只,鴨有多少只."}, {"role": "assistant", "content": "雞和鴨的比例為5:3,即雞的數(shù)量為5份,鴨為3份。已知雞有25只,對(duì)應(yīng)5份,每份數(shù)量為 $25 \\div 5 = 5$ 只。因此,鴨的數(shù)量為 $3 \\times 5 = 15$ 只。\n\n驗(yàn)證:設(shè)鴨的數(shù)量為$x$,根據(jù)比例 $\\frac{25}{x} = \\frac{5}{3}$,交叉相乘得 $5x = 75$,解得 $x = 15$,與份數(shù)法一致。\n\n{{ans:15}}"}]}========================================================================================
雞、鴨只數(shù)的比是5:3,雞有25只,鴨有多少只.
問題答案是:{{ans:15}}
-----------模型回答----------------
設(shè)雞和鴨的數(shù)量分別為 ??
和 ??
。根據(jù)題目信息,我們可以得到以下比例關(guān)系:
????=53
已知雞的數(shù)量 ??=25
。 代入上述比例關(guān)系求解鴨的數(shù)量 ??
:
25??=53
通過交叉相乘來解這個(gè)方程:
25×3=5×??
75=5??
\[ y = \frac{7
-----------回答結(jié)束----------------
模型回答錯(cuò)誤最終qwen2.5-1.5b-instruct模型在考試中得了60 分。
4、五次微調(diào)過程
這里使用 ms-swift(Modelscope Scalable lightWeight Infrastructure for Fine-Tuning)框架,它是阿里魔搭社區(qū)專門為模型訓(xùn)練開發(fā)的開源框架,在每次計(jì)算驗(yàn)證損失的時(shí)候,框架會(huì)自動(dòng)保存當(dāng)前訓(xùn)練階段的模型參數(shù),并在訓(xùn)練結(jié)束時(shí)自動(dòng)保存訓(xùn)練過程中驗(yàn)證損失最小的參數(shù)。
在接下來的多次實(shí)驗(yàn)中,將重點(diǎn)調(diào)整學(xué)習(xí)率、LoRA 的秩、數(shù)據(jù)集學(xué)習(xí)次數(shù)三個(gè)參數(shù),并替換數(shù)據(jù)集,展示如何進(jìn)行 LoRA 微調(diào),其它的參數(shù)的調(diào)整是為了方便實(shí)驗(yàn)效果呈現(xiàn),如增加批大小從而縮短訓(xùn)練時(shí)間,可以不用過多關(guān)注。
4.1第一次實(shí)驗(yàn)(26 秒)
參數(shù) | 參數(shù)值 |
學(xué)習(xí)率 | 0.1 |
LoRA 的秩 | 4 |
數(shù)據(jù)集學(xué)習(xí)次數(shù) | 1 |
數(shù)據(jù)集位置 | 數(shù)據(jù)集位置: 當(dāng)前目錄/resources/2_4/train_100.jsonl |
可以調(diào)整所有的參數(shù)自由嘗試,但根據(jù)展示效果和顯存限制,有這些限制 | batch_size <= 16 (顯存限制) |
注:ms-swift 框架的微調(diào)模塊默認(rèn)使用 LoRA 微調(diào)所以實(shí)驗(yàn)中不需要顯式地聲明微調(diào)方法。同時(shí)框架會(huì)在訓(xùn)練過程中智能地減少實(shí)際學(xué)習(xí)率,保證模型不會(huì)總是跳過最優(yōu)解。
%env CUDA_VISIBLE_DEVICES=0
%env LOG_LEVEL=INFO
!swift sft \
--learning_rate '0.1' \
--lora_rank 4 \
--num_train_epochs 1 \
--dataset './resources/2_7/train_100.jsonl' \
--batch_size '8' \
--max_length 512 \
--eval_step 1 \
--model_type 'qwen2_5-1_5b-instruct' \
--model_id_or_path './model'


觀察指標(biāo) | 訓(xùn)練損失增大、驗(yàn)證損失增大 |
訓(xùn)練狀態(tài) | 訓(xùn)練失敗 |
原因分析 | 很有可能是學(xué)習(xí)率過高,導(dǎo)致模型參數(shù)在最優(yōu)解附近反復(fù)橫跳,無法找到最優(yōu)解,訓(xùn)練失敗。 |
調(diào)整邏輯 | 大幅降低學(xué)習(xí)率至 0.00005,讓模型以更小步幅“謹(jǐn)慎學(xué)習(xí)”。 |
4.2第二次實(shí)驗(yàn)(22 秒)
參數(shù) | 舊參數(shù)值 | 新參數(shù)值 |
學(xué)習(xí)率 (learning_rate) | 0.1 | 0.00005 |
%env CUDA_VISIBLE_DEVICES=0
%env LOG_LEVEL=INFO
!swift sft \
--learning_rate '0.00005' \
--lora_rank 4 \
--num_train_epochs 1 \
--dataset './resources/2_7/train_100.jsonl' \
--batch_size '8' \
--max_length 512 \
--eval_step 1 \
--model_type 'qwen2_5-1_5b-instruct' \
--model_id_or_path './model'


觀察指標(biāo) | 訓(xùn)練損失減小、驗(yàn)證損失也減小 |
訓(xùn)練狀態(tài) | 欠擬合 |
原因分析 | 欠擬合是在訓(xùn)練中非常常見的現(xiàn)象。 說明在參數(shù)不變的情況下,只需要讓模型再多訓(xùn)練,就可以訓(xùn)練成功了。 當(dāng)然也可以修改參數(shù)來加速訓(xùn)練過程。 |
調(diào)整邏輯 | 1. 讓模型多訓(xùn)練:數(shù)據(jù)集學(xué)習(xí)次數(shù)epoch增加至 50。 |
4.3第三次實(shí)驗(yàn)(7 分鐘)
參數(shù) | 舊參數(shù)值 | 新參數(shù)值 |
數(shù)據(jù)集學(xué)習(xí)次數(shù) | 1 | 50 |
batch_size | 8 | 16 |
eval_step | 1 | 20 |



觀察指 | 訓(xùn)練損失減小、驗(yàn)證損失先減小后增大 |
訓(xùn)練狀態(tài): | 過擬合 |
原因分析 | 過擬合也是在訓(xùn)練中非常常見的現(xiàn)象。 說明模型在“背題”,沒有學(xué)習(xí)數(shù)據(jù)集中的知識(shí)。 我們可以通過降低數(shù)據(jù)集學(xué)習(xí)次數(shù) (epoch) 、增大數(shù)據(jù)量來讓模型“記不住題”。 |
調(diào)整邏輯 | 1. 數(shù)據(jù)集學(xué)習(xí)次數(shù) (epoch) 降低至 5。 |
一般來說,微調(diào)至少需要1000+條優(yōu)質(zhì)的訓(xùn)練集數(shù)據(jù)。低于此數(shù)量級(jí)時(shí),模型多學(xué)幾遍就開始“背題”而非學(xué)習(xí)數(shù)據(jù)中的知識(shí)。
4.4第四次實(shí)驗(yàn)(5 分鐘)
參數(shù) | 舊參數(shù)值 | 新參數(shù)值 |
更換數(shù)據(jù)集 | 100 條數(shù)據(jù) | 1000+ 條數(shù)據(jù) |
數(shù)據(jù)集學(xué)習(xí)次數(shù) | 50 | 3 |
LoRA 的秩 | 4 | 8 |
%env CUDA_VISIBLE_DEVICES=0
%env LOG_LEVEL=INFO
!swift sft \
--learning_rate '0.00005' \
--lora_rank 8 \
--num_train_epochs 3 \
--dataset './resources/2_7/train_1k.jsonl' \
--batch_size '16' \
--max_length 512 \
--eval_step 20 \
--model_type 'qwen2_5-1_5b-instruct' \
--model_id_or_path './model'


觀察指標(biāo) | 訓(xùn)練損失減小、驗(yàn)證損失也減小 |
訓(xùn)練狀態(tài) | 欠擬合 |
原因分析 | 訓(xùn)練快成功了 |
調(diào)整邏輯 | 讓模型多訓(xùn)練:數(shù)據(jù)集學(xué)習(xí)次數(shù)增加至 15。 |
4.5第五次實(shí)驗(yàn)(23 分鐘)
參數(shù) | 舊參數(shù)值 | 新參數(shù)值 |
數(shù)據(jù)集學(xué)習(xí)次數(shù) | 3 | 15 |
%env CUDA_VISIBLE_DEVICES=0
%env LOG_LEVEL=INFO
!swift sft \
--learning_rate '0.00005' \
--lora_rank 8 \
--num_train_epochs 15 \
--dataset './resources/2_7/train_1k.jsonl' \
--batch_size '16' \
--max_length 512 \
--eval_step 20 \
--model_type 'qwen2_5-1_5b-instruct' \
--model_id_or_path './model'


觀察指標(biāo)(訓(xùn)練損失、驗(yàn)證損失): | 訓(xùn)練損失基本不減小、驗(yàn)證損失也基本不減小甚至還略微升高 |
訓(xùn)練狀態(tài): | 訓(xùn)練成功 |
5、微調(diào)后測(cè)試
微調(diào)后一般會(huì)保存兩個(gè) checkpoint 文件,分別是 best_model_checkpoint(在驗(yàn)證集表現(xiàn)最佳的模型參數(shù))與 last_model_checkpoint(微調(diào)任務(wù)完成時(shí)的模型參數(shù))。這里選取 best_model_checkpoint 地址替換下面代碼中的 ckpt_dir,就能調(diào)用微調(diào)后的模型。

先加載模型到內(nèi)存中:
from swift.tuners import Swift
# 請(qǐng)?jiān)谶\(yùn)行前修改ckpt_dir到正確的位置
ckpt_dir = 'output/qwen2_5-1_5b-instruct/qwen2_5-1_5b-instruct/v4-20250710-200534/checkpoint-1065'
# 加載模型
ft_model = Swift.from_pretrained(model, ckpt_dir, inference_mode=True)來看看微調(diào)后的模型在考試中的表現(xiàn):
import json
from IPython.display import Markdown
sum, score = 0, 0.0
for line in open("./resources/2_7/test.jsonl"):
# 讀取測(cè)試集中的問題
math_question = json.loads(line)
query = math_question["messages"][1]["content"]
# 使用微調(diào)成功的模型推理
response, _ = inference(ft_model, template, query)
# 獲取正確答案
ans = math_question["messages"][2]["content"]
pos = ans.find("ans")
end_pos = ans[pos:].find('}}')
ans = ans[pos - 2: end_pos + pos + 2]
# 整理輸出
print(("========================================================================================"))
print(query.split("#數(shù)學(xué)題#\n")[1])
print("問題答案是:" + ans)
print("-----------模型回答----------------")
display(Latex(response))
print("-----------回答結(jié)束----------------")
# 計(jì)算模型得分
if ans in response:
score += 1
print("模型回答正確")
elif ans[6 : -2] in response:
score += 0.5
print("模型回答正確但輸出格式錯(cuò)誤")
else: print("模型回答錯(cuò)誤")
sum += 1
# 總結(jié)
display(Markdown("微調(diào)后的模型在考試中得分:**" + str(int(100*score/sum)) + "** 分"))
6、寫在最后
這篇只是個(gè)過程演示,在實(shí)際生產(chǎn)中,是否微調(diào)需綜合考慮算力成本、數(shù)據(jù)規(guī)模和數(shù)據(jù)質(zhì)量等因素。一般經(jīng)驗(yàn)是,能靠提示詞工程、RAG 、工作流編排能解決的先做工程優(yōu)化為好。
最后留個(gè)彩蛋:一道多選題。(答案公眾號(hào)聊天頁面發(fā)送“微調(diào)”兩個(gè)字看正確答案和解釋)
你正在使用 Swift 微調(diào)一個(gè) Qwen 模型,發(fā)現(xiàn)模型在驗(yàn)證集上的 loss 出現(xiàn)了明顯的上升趨勢(shì),以下哪些操作可以幫助你緩解或解決這個(gè)問題?
A. 增大 --learning_rate
B. 減小 --learning_rate
C. 增大 --num_train_epochs
D. 減小 --num_train_epochs































