Процесс обучения

Модели можно обучать следующими способами:

  • Напрямую из Jupyter Server на выделенных GPU.

  • Посредством Training Job API, отправляя задачи на кластер.

У каждого из этих способов есть свои особенности. Они отражены в таблице ниже.

Особенность

Обучение из Jupyter Server на выделенных GPU

Обучение посредством отправки задач на кластер

Количество GPU

При обучении из Jupyter Server максимальное количество выделенных GPU — 16.

При отправке задачи обучения на кластер можно задействовать 1000+ GPU (подробнее см. в разделе Обучение моделей на большом количестве GPU).

Особенности работы

Не требуется знакомство с библиотеками Horovod/dask/blink/PyTorch.distributed, методами библиотеки client_lib, не требуется реализация распределенных вычислений в коде.

Возможно исполнение каждой ячейки Jupyter Notebook по отдельности.

Требуется реализация распределенных вычислений при помощи библиотек Horovod/dask/blink/PyTorch.distributed.

Вывод логов обучения осуществляется единовременно для всего скрипта обучения.

Тарификация

При обучении из Jupyter Server на выделенных GPU взимается оплата с момента создания Jupyter Server до удаления, даже если он не используется.

При отправке задачи обучения на кластер пользователь платит за фактическое время исполнения задачи: от старта до окончания обучения.

До начала обучения моделей любым из указанных способов необходимо:

  1. Подключиться к Jupyter Server (см. Создание нового Jupyter Server).

  2. Переложить данные из объектного хранилища S3 в локальное хранилище NFS на кластере Christofari (см. Перемещение данных с S3 на NFS в разделе Управление данными).

Последующие шаги для каждого из способов представлены в таблице ниже.

Обучение из Jupyter Server с GPU

Обучение путем отправки задач на кластер

  1. Сборка кастомного образа с использованием pip install (опционально).

  1. Сборка кастомного образа с использованием класса ImageBuildJob библиотеки client_lib (опционально).

  1. Запуск обучения.

  1. Запуск задачи обучения на кластере.

Рассмотрим процесс обучения более подробно.

Подготовительные действия до начала обучения

  1. Подключение к Jupyter Server.

    Этот шаг описан в разделах Создание нового Jupyter Server и Подключение к существующему Jupyter Server.

  2. Выбор инструмента для работы.

    Можно обучать модели из Jupyter Notebook или JupyterLab. JupyterLab существенно обогащает возможности классического Jupyter Notebook, поскольку поддерживает работу одновременно с несколькими ноутбуками, текстовыми файлами, датасетами, терминалами и другими компонентами и позволяет упорядочить документы в рабочей области.

  3. Перекладка данных на NFS.

    Подробная информация о перемещении данных в NFS приведена в разделе Управление данными сервиса Data catalog.

Обучение из Jupyter Server с GPU

Сборка кастомного образа с необходимыми библиотеками

В каждом базовом образе есть предустановленный набор библиотек (см. Библиотеки в базовых образах). Если для работы необходимо установить дополнительные (или обновить существующие) питоновские модули, выполните блок:

pip install --user <package_name> == <version>

Где package_name — наименование модуля, который предполагается установить, а version — версия данного модуля. Подробная информация об использовании pip приведена в документации данного менеджера пакетов.

Внимание

Библиотеки устанавливаются в директорию home/jovyan/.local на NFS и будут доступны пользователю из вновь создаваемых Jupyter Server. Отметим одну важную деталь: при запуске другого Jupyter Server устанавливаемые пользователем библиотеки также попадут в директорию /.local. Таким образом, при наличии нескольких Jupyter Server в директории /.local могут оказаться разные версии библиотек, которые конфликтуют между собой. Способ обойти эту проблему изложен в разделе Вопросы и ответы про Environments.

Запуск обучения модели

Пример обучения модели из Jupyter Server c GPU приведен ниже.

mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
def create_model():
  return tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
                        ])
config = tf.ConfigProto( device_count = {'GPU': 2} )
sess = tf.Session(config=config)
keras.backend.set_session(sess)

model = create_model()
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

model.fit(x=x_train,
          y=y_train,
          epochs=5,
          validation_data=(x_test, y_test),
          callbacks=[tensorboard_callback])

Другие примеры обучения моделей приведены на Github.

Обучение путем отправки задач на кластер

Сборка кастомного образа с необходимыми библиотеками

Для сборки кастомного образа необходимо использовать средства библиотеки client_lib, класс ImageBuildJob. Параметры конструктора класса описаны в соответствующем разделе. Ниже представлен пример, как дополнить базовый образ registry.aicloud.sbcp.ru/base/horovod-cuda10.0-tf1.15.0 нужными зависимостями. Обратите внимание на то, что путь к файлу с зависимостями указывается полностью.

job = client_lib.ImageBuildJob(
                     from_image='registry.aicloud.sbcp.ru/base/horovod-cuda10.0-tf1.15.0', # базовый образ
                     requirements_file='/home/jovyan/quick-start/job_launch/requirements.txt' # файл с зависимостями для кастомного образа
)

Для запуска сборки кастомного образа выполните

job.submit()
job.new_image # идентификатор кастомного образа

Для просмотра логов сборки образа в интерактивном режиме выполните

job.logs()

Запуск задачи обучения на кластере

Внимание

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

Для запуска задачи необходимо использовать средства библиотеки client_lib, класс client_lib.Job. Параметры конструктора класса описаны в соответствующем разделе.

Пример 1. Задача обучения модели на кластере в базовом образе.

mnist_tf_run = client_lib.Job(base_image='registry.aicloud.sbcp.ru/base/horovod-cuda10.0-tf1.15.0',
                              script='/home/jovyan/quick-start/job_launch/tensorflow_mnist_estimator.py',
                              job_desc ="your_job_desc",
                              n_workers=2, n_gpus=4, warm_cache=False)

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

Параметр n_workers определяет количество рабочих узлов кластера; n_gpus — количество GPU на каждом рабочем узле; прогрев кэша не нужен. Количество GPU, выделенных под задачу, равно n_workers * n_gpus. Таким образом, для данной задачи оно составит 8 GPU.

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

job = Job(base_image='registry.aicloud.sbcp.ru/horovod-cuda10.1-tf2.2.0',
         script='/home/jovyan/my-script.py',
         n_workers = 1,
         n_gpus = 16,
         processes_per_worker = 1, # запустить 1 процесс
         warm_cache = False,
         type = 'horovod')

Пример 2. Задача обучения модели на кластере в кастомном образе.

mnist_tf_run = client_lib.Job(base_image=job.new_image, # собранный кастомный образ
                              script='/home/jovyan/quick-start/job_launch/tensorflow_mnist_estimator.py',
                              n_workers=2, n_gpus=4, warm_cache=False)

Для отправки задачи на кластер необходимо вызвать метод submit():

mnist_tf_run.submit()

Должно появиться сообщение вида

Job 'lm-mpi-job-7a7aa4e0-8a8c-46a3-96fa-7116cc392336' created

После запуска задачи обучения есть возможность просматривать ее логи в режиме реального времени. Для этого используется метод logs() библиотеки client_lib. Есть два варианта просмотра логов:

mnist_tf_run.logs() #просмотр логов по задаче

или

client_lib.logs("lm-mpi-job-7a7aa4e0-8a8c-46a3-96fa-7116cc392336") # просмотр логов по названию задачи

Внимание

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

Если вызвать метод logs() сразу после отправки задачи на кластер с помощью метода submit(), логи могут не отображаться сразу, поскольку кластеру нужно время на запуск. При возникновении такой ситуации вызовите метод logs() чуть позднее. Логи по задаче можно скачать с помощью кнопки Выгрузить Кнопка скачивания на вкладке Задачи.

При необходимости задачу обучения можно остановить в любой момент методом kill().

mnist_tf_run.kill()

или так

client_lib.kill('lm-mpi-job-7a7aa4e0-8a8c-46a3-96fa-7116cc392336')

Обратите внимание на то, что остановить задачу также можно, нажав кнопку Остановить на вкладке Задачи. Это действие приведет к потере всех несохраненных результатов обучения (см. Сохранение промежуточных результатов обучения).

Совет

Внутри кода задачи обучения также можно сохранять метрики модели с помощью mlflow. См. подробнее в примере.

Пример 3. Задача кросс-компиляции с использованием образов ML Space.

Скомпилированные бинарные файлы возможно собирать через openmpi, с помощью библиотеки client_lib. Для этого используется код, который позволяет запускать задачу:

import client_lib

binary_run = client_lib.Job(
base_image="registry.aicloud.sbcp.ru/horovod-cuda10.1-tf2.2.0",
script='/home/jovyan/NSC/mpi_omp_test',
 n_workers = 2,
 n_gpus = 16,
 type = 'binary'
 )

binary_run.submit()

binary_run.logs()

binary_run.kill()

Можно собрать бинарный файл mm из примера с GitHub. Для этого используется код:

git clone https://github.com/hpc/MPI-Examples.git
cd ~/MPI-Examples/matrix-multipy
make linux
./test.sh

Обучение моделей на большом количестве GPU

Количество GPU на каждом рабочем узле кластера задается при запуске задачи обучения с помощью параметра n_gpus (см. Пример 1 выше). Количество рабочих узлов кластера определяется параметром n_workers. Максимальное количество GPU на одном рабочем узле кластера — 16. Если пользователь указал, что для обучения ему необходимы 1 рабочий узел кластера на 16 GPU, под задачу аллоцируется один DGX. Чем больше GPU требуется пользователю для обучения модели, тем выше вероятность возникновения нехватки вычислительных ресурсов. На практике эта ситуация возникает при заданном количестве GPU от 64 до 128. В результате планировщик (scheduler) ставит задачу в очередь исполнения, и она может достаточно долго находиться в статусе «pending».

Возможный обходной путь в этой ситуации — увеличить количество рабочих узлов кластера, уменьшив при этом количество GPU. Например, если пользователь укажет n_workers=64, n_gpus=1, вероятность нахождения свободных ресурсов в этом случае гораздо выше, чем для n_workers=1, n_gpus=64. Задачу на 64 GPU можно разбить следующим образом: n_workers=4, n_gpus=16; n_workers=8, n_gpus=8; n_workers=16, n_gpus=4; n_workers=32, n_gpus=2; n_workers=64, n_gpus=1.

Обратите внимание на то, что увеличение количества рабочих узлов кластера может повлиять на скорость обмена данными, поскольку внутри одного DGX она выше, чем между несколькими DGX. Кроме того, может уменьшиться объем оперативной памяти. Подробнее см. в разделе Использование ресурсов.

В контексте уменьшения объема оперативной памяти необходимо упомянуть о таких случаях, как превышение исходными данными объема оперативной памяти, выделенной для обучения. Например, датасет разбит на множество csv-файлов, метаинформация о которых содержится в одном файле верхнего уровня. Память заканчивается при попытке «склеить» данные в один файл. Или еще одна ситуация, когда датасет включает несколько миллионов изображений, а пути к этим изображениям передаются, как списки (list).

Для всех этих случаев есть общая рекомендация — постарайтесь обращаться к данным оптимально. Файлы можно зачитывать по частям, элементы списка можно заменить на массивы numpy и т.д.

Использование обученной модели в рамках сервиса Deployments

Для дальнейшего использования модели в рамках сервиса Deployments ее необходимо сериализовать. Для этого можно использовать встроенные средства фреймворка, сохраняя контрольные точки обучения в формате bin, h5, pth и др. Также модели можно сериализовать в формат h5 средствами библиотеки h5py или в формат pickle средствами библиотеки pickle. Ниже приведены примеры, как это сделать.

При сериализации модели с помощью pickle последовательность действий следующая:

#import module
import pickle

#Train the data, for example:
model.fit(X_train, X_test)

#Dump the model
with open('fitted_model.pickle','wb') as modelFile:
pickle.dump(model,modelFile)

Сохранение модели Keras в формат h5

# save model
model.save('final_model.h5')

#load model
model = load_model('final_model.h5')

Подробнее см. в документации TensorFlow и Keras.