返回文章列表

大語言模型訓練資料準備與訓練迴圈實作

本文探討如何從零開始訓練大語言模型,包含資料準備、訓練迴圈定義、分散式訓練實作、超引數設定、日誌記錄、模型評估與儲存等關鍵步驟。文章詳細解析 ConstantLengthDataset 的實作細節,以及如何使用 Accelerate 函式庫實作多 GPU 分散式訓練,並結合 AdamW

機器學習 深度學習

本文介紹如何使用 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)

內容解密:

  1. tokenizer 處理輸入資料:使用 self.tokenizer 將緩衝區中的字串轉換成 token IDs。
  2. all_token_ids 串接 token IDs:將每個 tokenized_inputself.concat_token_id(通常是 EOS token)串接起來,存入 all_token_ids 列表中。
  3. 生成固定長度序列:以 self.seq_length 為單位,將 all_token_ids 切分成多個序列,並轉換成 PyTorch 張量輸出。
  4. 避免不完整序列:只有當序列長度等於 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()

內容解密:

  1. Accelerator 初始化:建立一個 Accelerator 物件,用於管理分散式訓練。
  2. prepare 方法:將模型、最佳化器和資料載入器準備好,並分配到適當的裝置上。
  3. 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__)
    # ...

內容解密:

  1. 超引數設定:將訓練相關的超引數儲存在 config 字典中,並轉換成 Namespace 物件以便存取。
  2. 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')

內容解密:

  1. accelerator.wait_for_everyone():確保所有處理程式同步完成,避免因非同步導致的模型儲存錯誤。
  2. unwrapped_model = accelerator.unwrap_model(model):解除模型的包裝,以便於儲存和推播到 Hugging Face Hub。
  3. unwrapped_model.save_pretrained("./"):將模型儲存到本地目錄。
  4. 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)

內容解密:

  1. AdamW:一種根據 Adam 最佳化器的變體,加入了權重衰減,能夠更好地控制模型的過擬合。
  2. CosineAnnealingLR:餘弦退火學習率排程策略,能夠動態調整學習率,促進模型收斂。

分散式訓練與梯度累積

由於大型模型的記憶體需求較高,需要採用分散式訓練和梯度累積技術來解決單一 GPU 記憶體不足的問題。

# 梯度累積組態
gradient_accumulation_steps = 16

內容解密:

  1. gradient_accumulation_steps:設定梯度累積的步數,將多個批次的梯度累積後再進行一次引數更新,有效減少記憶體使用。

結果與分析

經過一週的訓練後,模型的損失曲線和困惑度曲線如圖 10-7 所示。從圖中可以看出,訓練損失和驗證困惑度不斷下降,大型模型在處理 tokens 時收斂速度更快。

圖表說明

此圖示展示了訓練損失和驗證困惑度的變化趨勢。

透過本文的介紹,我們瞭解瞭如何使用分散式基礎設施訓練大語言模型,包括模型的儲存、最佳化和評估等關鍵步驟。這些技術和方法能夠有效地提高模型的訓練效率和效果,為自然語言處理任務提供更強大的支援。