بچینگ ناهمزمان: وقتی CPU و GPU با هم کار میکنن
خلاصهٔ کاملتر
وقتی با یه مدل زبانی بزرگ (LLM) کار میکنی، هر ثانیهای که GPU بیکاره یعنی پول دور ریختن. مقالهای که تیم Hugging Face منتشر کرده نشون میده که در بچینگ همزمان (synchronous batching)، CPU و GPU نوبتی کار میکنن: GPU محاسبه میکنه، CPU صبر میکنه؛ بعد CPU بچ بعدی رو آماده میکنه، GPU صبر میکنه. در یه آزمایش واقعی با مدل ۸ میلیاردپارامتری و بچسایز ۳۲ برای ۸ هزار توکن، مشخص شد که ۲۴٪ از کل زمان تولید GPU بیکار بوده.
راهحل، بچینگ ناهمزمان (async batching) هست: به جای اینکه منتظر بمونیم GPU کارش تموم بشه، CPU همزمان بچ بعدی رو آماده میکنه. برای این کار باید از CUDA streams استفاده کرد. یه stream یه صف مرتب از عملیاتهای GPUه که بهترتیب اجرا میشن. عملیاتهای streamهای مختلف میتونن موازی اجرا بشن. نکته مهم اینه که PyTorch پیشفرض از «default stream» استفاده میکنه که ذاتاً همزمانسازه و همزمانی رو خراب میکنه؛ پس باید از non-default streams استفاده کرد.
سه stream جداگانه لازمه: یکی برای انتقال داده از CPU به GPU (H2D)، یکی برای محاسبه، و یکی برای انتقال نتایج از GPU به CPU (D2H). اما streams به خودی خود نمیدونن باید منتظر هم باشن. اینجاست که CUDA events وارد میشن — نشانگرهایی که یه stream میتونه ثبت کنه و stream دیگهای میتونه منتظرشون بمونه، بدون اینکه CPU بلاک بشه. مثلاً compute stream منتظر h2d_done میمونه و D2H stream منتظر compute_done.
دو چالش فنی هم وجود داشت. اول race condition: اگه بچ N و N+1 از همون بافر حافظه استفاده کنن، دادهها خراب میشن. راهحل، دو اسلات جداگانه برای ورودیهاست که پشتسرهم استفاده میشن. برای اینکه CUDA graphs هم با این ساختار کار کنن، از یه memory pool مشترک استفاده میشه تا مصرف VRAM دوبرابر نشه.
دوم مسئله carry-over هست: توکن خروجی بچ N باید ورودی بچ N+1 بشه، ولی موقع آمادهسازی N+1 هنوز نتیجه N نیومده. راهحل اینه که یه placeholder صفر جای اون توکن میذاریم و یه «carry-over mask» میسازیم که بعد از تموم شدن بچ N، توکن واقعی رو جایگزین میکنه — این عملیات سبک هم داخل CUDA graph ضبط میشه.
نتیجه نهایی چشمگیره: در همون آزمایش، GPU حالا ۹۹.۴٪ از کل زمان فعاله (در مقابل ۷۶٪ قبلاً) و زمان تولید از ۳۰۰.۶ ثانیه به ۲۳۴.۵ ثانیه رسید — یعنی ۲۲٪ سریعتر، بدون هیچ تغییری در مدل یا کرنلها. این پیادهسازی الان بخشی از کتابخانه transformers شده.
نکات کلیدی:
- بچینگ همزمان معمولی باعث میشه GPU تا ۲۴٪ وقت بیکار بمونه
- CUDA streams اجازه میدن H2D، compute و D2H موازی اجرا بشن
- CUDA events بین streams ترتیب اجرا رو بدون بلاک کردن CPU تضمین میکنن
- دو اسلات حافظه + memory pool مشترک از race condition و اتلاف VRAM جلوگیری میکنن
- مکانیزم carry-over توکن خروجی بچ N رو بدون تأخیر به ورودی بچ N+1 میرسونه
- نتیجه: ۲۲٪ افزایش سرعت، بدون تغییر در مدل




