
Transformers Generate 功能介紹
--data_path Dahoas/rm-static \
--data_split 2,4,4 \
--model_name_or_path facebook/opt-1.3b \
--per_device_train_batch_size 8 \
--per_device_eval_batch_size 8 \
--max_seq_len 512 \
--learning_rate 9.65e-6 \
--weight_decay 0.1 \
--num_train_epochs 2 \
--gradient_accumulation_steps 1 \
--lr_scheduler_type cosine \
--num_warmup_steps 0 \
--seed 1234 \
--zero_stage $ZERO_STAGE \
--deepspeed \
--output_dir $OUTPUT \
&> $OUTPUT/training.log
結合 main.py
程序,我們可以將參數分為三大類
與數據相關的參數
data_path : 數據路徑,huggingface數據庫, 比如:Dahoas/rm-static
data_split : 數據的拆分方式,比如 2,4,4 是為step1,2,3分配的數據比例
max_seq_len : 最大序列長度(超過長度會被截掉)
data_output_path : 相關數據的存儲地址(local storage,不能是shared storage)
與模型相關的參數
model_name_or_path : 模型名稱或路徑,huggingface模型,比如:facebook/opt-1.3b
lora_dim : 如果大于0,則使用LoRA優化
lora_module_name : 設置LoRA的范圍,比如可以只針對 decoder.layers
only_optimize_lora : 是否只優化LoRA的參數
與訓練相關的參數
per_device_train_batch_size : 訓練時的 Batch size (per device: 每個GPU的Size)
per_device_eval_batch_size : 評價時的 Batch size (per device)
learning_rate : 學習率
weight_decay : 權重衰減,防止模型過擬合的技術。
num_train_epochs : 訓練 epoch 數
gradient_accumulation_steps : 累積多少個 mini-batch 的梯度后再進行一次參數更新。
lr_scheduler_type : learning rate的調整策略,比如 linear, cosine
deepspeed
zero_stage : 這個對應者DeepSpeed工具中的zero方式,分別是0,1,2,3
offload : ZeRO-Offload 通過利用主機CPU上的計算和內存資源來執行優化器,從而減少此類模型的GPU計算和內存需求。
local_rank : 分布式訓練時的一個變量,用于標識當前 GPU 設備的本地排名(本機排名,與global-rank不同)
gradient_checkpointing : 降低深度學習模型訓練過程中內存消耗的技術
其他
seed : 隨機排序是的seed
output_dir : 模型的存儲目錄
args.local_rank
local_rank 在分布式訓練時使用的一個變量,用于標識當前 GPU 設備的本地排名(local rank)。
當 args.local_rank 等于 -1 時,表示代碼不在分布式設置下運行,僅使用單個 GPU 進行訓練。如果 args.local_rank 不等于 -1,則表示代碼在分布式設置下運行,當前 GPU 設備被分配了一個唯一的本地排名。代碼會將設備設置為指定的 GPU(torch.device(“cuda”, args.local_rank)),并使用 deepspeed.init_distributed() 函數調用初始化分布式后端。
注意: 在 PyTorch 中也有分布式初始化方法 torch.distributed.init_process_group() 函數。但是當使用 DeepSpeed 庫時,不要替換為 deepspeed.init_distributed()。
args.global_rank
在分布式訓練中,每個進程都有一個唯一的全局排名,用于標識該進程在分布式環境中的位置。全局排名的范圍是從0到world_size-1,其中 world_size 是整個分布式環境中進程的總數。
本程序中通過 torch.distributed.get_rank() 來讀取 global_rank, 本函數在初始化分布式后端之后才能調用。
torch.distributed.barrier()
torch.distributed.barrier() 是一個同步函數,用于在分布式環境中同步各個進程的狀態。在調用該函數時,進程會阻塞等待,直到所有進程都調用了該函數之后,才會解除阻塞并繼續執行后面的代碼。
在分布式訓練中,torch.distributed.barrier() 通常用于同步各個進程的梯度更新。在每個進程完成一輪前向傳播和反向傳播后,它們需要同步各自的梯度,并且等待其他進程完成同樣的操作,才能進行下一輪更新。這時就可以使用 torch.distributed.barrier() 函數實現同步。
另外一個用法,在模型參數并行訓練時,數據的讀取只需要在 local_rank 為 0 的GPU上進行,其他進程使用 torch.distributed.barrier() 來阻塞來等待數據讀取完成。
現在的NLP模型訓練時,普遍的做法是,首先將文本轉換為Token。Token通常是指一個單詞或者字的一部分。將本文轉換為Token時會使用到 tokenizer,tokenizer是在訓練數據上統計并訓練出來的。有很多工具可以來訓練tokenizer,比如 Google 的 sentencepiece。下面給出了GPT-3的Token的例子。比如:
你可以通過OpenAI網站來體驗Token的拆分:https://platform.openai.com/tokenizer
DS-Chat工具中訓練時使用的 tokenizer 是來自預訓練模型,這段代碼使用了 Hugging Face Transformers 庫中的 AutoTokenizer 類,用于實例化一個預訓練模型的 tokenizer。AutoTokenizer 類可以自動選擇并加載對應的 tokenizer,從而避免了手動選擇的步驟。
tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path, fast_tokenizer=True)
tokenizer.pad_token = tokenizer.eos_token
AutoTokenizer.from_pretrained() 函數有兩個必選參數,model_name_or_path 是預訓練模型的名稱或路徑,例如 “bert-base-uncased” 或 “/path/to/model/directory”。 fast_tokenizer: 是否使用快速 tokenizer。如果為 True,則會選擇使用 Rust 實現的 tokenizer,速度更快;否則使用 Python 實現的 tokenizer。默認為 True。
數據準備函數:create_prompt_dataset
train_phase = 1
train_dataset, eval_dataset = create_prompt_dataset(
args.local_rank, args.data_path, args.data_split,
args.data_output_path, train_phase, args.seed, tokenizer,
args.max_seq_len)
local_rank 參數是為了讓數據下載等基本處理,只在local rank為 0 的 GPU 上執行。也就是每個node上只處理一次數據即可。 data_output_path 需要設定為 local storage path,應該是為了在分布式訓練時存儲本地數據用的。
然后是初始化sampler,單GPU使用RandomSampler和SequentialSampler,分布式處理使用DistributedSampler。sampler 主要用來設置數據采用的順序。比如隨機采樣來提高模型的魯棒性。
# DataLoaders creation:
if args.local_rank == -1:
train_sampler = RandomSampler(train_dataset)
eval_sampler = SequentialSampler(eval_dataset)
else:
train_sampler = DistributedSampler(train_dataset)
eval_sampler = DistributedSampler(eval_dataset)
數據讀取使用 PyTorch 標準的 DataLoader 來處理。使用Dataloader 不僅可以設置sampler定義采樣方式,還可以自動進行批處理,并且支持多進程數據加載。
train_dataloader = DataLoader(train_dataset,
collate_fn=default_data_collator,
sampler=train_sampler,
batch_size=args.per_device_train_batch_size)
eval_dataloader = DataLoader(eval_dataset,
collate_fn=default_data_collator,
sampler=eval_sampler,
batch_size=args.per_device_eval_batch_size)
模型初始化
以下的代碼用來對模型進行初始化。
model = create_hf_model(AutoModelForCausalLM, args.model_name_or_path,
tokenizer, ds_config)
其中 AutoModelForCausalLM 是 Hugging Face Transformers 庫中的一個類,能夠自動選擇并加載適當的預訓練 Transformer 模型,它支持多種預訓練 Transformer 模型,包括 GPT-2、GPT、CTRL、Transformer-XL、XLNet 和 XLM 等。使用該類時,您只需指定模型的名稱或路徑即可自動加載對應的模型。
具體實現代碼,可以參考:utils/model/model_utils.py。
LoRA
當設置 lora_dim 大于0時,將會使用LoRA技術對模型進行調整。 從而讓模型的優化參數大幅度的變少,改善優化的效率。通常情況下,使用LoRA技術,不僅可以減少參數量,還能進一步改善性能。這主要是因為,這種bottleneck的網絡設計,可以防止過擬合,從而提高模型的魯棒性。
if args.lora_dim > 0:
model = convert_linear_layer_to_lora(model, args.lora_module_name,
args.lora_dim)
if args.only_optimize_lora:
model = only_optimize_lora_parameters(model)
提取需要被優化的參數 optimizer_grouped_parameters
# Split weights in two groups, one with weight decay and the other not.
optimizer_grouped_parameters = get_optimizer_grouped_parameters(
model, args.weight_decay)
AdamOptimizer = DeepSpeedCPUAdam if args.offload else FusedAdam
optimizer = AdamOptimizer(optimizer_grouped_parameters,
lr=args.learning_rate,
betas=(0.9, 0.95))
在上面的代碼中,get_optimizer_grouped_parameters() 函數被用來將權重分成兩組,一組需要應用權重衰減,另一組則不需要。該函數通過遍歷模型的所有參數,并檢查參數名稱是否包含 bias 或 LayerNorm 等特殊字符串,來區分需要應用權重衰減的參數和不需要的參數。
分組原因的解說: 一般來說,對于參數名稱中不包含 bias 或 LayerNorm 等特殊字符串的參數,我們認為它們是需要應用權重衰減的參數。對于這些參數,通常會將它們的權重矩陣與權重衰減超參數相乘,以降低它們的權重。與此相反,對于參數名稱中包含 bias 或 LayerNorm 等特殊字符串的參數,我們認為它們是不需要應用權重衰減的參數。這是因為 bias 或 LayerNorm 參數通常只是用來偏移或縮放其他層的輸出,而不是真正的權重參數。通過將權重分成兩組,并分別應用權重衰減和不應用權重衰減,我們可以更好地控制模型的復雜度,從而提高模型的泛化性能。
然后設置Optimizer優化器,根據參數不同會選擇 DeepSpeedCPUAdam 或者 FusedAdam 優化器。 并傳入了一些參數,包括分組的參數、學習率和 betas。
Adam優化器的解說: 在 Hugging Face 的 Transformers 庫中,有兩種 Adam 優化器可供選擇:FusedAdam 和 DeepSpeedCPUAdam。它們都是基于 PyTorch 實現的優化器,但在不同的硬件上具有不同的優化和性能特征。FusedAdam 是使用 NVIDIA Apex 庫實現的優化器,它支持混合精度訓練,并且可以同時計算梯度和權重更新操作,從而提高訓練效率。FusedAdam 優化器在使用支持 CUDA 的 NVIDIA GPU 時具有較好的性能。DeepSpeedCPUAdam 是一種 CPU 上的優化器,它是 DeepSpeed 框架中的一部分,支持分布式訓練和模型平行化。DeepSpeedCPUAdam 優化器在使用 CPU 時具有較好的性能。在上面的代碼中,如果 args.offload 為 True,則表示使用基于 CPU 的優化,因此會選擇使用 DeepSpeedCPUAdam 優化器。
設置lr_scheduler
num_update_steps_per_epoch = math.ceil(
len(train_dataloader) / args.gradient_accumulation_steps)
lr_scheduler = get_scheduler(
name=args.lr_scheduler_type,
optimizer=optimizer,
num_warmup_steps=args.num_warmup_steps,
num_training_steps=args.num_train_epochs * num_update_steps_per_epoch,
)
lr_scheduler 是用來規劃整個訓練過程中 lr 是如何調整的。lr_scheduler_type 調度器類型,用來描述 lr 是按照什么樣的方式變化,例如 LinearWarmup、CosineAnnealing 等。num_warmup_steps 預熱步數指定了在訓練的前期階段 lr 增加過程的步數。 總訓練步數指定模型共被更新多少次。
DS初始化
model, optimizer, _, lr_scheduler = deepspeed.initialize(
model=model,
optimizer=optimizer,
args=args,
config=ds_config,
lr_scheduler=lr_scheduler,
dist_init_required=True)
使用DeepSpeed進行優化是,需要使用deepspeed.initialize() 函數來初始化模型、優化器、學習率調度器等訓練相關的組件。其中,model 和 optimizer 是必需的參數,而其他參數則是可選的。
deepspeed.initialize() 函數會對傳入的參數進行檢查和優化,并返回新的模型、優化器和學習率調度器等組件。例如,它會根據訓練參數設置和硬件配置自動調整優化器和梯度累積的設置,并設置模型權重的分布式訓練策略。dist_init_required=True 參數指示 DeepSpeed 是否需要進行分布式訓練初始化。
DS 配置文件
配置文件包含DeepSpeed模型訓練時所需要的相關設置信息,可以通過這里的修改來調整訓練過程。下面是 utils/ds_utils.py 中給出的設置 :
ds_config = {
"train_batch_size": GLOBAL_BATCH_SIZE,
"train_micro_batch_size_per_gpu": MICRO_BATCH_SIZE,
"steps_per_print": 10,
"zero_optimization": {
"stage": stage,
"offload_param": {
"device": device
},
"offload_optimizer": {
"device": device
},
"stage3_param_persistence_threshold": 1e4,
"stage3_max_live_parameters": 3e7,
"stage3_prefetch_bucket_size": 3e7,
"memory_efficient_linear": False
},
"fp16": {
"enabled": True,
"loss_scale_window": 100
},
"gradient_clipping": 1.0,
"prescale_gradients": False,
"wall_clock_breakdown": False,
"hybrid_engine": {
"enabled": enable_hybrid_engine,
"inference_tp_size": inference_tp_size,
"release_inference_cache": release_inference_cache,
"pin_parameters": pin_parameters,
"tp_gather_partition_size": tp_gather_partition_size,
}
}
下面是訓練部分的實現代碼,需要注意的是,使用DS以后,訓練部分的代碼與標準的PyTorch代碼不同。
for epoch in range(args.num_train_epochs):
print_rank_0(
f"Beginning of Epoch {epoch+1}/{args.num_train_epochs}, Total Micro Batches {len(train_dataloader)}",
args.global_rank)
model.train()
for step, batch in enumerate(train_dataloader):
batch = to_device(batch, device)
outputs = model(**batch, use_cache=False)
loss = outputs.loss
model.backward(loss)
model.step()
關于**batch
解釋: 這個操作可以方便地將一個批次的數據傳遞給模型,避免手動拆分列表或元組,使代碼更加簡潔易讀。
*batch 表示將一個列表對象 batch 中的元素拆分成獨立的參數傳遞給函數或方法。例如:batch = (input_ids, attention_mask, labels)
那么,使用 *batch 時,實際上等價于將這些 Tensor 對象拆分為獨立的參數,即:model(*batch)
等價于 model(input_ids, attention_mask, labels)
**batch 表示將一個字典對象 batch 拆分成獨立的參數傳遞給函數或方法。例如:batch = {'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': labels}
model(**batch)
等價于model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
此任務通過 perplexity 來對模型進行評價。
# Evaluate perplexity on the validation set
perplexity = evaluation(model, eval_dataloader)
if args.output_dir is not None:
print_rank_0('saving the final model ...', args.global_rank)
model = convert_lora_to_linear_layer(model)
if args.global_rank == 0:
save_hf_format(model, tokenizer, args)
if args.zero_stage == 3:
# For zero stage 3, each gpu only has a part of the model, so we need a special save function
save_zero_three_model(model,
args.global_rank,
args.output_dir,
zero_stage=args.zero_stage)
根據上面的分析,對模型微調的完整流程如下:
數據部分
模型部分
訓練及評價部分
Q/A 1: 模型初始化時,定義了dschf = HfDeepSpeedConfig(ds_config),后面沒有調用。
當使用 zero 3 時需要設置 dschf = HfDeepSpeedConfig(ds_config)。
具體說明請參考:
https://huggingface.co/docs/transformers/main_classes/deepspeed#nontrainer-deepspeed-integration
Q/A 2: ZeRO 是什么?
ZeRO(Zero Redundancy Optimizer)是 DeepSpeed 庫中的一種優化技術,旨在提高大規模模型訓練的效率和可擴展性。其中,ZeRO Offload 是 ZeRO 技術的一種變體,可以通過將模型參數存儲在 CPU 上,從而減少模型訓練時對GPU顯存的占用,并加速模型參數的梯度累積、梯度壓縮和通信等操作。 ZeRO 3 是在大模型進行模型參數并行時使用。
文章轉載自: DeepSpeed-Chat 代碼分析