--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

結(jié)合 main.py 程序,我們可以將參數(shù)分為三大類(lèi)

與數(shù)據(jù)相關(guān)的參數(shù)

data_path        : 數(shù)據(jù)路徑,huggingface數(shù)據(jù)庫(kù), 比如:Dahoas/rm-static
data_split : 數(shù)據(jù)的拆分方式,比如 2,4,4 是為step1,2,3分配的數(shù)據(jù)比例
max_seq_len : 最大序列長(zhǎng)度(超過(guò)長(zhǎng)度會(huì)被截掉)
data_output_path : 相關(guān)數(shù)據(jù)的存儲(chǔ)地址(local storage,不能是shared storage)

與模型相關(guān)的參數(shù)

model_name_or_path : 模型名稱(chēng)或路徑,huggingface模型,比如:facebook/opt-1.3b
lora_dim : 如果大于0,則使用LoRA優(yōu)化
lora_module_name : 設(shè)置LoRA的范圍,比如可以只針對(duì) decoder.layers
only_optimize_lora : 是否只優(yōu)化LoRA的參數(shù)

與訓(xùn)練相關(guān)的參數(shù)

per_device_train_batch_size : 訓(xùn)練時(shí)的 Batch size (per device: 每個(gè)GPU的Size)
per_device_eval_batch_size : 評(píng)價(jià)時(shí)的 Batch size (per device)
learning_rate : 學(xué)習(xí)率
weight_decay : 權(quán)重衰減,防止模型過(guò)擬合的技術(shù)。
num_train_epochs : 訓(xùn)練 epoch 數(shù)
gradient_accumulation_steps : 累積多少個(gè) mini-batch 的梯度后再進(jìn)行一次參數(shù)更新。
lr_scheduler_type : learning rate的調(diào)整策略,比如 linear, cosine

deepspeed

zero_stage  : 這個(gè)對(duì)應(yīng)者DeepSpeed工具中的zero方式,分別是0,1,2,3
offload : ZeRO-Offload 通過(guò)利用主機(jī)CPU上的計(jì)算和內(nèi)存資源來(lái)執(zhí)行優(yōu)化器,從而減少此類(lèi)模型的GPU計(jì)算和內(nèi)存需求。
local_rank : 分布式訓(xùn)練時(shí)的一個(gè)變量,用于標(biāo)識(shí)當(dāng)前 GPU 設(shè)備的本地排名(本機(jī)排名,與global-rank不同)
gradient_checkpointing : 降低深度學(xué)習(xí)模型訓(xùn)練過(guò)程中內(nèi)存消耗的技術(shù)

其他

seed        : 隨機(jī)排序是的seed
output_dir : 模型的存儲(chǔ)目錄

3、分布式訓(xùn)練的基本概念

args.local_rank
local_rank 在分布式訓(xùn)練時(shí)使用的一個(gè)變量,用于標(biāo)識(shí)當(dāng)前 GPU 設(shè)備的本地排名(local rank)。

當(dāng) args.local_rank 等于 -1 時(shí),表示代碼不在分布式設(shè)置下運(yùn)行,僅使用單個(gè) GPU 進(jìn)行訓(xùn)練。如果 args.local_rank 不等于 -1,則表示代碼在分布式設(shè)置下運(yùn)行,當(dāng)前 GPU 設(shè)備被分配了一個(gè)唯一的本地排名。代碼會(huì)將設(shè)備設(shè)置為指定的 GPU(torch.device(“cuda”, args.local_rank)),并使用 deepspeed.init_distributed() 函數(shù)調(diào)用初始化分布式后端。

注意: 在 PyTorch 中也有分布式初始化方法 torch.distributed.init_process_group() 函數(shù)。但是當(dāng)使用 DeepSpeed 庫(kù)時(shí),不要替換為 deepspeed.init_distributed()。

args.global_rank
在分布式訓(xùn)練中,每個(gè)進(jìn)程都有一個(gè)唯一的全局排名,用于標(biāo)識(shí)該進(jìn)程在分布式環(huán)境中的位置。全局排名的范圍是從0到world_size-1,其中 world_size 是整個(gè)分布式環(huán)境中進(jìn)程的總數(shù)。
本程序中通過(guò) torch.distributed.get_rank() 來(lái)讀取 global_rank, 本函數(shù)在初始化分布式后端之后才能調(diào)用。

torch.distributed.barrier()
torch.distributed.barrier() 是一個(gè)同步函數(shù),用于在分布式環(huán)境中同步各個(gè)進(jìn)程的狀態(tài)。在調(diào)用該函數(shù)時(shí),進(jìn)程會(huì)阻塞等待,直到所有進(jìn)程都調(diào)用了該函數(shù)之后,才會(huì)解除阻塞并繼續(xù)執(zhí)行后面的代碼。

在分布式訓(xùn)練中,torch.distributed.barrier() 通常用于同步各個(gè)進(jìn)程的梯度更新。在每個(gè)進(jìn)程完成一輪前向傳播和反向傳播后,它們需要同步各自的梯度,并且等待其他進(jìn)程完成同樣的操作,才能進(jìn)行下一輪更新。這時(shí)就可以使用 torch.distributed.barrier() 函數(shù)實(shí)現(xiàn)同步。

另外一個(gè)用法,在模型參數(shù)并行訓(xùn)練時(shí),數(shù)據(jù)的讀取只需要在 local_rank 為 0 的GPU上進(jìn)行,其他進(jìn)程使用 torch.distributed.barrier() 來(lái)阻塞來(lái)等待數(shù)據(jù)讀取完成。

4、代碼:數(shù)據(jù)相關(guān)

現(xiàn)在的NLP模型訓(xùn)練時(shí),普遍的做法是,首先將文本轉(zhuǎn)換為T(mén)oken。Token通常是指一個(gè)單詞或者字的一部分。將本文轉(zhuǎn)換為T(mén)oken時(shí)會(huì)使用到 tokenizer,tokenizer是在訓(xùn)練數(shù)據(jù)上統(tǒng)計(jì)并訓(xùn)練出來(lái)的。有很多工具可以來(lái)訓(xùn)練tokenizer,比如 Google 的 sentencepiece。下面給出了GPT-3的Token的例子。比如:

你可以通過(guò)OpenAI網(wǎng)站來(lái)體驗(yàn)Token的拆分:https://platform.openai.com/tokenizer

DS-Chat工具中訓(xùn)練時(shí)使用的 tokenizer 是來(lái)自預(yù)訓(xùn)練模型,這段代碼使用了 Hugging Face Transformers 庫(kù)中的 AutoTokenizer 類(lèi),用于實(shí)例化一個(gè)預(yù)訓(xùn)練模型的 tokenizer。AutoTokenizer 類(lèi)可以自動(dòng)選擇并加載對(duì)應(yīng)的 tokenizer,從而避免了手動(dòng)選擇的步驟。

tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path, fast_tokenizer=True)
tokenizer.pad_token = tokenizer.eos_token

AutoTokenizer.from_pretrained() 函數(shù)有兩個(gè)必選參數(shù),model_name_or_path 是預(yù)訓(xùn)練模型的名稱(chēng)或路徑,例如 “bert-base-uncased” 或 “/path/to/model/directory”。 fast_tokenizer: 是否使用快速 tokenizer。如果為 True,則會(huì)選擇使用 Rust 實(shí)現(xiàn)的 tokenizer,速度更快;否則使用 Python 實(shí)現(xiàn)的 tokenizer。默認(rèn)為 True。

數(shù)據(jù)準(zhǔn)備函數(shù):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 參數(shù)是為了讓數(shù)據(jù)下載等基本處理,只在local rank為 0 的 GPU 上執(zhí)行。也就是每個(gè)node上只處理一次數(shù)據(jù)即可。 data_output_path 需要設(shè)定為 local storage path,應(yīng)該是為了在分布式訓(xùn)練時(shí)存儲(chǔ)本地?cái)?shù)據(jù)用的。

然后是初始化sampler,單GPU使用RandomSampler和SequentialSampler,分布式處理使用DistributedSampler。sampler 主要用來(lái)設(shè)置數(shù)據(jù)采用的順序。比如隨機(jī)采樣來(lái)提高模型的魯棒性。

# 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)

數(shù)據(jù)讀取使用 PyTorch 標(biāo)準(zhǔn)的 DataLoader 來(lái)處理。使用Dataloader 不僅可以設(shè)置sampler定義采樣方式,還可以自動(dòng)進(jìn)行批處理,并且支持多進(jìn)程數(shù)據(jù)加載。

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)

5、代碼:模型相關(guān)

模型初始化
以下的代碼用來(lái)對(duì)模型進(jìn)行初始化。

model = create_hf_model(AutoModelForCausalLM, args.model_name_or_path,
tokenizer, ds_config)

其中 AutoModelForCausalLM 是 Hugging Face Transformers 庫(kù)中的一個(gè)類(lèi),能夠自動(dòng)選擇并加載適當(dāng)?shù)念A(yù)訓(xùn)練 Transformer 模型,它支持多種預(yù)訓(xùn)練 Transformer 模型,包括 GPT-2、GPT、CTRL、Transformer-XL、XLNet 和 XLM 等。使用該類(lèi)時(shí),您只需指定模型的名稱(chēng)或路徑即可自動(dòng)加載對(duì)應(yīng)的模型。

具體實(shí)現(xiàn)代碼,可以參考:utils/model/model_utils.py。

LoRA
當(dāng)設(shè)置 lora_dim 大于0時(shí),將會(huì)使用LoRA技術(shù)對(duì)模型進(jìn)行調(diào)整。 從而讓模型的優(yōu)化參數(shù)大幅度的變少,改善優(yōu)化的效率。通常情況下,使用LoRA技術(shù),不僅可以減少參數(shù)量,還能進(jìn)一步改善性能。這主要是因?yàn)?,這種bottleneck的網(wǎng)絡(luò)設(shè)計(jì),可以防止過(guò)擬合,從而提高模型的魯棒性。

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)

提取需要被優(yōu)化的參數(shù) 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() 函數(shù)被用來(lái)將權(quán)重分成兩組,一組需要應(yīng)用權(quán)重衰減,另一組則不需要。該函數(shù)通過(guò)遍歷模型的所有參數(shù),并檢查參數(shù)名稱(chēng)是否包含 bias 或 LayerNorm 等特殊字符串,來(lái)區(qū)分需要應(yīng)用權(quán)重衰減的參數(shù)和不需要的參數(shù)。

分組原因的解說(shuō): 一般來(lái)說(shuō),對(duì)于參數(shù)名稱(chēng)中不包含 bias 或 LayerNorm 等特殊字符串的參數(shù),我們認(rèn)為它們是需要應(yīng)用權(quán)重衰減的參數(shù)。對(duì)于這些參數(shù),通常會(huì)將它們的權(quán)重矩陣與權(quán)重衰減超參數(shù)相乘,以降低它們的權(quán)重。與此相反,對(duì)于參數(shù)名稱(chēng)中包含 bias 或 LayerNorm 等特殊字符串的參數(shù),我們認(rèn)為它們是不需要應(yīng)用權(quán)重衰減的參數(shù)。這是因?yàn)?bias 或 LayerNorm 參數(shù)通常只是用來(lái)偏移或縮放其他層的輸出,而不是真正的權(quán)重參數(shù)。通過(guò)將權(quán)重分成兩組,并分別應(yīng)用權(quán)重衰減和不應(yīng)用權(quán)重衰減,我們可以更好地控制模型的復(fù)雜度,從而提高模型的泛化性能。

然后設(shè)置Optimizer優(yōu)化器,根據(jù)參數(shù)不同會(huì)選擇 DeepSpeedCPUAdam 或者 FusedAdam 優(yōu)化器。 并傳入了一些參數(shù),包括分組的參數(shù)、學(xué)習(xí)率和 betas。

Adam優(yōu)化器的解說(shuō): 在 Hugging Face 的 Transformers 庫(kù)中,有兩種 Adam 優(yōu)化器可供選擇:FusedAdam 和 DeepSpeedCPUAdam。它們都是基于 PyTorch 實(shí)現(xiàn)的優(yōu)化器,但在不同的硬件上具有不同的優(yōu)化和性能特征。FusedAdam 是使用 NVIDIA Apex 庫(kù)實(shí)現(xiàn)的優(yōu)化器,它支持混合精度訓(xùn)練,并且可以同時(shí)計(jì)算梯度和權(quán)重更新操作,從而提高訓(xùn)練效率。FusedAdam 優(yōu)化器在使用支持 CUDA 的 NVIDIA GPU 時(shí)具有較好的性能。DeepSpeedCPUAdam 是一種 CPU 上的優(yōu)化器,它是 DeepSpeed 框架中的一部分,支持分布式訓(xùn)練和模型平行化。DeepSpeedCPUAdam 優(yōu)化器在使用 CPU 時(shí)具有較好的性能。在上面的代碼中,如果 args.offload 為 True,則表示使用基于 CPU 的優(yōu)化,因此會(huì)選擇使用 DeepSpeedCPUAdam 優(yōu)化器。

設(shè)置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 是用來(lái)規(guī)劃整個(gè)訓(xùn)練過(guò)程中 lr 是如何調(diào)整的。lr_scheduler_type 調(diào)度器類(lèi)型,用來(lái)描述 lr 是按照什么樣的方式變化,例如 LinearWarmup、CosineAnnealing 等。num_warmup_steps 預(yù)熱步數(shù)指定了在訓(xùn)練的前期階段 lr 增加過(guò)程的步數(shù)。 總訓(xùn)練步數(shù)指定模型共被更新多少次。

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進(jìn)行優(yōu)化是,需要使用deepspeed.initialize() 函數(shù)來(lái)初始化模型、優(yōu)化器、學(xué)習(xí)率調(diào)度器等訓(xùn)練相關(guān)的組件。其中,model 和 optimizer 是必需的參數(shù),而其他參數(shù)則是可選的。

deepspeed.initialize() 函數(shù)會(huì)對(duì)傳入的參數(shù)進(jìn)行檢查和優(yōu)化,并返回新的模型、優(yōu)化器和學(xué)習(xí)率調(diào)度器等組件。例如,它會(huì)根據(jù)訓(xùn)練參數(shù)設(shè)置和硬件配置自動(dòng)調(diào)整優(yōu)化器和梯度累積的設(shè)置,并設(shè)置模型權(quán)重的分布式訓(xùn)練策略。dist_init_required=True 參數(shù)指示 DeepSpeed 是否需要進(jìn)行分布式訓(xùn)練初始化。

DS 配置文件
配置文件包含DeepSpeed模型訓(xùn)練時(shí)所需要的相關(guān)設(shè)置信息,可以通過(guò)這里的修改來(lái)調(diào)整訓(xùn)練過(guò)程。下面是 utils/ds_utils.py 中給出的設(shè)置 :

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,
}
}

6、代碼:訓(xùn)練

下面是訓(xùn)練部分的實(shí)現(xiàn)代碼,需要注意的是,使用DS以后,訓(xùn)練部分的代碼與標(biāo)準(zhǔn)的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()

關(guān)于**batch解釋?zhuān)?/em> 這個(gè)操作可以方便地將一個(gè)批次的數(shù)據(jù)傳遞給模型,避免手動(dòng)拆分列表或元組,使代碼更加簡(jiǎn)潔易讀。

*batch 表示將一個(gè)列表對(duì)象 batch 中的元素拆分成獨(dú)立的參數(shù)傳遞給函數(shù)或方法。例如:
batch = (input_ids, attention_mask, labels)
那么,使用 *batch 時(shí),實(shí)際上等價(jià)于將這些 Tensor 對(duì)象拆分為獨(dú)立的參數(shù),即:
model(*batch) 等價(jià)于 model(input_ids, attention_mask, labels)

**batch 表示將一個(gè)字典對(duì)象 batch 拆分成獨(dú)立的參數(shù)傳遞給函數(shù)或方法。例如:
batch = {'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': labels}
model(**batch) 等價(jià)于
model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)

7、代碼:評(píng)價(jià)

此任務(wù)通過(guò) perplexity 來(lái)對(duì)模型進(jìn)行評(píng)價(jià)。

# Evaluate perplexity on the validation set
perplexity = evaluation(model, eval_dataloader)

8、代碼:模型的保存

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)

9、流程總結(jié):使用DeepSpeed進(jìn)行模型微調(diào)

根據(jù)上面的分析,對(duì)模型微調(diào)的完整流程如下:
數(shù)據(jù)部分

模型部分

訓(xùn)練及評(píng)價(jià)部分

10、常見(jiàn)問(wèn)題

Q/A 1: 模型初始化時(shí),定義了dschf = HfDeepSpeedConfig(ds_config),后面沒(méi)有調(diào)用。
當(dāng)使用 zero 3 時(shí)需要設(shè)置 dschf = HfDeepSpeedConfig(ds_config)。
具體說(shuō)明請(qǐng)參考:
https://huggingface.co/docs/transformers/main_classes/deepspeed#nontrainer-deepspeed-integration
Q/A 2: ZeRO 是什么?
ZeRO(Zero Redundancy Optimizer)是 DeepSpeed 庫(kù)中的一種優(yōu)化技術(shù),旨在提高大規(guī)模模型訓(xùn)練的效率和可擴(kuò)展性。其中,ZeRO Offload 是 ZeRO 技術(shù)的一種變體,可以通過(guò)將模型參數(shù)存儲(chǔ)在 CPU 上,從而減少模型訓(xùn)練時(shí)對(duì)GPU顯存的占用,并加速模型參數(shù)的梯度累積、梯度壓縮和通信等操作。 ZeRO 3 是在大模型進(jìn)行模型參數(shù)并行時(shí)使用。

參考文獻(xiàn)

文章轉(zhuǎn)載自: DeepSpeed-Chat 代碼分析

#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門(mén)場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)