本文介紹如何使用 PyTorch 和 Hugging Face 的 Transformers 與 Accelerate 函式庫,從零開始訓練大語言模型。首先,我們使用 ConstantLengthDataset 將輸入資料轉換成固定長度序列,確保模型輸入一致性。接著,利用 Accelerate 函式庫簡化分散式訓練流程,將模型、最佳化器和資料載入器分配到多個 GPU 上,並使用 accelerator.backward 方法計算梯度並同步更新引數。為了監控訓練過程,我們設定了相關超引數,例如批次大小、學習率、權重衰減等,並使用日誌記錄功能追蹤訓練過程中的損失、困惑度等指標。最後,文章也說明瞭如何評估模型效能、儲存模型檢查點,以及如何將訓練好的模型推播到 Hugging Face Hub。
從零開始訓練語言模型:資料準備與訓練迴圈實作
在前面的章節中,我們已經瞭解瞭如何建立一個可靠的資料來源供模型訓練使用。現在,我們將進一步探討如何定義訓練迴圈,並利用 Accelerate 函式庫實作分散式訓練,以充分利用多個 GPU 的運算能力。
資料準備:ConstantLengthDataset
首先,我們來檢視 ConstantLengthDataset 類別的實作,它負責將輸入的資料轉換成固定長度的序列。這對於訓練語言模型至關重要,因為模型需要處理不同長度的輸入資料。
tokenized_inputs = self.tokenizer(buffer, truncation=False)
for tokenized_input in tokenized_inputs["input_ids"]:
all_token_ids.extend(tokenized_input + [self.concat_token_id])
for i in range(0, len(all_token_ids), self.seq_length):
input_ids = all_token_ids[i : i + self.seq_length]
if len(input_ids) == self.seq_length:
yield torch.tensor(input_ids)
內容解密:
tokenizer處理輸入資料:使用self.tokenizer將緩衝區中的字串轉換成 token IDs。all_token_ids串接 token IDs:將每個tokenized_input與self.concat_token_id(通常是 EOS token)串接起來,存入all_token_ids列表中。- 生成固定長度序列:以
self.seq_length為單位,將all_token_ids切分成多個序列,並轉換成 PyTorch 張量輸出。 - 避免不完整序列:只有當序列長度等於
self.seq_length時,才會被輸出,確保每個序列都是完整的。
測試 ConstantLengthDataset
為了驗證 ConstantLengthDataset 的正確性,我們進行了簡單的測試:
shuffled_dataset = dataset.shuffle(buffer_size=100)
constant_length_dataset = ConstantLengthDataset(tokenizer, shuffled_dataset, num_of_sequences=10)
dataset_iterator = iter(constant_length_dataset)
lengths = [len(b) for _, b in zip(range(5), dataset_iterator)]
print(f"Lengths of the sequences: {lengths}")
輸出結果顯示,所有序列的長度均為 1024,符合預期。
定義訓練迴圈
接下來,我們將定義訓練迴圈,並利用 Accelerate 函式庫實作分散式訓練。
使用 Accelerate 實作分散式訓練
Accelerate 提供了一個簡單的 API,可以讓我們輕鬆地將訓練指令碼擴充套件到多個 GPU 或 TPU 上。以下是修改後的 PyTorch 訓練迴圈:
import torch
import torch.nn.functional as F
from datasets import load_dataset
from accelerate import Accelerator
accelerator = Accelerator()
model = torch.nn.Transformer()
optimizer = torch.optim.Adam(model.parameters())
dataset = load_dataset('my_dataset')
data = torch.utils.data.DataLoader(dataset, shuffle=True)
model, optimizer, data = accelerator.prepare(model, optimizer, data)
model.train()
for epoch in range(10):
for source, targets in data:
optimizer.zero_grad()
output = model(source)
loss = F.cross_entropy(output, targets)
accelerator.backward(loss)
optimizer.step()
內容解密:
Accelerator初始化:建立一個Accelerator物件,用於管理分散式訓練。prepare方法:將模型、最佳化器和資料載入器準備好,並分配到適當的裝置上。accelerator.backward方法:替代原生的loss.backward()方法,用於計算梯度並同步更新引數。
設定超引數與日誌記錄
為了更好地監控訓練過程,我們設定了一些超引數,並實作了日誌記錄功能:
from argparse import Namespace
config = {
"train_batch_size": 2,
"valid_batch_size": 2,
"weight_decay": 0.1,
"shuffle_buffer": 1000,
"learning_rate": 2e-4,
"lr_scheduler_type": "cosine",
"num_warmup_steps": 750,
"gradient_accumulation_steps": 16,
"max_train_steps": 50000,
"max_eval_steps": -1,
"seq_length": 1024,
"seed": 1,
"save_checkpoint_steps": 50000
}
args = Namespace(**config)
def setup_logging(project_name):
# 設定日誌記錄
logger = logging.getLogger(__name__)
# ...
內容解密:
- 超引數設定:將訓練相關的超引數儲存在
config字典中,並轉換成Namespace物件以便存取。 setup_logging方法:設定日誌記錄,包括 Python Logger、TensorBoard 和 Weights & Biases,以監控訓練過程。
綜上所述,我們已經完成了資料準備、訓練迴圈定義和日誌記錄設定的工作,為從零開始訓練語言模型奠定了基礎。接下來,我們將進一步探討如何利用這些元件進行有效的模型訓練。
從零開始訓練模型:技術深度解析與實踐
在人工智慧領域中,訓練大語言模型是一項複雜且具有挑戰性的任務。本文將探討如何使用Hugging Face的Transformers函式庫和Accelerate函式庫從零開始訓練一個根據轉換器(Transformer)架構的語言模型。我們將詳細分析程式碼的每個部分,並提供技術深度解析。
設定日誌記錄與TensorBoard
首先,我們需要設定日誌記錄和TensorBoard,以便在訓練過程中監控模型的效能。
datasets.utils.logging.set_verbosity_error()
transformers.utils.logging.set_verbosity_error()
return logger, tb_writer, run_name
內容解密:
datasets.utils.logging.set_verbosity_error()和transformers.utils.logging.set_verbosity_error()用於設定日誌記錄的層級,只顯示錯誤訊息,以減少輸出訊息的數量。- 這兩個函式呼叫確保了在訓練過程中,只有錯誤訊息才會被記錄和顯示,從而簡化了除錯過程。
建立資料載入器
接下來,我們需要建立訓練和驗證資料的載入器。
from torch.utils.data.dataloader import DataLoader
def create_dataloaders(dataset_name):
train_data = load_dataset(dataset_name+'-train', split="train", streaming=True)
train_data = train_data.shuffle(buffer_size=args.shuffle_buffer, seed=args.seed)
valid_data = load_dataset(dataset_name+'-valid', split="validation", streaming=True)
train_dataset = ConstantLengthDataset(tokenizer, train_data, seq_length=args.seq_length)
valid_dataset = ConstantLengthDataset(tokenizer, valid_data, seq_length=args.seq_length)
train_dataloader = DataLoader(train_dataset, batch_size=args.train_batch_size)
eval_dataloader = DataLoader(valid_dataset, batch_size=args.valid_batch_size)
return train_dataloader, eval_dataloader
內容解密:
load_dataset函式用於載入訓練和驗證資料集。ConstantLengthDataset類別用於建立固定長度的資料集,這有助於在訓練過程中保持批次的一致性。DataLoader類別用於建立資料載入器,它負責將資料集分成批次並在訓練過程中提供給模型。
最佳化器與學習率排程器
為了最佳化模型的引數,我們需要建立一個最佳化器和一個學習率排程器。
def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]):
params_with_wd, params_without_wd = [], []
for n, p in model.named_parameters():
if any(nd in n for nd in no_decay):
params_without_wd.append(p)
else:
params_with_wd.append(p)
return [{'params': params_with_wd, 'weight_decay': args.weight_decay},
{'params': params_without_wd, 'weight_decay': 0.0}]
內容解密:
get_grouped_params函式用於將模型的引數分成兩組:需要權重衰減(weight decay)的引數和不需要權重衰減的引數。- 通常,偏差(bias)和LayerNorm的權重不需要權重衰減。
模型評估
在訓練過程中,我們需要定期評估模型的效能。
def evaluate():
model.eval()
losses = []
for step, batch in enumerate(eval_dataloader):
with torch.no_grad():
outputs = model(batch, labels=batch)
loss = outputs.loss.repeat(args.valid_batch_size)
losses.append(accelerator.gather(loss))
if args.max_eval_steps > 0 and step >= args.max_eval_steps: break
loss = torch.mean(torch.cat(losses))
try:
perplexity = torch.exp(loss)
except OverflowError:
perplexity = torch.tensor(float("inf"))
return loss.item(), perplexity.item()
內容解密:
evaluate函式用於評估模型在驗證資料集上的效能。- 我們計算了模型的損失(loss)和困惑度(perplexity),困惑度越低,代表模型的效能越好。
訓練模型
最後,我們可以開始訓練模型了。
set_seed(args.seed)
accelerator = Accelerator()
# ...
model.train()
completed_steps = 0
for step, batch in enumerate(train_dataloader, start=1):
loss = model(batch, labels=batch).loss
log_metrics(step, {'lr': get_lr(), 'samples': step*samples_per_step,
'steps': completed_steps, 'loss/train': loss.item()})
loss = loss / args.gradient_accumulation_steps
accelerator.backward(loss)
if step % args.gradient_accumulation_steps == 0:
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
completed_steps += 1
# ...
內容解密:
- 我們使用Accelerate函式庫來簡化分散式訓練的過程。
- 在每個訓練步驟中,我們計算了模型的損失,並根據損失更新了模型的引數。
從零開始訓練大語言模型
訓練大語言模型是一項複雜且資源密集的任務,需要仔細規劃和執行。本文將探討使用分散式基礎設施訓練大語言模型的過程,並重點介紹關鍵程式碼片段和技術考量。
模型儲存與檢查點
在訓練過程中,模型的儲存和檢查點的建立至關重要。以下程式碼片段展示瞭如何在訓練結束後評估和儲存模型:
# 評估並儲存最後的檢查點
logger.info('Evaluating and saving model after training')
eval_loss, perplexity = evaluate()
log_metrics(step, {'loss/eval': eval_loss, 'perplexity': perplexity})
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
if accelerator.is_main_process:
unwrapped_model.save_pretrained("./")
hf_repo.push_to_hub(commit_message=f'final model')
內容解密:
accelerator.wait_for_everyone():確保所有處理程式同步完成,避免因非同步導致的模型儲存錯誤。unwrapped_model = accelerator.unwrap_model(model):解除模型的包裝,以便於儲存和推播到 Hugging Face Hub。unwrapped_model.save_pretrained("./"):將模型儲存到本地目錄。hf_repo.push_to_hub(commit_message=f'final model'):將模型推播到 Hugging Face Hub,並附上提交訊息。
最佳化與評估
最佳化器的選擇和超引數的設定對模型的訓練效果有著重要影響。本文采用 AdamW 最佳化器,並結合餘弦學習率排程策略。
# 最佳化器組態
optimizer = AdamW(model.parameters(), lr=1e-4)
scheduler = CosineAnnealingLR(optimizer, T_max=1000)
內容解密:
AdamW:一種根據 Adam 最佳化器的變體,加入了權重衰減,能夠更好地控制模型的過擬合。CosineAnnealingLR:餘弦退火學習率排程策略,能夠動態調整學習率,促進模型收斂。
分散式訓練與梯度累積
由於大型模型的記憶體需求較高,需要採用分散式訓練和梯度累積技術來解決單一 GPU 記憶體不足的問題。
# 梯度累積組態
gradient_accumulation_steps = 16
內容解密:
gradient_accumulation_steps:設定梯度累積的步數,將多個批次的梯度累積後再進行一次引數更新,有效減少記憶體使用。
結果與分析
經過一週的訓練後,模型的損失曲線和困惑度曲線如圖 10-7 所示。從圖中可以看出,訓練損失和驗證困惑度不斷下降,大型模型在處理 tokens 時收斂速度更快。
圖表說明
此圖示展示了訓練損失和驗證困惑度的變化趨勢。
透過本文的介紹,我們瞭解瞭如何使用分散式基礎設施訓練大語言模型,包括模型的儲存、最佳化和評估等關鍵步驟。這些技術和方法能夠有效地提高模型的訓練效率和效果,為自然語言處理任務提供更強大的支援。