基於SWIFT和Qwen1.5-14B-Chat進行大模型LoRA微調測試

一蓑烟雨度平生發表於2024-03-08

基於SWIFT和Qwen1.5-14B-Chat進行大模型LoRA微調測試

環境準備

基礎環境

  • 作業系統:Ubuntu 18.04.5 LTS (GNU/Linux 3.10.0-1127.el7.x86_64 x86_64)
  • Anaconda3:Anaconda3-2023.03-1-Linux-x86_64
  • 根據伺服器網路情況配置好conda源和pip源,此處使用的是超算山河源
  • 伺服器硬體配置:CPU 96核;GPU 8×NVIDIA A100 40GB

環境安裝

透過原始碼安裝SWIFT:

建立一個新的conda環境:

conda create --name swift python=3.8

啟用剛剛建立的conda環境:

conda activate swift

下載SWIFT原始碼:

git clone https://github.com/modelscope/swift.git

切換到SWIFT路徑:

cd /yldm0226/swift

安裝SWIFT:

pip install -e .[llm]

非必要步驟:檢查伺服器cuda版本是否與當前安裝的pytorch對應,詳見:搭建一個大模型API服務

資料集準備

對於資料集,我們均採用json或jsonl的格式。

在做大模型SFT(Supervised Fine-Tuning)時,可以準備兩種資料:

  1. 單輪問答
  2. 多輪對話

對於單輪問答資料,其格式可以為:

{"query": "11111", "response": "22222"}

對於多輪對話資料,其格式可以為:

{"query": "eeeee", "response": "fffff", "history": []}
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]]}

同時,也可以用以下兩種格式的資料:

{"conversations": [{"from": "user", "value": "11111"}, {"from": "assistant", "value": "22222"}]}
{"conversations": [{"from": "user", "value": "aaaaa"}, {"from": "assistant", "value": "bbbbb"}, {"from": "user", "value": "ccccc"}, {"from": "assistant", "value": "ddddd"}]}
{"conversations": [{"from": "user", "value": "AAAAA"}, {"from": "assistant", "value": "BBBBB"}, {"from": "user", "value": "CCCCC"}, {"from": "assistant", "value": "DDDDD"}]}
{"messages": [{"role": "user", "content": "11111"}, {"role": "assistant", "content": "22222"}]}
{"messages": [{"role": "user", "content": "aaaaa"}, {"role": "assistant", "content": "bbbbb"}, {"role": "user", "content": "ccccc"}, {"role": "assistant", "content": "ddddd"}]}
{"messages": [{"role": "user", "content": "AAAAA"}, {"role": "assistant", "content": "BBBBB"}, {"role": "user", "content": "CCCCC"}, {"role": "assistant", "content": "DDDDD"}]}

在本文中,共使用了9個資料集,資料集的詳細資訊如下:

序號 資料集 簡介 資料量
1 Chinese_medical_dialogue_six_department 中文醫療問答資料集,包括男科、內科、婦產科、腫瘤科、兒科、外科六個科室的問題。 792K
2 HuatuoGPT2_sft_instruct_GPT4 華佗GPT(HuatuoGPT)第二版訓練資料集。 50K
3 ChatMed_Consult-v0.3 中文醫療線上問診資料集ChatMed_Consult_Dataset的50w+線上問診+ChatGPT回覆。 500K
4 ChatMed_TCM-v0.2 以開源的中醫藥知識圖譜為基礎,採用以實體為中心的自指令方法(entity-centric self-instruct),呼叫ChatGPT得到11w+的圍繞中醫藥的指令資料。 110K
5 QiZhen_sft_20k 包含20k訓練資料(該資料集來自於啟真醫學知識庫收集整理的真實醫患知識問答資料以及在啟真醫學知識庫的藥品文字知識基礎上,透過對半結構化資料設定特定的問題模板構造的指令資料)。 20K
6 Huatuo_Lite Huatuo-Lite 是在Huatuo26M資料集的基礎上經過多次提純和重寫而精煉最佳化的資料集。它包含了18萬個高質量的醫療問答對,並具有醫院科室和相關疾病兩個額外的資料維度。 180K
7 ZhongJing_CMtMedQA 仲景SFT訓練集。 70K
8 DISC-Med-SFT_released 包含了超過47萬個衍生於現有的醫療資料集重新構建得到的樣本。採用了目標導向的策略,透過對於精心選擇的幾個資料來源進行重構來得到SFT資料集。這些資料的作用在於幫助模型學習醫療領域知識,將行為模式與人類偏好對齊,並對齊真實世界線上醫療對話的分佈情況。 514K
9 SZY_TCM_QA 私有資料集。 12K

以下是載入後的資料集資訊:

[INFO:swift] train_dataset: Dataset({
    features: ['query', 'response', 'history'],
    num_rows: 2223540
})
[INFO:swift] val_dataset: Dataset({
    features: ['query', 'response', 'history'],
    num_rows: 22460
})

資料總量為2,246,000,從中抽取出約1%作為驗證集,其餘的作為訓練集。

透過max_lengt=4096進行過濾後的資料集資訊如下:

[INFO:swift] Dataset Token Length: 224.276768±159.001432, min=25.000000, max=4089.000000, size=2223411
[INFO:swift] Dataset Token Length: 224.254464±157.600093, min=28.000000, max=3086.000000, size=22459

編寫微調指令碼

SWIFT框架提供了部分大模型的微調指令碼,可以在我們下載的原始碼中的swift/examples/pytorch/llm/scripts路徑中找到這些指令碼。如果這些指令碼能夠滿足我們大部分的微調需求,我們可以選擇直接對這些指令碼進行修改。如果找不到我們需要的指令碼,需要我們根據swift/docs/source/LLM中的命令列引數文件自行編寫訓練指令碼。

以下是對Qwen1.5-14B-Chat進行LoRA微調的一個訓練指令碼:

nproc_per_node=8

CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
NPROC_PER_NODE=$nproc_per_node \
MASTER_PORT=29500 \
swift sft \
    --model_type qwen1half-14b-chat \
    --model_id_or_path /yldm0226/models/Qwen1.5-14B-Chat \
    --model_revision master \
    --sft_type lora \
    --tuner_backend swift \
    --template_type qwen \
    --dtype AUTO \
    --output_dir /yldm0226/llm_sft_output \
    --ddp_backend nccl \
    --custom_train_dataset_path /yldm0226/data/1-Chinese_medical_dialogue_six_department.jsonl /yldm0226/data/2-HuatuoGPT2_sft_instruct_GPT4.jsonl /yldm0226/data/3-ChatMed_Consult-v0.3.jsonl /yldm0226/data/4-ChatMed_TCM-v0.2.jsonl /yldm0226/data/5-QiZhen_sft_20k.jsonl /yldm0226/data/6-Huatuo_Lite.jsonl /yldm0226/data/7-ZhongJing_CMtMedQA.jsonl /yldm0226/data/8-DISC-Med-SFT_released.jsonl /yldm0226/data/9-SZY_TCM_QA.jsonl \
    --train_dataset_sample -1 \
    --num_train_epochs 1 \
    --max_length 4096 \
    --check_dataset_strategy warning \
    --lora_rank 8 \
    --lora_alpha 32 \
    --lora_dropout_p 0.05 \
    --lora_target_modules ALL \
    --gradient_checkpointing true \
    --batch_size 1 \
    --weight_decay 0.01 \
    --learning_rate 1e-4 \
    --gradient_accumulation_steps $(expr 64 / $nproc_per_node) \
    --max_grad_norm 0.5 \
    --warmup_ratio 0.03 \
    --eval_steps 100 \
    --save_steps 100 \
    --save_total_limit 3 \
    --logging_steps 10 \
    --use_flash_attn false \
    --deepspeed default-zero3 \
    --save_only_model true

該指令碼中的一些引數在基於SWIFT和Qwen1.5-14B-Chat進行大模型全參微調測試中已經解釋過了,此處簡單介紹一下與LoRA相關的幾個引數,如果你想了解LoRA具體的原理,請閱讀該論文LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

lora_rank:微調中的秩大小。秩的值並不是越大越好,此處設定的8是LoRA原論文中測試的最優解,根據論文中的結果,1或者2這種很小的秩的表現也是很好的。

lora_alpha:LoRA 微調中的縮放係數。

lora_dropout_p:LoRA 微調中的 Dropout 係數。

lora_target_modules:指定lora模組, 預設為['DEFAULT']. 如果lora_target_modules傳入'DEFAULT' or 'AUTO', 則根據model_type查詢MODEL_MAPPING中的lora_target_modules(預設指定為qkv)。如果傳入'ALL', 則將所有的Linear層(不含head)指定為lora模組。 如果傳入'EMBEDDING', 則Embedding層指定為lora模組。 如果記憶體允許, 建議設定成'ALL'。 當然, 你也可以設定['ALL', 'EMBEDDING'], 將所有的Linear和embedding層指定為lora模組。該引數只有當sft_type指定為'lora'時才生效。

deepspeed:用於指定deepspeed的配置檔案的路徑或者直接傳入json格式的配置資訊, 預設為None, 即不開啟deepspeed. deepspeed可以節約視訊記憶體。 SWIFT書寫了預設的ZeRO-2配置檔案, ZeRO-3配置檔案。你只需要指定'default-zero2', 就會使用預設zero2配置檔案; 指定'default-zero3', 就會使用預設的zero3配置檔案。

測試

以下是訓練過程中的部分輸出:

{'loss': 3.91967845, 'acc': 0.46053511, 'learning_rate': 0.0, 'epoch': 0.0, 'global_step': 1}                                                                                                                                                             
{'loss': 3.13938289, 'acc': 0.50242286, 'learning_rate': 3.313e-05, 'epoch': 0.0, 'global_step': 10}                                                                                                                                                      
{'loss': 2.02636986, 'acc': 0.56641636, 'learning_rate': 4.31e-05, 'epoch': 0.0, 'global_step': 20}                                                                                                                                                       
{'loss': 1.51573572, 'acc': 0.62124624, 'learning_rate': 4.894e-05, 'epoch': 0.0, 'global_step': 30}                                                                                                                                                      
{'loss': 1.37469482, 'acc': 0.65222416, 'learning_rate': 5.308e-05, 'epoch': 0.0, 'global_step': 40}                                                                                                                                                      
{'loss': 1.44527245, 'acc': 0.64013515, 'learning_rate': 5.629e-05, 'epoch': 0.0, 'global_step': 50}                                                                                                                                                      
{'loss': 1.36220665, 'acc': 0.65485716, 'learning_rate': 5.891e-05, 'epoch': 0.0, 'global_step': 60}                                                                                                                                                      
{'loss': 1.34706726, 'acc': 0.65729899, 'learning_rate': 6.113e-05, 'epoch': 0.0, 'global_step': 70}                                                                                                                                                      
{'loss': 1.3558219, 'acc': 0.65412712, 'learning_rate': 6.305e-05, 'epoch': 0.0, 'global_step': 80}                                                                                                                                                       
{'loss': 1.38924046, 'acc': 0.6498558, 'learning_rate': 6.475e-05, 'epoch': 0.0, 'global_step': 90}                                                                                                                                                       
{'loss': 1.31848869, 'acc': 0.66292844, 'learning_rate': 6.626e-05, 'epoch': 0.0, 'global_step': 100}                                                                                                                                                     
Train:   0%|▌                                                                                                                                                                                                     | 100/34740 [20:07<113:54:29, 11.84s/it]
Val:  22%|████████████████████████████████████████████                            | 615/2808 [04:56<17:36,  2.07it/s]                                                                                                                    

訓練一個epoch大約需要114小時;進行一次驗證大約需要22分鐘。(這裡的時間只是一個大概值,在訓練時,不同資料的處理速度不同,花費的總時間會一直變化)。

相比於全參,LoRA的微調方式能夠節約大量的視訊記憶體,因此我們可以將nproc_per_node設定的大一些,以提高訓練的速度。

相關文章