مقدمه
چرا آموزش یک مدل هوش مصنوعی که روی پردازندهی معمولی (CPU) هفتهها طول میکشد، روی یک کارت گرافیک در چند ساعت انجام میشود؟ راز این تفاوت در ساختار پردازندهی گرافیکی (GPU) و توانایی آن در انجام هزاران محاسبه بهصورت همزمان نهفته است. اما برای آنکه بتوانیم این قدرت را برای محاسبات علمی و عمومی به کار بگیریم، به یک «پل» نرمافزاری نیاز داریم؛ پلی که پرکاربردترین آن CUDA نام دارد.
در این مقاله به زبان ساده توضیح میدهیم که CUDA چیست، چرا GPU برای پردازش موازی مناسب است، مفاهیم کلیدی آن کداماند و چگونه میتوانید اولین برنامهی CUDA خود را بنویسید و اجرا کنید.
CUDA چیست؟
CUDA (مخفف Compute Unified Device Architecture ) یک پلتفرم محاسبات موازی و مدل برنامهنویسی است که توسط شرکت NVIDIA توسعه یافته است. CUDA به برنامهنویسان اجازه میدهد از پردازندهی گرافیکی نه فقط برای رندر گرافیک، بلکه برای انجام محاسبات همهمنظوره (که به آن GPGPU یا «محاسبات همهمنظوره روی GPU» گفته میشود) استفاده کنند.
به بیان ساده، CUDA مجموعهای از ابزارها، کتابخانهها و افزونههایی برای زبانهایی مانند C،C ++ و Python فراهم میکند تا برنامهنویس بتواند بخشهای سنگین و قابل موازیسازی برنامه را روی هزاران هستهی پردازندهی گرافیکی اجرا کند، در حالی که بقیهی برنامه روی پردازندهٔ مرکزی باقی میماند.
چرا از GPU برای محاسبات موازی استفاده میکنیم؟
تفاوت اصلی
CPUو GPU در فلسفهی طراحی آنهاست.
پردازندهی مرکزی تعداد کمی هسته بسیار قدرتمند دارد که برای اجرای سریع وظایف پیچیده و ترتیبی بهینه شدهاند. در مقابل،
پردازنده گرافیکی هزاران هستهی سادهتر دارد که برای انجام همزمان تعداد بسیار زیادی محاسبهی مشابه طراحی شدهاند.
این ساختار، GPU را برای کارهایی که «بهطور ذاتی موازی» هستند فوقالعاده کارآمد میکند؛ یعنی کارهایی که میتوان آنها را به هزاران محاسبهی مستقل و همزمان تقسیم کرد. نمونههایی از این کارها عبارتاند از:
- آموزش و اجرای شبکههای عصبی و مدلهای یادگیری عمیق
- پردازش تصویر و ویدئو و عملیات ماتریسی سنگین
- شبیهسازیهای علمی مانند دینامیک سیالات، پیشبینی آبوهوا و دینامیک مولکولی
- محاسبات مالی و تحلیل دادههای حجیم
معماری و مفاهیم کلیدی CUDA
-
برای کار با CUDA باید با چند مفهوم پایه آشنا شوید:
- میزبان و دستگاه (Host & Device) در اصطلاح CUDA، به پردازندهی مرکزی و حافظهی آن «میزبان» و به پردازندهی گرافیکی و حافظهی آن «دستگاه» گفته میشود. برنامه روی میزبان آغاز میشود و بخشهای موازی را به دستگاه میسپارد
- کرنل (Kernel) تابعی است که روی GPU و بهصورت موازی توسط هزاران نخ (thread) اجرا میشود. کرنل با کلیدواژهی __global__ تعریف میشود.
- نخ، بلوک و گرید (Thread, Block, Grid) واحد اجرای CUDA «نخ» است. نخها در «بلوکها» و بلوکها در یک «گرید» سازماندهی میشوند. این ساختار سلسلهمراتبی به CUDA اجازه میدهد محاسبات را روی دادههای بزرگ توزیع کند.
- سلسلهمراتب حافظه (Memory Hierarchy) دستگاه GPU انواع حافظه دارد (سراسری، اشتراکی، ثبات و ...) که مدیریت درست آنها نقش کلیدی در کارایی برنامه دارد.
روند کلی یک برنامهی CUDA معمولاً به این صورت است:
- ابتدا دادهها از حافظه میزبان به حافظهی دستگاه کپی میشوند، سپس کرنل روی GPU اجرا میشود
- و در پایان نتایج از دستگاه به میزبان بازگردانده میشوند.
نوشتن اولین برنامه CUDA
بیایید یک مثال کلاسیک را ببینیم: جمع دو بردار.
در این برنامه، هر نخ مسئول جمعکردن یک عنصر از دو بردار است، بنابراین هزاران جمع بهصورت همزمان انجام میشود. ابتدا کرنل را تعریف میکنیم:
__global__ void vectorAdd(float* A, float* B, float* C, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
C[i] = A[i] + B[i];
}
}
هر نخ با استفاده از شناسهی بلوک (blockIdx) و شناسهی نخ (threadIdx)، اندیس عنصری را که باید پردازش کند محاسبه میکند. سپس در برنامهی اصلی، کرنل را با تعیین تعداد بلوکها و نخها فراخوانی میکنیم:
// تخصیص حافظه روی GPU و کپی دادهها از CPU به GPU انجام میشود.
int threadsPerBlock = 256;
int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
vectorAdd<<< blocksPerGrid, threadsPerBlock >>>(d_A, d_B, d_C, n);
// سپس نتیجه از GPU به CPU بازگردانده میشود.
نشانهی <<< >>> همان چیزی است که اجرای موازی کرنل را روی GPU مشخص میکند. برای کامپایل این برنامه از کامپایلر اختصاصی NVIDIA به نام nvcc استفاده میشود:
nvcc vector_add.cu -o vector_addاجرای CUDA روی کلاستر و ابر سیمرغ
برای اجرای برنامههای CUDA به یک پردازندهی گرافیکی سازگار با NVIDIA نیاز دارید. در محیطهای محاسبات با کارایی بالا، این پردازندههای گرافیکی
معمولاً روی گرههای یک کلاستر قرار دارند و دسترسی به آنها از طریق زمانبند بار کاری
(مانند SLURM) مدیریت میشود.
به این ترتیب میتوانید برنامهی CUDA خود را در قالب یک کار به صف بفرستید و یک یا چند GPU درخواست کنید. نمونهی زیر یک اسکریپت SLURM برای اجرای برنامهی CUDA است:
#!/bin/bash
#SBATCH --job-name=cuda_job
#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH --time=00:30:00
#SBATCH --output=cuda_%j.log
module load cuda/12.2
srun ./vector_add
ابر سیمرغ این امکان را فراهم میکند که بدون نیاز به خرید سختافزار گرانقیمت، از منابع پردازش گرافیکی بهره ببرید. میتوانید خدمات پردازش ابری مجهز به GPU را از سیمرغ تهیه کنید، برنامههای CUDA خود را روی آن اجرا کنید و کارهای سنگین یادگیری عمیق و شبیهسازی را با هزینه و زمان بسیار کمتر به انجام برسانید.
برای آشنایی بیشتر میتوانید به وبسایت ابر سیمرغ مراجعه کنید.
جمع بندی
CUDA پلی است که قدرت عظیم پردازندههای گرافیکی را در اختیار برنامهنویسان قرار میدهد و امروزه ستون فقرات بسیاری از پیشرفتها در هوش مصنوعی و محاسبات علمی به شمار میرود. با درک مفاهیم پایهای مانند کرنل، سلسلهمراتب نخها و مدیریت حافظه، میتوانید نخستین گامها را در دنیای برنامهنویسی موازی روی GPU بردارید.
در مقالههای بعدی به موضوعات پیشرفتهتری مانند بهینهسازی حافظه در CUDA، استفاده از کتابخانههای آماده مانند cuBLAS و cuDNN، و برنامهنویسی چندماژوله (Multi-GPU) خواهیم پرداخت.
سوالات متداول
- آیا برای یادگیری CUDA حتماً باید کارت گرافیک NVIDIA داشته باشم؟ برای اجرای واقعی برنامههای CUDA به یک GPU سازگار با NVIDIA نیاز دارید، اما میتوانید از سرویسهای ابری مجهز به GPU (از جمله خدمات پردازشگر گرافیکی و/یا ابر هوش مصنوعی ابر سیمرغ) نیز استفاده کنید.
- تفاوت CUDA با OpenCL چیست؟ CUDA اختصاصی شرکت NVIDIA و فقط برای پردازندههای گرافیکی این شرکت است، در حالی که OpenCL یک استاندارد باز است که روی سختافزارهای مختلف اجرا میشود؛ اما CUDA معمولاً کارایی و پشتیبانی بهتری روی سختافزار NVIDIA دارد.
- آیا با پایتون هم میتوان از CUDA استفاده کرد؟ بله، کتابخانههایی مانند Numba، CuPy و PyCUDA امکان استفاده از CUDA را در پایتون فراهم میکنند و بسیاری از فریمورکهای یادگیری عمیق مانند PyTorch و TensorFlow بهصورت داخلی از CUDA بهره میبرند.
- کرنل (Kernel) در CUDA چیست؟ کرنل تابعی است که روی GPU و بهصورت موازی توسط هزاران نخ اجرا میشود و با کلیدواژه __global__ تعریف میشود.
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
ثبت نظر