Python + GPU = мощность, от 2 дней до 20 секунд

Все мы в той или иной форме слышали ажиотаж вокруг GPU в последнее время. Прежде чем идти дальше, я хочу уточнить, что я не эксперт по графическим процессорам. Я только начал свое путешествие в мир графических процессоров. Но в наши дни он такой мощный и мощный, что может выполнять чертовски много задач. Мне было поручено задание на работе, выполнение которого занимало несколько часов, но по-прежнему не показывало никакого прогресса. GPU пришел мне на помощь и решил мою проблему за секунды. Я смог выполнить задачу с расчетным временем в 2 дня всего за 20 секунд.

Подробно поставлю задачу в следующих разделах. Я также расскажу, как и когда использовать GPU для подобных задач. Итак, следите за обновлениями, и я обещаю, что это стоит того, чтобы его прочитать. Основной поток будет заключаться в понимании деталей задачи, начале работы с графическим процессором и использовании графического процессора для решения возникшей проблемы. Я буду использовать библиотеку Python Numba вместе с Nvidia Volta V100 16GB GPU.

Я также буду стараться изо всех сил, чтобы все оставалось простым и интуитивно понятным, но, пожалуйста, не стесняйтесь переходить к разделу ссылок и исследовать эту область подробнее. Основная идея этого сообщения в блоге - помочь аудитории понять мощь графического процессора и развить интуицию о том, как эффективно использовать его в своих повседневных рабочих задачах.

1. Подробная информация о задаче

В сфере розничной торговли поиск похожих или ближайших объектов - обычная задача. Мне предоставили список элементов, и каждый элемент был представлен k скрытыми атрибутами. Теперь моя задача заключалась в том, чтобы найти топ-3 одинаковых предмета для каждого предмета в списке. Метрикой подобия, выбранной для этой задачи, было косинусное подобие. Так выглядели мои данные

Сложность задачи

Мне предоставили список примерно из 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. Следует отметить, что все эти операции выполняются последовательно в процессоре.

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

Далее следует важная часть, оценивающая время для фактического списка примерно из 10⁵ пунктов. Если мы посчитаем, временная сложность снова увеличится в 100 раз, поскольку временная сложность алгоритма составляет O (n²k).

Расчетное время = 1700 * 100 секунд = 2834 минуты = 47,2 часа ~ 2 дня.

О Боже!! Так долго!!

Как вы правильно догадались, я смог сделать это очень быстро, используя мощность графического процессора. На самом деле, вы будете шокированы, узнав выигрыш во времени после использования GPU. Я оставлю цифры на потом и сначала познакомлю вас с миром GPU.

2. ЦП против графического процессора

Центральный процессор (ЦП) - это, по сути, мозг любого вычислительного устройства, выполняющий инструкции программы, выполняя операции управления, логические операции и операции ввода / вывода (I / O).

Однако современные процессоры имеют всего несколько ядер, и их основная конструкция и назначение - обработка сложных вычислений - практически не изменились. По сути, они в основном применимы для задач, требующих анализа или интерпретации сложной логики в коде.

С другой стороны, графический процессор (GPU) имеет меньшие размеры, но гораздо больше логических ядер (арифметические логические блоки или ALU, блоки управления и кэш памяти), основная конструкция которых заключается в обработке набора более простых и идентичных вычислений в параллельно.

3. Когда использовать вычисления на GPU

ЦП лучше подходит для решения сложных линейных задач. Несмотря на то, что ядра ЦП сильнее, графические процессоры могут справляться с задачами AI, ML и DL намного эффективнее и быстрее. GPU обрабатывает рабочую нагрузку путем обработки аналогичных параллельных операций.

Понимание операций с точки зрения графических процессоров - возьмите операцию поиска слова в документе. Это может быть выполнено последовательно, то есть повторением каждого слова или параллельным сканированием или строками и сканированием для конкретной работы.

Понимание операции с точки зрения ЦП. Конкретные операции, такие как вычисление Фибоначчи, нельзя проводить параллельно. Поскольку мы можем найти его только после двух предыдущих вычислений. Это лучше всего подходит для работы ЦП.

Точно так же операции, такие как сложение матриц, умножение, можно легко вычислить с помощью графического процессора, поскольку большинство этих операций в ячейках матрицы являются независимыми и похожими по своей природе, которые можно распараллелить.

4. Кратко о CUDA

CUDA - это платформа параллельных вычислений и модель интерфейса прикладного программирования (API), созданная Nvidia. Он позволяет разработчикам программного обеспечения и разработчикам программного обеспечения использовать графический процессор (GPU) с поддержкой CUDA для обработки общего назначения - подход, называемый GPGPU (универсальные вычисления на графических процессорах). "Узнать больше".

ЧИСЛО

Numba - это JIT-компилятор с открытым исходным кодом, который переводит подмножество Python и NumPy в быстрый машинный код с использованием LLVM через пакет Python llvmlite. Он предлагает ряд опций для распараллеливания кода Python для процессоров и графических процессоров, часто с незначительными изменениями кода. "Узнать больше".

Я использовал библиотеку Numba в своей работе вместе с Nvidia Volta V100 16GB GPU.

Детали архитектуры ~ Потоки, блоки и сетки

CUDA организует параллельные вычисления, используя абстракции потоков, блоков и сеток.

Поток: поток CUDA - это запланированная цепочка инструкций, выполняемых / проходящих в ядре CUDA (на самом деле это просто конвейер). На одном и том же ядре CUDA может быть до 32 потоков CUDA (для заполнения всех этапов этого конвейера). Это выполнение ядра с заданным индексом. Каждый поток использует свой индекс для доступа к элементам в массиве, так что совокупность всех потоков совместно обрабатывает весь набор данных.

Блок: это группа цепочек. Вы мало что можете сказать о выполнении потоков внутри блока - они могут выполняться одновременно или последовательно и в произвольном порядке. Вы можете несколько координировать потоки, используя функцию _syncthreads (), которая останавливает поток в определенной точке ядра, пока все другие потоки в его блоке не достигнут той же точки.

Сетка: это группа блоков. Никакой синхронизации между блоками нет.

Но где на самом деле выполняются потоки, блоки и сетки? Что касается чипа графического процессора Nvidia G80, похоже, вычисления распределяются следующим образом:

Сетка → GPU: вся сетка обрабатывается одним чипом GPU.

Блок → MP: Чип графического процессора организован как набор мультипроцессоров (MP), каждый из которых отвечает за обработку одного или нескольких блоков в сетке. Блок никогда не делится между несколькими депутатами.

Поток → SP: каждый MP дополнительно делится на несколько потоковых процессоров (SP), причем каждый SP обрабатывает один или несколько потоков в блоке.

Я скопировал кое-что из этого прекрасно написанного сообщения в блоге. Пожалуйста, узнайте больше.

5. Простая программа добавления массивов с использованием графического процессора в Python.

Как было сказано в начале, основная идея этого поста - помочь аудитории понять мощь GPU и развить интуицию относительно того, как использовать его в своих повседневных рабочих задачах. Чтобы начать программировать на GPU, может потребоваться небольшое исследование. На этом перейдем к нашей первой программе сложения массивов.

Учитывая два массива «a» и «b» размера «n», мы хотим сгенерировать массив «c», где каждый элемент c представляет собой сумму элементов с одинаковым индексом из «a» и «b». Разница здесь в том, что вместо последовательных вычислений мы будем выполнять параллельные вычисления с использованием графического процессора.

Запустим 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. Мы будем использовать для них стандартные числа .

Еще одна важная концепция, которую следует отметить, заключается в том, что всякий раз, когда какое-либо вычисление должно быть выполнено в графическом процессоре, данные должны быть перенесены в глобальную память графического процессора, а результаты после вычисления могут быть переданы обратно на хост. Это стало возможным благодаря функциям cuda.to_device () и copy_to_host (), предоставляемым библиотекой Numba на Python.

Ниже приведен код для реализации проблемы как CPU, так и GPU, просканируйте оба кода, чтобы увидеть разницу.

6. Поиск топ-3 похожих элементов для каждого элемента в списке с помощью графического процессора.

Обладая достаточным объемом теории и практики, мы возвращаемся к нашей исходной задаче поиска топ-3 похожих элементов для каждого элемента в списке с использованием вычислений на графическом процессоре.

Основная идея здесь в том, что у нас есть n элементов, и мы будем запускать n потоков. Каждый поток будет работать независимо и параллельно, чтобы вычислить первые 3 элемента для каждого элемента в списке. Один поток будет отвечать за один элемент.

Затраченное время

Общее время, необходимое графическому процессору для поиска топ-3 похожих элементов для каждого элемента в списке, составляет 481 мс (0,5 секунды). Дополнительные 20 секунд потребовались для копирования данных с устройства на хост и с хоста на устройство.

7. Заключение

Расчетное время CPU в 2 дня было уменьшено до 20,5 секунд с использованием GPU. Это было возможно только из-за характера задачи. Поиск 3-х элементов, похожих на элемент «A», не зависит от поиска 3-х элементов, аналогичных элементу «B». Мы воспользовались этим фактом и использовали параллелизм, обеспечиваемый графическим процессором, для ускорения процесса. Мы также поняли, в каких задачах мы можем использовать мощный графический процессор.

Написание хорошего поста, ясного и понятного для аудитории, требует больших усилий. Я буду продолжать пытаться оправдать свою работу. Подпишитесь на меня в Medium и посмотрите мои предыдущие сообщения. Приветствую отзывы и конструктивную критику. Полный код задания можно получить здесь.

8. Благодарность

Все эти усилия были выполнены через MLP (платформу машинного обучения) от Walmart Labs с точки зрения используемой инфраструктуры и графического процессора. Спасибо Аюш Кумар за помощь в оптимизации рабочего процесса.

Мой канал Youtube для получения дополнительной информации:



9. Ссылки

  1. Https://en.wikipedia.org/wiki/Time_complexity
  2. Https://en.wikipedia.org/wiki/Graphics_processing_unit
  3. Https://blogs.nvidia.com/blog/2009/12/16/whats-the-difference-between-a-cpu-and-a-gpu/
  4. Https://www.datascience.com/blog/cpu-gpu-machine-learning
  5. Https://qr.ae/TWIuic
  6. Https://numba.pydata.org/numba-doc/latest/index.html
  7. Https://en.wikipedia.org/wiki/CUDA
  8. Https://www.nvidia.in/object/cuda-parallel-computing-in.html
  9. Https://llpanorama.wordpress.com/2008/06/11/threads-and-blocks-and-grids-oh-my/
  10. Https://qr.ae/TWIwEW