Ускорьте свои алгоритмы, часть 1 - PyTorch

Ускорьте ваши модели PyTorch

Это первый пост из серии, которую я пишу. Все посты здесь:

  1. Ускорьте свои алгоритмы. Часть 1 - PyTorch
  2. Ускорьте свои алгоритмы, часть 2 - Numba
  3. Ускорьте свои алгоритмы. Часть 3 - Распараллеливание
  4. Ускорьте свои алгоритмы. Часть 4 - Dask

И это относится к блокнотам Jupyter, доступным здесь:

[Github-SpeedUpYourAlgorithms] и [Kaggle]

(Edit -28/11/18) - добавлен раздел torch.multiprocessing.

Показатель:

  1. Вступление
  2. Как проверить наличие cuda?
  3. Как получить дополнительную информацию об устройствах cuda?
  4. Как хранить тензоры и запускать модели на GPU?
  5. Как выбрать и работать с графическими процессорами, если у вас их несколько?
  6. Параллелизм данных
  7. Сравнение параллелизма данных
  8. torch.multiprocessing
  9. использованная литература
NOTE:
This post goes with Jupyter Notebook available in my Repo on Github:[SpeedUpYourAlgorithms-Pytorch]

1. Введение:

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

PyTorch - это библиотека машинного обучения, построенная на базе torch. Его поддерживает исследовательская группа Facebook в области искусственного интеллекта. После недавней разработки он приобрел большую популярность из-за своей простоты, динамических графиков и питонической природы. По скорости он по-прежнему не отстает, во многих случаях может даже превзойти.

pycuda позволяет получить доступ к API параллельных вычислений Nvidia CUDA из Python.

2. Как проверить наличие cuda?

Чтобы проверить, доступно ли cuda устройство с помощью Torch, вы можете просто запустить:

import torch
torch.cuda.is_available()
# True

3. Как получить дополнительную информацию о ваших устройствах cuda?

Чтобы получить основную информацию об устройствах, вы можете использовать torch.cuda. Но чтобы получить больше информации о своих устройствах, вы можете использовать pycuda, оболочку python для CUDA библиотеки. Вы можете использовать что-то вроде:

import torch
import pycuda.driver as cuda
cuda.init()
## Get Id of default device
torch.cuda.current_device()
# 0
cuda.Device(0).name() # '0' is the id of your GPU
# Tesla K80

Or,

torch.cuda.get_device_name(0) # Get name device with ID '0'
# 'Tesla K80'

Я написал простой класс для получения информации о ваших cudaсовместимых графических процессорах:

Чтобы получить текущее использование памяти, вы можете использовать функции pyTorch, такие как:

import torch
# Returns the current GPU memory usage by 
# tensors in bytes for a given device
torch.cuda.memory_allocated()
# Returns the current GPU memory managed by the
# caching allocator in bytes for a given device
torch.cuda.memory_cached()

А после того, как вы запустите приложение, вы можете очистить кеш с помощью простой команды:

# Releases all unoccupied cached memory currently held by
# the caching allocator so that those can be used in other
# GPU application and visible in nvidia-smi
torch.cuda.empty_cache()

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

Эти методы памяти доступны только для графических процессоров. И вот где они действительно нужны.

4. Как хранить тензоры и запускать модели на GPU?

.cuda Магия.

Если вы хотите сохранить что-то на процессоре, вы можете просто написать:

a = torch.DoubleTensor([1., 2.])

Этот вектор хранится в процессоре, и любая операция, которую вы выполняете с ним, будет выполняться на нем. Чтобы перенести его на gpu, вам просто нужно сделать .cuda:

a = torch.FloatTensor([1., 2.]).cuda()

Or,

a = torch.cuda.FloatTensor([1., 2.])

И это выберет для него устройство по умолчанию, которое можно увидеть с помощью команды:

torch.cuda.current_device()
# 0

Или вы также можете:

a.get_device()
# 0

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

sq = nn.Sequential(
         nn.Linear(20, 20),
         nn.ReLU(),
         nn.Linear(20, 4),
         nn.Softmax()
)

Чтобы отправить это на устройство с графическим процессором, просто выполните:

model = sq.cuda()

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

# From the discussions here: discuss.pytorch.org/t/how-to-check-if-model-is-on-cuda
next(model.parameters()).is_cuda
# True

5. Как выбрать и работать с графическими процессорами, если у вас их несколько?

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

Как уже было показано в части (2), мы можем получить все наши cuda совместимые устройства и их Id с помощью pycuda, мы не будем обсуждать это здесь.

Учитывая, что у вас есть 3 cuda совместимых устройства, вы можете инициализировать и назначить tensors конкретному устройству следующим образом:

cuda0 = torch.device('cuda:0')
cuda1 = torch.device('cuda:1')
cuda2 = torch.device('cuda:2')
# If you use 'cuda' only, Tensors/models will be sent to 
# the default(current) device. (default= 0)
x = torch.Tensor([1., 2.], device=cuda1)
# Or
x = torch.Tensor([1., 2.]).to(cuda1)
# Or
x = torch.Tensor([1., 2.]).cuda(cuda1)
# NOTE:
# If you want to change the default device, use:
torch.cuda.set_device(2) # where '2' is Id of device
# And if you want to use only 2 of the 3 GPU's, you
# will have to set the environment variable 
# CUDA_VISIBLE_DEVICES equal to say, "0,2" if you 
# only want to use first and third GPUs. Now if you 
# check how many GPUs you have, it will show two(0, 1).
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0,2"

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

x = torch.Tensor([1., 2.]).to(cuda2)
y = torch.Tensor([3., 4.]).to(cuda2)
# This Tensor will be saved on 'cuda2' only
z = x + y

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

Собственно есть еще одна проблема. В PyTorch все операции с графическим процессором по умолчанию асинхронны. И хотя это делает необходимую синхронизацию при копировании данных между CPU и GPU или между двумя GPU, тем не менее, если вы создаете свой собственный поток с помощью команды torch.cuda.Stream(), вам придется самостоятельно следить за синхронизацией инструкций.

Приводя пример из документации PyTorch, это неверно:

cuda = torch.device('cuda')
s = torch.cuda.Stream()  # Create a new stream.
A = torch.empty((100, 100), device=cuda).normal_(0.0, 1.0)
with torch.cuda.stream(s):
    # because sum() may start execution before normal_() finishes!
    B = torch.sum(A)

Если вы хотите использовать несколько графических процессоров в полной мере, вы можете:

  1. использовать все графические процессоры для разных задач / приложений,
  2. использовать каждый графический процессор для одной модели в ансамбле или стеке, каждый графический процессор имеет копию данных (если возможно), поскольку большая часть обработки выполняется во время подгонки к модели,
  3. используйте каждый графический процессор с нарезанным вводом и копией модели в каждом графическом процессоре. Каждый графический процессор будет вычислять результат отдельно и отправлять результаты на целевой графический процессор, где будут выполняться дальнейшие вычисления и т. Д.

6. Параллелизм данных?

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

В PyTorch параллелизм данных реализован с помощью torch.nn.DataParallel.

Но мы рассмотрим простой пример, чтобы увидеть, что происходит под капотом. И для этого нам придется использовать некоторые функции nn.parallel, а именно:

  1. Репликация: для репликации Module на нескольких устройствах.
  2. Scatter: распределить input в первом измерении между этими устройствами.
  3. Собрать: собрать и объединить input в первом измерении с этих устройств.
  4. parallel_apply: применить набор распределенных input, которые мы получили от Scatter, к соответствующему набору распределенных Module, которые мы получили от Replicate.
# Replicate module to devices in device_ids
replicas = nn.parallel.replicate(module, device_ids)
# Distribute input to devices in device_ids
inputs = nn.parallel.scatter(input, device_ids)
# Apply the models to corresponding inputs
outputs = nn.parallel.parallel_apply(replicas, inputs)
# Gather result from all devices to output_device
result = nn.parallel.gather(outputs, output_device)

Или просто:

model = nn.DataParallel(model, device_ids=device_ids)
result = model(input)

7. Сравнение параллельных данных

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

Его результаты:

[последнее обновление: (19 июня 2018 г.)] то есть его репозиторий на github. Запуск PyTorch 1.0, Tensorflow 2.0, а также новых графических процессоров мог бы изменить это…

Итак, как вы можете видеть, параллельная обработка определенно помогает, даже если она должна взаимодействовать с основным устройством в начале и в конце. И PyTorch дает результаты быстрее, чем все они, чем только Chainer, только в случае с несколькими GPU. Pytorch тоже упрощает работу - всего лишь один вызов DataParallel.

8. torch.multiprocessing

torch.multiprocessing - это оболочка для Python multiprocessingmodule, а его API на 100% совместим с исходным модулем. Таким образом, вы можете использовать Queue, Pipe, Array и т. Д., Которые находятся здесь в модуле многопроцессорности Python. Чтобы добавить к этому, чтобы сделать это быстрее, они добавили метод share_memory_(), который позволяет данным переходить в состояние, в котором любой процесс может использовать их напрямую, и поэтому передача этих данных в качестве аргумента различным процессам не будет копировать эти данные. .

Вы можете поделиться Tensors, модель parameters, и вы можете поделиться ими на CPU или GPU по своему усмотрению.

Warning from Pytorch: (Regarding sharing on GPU)
  CUDA API requires that the allocation exported to other processes remains valid as long as it’s used by them. You should be careful and ensure that CUDA tensors you shared don’t go out of scope as long as it’s necessary. This shouldn’t be a problem for sharing model parameters, but passing other kinds of data should be done with care. Note that this restriction doesn’t apply to shared CPU memory.

Вы можете использовать методы, описанные выше в разделе «Пул и процесс» здесь, а для увеличения скорости вы можете использовать метод share_memory_() для совместного использования Tensor (скажем) между всеми процессами без копирования.

# Training a model using multiple processes:
import torch.multiprocessing as mp
def train(model):
    for data, labels in data_loader:
        optimizer.zero_grad()
        loss_fn(model(data), labels).backward()
        optimizer.step()  # This will update the shared parameters
model = nn.Sequential(nn.Linear(n_in, n_h1),
                      nn.ReLU(),
                      nn.Linear(n_h1, n_out))
model.share_memory() # Required for 'fork' method to work
processes = []
for i in range(4): # No. of processes
    p = mp.Process(target=train, args=(model,))
    p.start()
    processes.append(p)
for p in processes: p.join()

Вы также можете работать с кластером машин. Подробнее см. Здесь.

9. Ссылки:

  1. Https://documen.tician.de/pycuda/
  2. Https://pytorch.org/docs/stable/notes/cuda.html
  3. Https://discuss.pytorch.org/t/how-to-check-if-model-is-on-cuda
  4. Https://pytorch.org/tutorials/beginner/blitz/data_parallel_tutorial.html
  5. Https://medium.com/@iliakarmanov/multi-gpu-rosetta-stone-d4fa96162986
Suggestions and reviews are welcome.
Thank you for reading!

Подпись: