Как использовать cuda в python
GPU-Accelerated Computing with Python
NVIDIA’s CUDA Python provides a driver and runtime API for existing toolkits and libraries to simplify GPU-based accelerated processing. Python is one of the most popular programming languages for science, engineering, data analytics, and deep learning applications. However, as an interpreted language, it’s been considered too slow for high-performance computing.
Set Up CUDA Python
To run CUDA Python, you’ll need the CUDA Toolkit installed on a system with CUDA-capable GPUs. Use this guide to install CUDA. If you don’t have a CUDA-capable GPU, you can access one of the thousands of GPUs available from cloud service providers, including Amazon AWS, Microsoft Azure, and IBM SoftLayer. The NVIDIA-maintained CUDA Amazon Machine Image (AMI) on AWS, for example, comes pre-installed with CUDA and is available for use today.
To get started with Numba, the first step is to download and install the Anaconda Python distribution that includes many popular packages (Numpy, SciPy, Matplotlib, iPython, etc.) and “conda,” a powerful package manager. Once you have Anaconda installed, install the required CUDA packages by typing conda install numba cudatoolkit pyculib.
Getting Started Blogs
Numba: High-Performance Python with CUDA Acceleration
Numba provides Python developers with an easy entry into GPU-accelerated computing and a path for using increasingly sophisticated CUDA code with a minimum of new syntax and jargon.
Numba Tutorial for CUDA
Check out the Numba tutorial for CUDA on the ContinuumIO github repository.
Seven Things You Might Not Know About Numba
Numba runs inside the standard Python interpreter, so you can write CUDA kernels directly in Python syntax and execute them on the GPU. Dive deeper into several aspects of using Numba on the GPU that are often overlooked.
GPU-Accelerated Graph Analytics in Python with Numba
Numba provides Python developers with an easy entry into GPU-accelerated computing and a path for using increasingly sophisticated CUDA code with a minimum of new syntax and jargon.
NVIDIA CUDA(сиквел) — Настройка PyCUDA
import pycuda.autoinit
import pycuda.driver as drv
import numpy
mod = drv.SourceModule(«»»
__global__ void multiply_them(float *dest, float *a, float *b)
<
const int i = threadIdx.x;
dest[i] = a[i] * b[i];
>
«»»)
a = numpy.random.randn(400).astype(numpy.float32)
b = numpy.random.randn(400).astype(numpy.float32)
dest = numpy.zeros_like(a)
multiply_them(
drv.Out(dest), drv.In(a), drv.In(b),
block=(400,1,1))
Не правда ли, удобно и просто?
Начнем!
Для нормальной работы PyCUDA нам потребуются:
— Машина с UNIX-подобной системой и доступом к интернету.
— Nvidia CUDA toolkit. PyCuda была разработана с оглядкой на версию 2.0 beta, однако у меня она вроде как нормально работает и с 2.1 beta.
— C++ компилятор, желательно gcc версии не ниже 4.х.
— Установленный интерпретатор Python, версия 2.4 или новее.
Теперь установим последнюю версию библиотеки Boost C++. Перед установкой стоит проверить, установлен ли девелоперский пакет для Python, он будет называться python-dev или python-devel. Установка фактически стандартная, только не надо забывать задать префикс — каталог для собранных инклюдов. В данном случае пусть будет $HOME/pool.
После сборки (должна пройти успешно) проверяем, появилась ли папка $HOME/pool. Если да — то все путем, идем дальше 🙂
Важное примечание: Если в конце установки появилось что-то в духе
. failed updating 30 targets.
. skipped 2 targets.
то еще раз перепроверьте, установлены ли у вас девелоперские заголовки питона. Если не удалось обновить всего лишь несколько файлов (менее 5), то, скорее всего, все будет работать нормально, но для верности можно поставить libz-dev и libbz2-dev и собрать Boost заново.
Теперь добавим путь к только что откомпилированным библиотекам в переменную окружения:
$ export LD_LIBRARY_PATH=$HOME/pool/lib:$
Если этого не сделать, то на этапе сборки PyCUDA появится что-то вроде:
. blablabla.
ImportError: libboost_python-gcc42-mt-1_35.so.1.35.0:
cannot open shared object file: No such file or directory
. blablabla.
Нужно установить пакет numpy. Сделать это можно, либо скачав его самому с numpy.org и собрать, либо с помощью самой PyCUDA. Поскольку мы уже перешли в каталог с дистрибутивом, просто набираем:
$ sudo python ez_setup.py //установка setup-tools
$ sudo easy_install numpy //установка numpy с помощью setup-tools
Собираем PyCUDA.
Ну а в последнем пункте, как несложно догадаться, нужно заменить /where/ever/you/installed/cuda на каталог с CUDA, то есть по умолчанию /usr/local/cuda.
Кстати, перед вводом make install можно еще раз перепроверить введенные адреса. Они все сохраняются в файле siteconf.py, который создается при конфигурировании. У меня он перед сборкой выглядел вот так:
BOOST_INC_DIR = [‘/home/username/pool/include/boost-1_37’]
BOOST_LIB_DIR = [‘/home/username/pool/lib’]
BOOST_PYTHON_LIBNAME = [‘boost_python-gcc41-mt’]
CUDA_ROOT = ‘/usr/local/cuda’
CUDADRV_LIB_DIR = []
CUDADRV_LIBNAME = [‘cuda’]
CXXFLAGS = []
LDFLAGS = []
Если вы хотите переконфигурировать PyCUDA с новыми параметрами — то сначала удалите этот файл. Либо можно просто внести в него изменения и набрать
$ sudo python setup.py build
Теперь можно смело компилировать:
$ sudo make install
Если все сделано правильно — то ошибок при сборке быть не должно. Перезагружаем компьютер, и для полной уверенности можно пойти в каталог
/pycuda-0.91.1/test(или какая у вас там версия) и запустить test_driver.py.
$ cd /pycuda-0.91.1/test
$ python test_driver.py
Если в результате появилось ОК — то все в шоколаде. Если одна-две ошибки (у меня почему-то иногда случались глюки с памятью) — просто попробуйте чуть позже еще раз.
Спасибо, что дочитали до конца, буду рад выслушать критику 🙂
Ну и кроме всего прочего, ни интерпретатор, ни сам Eclipse не должны быть запущены из-под рута. Ибо переменные окружения другие 🙂 Спасибо за помощь в настройке товарищу Riz.
Глубокое обучение и нейронные сети с Python и Pytorch. Часть VII: запускаем обучение на GPU
Данная статья предполагает, что у вас есть доступ к GPU либо локально, либо через облако.
Если вы используете сервер, вам нужно будет загрузить данные, извлечь, а также установить jupyter notebook :
Далее вы можете запустить его следующим образом:
После этого вы увидите что-то вроде этого:
Затем вам нужно будет перейти по указанному выше URL-адресу, заменив 127.0.0.1 на IP-адрес вашего сервера.
Наш код на настоящий момент имеет следующий вид:
Результат:
Мы пошли несколько дальше и сделали быструю функцию для обработки процесса обучения, в основном для того, чтобы не запускать заново тренировочную часть нашего кода. Вместо этого мы хотим поговорить про работу на GPU.
Для начала вам понадобится версия Pytorch для GPU. А чтобы использовать Pytorch на графическом процессоре, вам понадобится графический процессор NVIDIA с поддержкой технологии CUDA.
Если его нет, то потребуются облачные провайдеры, например Linode. Прим. переводчика — отлично подойдут Google Colaboratory или Kaggle. Они абсолютно бесплатны.
Для работы на локальном компьютере вам нужно загрузить CUDA toolkit.
После этого необходимо загрузить и распаковать CuDNN, переместив содержимое CuDNN в каталог Cuda Toolkit. Когда вы распакуете архив CuDNN, у вас будет 3 каталога внутри каталога cuda. Вам просто нужно переместить содержимое этих каталогов (bin, include и lib) в одноименные директории, находящиеся в каталоге Cuda Toolkit.
Сделав это, вам надо будет убедиться что у вас установлена версия Pytorch для GPU. Ее можно найти на стартовой странице фреймворка Pytorch. Также надо сказать, что если вы используете пакет Anaconda, то в нем уже предустановлены все возможные версии Pytorch.
Убедиться, что CUDA установлена, можно следующим образом:
Результат:
Теперь мы можем подумать о том, что именно хотим делать с графическим процессором. Как минимум нам нужно, чтобы на нем выполнялось обучение нашей модели.
Это означает, что все наши данные также должны обрабатываться на графическом процессоре.
Для начала давайте разместим на GPU нашу нейронную сеть. Для этого мы можем просто установить следующий флаг:
Результат:
Однако зачастую мы хотим написать код, которым будут пользоваться разные люди, в том числе и те, у кого может отсутствовать возможность использовать графический процессор. Решить эту проблему можно следующим образом:
Результат:
Большинство базовых нейронных сетей не особо выигрывают от использования нескольких графических процессоров, но в дальнейшем вы можете все же захотеть использовать несколько GPU для своей задачи. Узнать, сколько вам доступно процессоров, можно следующим образом:
Результат:
Таким образом мы можем экстраполировать номера индексов и назначить конкретным графическим процессорам определенные слои нашей сети.
На данный момент мы пишем код, которому в действительности нужен только один графический (или обычный) процессор, поэтому мы будем использовать только одно устройство. И теперь, выяснив, какое устройство лучше всего использовать, мы можем приступить к его настройке. Сделать это очень просто, достаточно лишь выполнить следующий код:
Результат:
Выше мы уже определили нашу сеть, но обычно вы просто сразу ее определяете и отправляете на устройство, например следующим образом:
Теперь мы можем перейти к обучению, но на этот раз мы поместим наши батчи в GPU. В этом примере мы реально можем за один раз поместить все наши данные в GPU, так как у нас не очень большой объем данных. И это, конечно, сэкономит нам некоторое время, затрачиваемое на передачу данных из VRAM в GPU и обратно. Но обычно подобное невозможно, поэтому мы покажем вам, как это происходит в нормальных условиях.
Результат:
Как можно заметить, сейчас это работает намного быстрее. Теперь мы можем проверить нашу модель на тестовой выборке (это можно сделать как на CPU, так и на GPU). Поскольку эта выборка совсем небольшая, сделаем это тоже на GPU:
Результат:
А вот тоже самое с использованием батчей работает быстрее:
Результат:
Итак, по сравнению с тем, что было, мы добились значительного прогресса.
Мы научились создавать и обучать нейронные сети и уже получили приличный результат.
Но можем ли мы добиться большего, чем точность в 70%? Должны ли мы увеличивать количество эпох? И если да, то когда нам все же остановиться?
Что, если у нас есть несколько моделей, и чтобы их сравнить, нужно довести их до совершенства? Мы же не знаем, когда это произойдет!
В следующей статье мы рассмотрим некоторые базовые методы анализа и визуализации результатов. А также разберем несколько концепций, которые необходимо учитывать при анализе эффективности работы модели.
Сравнение производительности GPU-расчетов на Python и C
Введение
По роду деятельности я частенько занимаюсь задачами численного моделирования. Во многих случаях мы используем технологию CUDA для ускорения расчетов за счет использования возможностей параллельных вычислений на GPU, при этом программы пишутся на языке C. В то же время, в некоторых случаях хотелось бы иметь возможность реализовывать расчеты на питоне, потому что это удобно, быстро, гибко, лаконично, молодежно. При этом, однако, очень важно не терять в скорости выполнения программ, поскольку некоторые расчеты могут занимать от нескольких часов до суток. В статье продемонстрировано использование python-библиотек Numba и PyCUDA для реализации параллельных расчетов на GPU и приведены результаты сравнения их производительности на тестовой задаче.
Тестовая задача
Выбор тестовой задачи и условий тестирования был обусловлен характером реальных задач, для решения которых в дальнейшем планируется использование python. При этом была выбрана максимально простая задача, а именно задача решения двумерного уравнения теплопроводности с помощью явной конечно-разностной схемы. Задача рассматривалась на квадратной области с заданными значениями температуры на границах. Количество узлов расчетной сетки по x и y одинаково и равно n. На рисунке в начале статьи показано установившееся решение тестовой задачи.
Алгоритм и условия тестирования
Алгоритм решения задачи можно представить следующим псевдокодом:
Во всех тестах представленных ниже число узлов квадратной сетки в каждом направлении (n) варьировалось от 512 до 4096, а nstp = 5000.
Программное и аппаратное обеспечение
Тестирование проводилось на персональном компьютере:
Intel® Core(TM)2 Quad CPU Q9650 @ 3.00GHz, 8 Gb ОЗУ
GPU: Nvidia GTX 580
Операционная система: Ubuntu 16.04 LTS с установленной CUDA 7.5
Реализация на C
Все дальнейшие python-реализации сравнивались с результатами, полученными с помощью программы на C, описанной в этом разделе.
Расчеты показывают, что для N = 512 время выполнения C-программы распараллеленной на GPU составляет 0.27 секунд против 33.06 секунд для последовательной реализации алгоритма на CPU. То есть ускорение CPU/GPU составляет около 120 раз. С ростом N величина ускорения не убывает.
Python with Numba
Библиотека Numba предоставляет возможность jit (just-in-time) компиляции кода на питоне в байт-код сравнимый по производительности с C или Fortran кодом. Numba поддерживает компиляцию и запуск python-кода не только на CPU, но и на GPU, при этом стиль и вид программы, использующей библиотеку Numba, остается чисто питоновским.
Отметим тут несколько приятных особенностей. Во-первых, эта реализация намного короче и нагляднее. Здесь мы использовали двумерные массивы, что делает код намного более удобочитаемым. Во-вторых, если в C-реализации от нас требовалось передать все константы (например N) путем исполнения функций вроде cudaMemcpyToSymbol(dN, &N, sizeof(int)); то здесь мы просто пользуемся глобальными переменными как в обычной python-функции. Ну и наконец, реализация не требует никаких знаний языка C и архитектуры GPU.
Этот код легко переписать и для случая использования одномерных массивов размера n*n, как будет показано далее это существенно влияет на скорость выполнения.
PyCUDA
Второй из протестированных python-библиотек была библиотека PyCUDA. В отличии от Numba здесь от разработчика потребуется написать код ядра на C, поэтому без знания этого языка не обойтись. С другой стороны кроме собственно ядра на C ничего писать не надо.
Выглядит это все как чистый питон за исключением локальной вставки C-кода.
Сравнение производительности
На Рисунке 1 и в Таблице 1 приведены зависимости времени выполнения тестовой программы (в секундах) от размера сетки n, полученные при запуске C-кода (кривая CUDA C) и python-реализаций с библиотекой Numba и двумерными массивами (Numba 2DArr), с библиотекой Numba и одномерными массивами (Numba 1DArr), с библиотекой PyCUDA (кривая PyCUDA).
Рисунок 1
n | Cuda C | Numba 2DArr | Numba 1DArr | PyCUDA |
---|---|---|---|---|
512 | 0.25 | 0.8 | 0.66 | 0.216 |
1024 | 0.77 | 3.26 | 1.03 | 0.808 |
2048 | 2.73 | 12.23 | 4.07 | 2.87 |
3073 | 6.1 | 27.3 | 9.12 | 6.6 |
4096 | 11.05 | 55.88 | 16.27 | 12.02 |
На Рисунке 2 приведены отношения времени выполнения различных python-реализаций к времени выполнения C-кода. Как видно из рисунков, самой медленной, из рассмотренных, является реализация с помощью библиотеки Numba с использованием двумерных массивов. При этом, этот подход является самым наглядным и простым. Интересно, что развертка двумерных массивов в одномерные приводит к примерно троекратному ускорению кода. Самым быстрым решением оказалось использование библиотеки PyCUDA. В то же время, как отмечалось выше, использование этой библиотеки несколько более трудоемко, поскольку требует написания ядра на C. Однако затраты окупаются и скорость выполнения такой python-программы всего на 5-8% меньше чем программы, написанной полностью на C.
Выводы
Чудес не бывает и самые простые и наглядные решения оказываются одновременно и самыми медленными. В то же время, имеющиеся библиотеки позволяют добиться скорости выполнения python-программ, сравнимой со скоростью выполнения чистого C-кода. Существующие библиотеки дают разработчику выбор между более и менее высокоуровневыми решениями. Этот выбор, однако, всегда есть компромисс между скоростью разработки и скоростью выполнения программы.
Как 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 – атрибуты на каждую позицию. Потребовалось бы получить скалярное произведение данной позиции с каждой из остальных позиций в списке.
Код для нахождения трех позиций, максимально подобных заданной
Теперь, при нахождении топ-3 схожих для всех позиций в списке, сложность умножается еще на n. Окончательная сложность оказывается равна O(n*n*k) = O(n²k).
Код для нахождения трех наиболее схожих позиций для каждой позиции в списке
Тестовый прогон и оценка времени
Я запустил код, попробовав найти 3 наиболее похожие позиции из подмножества, содержащего n = 10³ позиций с k = 64. На выполнение этой задачи при помощи Python потребовалось около 17 секунд со средней скоростью 3.7 *10⁶ операций в секунду. Код был хорошо оптимизирован с применением операций и массивов Numpy. Отмечу, что все эти операции последовательно выполняются на CPU.
Прогон для 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 часа
О, Господи! Так долго.
Вероятно, вы уже догадываетесь, что на самом деле мне удалось все сделать очень быстро, воспользовавшись силой 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 потоков (в таком случае заполняются все звенья этого конвейера). Это выполнение ядра с заданным индексом. Каждый поток использует свой индекс для доступа элементов в массиве таким образом, что вся совокупность имеющихся потоков совместно обрабатывает все множество данных.
Грид: Это группа блоков. Между блоками отсутствует всякая синхронизация.
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. Воспользуемся стандартными числами для них.
Ниже приведены реализации данного решения как для CPU, так и для GPU. Посмотрите оба листинга, чтобы уловить разницу.
Последовательная реализация для CPU.
Параллельная реализация для GPU
6. Нахождение 3 наиболее похожих позиций для каждой позиции в списке при помощи GPU
Хорошенько вникнув в теорию и практику, возвращаемся к исходно поставленной задаче: найти топ-3 похожих позиции для каждой позиции в списке при помощи вычислений на GPU.
В данном случае основная идея заключается в том, что у нас n позиций, и мы запустим n потоков. Каждый поток будет работать параллельно с остальными и независимо от них, вычисляя по 3 наиболее похожие позиции для каждой позиции в списке. За каждую позицию будет отвечать один поток.
Реализация для GPU
Код для нахождения 3 позиций, наиболее похожих на данную
Общее время, затраченное GPU для нахождения топ-3 схожих позиций для каждой позиции в списке, составило 481 мс (0,5 секунд). Еще 20 секунд потребовалось для копирования данных с устройства на хост и с хоста на устройство.
7. Вывод
Задача, решение которой на CPU потребовало бы около 2 дней, на GPU была решена за 20,5 секунд. Это оказалось возможно только благодаря природе задачи. Поиск 3 наиболее похожих позиций для ‘A’ не зависит от поиска 3 наиболее похожих позиций для ‘B’. Мы воспользовались этим фактом и применили параллелизм, предоставляемый GPU, для ускорения процесса. Также пример иллюстрирует, задачи какого типа удобнее всего решать при помощи могучего GPU.