Первые впечатления от графических процессоров и PyData

Возможности и проблемы интеграции графических процессоров в традиционные рабочие нагрузки обработки данных

Автор: Мэтью Роклин

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

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

Производительность графического процессора

Как и многие разработчики PyData, я плохо осознаю, что графические процессоры иногда бывают быстрыми, но не имею дела с ними достаточно часто, чтобы иметь о них сильное впечатление.

Чтобы получить более интуитивное представление о различиях в производительности, я вошел в систему с графическим процессором, открыл CuPy (библиотека графического процессора, подобная Numpy, разработанная в основном Chainer в Японии) и cuDF (подобная пандам библиотека, разрабатываемая в NVIDIA) и провела пару небольших сравнений скорости:

Сравните Numpy и CuPy

>>> import numpy, cupy

>>> x = numpy.random.random((10000, 10000))
>>> y = cupy.random.random((10000, 10000))

>>> %timeit bool((numpy.sin(x) ** 2 + numpy.cos(x) ** 2 == 1).all())
446 ms ± 53.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit bool((cupy.sin(y) ** 2 + cupy.cos(y) ** 2 == 1).all())
86.3 ms ± 50.7 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

В этом примитивном примере вычисления на GPU выполняются в пять раз быстрее.

Сравните pandas и cuDF

>>> import pandas as pd, numpy as np, cudf

>>> pdf = pd.DataFrame({'x': np.random.random(10000000),
                        'y': np.random.randint(0, 10000000, size=10000000)})
>>> gdf = cudf.DataFrame.from_pandas(pdf)

>>> %timeit pdf.x.mean()  # 30x faster
50.2 ms ± 970 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit gdf.x.mean()
1.42 ms ± 5.84 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

>>> %timeit pdf.groupby('y').mean()  # 40x faster
1.15 s ± 46.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit gdf.groupby('y').mean()
54 ms ± 182 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit pdf.merge(pdf, on='y')  # 30x faster
10.3 s ± 38.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit gdf.merge(gdf, on='y')
280 ms ± 856 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

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

Это было сделано наивно на одном графическом процессоре на машине DGX. Примеры фреймов данных были тщательно отобраны, чтобы найти поддерживаемые операции (см. Проблемы фреймов данных ниже).

Анализ

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

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

Препятствия

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

  • Не у всех есть GPU. Они могут быть большими и дорогими
  • Установка библиотек с поддержкой CUDA может быть сложной задачей даже с conda
  • Текущие библиотеки с поддержкой CUDA еще не образуют целостную экосистему с установленными соглашениями.

Многим библиотекам RAPIDS нужна конкретная помощь:

  • cuDF незрелый и требует множества простых улучшений API и функций.
  • Вычислительным библиотекам массивов нужны протоколы для обмена данными и функциональными возможностями.
  • Библиотеки глубокого обучения обладают функциональными возможностями, но не могут легко делиться ими.
  • Развертывание Dask в системах с несколькими графическими процессорами можно улучшить
  • Python нужен лучший доступ к высокопроизводительным коммуникационным библиотекам

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

Не у всех есть GPU

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

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

Две недели назад я посетил Джо Хаммана, научного сотрудника NCAR и UW eScience Institute, и он сказал: «О да, у нас работает кластер GPU, который я никогда не использую». Примерно через 20 минут у него был установлен стек графического процессора, и он проводил эксперимент, очень похожий на то, что мы делали выше.

Установка библиотек с поддержкой CUDA усложняется драйверами.

До пакетов conda, wheels, Anaconda и conda forge установка программного стека PyData (Numpy, pandas, scikit-learn, Matplotlib) была сложной задачей. Это было связано с тем, что пользователи должны были сопоставить комбинацию системных библиотек, стеков компилятора и пакетов Python. «О, ты на Mac? Сначала заварите установку X, затем убедитесь, что у вас gfortran, затем pip install scipy »

Экосистема решила эту проблему, объединив весь стек под единым зонтиком conda, где всем можно было бы управлять последовательно, или, в качестве альтернативы, это было значительно уменьшено с помощью пип-колес.

К сожалению, драйверы CUDA должны управляться на стороне системы, поэтому мы возвращаемся к сопоставлению системных библиотек с библиотеками Python, в зависимости от того, какую версию CUDA вы используете.

Вот как пример инструкции по установке PyTorch:

  • CUDA 8.0: conda install pytorch torchvision cuda80 -c pytorch
  • CUDA 9.2: conda install pytorch torchvision -c pytorch
  • CUDA 10.0: conda install pytorch torchvision cuda100 -c pytorch
  • Без CUDA: conda install pytorch-cpu torchvision-cpu -c pytorch

Кроме того, эти соглашения отличаются от соглашений, используемых Anaconda в упаковке TensorFlow и NVIDIA в упаковке RAPIDS. Из-за этого несоответствия в соглашениях маловероятно, что начинающий пользователь получит работающую систему, если не проведет какое-то исследование заранее. PyData выживает, ухаживая за неопытными пользователями компьютеров (они часто являются экспертами в какой-то другой области), так что это проблема.

В Conda проводится некоторая работа, которая может помочь в этом в будущем. Тем не менее, нам нужно будет разработать более совершенные общие соглашения между различными группами разработчиков Python GPU.

Стандартной инфраструктуры сборки сообщества не существует

После разговора об этом с Джоном Киркхэмом (сопровождающим Conda Forge) он предположил, что ситуация немного похожа на экосистему conda до conda-forge, где каждый создавал свои собственные пакеты, как им нравилось, и загружал их на anaconda.org без согласование общей среды сборки. Как известно многим научному сообществу, это несоответствие может привести к фрагментированному стеку, когда определенные семейства пакетов хорошо работают только с определенными пакетами в своем семействе.

Команды разработчиков разделены по компаниям.

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

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

RAPIDS и Dask требуют особого внимания

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

Обычно цель RAPIDS - построить стек науки о данных на основе обычных числовых вычислений, имитирующих стек PyData / SciPy. Похоже, они нацелены на такие библиотеки, как:

  • pandas, создав новую библиотеку cuDF
  • scikit-learn / традиционное неглубокое машинное обучение путем создания новой библиотеки cuML
  • Numpy за счет использования существующих библиотек, таких как CuPy, PyTorch, TensorFlow, и сосредоточения внимания на улучшении взаимодействия в экосистеме.

На основе стандартного набора научных приложений и приложений, ориентированных на данные, таких как визуализация, науки о Земле, ETL, финансы и т. Д.

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

cuDF не хватает функциональности pandas

fКогда я показал cuDF в верхней части этого поста, я выполнил следующие вычисления, которые выполнялись в 30–40 раз быстрее, чем pandas.

gdf.x.mean()
gdf.groupby('y').mean()
gdf.merge(gdf, on='y')

Что я не смог показать, так это то, что многие операции были ошибочными. Библиотека cuDF имеет большие перспективы, но все еще требует доработки для заполнения API pandas.

# There are many holes in the cuDF API
cudf.read_csv(...)      # works
cudf.read_parquet(...)  # fails if compression is present

df.x.mean()  # works
df.mean()    # fails

df.groupby('id').mean()     # works
df.groupby('id').x.mean()   # fails
df.x.groupby(df.id).mean()  # fails

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

Кроме того, есть области, в которых семантика cudf не соответствует семантике pandas. В целом это нормально (не всем нравится семантика pandas), но это усложняет задачу, поскольку мы пытаемся обернуть Dask Dataframe вокруг cuDF. Мы хотели бы расширить Dask Dataframe так, чтобы он мог принимать pandas- подобные фреймы данных, а затем получать фреймы данных вне ядра GPU на одном узле и распределенные фреймы данных GPU на мульти-GPU или мульти-GPU. node, и мы хотели бы увеличить cudf, чтобы он соответствовал этим ожиданиям.

Эта работа должна происходить как на низкоуровневом коде C ++ / CUDA, так и на уровне Python. Я чувствую, что у NVIDIA есть масса людей, доступных на уровне CUDA, но только несколько (очень хороших) людей на уровне Python, которые работают, чтобы не отставать (приходите на помощь!).

Массивные вычисления надежны, но фрагментированы

Опыт работы с Numpy стал намного более гладким, в основном из-за ажиотажа вокруг глубокого обучения в последние несколько лет. Многие крупные технологические компании создали свои собственные фреймворки глубокого обучения, каждая из которых содержит частичный клон Numpy API. К ним относятся такие библиотеки, как TensorFlow, PyTorch, Chainer / CuPy и другие.

Это замечательно, потому что эти библиотеки предоставляют высококачественную функциональность на выбор, но также болезненно, потому что экосистема сильно фрагментирована. Данные, выделенные с помощью TensorFlow, нельзя вычислить с помощью операций Numba или CuPy.

Мы можем помочь залечить этот разрыв с помощью нескольких технических приемов:

  • Стандарт для передачи низкоуровневой информации о массивах графических процессоров между платформами. Это будет включать информацию о массиве, таком как указатель памяти устройства, тип данных, форма, шаги и т. Д., Аналогично тому, что есть в протоколе буферов Python сегодня. Это позволило бы людям выделить массив в одной структуре, но затем использовать вычислительные операции, определенные в другой структуре. Команда Numba создала прототип чего-то подобного несколько месяцев назад, и команда CuPy выглядела достаточно довольной этим. См. Купи / купи №1144. Я считаю, что это тоже было принято в PyTorch.
@property
def __cuda_array_interface__(self):
    desc = {
        'shape': self.shape,
        'typestr': self.dtype.str,
        'descr': self.dtype.descr,
        'data': (self.data.mem.ptr, False),
        'version': 0,
    }
    if not self._c_contiguous:
        desc['strides'] = self._strides
     return desc
  • Стандартный способ для разработчиков писать код массива, не зависящий от серверной части. В настоящее время мой любимый подход - использовать функции Numpy в качестве лингва-франка и позволить фреймворкам перехватывать эти функции и интерпретировать их по своему усмотрению.
  • Это было предложено и принято внутри самого Numpy в NEP-0018 и продвигалось такими людьми, как Стефан Хойер, Хамир Аббаси, Мартен ван Керквейк и Эрик Визер.
  • Это также полезно для других библиотек массивов, таких как pydata / sparse и dask array, и будет иметь большое значение для унификации операций с такими библиотеками, как XArray.

cuML нужны функции, Scikit-Learn нуждается в агностицизме структуры данных

В то время как глубокое обучение на графическом процессоре сегодня является обычным явлением, более традиционные алгоритмы, такие как GLM, случайные леса, предварительная обработка и т. Д., Не получили такой тщательной обработки.

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

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

# This code is aspirational

from sklearn.model_selection import RandomSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.feature_extraction.text import TfidfTransformer

# from sklearn.feature_extraction.text import HashingVectorizer
from cuml.feature_extraction.text import HashingVectorizer  # swap out for GPU versions

# from sklearn.linear_model import LogisticRegression, RandomForest
from cuml.linear_model import LogisticRegression, RandomForest

pipeline = make_pipeline([HashingVectorizer(),  # use Scikit-Learn infrastructure
                          TfidfTransformer(),
                          LogisticRegression()])

RandomSearchCV(pipeline).fit(data, labels)

Обратите внимание: приведенный выше пример является амбициозным (такого кода Cuml еще не существует) и, вероятно, наивным (я плохо знаю ML).

Однако, помимо простой задачи создания этих оценщиков с поддержкой графического процессора (что кажется обычным делом для разработчиков CUDA в NVIDIA), все еще существуют проблемы, связанные с чистой передачей массивов, отличных от Numpy, принуждением только при необходимости и т. Д., Что мы нужно будет потренироваться в Scikit-Learn.

К счастью, эта работа уже началась из-за Dask Array, у которого та же проблема. Сообщества Dask и Scikit-Learn в течение последнего года сотрудничали, чтобы улучшить возможность расширения. Надеюсь, этот дополнительный вариант использования будет продолжен в соответствии с существующими усилиями, но теперь с большей поддержкой.

Фреймворки глубокого обучения чрезмерно специализированы

Стек SciPy / PyData процветал, потому что он был модульным и адаптировался к новым ситуациям. Есть много мелких проблем, связанных с интеграцией компонентов фреймворков глубокого обучения в более общую экосистему.

Мы прошли аналогичный опыт с Dask на раннем этапе, когда экосистема Python не была готова к параллельным вычислениям. По мере расширения Dask мы столкнулись с множеством мелких проблем, связанных с параллельными вычислениями, которые ранее не решались, потому что в то время по большей части немногие использовали Python для параллелизма.

  • Различные библиотеки не выпускали GIL (спасибо за работу, pandas, scikit-image и другие!)
  • Различные библиотеки в некоторых случаях не были потокобезопасными (например, h5py и даже Scikit-Learn в одном случае)
  • Сериализация функций все еще требует доработки (спасибо cloudpickle разработчикам!)
  • Библиотеки сжатия не поддерживались (например, LZ4)
  • Сетевые библиотеки не использовались для рабочих нагрузок с высокой пропускной способностью (спасибо разработчикам Tornado!)

Эти проблемы были исправлены разработчиками Dask и широким сообществом (удивительно, что люди сделают, если вы предоставите хорошо обозначенную и хорошо описанную проблему на GitHub). Эти библиотеки были разработаны для использования с другими библиотеками, и поэтому они были хорошо заинтересованы в улучшении их удобства использования со стороны более широкой экосистемы.

Сегодня у фреймворков глубокого обучения есть те же проблемы. Они редко хорошо сериализуются, небезопасны для потоков, когда вызываются внешними потоками и т. Д. Этого и следовало ожидать, большинство людей, использующих такие инструменты, как TensorFlow или PyTorch, почти полностью работают в этих фреймворках. Эти проекты не подвергаются стрессу по сравнению с остальной частью экосистемы (никто не помещает массивы PyTorch в качестве столбцов в пандах и не обрабатывает их для отправки по сети). Использование инструментов, разработанных для узких рабочих процессов, и поощрение их к совместной работе общего назначения требует времени и усилий, как в техническом, так и в социальном плане.

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

Примечание: Chainer / CuPy здесь является заметным исключением. Библиотека Chainer (еще один фреймворк для глубокого обучения) явно отделяет свою библиотеку массивов CuPy, что упрощает работу. Это, в сочетании со строгим соблюдением Numpy API, вероятно, поэтому они стали первой целью для большинства текущих взаимодействий с Python OSS.

Dask нужны удобные скрипты для развертывания GPU

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

Dask имеет все возможности для решения этой проблемы для пользователей. Однако у большинства людей, использующих Dask и графические процессоры сегодня, есть сложный сценарий настройки, который включает комбинацию переменных среды, dask-worker вызовов, дополнительных вызовов утилит профилирования CUDA и так далее. Мы должны создать простой LocalGPUCluster объект Python, который люди могут легко вызывать в локальном скрипте, подобно тому, как они делают это сегодня для LocalCluster.

Кроме того, эта проблема относится к случаю с несколькими графическими процессорами и несколькими узлами и потребует от нас творческого подхода к существующим решениям распределенного развертывания (например, dask-kubernetes, dask-yarn и dask-jobqueue). . Конечно, добавление такой сложности без значительного влияния на корпус без GPU и увеличение затрат сообщества на обслуживание будет интересной задачей и потребует творческого подхода.

Python нужны высокопроизводительные коммуникационные библиотеки

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

В прошлом году Антуан работал над улучшением обработки Tornado соединений с высокой пропускной способностью, чтобы получить около 1 ГБ / с на процесс в сетях Infiniband от Python. Возможно, нам придется пойти намного дальше, как для Infiniband, так и для более экзотических сетевых решений. В частности, у NVIDIA есть системы, которые поддерживают эффективную передачу данных напрямую между устройствами GPU, минуя память хоста.

Здесь уже есть работа, которую мы можем использовать. Проект OpenUCX переносит экзотические сетевые решения (например, Infiniband) на единый API. Сейчас они работают над предоставлением доступного для Python API, который затем можно будет подключить к Dask. Это хорошо также для пользователей Dask-CPU, потому что соединения Infiniband станут более эффективными (пользователи HPC радуются), а также для общего сообщества Python HPC, которое, наконец, получит разумный API Python для высокопроизводительной сети. В настоящее время эта работа нацелена на цикл обработки событий Asyncio.

Кстати, я бы хотел, чтобы в будущем NVIDIA (и другие крупные технологические компании) выполнила именно такую ​​работу. Да, он помогает подключать пользователей Python к их конкретному оборудованию, но также помогает многим другим системам и одновременно обеспечивает общую инфраструктуру, применимую во всем сообществе.

Приходите на помощь!

Этот пост начался с обещания увеличения скорости в 5–40 раз (по крайней мере, для некоторых простых вычислений), а затем обрисовал в общих чертах многие из проблем, которые необходимо выполнить. Это серьезные проблемы, но в большинстве своем они являются простыми техническими проблемами и проблемами социальной инженерии. Есть много базовой работы с высокой потенциальной отдачей.

План NVIDIA создать стек для обработки данных, совместимый с GPU, кажется амбициозным, но они, похоже, серьезно относятся к проблеме и, похоже, готовы направить ресурсы и внимание компании на решение проблемы.

Если какая-либо из вышеперечисленных работ кажется вам интересной, пожалуйста, обратитесь в качестве…

  • Физическое лицо: либо как участник с открытым исходным кодом (RAPIDS имеет лицензию Apache 2.0 и, похоже, работает как обычный проект OSS на GitHub), либо как сотрудник (см. активные объявления о вакансиях). Здесь много интересной работы. Или как…
  • Учреждение. Возможно, у вас уже есть как недостаточно используемый кластер графических процессоров в вашем учреждении, так и большие группы специалистов по обработке данных, знакомых с Python, но не знакомых с CUDA. NVIDIA, похоже, стремится найти партнеров, которые заинтересованы во взаимных договоренностях по расширению функциональности для определенных областей. Пожалуйста, свяжитесь с нами, если это звучит знакомо.

Первоначально опубликовано на сайте matthewrocklin.com 17 декабря 2018 г.