Accelerate documentation

إطلاق التدريب الموزّع من كتيّبات Jupyter

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v1.15.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

إطلاق التدريب الموزّع من كتيّبات Jupyter

يعلّمك هذا الشرح التعليمي كيفية ضبط نموذج رؤية حاسوبية باستخدام 🤗 Accelerate من دفتر Jupyter على نظام موزّع. ستتعلم أيضاً كيفية إعداد بعض المتطلبات اللازمة للتأكد من أن بيئتك مُهيأة بشكل صحيح، وأن بياناتك مُعدّة بشكل صحيح، وأخيراً كيفية بدء التدريب.

يتوفر هذا الشرح التعليمي أيضًا كدفتر Jupyter هنا

تهيئة البيئة

قبل إجراء أي تدريب، يجب أن يكون ملف إعدادات Accelerate موجودًا في النظام. وعادةً ما يتم ذلك عن طريق تشغيل الأمر التالي في الطرفية والإجابة على الأسئلة المطروحة:

accelerate config

ومع ذلك، إذا كانت الإعدادات الافتراضية العامة مناسبة لك وأنت لا تعمل على TPU، فإن Accelerate يوفر أداة لكتابة إعدادات جهازك بسرعة في ملف إعداد عبر utils.write_basic_config().

ستعيد the following code تشغيل Jupyter بعد كتابة الإعدادات، حيث تم استدعاء CUDA runtime أو XPU runtime لتنفيذ هذه العملية.

لا يمكن تهيئة CUDA وXPU أكثر من مرة واحدة على نظام متعدد الأجهزة. لا مشكلة في إجراء عمليات التصحيح داخل الدفتر مع وجود استدعاءات إلى CUDA/XPU، ولكن من أجل تدريب النموذج بالكامل في النهاية، يجب إجراء تنظيف كامل وإعادة تشغيل.

import os
from accelerate.utils import write_basic_config

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

تجهيز مجموعة البيانات والنموذج

في الخطوة التالية، يجب عليك تجهيز مجموعة البيانات. وكما ذُكر سابقاً، يجب توخي الحذر الشديد عند تجهيز DataLoaders والنموذج للتأكد من أن لا شيء يُوضع على أي وحدة معالجة رسومية (GPU).

إذا كان الأمر كذلك، فيُنصح بوضع ذلك الكود المحدد في دالة واستدعائها من داخل واجهة مُطلق دفتر الملاحظات، والتي ستُعرض لاحقًا.

تأكد من تنزيل مجموعة البيانات وفقاً للتعليمات هنا

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

أولاً، عليك إنشاء دالة لاستخراج اسم الفئة بناءً على اسم الملف:

import os

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

في هذه الحالة، التسمية هي beagle. باستخدام تعبيرات نمطية (regex) يمكنك استخراج التسمية من اسم الملف:

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)

ويمكنك ملاحظة أنه أعاد اسم الملف الصحيح بشكل صحيح:

"beagle"

في الخطوة التالية، يجب إنشاء فئة Dataset للتعامل مع جلب الصورة والوسم:

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}

الآن، لنُجهّز مجموعة البيانات. خارج دالة التدريب، يمكنك العثور على جميع أسماء الملفات والوسوم وإعلانها، ثم استخدامها كمراجع داخل الدالة المُطلقة:

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

ثم اجمع جميع التسميات:

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)}

في الخطوة التالية، يجب عليك إنشاء دالة get_dataloaders تُرجع لك مُحمِّلات البيانات التي بنيتها. وكما ذُكر سابقاً، إذا كانت البيانات تُرسل تلقائياً إلى جهاز GPU أو TPU عند بناء DataLoaders الخاصة بك، فيجب بناؤها باستخدام هذه الطريقة.

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

أخيرًا، يجب عليك استيراد المجدول (scheduler) ليُستخدم لاحقًا:

from torch.optim.lr_scheduler import CosineAnnealingLR

كتابة دالة التدريب

الآن يمكنك بناء حلقة التدريب. يعمل notebook_launcher() من خلال تمرير دالة استدعاء ستُنفَّذ عبر النظام الموزّع.

إليك حلقة تدريب أساسية لمشكلة تصنيف الحيوانات:

تم تقسيم الكود للسماح بتقديم شرح لكل قسم. ستكون نسخة كاملة يمكن نسخها ولصقها متاحة في النهاية

def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
    set_seed(seed)
    accelerator = Accelerator(mixed_precision=mixed_precision)

يجب عليك أولاً ضبط البذرة وإنشاء كائن Accelerator في أقرب وقت ممكن داخل حلقة التدريب.

إذا كنت تتدرب على TPU، فيجب أن يستقبل حلقة التدريب النموذج كمتغير، وأن يتم إنشاؤه خارج دالة حلقة التدريب. راجع أفضل الممارسات الخاصة بـ TPU لمعرفة السبب

في الخطوة التالية، يجب عليك بناء محمّلات البيانات وإنشاء نموذجك:

    train_dataloader, eval_dataloader = get_dataloaders(batch_size)
    model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))

تقوم ببناء النموذج هنا بحيث تتحكم البذرة أيضًا في تهيئة الأوزان الجديدة

بما أنك تقوم بالتعلم الانتقالي في هذا المثال، فإن مُشفِّر النموذج يبدأ مجمّدًا بحيث يمكن تدريب رأس النموذج فقط في البداية:

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

سيؤدي تطبيع دفعات الصور إلى تسريع التدريب قليلاً:

    mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
    std = torch.tensor(model.default_cfg["std"])[None, :, None, None]

لجعل هذه الثوابت متاحة على الجهاز النشط، يجب عليك تعيينه إلى جهاز Accelerator:

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

الآن قم بإنشاء باقي فئات PyTorch المستخدمة للتدريب:

    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))

قبل تمرير كل شيء إلى prepare().

لا يوجد ترتيب محدد يجب عليك حفظه، بل يكفيك فكّ تغليف الكائنات بنفس الترتيب الذي قمت بتزويد منه طريقة prepare.

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

الآن درّب النموذج:

    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

في الخطوة التالية، لديك بقية حلقة PyTorch القياسية:

        for batch in eval_dataloader:
            inputs = (batch["image"] - mean) / std
            with torch.no_grad():
                outputs = model(inputs)
            predictions = outputs.argmax(dim=-1)

قبل أن نصل أخيرًا إلى آخر اختلاف رئيسي.

عند إجراء التقييم الموزّع، يجب تمرير التوقعات والوسوم عبر gather() حتى تصبح جميع البيانات متاحة على الجهاز الحالي ويتم تحقيق مقياس محسوب بشكل صحيح:

            accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
            num_elems += accurate_preds.shape[0]
            accurate += accurate_preds.long().sum()

الآن كل ما عليك فعله هو حساب المقياس الفعلي لهذه المشكلة، ويمكنك طباعته على العملية الرئيسية باستخدام print():

        eval_metric = accurate.item() / num_elems
        accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")

يتوفر إصدار كامل لهذا حلقة التدريب أدناه:

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}")

استخدام notebook_launcher

كل ما تبقّى هو استخدام notebook_launcher().

تمرر الدالة، والحجج (كسلسلة)، وعدد العمليات للتدريب عليها. (انظر التوثيق لمزيد من المعلومات)

from accelerate import notebook_launcher
args = ("fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=2)

في حالة التشغيل على عدة عقد، يجب عليك إعداد جلسة Jupyter في كل عقدة وتشغيل خلية الإقلاع في الوقت نفسه.

في بيئة تحتوي على عقدتين (حاسوبين) بثمانية وحدات معالجة رسومية (GPU) لكل منهما، والحاسوب الرئيسي بعنوان IP هو “172.31.43.8”, سيبدو الأمر على النحو التالي:

notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=0, num_nodes=2, num_processes=8)

وفي جلسة Jupyter الثانية على الجهاز الآخر:

لاحظ كيف تغيّر node_rank

notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=1, num_nodes=2, num_processes=8)

في حالة التشغيل على TPU، سيبدو الأمر على النحو التالي:

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)

لبدء عملية التدريب مع المرونة، مما يتيح تحمل الأعطال، يمكنك استخدام ميزة elastic_launch التي يوفرها PyTorch. ويتطلب ذلك ضبط معاملات إضافية مثل rdzv_backend وmax_restarts. فيما يلي مثال على كيفية استخدام notebook_launcher مع قدرات مرنة:

notebook_launcher(
    training_loop,
    args,
    num_processes=2,
    max_restarts=3
)

أثناء التشغيل، سيطبع التقدم كما سيوضح عدد الأجهزة التي تم التشغيل عليها. تم تنفيذ هذا الشرح باستخدام بطاقتَي 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.71

وهذا كل شيء!

يرجى ملاحظة أن notebook_launcher() يتجاهل ملف إعدادات Accelerate، ولإطلاقه بناءً على الإعدادات استخدم:

accelerate launch

استكشاف الأخطاء وإصلاحها

مشكلة شائعة عند تشغيل notebook_launcher هي ظهور خطأ يشير إلى أن CUDA/XPU تم تهيئته مسبقًا. وعادةً ما ينشأ هذا الخطأ عن استيراد أو كود سابق في الدفتر (notebook) يستدعي مكتبة torch.cuda أو torch.xpu الفرعية من PyTorch. وللمساعدة في تحديد سبب المشكلة، يمكنك تشغيل notebook_launcher مع ضبط ACCELERATE_DEBUG_MODE=yes في بيئة التشغيل، وسيتم إجراء فحص إضافي عند إنشاء العملية للتأكد من إمكانية إنشاء عملية عادية واستخدام CUDA/XPU دون أي مشاكل. (لا يزال بإمكانك تشغيل كود CUDA/XPU الخاص بك بعد ذلك).

الخلاصة

أظهر هذا الدفتر كيفية تنفيذ التدريب الموزّع من داخل دفتر Jupyter. بعض الملاحظات الرئيسية التي يجب تذكّرها:

  • تأكد من حفظ أي كود يستخدم CUDA/XPU (أو استيرادات CUDA/XPU) للدالة المُمرَّرة إلى notebook_launcher()
  • اضبط num_processes ليكون عدد الأجهزة المستخدمة للتدريب (مثل عدد وحدات GPU أو XPU أو CPU أو TPU، إلخ)
  • عند استخدام TPU، عرّف نموذجك خارج دالة حلقة التدريب
Update on GitHub