Accelerate documentation
Khởi động huấn luyện phân tán từ Jupyter Notebooks
Khởi động huấn luyện phân tán từ Jupyter Notebooks
Hướng dẫn này dạy bạn cách tinh chỉnh một mô hình thị giác máy tính bằng 🤗 Accelerate từ một Jupyter Notebook trên một hệ thống phân tán. Bạn cũng sẽ học cách thiết lập một số yêu cầu cần thiết để đảm bảo môi trường của bạn được cấu hình đúng, dữ liệu của bạn đã được chuẩn bị đúng, và cuối cùng là cách khởi động quá trình huấn luyện.
Hướng dẫn này cũng có sẵn dưới dạng Jupyter Notebook tại đây
Cấu hình Môi trường
Trước khi có thể thực hiện bất kỳ quá trình huấn luyện nào, một tệp cấu hình Accelerate phải tồn tại trong hệ thống. Thông thường, bạn có thể thực hiện điều này bằng cách chạy lệnh sau trong terminal và trả lời các câu hỏi:
accelerate config
Tuy nhiên, nếu các giá trị mặc định chung là phù hợp và bạn không chạy trên TPU, Accelerate có một tiện ích để nhanh chóng ghi cấu hình thiết bị của bạn vào tệp cấu hình thông qua utils.write_basic_config().
Đoạn mã sau sẽ khởi động lại Jupyter sau khi ghi cấu hình, vì CUDA runtime hoặc XPU runtime đã được gọi để thực hiện thao tác này.
CUDA và XPU không thể được khởi tạo nhiều hơn một lần trên hệ thống đa thiết bị. Việc gỡ lỗi trong notebook và thực hiện các lệnh gọi đến CUDA/XPU là hoàn toàn ổn, nhưng để cuối cùng huấn luyện được, cần phải thực hiện một quy trình dọn dẹp và khởi động lại hoàn toàn.
import os
from accelerate.utils import write_basic_config
write_basic_config() # Write a config file
os._exit(00) # Restart the notebookChuẩn bị Dữ liệu và Mô hình
Tiếp theo, bạn cần chuẩn bị bộ dữ liệu của mình. Như đã đề cập ở trên, cần hết sức cẩn thận khi chuẩn bị DataLoaders và mô hình để đảm bảo rằng không có gì được đặt lên bất kỳ GPU nào.
Nếu bạn có, việc khuyến nghị là đặt đoạn mã cụ thể đó vào một hàm và gọi hàm đó từ bên trong giao diện khởi chạy notebook, sẽ được giới thiệu sau.
Hãy đảm bảo rằng bộ dữ liệu đã được tải xuống theo các hướng dẫn tại đây
import os, re, torch, PIL
import numpy as np
from torch.optim.lr_scheduler import OneCycleLR
from torch.utils.data import DataLoader, Dataset
from torchvision.transforms import Compose, RandomResizedCrop, Resize, ToTensor
from accelerate import Accelerator
from accelerate.utils import set_seed
from timm import create_modelTrước tiên, bạn cần tạo một hàm để trích xuất tên lớp dựa trên tên tệp:
import os
data_dir = "../../images"
fnames = os.listdir(data_dir)
fname = fnames[0]
print(fname)beagle_32.jpg
Trong trường hợp này, nhãn là beagle. Sử dụng regex, bạn có thể trích xuất nhãn từ tên tệp:
import re
def extract_label(fname):
stem = fname.split(os.path.sep)[-1]
return re.search(r"^(.*)_\d+\.jpg$", stem).groups()[0]extract_label(fname)
Và bạn có thể thấy nó đã trả về đúng tên tệp của chúng ta:
"beagle"Tiếp theo, cần tạo một lớp Dataset để xử lý việc lấy ảnh và nhãn:
class PetsDataset(Dataset):
def __init__(self, file_names, image_transform=None, label_to_id=None):
self.file_names = file_names
self.image_transform = image_transform
self.label_to_id = label_to_id
def __len__(self):
return len(self.file_names)
def __getitem__(self, idx):
fname = self.file_names[idx]
raw_image = PIL.Image.open(fname)
image = raw_image.convert("RGB")
if self.image_transform is not None:
image = self.image_transform(image)
label = extract_label(fname)
if self.label_to_id is not None:
label = self.label_to_id[label]
return {"image": image, "label": label}Bây giờ đến bước xây dựng bộ dữ liệu. Bên ngoài hàm huấn luyện, bạn có thể tìm và khai báo tất cả các tên tệp và nhãn, sau đó sử dụng chúng như các tham chiếu bên trong hàm được khởi chạy:
fnames = [os.path.join("../../images", fname) for fname in fnames if fname.endswith(".jpg")]Tiếp theo, thu thập tất cả các nhãn:
all_labels = [extract_label(fname) for fname in fnames]
id_to_label = list(set(all_labels))
id_to_label.sort()
label_to_id = {lbl: i for i, lbl in enumerate(id_to_label)}Tiếp theo, bạn nên tạo một hàm get_dataloaders để trả về các dataloader đã xây dựng. Như đã đề cập ở trên, nếu dữ liệu được tự động gửi đến thiết bị GPU hoặc TPU khi xây dựng DataLoaders, chúng phải được xây dựng bằng phương pháp này.
def get_dataloaders(batch_size: int = 64):
"Builds a set of dataloaders with a batch_size"
random_perm = np.random.permutation(len(fnames))
cut = int(0.8 * len(fnames))
train_split = random_perm[:cut]
eval_split = random_perm[cut:]
# For training a simple RandomResizedCrop will be used
train_tfm = Compose([RandomResizedCrop((224, 224), scale=(0.5, 1.0)), ToTensor()])
train_dataset = PetsDataset([fnames[i] for i in train_split], image_transform=train_tfm, label_to_id=label_to_id)
# For evaluation a deterministic Resize will be used
eval_tfm = Compose([Resize((224, 224)), ToTensor()])
eval_dataset = PetsDataset([fnames[i] for i in eval_split], image_transform=eval_tfm, label_to_id=label_to_id)
# Instantiate dataloaders
train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, num_workers=4)
eval_dataloader = DataLoader(eval_dataset, shuffle=False, batch_size=batch_size * 2, num_workers=4)
return train_dataloader, eval_dataloaderCuối cùng, bạn nên import trình lập lịch (scheduler) để sử dụng sau:
from torch.optim.lr_scheduler import CosineAnnealingLRViết hàm huấn luyện
Bây giờ bạn có thể xây dựng vòng lặp huấn luyện. notebook_launcher() hoạt động bằng cách truyền vào một hàm cần gọi, hàm này sẽ được thực thi trên hệ thống phân tán.
Dưới đây là một vòng lặp huấn luyện cơ bản cho bài toán phân loại động vật:
Mã đã được tách ra để có thể giải thích từng phần. Một phiên bản đầy đủ có thể sao chép và dán sẽ có sẵn ở cuối
def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
set_seed(seed)
accelerator = Accelerator(mixed_precision=mixed_precision)Trước tiên, bạn nên đặt seed và tạo một đối tượng Accelerator càng sớm càng tốt trong vòng lặp huấn luyện.
Nếu huấn luyện trên TPU, vòng lặp huấn luyện của bạn nên nhận model dưới dạng tham số và model nên được khởi tạo bên ngoài hàm vòng lặp huấn luyện. Xem các thực hành tốt nhất cho TPU để tìm hiểu lý do
Tiếp theo, bạn nên xây dựng các dataloader và tạo mô hình của mình:
train_dataloader, eval_dataloader = get_dataloaders(batch_size)
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))Bạn xây dựng mô hình tại đây để hạt giống cũng kiểm soát việc khởi tạo trọng số mới
Vì bạn đang thực hiện transfer learning trong ví dụ này, encoder của mô hình ban đầu được đóng băng để chỉ phần head của mô hình được huấn luyện ban đầu:
for param in model.parameters():
param.requires_grad = False
for param in model.get_classifier().parameters():
param.requires_grad = TrueViệc chuẩn hóa các lô ảnh sẽ giúp quá trình huấn luyện nhanh hơn một chút:
mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
std = torch.tensor(model.default_cfg["std"])[None, :, None, None]Để các hằng số này có sẵn trên thiết bị đang hoạt động, bạn nên đặt chúng vào thiết bị của Accelerator:
mean = mean.to(accelerator.device)
std = std.to(accelerator.device)Tiếp theo, khởi tạo các lớp PyTorch còn lại được sử dụng cho quá trình huấn luyện:
optimizer = torch.optim.Adam(params=model.parameters(), lr=3e-2 / 25)
lr_scheduler = OneCycleLR(optimizer=optimizer, max_lr=3e-2, epochs=5, steps_per_epoch=len(train_dataloader))Trước khi truyền mọi thứ vào prepare().
Không có thứ tự cụ thể nào cần ghi nhớ, bạn chỉ cần giải nén các đối tượng theo đúng thứ tự mà bạn đã truyền cho phương thức prepare.
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
)Bây giờ huấn luyện mô hình:
for epoch in range(5):
model.train()
for batch in train_dataloader:
inputs = (batch["image"] - mean) / std
outputs = model(inputs)
loss = torch.nn.functional.cross_entropy(outputs, batch["label"])
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()Vòng lặp đánh giá sẽ có một số điểm khác biệt so với vòng lặp huấn luyện. Số lượng phần tử được truyền cũng như độ chính xác tổng thể của mỗi lô (batch) sẽ được cộng vào hai hằng số:
model.eval()
accurate = 0
num_elems = 0Tiếp theo, bạn có phần còn lại của vòng lặp PyTorch tiêu chuẩn:
for batch in eval_dataloader:
inputs = (batch["image"] - mean) / std
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.argmax(dim=-1)Trước khi đi đến sự khác biệt lớn cuối cùng.
Khi thực hiện đánh giá phân tán, các dự đoán và nhãn cần được truyền qua gather() để toàn bộ dữ liệu có sẵn trên thiết bị hiện tại và có thể đạt được chỉ số được tính toán chính xác:
accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
num_elems += accurate_preds.shape[0]
accurate += accurate_preds.long().sum()Bây giờ bạn chỉ cần tính toán chỉ số thực tế cho bài toán này, và bạn có thể in nó ra trên tiến trình chính bằng cách sử dụng print():
eval_metric = accurate.item() / num_elems
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")Phiên bản đầy đủ của vòng lặp huấn luyện này có sẵn bên dưới:
def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
set_seed(seed)
# Initialize accelerator
accelerator = Accelerator(mixed_precision=mixed_precision)
# Build dataloaders
train_dataloader, eval_dataloader = get_dataloaders(batch_size)
# Instantiate the model (you build the model here so that the seed also controls new weight initializations)
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))
# Freeze the base model
for param in model.parameters():
param.requires_grad = False
for param in model.get_classifier().parameters():
param.requires_grad = True
# You can normalize the batches of images to be a bit faster
mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
std = torch.tensor(model.default_cfg["std"])[None, :, None, None]
# To make these constants available on the active device, set it to the accelerator device
mean = mean.to(accelerator.device)
std = std.to(accelerator.device)
# Instantiate the optimizer
optimizer = torch.optim.Adam(params=model.parameters(), lr=3e-2 / 25)
# Instantiate the learning rate scheduler
lr_scheduler = OneCycleLR(optimizer=optimizer, max_lr=3e-2, epochs=5, steps_per_epoch=len(train_dataloader))
# Prepare everything
# There is no specific order to remember, you just need to unpack the objects in the same order you gave them to the
# prepare method.
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
)
# Now you train the model
for epoch in range(5):
model.train()
for batch in train_dataloader:
inputs = (batch["image"] - mean) / std
outputs = model(inputs)
loss = torch.nn.functional.cross_entropy(outputs, batch["label"])
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
model.eval()
accurate = 0
num_elems = 0
for batch in eval_dataloader:
inputs = (batch["image"] - mean) / std
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.argmax(dim=-1)
accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
num_elems += accurate_preds.shape[0]
accurate += accurate_preds.long().sum()
eval_metric = accurate.item() / num_elems
# Use accelerator.print to print only on the main process.
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")Sử dụng notebook_launcher
Còn lại chỉ là sử dụng notebook_launcher().
Bạn truyền vào hàm, các đối số (dưới dạng tuple) và số lượng tiến trình để huấn luyện. (Xem tài liệu để biết thêm thông tin)
from accelerate import notebook_launcherargs = ("fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=2)Trong trường hợp chạy trên nhiều nút, bạn cần thiết lập phiên Jupyter tại mỗi nút và chạy cell khởi chạy cùng một lúc.
Đối với một môi trường chứa 2 nút (máy tính) với 8 GPU mỗi nút và máy tính chính có địa chỉ IP là “172.31.43.8”, nó sẽ trông như sau:
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=0, num_nodes=2, num_processes=8)Và trong phiên Jupyter thứ hai trên máy khác:
Chú ý cách
node_rankđã thay đổi
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=1, num_nodes=2, num_processes=8)Trong trường hợp chạy trên TPU, nó sẽ trông như sau:
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))
args = (model, "fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=8)Để khởi chạy quy trình huấn luyện với tính linh hoạt, cho phép chịu lỗi, bạn có thể sử dụng tính năng elastic_launch do PyTorch cung cấp. Điều này yêu cầu thiết lập các tham số bổ sung như rdzv_backend và max_restarts. Dưới đây là một ví dụ về cách sử dụng notebook_launcher với khả năng linh hoạt:
notebook_launcher(
training_loop,
args,
num_processes=2,
max_restarts=3
)Trong quá trình chạy, nó sẽ in ra tiến độ cũng như cho biết bạn đã chạy trên bao nhiêu thiết bị. Bài hướng dẫn này được chạy trên hai GPU:
Launching training on 2 GPUs.
epoch 0: 88.12
epoch 1: 91.73
epoch 2: 92.58
epoch 3: 93.90
epoch 4: 94.71Và đó là tất cả!
Lưu ý rằng notebook_launcher() bỏ qua tệp cấu hình Accelerate, để khởi chạy dựa trên cấu hình hãy sử dụng:
accelerate launch
Gỡ lỗi
Một vấn đề phổ biến khi chạy notebook_launcher là gặp lỗi CUDA/XPU has already been initialized. Lỗi này thường bắt nguồn từ một lệnh import hoặc đoạn mã trước đó trong notebook đã gọi đến thư viện con torch.cuda hoặc torch.xpu của PyTorch. Để giúp xác định nguyên nhân, bạn có thể khởi chạy notebook_launcher với ACCELERATE_DEBUG_MODE=yes trong môi trường của mình, và một bước kiểm tra bổ sung sẽ được thực hiện khi khởi tạo để đảm bảo một tiến trình thông thường có thể được tạo ra và sử dụng CUDA/XPU mà không gặp sự cố. (Mã CUDA/XPU của bạn vẫn có thể được chạy sau đó).
Kết luận
Notebook này đã cho thấy cách thực hiện huấn luyện phân tán từ bên trong một Jupyter Notebook. Một số lưu ý chính cần ghi nhớ:
- Hãy đảm bảo lưu mọi mã sử dụng CUDA/XPU (hoặc các import CUDA/XPU) cho hàm được truyền vào notebook_launcher()
- Đặt
num_processesbằng số lượng thiết bị được sử dụng để huấn luyện (chẳng hạn như số lượng GPU, XPU, CPU, TPU, v.v.) - Nếu sử dụng TPU, hãy khai báo mô hình của bạn bên ngoài hàm vòng lặp huấn luyện