# 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](https://github.com/huggingface/notebooks/blob/main/examples/accelerate_examples/simple_cv_example.ipynb)

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

```bash
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()](/docs/accelerate/main/id/package_reference/utilities#accelerate.commands.config.default.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.
    

```python
import os
from accelerate.utils import write_basic_config

write_basic_config()  # Write a config file
os._exit(00)  # Restart the notebook
```

## Menyiapkan 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](https://github.com/huggingface/accelerate/tree/main/examples#simple-vision-example)

```python
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_model
```

Pertama, Anda perlu membuat fungsi untuk mengekstrak nama kelas berdasarkan nama file:

```python
import os

data_dir = "../../images"
fnames = os.listdir(data_dir)
fname = fnames[0]
print(fname)
```

```python out
beagle_32.jpg
```

Dalam kasus ini, labelnya adalah `beagle`. Dengan menggunakan regex, Anda dapat mengekstrak label dari nama file:

```python
import re

def extract_label(fname):
    stem = fname.split(os.path.sep)[-1]
    return re.search(r"^(.*)_\d+\.jpg$", stem).groups()[0]
```

```python
extract_label(fname)
```

Dan Anda dapat melihat bahwa nama file yang benar dikembalikan dengan tepat:

```python out
"beagle"
```

Selanjutnya, kelas `Dataset` harus dibuat untuk menangani pengambilan gambar dan label:

```python
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 dijalankan:

```python
fnames = [os.path.join("../../images", fname) for fname in fnames if fname.endswith(".jpg")]
```

Selanjutnya, kumpulkan semua label:

```python
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.

```python
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_dataloader
```

Terakhir, Anda harus mengimpor scheduler yang akan digunakan nanti:

```python
from torch.optim.lr_scheduler import CosineAnnealingLR
```

## Menulis Fungsi Pelatihan

Sekarang Anda dapat membangun loop pelatihan. [notebook_launcher()](/docs/accelerate/main/id/package_reference/launchers#accelerate.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

```python
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](/docs/accelerate/main/id/package_reference/accelerator#accelerate.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](../concept_guides/training_tpu) untuk mempelajari alasannya

Selanjutnya, Anda harus membangun dataloaders Anda dan membuat model Anda:

```python
    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:

```python
    for param in model.parameters():
        param.requires_grad = False
    for param in model.get_classifier().parameters():
        param.requires_grad = True
```

Menormalisasi batch gambar akan membuat pelatihan sedikit lebih cepat:

```python
    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:

```python
    mean = mean.to(accelerator.device)
    std = std.to(accelerator.device)
```

Selanjutnya, instansiasi kelas-kelas PyTorch lainnya yang digunakan untuk pelatihan:

```python
    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()](/docs/accelerate/main/id/package_reference/accelerator#accelerate.Accelerator.prepare).

    Tidak ada urutan khusus yang perlu diingat, Anda hanya perlu mengurai objek dalam urutan yang sama seperti saat Anda memberikannya ke metode prepare.

```python
    model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
        model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
    )
```

Sekarang latih modelnya:

```python
    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:

```python
        model.eval()
        accurate = 0
        num_elems = 0
```

Selanjutnya Anda memiliki sisa loop PyTorch standar Anda:

```python
        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()](/docs/accelerate/main/id/package_reference/accelerator#accelerate.Accelerator.gather) agar semua data tersedia pada perangkat saat ini dan metrik yang dihitung dengan benar dapat dicapai:

```python
            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()](/docs/accelerate/main/id/package_reference/accelerator#accelerate.Accelerator.print):

```python
        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:

```python
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()](/docs/accelerate/main/id/package_reference/launchers#accelerate.notebook_launcher).

Anda melewatkan fungsi, argumen (sebagai tuple), dan jumlah proses untuk pelatihan. (Lihat [dokumentasi](../package_reference/launchers) untuk informasi lebih lanjut)

```python
from accelerate import notebook_launcher
```

```python
args = ("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:

```python
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_rank` telah berubah

```python
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:

```python
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:

```python
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:

```python out
Launching training on 2 GPUs.
epoch 0: 88.12
epoch 1: 91.73
epoch 2: 92.58
epoch 3: 93.90
epoch 4: 94.71
```

Dan itu saja!

Perhatikan bahwa [notebook_launcher()](/docs/accelerate/main/id/package_reference/launchers#accelerate.notebook_launcher) mengabaikan file konfigurasi Accelerate, untuk meluncurkan berdasarkan konfigurasi gunakan:

```bash
accelerate launch
```

## Debugging 

Masalah umum saat menjalankan `notebook_launcher` adalah munculnya pesan kesalahan CUDA/XPU has already been initialized. 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()](/docs/accelerate/main/id/package_reference/launchers#accelerate.notebook_launcher)
- Atur `num_processes` menjadi jumlah perangkat yang digunakan untuk pelatihan (seperti jumlah GPU, XPU, CPU, TPU, dll)
- Jika menggunakan TPU, deklarasikan model Anda di luar fungsi loop pelatihan

