Accelerate documentation
Meluncurkan pelatihan terdistribusi dari Jupyter Notebooks
Meluncurkan pelatihan terdistribusi dari Jupyter Notebooks
Tutorial ini mengajarkan Anda cara melakukan fine-tuning model computer vision dengan 🤗 Accelerate dari Jupyter Notebook pada sistem terdistribusi. Anda juga akan belajar cara menyiapkan beberapa persyaratan yang diperlukan untuk memastikan lingkungan Anda dikonfigurasi dengan benar, data Anda telah disiapkan dengan baik, dan akhirnya cara meluncurkan pelatihan.
Tutorial ini juga tersedia sebagai Jupyter Notebook di sini
Mengonfigurasi Lingkungan
Sebelum pelatihan dapat dilakukan, file konfigurasi Accelerate harus ada di sistem. Biasanya, ini dapat dilakukan dengan menjalankan perintah berikut di terminal dan menjawab prompt yang muncul:
accelerate config
Namun, jika nilai default umum sudah cukup dan Anda tidak menjalankan pada TPU, Accelerate memiliki utilitas untuk dengan cepat menulis konfigurasi perangkat Anda ke file konfigurasi melalui utils.write_basic_config().
Kode berikut akan memulai ulang Jupyter setelah menulis konfigurasi, karena runtime CUDA atau runtime XPU dipanggil untuk melakukan hal ini.
CUDA dan XPU tidak dapat diinisialisasi lebih dari sekali pada sistem multi-perangkat. Tidak masalah untuk melakukan debug di notebook dan memiliki panggilan ke CUDA/XPU, tetapi untuk akhirnya melatih model, pembersihan penuh dan restart perlu dilakukan.
import os
from accelerate.utils import write_basic_config
write_basic_config() # Write a config file
os._exit(00) # Restart the notebookMenyiapkan Dataset dan Model
Selanjutnya, Anda harus menyiapkan dataset Anda. Seperti yang disebutkan sebelumnya, perhatian besar harus diberikan saat menyiapkan DataLoaders dan model untuk memastikan bahwa tidak ada yang ditempatkan di GPU mana pun.
Jika Anda melakukannya, disarankan untuk menempatkan kode spesifik tersebut ke dalam sebuah fungsi dan memanggilnya dari dalam antarmuka peluncur notebook, yang akan ditunjukkan nanti.
Pastikan dataset diunduh berdasarkan instruksi di sini
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_modelPertama, Anda perlu membuat fungsi untuk mengekstrak nama kelas berdasarkan nama file:
import os
data_dir = "../../images"
fnames = os.listdir(data_dir)
fname = fnames[0]
print(fname)beagle_32.jpg
Dalam kasus ini, labelnya adalah beagle. Dengan menggunakan regex, Anda dapat mengekstrak label dari nama file:
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)
Dan Anda dapat melihat bahwa nama file yang benar dikembalikan dengan tepat:
"beagle"Selanjutnya, kelas Dataset harus dibuat untuk menangani pengambilan gambar dan label:
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}Sekarang untuk membangun dataset. Di luar fungsi pelatihan, Anda dapat menemukan dan mendeklarasikan semua nama file dan label, lalu menggunakannya sebagai referensi di dalam fungsi yang diluncurkan:
fnames = [os.path.join("../../images", fname) for fname in fnames if fname.endswith(".jpg")]Selanjutnya, kumpulkan semua label:
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)}Selanjutnya, Anda harus membuat fungsi get_dataloaders yang akan mengembalikan dataloaders yang telah Anda buat. Seperti yang disebutkan sebelumnya, jika data secara otomatis dikirim ke perangkat GPU atau TPU saat membangun DataLoaders Anda, mereka harus dibangun menggunakan metode ini.
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_dataloaderTerakhir, Anda harus mengimpor scheduler yang akan digunakan nanti:
from torch.optim.lr_scheduler import CosineAnnealingLRMenulis Fungsi Pelatihan
Sekarang Anda dapat membangun loop pelatihan. notebook_launcher() bekerja dengan meneruskan fungsi yang akan dipanggil untuk dijalankan di seluruh sistem terdistribusi.
Berikut adalah loop pelatihan dasar untuk masalah klasifikasi hewan:
Kode telah dipecah untuk memungkinkan penjelasan pada setiap bagian. Versi lengkap yang dapat disalin dan ditempelkan akan tersedia di akhir
def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
set_seed(seed)
accelerator = Accelerator(mixed_precision=mixed_precision)Pertama, Anda harus mengatur seed dan membuat objek Accelerator sedini mungkin dalam loop pelatihan.
Jika melatih pada TPU, loop pelatihan Anda harus menerima model sebagai parameter dan model tersebut harus diinstansiasi di luar fungsi loop pelatihan. Lihat praktik terbaik TPU untuk mempelajari alasannya
Selanjutnya, Anda harus membangun dataloaders Anda dan membuat model Anda:
train_dataloader, eval_dataloader = get_dataloaders(batch_size)
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))Anda membangun model di sini agar seed juga mengontrol inisialisasi bobot baru
Karena Anda melakukan transfer learning dalam contoh ini, encoder dari model dimulai dalam keadaan beku sehingga head dari model hanya dapat dilatih pada awalnya:
for param in model.parameters():
param.requires_grad = False
for param in model.get_classifier().parameters():
param.requires_grad = TrueMenormalisasi batch gambar akan membuat pelatihan sedikit lebih cepat:
mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
std = torch.tensor(model.default_cfg["std"])[None, :, None, None]Untuk membuat konstanta ini tersedia pada perangkat aktif, Anda harus menyetelnya ke perangkat Accelerator:
mean = mean.to(accelerator.device)
std = std.to(accelerator.device)Selanjutnya, instansiasi kelas-kelas PyTorch lainnya yang digunakan untuk pelatihan:
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))Sebelum meneruskan semuanya ke prepare().
Tidak ada urutan khusus yang perlu diingat, Anda hanya perlu mengurai objek dalam urutan yang sama seperti saat Anda memberikannya ke metode prepare.
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
)Sekarang latih modelnya:
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()Loop evaluasi akan terlihat sedikit berbeda dibandingkan loop pelatihan. Jumlah elemen yang diteruskan serta akurasi total keseluruhan dari setiap batch akan ditambahkan ke dua konstanta:
model.eval()
accurate = 0
num_elems = 0Selanjutnya Anda memiliki sisa loop PyTorch standar Anda:
for batch in eval_dataloader:
inputs = (batch["image"] - mean) / std
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.argmax(dim=-1)Sebelum akhirnya, perbedaan besar terakhir.
Saat melakukan evaluasi terdistribusi, prediksi dan label perlu dilewatkan melalui gather() agar semua data tersedia pada perangkat saat ini dan metrik yang dihitung dengan benar dapat dicapai:
accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
num_elems += accurate_preds.shape[0]
accurate += accurate_preds.long().sum()Sekarang Anda hanya perlu menghitung metrik sebenarnya untuk masalah ini, dan Anda dapat mencetaknya pada proses utama menggunakan print():
eval_metric = accurate.item() / num_elems
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")Versi lengkap dari loop pelatihan ini tersedia di bawah ini:
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}")Menggunakan notebook_launcher
Yang tersisa hanyalah menggunakan notebook_launcher().
Anda melewatkan fungsi, argumen (sebagai tuple), dan jumlah proses untuk pelatihan. (Lihat dokumentasi untuk informasi lebih lanjut)
from accelerate import notebook_launcherargs = ("fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=2)Dalam kasus menjalankan di beberapa node, Anda perlu menyiapkan sesi Jupyter di setiap node dan menjalankan sel peluncuran secara bersamaan.
Untuk lingkungan yang berisi 2 node (komputer) dengan masing-masing 8 GPU dan komputer utama dengan alamat IP “172.31.43.8”, akan terlihat seperti ini:
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=0, num_nodes=2, num_processes=8)Dan di sesi Jupyter kedua di mesin lain:
Perhatikan bagaimana
node_ranktelah berubah
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=1, num_nodes=2, num_processes=8)Dalam kasus menjalankan pada TPU, akan terlihat seperti ini:
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)Untuk meluncurkan proses pelatihan dengan elastisitas, yang memungkinkan toleransi terhadap kegagalan, Anda dapat menggunakan fitur elastic_launch yang disediakan oleh PyTorch. Hal ini memerlukan pengaturan parameter tambahan seperti rdzv_backend dan max_restarts. Berikut adalah contoh cara menggunakan notebook_launcher dengan kemampuan elastis:
notebook_launcher(
training_loop,
args,
num_processes=2,
max_restarts=3
)Saat berjalan, proses ini akan mencetak progres serta menunjukkan berapa banyak perangkat yang digunakan. Tutorial ini dijalankan dengan dua 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.71Dan itu saja!
Perhatikan bahwa notebook_launcher() mengabaikan file konfigurasi Accelerate, untuk meluncurkan berdasarkan konfigurasi gunakan:
accelerate launch
Debugging
Masalah umum saat menjalankan notebook_launcher adalah munculnya pesan bahwa CUDA/XPU sudah diinisialisasi. Hal ini biasanya disebabkan oleh impor atau kode sebelumnya di notebook yang memanggil sublibrary PyTorch torch.cuda atau torch.xpu. Untuk membantu mengidentifikasi penyebabnya, Anda dapat menjalankan notebook_launcher dengan ACCELERATE_DEBUG_MODE=yes di lingkungan Anda, sehingga pemeriksaan tambahan akan dilakukan saat proses dimulai untuk memastikan bahwa proses reguler dapat dibuat dan memanfaatkan CUDA/XPU tanpa masalah. (Kode CUDA/XPU Anda tetap dapat dijalankan setelahnya).
Kesimpulan
Notebook ini menunjukkan cara melakukan pelatihan terdistribusi dari dalam Jupyter Notebook. Beberapa catatan penting untuk diingat:
- Pastikan untuk menyimpan semua kode yang menggunakan CUDA/XPU (atau impor CUDA/XPU) untuk fungsi yang diteruskan ke notebook_launcher()
- Atur
num_processesmenjadi jumlah perangkat yang digunakan untuk pelatihan (seperti jumlah GPU, XPU, CPU, TPU, dll) - Jika menggunakan TPU, deklarasikan model Anda di luar fungsi loop pelatihan