基於SWIFT和Qwen1.5-14B-Chat進行大模型全參微調測試
環境準備
基礎環境
- 作業系統: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)時,可以準備兩種資料:
- 單輪問答
- 多輪對話
對於單輪問答資料,其格式可以為:
{"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進行全參微調的一個訓練指令碼:
# Experimental environment: 8 * A100 40GB
nproc_per_node=1
NPROC_PER_NODE=$nproc_per_node \
MASTER_PORT=29500 \
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
swift sft \
--model_type qwen1half-14b-chat \
--model_id_or_path /yldm0226/models/Qwen1.5-14B-Chat \
--model_revision master \
--sft_type full \
--tuner_backend swift \
--template_type AUTO \
--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 \
--gradient_checkpointing true \
--batch_size 1 \
--weight_decay 0.01 \
--learning_rate 1e-4 \
--gradient_accumulation_steps $(expr 8 / $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 \
--save_only_model true \
下面對該指令碼中的一些重要引數作出解釋:
nproc_per_node
:在多機分散式訓練中,每個主機被當做一個node;nproc_per_node代表的是每個node中有幾個執行緒;以該指令碼為例,該指令碼執行在單機環境中,因此nproc_per_node就代表著我們使用的單臺伺服器有幾個執行緒去同時訓練模型;假如nproc_per_node設定為8,那麼將有8個執行緒同時訓練模型,速度會提高很多,但是這樣每塊GPU都要負責儲存完整的模型權重,視訊記憶體會受到很大的挑戰;假如nproc_per_node設定為4,那麼將有4個執行緒同時訓練模型,每個執行緒中有兩塊GPU,這兩塊GPU共同負責儲存模型權重,這樣雖然速度降低了,但是能夠得到更寬裕的GPU視訊記憶體;但此時我們的視訊記憶體還是不足以訓練14B的Qwen1.5-Chat,我們只能捨棄時間以換取空間,所以將nproc_per_node設定為1,此時該伺服器只有1個執行緒去訓練模型,模型被切分到8塊GPU卡上。請注意,模型被切分到8塊GPU卡上時,並不代表著這8塊GPU只需要承擔模型部分權重的視訊記憶體,還需要承擔最佳化器中各種梯度的儲存,這也是相當大的一部分視訊記憶體開銷。所以如果視訊記憶體允許,我們應該儘可能的提高nproc_per_node的值,提高顯示卡的利用率。CUDA_VISIBLE_DEVICES
:伺服器中如果有多張顯示卡,可以透過該引數指定使用哪幾張顯示卡。顯示卡的序號可以透過nvidia-smi檢視。model_type
:model_type指定我們要微調的大模型的型別,這些型別必須是SWIFT框架所支援大模型型別的一種,具體有哪些支援的模型可以在swift原始碼的swift/docs/source/LLM路徑中的支援的模型和資料集文件中檢視。model_id_or_path
:model_id_or_path用於指定大模型權重的本地路徑。sft_type
: sft_type表示微調的方式, 預設是lora。可以選擇的值包括: 'lora', 'full', 'longlora', 'qalora'。此處使用的是full,即全參微調。output_dir
:output_dir用於指定大模型微調過程中輸出日誌的儲存路徑。custom_train_dataset_path
:用於指定我們資料集的存放路徑,每個資料集之間用空格分隔。train_dataset_sample
:對訓練集進行取樣, 預設是20000, 用於加快訓練的速度。 該引數是為了避免資料集過大, 單個epoch訓練時間過長的問題。 如果你指定為-1, 則使用完整的訓練集進行訓練。max_length
:token的最大長度, 預設為2048。該引數可以避免個別過長的資料樣本造成OOM的問題。當指定--truncation_strategy delete時, 如果某資料樣本長度超過max_length, 我們會刪除該資料樣本。如果指定--truncation_strategy truncation_left時, 我們會切除最前面的token: input_ids[-max_length:]。如果設定為-1, 則無限制。該引數很重要,要根據視訊記憶體情況選擇合適的max_length,不然在訓練中會出現OOM的情況,導致訓練終止。
對於其他引數,這裡不做過多講解。此外,這個指令碼只涉及到了部分引數,如果需要進一步的定製化,需根據文件自行修改。
測試
執行指令碼,可以得到以下資訊:
資料集預處理所需要的時間大概30分鐘:
703283/2223540 [09:23<20:31, 1234.07it/s]
max_length設定為2048的情況下,估算訓練時間:
100/277856 [05:38<255:02:02, 3.31s/it]
8596/22452 [10:15<17:42, 13.04it/s]
訓練一個epoch大約需要255小時;進行一次驗證大約需要27分鐘,我們設定每100步進行一次驗證,總步數為277856,需要進行2778次驗證,預計用時為1250小時;當然,設定100步進行一次驗證有些太頻繁了,在實際進行訓練時可以設定為10000步進行一次驗證,預計用時為12.6小時。(這裡的時間只是一個大概值,在訓練時,不同資料的處理速度不同,花費的總時間會一直變化)
max_length設定為4096的情況下,訓練所需的大概時間如下:
100/277926 [05:50<259:11:42, 3.36s/it]
1512/22459 [01:43<24:31, 14.24it/s]
情況與max_length設定為2048的情況差距不大。
max_length設定為8192的情況下,視訊記憶體不夠了,出現溢位:
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 9.95 GiB (GPU 0; 39.45 GiB total capacity; 26.79 GiB already allocated; 8.68 GiB free; 29.46 GiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF
最後看一下視訊記憶體佔用情況: