چرا GPU برای یادگیری عمیق
جواب کوتاه
آموزش یه شبکهی عصبی بیشترش ضرب ماتریسه؛ یعنی میلیونها ضرب و جمع خیلی ساده که هیچکدوم منتظر جواب اون یکی نمیمونه. پردازندهی اصلی کامپیوتر، یعنی CPU، چند تا هستهی خیلی قوی داره که کارهای پیچیده رو یکی بعد از دیگری خوب انجام میدن. پردازندهی گرافیکی، یعنی GPU، که اول برای رنگ کردن پیکسلهای بازی ساخته شد، هزاران هستهی ساده داره که همه با هم کار میکنن. برای همین برای ضرب ماتریس خیلی سریعتره. سال ۲۰۱۲ یه شبکه که روی دو تا کارت گرافیک آموزش دیده بود، مسابقهی تشخیص تصویر رو با فاصلهی زیاد برد، و از اون به بعد یادگیری عمیق بدون GPU تقریبا معنی نداره.
دو جور پردازنده
فرض کنید هزار تا برگهی امتحان تستی دارید که باید صحیح بشن. دو تا راه دارید: یه استاد خیلی باهوش، یا هزار تا دانشجو که هر کدوم فقط یه برگه رو صحیح کنه.
استاد هر برگه رو خیلی سریع صحیح میکنه، ولی بالاخره باید هزار تا برگه رو یکییکی بره. دانشجوها شاید هر کدوم کندتر از استاد باشن، ولی چون همه با هم شروع میکنن، کل کار توی زمان صحیح کردن یه برگه تموم میشه. برای صحیح کردن برگهی تستی، که کار ساده و تکراریه، دانشجوها میبرن.
کامپیوتر هم دو جور پردازنده داره. CPU یا پردازندهی اصلی، همون استاده: چند تا هسته داره که هر کدوم خیلی قویان و هر کار پیچیدهای رو بلدن؛ از باز کردن یه برنامه تا تصمیمگیریهای پشت سر هم. GPU یا پردازندهی گرافیکی، همون دانشجوهاست: صدها یا هزاران واحد محاسبهی ساده داره که همه همزمان کار میکنن.
هیچکدوم از اون یکی بهتر نیست؛ هر کدوم برای یه جور کار ساخته شده. سوال اینه که آموزش شبکهی عصبی شبیه کدوم کاره. جواب: خیلی شبیه صحیح کردن برگهی تستی.
چقدر ضرب
توی درس قبل، شبکهمون سه تا نورون پنهان داشت و هر نورون رو با یه حلقه حساب کردیم. حالا یه لایهی معمولی رو در نظر بگیرید، با هزار تا ورودی و هزار تا نورون:
هر نورون باید هزار تا ورودی رو در هزار تا وزن خودش ضرب کنه و جمع بزنه. هزار تا نورون داریم، پس میشه هزار ضرب در هزار: یک میلیون ضرب. اینها رو اگه کنار هم بچینیم، میشه همون ضرب ماتریس: یه جدول هزار در هزار از وزنها، ضرب در یه ستون از ورودیها.
حالا حساب کنید. این یک میلیون ضرب فقط برای یه لایه و فقط برای یه مثال بود. یه شبکهی عمیق دهها لایه داره. هر دور آموزش هم صدها هزار یا میلیونها مثال رو میبینه. تازه پسانتشار هم برای برگشت تقریبا همینقدر ضرب لازم داره. و آموزش دهها دور تکرار میشه. عدد آخر اونقدر بزرگه که دیگه حسش نمیکنیم.
خبر خوب اینه که تقریبا همهی این کار از یه جنسه: ضرب دو تا عدد و جمع زدنش با بقیه. هیچ تصمیم پیچیدهای وسطش نیست. درست مثل برگهی تستی.
مستقل از هم
یه نکتهی دیگه این کار رو برای GPU عالی میکنه: هیچکدوم از این ضربها منتظر جواب اون یکی نیست.
برای حساب کردن جواب نورون اول، لازم نیست جواب نورون دوم رو بدونیم. هر هزار تا نورون میتونن همزمان حساب بشن. به این مدل کارها، که میشه بیدردسر تیکهتیکه کرد و بین کارگرها پخش کرد، میگن کار موازی.
اگه چهار تا هسته داشته باشیم، باید کار رو چهار قسمت کنیم و هر هسته قسمت خودش رو یکییکی جلو ببره؛ مثل ردیفهای بالای تصویر. اگه هزار تا هسته داشته باشیم، هر کدوم یه تیکهی کوچیک برمیداره و همه با هم تموم میکنن؛ مثل ردیفهای پایین. حتی اگه هر هستهی GPU کندتر از یه هستهی CPU باشه، تعدادشون جبران میکنه.
همهی کارها اینجوری نیستن. اگه هر قدم به جواب قدم قبل احتیاج داشته باشه، مثل یه دستور پخت که باید به ترتیب رفت، هزار تا کارگر هم کمکی نمیکنن. برای همین کامپیوتر هنوز CPU لازم داره، و CPU همیشه کنار GPU کار میکنه.
خودمون امتحانش کنیم
حرف «کارهای مستقل رو میشه با هم انجام داد» رو روی همین CPU معمولی هم میشه دید. یه لپتاپ با ۱۰ هسته داشتیم. دو تا ماتریس ۳۰۰ در ۳۰۰ رو با پایتون خالی ضرب کردیم؛ یعنی ۲۷ میلیون ضرب، چون برای هر کدوم از ۹۰ هزار خونهی جواب، ۳۰۰ ضرب لازمه. بعد ردیفهای جواب رو بین ۱، ۲، ۴ و ۸ تا کارگر پخش کردیم؛ هر کارگر یه هسته، و هر ردیف از بقیه مستقله:
import os, random, time
from multiprocessing import Pool
N = 300
random.seed(0)
A = [[random.random() for _ in range(N)] for _ in range(N)]
B = [[random.random() for _ in range(N)] for _ in range(N)]
Bt = list(zip(*B)) # columns of B
def row(i): # one row of the answer
return [sum(a * b for a, b in zip(A[i], col)) for col in Bt]
if __name__ == "__main__":
print("cores:", os.cpu_count(), " multiplications:", N ** 3)
for workers in (1, 2, 4, 8):
t = time.perf_counter()
with Pool(workers) as p:
C = p.map(row, range(N))
print(workers, "workers:", round(time.perf_counter() - t, 2), "s")cores: 10 multiplications: 27000000 1 workers: 0.67 s 2 workers: 0.36 s 4 workers: 0.23 s 8 workers: 0.21 s
با دو کارگر، تقریبا نصف شد؛ دقیقا همون چیزی که انتظار داشتیم. با چهار کارگر، ۰٫۲۳ ثانیه؛ بهتر، ولی نه یکچهارم. و با هشت کارگر، ۰٫۲۱؛ تقریبا هیچ فرقی با چهار. دو بار اجرا کردیم و عددها تقریبا همین بودن. چرا با هستهی بیشتر، سرعت متناسب بالا نرفت؟ چون همهی کار تقسیمشدنی نیست. ساختن کارگرها، فرستادن ماتریسها براشون، و جمع کردن جوابها، کارهاییان که یه نفر باید انجام بده؛ و هر چی کارگرها بیشتر بشن، سهم همین تیکهی تقسیمنشدنی بیشتر به چشم میاد. بعضی از این ۱۰ هسته هم هستههای کممصرف و کندترن.
همین ماجرا، خیلی بزرگتر، دلیل وجود GPUه. GPU بهجای ۱۰ هسته هزاران هسته داره، و برای ضرب ماتریس طوری ساخته شده که این تیکهی تقسیمنشدنی خیلی کوچیک بمونه.
سقفی که هستهی بیشتر نمیشکنه
جین امدال سال ۱۹۶۷ این رو به شکل یه حساب ساده گفت. فرض کنید ۹۵ درصد یه کار تقسیمشدنیه و ۵ درصد نه. اون ۵ درصد، هر چقدر هم هسته داشته باشید، همونقدر طول میکشه. پس حتی با بینهایت هسته، کار حداکثر ۲۰ برابر سریعتر میشه، چون ۱ تقسیم بر ۰٫۰۵ میشه ۲۰:
با ۱۰ هسته ۶٫۹ برابر، با ۱۰۰ هسته ۱۶٫۸، با هزار هسته ۱۹٫۶، و از اون به بعد تقریبا هیچ. حالا اگه ۹۹ درصد کار تقسیمشدنی باشه، سقف میشه ۱۰۰ برابر، و هزار هسته ۹۱ برابر سرعت میده. یعنی هر چی هسته بیشتر باشه، اون یه درصد آخر مهمتر میشه.
ضرب ماتریس از اون کارهای کمیابیه که تقریبا همهش تقسیمشدنیه؛ هر خونهی جواب کاملا از بقیه مستقله. برای همین GPU با هزاران هستهش، روی این کار اینقدر خوب جواب میده. ولی همون آموزش مدل هم تیکههای تقسیمنشدنی داره: خوندن داده از دیسک، آماده کردنش، و جابهجا کردنش از حافظهی کامپیوتر به حافظهی کارت. اگه اینها کند باشن، GPU گرونقیمت بیشتر وقتش رو منتظر میمونه. برای همین معمولا داده رو دستهدسته و از قبل آماده میکنن، که وقتی GPU داره روی یه دسته کار میکنه، دستهی بعدی آماده باشه.
از بازی تا هوش مصنوعی
GPU رو کسی برای هوش مصنوعی نساخته بود. اسمش هم میگه: پردازندهی گرافیکی، برای بازی و تصویر.
صفحهی نمایش از میلیونها پیکسل درست شده، و برای هر تصویر بازی، رنگ هر پیکسل باید حساب بشه. نکته اینه که رنگ هر پیکسل رو میشه جدا از بقیه حساب کرد. برای همین سازندههای کارت گرافیک، به جای چند تا هستهی قوی، هستههای سادهی خیلی زیاد گذاشتن. یعنی دقیقا همون جنس کاری که شبکهی عصبی لازم داره، فقط برای یه هدف دیگه.
از حدود سال ۲۰۰۱ کمکم شد از کارت گرافیک برای حسابهای غیرگرافیکی هم استفاده کرد، و کارهایی که با ماتریس و بردار سروکار داشتن، راحتتر از همه به GPU منتقل میشدن. پژوهشگرها هم امتحان کردن: سال ۲۰۰۶ یه شبکهی عصبی روی GPU چهار برابر سریعتر از CPU آموزش دید، و سال ۲۰۰۹ یه شبکهی ۱۰۰ میلیون پارامتری تا ۷۰ برابر سریعتر.
کمکم سازندهها هم فهمیدن این بازار چقدر بزرگه، و از حدود ۲۰۱۷ توی کارتها هستههایی گذاشتن که مخصوص ضرب ماتریسهای کوچیکان؛ یعنی دیگه فقط برای بازی ساخته نمیشن، برای یادگیری عمیق هم طراحی میشن.
آموزش چقدر کار داره؟
یه لایه با هزار ورودی و هزار خروجی، برای هر مثال یه میلیون ضرب داره؛ برای هر کدوم از هزار خروجی، هزار ضرب. اگه دستهی مثالها ۲۵۶ تایی باشه، ۲۵۶ میلیون ضرب، فقط برای یه لایه و فقط برای رفت. برگشت هم، که شیبها رو حساب میکنه، تقریبا دو برابر رفت کار داره، چون هم باید شیب وزنها رو حساب کنه هم شیبی که به لایهی قبل میرسه.
جرد کاپلان و همکارهاش سال ۲۰۲۰ برای مدلهای زبانی یه حساب سرانگشتی دادن: کل کار آموزش تقریبا میشه ۶ ضرب در تعداد پارامترها ضرب در تعداد کلمههایی که مدل میخونه؛ ۲ برای رفت و ۴ برای برگشت. یه مثال ساختگی: مدلی با ۷ میلیارد پارامتر که هزار میلیارد کلمه بخونه، حدود ۴٫۲ ضرب در ۱۰ به توان ۲۲ عمل حساب لازم داره؛ یه عدد ۲۳ رقمی.
حالا فرض کنید یه کارت، در عمل، صد هزار میلیارد عمل در ثانیه انجام بده؛ این عدد هم ساختگیه و فقط برای حسابه. اونوقت این آموزش با یه کارت حدود ۴۸۶۱ روز طول میکشه، یعنی بیش از ۱۳ سال. با هزار کارت، اگه هیچ وقتی برای حرف زدن کارتها با هم تلف نشه، حدود ۵ روز؛ که با قانون امدال میدونیم در عمل بیشتر میشه. همین حساب ساده نشون میده چرا آموزش مدلهای بزرگ کار هر کسی نیست، ولی استفاده ازشون هست: جواب دادن به یه سوال فقط همون رفته، برای چند صد کلمه، نه برای هزار میلیارد.
دو کارت توی اتاق خواب
لحظهای که همهچیز عوض شد، سال ۲۰۱۲ بود، توی یه مسابقهی معروف تشخیص تصویر.
یه دانشجو یه شبکهی کانولوشنی با ۶۰ میلیون پارامتر ساخت و روی ۱٫۲ میلیون عکس آموزشش داد. آموزش ۹۰ دور طول کشید، یعنی ۵ تا ۶ روز، روی دو تا کارت گرافیک معمولی که هر کدوم ۳ گیگابایت حافظه داشتن. جالبش اینه که این کار توی اتاق خواب خونهی پدر و مادرش انجام شد، نه یه آزمایشگاه بزرگ.
شبکهش اونقدر بزرگ بود که توی حافظهی یه کارت جا نمیشد. برای همین دو نیمش کردن: هر نیمه روی یه کارت، و دو تا کارت جوابهاشون رو با هم رد و بدل میکردن. این شبکه مسابقه رو با فاصلهای برد که همه رو شوکه کرد.
از اون به بعد، تقریبا هر کسی که یادگیری عمیق کار میکرد، رفت سراغ GPU. خیلیها این لحظه رو شروع موج امروزی یادگیری عمیق میدونن. ایدهی شبکهی عصبی دهها سال قبلتر بود؛ چیزی که کم بود، دادهی زیاد و ماشینی بود که اینهمه ضرب رو توی چند روز انجام بده.
حافظه
داستان دو کارت یه چیز مهم دیگه رو هم نشون داد: فقط تعداد هستهها مهم نیست، حافظه هم مهمه.
هزار تا دانشجو اگه برگهها دیر به دستشون برسه، بیکار میشینن. GPU هم همینه: هستهها هر چقدر هم زیاد باشن، اگه وزنها و داده با سرعت کافی بهشون نرسه، منتظر میمونن. برای همین کارت گرافیک حافظهی مخصوص خودش رو داره که خیلی سریعتر از حافظهی معمولی کامپیوتره. حافظهی معمولی تا حدود ۱۲۸ گیگابایت در ثانیه جابهجا میکنه؛ حافظهی یه کارت جدا میتونه بیش از ۱۰۰۰ گیگابایت.
حافظهی کارت یه محدودیت هم میذاره: همهی وزنهای مدل، به اضافهی چیزهایی که موقع آموزش لازمه، باید توش جا بشه. اگه جا نشه، یا باید مثل اون دانشجو مدل رو بین چند کارت پخش کنید، یا مدل رو کوچیکتر کنید، یا مثالهای کمتری رو با هم بفرستید. برای همین موقع انتخاب کارت، مقدار حافظهش خیلی وقتها از سرعتش مهمتره.
عددهای کوچیکتر
یه ترفند دیگه هم برای جا دادن و سرعت هست: عددها رو با جای کمتری نگه داریم.
کامپیوتر هر عدد رو با یه تعداد مشخص صفر و یک نگه میداره که بهشون میگن بیت. حالت معمول برای عددهای اعشاری ۳۲ بیته. ولی شبکهی عصبی لازم نداره هر وزن رو با اینهمه دقت بدونه؛ فرق ۰٫۱۲۳۴۵۶ و ۰٫۱۲۳۴ براش تقریبا مهم نیست.
اگه هر عدد رو با ۱۶ بیت نگه داریم، نصف جا میگیره. یعنی توی همون حافظه دو برابر وزن جا میشه، و توی همون لوله دو برابر عدد رد میشه. برای همین کارتهایی که مخصوص یادگیری عمیق ساخته میشن، بیشتر روی عددهای ۱۶ بیتی و کمتر و روی ضرب ماتریس سرمایه میذارن، نه روی دقت خیلی بالا که کارهای علمی لازم دارن.
حافظه، با حساب
بیایید حساب کنیم یه مدل واقعا چقدر حافظه لازم داره. هر عدد ۳۲ بیتی ۴ بایت جا میگیره. پس شبکهی ۲۰۱۲ با ۶۰ میلیون پارامتر، فقط برای وزنهاش ۲۴۰ مگابایت لازم داشت. موقع آموزش ولی کار بیشتره: کنار هر وزن، شیبش هم باید نگه داشته بشه، و اگه با بهینهساز آدام آموزش بدید، دو تا عدد دیگه هم برای هر وزن. یعنی حدود چهار برابر، ۹۶۰ مگابایت، بهعلاوهی خروجی همهی لایهها برای همهی مثالهای یه دسته، که پسانتشار لازمشون داره. دو تا کارت ۳ گیگی که اون سال استفاده شد، دقیقا برای همین بود.
حالا یه مدل زبانی با ۷ میلیارد پارامتر. با عددهای ۳۲ بیتی، فقط وزنهاش ۲۸ گیگابایته. با ۱۶ بیت، ۱۴ گیگ؛ و اگه هر عدد رو با ۴ بیت نگه داریم، ۳٫۵ گیگ، که توی حافظهی یه کامپیوتر معمولی هم جا میشه. این همون ترفند عدد کوچیکتره که بالاتر دیدیم، و دلیل اینکه امروز میشه بعضی مدلهای زبانی رو روی لپتاپ اجرا کرد. ولی برای آموزش همین مدل با ۳۲ بیت و آدام، حدود چهار برابر ۲۸ گیگ، یعنی بیش از ۱۰۰ گیگابایت لازمه، و این یعنی چند کارت با هم.
اگه موقع آموزش حافظه کم اومد، سادهترین کار کوچیک کردن دستهست؛ چون خروجی لایهها برای همهی مثالهای دسته نگه داشته میشه، دستهی نصف تقریبا نصف این بخش حافظه رو میگیره. اگه دستهی بزرگتر لازم بود، یه ترفند هست: چند دستهی کوچیک رو پشت سر هم رد کنید، شیبهاشون رو جمع کنید، و فقط بعد از چند تا یه بار وزنها رو بهروز کنید. نتیجه تقریبا مثل یه دستهی بزرگه، با حافظهی یه دستهی کوچیک؛ فقط کندتر.
چند کارت با هم
وقتی یه کارت کافی نیست، دو راه اصلی هست. راه اول تقسیم دادهست: هر کارت یه نسخهی کامل از مدل داره، ولی هر کدوم یه تیکه از دستهی مثالها رو میگیره. هر کارت شیبهای خودش رو حساب میکنه، بعد همه میانگین شیبها رو با هم به اشتراک میذارن و همه یه جور بهروز میشن. این سادهست، ولی فقط وقتی کار میکنه که مدل توی یه کارت جا بشه.
راه دوم تقسیم خود مدله: هر کارت یه بخش از لایهها یا نورونها رو نگه میداره. شبکهی ۲۰۱۲ دقیقا همین کار رو کرد؛ چون توی یه کارت ۳ گیگی جا نمیشد، نورونهاش رو بین دو کارت پخش کردن و فقط توی بعضی لایهها دو کارت با هم حرف میزدن. مدلهای خیلی بزرگ امروزی هر دو راه رو با هم استفاده میکنن، روی صدها یا هزاران کارت. و اینجا هم دوباره قانون امدال سر میرسه: هر چی کارت بیشتر بشه، وقتی که صرف حرف زدن کارتها با هم میشه، سهم بیشتری از کل کار رو میگیره.
کی لازمه
حالا که میدونید GPU چرا سریعه، سوال عملی اینه: شما لازمش دارید یا نه؟
مدلهای کوچیک نه؛ رگرسیون خطی، درخت تصمیم و همهی کارهایی که تویپروژهی اول و درس قبل کردیم، روی CPU معمولی در یه چشم به هم زدن تموم میشن. شبکههای عمیق روی عکس، صدا یا متن آره؛ اینجاست که میلیونها پارامتر و میلیونها مثال پیدا میشه و تفاوت روز و ساعت میشه. ولی لازم نیست بخرید؛ دفترچههای کد آنلاین هستن که یه GPU هم همراهشون میدن، و سرویسهای ابری GPU رو ساعتی اجاره میدن. برای استفاده از مدل آماده هم خیلی وقتها کمتر لازمه، چون آموزش سنگینترین بخشه و گرفتن جواب از یه مدل آموزشدیده خیلی سبکتره. و برق و گرما رو هم حساب کنید؛ GPUهای قوی صدها وات برق میکشن.
اشتباههای رایج
رایجترینش خریدن GPU برای کاری که لازمش نداره؛ برای یه جدول چند هزار ردیفی، CPU کافیه و گاهی حتی سریعتره، چون جابهجا کردن داده به کارت خودش وقت میگیره. بعدی نگاه کردن فقط به تعداد هستهها و فراموش کردن حافظه؛ مدلی که جا نشه، هیچجوره اجرا نمیشه. بعدی اینکه داده دیر به GPU برسه و GPU بیشتر وقتش بیکار بمونه. بعدی انتظار سرعت متناسب با تعداد کارتها، که قانون امدال میگه همیشه ممکن نیست. و آخری، فراموش کردن هزینهی برق و خنک کردن، که توی کارهای طولانی کم نیست.
از کجا اومد
قصهی GPU و یادگیری عمیق، کوتاه ولی پرسرعته:
از حدود سال ۲۰۰۱، استفاده از کارت گرافیک برای حسابهای غیرگرافیکی عملی شد. کومار چلاپیلا و همکارهاش سال ۲۰۰۶ یه شبکهی پیچشی رو روی GPU چهار برابر سریعتر از CPU آموزش دادن. راجات راینا، آناند مادهاوان و اندرو انگ سال ۲۰۰۹ تا ۷۰ برابر سرعت گرفتن. سال ۲۰۱۲ الکس کریژفسکی، ایلیا سوتسکور و جفری هینتون شبکهشون رو روی دو کارت توی اتاق خواب آموزش دادن و همهچی عوض شد. و از ۲۰۱۷ کارتها هستههای مخصوص ضرب ماتریس گرفتن.
با این درس، ایستگاه ابزار و کد تموم شد. حالا وقتشه از دفترچه بیایم بیرون و ببینیم مدل توی دنیای واقعی، با آدمهای واقعی، چه مسیری داره: مراحل یک پروژهی واقعی.
جمعبندی. آنچه از این درس با خودتان میبرید.
- CPU: چند هستهی قوی. GPU: هزاران هستهی ساده.
- هر لایهی شبکه میلیونها ضرب مستقل از همه.
- کار مستقل رو میشه بین هزار تا کارگر پخش کرد.
- حافظهی کارت هم سریعتره و هم اندازهی مدل رو محدود میکنه.
- برای مدلهای کوچیک CPU کافیه؛ برای شبکههای بزرگ GPU.
خودتون رو بسنجید
۱۵ پرسش، هر بار تازه از میان ۳۰ پرسش این درس. آخرش فقط کارنامه رو میبینید: چند تا درست، چند تا نادرست.