استفاده از GPU به جای CPU برای اجرای کد ها
خیلی دوست داشتم یک روش ساده پیدا کنم که جدا از پیچیدگیها مرسوم فریمورکهای استفادهها از gpu ها بتوانم کدهای سادهام را به سریعترین حالت روی گرافیک اجرا بگیرم که خوب دیدم راهحلهای جالبی هست. یکی از این راهحلها که من خیلی حال کردم و خوب از استفاده کردم. ILGPU بود که برای فریمورک و ماشین داتنت طراحی و پیادهسازی شده.
چند تا نکته کوچک:
ما نمیتوانیم GPU را مانند یک CPU در پیادهسازی در نظر بگیریم، در واقع در این سه حوزه باید مقایسه کرد:
- دسترسی به حافظه
- محلی بودن داده
- Threading
در واقع GPU یک CPU نیست. ( GPU یک مجتمع از هزار یا هزاران هسته با اشتراک چند ده یا صدها واحد کنترل و کش است ولی CPU یک یا حداکثر چند ده هسته هست با همان تعداد واحد کنترل و کش و داستان TPU که جدا و اختصاصی برای یادگیری ماشین هست با معماری ماتریسی و سیستولی)
CPU چگونه کار میکند؟
یک پردازنده سنتی یکچرخه بسیار ساده دارد: واکشی، رمزگشایی و اجرا.
یک دستورالعمل را از حافظه میگیرد (واکشی)، نحوه اجرای دستورالعمل مذکور (رمزگشایی) را مشخص میکند و دستور را انجام میدهد (اجرا). سپس این چرخه برای تمام دستورالعملهای الگوریتم شما تکرار میشود. اجرای این جریان خطی از دستورالعملها برای اکثر برنامهها خوب است زیرا CPU ها بسیار سریع هستند و اکثر الگوریتمها بهصورت سریالی هستند.
وقتی الگوریتمی دارید که میتواند بهصورت موازی پردازش شود چه اتفاقی میافتد؟ یک CPU چندین هسته دارد که هرکدام واکشی، رمزگشایی و اجرای خود را انجام میدهند. میتوانید الگوریتم را در تمام هستههای CPU پخش کنید، اما در نهایت هر هسته همچنان یک جریان دستورالعملها را اجرا میکند، احتمالاً همان جریان دستورالعملها، اما با دادههای متفاوت. CPU ها ترفندی برای برنامههای موازی به نام SIMD دارند. اینها مجموعهای از دستورالعملها هستند که به شما اجازه میدهند تا یک دستور عملیات را روی چندین قطعه داده به طور همزمان انجام دهد. (بحث بیشتر مختص درس معماری کامپیوترهاست که بسیار مفصل است.) کتابخانه و فریمورکهای زیادی به ما کمک میکنند که اکثرشان تبدیل و دسترسی به کدهای c++ را پیشنهاد میکنند. من قبلاً برای کارهای گرافیکی از کتابخانه SHARPDL استفاده کرده بودم. وای الان منظور کارهای گرافیکی نیست و اجرای کد روی پردازنده گرافیکی به رام موضوعیت دارد.
لیست اینها عبارتاند از:
- CUDAfy .NET allows easy development of high performance GPGPU applications completely from the .NET. It’s developed in C#. (by lepoco)
- arrayfire-rust – Rust wrapper for ArrayFire
- NvAPIWrapper – NvAPIWrapper is a .Net wrapper for NVIDIA public API, capable of managing all aspects of a display setup using NVIDIA GPUs
- novideo_srgb – Calibrate monitors to sRGB or other color spaces on NVIDIA GPUs, based on EDID data or ICC profiles
- cuda-api-wrappers – Thin C++-flavored header-only wrappers for core CUDA APIs: Runtime, Driver, NVRTC, NVTX.
- TinyNvidiaUpdateChecker – Check for NVIDIA GPU driver updates!
- srmd-ncnn-vulkan – SRMD super resolution implemented with ncnn library
- ZenTimings A free, simple and lightweight app for monitoring memory timings on Ryzen platform.
- Amplifier.NET – Amplifier allows .NET developers to easily run complex applications with intensive mathematical computation on Intel CPU/GPU, NVIDIA, AMD without writing any additional C kernel code. Write your function in .NET and Amplifier will take care of running it on your favorite hardware.
- Hybridizer – Examples of C# code compiled to GPU by hybridizer
- Hades-VegaM – Moded AMD RX Vega M series gpu driver to support Intel NUC Hades Canyon and all other products on Windows 10/11
- waifu2x-converter-cpp – Improved fork of Waifu2X C++ using OpenCL and OpenCVV
شما می توانید تست این برنامه را در گیت هاب مشاهده نمایید.
برنامه نویسی پیادهسازی با استفاده از منابع کارت گرافیک در داتنت
برنامهنویسی بسیار سادهای دارد. کافی است بعد از نوشتن روال، اجرا به تابع کرنل پاس بدهید و تمام.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | using ILGPU; using ILGPU.Runtime; using ILGPU.Runtime.Cuda; using System.Diagnostics; //گرفتن یک زمینه پیشفرض برای شروع کار using var context = Context.CreateDefault(); بهازای هر دستگاه موجود یکبار برنامه را اجرا میکنیم.// foreach (var device in context.Devices) { using var accelerator = device.CreateAccelerator(context); Console.WriteLine($"Performing operations on {accelerator}"); var sw = new Stopwatch(); sw.Restart(); MyKernel(accelerator); sw.Stop(); Console.WriteLine($"- Naive implementation: {sw.ElapsedMilliseconds}ms"); } Console.ReadLine(); روال اصلی برنامه که محاسبه عدد اول را دارد// static void MyCalcute(Index1D total, ArrayView<long> results, Input<int> TotalTag) { int Total = 0; for (int i = 1; i < 10000; i++) { bool isPrime = true; for (int j = 2; j < i - 1; j++) if ((i % j) == 0) { isPrime = false; break; } if (isPrime) { results[Total] = i; Total++; } } } پاسدادن روال به کرنل دستگاه// static void MyKernel(Accelerator accelerator) { using var buffer = accelerator.Allocate1D<long>(10000); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<long>, Input<int>>(MyCalcute); kernel( (int)buffer.Length, buffer.View, 10000); var results = buffer.GetAsArray1D(); } |
البته این با CUDA متفاوت است؛ ولی نگران نباشید. کافی ایست این افزونه را درصورتیکه پردازنده گرافیکی شما پشتیبانی میکند دانلود کنید و سپس با تغییر کوچکی از این روش استفاده کنید.
1 2 3 | using var context = context.GetCudaDevices() بهجای Context.CreateDefault(); |
هدفم صرفاً شراکت شما در حال برده شده از این کار بود. طبعاً دانش و اطلاعات بیشتری باید در این زمینه داشت برای کارهای بزرگتر.