چگونه استفاده از GPU را به حداکثر برسانیم ؟

مقدمه
یکی از پرتکرارترین سؤالاتی که دانشمندان داده و مهندسان یادگیری ماشین تازه‌کار می‌پرسند این است که آیا فرآیندهای آموزش مدل‌های یادگیری عمیق آن‌ها بهینه اجرا می‌شوند یا خیر. در این راهنما، یاد می‌گیریم که چگونه مشکلات عملکردی یادگیری عمیق را تشخیص داده و برطرف کنیم، صرف‌نظر از اینکه روی یک ماشین واحد کار می‌کنیم یا چندین ماشین. هدف این راهنما این است که به ما کمک کند تا از طیف گسترده‌ای از GPUهای ابری موجود به‌صورت عملی و مؤثر استفاده کنیم.
ابتدا مفهوم استفاده از GPU را درک خواهیم کرد و در نهایت درباره‌ی اندازه‌ی دسته‌ی (Batch Size) بهینه برای حداکثر بهره‌وری از GPU بحث خواهیم کرد.
توجه: این راهنما فرض می‌کند که با سیستم‌عامل لینوکس و زبان برنامه‌نویسی پایتون آشنایی اولیه داریم. نسخه‌های جدید لینوکس معمولاً به‌صورت پیش‌فرض با اوبونتو ارائه می‌شوند، بنابراین می‌توانیم به‌راحتی pip و conda را نصب کنیم، زیرا در این راهنما از آن‌ها استفاده خواهیم کرد.
پیش‌نیازها
برای دنبال کردن این مقاله، نیاز به تجربه‌ی کدنویسی با پایتون و آشنایی مقدماتی با یادگیری عمیق دارید. ما فرض را بر این می‌گذاریم که همه‌ی خوانندگان به ماشین‌های قدرتمند کافی دسترسی دارند تا بتوانند کدهای ارائه‌شده را اجرا کنند.
اگر به GPU دسترسی ندارید، پیشنهاد می‌کنیم از DigitalOcean GPU Droplets استفاده کنید.
برای راهنمایی در مورد شروع کار با کدنویسی پایتون، توصیه می‌کنیم راهنمای مبتدیان را مطالعه کنید تا سیستم خود را راه‌اندازی کرده و برای اجرای آموزش‌های مقدماتی آماده شوید.
استفاده از GPU چیست؟
در جلسات آموزشی یادگیری ماشین و یادگیری عمیق، میزان استفاده از GPU یکی از مهم‌ترین جنبه‌هایی است که باید نظارت شود. این اطلاعات را می‌توان از طریق ابزارهای داخلی یا ابزارهای شخص ثالث مخصوص GPU مشاهده کرد.
به‌طور کلی، میزان استفاده از GPU به سرعتی اشاره دارد که یک یا چند هسته‌ی GPU در یک ثانیه‌ی گذشته فعالیت داشته‌اند، که این میزان معمولاً نشان‌دهنده‌ی میزان درگیر بودن GPU توسط برنامه‌ی یادگیری عمیق است.
چگونه بفهمیم که به توان پردازشی بیشتری نیاز داریم؟
یک سناریوی واقعی را در نظر بگیرید:
یک دانشمند داده دو GPU در اختیار دارد که باید منابع کافی برای کارهای او باشند. در اغلب روزها، در مرحله‌ی توسعه‌ی مدل، هیچ مشکلی وجود ندارد و تعامل با GPU روان است. اما زمانی که مرحله‌ی آموزش مدل آغاز می‌شود، ناگهان نیاز به توان پردازشی بیشتری احساس می‌شود که در دسترس نیست.
این بدان معناست که برای انجام کارهای سنگین‌تر، نیاز به منابع پردازشی بیشتری خواهیم داشت. به‌ویژه، اگر تمام رم GPU اشغال شده باشد، انجام وظایف زیر غیرممکن خواهد شد:
•اجرای آزمایش‌های بیشتر
•استفاده از چند GPU برای آموزش سریع‌تر مدل با دسته‌های داده‌ی بزرگ‌تر و افزایش دقت مدل
•کار روی یک مدل جدید در حالی که مدل قبلی به‌طور مستقل در حال آموزش است
مزایای بهینه‌سازی استفاده از GPU
بهبود کارایی GPU می‌تواند دو برابر افزایش در بهره‌وری سخت‌افزار و ۱۰۰٪ افزایش در سرعت آموزش مدل‌ها را به همراه داشته باشد.
مدیریت بهتر منابع: استفاده‌ی مؤثر از GPU باعث کاهش زمان بیکاری آن و افزایش بهره‌وری خوشه‌های پردازشی می‌شود.
افزایش تعداد آزمایش‌ها: دانشمندان داده و متخصصان یادگیری عمیق می‌توانند آزمایش‌های بیشتری را اجرا کنند و کیفیت مدل‌های خود را ارتقا دهند.
کاهش زمان آموزش مدل: مدیران IT می‌توانند با استفاده از چند GPU، مدل‌های توزیع‌شده را اجرا کنند و زمان آموزش را کاهش دهند (مانند ماشین‌های چند GPU مجهز به NVLink که توسط DigitalOcean Droplets ارائه می‌شوند).
اندازه‌ی دسته‌ی بهینه برای استفاده از GPU
انتخاب اندازه‌ی دسته‌ی مناسب همواره چالش‌برانگیز است زیرا هیچ مقدار ثابت و بهینه‌ای برای همه‌ی مجموعه‌داده‌ها و معماری‌های مدل وجود ندارد.
•اگر اندازه‌ی دسته‌ی بزرگی را انتخاب کنیم، آموزش سریع‌تر خواهد بود و حافظه‌ی بیشتری مصرف خواهد کرد، اما دقت نهایی مدل ممکن است کاهش یابد.
•در مقابل، اگر اندازه‌ی دسته‌ی کوچکی را انتخاب کنیم، دقت بهتری ممکن است حاصل شود، اما آموزش کندتر خواهد بود.
اما ابتدا، بیایید ببینیم که اندازه‌ی دسته چیست و چرا به آن نیاز داریم.
اندازه‌ی دسته (Batch Size) چیست؟
در آموزش یک مدل یادگیری عمیق، اندازه‌ی دسته مشخص می‌کند که چند نمونه در هر مرحله از آموزش از طریق شبکه‌ی عصبی عبور داده می‌شود.
مثالی از اندازه‌ی دسته
فرض کنیم که می‌خواهیم یک شبکه‌ی عصبی را برای شناسایی نژادهای مختلف گربه‌ها آموزش دهیم و ۱۰۰۰ عکس از گربه‌ها داریم.
•اگر اندازه‌ی دسته را ۱۰ انتخاب کنیم، در هر مرحله از آموزش، ۱۰ عکس گربه به شبکه‌ی عصبی ارسال خواهد شد.
اما چرا به دسته‌بندی داده‌ها نیاز داریم؟
چرا از دسته‌ها استفاده می‌کنیم؟
همان‌طور که اشاره کردیم، استفاده از دسته‌های بزرگ‌تر باعث می‌شود هر epoch سریع‌تر تکمیل شود. زیرا سیستم‌های مدرن می‌توانند بیش از یک نمونه را هم‌زمان پردازش کنند.
با این حال، اگرچه یک سیستم ممکن است بتواند دسته‌های بسیار بزرگی را پردازش کند، اما افزایش بیش از حد اندازه‌ی دسته ممکن است باعث کاهش دقت مدل شود و توانایی مدل را برای تعمیم روی داده‌های جدید محدود کند.
بنابراین، اندازه‌ی دسته یک ابرپارامتر مهم است که باید بسته به عملکرد مدل در طول آموزش تنظیم شود. همچنین، باید بررسی کنیم که آیا GPU ما توانایی پردازش اندازه‌ی دسته‌ی انتخابی را دارد یا خیر.
یک مثال برای انتخاب اندازه‌ی دسته‌ی مناسب
•فرض کنیم که اندازه‌ی دسته را ۱۰۰ انتخاب کنیم.
•اگر GPU ما توانایی پردازش هم‌زمان ۱۰۰ تصویر را نداشته باشد، پردازش کند شده و نیاز به کاهش اندازه‌ی دسته خواهیم داشت.
حال که مفهوم کلی اندازه‌ی دسته را درک کردیم، در ادامه یاد می‌گیریم که چگونه اندازه‌ی دسته‌ی بهینه را در کدهای PyTorch و Keras پیاده‌سازی کنیم.
یافتن اندازه مناسب دسته در PyTorch
در این بخش، به بررسی یافتن اندازه مناسب دسته روی یک مدل Resnet18 می‌پردازیم. از ابزار PyTorch profiler برای اندازه‌گیری عملکرد آموزش و میزان استفاده از GPU در مدل Resnet18 استفاده خواهیم کرد.
برای نمایش بیشتر استفاده از PyTorch در TensorBoard جهت نظارت بر عملکرد مدل، از PyTorch profiler در این کد استفاده می‌کنیم اما گزینه‌های اضافی را فعال خواهیم کرد.
همراه با این آموزش پیش بروید
روی ماشین ابری مجهز به GPU خود، از wget برای دانلود نوت‌بوک مربوطه استفاده کنید. سپس، Jupyter Labs را اجرا کنید تا نوت‌بوک را باز کنید. می‌توانید این کار را با قرار دادن دستورات زیر و باز کردن لینک نوت‌بوک انجام دهید:
jupyter lab  
تنظیم و آماده‌سازی داده و مدل
دستور زیر را برای نصب torch، torchvision و Profiler اجرا کنید:
pip3 install torch torchvision torch-tb-profiler  
کد زیر مجموعه داده CIFAR10 را دریافت می‌کند. سپس، از یادگیری انتقالی با مدل از پیش‌آموزش‌دیده‌شده Resnet18 استفاده خواهیم کرد و مدل را آموزش می‌دهیم.
#import all the necessary libraries  
import torch  
import torch.nn  
import torch.optim  
import torch.profiler  
import torch.utils.data  
import torchvision.datasets  
import torchvision.models  
import torchvision.transforms as T  
#prepare input data and transform it  
transform = T.Compose(  
    [T.Resize(224),  
     T.ToTensor(),  
     T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])  
train_set = torchvision.datasets.CIFAR10(root=’./data’, train=True, download=True, transform=transform)  
# use dataloader to launch each batch  
train_loader = torch.utils.data.DataLoader(train_set, batch_size=1, shuffle=True, num_workers=4)  
# Create a Resnet model, loss function, and optimizer objects. To run on GPU, move model and loss to a GPU device  
device = torch.device(“cuda:0”)  
model = torchvision.models.resnet18(pretrained=True).cuda(device)  
criterion = torch.nn.CrossEntropyLoss().cuda(device)  
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)  
model.train()  
# define the training step for each batch of input data  
def train(data):  
    inputs, labels = data[0].to(device=device), data[1].to(device=device)  
    outputs = model(inputs)  
    loss = criterion(outputs, labels)  
    optimizer.zero_grad()  
    loss.backward()  
    optimizer.step()  
فعال‌سازی ویژگی‌های اضافی در پروفایلر
پس از راه‌اندازی موفق مدل پایه، اکنون گزینه‌های اضافی را در پروفایلر فعال می‌کنیم تا اطلاعات بیشتری در طول فرآیند آموزش ثبت شود. بیایید پارامترهای زیر را اضافه کنیم:
•schedule – این پارامتر یک مقدار step(int) را می‌گیرد و اقدام موردنظر پروفایلر را در هر مرحله مشخص می‌کند.
•profile_memory – برای تخصیص حافظه GPU استفاده می‌شود و تنظیم آن روی مقدار True ممکن است زمان بیشتری ببرد.
•with_stack – برای ثبت اطلاعات منبع تمامی ردپاها استفاده می‌شود.
اکنون که این مفاهیم را درک کردیم، می‌توانیم به کد برگردیم:
with torch.profiler.profile(  
     schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),  
     on_trace_ready=torch.profiler.tensorboard_trace_handler(‘./log/resnet18_batchsize1’),  
     record_shapes=True,  
     profile_memory=True,  
     with_stack=True  
) as prof:  
    for step, batch_data in enumerate(train_loader):  
        if step >= (1 + 1 + 3) * 2:  
            break  
        train(batch_data)  
        prof.step()  # Need call this at the end of each step to notify profiler of steps’ boundary.  
یافتن اندازه مناسب دسته در Keras
در این مثال، از یک مدل ترتیبی دلخواه استفاده خواهیم کرد:
model = Sequential([
    Dense(units=16, input_shape=(1,), activation=’relu’),
    Dense(units=32, activation=’relu’, kernel_regularizer=regularizers.l2(0.01)),
    Dense(units=2, activation=’sigmoid’)
])
بیایید روی بخشی که model.fit() را فراخوانی می‌کنیم تمرکز کنیم. این تابع جایی است که یک شبکه عصبی مصنوعی یادگیری را انجام می‌دهد و مدل ما را آموزش می‌دهد:
model.fit(
    x=scaled_train_samples,
    y=train_labels,
    validation_data=valid_set,
    batch_size=10,
    epochs=20,
    shuffle=True,
    verbose=2
)
تابع fit() در بالا، یک پارامتر به نام batch_size را می‌پذیرد. این همان بخشی است که مقدار اندازه دسته را مشخص می‌کنیم. در این مدل، مقدار آن را برابر ۱۰ قرار داده‌ایم. بنابراین، در فرآیند آموزش این مدل، داده‌ها را ۱۰ تایی وارد مدل می‌کنیم تا کل چرخه کامل شود. سپس فرآیند را دوباره برای تکمیل چرخه بعدی آغاز می‌کنیم.
نکات مهمی که باید به آن‌ها توجه کرد
۱. تأثیر بر سرعت و حافظه
بدون شک، آموزش و پیش‌بینی با دسته‌های بزرگ‌تر سریع‌تر انجام می‌شود. دسته‌های کوچک‌تر به دلیل سربار اضافی ناشی از بارگیری و تخلیه داده از GPU، زمان بیشتری نیاز دارند. با این حال، برخی تحقیقات نشان داده‌اند که آموزش با اندازه دسته کوچک‌تر، در نهایت امتیاز بهره‌وری بهتری را برای چنین مدل‌هایی ارائه می‌دهد. از سوی دیگر، دسته‌های بزرگ‌تر نیاز به حافظه بیشتر دارند. اگر دسته‌های خیلی بزرگ انتخاب کنید، ممکن است با مشکل کمبود حافظه (Out-of-Memory) مواجه شوید، زیرا ورودی‌های هر لایه در حافظه ذخیره می‌شوند، به‌ویژه هنگام آموزش که برای مرحله پس‌انتشار (Backpropagation) مورد نیاز هستند.
۲. تأثیر بر همگرایی (Convergence)
اگر مدل خود را با استفاده از نزول گرادیان تصادفی (SGD) یا یکی از نسخه‌های آن آموزش می‌دهید، باید بدانید که اندازه دسته می‌تواند بر همگرایی و تعمیم‌پذیری شبکه تأثیر بگذارد. در بسیاری از مسائل بینایی کامپیوتری، اندازه دسته معمولاً بین ۳۲ تا ۵۱۲ نمونه است.
۳. مشکلات در حین آموزش چند-GPU
این نکته فنی می‌تواند اثرات فاجعه‌باری داشته باشد. در هنگام آموزش روی چندین GPU، مهم است که داده‌ها به درستی بین همه GPUها توزیع شوند. ممکن است اندازه دسته نهایی در یک epoch کمتر از مقدار مورد انتظار باشد (زیرا ممکن است تعداد کل داده‌ها به طور دقیق بر اندازه دسته تقسیم نشود).
برخی از GPUها ممکن است در گام نهایی داده‌ای دریافت نکنند، که این موضوع می‌تواند مشکل‌ساز باشد. به عنوان مثال، لایه Batch Normalization در Keras نمی‌تواند چنین شرایطی را مدیریت کند و باعث ایجاد مقادیر NaN در وزن‌های مدل (میانگین و واریانس در لایه BN) می‌شود.
مشکل بزرگ‌تر این است که این نقص در طول آموزش مشخص نمی‌شود، زیرا در این مرحله، لایه Batch Normalization از میانگین/واریانس دسته برای تخمین استفاده می‌کند. اما در هنگام پیش‌بینی، از میانگین/واریانس ذخیره‌شده استفاده می‌شود، که در این حالت می‌تواند مقدار NaN داشته باشد و به نتایج ضعیف منجر شود.
راهکارها برای جلوگیری از مشکلات چند-GPU
هنگام آموزش مدل روی چندین GPU، اندازه دسته باید مقدار ثابتی داشته باشد. دو راهکار ساده برای دستیابی به این هدف عبارتند از:
رد کردن دسته‌هایی که اندازه آن‌ها مناسب نیست.
تکرار داده‌های دسته تا زمانی که اندازه مناسب شود.
در نهایت، در یک پیکربندی چند-GPU، اندازه دسته باید بیشتر از تعداد کل GPUهای سیستم باشد.
نتیجه‌گیری
در این مقاله، یاد گرفتیم که چگونه از ابزارهای مختلف برای بهینه‌سازی استفاده از GPU با تنظیم اندازه مناسب دسته بهره ببریم.
تا زمانی که یک اندازه دسته مناسب (حداقل ۱۶) را تنظیم کنیم و تعداد epochs و تکرارها ثابت بماند، اندازه دسته تأثیر زیادی بر عملکرد مدل نخواهد داشت. با این حال، زمان آموزش تحت تأثیر قرار می‌گیرد.
برای آموزش چند-GPU، اندازه دسته باید کوچک‌ترین مقدار ممکن باشد تا هر GPU بتواند با ظرفیت کامل خود کار کند. مقدار ۱۶ نمونه برای هر GPU مقدار مناسبی است.
برای امتیاز به این نوشته کلیک کنید!
[کل: 0 میانگین: 0]

نظرات کاربران

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *