Что такое Куда
Собственная разработанная NVIDIA архитектура, называемая CUDA (Computer Unified Device Architecture), используется для параллельных вычислений и представляет собой модуль интерфейса прикладного программирования. Он предлагает простые API для управления устройствами и памятью и выполняет все вычисления параллельно на графических процессорах, используя сетку потоков и блоков. CUDA использует гетерогенную систему или ЦП (хост) и графический процессор, чтобы обеспечить общую память и синхронизацию потоков (устройство). Языки C/C++ поддерживаются CUDA, а для компиляции программы используется компилятор NVIDIA nvcc.
Давайте более подробно обсудим логическую иерархию CUDA, прежде чем переходить к программированию CUDA.
Зачем вам использовать GPU?
ЦП невероятно мощный и лучше всего работает для операций с малой задержкой, когда мы хотим, чтобы небольшое количество последовательных задач выполнялось одновременно. Операционная система включает и выключает потоки для достижения многопроцессорности, что является медленной и дорогостоящей процедурой, но если нам нужно запустить код, требующий массивной параллельной обработки, процессору потребуется больше времени для выполнения, поскольку он может сделать это только потому, что потоки в ЦП тяжелые. По сравнению с CPU, GPU имеют значительно более высокую вычислительную производительность и пропускную способность. Для параллельной обработки данных GPU лучше. Они работают лучше при работе с большими объемами данных и выполнении небольших операций, таких как арифметика с плавающей запятой. Лучший метод реализации параллелизма в программировании — использование графического процессора.
Терминология
Хост: Доступный ЦП системы является хостом. Память хоста — это системная память (D RAM), подключенная к хосту.
Устройства: графический процессор — это устройство, поэтому память графического процессора называется памятью устройства.
Ядро: выполняется один поток в графическом процессоре. функции ядра.
Запуск ядра: это когда ЦП сообщает графическому процессору о необходимости одновременного выполнения кода.
Конфигурация выполнения: сколько потоков выполняется на графическом процессоре и сколько их в целом.
Видеокарты NVDIA имеют модуль с плавающей запятой, называемый ядрами CUDA, который способен выполнять карты с плавающей запятой.
Рабочий процесс
Посмотрим рабочий процесс-
Перенос с хоста на устройство: память выделяется памяти устройства с помощью cudaMalloc. CudaMemcpy используется для копирования данных из памяти хоста на целевое устройство. Связь между устройством и хостом осуществляется через шину PCI.
Загрузка ядра: загрузка программы графического процессора, ее запуск и локальное кэширование данных для повышения производительности.
Перенос с устройства на хост: с помощью cudaMemory скопируйте результаты из памяти устройства в память хоста. Используя cudaFree, память стирается с оборудования.
Давайте сделаем основную программу в cuda
#include <cuda.h> __global__ void Cuda_programming() { printf("Cuda programming\n"); } int main() { Cuda_programming<<<1,1>>>(); cudaDeviceSynchronize(); return 0; }
- Фраза «__global__: is a» означает, что функция вызывается с хоста и выполняется на GPU (ЦП). Код ядра определен.
- Cuda_programming‹‹‹1,1››(): Запуск ядра обозначается символом
<<<M,T>>>.
Для кода, содержащего символ __global__, он должен быть вызван. После запуска ядра M блоков потоков располагаются в сетке. В каждом блоке потоков имеется T параллельных потоков. - cudaDeviceSynchronize(): код cuda запускается асинхронно, поэтому хост может не всегда ждать, пока устройство завершит его выполнение. cudaDeviceSyncronize ожидает завершения выполнения устройства.
После выполнения кода-
$ nvcc -o Cuda programming Cuda programming.cu
Код устройства и код хоста разделены компилятором NVIDIA. Основная функция построена с помощью компилятора gcc, а код устройства: Cuda_programming создан с помощью компилятора NVDIA.
Программное обеспечение просто печатает «Программирование Cuda». Оно не демонстрирует все возможности cuda. Программирование Cuda выполняется только в этом потоке. Но давайте сначала изучим немного больше теории.
Иерархия потоков
- Блоки: блок представляет собой группу потоков.
- grid: коллекции блоков составляют сетки.
- Поток — это отдельный процесс, который выполняется в ядре cuda. Единая сетка создается каждым вызовом ядра.
Встроенные переменные
- threadIdx.x: используя внутреннюю переменную, мы можем получить индекс блока текущего потока.
- blockId.x: у нас есть доступ к индексу текущего блока сетки.
- blockDim.x: количество потоков в блоке можно получить с помощью встроенной переменной.
- gridDim.x: Мы можем настроить размер сетки, используя внутреннюю переменную.
давайте возьмем пример
Распараллеливание добавления векторов с использованием многопоточности
Используя блок потоков с 256 потоками, в этом упражнении мы будем распараллеливать сложение векторов (вектор add.cu). Ниже приведена схема новой установки выполнения ядра.
vector_add <<< 1 , 256 >>> (d_out, d_a, d_b, N);
Переменные, встроенные в ядро CUDA, используются для получения доступа к данным потока. Из них в этом эксперименте будут использоваться 2 threadIdx.x и blockDim.x:
threadIdx.x: этоиндекс потока в блоке.
blockDim.x: это размер блока потока (количество потоков в блоке потока).
Значение threadIdx.x: из диапазона 0–255 и равно blockDim.x: 256 для настройки вектора add().
__global__ void vector_add(float *out, float *a11, float *a12, int a13) { int index = 0; int stride = 1 int i = index; while(i < n)){ out[i] = a11[i] + a12[i]; i += stride; } } int main() { float *b11, *a12, *out; float *b12; b11 = (float*)malloc(sizeof(float) * N); a12 = (float*)malloc(sizeof(float) * N); out = (float*)malloc(sizeof(float) * N); int i=0; while(i<N){ b11[i] = 0.1f; a12[i] = 0.2f; i++; } cudaMalloc((void**)&b12, sizeof(float) * N); cudaMemcpy(b12, b11, sizeof(float) * N, cudaMemcpyHostToDevice); vector_add<<<1,256>>>(b11, a12, out, N); cudaFree(b12); free(b11); free(a12); free(out); return 0; }
Конфигурация ядра потока передается через M и N. 32-битное ядро выполняется через CUDA. Значение thread(threadIdx) в этом случае находится в диапазоне от 1 до 256. А 256 — это значение block(blockDim.x). Если мы установим 1,1›››, тогда и шаг, и индекс будут равны 1. Тогда код добавления вектора будет следующим:
int i=0; while(i<0){ ... i++; }
В этой реализации все массивы перебираются одним потоком для вычисления сложения векторов. Процесс сложения потенциально может быть разделен на 256 потоков и вычисляться одновременно.
Цикл для k-го потока перебирает массив с шагом 256 с помощью цикла, начиная с k-го элемента. Например, сложение вычисляется k-м потоком элемента, который является k-м в 0-й итерации. Добавление (k+245)-элемент вычисляется по k-му с помощью итерации и так далее. Принцип показан на следующем рисунке.
Компоненты графического процессора
глобальная память: эквивалентна оперативной памяти процессора. Он доступен как для хостов, так и для устройств.
Потоковый мультипроцессор: система, которая фактически выполняет вычисления. Его ядра CUDA, говоря простым языком.
Несколько параллельных процессоров, известных как SM, находятся в графических процессорах CUDA. Каждый SM имеет несколько параллельных процессоров и может управлять несколькими параллельными блоками потоков. Используем это в своих интересах. Размер блока 256 уже установлен. Теперь необходимо найти размер сетки, которую мы разделили. по размеру блока, количеству элементов.
int BLOCKS_NUM = (N + BLOCK_SIZE — 1) / BLOCK_SIZE; vector_add<<<BLOCKS_NUM, BLOCK_SIZE>>>(h_a, b, out, N);
Преимущества:
- Простота в использовании: API CUDA позволяет нам использовать GPU без необходимости глубокого понимания GPU. Кроме того, учитывая, что CUDA — это просто C с расширениями NADIA. Его можно использовать для существующих кодовых баз практически без модификации кода.
- Производительность: NVDIA разрабатывает не только оборудование, но и API. Это позволяет им точно настроить API, чтобы получить максимальную производительность от вашего графического процессора.
Недостатки:
- Частное программное обеспечение: компания NVDIA с закрытой архитектурой является владельцем CUDA. Для запуска в системе нам требуется аппаратное обеспечение NVDIA и инструменты CUDA sdk.
- Рекурсия возможна только в простых циклах; это трудно построить и трудно уйти с рук.