مقدمهای کامل بر برنامهنویسی GPU با مثالهای عملی در CUDA و پایتون
برنامه نویسی GPU چیست؟
به گزارش اپست به نقل از cherryservers ، برنامه نویسی GPU روشی برای اجرای محاسبات عمومی بسیار موازی بر روی شتاب دهنده های GPU است.
در حالی که GPU های گذشته منحصراً برای گرافیک کامپیوتر طراحی شده بودند، امروزه آنها به طور گسترده برای محاسبات عمومی (محاسبات GPGPU) نیز استفاده می شوند. علاوه بر رندر گرافیکی، محاسبات موازی مبتنی بر GPU امروزه برای مدلسازی علمی، یادگیری ماشین و سایر مشاغل مستعد موازیسازی استفاده میشود.
تفاوت های محاسباتی CPU در مقابل GPU
واحد پردازش مرکزی (CPU) یک پردازنده همه منظوره بهینه شده برای تأخیر است که برای رسیدگی به طیف گسترده ای از وظایف متمایز به صورت متوالی طراحی شده است، در حالی که واحد پردازش گرافیکی (GPU) یک پردازنده تخصصی بهینه شده برای گذردهند که برای محاسبات موازی سطح بالا طراحی شده است.
سوال این است که آیا شما به یک شتاب دهنده GPU نیاز دارید به جزئیات مسئله ای که سعی در حل آن دارید بستگی دارد. هم CPU و هم GPU دارای حوزه های مختلفی از برتری هستند و دانستن محدودیت های آنها در هنگام تصمیم گیری در مورد استفاده از برنامه نویسی GPU برای پروژه خود به شما کمک خواهد کرد.
پروژه های پایتون خود را به راحتی بر روی سرورهای اختصاصی یا مجازی قدرتمند و مقرون به صرفه Cherry Servers مستقر و مقیاس کنید. از یک اکوسیستم ابری باز با API و ادغام های یکپارچه و یک کتابخانه پایتون بهره مند شوید.
API های برنامه نویسی GPU
GPU مسائل محاسباتی را از نظر اشکال هندسی اولیه درک میکند. امروزه چارچوبهای برنامهنویسی متعددی در دسترس هستند که این اشکال هندسی اولیه را برای شما در پسزمینه مدیریت میکنند، بنابراین میتوانید بر مفاهیم محاسباتی سطح بالاتر تمرکز کنید.
CUDA
CUDA پلتفرم محاسبات موازی و رابط برنامهنویسی کاربردی (API) است که توسط Nvidia در سال ۲۰۰۶ ایجاد شده است و دسترسی مستقیم به مجموعه دستورالعملهای مجازی GPU را برای اجرای هستههای محاسباتی فراهم میکند.
هستهها توابعی هستند که بر روی GPU اجرا میشوند. هنگامی که یک هسته را اجرا میکنیم، به صورت مجموعهای از رشتهها اجرا میشود. هر رشته به یک هسته CUDA واحد بر روی GPU نگاشت میشود و عملیات مشابهی را بر روی زیرمجموعهای از دادهها انجام میدهد. بر اساس طبقهبندی فلین، این یک محاسبه دادههای چندگانه دستورالعمل واحد (SIMD) است.
رشتهها در بلوکها گروهبندی میشوند و هنگام اجرای یک هسته، به مجموعهای متناظر از هستههای CUDA نگاشت میشوند. بلوکها بیشتر در شبکهها گروهبندی میشوند و هر اجرای هسته یک شبکه واحد ایجاد میکند.
مدل برنامهنویسی CUDA به مهندسان نرمافزار اجازه میدهد از GPUهای با قابلیت CUDA برای پردازش عمومی در C/C++ و Fortran استفاده کنند، و همچنین بستههای جانبی شخص ثالث برای Python، Java، R و چندین زبان برنامهنویسی دیگر نیز در دسترس هستند. CUDA با تمام GPUهای Nvidia از سری G8x به بعد و همچنین اکثر سیستمهای عامل استاندارد سازگار است.
OpenCL
در حالی که CUDA یک چارچوب اختصاصی است، OpenCL یک استاندارد باز برای برنامهنویسی موازی در پلتفرمهای ناهمگن است که توسط گروه Khronos ایجاد شده است. OpenCL با واحدهای پردازش مرکزی (CPU)، واحدهای پردازش گرافیکی (GPU)، پردازندههای سیگنال دیجیتال، آرایههای دروازهای قابل برنامهریزی میدانی (FPGA) و سایر پردازندهها یا شتابدهندههای سختافزاری کار میکند.
OpenCL بسیار متنوع است و با موفقیت توسط غولهای صنعت فناوری از جمله AMD، اپل، IBM، Intel، Nvidia، Qualcomm، سامسونگ و بسیاری دیگر پذیرفته شده است. این زبان بر اساس زبان C/C++ است و بستههای جانبی شخص ثالث نیز برای پایتون، جاوا، R، GO، جاوا اسکریپت و بسیاری دیگر در دسترس هستند.
OpenACC
OpenACC یک استاندارد برنامهنویسی موازی مبتنی بر دستورالعمل است که توسط کاربران طراحی شده است و برای دانشمندان و مهندسانی که علاقهمند به انتقال کدهای خود به طیف گستردهای از پلتفرمهای سختافزاری محاسبات با کارایی بالا (HPC) هستند، طراحی شده است. این استاندارد توسط کاربران و برای کاربران طراحی شده است.
هدف OpenACC سادهسازی برنامهنویسی موازی پلتفرمها و معماریهای سختافزاری ناهمگن CPU/GPU با تلاش برنامهنویسی بسیار کمتر از آنچه در یک مدل سطح پایین مورد نیاز است، میباشد. از زبانهای برنامهنویسی C/C++ و Fortran پشتیبانی میکند.
برنامه نویسی GPU با CUDA و پایتون
استانداردها و زبانهای برنامهنویسی متعددی برای شروع ساخت برنامههای شتابیافته با GPU وجود دارد، اما ما برای مثال خود، CUDA و پایتون را انتخاب کردهایم. CUDA سادهترین چارچوب برای شروع است و پایتون در زمینههای علمی، مهندسی، تحلیل داده و یادگیری عمیق که همگی به شدت به محاسبات موازی متکی هستند، بسیار محبوب است.
حتی در پایتون نیز میتوانید با سطوح مختلف انتزاع به برنامهنویسی GPU نزدیک شوید. منطقی است که از بالاترین سطح انتزاعی که نیازهای برنامه شما را برآورده میکند شروع کنید، مگر اینکه به قابلیتهای سفارشیسازی و کنترل بیشتری نیاز داشته باشید که سطوح پایینتر انتزاع میتوانند ارائه دهند.
بیایید روشهای استفاده از CUDA در پایتون را با شروع از بالاترین سطح انتزاع و حرکت به سمت پایینترین سطح مرور کنیم.
CUDA برای کتابخانههای تخصصی
اگر فقط میخواهید با شبکههای عصبی یا هر الگوریتم یادگیری عمیق دیگری کار کنید، احتمالا کتابخانههای تخصصی یادگیری عمیق مانند TensorFlow یا PyTorch انتخاب مناسبی برای شما هستند. این کتابخانهها میتوانند به طور خودکار بین پردازش CPU و GPU برای شما سوئیچ کنند.
CUDA به عنوان جایگزینی مستقیم
اگر شما دانشمندی هستید که با NumPy و SciPy کار میکنید، سادهترین راه برای بهینهسازی کد خود برای محاسبات GPU، استفاده از CuPy است. این کتابخانه بسیاری از توابع NumPy را تقلید میکند و به شما اجازه میدهد به سادگی کد NumPy خود را با توابع CuPy جایگزین کنید که به جای CPU بر روی GPU پردازش میشوند.
هنگامی که نیاز به استفاده از الگوریتمهای سفارشی دارید، ناگزیر باید به سطوح پایینتر انتزاع بروید و از NUMBA استفاده کنید. این کتابخانه دارای ارتباطاتی با CUDA است و به شما اجازه میدهد کرنلهای CUDA خود را به زبان پایتون بنویسید. به این ترتیب میتوانید با استفاده از فقط پایتون و بدون نیاز به تخصیص حافظه به صورت دستی، به CUDA C/C++ بسیار نزدیک شوید.
CUDA به عنوان افزونه C/C++
اگر بالاترین سطح کنترل بر روی سخت افزار مانند تخصیص دستی حافظه، موازی سازی پویا یا مدیریت حافظه بافت را می خواهید، هیچ راهی برای دور زدن استفاده از C/C++ وجود ندارد. راحت ترین راه برای انجام این کار برای یک برنامه پایتون، استفاده از یک افزونه PyCUDA است که به شما امکان می دهد کد CUDA C/C++ را در رشته های پایتون بنویسید.
نحوه شروع کار با CUDA برای پایتون در اوبونتو ۲۰٫۰۴؟
نصب CUDA در اوبونتو ۲۰٫۰۴
ابتدا باید درایورهای CUDA را دانلود کرده و آن را روی دستگاهی با GPU سازگار با CUDA نصب کنید. برای نصب CUDA بر روی اوبونتو ۲۰٫۰۴ با استفاده از یک نصب کننده محلی، دستورالعمل های زیر را دنبال کنید:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda-repo-ubuntu2004-11-4-local_11.4.2-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-4-local_11.2-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda
نصب کتابخانه CuPy
در مرحله بعد، نیاز داریم تا یک کتابخانه پایتون برای کار با CUDA نصب کنیم. همانطور که در بالا بحث شد، روش های مختلفی برای استفاده از CUDA در پایتون در سطوح انتزاع مختلف وجود دارد. از آنجایی که NumPy ستون فقرات اکوسیستم علم داده پایتون است، برای این ارائه تصمیم خواهیم گرفت که آن را شتاب دهیم.
ساده ترین راه برای استفاده از NumPy، استفاده از یک کتابخانه جایگزین به نام CuPy است که توابع NumPy را روی GPU تکرار می کند. می توانید نسخه پایدار بسته منبع CuPy را از طریق pip نصب کنید:
pip install cupy
نوشتن یک اسکریپت برای مقایسه CPU در مقابل GPU
در نهایت، می خواهید مطمئن شوید که CuPy به درستی روی سیستم شما کار می کند و چقدر می تواند عملکرد شما را بهبود بخشد. برای انجام این کار، بیایید یک اسکریپت ساده بنویسیم که این کار را انجام دهد.
کتابخانه های NumPy و CuPy و همچنین کتابخانه زمان را که قصد داریم برای معیار پردازش واحدها استفاده کنیم، وارد کنید.
import numpy as np
import cupy as cp
from time import time
در مرحله بعد، بیایید تابعی را تعریف کنیم که برای مقایسه عملکرد استفاده خواهد شد.
def benchmark_processor(arr, func, argument):
start_time = time()
func(arr, argument) # your argument will be broadcasted into a matrix automatically
finish_time = time()
elapsed_time = finish_time – start_time
return elapsed_time
سپس شما نیاز دارید که دو ماتریس را نمونهسازی کنید: یکی برای CPU و دیگری برای GPU. ما قصد داریم برای ماتریسهای خود شکلی با ابعاد ۹۹۹۹ در ۹۹۹۹ را انتخاب کنیم.
# load a matrix to global memory
array_cpu = np.random.randint(0, 255, size=(9999, 9999))
# load the same matrix to GPU memory
array_gpu = cp.asarray(array_cpu)
در نهایت، ما می خواهیم یک تابع جمع ساده را اجرا کنیم تا تفاوت عملکرد پردازنده CPU در مقابل پردازنده GPU را تعیین کنیم.
# benchmark matrix addition on CPU by using a NumPy addition function
cpu_time = benchmark_processor(array_cpu, np.add, 999)
# you need to run a pilot iteration on a GPU first to compile and cache the function kernel on a GPU
benchmark_processor(array_gpu, cp.add, 1)
# benchmark matrix addition on GPU by using CuPy addition function
gpu_time = benchmark_processor(array_gpu, cp.add, 999)
# determine how much is GPU faster
faster_processor = (gpu_time - cpu_time) / gpu_time * 100
و نتیجه را روی کنسول چاپ کنید.
print(f"CPU time: {cpu_time} seconds\nGPU time: {gpu_time} seconds.\nGPU was {faster_processor} percent faster")
پس از اجرای این اسکریپت بر روی یک دستگاه Intel Xeon 1240v3 با شتابدهنده گرافیکی Nvidia Geforce GT1030 از Cherry Servers GPU Cloud، تأیید کردهایم که افزودن اعداد صحیح چندین برابر سریعتر روی GPU اجرا میشود. به عنوان مثال، GPU هنگام استفاده از ماتریس ۱۰۰۰۰×۱۰۰۰۰ ۱۲۹۴ برابر سریعتر جمع اعداد صحیح را اجرا می کند.
در واقع، هر چه ماتریس بزرگتر باشد، ممکن است انتظار افزایش عملکرد بالاتری داشته باشید.

ما با استفاده از جمع اعداد صحیح برای ماتریس های دو بعدی ۱۰۰×۱۰۰، ۵۰۰×۵۰۰، ۱۰۰۰×۱۰۰۰، ۷۵۰۰×۷۵۰۰ و ۱۰۰۰۰×۱۰۰۰۰ عملکرد CPU را در مقابل GPU (در ثانیه) مقایسه کرده ایم. زمانی که از ماتریس های به اندازه کافی بزرگ استفاده می شود، GPU به طور قابل توجهی از CPU پیشی می گیرد.
نتیجه گیری
اگر با تکه های بزرگ داده کار می کنید که می توانند به صورت موازی پردازش شوند، احتمالاً ارزش آن را دارد که عمیق تر در برنامه نویسی GPU غوطه ور شوید. همانطور که مشاهده کردید، افزایش عملکرد هنگام استفاده از محاسبات GPU برای پردازش ماتریس های بزرگ قابل توجه است. در پایان روز، اگر برنامه شما بتواند از محاسبات موازی استفاده کند، ممکن است در زمان و منابع گرانبهای شما صرفه جویی کند.