Можно ли использовать видеокарту для вычислений?
Вроде как поддержки CUDA у python нет, но есть обертка PyCUDA, если у Вас видеокарта от NVidia: https://documen.tician.de/pycuda/, либо NUMBA, как написали выше. Вот статтья на сравнение этих двух: https://habr.com/ru/post/317328/ И пособие в котором описано больше по GPU+Python: https://cmp.phys.msu.ru/sites/default/files/GPUPython.pdf
Отслеживать
ответ дан 11 дек 2022 в 12:17
56 6 6 бронзовых знаков
- python
- производительность
- видеокарта
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.11.15.1019
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Как GPU-вычисления буквально спасли меня на работе. Пример на Python
Сегодня мы затрагиваем актуальнейшую тему — Python для работы с GPU. Автор рассматривает пример, тривиальный в своей монструозности, и демонстрирует решение, сопровождая его обширными листингами. Приятного чтения!
Никого из нас в той или иной форме не обошел хайп вокруг GPU-вычислений, развернувшийся в последнее время. Прежде, чем вы станете читать далее, поясню: я не эксперт по GPU. Мой путь в мире GPU только начинается. Но эта технология сегодня достигла такой мощи, что, вооружившись ею, можно решать целую уйму задач. Мне на работе поручили задачу, на выполнение которой машина тратила целые часы, а прогресса так и не было видно. Но, стоило мне взяться за GPU – и проблема стала решаться за секунды. Задачу, на выполнение которой ориентировочно требовалось 2 суток, я смог решить всего за 20 секунд.
В следующих разделах я детально опишу эту задачу. Также мы обсудим, как и когда использовать GPU для решения любых подобных задач. Итак, читаем внимательно – поверьте, вы не пожалеете. Сначала мы вникнем в детали задачи, затем освоимся с GPU и, наконец, воспользуемся GPU для решения этой задачи. Я буду пользоваться библиотекой Python Numba и графическим процессором Nvidia Volta V100 16GB GPU.
1. Подробное описание задачи
В сфере розничной торговли часто приходится искать похожие или наиболее близкие объекты. Мне дали список позиций, каждая из которых была представлена k латентными атрибутами. Итак, мне было поручено найти топ-3 наиболее схожих позиций к каждой из позиций списка. Метрикой схожести в данной задаче было выбрано косинусное сходство. Вот как выглядели мои данные.

Список позиций данных с 64-латентными признаками
Сложность задачи
Мне дали список, в котором было около 10⁵ позиций. Поиск 3 наиболее схожих позиций для каждой из них потребовал бы проверить косинусное сходство с каждым без исключения элементов в списке. Получалось бы n * k операций, где n – количество позиций, а k – атрибуты на каждую позицию. Потребовалось бы получить скалярное произведение данной позиции с каждой из остальных позиций в списке.
def top_3_similar_items(X,mindex): # X это список позиций, нам требуется найти топ-3 позиций, наиболее похожих на позицию с индексом 'mindex' SMALL = -9999.0 # Просто для инициализации, поскольку нам нужно найти позицию с максимальным уровнем сходства(косинусный показатель) first_best_val = SMALL # индекс первой по схожести позиции будет храниться здесь first_best_index = -1 # косинусное сходство с первой по схожести позицией second_best_val = SMALL # индекс второй по схожести позиции будет храниться здесь second_best_index = -1 # косинусное сходство со второй по схожести позицией third_best_val = SMALL # индекс третьей по схожести позиции будет храниться здесь third_best_index = -1 # косинусное сходство с третьей по схожести позицией for j in range(n): # Всего n-позиций в списке if(mindex==j): continue tmp = np.dot(X[mindex],X[j])/(np.sqrt(np.sum(np.square(X[mindex]))) * np.sqrt(np.sum(np.square(X[j])))) # вычисление скалярного произведения if(tmp>=first_best_val): third_best_val = second_best_val third_best_index = second_best_index second_best_val = first_best_val second_best_index = first_best_index first_best_val = tmp first_best_index = j elif(tmp>=second_best_val): third_best_val = second_best_val third_best_index = second_best_index second_best_val = tmp second_best_index = j elif(tmp>third_best_val): third_best_val = tmp third_best_index = j return first_best_val,first_best_index,second_best_val,second_best_index,third_best_val,third_best_index # Возврат всех значений
Код для нахождения трех позиций, максимально подобных заданной
Теперь, при нахождении топ-3 схожих для всех позиций в списке, сложность умножается еще на n. Окончательная сложность оказывается равна O(n*n*k) = O(n²k).
def top_3_similar_all_items(X): # X это список позиций, из которого мы должны выбрать 3 наиболее схожих с каждой позицией SMALL = -9999.99 # Просто для инициализации, поскольку нам нужно найти позицию с максимальным уровнем сходства(косинусный показатель) first_best_index = np.zeros(n,dtype=int) # индекс первой по схожести позиции будет храниться здесь first_best_val = np.zeros(n,dtype=float) # косинусное сходство с первой по схожести позицией second_best_index = np.zeros(n,dtype=int) # индекс второй по схожести позиции будет храниться здесь second_best_val = np.zeros(n,dtype=float) # косинусное сходство со второй по схожести позицией third_best_index = np.zeros(n,dtype=int) # индекс третьей по схожести позиции будет храниться здесь third_best_val = np.zeros(n,dtype=float) # косинусное сходство с третьей по схожести позицией for i in range(n): # Initialisation first_best_val[i] = SMALL first_best_index[i] = -1 second_best_val[i] = SMALL second_best_index[i] = -1 third_best_val[i] = SMALL third_best_index[i] = -1 for j in range(n): if(i==j): continue tmp = np.dot(X[i],X[j])/(np.sqrt(np.sum(np.square(X[i]))) * np.sqrt(np.sum(np.square(X[j])))) # вычисление скалярного произведения if(tmp>=first_best_val[i]): third_best_val[i] = second_best_val[i] third_best_index[i] = second_best_index[i] second_best_val[i] = first_best_val[i] second_best_index[i] = first_best_index[i] first_best_val[i] = tmp first_best_index[i] = j elif(tmp>=second_best_val[i]): third_best_val[i] = second_best_val[i] third_best_index[i] = second_best_index[i] second_best_val[i] = tmp second_best_index[i] = j elif(tmp>third_best_val[i]): third_best_val[i] = tmp third_best_index[i] = j return first_best_val,first_best_index,second_best_val,second_best_index,third_best_val,third_best_index # Возврат всех значений
Код для нахождения трех наиболее схожих позиций для каждой позиции в списке
Тестовый прогон и оценка времени
Я запустил код, попробовав найти 3 наиболее похожие позиции из подмножества, содержащего n = 10³ позиций с k = 64. На выполнение этой задачи при помощи Python потребовалось около 17 секунд со средней скоростью 3.7 *10⁶ операций в секунду. Код был хорошо оптимизирован с применением операций и массивов Numpy. Отмечу, что все эти операции последовательно выполняются на CPU.
%time first_best_val,first_best_index,second_best_val,second_best_index,third_best_val,third_best_index = top_3_similar_all_items(X)
Прогон для n = 10³ позиций

Вывод: время, потребовавшееся для n = 10³ позиций
Далее я увеличил тестовое подмножество до n =10⁴ позиций. Поскольку сложность равна O(n²k), длительность выполнения возросла в 100 раз (поскольку n возросла в 10 раз). На выполнение кода ушло 1700 секунд = 28,33 минуты.

Вывод: время, потребовавшееся на обработку n = 10⁴ позиций
Далее подходим к самому важному: оценке времени, которое потребуется на обработку полноценного списка из 10⁵ позиций. Посчитав, увидим, что временная сложность вновь возрастет в 100 раз, поскольку временная сложность алгоритма равна O(n²k).
Ориентировочное время = 1700 * 100 секунд = 2834 минуты = 47,2 часа ~ 2 дня.
О, Господи! Так долго.
Вероятно, вы уже догадываетесь, что на самом деле мне удалось все сделать очень быстро, воспользовавшись силой GPU. На самом деле, выигрыш во времени при использовании GPU просто шокирует. Цифры я оставлю на закуску, а пока предлагаю вам познакомиться с миром GPU.
2. Сравнение CPU и GPU
Центральный процессор (CPU), в сущности, является мозгом любого вычислительного устройства: он выполняет записанные в программе инструкции, осуществляя управляющие, логические операции, а также операции ввода/вывода.
Правда, у современных CPU по-прежнему не так много ядер, и базовая структура и назначение CPU – обработка сложных вычислений – в сущности, не изменились. На самом деле, CPU лучше всего подходит для решения задач, заключающихся в разборе или интерпретации сложной логики, содержащейся в коде.
В свою очередь, графический процессор (GPU) обладает более мелкими логическими ядрами, которых, однако, гораздо больше (речь идет об арифметико-логических устройствах (АЛУ), управляющих элементах и кэш-памяти), которые спроектированы с расчетом на параллельную обработку в целом идентичных и сравнительно простых операций.
У GPU больше арифметических логических устройств (ALU), чем у типичного CPU, поэтому повышена способность параллельной обработки простых операций
Рисунок: сравнение CPU и GPU ~ Источник изображения
3. Когда использовать вычисления на GPU
CPU лучше подходит для выполнения сложных линейных задач. Несмотря на то, что ядра CPU мощнее, GPU позволяют эффективнее и быстрее обрабатывать задачи, связанные с ИИ, машинным и глубоким обучением. GPU справляется с рабочими нагрузками, распараллеливая схожие операции.
Представление об операциях с точки зрения GPU: возьмем, к примеру, операцию поиска слова в документе. Ее можно выполнить последовательно, перебрав одно за другим все слова в документе, либо параллельно, то есть, построчно, либо с поиском по конкретному слову.
Представление об операциях с точки зрения CPU — есть такие операции, например, расчет ряда Фибоначчи, которые нельзя распараллелить. Ведь найти очередное число можно лишь после того, как вычислишь предыдущие два. Такие операции лучше всего подходят для CPU.
В свою очередь, такие операции, как сложение и перемножение матриц, легко выполняются с применением GPU, поскольку большинство из этих операций в матричных ячейках являются независимыми друг от друга, схожи по природе, и поэтому могут быть распараллелены.
4. Кратко о CUDA
CUDA – это платформа для параллельных вычислений и модель API, созданная компанией Nvidia. С помощью этого API можно использовать процессор с поддержкой CUDA-графики для широкого спектра вычислений. Такой подход получил название GPGPU (неспециализированные вычисления на графических процессорах). Здесь о них рассказано подробнеe.
Numba – это свободно распространяемый JIT-компилятор, транслирующий подмножество Python и NumPy в быстрый машинный код при помощи LLVM, это делается средствами пакета llvmlite на Python. В этом пакете предлагается ряд вариантов по распараллеливанию кода на Python для CPU и GPU, в самом коде при этом зачастую достаточно минимальных изменений. См. подробнеe.
Работая с процессором Nvidia Volta V100 16GB GPU, я пользовался библиотекой Numba.
Детали архитектуры ~ потоки, блоки и гриды
CUDA организует параллельные вычисления при помощи таких абстракций как потоки, блоки и гриды.
Поток: поток CUDA – это назначенная цепочка инструкций, поступающих/текущих в ядро CUDA (на самом деле, это просто конвейер). На лету на одном и том же ядре CUDA может существовать до 32 потоков (в таком случае заполняются все звенья этого конвейера). Это выполнение ядра с заданным индексом. Каждый поток использует свой индекс для доступа элементов в массиве таким образом, что вся совокупность имеющихся потоков совместно обрабатывает все множество данных.
Блок: это группа потоков. Не так много можно рассказать о выполнении потоков внутри блока: они могут выполняться как последовательно, так и параллельно, так и без какого-либо определенного порядка. Можно координировать потоки, например, при помощи функции _syncthreads() , заставляющей поток остановиться в определенной точке в ядре и дождаться, пока все остальные потоки в том же блоке также достигнут этой же точки.
Грид: Это группа блоков. Между блоками отсутствует всякая синхронизация.

CUDA: потоки, блоки, гриды ~ Источник
Но где же именно выполняются потоки, блоки и гриды? В случае архитектуры G80 GPU от Nvidia, вычисления, по-видимому, распределены следующим образом:
Грид → GPU: Весь грид обрабатывается единственным GPU-процессором.
Блок → МП: Процессор GPU организован как коллекция мультипроцессоров, где каждый мультипроцессор отвечает за обработку одного или более блоков в гриде. Один блок никогда не делится между несколькими МП.
Поток → ПП: Каждый МП далее подразделяется на процессоры потоков (ПП), и каждый ПП обрабатывает один или более потоков в блоке.
Я позаимствовал некоторый материал из этой отлично написанной статьи. Рекомендую почитать ее внимательно.
5. Простая программа для сложения массивов на Python, использующая GPU
Как я уже говорил в самом начале, суть данной статьи – помочь широкой аудитории понять силу GPU и приобрести интуитивное представление, как применять GPU для решения повседневных задач. Перед тем, как начинать писать код для GPU, возможно, придется провести некоторые предварительные исследования. Для этого давайте разберем в качестве примера программу для сложения массивов.
Допустим, у нас есть два массива, ‘a’ и ‘b’ размера ‘n’. Мы хотим сгенерировать массив ‘c’, такой, чтобы каждый элемент массива c являлся суммой элементов с теми же индексами из массивов ‘a’ и ‘b’. Но в данном случае для решения задачи мы применим не последовательные вычисления, а параллельные, которые делаются при помощи GPU.
Мы запустим n потоков/ядер. Индекс, под которым работает каждый конкретный поток, можно вывести из следующей формулы:
index = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
В случае с двумерной матрицей в индексе присутствует два компонента, означающих строку и столбец, которые можно вывести следующим образом:
row = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
col = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
Также нам придется определить количество потоков на блок, скажем, tpb и блоков на грид, допустим bpg. Воспользуемся стандартными числами для них.
Здесь важно отметить еще одну важную концепцию: когда какие-либо вычисления должны быть выполнены на GPU, соответствующие данные должны быть перенесены в глобальную память GPU, а результаты вычислений после этого могут быть перенесены обратно на хост. Эти операции выполняются при помощи функций cuda.to_device() и copy_to_host() , предоставляемых в библиотеке Numba на Python.
Ниже приведены реализации данного решения как для CPU, так и для GPU. Посмотрите оба листинга, чтобы уловить разницу.
c = np.zeroes(n) for i in range(n): c[i] = a[i] + b[i] return c
Последовательная реализация для CPU.
from numba import cuda # Библиотека Nvidia для работы с GPU import numpy as np @cuda.jit('void(float32[:], float32[:], float32[:])') #Динамический компилятор Cuda def cuda_addition(a,b,c): """Поток будет выполнять эту функцию ядра.""" i = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x # Отображение потока на индекс массива if i > c.size: return c[i] = a[i]+b[i] #Perform the addition # Подробности об устройстве device = cuda.get_current_device() # Перенос с хоста на устройство d_a = cuda.to_device(a) # Перенос данных в глобальную память GPU d_b = cuda.to_device(b) # Перенос данных в глобальную память GPU d_c = cuda.device_array_like(a) tpb = device.WARP_SIZE #blocksize или количество потоков на блок, стандартное значение = 32 bpg = int(np.ceil((n)/tpb)) # блоков на грид cuda_addition[bpg, tpb](d_a, d_b, d_c) # вызов ядра # Перенос вывода с устройства на хост c = d_c.copy_to_host() print(c)
Параллельная реализация для GPU
6. Нахождение 3 наиболее похожих позиций для каждой позиции в списке при помощи GPU
Хорошенько вникнув в теорию и практику, возвращаемся к исходно поставленной задаче: найти топ-3 похожих позиции для каждой позиции в списке при помощи вычислений на GPU.
В данном случае основная идея заключается в том, что у нас n позиций, и мы запустим n потоков. Каждый поток будет работать параллельно с остальными и независимо от них, вычисляя по 3 наиболее похожие позиции для каждой позиции в списке. За каждую позицию будет отвечать один поток.
from numba import cuda # Nvidia's GPU Library import numpy as np import math #Выполнение математических вычислений, например, нахождение скалярного произведения, Numpy не поддерживается на GPU import random # Определение функции ядра @cuda.jit('void(float32[. ],float32[:],int32[:],float32[:],int32[:],float32[:],int32[:])') # CUDA Just-in-time Compiler def cuda_dist(X,first_best_val,first_best_index,second_best_val,second_best_index,third_best_val,third_best_index): """Поток будет выполнять эту функцию ядра.""" # Отображение потока на строку row = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x; if ((row >= n)): # Принимать потоки только в границах матриц return first_best_val[row] = SMALL # косинусное сходство с первой по схожести позицией first_best_index[row] = -1 # индекс первой по схожести позиции будет храниться здесь second_best_val[row] = SMALL # косинусное сходство со второй по схожести позицией second_best_index[row] = -1 # индекс второй по схожести позиции будет храниться здесь third_best_val[row] = SMALL # косинусное сходство с третьей по схожести позицией third_best_index[row] = -1 # индекс третьей по схожести позиции будет храниться здесь for i in range(n): if(i==row): # Тот же элемент continue # Переменные для вычисления скалярного произведения, так как мы не можем использовать операции numpy в контексте GPU tmp = 0.0 magnitude1 = 0.0 magnitude2 = 0.0 for j in range(k): tmp += X[row,j] * X[i,j] magnitude1 += (X[row,j]* X[row,j]) magnitude2 += (X[i,j]* X[i,j]) tmp /= (math.sqrt(magnitude1)*math.sqrt(magnitude2)) # Значение скалярного произведения Dot_product(a,b) = a.b/(|a|*|b|) if(tmp>=first_best_val[row]): third_best_val[row] = second_best_val[row] third_best_index[row] = second_best_index[row] second_best_val[row] = first_best_val[row] second_best_index[row] = first_best_index[row] first_best_val[row] = tmp first_best_index[row] = i elif(tmp>=second_best_val[row]): third_best_val[row] = second_best_val[row] third_best_index[row] = second_best_index[row] second_best_val[row] = tmp second_best_index[row] = i elif(tmp>third_best_val[row]): third_best_val[row] = tmp third_best_index[row] = i # Подробности об устройстве device = cuda.get_current_device() d_x = cuda.to_device(X) d_first_val = cuda.device_array_like(first_val) d_first_index = cuda.device_array_like(first_index) d_second_val = cuda.device_array_like(second_val) d_second_index = cuda.device_array_like(second_index) d_third_val = cuda.device_array_like(third_val) d_third_index = cuda.device_array_like(third_index) tpb = device.WARP_SIZE # blocksize или количество потоков на блок bpg = int(np.ceil((n)/tpb)) # блоков на грид %time cuda_dist[bpg,tpb](d_x,d_first_val,d_first_index,d_second_val,d_second_index,d_third_val,d_third_index) # вызов ядра # Перенос вывода с устройства на хост first_val = d_first_val.copy_to_host() print (first_val[:10]) # Первые 10 значений # Перенос вывода с устройства на хост first_index = d_first_index.copy_to_host() print (first_index[:10]) # Первые 10 индексов
Реализация для GPU ~ Код для нахождения 3 позиций, наиболее похожих на данную
Затраченное время
Общее время, затраченное GPU для нахождения топ-3 схожих позиций для каждой позиции в списке, составило 481 мс (0,5 секунд). Еще 20 секунд потребовалось для копирования данных с устройства на хост и с хоста на устройство.
7. Вывод
Задача, решение которой на CPU потребовало бы около 2 дней, на GPU была решена за 20,5 секунд. Это оказалось возможно только благодаря природе задачи. Поиск 3 наиболее похожих позиций для ‘A’ не зависит от поиска 3 наиболее похожих позиций для ‘B’. Мы воспользовались этим фактом и применили параллелизм, предоставляемый GPU, для ускорения процесса. Также пример иллюстрирует, задачи какого типа удобнее всего решать при помощи могучего GPU.
8. Благодарности
Данное исследование было выполнено на MLP (платформе машинного обучения) компании Walmart Labs. Спасибо Аюшу Кумару за помощь в оптимизации рабочего процесса.
Программирование GPU при помощи Python и CUDA

Книга предлагает быстрое погружение в программирование GPU. Вы узнаете, как применять закон Амдала, использовать профилировщик для определения узких мест в коде на Python, настроить окружения для программирования GPU. По мере чтения вы будете запускать свой код на GPU и писать полноценные ядра и функции на CUDA С, научитесь отлаживать код при помощи NSight IDE и получите представление об известных библиотеках от NVIDIA, в частности cuFFT и cuBLAS. Вооружившись этими знаниями, вы сможете написать с нуля глубокую нейронную сеть, использующую GPU, и изучить более основательные тем. Книга предназначена для разработчиков и специалистов по обработке данных, которые хотят познакомиться с основами эффективного программирования GPU для улучшения быстродействия, используя программирование на Python. Желательно общее знакомство с базовыми понятиями математики и физики, а также опыт программирования на Python и любом основанном на С языке программирования.
- Полная коллекция по информатике и вычислительной технике
- Аналитика данных
- ДМК Пресс. Информатика
- Программирование и алгоритмизация
- Программирование на Python
- ВО — Магистратура
- 09.04.01: Информатика и вычислительная техника
- 09.04.02: Информационные системы и технологии
- 09.04.03: Прикладная информатика
- 09.04.04: Программная инженерия
Тоуманен, Б. Программирование GPU при помощи Python и CUDA : практическое пособие / Б. Тоуманен ; пер. с англ. А. В. Борескова. — Москва : ДМК Пресс, 2020. — 254 с. — ISBN 978-5-97060-821-0. — Текст : электронный. — URL: https://znanium.com/catalog/product/1210649 (дата обращения: 20.11.2023). – Режим доступа: по подписке.
Скопировать запись
Фрагмент текстового слоя документа размещен для индексирующих роботов. Для полноценной работы с документом, пожалуйста, перейдите в ридер.
д-р Бриан Тоуманен Программирование GPU при помощи Python и CUDA
BIRMINGHAM – MUMBAI Dr. Brian Tuomanen Hands-On GPU Programming with Python and CUDA Explore high-performance parallel computing with CUDA
д-р Бриан Тоуманен Москва, 2020 Программирование GPU при помощи Python и CUDA Исследуйте высокопроизводительные параллельные вычисления с помощью CUDA
УДК 004.438Python ББК 32.973.22 Т50 Тоуманен Б. Т50 Программирование GPU при помощи Python и CUDA / пер. с анг. А. В. Борескова. – М.: ДМК Пресс, 2020. – 254 с.: ил. ISBN 978-5-97060-821-0 Книга предлагает быстрое погружение в программирование GPU. Вы узнаете, как применять закон Амдала, использовать профилировщик для определения узких мест в коде на Python, настроить окружения для программирования GPU. По мере чтения вы будете запускать свой код на GPU и писать полноценные ядра и функции на CUDA C, научитесь отлаживать код при помощи NSight IDE и получите представление об известных библиотеках от NVIDIA, в частности cuFFT и cuBLAS. Вооружившись этими знаниями, вы сможете написать с нуля глубокую нейронную сеть, использующую GPU, и изучить более основательные темы. Книга предназначена для разработчиков и специалистов по обработке данных, которые хотят познакомиться с основами эффективного программирования GPU для улучшения быстродействия, используя программирование на Python. Желательно общее знакомство с базовыми понятиями математики и физики, а также опыт программирования на Python и любом основанном на С языке программирования. УДК 004.438Python ББК 32.973.22 Authorized Russian translation of the English edition of Hands-On GPU Programming with Python and CUDA ISBN 9781789136678 © 2018 Packt Publishing. This translation is published and sold by permission of Packt Publishing, which owns or controls all rights to publish and sell the same. Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. ISBN 978-1-78899-391-3 (анг.) © 2018 Packt Publishing ISBN 978-5-97060-821-0 (рус.) © Оформление, издание, перевод, ДМК Пресс, 2020
Содержание Об авторе . 10 О рецензенте . 11 Предисловие . 12 Глава 1. Почему программирование GPU? . 18 Технические требования . 19 Параллелизация и закон Амдала . 19 Использование закона Амдала . 21 Множество Мандельброта . 22 Профилировка вашего кода . 25 Использование модуля cProfile . 25 Резюме . 26 Вопросы . 27 Глава 2. Настройка окружения для программирования GPU . 28 Технические требования . 29 Убедитесь, что у вас есть требуемое оборудование . 29 Проверка вашего оборудования (Linux) . 30 Проверка вашего оборудования (Windows) . 31 Установка драйверов для GPU . 33 Установка драйверов GPU (Linux) . 33 Установка драйвера GPU (Windows) . 35 Установка окружения для программирования на С++ . 35 Настройка GCC, Eclipse IDE и графических зависимостей (Linux) . 35 Установка Visual Studio (Windows) . 36 Установка CUDA Toolkit . 38 Установка окружения Python для программирования GPU . 39 Установка PyCUDA (Linux) . 40 Создание скрипта для настройки окружения (Windows). 40 Установка PyCUDA (Windows) . 41 Проверка PyCUDA . 42 Резюме . 42 Вопросы . 43
Содержание Глава 3. Начало работы с PyCUDA . 44 Технические требования . 44 Опрос вашего GPU . 45 Опрос вашего GPU при помощи PyCUDA . 46 Использование класса gpuarray модуля PyCUDA . 49 Перенос данных в и из GPU при помощи gpuarray . 49 Использование основных поэлементных операций через методы gpuarray . 50 Использование ElementWiseKernel из PyCUDA для выполнения поэлементных операций. 55 Возвращаемся к множеству Мандельброта . 58 Краткая вылазка в функциональное программирование . 61 Основа параллельного сканирования и редуцирования . 63 Резюме . 64 Вопросы . 65 Глава 4. Ядра, нити, блоки и сетки . 66 Технические требования . 67 Ядра . 67 Функция SourceModule из PyCUDA . 67 Нити, блоки и сетки . 70 Игра «Жизнь» Джона Конвея . 70 Синхронизация и взаимодействие нитей . 77 Использование функции устройства __syncthreads() . 77 Использование разделяемой памяти . 80 Алгоритм параллельной префиксной суммы . 82 Алгоритм наивный параллельной префиксной суммы. 82 Исключающая префиксная сумма и включающая префиксная сумма . 85 Эффективный алгоритм параллельной префиксной суммы . 85 Эффективный алгоритм параллельной префиксной суммы (реализация) . 87 Резюме . 89 Вопросы . 90 Глава 5. Потоки, события, контексты и одновременность. 91 Технические требования . 92 Синхронизация устройства CUDA . 92 Использование класса stream из PyCUDA . 93 Параллельная игра «Жизнь» Конвея при помощи потоков CUDA . 97 События . 100 События и потоки . 102 Контексты . 103 Синхронизация в текущем контексте . 104
Содержание 7 Создание контекста . 105 Многопроцессность и многонитиевость на стороне хоста . 106 Различные контексты для параллельности на стороне хоста . 107 Резюме . 110 Вопросы . 111 Глава 6. Отладка и профилирование вашего кода на CUDA . 112 Технические требования . 113 Использование printf внутри ядер CUDA . 113 Использование printf для отладки . 115 Заполняем пробелы в CUDA C . 119 Использование NSight IDE для разработки и отладки кода на CUDA C . 124 Использование NSight c Visual Studio IDE под Windows . 125 Использование NSight с Eclipse под Linux . 128 Использование NSight для понимания варпа в CUDA . 131 Использование профайлера nvprof и Visual Profiler . 134 Резюме . 136 Вопросы . 136 Глава 7. Использование библиотек CUDA вместе со Scikit-CUDA . 137 Технические требования . 138 Установка Scikit-CUDA . 139 Базовая линейная алгебра при помощи cuBLAS . 139 Функции 1-го уровня AXPY в cuBLAS . 139 Другие функции cuBLAS 1-го уровня . 141 GEMV 2-го уровня в cuBLAS . 142 Функции 3-го уровня GEMM в cuBLAS для измерения производительности GPU . 144 Быстрое преобразование Фурье при помощи cuFFT . 147 Простое одномерное FFT . 148 Использование FFT для свертки . 149 Использование cuFFT для двумерной свертки . 150 Использование cuSolver из Scikit-CUDA . 155 Сингулярное разложение (SVD) . 155 Использование SVD для анализа методом главных компонент (PCA) . 156 Резюме . 158 Вопросы . 158 Глава 8. Библиотеки функций для GPU CUDA и Thrust . 159 Технические требования . 160 Библиотека функций GPU cuRAND . 160
Содержание Оценка π при помощи метода Монте-Карло . 161 CUDA Math API . 165 Краткий обзор определенных интегралов . 165 Вычисление определенного интеграла при помощи метода Монте-Карло . 166 Пишем тесты . 172 Библиотека CUDA Thrust . 174 Использование функторов в Thrust . 176 Резюме . 178 Вопросы . 178 Глава 9. Реализация глубокой нейросети . 180 Технические требования . 181 Искусственные нейроны и нейросети . 181 Реализация плотного слоя искусственных нейронов . 182 Реализация слоя мягкого максимума . 187 Реализация функции потери перекрестной энтропии . 189 Реализация последовательной сети . 189 Реализация методов вывода . 191 Градиентный спуск . 193 Подготовка и нормализация данных . 197 Данные Iris . 197 Резюме . 200 Вопросы . 200 Глава 10. Работа с компилированным кодом для GPU . 201 Запуск откомпилированного кода при помощи Ctypes . 202 Снова возвращаемся к вычислению множества Мандельброта . 202 Компиляция и запуск PTX-кода . 208 Написание «оберток» для CUDA Driver API . 209 Использование CUDA Driver API . 213 Резюме . 216 Вопросы . 217 Глава 11. Оптимизация быстродействия в CUDA . 218 Динамический параллелизм . 219 Быстрая сортировка при помощи динамического параллелизма . 220 Векторные типы данных и доступ к памяти. 222 Потокобезопасные атомарные операции . 224 Перестановки в пределах варпа . 225 Вставка PTX-ассемблера прямо в код . 228
Содержание 9 Оптимизированная по быстродействию версия суммирования элементов массива . 232 Резюме . 235 Вопросы . 235 Глава 12. Куда идти далее? . 237 Расширение знаний о CUDA и программировании GPGPU . 238 Системы из нескольких GPU . 238 Кластерные вычисления и MPI . 238 OpenCL PyOpenCL . 239 Графика . 239 OpenGL . 240 DirectX12 . 240 Vulkan . 240 Машинное обучение и компьютерное зрение . 241 Основы . 241 cuDNN . 241 Tensorflow и Keras . 242 Chainer . 242 OpenCV . 242 Технология блокчейн . 242 Резюме . 243 Вопросы . 243 Ответы на вопросы . 244 Предметный указатель . 250
Об авторе Доктор Бриан Тоуманен работал с CUDA и программированием GPU с 2014 г. Он получил степень бакалавра по специальности «Электроинженерия» в университете Вашингтона в Сиэтле, затем некоторое время являлся разработчиком программного обеспечения, после чего продолжил обучение в области математики. В университете Миссури (Колумбия) Бриан защитил кандидатскую диссертацию по математике, где впервые столкнулся с программированием GPU для решения научных задач. Доктор Тоуманен был приглашен в исследовательскую лабораторию министерства армии США по программированию GPU для выяснения вопросов общего назначения и не так давно руководил интеграцией GPU и разработкой стартапа в Мэриленде. Сейчас он работает в качестве специалиста по машинному обучению (Azure CSI) в компании Microsoft в Сиэтле. Я бы хотел поблагодарить профессора Мишеллу Беччи из отдела NCSU ECE и ее студента Эндрю Тодда за помощь, оказанную мне как начинающему программисту GPU в 2014 г. Также хочу выразить особую признательность моему редактору в Packt Акшаде Иер за содействие в написании данного труда и, наконец, профессору Андреасу Клекнеру за составление великолепной библиотеки PyCUDA, которую я активно использовал в своей книге.
О рецензенте Вандане Шах была присуждена степень бакалавра по специальности «Электроэнергетика и электротехника» в 2001 г. Она также приобрела навыки MBA «Управление персоналом» и магистерскую степень по электронике со специализацией в области разработки VLSI. Также ею была представлена работа на получение кандидатской степени по специальности «Электроника», в частности в области обработки изображений и глубокого обучения для диагностики опухоли мозга. Областями интересов Ванданы являются обработка изображений, а также встраиваемые системы. Имеет более 13 лет опыта в исследованиях, а также в обучении и подготовке студентов по дисциплине «Электроника и связь». Ею было опубликовано множество работ в уважающих себя журналах, таких как IEEE, Springer и Interscience. Она также получила государственный грант для проведения исследований в области обработки изображений в MRI. Кроме того что Вандана прекрасно разбирается в вопросах техники, она неплохо владеет искусством индийского танца катхак. Я благодарю членов моей семьи за их поддержку во всем.
Предисловие Приветствую вас и поздравляю! Эта книга является вводным курсом по программированию GPU с использованием Python и CUDA. GPU обычно обозначает Graphics Programming Unit (Графический процессор), но здесь пойдет речь не о программировании графики – она является введением в программирование общего вида на GPU или программирование GPGPU (General Purpose GPU). За последнее десятилетие стало очевидным, что GPU хорошо подходят для вычислений не только графических изображений, но параллельно с этим для тех, которые одновременно требуют высокой пропускной способности. Для этого NVIDIA выпустила CUDA Toolkit, благодаря чему область программирования GPGPU стала более доступной практически для любого человека, хоть немного знакомого с некоторыми знаниями языка программирования. Целью книги «Программирование GPU при помощи Python и CUDA» является огромное желание как можно быстрее ввести читателя в мир технологий GPGPU. Я старался подать материал так, чтобы в каждой главе можно было встретить не только интересные примеры и упражнения, но и прочесть о некоторых веселых случаях. В частности, вы можете набирать приведенные упражнения и выполнять их в вашей любимой среде Python (могут подойти Spyder, Jupiter и PyCharm). Таким образом, вы с легкостью запомните все необходимые функции и команды и одновременно получите некоторый опыт, как при помощи интуиции могут быть написаны программы для GPGPU. Поначалу параллельное программирование для GPGPU кажется довольно сложным и несколько обескураживающим, особенно если ранее вы соприкасались только с программированием CPU. Вам придется выучить так много новых понятий и соглашений, что иногда будет казаться, словно вы видите это в первый раз. В такие моменты лучше не отчаиваться, а верить, что все приложенные усилия по освоению данной области не напрасны. Обещаю, что при некоторой любознательности и соблюдении дисциплины столь загадочная область, по мере того как вы дойдете до конца книги, станет как бы вашей второй сущностью. Для кого эта книга Эта книга предназначена прежде всего для одного особенного человека – меня в 2014 г., когда я пытался написать программу для моделирования при помощи GPU для своей кандидатской диссертации по математике. Мне приходилось часами просиживать над многочисленными книгами и руководствами по программированию GPU, пытаясь отыскать хоть малейший смысл во всем этом;
О чем рассказывает эта книга? 13 большинство книг, казалось, представляло собой сплошной парад аппаратных схем и непонятных слов на каждой странице, в то время как само программирование оставалось где-то на заднем плане. Также моя книга адресована тем, кто на самом деле мечтает по-серьезному заняться программированием GPU, но без влезания в многочисленные технические детали и схемы аппаратуры. Мы будем программировать GPU на С/С++ (CUDA C), но при этом использовать Python при помощи модуля PyCUDA. PyCUDA позволяет писать лишь действительно необходимый низкоуровневый код для GPU, причем данный модуль призван самостоятельно отвечать за всю необходимую компиляцию, линковку и запуск кода на GPU для нас. о чем рассказывает эта книга? Глава 1 «Почему программирование GPU?» объясняет, для чего нам необходимо разбираться в данных вопросах и как правильно применить закон Амдала для оценки потенциального выигрыша в быстродействии от перевода последовательной программы на GPU. Глава 2 «Настройка окружения для программирования GPU» объясняет, как настроить соответствующее окружение для Python и C++ для использования CUDA под Windows и Linux. Глава 3 «Начало работы с PyCUDA» описывает технические навыки, которые вам понадобятся для программирования GPU при помощи Python. В частности, мы увидим, как копировать данные в и из GPU при помощи класса gpuarray и компилировать простейшие ядра при помощи функции PyCUDA ElementwiseKernel. Глава 4 «Ядра, нити, блоки и сетки» обучит основам написания эффективных ядер CUDA, которые являются параллельными функциями, выполняемыми на GPU. Мы увидим, как писать выполняемые на GPU функции («последовательные» функции, вызываемые непосредственно ядрами CUDA), и познакомимся со структурой сетка/блок CUDA и ее ролью в запуске ядер. Глава 5 «Потоки, события, контексты и одновременность» покрывает такие понятия, как «потоки CUDA», которые позволяют одновременно запускать и синхронизировать на GPU множество ядер. Мы увидим, как использовать события CUDA для замера времени выполнения ядер и как создавать и использовать контексты CUDA. Глава 6 «Отладка и профилирование вашего кода на CUDA» заполнит некоторые пробелы в области чистого CUDA C программирования и покажет, как использовать NVIDIA NSight IDE для отладки и разработки, а также как использовать средства профилирования от NVIDIA. Глава 7 «Использование библиотек CUDA вместе со ScikitCUDA» дает краткий обзор некоторых важных библиотек CUDA при помощи модуля Scikit-CUDA, включая cuBLAS, cuFFT и cuSOLVER.
Предисловие Глава 8 «Библиотеки функций для GPU CUDA и Thrust» покажет, как использовать cuRAND и функции математического API CUDA в вашем коде, а также как использовать контейнеры CUDA Thrust в коде на С++. Глава 9 «Реализация глубокой нейросети» служит основой, на которой мы построим с самого начала глубокую нейросеть, применяя многие из идей, которые разбирались в книге. Глава 10 «Работа с компилированным кодом для GPU» покажет, как связывать наш код на Python с заранее откомпилированным кодом для GPU при помощи PyCUDA и Ctypes. Глава 11 «Оптимизация быстродействия в CUDA» научит ряду низкоуровневых приемов для CUDA, таких как перестановка в пределах варпа (warp shuffling), векторизованный доступ к памяти, использование PTX и атомарные операции. Глава 12 «Куда идти далее?» является обзором некоторых направлений, которые помогут вам развить ваши навыки программирования GPU. как получить максимум от этой книги Это уже техническая тема. Будут сделаны лишь некоторые предположения относительно уровня программирования читателя. Мы будем считать, что: у вас средний уровень программирования на Python; вы знакомы со стандартными научными пакетами для Python, такими как NumPy, SciPy и Matplotlib; вы обладаете средним уровнем в любом С-подобном языке (С, С++, Java, Rust, Go и т. п.); вы понимаете динамическое выделение памяти в С (в частности, как использовать функции malloc и free). Программирование GPU применимо в различных научных областях, где чаще всего требуются знания математики, поэтому при приведении множества (если не большинства) примеров будут использоваться ее основы. Я предполагаю, что читатель знаком с программой первого или второго курса высшей математики, включая: тригонометрию (такие функции, как sin, cos, tg…); вычисления (интегралы, производные, градиенты); статистику (равномерное и нормальное распределение); линейную алгебру (векторы, матрицы, векторные пространства, размерность). Не беспокойтесь, если вы не выучили некоторые из этих тем или проходили их очень давно, по ходу книги мы будем рассматривать основные понятия программирования и математические понятия. Также хочется сделать еще одно предположение. Если вы помните, в начале книги я говорил, что мы будем работать только с CUDA, которая является про
Используемые соглашения 15 приетарным языком программирования для оборудования NVIDIA. Так что для начала вам необходимо наличие соответствующего оборудования. Поэтому я считаю, что у читателя есть доступ к: 64-битовому компьютеру на базе процессора от Intel/AMD; не менее 4 Гб оперативной памяти; GPU NVIDIA GTX 1050 или более усовершенствованная. Читателю также следует знать, что многие прежние версии GPU, скорее всего, будут пригодны для большинства, если не для всех, примеров, приведенных в книге, но тем не менее все они были проверены на GTX 1050 под Windows 10 и GTX 1070 под Linux. Конкретные инструкции по настройке и конфигурации приводятся в главе 2 «Настройка окружения для программирования GPU». используемые соглашения На протяжении всей книги будут использоваться следующие соглашения. CodeInText обозначает текст программы, имена таблиц баз данных, имена каталогов, файлов, расширения файлов, пути, URL, ввод пользователя и т. п. Например: «Теперь мы можем использовать функцию cublasSaxpy». Блок кода выглядит следующим образом: cublas.cublasDestroy(handle) print 'cuBLAS returned the correct value: %s' % np.allclose(np.dot(A,x), y_gpu.get()) Когда мне хочется привлечь ваше внимание к определенному участку кода, я соответствующие строки выделяю жирным шрифтом: def compute_gflops(precision='S'): if precision=='S': float_type = 'float32' elif precision=='D': float_type = 'float64' else: return -1 Любые команды показываются следующим образом: $ run cublas_gemm_flops.py Полужирное выделение обозначает новое понятие, важное слово или слова, которые вы видите на экране. Например, слова в меню или диалоговых окнах показываются таким образом. Предупреждения или важные замечания показываются таким образом. Подсказки или приемы показываются таким образом.
Практикум под руководством инструктора
Основы ускоренных вычислений с CUDA Python
На этом семинаре вы научитесь использовать основные инструменты и методы работы приложений с ускорением на GPU на Python при помощи CUDA ® и компилятора Numba на GPU. Вы проработаете десятки практических упражнений по кодированию, а в конце обучения создадите новую задачу по ускорению полностью функциональной программы, изначально разработанной для CPU, для еще большего прироста производительности. По окончании практикума у вас появятся дополнительные материалы, которые помогут вам самостоятельно создавать новые приложения с ускорением на GPU.
Цели обучения
На этом практикуме вам предстоит:
- Создать универсальные функции NumPy (ufuncs) с ускорением на GPU и несколькими строками кода
- Настроить распараллеливание кода с использованием иерархии потоков CUDA
- Создать пользовательские ядра CUDA для максимальной производительности и гибкости
- Использовать объединение запросов и общую память для увеличения пропускной способности ядер CUDA
- Создавать случайные числа на GPU
- Узнать о методах управления промежуточной памятью GPU
Краткое описание практикума
- Знакомство с инструктором
- Создание аккаунта на courses.nvidia.com/join
- Начните работать с компилятором Numba и программированием CUDA на Python
- Используйте декораторы Numba для ускорения на GPU числовых функций Python
- Оптимизируйте передачу памяти от хоста к устройству и от устройства к хосту
- Изучите иерархию параллельных потоков CUDA и как расширить возможности параллельной программы.
- Запустите массивно-параллельные пользовательские ядра CUDA на GPU
- Используйте атомные операции CUDA, чтобы избежать ошибку состояния гонки во время параллельного выполнения
- Используйте генерацию случайных чисел (RNG) xoroshiro128+ для поддержки методов Монте-Карло с ускорением на GPU
- Научитесь создавать многомерные сетки и работать параллельно на 2D-матрицах
- Используйте общую память на устройстве для объединения запросов при перестройке 2D-матриц
- Еще раз ознакомьтесь с основными выводами и ответьте на вопросы
- Завершите тестирование и получите сертификат
- Пройдите опрос
Информация о практикуме
Продолжительность: 8 часов
Необходимые требования:
- Базовые знания Python, в том числе типов переменных, циклов, условных операторов, функций и операций с массивами
- Знание NumPy, в том числе применения ndarrays и ufuncs
- Знание программирования CUDA необязательно.
Технологии: Numba, NumPy
Сертификат: После успешного завершения тестирования участники получат сертификат NVIDIA DLI для подтверждения своей квалификации и дальнейшего карьерного роста.
Аппаратные требования: ПК или ноутбук с поддержкой последней версии Chrome или Firefox. Каждому участнику будет предоставлен доступ к полностью настроенному облачному серверу с ускорением на GPU.
